Commit 6a4e31b4 authored by Edward Lemur's avatar Edward Lemur Committed by Commit Bot

gclient_scm: Add option to specify the branch the patch is based on.

Also add shortcuts, so we don't have to look through all the branches
if the patch is based on a common branch (i.e. master, infra/config, lkgr)


Bug: 870279
Change-Id: I625a8481dccac9a475b096b926e6fab7efe676b0
Reviewed-on: https://chromium-review.googlesource.com/1161094
Commit-Queue: Edward Lesmes <ehmaldonado@chromium.org>
Reviewed-by: 's avatarAndrii Shyshkalov <tandrii@chromium.org>
parent 284b5e0c
...@@ -848,7 +848,7 @@ class Dependency(gclient_utils.WorkItem, DependencySettings): ...@@ -848,7 +848,7 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
# Arguments number differs from overridden method # Arguments number differs from overridden method
# pylint: disable=arguments-differ # pylint: disable=arguments-differ
def run(self, revision_overrides, command, args, work_queue, options, def run(self, revision_overrides, command, args, work_queue, options,
patch_refs): patch_refs, target_branches):
"""Runs |command| then parse the DEPS file.""" """Runs |command| then parse the DEPS file."""
logging.info('Dependency(%s).run()' % self.name) logging.info('Dependency(%s).run()' % self.name)
assert self._file_list == [] assert self._file_list == []
...@@ -875,9 +875,11 @@ class Dependency(gclient_utils.WorkItem, DependencySettings): ...@@ -875,9 +875,11 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
patch_repo = self.url.split('@')[0] patch_repo = self.url.split('@')[0]
patch_ref = patch_refs.pop(self.FuzzyMatchUrl(patch_refs), None) patch_ref = patch_refs.pop(self.FuzzyMatchUrl(patch_refs), None)
target_branch = target_branches.pop(
self.FuzzyMatchUrl(target_branches), None)
if command == 'update' and patch_ref is not None: if command == 'update' and patch_ref is not None:
self._used_scm.apply_patch_ref(patch_repo, patch_ref, options, self._used_scm.apply_patch_ref(patch_repo, patch_ref, target_branch,
file_list) options, file_list)
if file_list: if file_list:
file_list = [os.path.join(self.name, f.strip()) for f in file_list] file_list = [os.path.join(self.name, f.strip()) for f in file_list]
...@@ -1520,19 +1522,23 @@ it or fix the checkout. ...@@ -1520,19 +1522,23 @@ it or fix the checkout.
index += 1 index += 1
return revision_overrides return revision_overrides
def _EnforcePatchRefs(self): def _EnforcePatchRefsAndBranches(self):
"""Checks for patch refs.""" """Checks for patch refs."""
patch_refs = {} patch_refs = {}
target_branches = {}
if not self._options.patch_refs: if not self._options.patch_refs:
return patch_refs return patch_refs, target_branches
for given_patch_ref in self._options.patch_refs: for given_patch_ref in self._options.patch_refs:
patch_repo, _, patch_ref = given_patch_ref.partition('@') patch_repo, _, patch_ref = given_patch_ref.partition('@')
if not patch_repo or not patch_ref: if not patch_repo or not patch_ref:
raise gclient_utils.Error( raise gclient_utils.Error(
'Wrong revision format: %s should be of the form ' 'Wrong revision format: %s should be of the form '
'patch_repo@patch_ref.' % given_patch_ref) 'patch_repo@[target_branch:]patch_ref.' % given_patch_ref)
if ':' in patch_ref:
target_branch, _, patch_ref = patch_ref.partition(':')
target_branches[patch_repo] = target_branch
patch_refs[patch_repo] = patch_ref patch_refs[patch_repo] = patch_ref
return patch_refs return patch_refs, target_branches
def RunOnDeps(self, command, args, ignore_requirements=False, progress=True): def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
"""Runs a command on each dependency in a client and its dependencies. """Runs a command on each dependency in a client and its dependencies.
...@@ -1546,6 +1552,7 @@ it or fix the checkout. ...@@ -1546,6 +1552,7 @@ it or fix the checkout.
revision_overrides = {} revision_overrides = {}
patch_refs = {} patch_refs = {}
target_branches = {}
# It's unnecessary to check for revision overrides for 'recurse'. # It's unnecessary to check for revision overrides for 'recurse'.
# Save a few seconds by not calling _EnforceRevisions() in that case. # Save a few seconds by not calling _EnforceRevisions() in that case.
if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert', if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
...@@ -1554,7 +1561,7 @@ it or fix the checkout. ...@@ -1554,7 +1561,7 @@ it or fix the checkout.
revision_overrides = self._EnforceRevisions() revision_overrides = self._EnforceRevisions()
if command == 'update': if command == 'update':
patch_refs = self._EnforcePatchRefs() patch_refs, target_branches = self._EnforcePatchRefsAndBranches()
# Disable progress for non-tty stdout. # Disable progress for non-tty stdout.
should_show_progress = ( should_show_progress = (
setup_color.IS_TTY and not self._options.verbose and progress) setup_color.IS_TTY and not self._options.verbose and progress)
...@@ -1571,7 +1578,7 @@ it or fix the checkout. ...@@ -1571,7 +1578,7 @@ it or fix the checkout.
if s.should_process: if s.should_process:
work_queue.enqueue(s) work_queue.enqueue(s)
work_queue.flush(revision_overrides, command, args, options=self._options, work_queue.flush(revision_overrides, command, args, options=self._options,
patch_refs=patch_refs) patch_refs=patch_refs, target_branches=target_branches)
if revision_overrides: if revision_overrides:
print('Please fix your script, having invalid --revision flags will soon ' print('Please fix your script, having invalid --revision flags will soon '
...@@ -1705,7 +1712,8 @@ it or fix the checkout. ...@@ -1705,7 +1712,8 @@ it or fix the checkout.
for s in self.dependencies: for s in self.dependencies:
if s.should_process: if s.should_process:
work_queue.enqueue(s) work_queue.enqueue(s)
work_queue.flush({}, None, [], options=self._options, patch_refs=None) work_queue.flush({}, None, [], options=self._options, patch_refs=None,
target_branches=None)
def ShouldPrintRevision(dep): def ShouldPrintRevision(dep):
return (not self._options.filter return (not self._options.filter
...@@ -1845,14 +1853,15 @@ class CipdDependency(Dependency): ...@@ -1845,14 +1853,15 @@ class CipdDependency(Dependency):
#override #override
def run(self, revision_overrides, command, args, work_queue, options, def run(self, revision_overrides, command, args, work_queue, options,
patch_refs): patch_refs, target_branches):
"""Runs |command| then parse the DEPS file.""" """Runs |command| then parse the DEPS file."""
logging.info('CipdDependency(%s).run()' % self.name) logging.info('CipdDependency(%s).run()' % self.name)
if not self.should_process: if not self.should_process:
return return
self._CreatePackageIfNecessary() self._CreatePackageIfNecessary()
super(CipdDependency, self).run(revision_overrides, command, args, super(CipdDependency, self).run(revision_overrides, command, args,
work_queue, options, patch_refs) work_queue, options, patch_refs,
target_branches)
def _CreatePackageIfNecessary(self): def _CreatePackageIfNecessary(self):
# We lazily create the CIPD package to make sure that only packages # We lazily create the CIPD package to make sure that only packages
...@@ -2520,13 +2529,21 @@ def CMDsync(parser, args): ...@@ -2520,13 +2529,21 @@ def CMDsync(parser, args):
'work even if the src@ part is skipped.') 'work even if the src@ part is skipped.')
parser.add_option('--patch-ref', action='append', parser.add_option('--patch-ref', action='append',
dest='patch_refs', metavar='GERRIT_REF', default=[], dest='patch_refs', metavar='GERRIT_REF', default=[],
help='Patches the given reference with the format dep@ref. ' help='Patches the given reference with the format '
'For dep, you can specify URLs as well as paths, with ' 'dep@[target-ref:]patch-ref. '
'URLs taking preference. The reference will be ' 'For |dep|, you can specify URLs as well as paths, '
'applied to the necessary path, will be rebased on ' 'with URLs taking preference. '
'top what the dep was synced to, and then will do a ' '|patch-ref| will be applied to |dep|, rebased on top '
'soft reset. Use --no-rebase-patch-ref and ' 'of what |dep| was synced to, and a soft reset will '
'--reset-patch-ref to disable this behavior.') 'be done. Use --no-rebase-patch-ref and '
'--no-reset-patch-ref to disable this behavior. '
'|target-ref| is the target branch against which a '
'patch was created, it is used to determine which '
'commits from the |patch-ref| actually constitute a '
'patch. If not given, we will iterate over all remote '
'branches and select one that contains the revision '
'|dep| is synced at. '
'WARNING: |target-ref| will be mandatory soon.')
parser.add_option('--with_branch_heads', action='store_true', parser.add_option('--with_branch_heads', action='store_true',
help='Clone git "branch_heads" refspecs in addition to ' help='Clone git "branch_heads" refspecs in addition to '
'the default refspecs. This adds about 1/2GB to a ' 'the default refspecs. This adds about 1/2GB to a '
......
...@@ -92,7 +92,6 @@ class SCMWrapper(object): ...@@ -92,7 +92,6 @@ class SCMWrapper(object):
This is the abstraction layer to bind to different SCM. This is the abstraction layer to bind to different SCM.
""" """
def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None, def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
out_cb=None, print_outbuf=False): out_cb=None, print_outbuf=False):
self.url = url self.url = url
...@@ -345,15 +344,22 @@ class GitWrapper(SCMWrapper): ...@@ -345,15 +344,22 @@ class GitWrapper(SCMWrapper):
self.Print('FAILED to break lock: %s: %s' % (to_break, ex)) self.Print('FAILED to break lock: %s: %s' % (to_break, ex))
raise raise
def _GetBranchForCommit(self, commit): # TODO(ehmaldonado): Remove after bot_update is modified to pass the patch's
# branch.
def _GetTargetBranchForCommit(self, commit):
"""Get the remote branch a commit is part of.""" """Get the remote branch a commit is part of."""
if scm.GIT.IsAncestor(self.checkout_path, commit, _WELL_KNOWN_BRANCHES = [
'refs/remotes/origin/master'): 'refs/remotes/origin/master',
return 'refs/remotes/origin/master' 'refs/remotes/origin/infra/config',
'refs/remotes/origin/lkgr',
]
for branch in _WELL_KNOWN_BRANCHES:
if scm.GIT.IsAncestor(self.checkout_path, commit, branch):
return branch
remote_refs = self._Capture( remote_refs = self._Capture(
['for-each-ref', 'refs/remotes/%s/*' % self.remote, ['for-each-ref', 'refs/remotes/%s' % self.remote,
'--format=%(refname)']).splitlines() '--format=%(refname)']).splitlines()
for ref in remote_refs: for ref in sorted(remote_refs, reverse=True):
if scm.GIT.IsAncestor(self.checkout_path, commit, ref): if scm.GIT.IsAncestor(self.checkout_path, commit, ref):
return ref return ref
self.Print('Failed to find a remote ref that contains %s. ' self.Print('Failed to find a remote ref that contains %s. '
...@@ -364,7 +370,42 @@ class GitWrapper(SCMWrapper): ...@@ -364,7 +370,42 @@ class GitWrapper(SCMWrapper):
# everything in between. # everything in between.
return commit return commit
def apply_patch_ref(self, patch_repo, patch_ref, options, file_list): def apply_patch_ref(self, patch_repo, patch_ref, target_branch, options,
file_list):
"""Apply a patch on top of the revision we're synced at.
The patch ref is given by |patch_repo|@|patch_ref|, and the current revision
is |base_rev|.
We also need the |target_branch| that the patch was uploaded against. We use
it to find a merge base between |patch_rev| and |base_rev|, so we can find
what commits constitute the patch:
Graphically, it looks like this:
... -> merge_base -> [possibly already landed commits] -> target_branch
\
-> [possibly not yet landed dependent CLs] -> patch_rev
Next, we apply the commits |merge_base..patch_rev| on top of whatever is
currently checked out, denoted |base_rev|. Typically, it'd be a revision
from |target_branch|, but this is not required.
Graphically, we cherry pick |merge_base..patch_rev| on top of |base_rev|:
... -> base_rev -> [possibly not yet landed dependent CLs] -> patch_rev
After application, if |options.reset_patch_ref| is specified, we soft reset
the just cherry-picked changes, keeping them in git index only.
Args:
patch_repo: The patch origin. e.g. 'https://foo.googlesource.com/bar'
patch_ref: The ref to the patch. e.g. 'refs/changes/1234/34/1'.
target_branch: The branch the patch was uploaded against.
e.g. 'refs/heads/master' or 'refs/heads/infra/config'.
options: The options passed to gclient.
file_list: A list where modified files will be appended.
"""
# Abort any cherry-picks in progress. # Abort any cherry-picks in progress.
try: try:
self._Capture(['cherry-pick', '--abort']) self._Capture(['cherry-pick', '--abort'])
...@@ -372,10 +413,12 @@ class GitWrapper(SCMWrapper): ...@@ -372,10 +413,12 @@ class GitWrapper(SCMWrapper):
pass pass
base_rev = self._Capture(['rev-parse', 'HEAD']) base_rev = self._Capture(['rev-parse', 'HEAD'])
branch_rev = self._GetBranchForCommit(base_rev) target_branch = target_branch or self._GetTargetBranchForCommit(base_rev)
self.Print('===Applying patch ref===') self.Print('===Applying patch ref===')
self.Print('Repo is %r @ %r, ref is %r (%r), root is %r' % ( self.Print('Patch ref is %r @ %r. Target branch for patch is %r. '
patch_repo, patch_ref, base_rev, branch_rev, self.checkout_path)) 'Current HEAD is %r. Current dir is %r' % (
patch_repo, patch_ref, target_branch, base_rev,
self.checkout_path))
self._Capture(['reset', '--hard']) self._Capture(['reset', '--hard'])
self._Capture(['fetch', patch_repo, patch_ref]) self._Capture(['fetch', patch_repo, patch_ref])
patch_rev = self._Capture(['rev-parse', 'FETCH_HEAD']) patch_rev = self._Capture(['rev-parse', 'FETCH_HEAD'])
...@@ -386,9 +429,9 @@ class GitWrapper(SCMWrapper): ...@@ -386,9 +429,9 @@ class GitWrapper(SCMWrapper):
else: else:
# Find the merge-base between the branch_rev and patch_rev to find out # Find the merge-base between the branch_rev and patch_rev to find out
# the changes we need to cherry-pick on top of base_rev. # the changes we need to cherry-pick on top of base_rev.
merge_base = self._Capture(['merge-base', branch_rev, patch_rev]) merge_base = self._Capture(['merge-base', target_branch, patch_rev])
self.Print('Merge base of %s and %s is %s' % ( self.Print('Merge base of %s and %s is %s' % (
branch_rev, patch_rev, merge_base)) target_branch, patch_rev, merge_base))
if merge_base == patch_rev: if merge_base == patch_rev:
# If the merge-base is patch_rev, it means patch_rev is already part # If the merge-base is patch_rev, it means patch_rev is already part
# of the history, so just check it out. # of the history, so just check it out.
...@@ -406,8 +449,11 @@ class GitWrapper(SCMWrapper): ...@@ -406,8 +449,11 @@ class GitWrapper(SCMWrapper):
file_list.extend(self._GetDiffFilenames(base_rev)) file_list.extend(self._GetDiffFilenames(base_rev))
except subprocess2.CalledProcessError as e: except subprocess2.CalledProcessError as e:
self.Print('Failed to apply %r @ %r to %r at %r' % ( self.Print('Failed to apply patch.')
patch_repo, patch_ref, base_rev, self.checkout_path)) self.Print('Patch ref is %r @ %r. Target branch for patch is %r. '
'Current HEAD is %r. Current dir is %r' % (
patch_repo, patch_ref, target_branch, base_rev,
self.checkout_path))
self.Print('git returned non-zero exit status %s:\n%s' % ( self.Print('git returned non-zero exit status %s:\n%s' % (
e.returncode, e.stderr)) e.returncode, e.stderr))
# Print the current status so that developers know what changes caused the # Print the current status so that developers know what changes caused the
......
...@@ -55,9 +55,6 @@ COMMIT_ORIGINAL_POSITION_FOOTER_KEY = 'Cr-Original-Commit-Position' ...@@ -55,9 +55,6 @@ COMMIT_ORIGINAL_POSITION_FOOTER_KEY = 'Cr-Original-Commit-Position'
# Regular expression to parse gclient's revinfo entries. # Regular expression to parse gclient's revinfo entries.
REVINFO_RE = re.compile(r'^([^:]+):\s+([^@]+)@(.+)$') REVINFO_RE = re.compile(r'^([^:]+):\s+([^@]+)@(.+)$')
# Regular expression to match gclient's patch error message.
PATCH_ERROR_RE = re.compile('Failed to apply .* @ .* to .* at .*')
# Copied from scripts/recipes/chromium.py. # Copied from scripts/recipes/chromium.py.
GOT_REVISION_MAPPINGS = { GOT_REVISION_MAPPINGS = {
CHROMIUM_SRC_URL: { CHROMIUM_SRC_URL: {
...@@ -385,7 +382,7 @@ def gclient_sync( ...@@ -385,7 +382,7 @@ def gclient_sync(
except SubprocessFailed as e: except SubprocessFailed as e:
# If gclient sync is handling patching, parse the output for a patch error # If gclient sync is handling patching, parse the output for a patch error
# message. # message.
if apply_patch_on_gclient and PATCH_ERROR_RE.search(e.output): if apply_patch_on_gclient and 'Failed to apply patch.' in e.output:
raise PatchFailed(e.message, e.code, e.output) raise PatchFailed(e.message, e.code, e.output)
# Throw a GclientSyncFailed exception so we can catch this independently. # Throw a GclientSyncFailed exception so we can catch this independently.
raise GclientSyncFailed(e.message, e.code, e.output) raise GclientSyncFailed(e.message, e.code, e.output)
......
...@@ -1088,12 +1088,12 @@ class GerritChangesTest(fake_repos.FakeReposTestBase): ...@@ -1088,12 +1088,12 @@ class GerritChangesTest(fake_repos.FakeReposTestBase):
""" """
for i in commits: for i in commits:
name = os.path.join(self.root_dir, 'commit ' + str(i)) name = os.path.join(self.root_dir, 'commit ' + str(i))
self.assertTrue(os.path.exists(name)) self.assertTrue(os.path.exists(name), 'Commit not found: %s' % name)
all_commits = set(range(1, len(self.FAKE_REPOS.git_hashes['repo_1']))) all_commits = set(range(1, len(self.FAKE_REPOS.git_hashes['repo_1'])))
for i in all_commits - set(commits): for i in all_commits - set(commits):
name = os.path.join(self.root_dir, 'commit ' + str(i)) name = os.path.join(self.root_dir, 'commit ' + str(i))
self.assertFalse(os.path.exists(name)) self.assertFalse(os.path.exists(name), 'Unexpected commit: %s' % name)
def testCanCloneGerritChange(self): def testCanCloneGerritChange(self):
scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.') scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
...@@ -1133,7 +1133,7 @@ class GerritChangesTest(fake_repos.FakeReposTestBase): ...@@ -1133,7 +1133,7 @@ class GerritChangesTest(fake_repos.FakeReposTestBase):
scm.update(self.options, None, file_list) scm.update(self.options, None, file_list)
self.assertEqual(self.githash('repo_1', 4), self.gitrevparse(self.root_dir)) self.assertEqual(self.githash('repo_1', 4), self.gitrevparse(self.root_dir))
scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1', self.options, scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1', None, self.options,
file_list) file_list)
self.assertCommits([1, 2, 3, 4, 5, 6]) self.assertCommits([1, 2, 3, 4, 5, 6])
...@@ -1155,7 +1155,7 @@ class GerritChangesTest(fake_repos.FakeReposTestBase): ...@@ -1155,7 +1155,7 @@ class GerritChangesTest(fake_repos.FakeReposTestBase):
self.assertEqual(self.githash('repo_1', 1), self.gitrevparse(self.root_dir)) self.assertEqual(self.githash('repo_1', 1), self.gitrevparse(self.root_dir))
# Apply the change on top of that. # Apply the change on top of that.
scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1', self.options, scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1', None, self.options,
file_list) file_list)
self.assertCommits([1, 5, 6]) self.assertCommits([1, 5, 6])
...@@ -1172,7 +1172,7 @@ class GerritChangesTest(fake_repos.FakeReposTestBase): ...@@ -1172,7 +1172,7 @@ class GerritChangesTest(fake_repos.FakeReposTestBase):
self.assertEqual(self.githash('repo_1', 9), self.gitrevparse(self.root_dir)) self.assertEqual(self.githash('repo_1', 9), self.gitrevparse(self.root_dir))
# Apply the change on top of that. # Apply the change on top of that.
scm.apply_patch_ref(self.url, 'refs/changes/36/1236/1', self.options, scm.apply_patch_ref(self.url, 'refs/changes/36/1236/1', None, self.options,
file_list) file_list)
self.assertCommits([1, 2, 7, 8, 9, 10]) self.assertCommits([1, 2, 7, 8, 9, 10])
...@@ -1190,7 +1190,7 @@ class GerritChangesTest(fake_repos.FakeReposTestBase): ...@@ -1190,7 +1190,7 @@ class GerritChangesTest(fake_repos.FakeReposTestBase):
self.assertEqual(self.githash('repo_1', 7), self.gitrevparse(self.root_dir)) self.assertEqual(self.githash('repo_1', 7), self.gitrevparse(self.root_dir))
# Apply the change on top of that. # Apply the change on top of that.
scm.apply_patch_ref(self.url, 'refs/changes/36/1236/1', self.options, scm.apply_patch_ref(self.url, 'refs/changes/36/1236/1', None, self.options,
file_list) file_list)
# We shouldn't have rebased on top of 2 (which is the merge base between # We shouldn't have rebased on top of 2 (which is the merge base between
...@@ -1199,6 +1199,24 @@ class GerritChangesTest(fake_repos.FakeReposTestBase): ...@@ -1199,6 +1199,24 @@ class GerritChangesTest(fake_repos.FakeReposTestBase):
self.assertCommits([1, 2, 7, 10]) self.assertCommits([1, 2, 7, 10])
self.assertEqual(self.githash('repo_1', 7), self.gitrevparse(self.root_dir)) self.assertEqual(self.githash('repo_1', 7), self.gitrevparse(self.root_dir))
def testCheckoutOriginFeaturePatchBranch(self):
scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
file_list = []
# Sync to the hash instead of 'origin/feature'
self.options.revision = self.githash('repo_1', 9)
scm.update(self.options, None, file_list)
self.assertEqual(self.githash('repo_1', 9), self.gitrevparse(self.root_dir))
# Apply refs/changes/34/1234/1, created for branch 'origin/master' on top of
# 'origin/feature'.
scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1', 'origin/master',
self.options, file_list)
# Commits 5 and 6 are part of the patch, and commits 1, 2, 7, 8 and 9 are
# part of 'origin/feature'.
self.assertCommits([1, 2, 5, 6, 7, 8, 9])
self.assertEqual(self.githash('repo_1', 9), self.gitrevparse(self.root_dir))
def testDoesntRebasePatchMaster(self): def testDoesntRebasePatchMaster(self):
"""Tests that we can apply a patch without rebasing it. """Tests that we can apply a patch without rebasing it.
...@@ -1211,7 +1229,7 @@ class GerritChangesTest(fake_repos.FakeReposTestBase): ...@@ -1211,7 +1229,7 @@ class GerritChangesTest(fake_repos.FakeReposTestBase):
self.assertEqual(self.githash('repo_1', 4), self.gitrevparse(self.root_dir)) self.assertEqual(self.githash('repo_1', 4), self.gitrevparse(self.root_dir))
# Apply the change on top of that. # Apply the change on top of that.
scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1', self.options, scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1', None, self.options,
file_list) file_list)
self.assertCommits([1, 2, 3, 5, 6]) self.assertCommits([1, 2, 3, 5, 6])
...@@ -1230,7 +1248,7 @@ class GerritChangesTest(fake_repos.FakeReposTestBase): ...@@ -1230,7 +1248,7 @@ class GerritChangesTest(fake_repos.FakeReposTestBase):
self.assertEqual(self.githash('repo_1', 1), self.gitrevparse(self.root_dir)) self.assertEqual(self.githash('repo_1', 1), self.gitrevparse(self.root_dir))
# Apply the change on top of that. # Apply the change on top of that.
scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1', self.options, scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1', None, self.options,
file_list) file_list)
self.assertCommits([1, 2, 3, 5, 6]) self.assertCommits([1, 2, 3, 5, 6])
...@@ -1245,7 +1263,7 @@ class GerritChangesTest(fake_repos.FakeReposTestBase): ...@@ -1245,7 +1263,7 @@ class GerritChangesTest(fake_repos.FakeReposTestBase):
scm.update(self.options, None, file_list) scm.update(self.options, None, file_list)
self.assertEqual(self.githash('repo_1', 4), self.gitrevparse(self.root_dir)) self.assertEqual(self.githash('repo_1', 4), self.gitrevparse(self.root_dir))
scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1', self.options, scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1', None, self.options,
file_list) file_list)
self.assertCommits([1, 2, 3, 4, 5, 6]) self.assertCommits([1, 2, 3, 4, 5, 6])
...@@ -1265,14 +1283,14 @@ class GerritChangesTest(fake_repos.FakeReposTestBase): ...@@ -1265,14 +1283,14 @@ class GerritChangesTest(fake_repos.FakeReposTestBase):
# Checkout 'refs/changes/34/1234/1' modifies the 'change' file, so trying to # Checkout 'refs/changes/34/1234/1' modifies the 'change' file, so trying to
# patch 'refs/changes/36/1236/1' creates a patch failure. # patch 'refs/changes/36/1236/1' creates a patch failure.
with self.assertRaises(subprocess2.CalledProcessError) as cm: with self.assertRaises(subprocess2.CalledProcessError) as cm:
scm.apply_patch_ref(self.url, 'refs/changes/36/1236/1', self.options, scm.apply_patch_ref(self.url, 'refs/changes/36/1236/1', None,
file_list) self.options, file_list)
self.assertEqual(cm.exception.cmd[:2], ['git', 'cherry-pick']) self.assertEqual(cm.exception.cmd[:2], ['git', 'cherry-pick'])
self.assertIn('error: could not apply', cm.exception.stderr) self.assertIn('error: could not apply', cm.exception.stderr)
# Try to apply 'refs/changes/35/1235/1', which doesn't have a merge # Try to apply 'refs/changes/35/1235/1', which doesn't have a merge
# conflict. # conflict.
scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1', self.options, scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1', None, self.options,
file_list) file_list)
self.assertCommits([1, 2, 3, 5, 6]) self.assertCommits([1, 2, 3, 5, 6])
self.assertEqual(self.githash('repo_1', 5), self.gitrevparse(self.root_dir)) self.assertEqual(self.githash('repo_1', 5), self.gitrevparse(self.root_dir))
...@@ -1290,7 +1308,7 @@ class GerritChangesTest(fake_repos.FakeReposTestBase): ...@@ -1290,7 +1308,7 @@ class GerritChangesTest(fake_repos.FakeReposTestBase):
# 'refs/changes/34/1234/1' will be an empty commit, since the changes were # 'refs/changes/34/1234/1' will be an empty commit, since the changes were
# already present in the tree as commit 11. # already present in the tree as commit 11.
# Make sure we deal with this gracefully. # Make sure we deal with this gracefully.
scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1', self.options, scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1', None, self.options,
file_list) file_list)
self.assertCommits([1, 2, 3, 5, 6, 12]) self.assertCommits([1, 2, 3, 5, 6, 12])
self.assertEqual(self.githash('repo_1', 12), self.assertEqual(self.githash('repo_1', 12),
...@@ -1313,7 +1331,7 @@ class GerritChangesTest(fake_repos.FakeReposTestBase): ...@@ -1313,7 +1331,7 @@ class GerritChangesTest(fake_repos.FakeReposTestBase):
# Try to apply 'refs/changes/35/1235/1', which doesn't have a merge # Try to apply 'refs/changes/35/1235/1', which doesn't have a merge
# conflict. # conflict.
scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1', self.options, scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1', None, self.options,
file_list) file_list)
self.assertCommits([1, 2, 3, 5, 6]) self.assertCommits([1, 2, 3, 5, 6])
self.assertEqual(self.githash('repo_1', 5), self.gitrevparse(self.root_dir)) self.assertEqual(self.githash('repo_1', 5), self.gitrevparse(self.root_dir))
......
...@@ -601,6 +601,30 @@ class GClientSmokeGIT(GClientSmokeBase): ...@@ -601,6 +601,30 @@ class GClientSmokeGIT(GClientSmokeBase):
self.githash('repo_2', 1), self.githash('repo_2', 1),
self.gitrevparse(os.path.join(self.root_dir, 'src/repo2'))) self.gitrevparse(os.path.join(self.root_dir, 'src/repo2')))
def testSyncPatchRefBranch(self):
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
self.gclient([
'sync', '-v', '-v', '-v',
'--revision', 'src/repo2@%s' % self.githash('repo_2', 1),
'--patch-ref',
'%srepo_2@refs/heads/master:%s' % (
self.git_base, self.githash('repo_2', 2)),
])
# Assert that repo_2 files coincide with revision @2 (the patch ref)
tree = self.mangle_git_tree(('repo_1@2', 'src'),
('repo_2@2', 'src/repo2'),
('repo_3@2', 'src/repo2/repo_renamed'))
tree['src/git_hooked1'] = 'git_hooked1'
tree['src/git_hooked2'] = 'git_hooked2'
self.assertTree(tree)
# Assert that HEAD revision of repo_2 is @1 (the base we synced to) since we
# should have done a soft reset.
self.assertEqual(
self.githash('repo_2', 1),
self.gitrevparse(os.path.join(self.root_dir, 'src/repo2')))
def testSyncPatchRefNoHooks(self): def testSyncPatchRefNoHooks(self):
if not self.enabled: if not self.enabled:
return return
......
...@@ -310,7 +310,8 @@ class GclientTest(trial_dir.TestCase): ...@@ -310,7 +310,8 @@ class GclientTest(trial_dir.TestCase):
work_queue = gclient_utils.ExecutionQueue(options.jobs, None, False) work_queue = gclient_utils.ExecutionQueue(options.jobs, None, False)
for s in client.dependencies: for s in client.dependencies:
work_queue.enqueue(s) work_queue.enqueue(s)
work_queue.flush({}, None, [], options=options, patch_refs={}) work_queue.flush({}, None, [], options=options, patch_refs={},
target_branches={})
self.assertEqual( self.assertEqual(
[h.action for h in client.GetHooks(options)], [h.action for h in client.GetHooks(options)],
[tuple(x['action']) for x in hooks]) [tuple(x['action']) for x in hooks])
...@@ -361,7 +362,8 @@ class GclientTest(trial_dir.TestCase): ...@@ -361,7 +362,8 @@ class GclientTest(trial_dir.TestCase):
work_queue = gclient_utils.ExecutionQueue(options.jobs, None, False) work_queue = gclient_utils.ExecutionQueue(options.jobs, None, False)
for s in client.dependencies: for s in client.dependencies:
work_queue.enqueue(s) work_queue.enqueue(s)
work_queue.flush({}, None, [], options=options, patch_refs={}) work_queue.flush({}, None, [], options=options, patch_refs={},
target_branches={})
self.assertEqual( self.assertEqual(
[h.action for h in client.GetHooks(options)], [h.action for h in client.GetHooks(options)],
[tuple(x['action']) for x in hooks + extra_hooks + sub_hooks]) [tuple(x['action']) for x in hooks + extra_hooks + sub_hooks])
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment