| Index: checkout.py
|
| ===================================================================
|
| --- checkout.py (revision 220987)
|
| +++ checkout.py (working copy)
|
| @@ -550,16 +550,21 @@
|
| return len([l for l in out.splitlines() if l.startswith('r')]) - 1
|
|
|
|
|
| -class GitCheckoutBase(CheckoutBase):
|
| - """Base class for git checkout. Not to be used as-is."""
|
| - def __init__(self, root_dir, project_name, remote_branch,
|
| - post_processors=None):
|
| - super(GitCheckoutBase, self).__init__(
|
| - root_dir, project_name, post_processors)
|
| - # There is no reason to not hardcode it.
|
| - self.remote = 'origin'
|
| +class GitCheckout(CheckoutBase):
|
| + """Manages a git checkout."""
|
| + def __init__(self, root_dir, project_name, remote_branch, git_url,
|
| + commit_user, post_processors=None):
|
| + assert git_url
|
| + assert commit_user
|
| + super(GitCheckout, self).__init__(root_dir, project_name, post_processors)
|
| + self.git_url = git_url
|
| + self.commit_user = commit_user
|
| self.remote_branch = remote_branch
|
| + # The working branch where patches will be applied. It will track the
|
| + # remote branch.
|
| self.working_branch = 'working_branch'
|
| + # There is no reason to not hardcode origin.
|
| + self.remote = 'origin'
|
|
|
| def prepare(self, revision):
|
| """Resets the git repository in a clean state.
|
| @@ -567,10 +572,25 @@
|
| Checks it out if not present and deletes the working branch.
|
| """
|
| assert self.remote_branch
|
| - assert os.path.isdir(self.project_path)
|
| - self._check_call_git(['reset', '--hard', '--quiet'])
|
| +
|
| + if not os.path.isdir(self.project_path):
|
| + # Clone the repo if the directory is not present.
|
| + logging.info(
|
| + 'Checking out %s in %s', self.project_name, self.project_path)
|
| + self._check_call_git(
|
| + ['clone', self.git_url, '-b', self.remote_branch, self.project_path],
|
| + cwd=None, timeout=FETCH_TIMEOUT)
|
| + else:
|
| + # Throw away all uncommitted changes in the existing checkout.
|
| + self._check_call_git(['checkout', self.remote_branch])
|
| + self._check_call_git(
|
| + ['reset', '--hard', '--quiet',
|
| + '%s/%s' % (self.remote, self.remote_branch)])
|
| +
|
| if revision:
|
| try:
|
| + # Look if the commit hash already exist. If so, we can skip a
|
| + # 'git fetch' call.
|
| revision = self._check_output_git(['rev-parse', revision])
|
| except subprocess.CalledProcessError:
|
| self._check_call_git(
|
| @@ -581,12 +601,32 @@
|
| branches, active = self._branches()
|
| if active != 'master':
|
| self._check_call_git(['checkout', '--force', '--quiet', 'master'])
|
| - self._check_call_git(['pull', self.remote, self.remote_branch, '--quiet'])
|
| + self._sync_remote_branch()
|
| +
|
| if self.working_branch in branches:
|
| self._call_git(['branch', '-D', self.working_branch])
|
| + return self._get_head_commit_hash()
|
|
|
| + def _sync_remote_branch(self):
|
| + """Syncs the remote branch."""
|
| + # We do a 'git pull origin master:refs/remotes/origin/master' instead of
|
| + # 'git pull origin master' because from the manpage for git-pull:
|
| + # A parameter <ref> without a colon is equivalent to <ref>: when
|
| + # pulling/fetching, so it merges <ref> into the current branch without
|
| + # storing the remote branch anywhere locally.
|
| + remote_tracked_path = 'refs/remotes/%s/%s' % (
|
| + self.remote, self.remote_branch)
|
| + self._check_call_git(
|
| + ['pull', self.remote,
|
| + '%s:%s' % (self.remote_branch, remote_tracked_path),
|
| + '--quiet'])
|
| +
|
| + def _get_head_commit_hash(self):
|
| + """Gets the current revision from the local branch."""
|
| + return self._check_output_git(['rev-parse', 'HEAD']).strip()
|
| +
|
| def apply_patch(self, patches, post_processors=None, verbose=False):
|
| - """Applies a patch on 'working_branch' and switch to it.
|
| + """Applies a patch on 'working_branch' and switches to it.
|
|
|
| Also commits the changes on the local branch.
|
|
|
| @@ -597,8 +637,9 @@
|
| # trying again?
|
| if self.remote_branch:
|
| self._check_call_git(
|
| - ['checkout', '-b', self.working_branch,
|
| - '%s/%s' % (self.remote, self.remote_branch), '--quiet'])
|
| + ['checkout', '-b', self.working_branch, '-t', self.remote_branch,
|
| + '--quiet'])
|
| +
|
| for index, p in enumerate(patches):
|
| stdout = []
|
| try:
|
| @@ -667,21 +708,43 @@
|
| if verbose:
|
| cmd.append('--verbose')
|
| self._check_call_git(cmd)
|
| - # TODO(maruel): Weirdly enough they don't match, need to investigate.
|
| - #found_files = self._check_output_git(
|
| - # ['diff', 'master', '--name-only']).splitlines(False)
|
| - #assert sorted(patches.filenames) == sorted(found_files), (
|
| - # sorted(out), sorted(found_files))
|
| + found_files = self._check_output_git(
|
| + ['diff', '%s/%s' % (self.remote, self.remote_branch),
|
| + '--name-only']).splitlines(False)
|
| + assert sorted(patches.filenames) == sorted(found_files), (
|
| + sorted(patches.filenames), sorted(found_files))
|
|
|
| def commit(self, commit_message, user):
|
| - """Updates the commit message.
|
| -
|
| - Subclass needs to dcommit or push.
|
| - """
|
| + """Commits, updates the commit message and pushes."""
|
| assert isinstance(commit_message, unicode)
|
| - self._check_call_git(['commit', '--amend', '-m', commit_message])
|
| - return self._check_output_git(['rev-parse', 'HEAD']).strip()
|
| + current_branch = self._check_output_git(
|
| + ['rev-parse', '--abbrev-ref', 'HEAD']).strip()
|
| + assert current_branch == self.working_branch
|
| +
|
| + commit_cmd = ['commit', '--amend', '-m', commit_message]
|
| + if user and user != self.commit_user:
|
| + # We do not have the first or last name of the user, grab the username
|
| + # from the email and call it the original author's name.
|
| + # TODO(rmistry): Do not need the below if user is already in
|
| + # "Name <email>" format.
|
| + name = user.split('@')[0]
|
| + commit_cmd.extend(['--author', '%s <%s>' % (name, user)])
|
| + self._check_call_git(commit_cmd)
|
|
|
| + # Push to the remote repository.
|
| + self._check_call_git(
|
| + ['push', 'origin', '%s:%s' % (self.working_branch, self.remote_branch),
|
| + '--force', '--quiet'])
|
| + # Get the revision after the push.
|
| + revision = self._get_head_commit_hash()
|
| + # Switch back to the remote_branch and sync it.
|
| + self._check_call_git(['checkout', self.remote_branch])
|
| + self._sync_remote_branch()
|
| + # Delete the working branch since we are done with it.
|
| + self._check_call_git(['branch', '-D', self.working_branch])
|
| +
|
| + return revision
|
| +
|
| def _check_call_git(self, args, **kwargs):
|
| kwargs.setdefault('cwd', self.project_path)
|
| kwargs.setdefault('stdout', self.VOID)
|
| @@ -727,13 +790,7 @@
|
|
|
| def _fetch_remote(self):
|
| """Fetches the remote without rebasing."""
|
| - raise NotImplementedError()
|
| -
|
| -
|
| -class GitCheckout(GitCheckoutBase):
|
| - """Git checkout implementation."""
|
| - def _fetch_remote(self):
|
| - # git fetch is always verbose even with -q -q so redirect its output.
|
| + # git fetch is always verbose even with -q, so redirect its output.
|
| self._check_output_git(['fetch', self.remote, self.remote_branch],
|
| timeout=FETCH_TIMEOUT)
|
|
|
|
|