diff --git a/giftwrap/openstack_commit.py b/giftwrap/openstack_commit.py new file mode 100644 index 0000000..0feacc8 --- /dev/null +++ b/giftwrap/openstack_commit.py @@ -0,0 +1,140 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2014, Craig Tracey +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations + +import logging +import os +import re +import yaml + +from giftwrap.gerrit import GerritReview + +LOG = logging.getLogger(__name__) + + +class OpenstackCommit(object): + + def __init__(self, commit, project, branch, meta_cache_dir=None): + self.commit = commit + self.project = project + self.branch = branch + self._change_id = None + self._editable_dependencies = None + self._pip_dependencies = None + self._is_merge = None + self._parent = None + self._gerrit_review = None + self._meta_cache_dir = meta_cache_dir + + @property + def hexsha(self): + return self.commit.hexsha + + @property + def change_id(self): + if not self._change_id: + self._change_id = str(self._get_change_id()) + return self._change_id + + @property + def is_merge(self): + if self._is_merge is None: + self._is_merge = (len(self.commit.parents) == 2) + return self._is_merge + + @property + def parent(self): + if self.is_merge: + self._parent = OpenstackCommit(self.commit.parents[1], + self.project, self.branch) + return self._parent + + @property + def gerrit_review(self): + if not self._gerrit_review: + self._gerrit_review = GerritReview(self.change_id, + self.project, self.branch) + return self._gerrit_review + + def _gather_dependencies(self): + try: + deps = self.gerrit_review.build_pip_dependencies() + self._editable_dependencies = [] + self._pip_dependencies = {} + for dep in deps: + if '-e' in dep: + self._editable_dependencies.append(dep) + else: + parts = dep.split('==') + self._pip_dependencies[parts[0]] = parts[1] + except Exception as e: + LOG.debug("Couldn't find dependencies for %s: %s", self.hexsha, e) + + @property + def pip_dependencies(self): + if not self._pip_dependencies: + self._gather_dependencies() + return self._pip_dependencies + + @property + def editable_dependencies(self): + if not self._editable_dependencies: + self._gather_dependencies() + return self._editable_dependencies + + def _get_change_id(self): + commit = self.commit + if self.is_merge: + commit = self.parent.commit + match = re.search('Change-Id:\s*(I\w+)', commit.message) + if match: + return match.group(1) + + def is_cached(self): + return os.path.isfile(self.cache_file) + + @property + def cache_file(self): + return os.path.join(self._meta_cache_dir, self.hexsha) + + def _get_from_cache(self, key): + if self.is_cached(): + with open(self.cache_file, 'r') as fh: + cached_data = yaml.load(fh) + if key in cached_data: + return cached_data[key] + return None + + def is_cacheable(self): + if self.pip_dependencies or self.editable_dependencies: + return True + return False + + def __dict__(self): + data = {} + data['pip_dependencies'] = self.pip_dependencies + data['editable_dependencies'] = self.editable_dependencies + data['change_id'] = self.change_id + return data + + def persist_to_cache(self): + if not self.is_cacheable(): + LOG.debug("Not caching %s as there is no point", self.hexsha) + return + dirname = os.path.dirname(self.cache_file) + if not os.path.exists(dirname): + os.makedirs(dirname) + with open(self.cache_file, 'w') as fh: + fh.write(yaml.dump(self.__dict__())) diff --git a/giftwrap/openstack_git_repo.py b/giftwrap/openstack_git_repo.py index fa2536f..f8d0408 100644 --- a/giftwrap/openstack_git_repo.py +++ b/giftwrap/openstack_git_repo.py @@ -16,22 +16,27 @@ import datetime import logging +import os import re import time +import urlparse +from giftwrap.openstack_commit import OpenstackCommit from git import Repo LOG = logging.getLogger(__name__) class OpenstackGitRepo(object): - def __init__(self, url, ref='master'): + + def __init__(self, url, project=None, branch='master', + metadata_cache_dir=None): self.url = url - self.ref = ref + self._project = project + self.branch = branch self._repo = None - self._head = None - self._change_id = None - self._committed_date = None + self._metadata_cache_dir = metadata_cache_dir + self._head_commit = None @property def cloned(self): @@ -39,50 +44,79 @@ class OpenstackGitRepo(object): @property def head(self): - if not self._head and self._repo: - self._head = self._repo.head.commit.hexsha - return self._head + if not self._head_commit and self._repo: + self._head_commit = OpenstackCommit(self._repo.head.commit, + self.project, self.branch, + self._cache_dir()) + return self._head_commit @property - def change_id(self): - if not self._change_id and self._repo: - for commit in self._repo.iter_commits(): - match = re.search('Change-Id:\s*(I\w+)', commit.message) - if match: - self._change_id = match.group(1) - break - return self._change_id - - @property - def committed_date(self): - if not self._committed_date and self._repo: - self._committed_date = self._repo.head.commit.committed_date - return self._committed_date - - def _invalidate_attrs(self): - self._head = None - self._change_id = None - self._committed_date = None + def project(self): + if not self._project: + parsed_url = urlparse.urlparse(self.url) + project = os.path.splitext(parsed_url.path)[0] + self._project = re.sub(r'^/', '', project) + return self._project def clone(self, outdir): - LOG.info("Cloning '%s' to '%s'", self.url, outdir) - self._repo = Repo.clone_from(self.url, outdir) + LOG.debug("Cloning '%s' to '%s'", self.url, outdir) + self._repo = Repo.clone_from(self.url, outdir, recursive=True) git = self._repo.git - git.checkout(self.ref) + git.checkout(self.branch) self._invalidate_attrs() + def checkout_branch(self, branch, update=True): + if not self._repo: + raise Exception("Cannot checkout on non-existent repo") + LOG.debug("Checking out branch: %s (update: %s)", branch, update) + self._repo.git.checkout(branch) + self._invalidate_attrs() + self.branch = branch + if update: + self._repo.git.pull('origin', branch) + + @property + def branches(self): + branches = [] + for ref in self._repo.remotes.origin.refs: + branches.append(re.sub('^\w*/', '', ref.name)) + return branches + + def __iter__(self): + if not self._repo: + raise Exception("iterator called before clone") + self._commit_iterator = self._repo.iter_commits() + return self + + def next(self): + print self._cache_dir() + return OpenstackCommit(next(self._commit_iterator), + self.project, self.branch, + self._cache_dir()) + + def _cache_dir(self): + if self._metadata_cache_dir: + return os.path.join(self._metadata_cache_dir, + self.project, self.branch) + return None + + def _invalidate_attrs(self): + self._head_commit = None + self._commit_iterator = None + def reset_to_date(self, date): if self._repo: commit_date_sha = None for commit in self._repo.iter_commits(): if commit.committed_date >= date: - commit_date_sha = commit.hexsha + continue elif commit.committed_date < date: + commit_date_sha = commit.hexsha break if not commit_date_sha: raise Exception("Unable to find commit for date %s", datetime.datetime.fromtimestamp(date)) git = self._repo.git - LOG.info("Reset repo '%s' to commit at '%s'", self.url, - time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(date))) + LOG.debug("Reset repo '%s' to commit at '%s'", self.url, + time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(date))) git.checkout(commit_date_sha)