[depot_tools] New "git cl upload" flag to traverse dependent branches and re-upload them.

Motivation:
The conversation in https://docs.google.com/document/d/1KZGFKZpOPvco81sYVRCzwlnjGctup71RAzY0MSb0ntc/edit?disco=AAAAAXU60E8

BUG=502257

Review URL: https://codereview.chromium.org/1191473002

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@295779 0039d316-1c4b-4281-b951-d872f2087c98
changes/01/332501/1
rmistry@google.com 10 years ago
parent c2b9bd03e9
commit 2dd9986270

@ -10,6 +10,7 @@
from distutils.version import LooseVersion from distutils.version import LooseVersion
from multiprocessing.pool import ThreadPool from multiprocessing.pool import ThreadPool
import base64 import base64
import collections
import glob import glob
import httplib import httplib
import json import json
@ -1516,6 +1517,105 @@ def get_cl_statuses(
url = cl.GetIssueURL() url = cl.GetIssueURL()
yield (b, url, 'waiting' if url else 'error') yield (b, url, 'waiting' if url else 'error')
def upload_branch_deps(cl, args):
"""Uploads CLs of local branches that are dependents of the current branch.
If the local branch dependency tree looks like:
test1 -> test2.1 -> test3.1
-> test3.2
-> test2.2 -> test3.3
and you run "git cl upload --dependencies" from test1 then "git cl upload" is
run on the dependent branches in this order:
test2.1, test3.1, test3.2, test2.2, test3.3
Note: This function does not rebase your local dependent branches. Use it when
you make a change to the parent branch that will not conflict with its
dependent branches, and you would like their dependencies updated in
Rietveld.
"""
if git_common.is_dirty_git_tree('upload-branch-deps'):
return 1
root_branch = cl.GetBranch()
if root_branch is None:
DieWithError('Can\'t find dependent branches from detached HEAD state. '
'Get on a branch!')
if not cl.GetIssue() or not cl.GetPatchset():
DieWithError('Current branch does not have an uploaded CL. We cannot set '
'patchset dependencies without an uploaded CL.')
branches = RunGit(['for-each-ref',
'--format=%(refname:short) %(upstream:short)',
'refs/heads'])
if not branches:
print('No local branches found.')
return 0
# Create a dictionary of all local branches to the branches that are dependent
# on it.
tracked_to_dependents = collections.defaultdict(list)
for b in branches.splitlines():
tokens = b.split()
if len(tokens) == 2:
branch_name, tracked = tokens
tracked_to_dependents[tracked].append(branch_name)
print
print 'The dependent local branches of %s are:' % root_branch
dependents = []
def traverse_dependents_preorder(branch, padding=''):
dependents_to_process = tracked_to_dependents.get(branch, [])
padding += ' '
for dependent in dependents_to_process:
print '%s%s' % (padding, dependent)
dependents.append(dependent)
traverse_dependents_preorder(dependent, padding)
traverse_dependents_preorder(root_branch)
print
if not dependents:
print 'There are no dependent local branches for %s' % root_branch
return 0
print ('This command will checkout all dependent branches and run '
'"git cl upload".')
ask_for_data('[Press enter to continue or ctrl-C to quit]')
# Add a default patchset title to all upload calls.
args.extend(['-t', 'Updated patchset dependency'])
# Record all dependents that failed to upload.
failures = {}
# Go through all dependents, checkout the branch and upload.
try:
for dependent_branch in dependents:
print
print '--------------------------------------'
print 'Running "git cl upload" from %s:' % dependent_branch
RunGit(['checkout', '-q', dependent_branch])
print
try:
if CMDupload(OptionParser(), args) != 0:
print 'Upload failed for %s!' % dependent_branch
failures[dependent_branch] = 1
except: # pylint: disable=W0702
failures[dependent_branch] = 1
print
finally:
# Swap back to the original root branch.
RunGit(['checkout', '-q', root_branch])
print
print 'Upload complete for dependent branches!'
for dependent_branch in dependents:
upload_status = 'failed' if failures.get(dependent_branch) else 'succeeded'
print ' %s : %s' % (dependent_branch, upload_status)
print
return 0
def CMDstatus(parser, args): def CMDstatus(parser, args):
"""Show status of changelists. """Show status of changelists.
@ -2197,7 +2297,11 @@ def CMDupload(parser, args):
parser.add_option('--cq-dry-run', dest='cq_dry_run', action='store_true', parser.add_option('--cq-dry-run', dest='cq_dry_run', action='store_true',
help='Send the patchset to do a CQ dry run right after ' help='Send the patchset to do a CQ dry run right after '
'upload.') 'upload.')
parser.add_option('--dependencies', action='store_true',
help='Uploads CLs of all the local branches that depend on '
'the current branch')
orig_args = args
add_git_similarity(parser) add_git_similarity(parser)
auth.add_auth_options(parser) auth.add_auth_options(parser)
(options, args) = parser.parse_args(args) (options, args) = parser.parse_args(args)
@ -2282,6 +2386,16 @@ def CMDupload(parser, args):
options.verbose, options.verbose,
sys.stdout) sys.stdout)
# Upload all dependencies if specified.
if options.dependencies:
print
print '--dependencies has been specified.'
print 'All dependent local branches will be re-uploaded.'
print
# Remove the dependencies flag from args so that we do not end up in a
# loop.
orig_args.remove('--dependencies')
upload_branch_deps(cl, orig_args)
return ret return ret

@ -696,6 +696,54 @@ class TestGitCl(TestCase):
squash=True, squash=True,
expected_upstream_ref='origin/master') expected_upstream_ref='origin/master')
def test_upload_branch_deps(self):
def mock_run_git(*args, **_kwargs):
if args[0] == ['for-each-ref',
'--format=%(refname:short) %(upstream:short)',
'refs/heads']:
# Create a local branch dependency tree that looks like this:
# test1 -> test2 -> test3 -> test4 -> test5
# -> test3.1
# test6 -> test0
branch_deps = [
'test2 test1', # test1 -> test2
'test3 test2', # test2 -> test3
'test3.1 test2', # test2 -> test3.1
'test4 test3', # test3 -> test4
'test5 test4', # test4 -> test5
'test6 test0', # test0 -> test6
'test7', # test7
]
return '\n'.join(branch_deps)
self.mock(git_cl, 'RunGit', mock_run_git)
class RecordCalls:
times_called = 0
record_calls = RecordCalls()
def mock_CMDupload(*args, **_kwargs):
record_calls.times_called += 1
return 0
self.mock(git_cl, 'CMDupload', mock_CMDupload)
self.calls = [
(('[Press enter to continue or ctrl-C to quit]',), ''),
]
class MockChangelist():
def __init__(self):
pass
def GetBranch(self):
return 'test1'
def GetIssue(self):
return '123'
def GetPatchset(self):
return '1001'
ret = git_cl.upload_branch_deps(MockChangelist(), [])
# CMDupload should have been called 5 times because of 5 dependent branches.
self.assertEquals(5, record_calls.times_called)
self.assertEquals(0, ret)
def test_config_gerrit_download_hook(self): def test_config_gerrit_download_hook(self):
self.mock(git_cl, 'FindCodereviewSettingsFile', CodereviewSettingsFileMock) self.mock(git_cl, 'FindCodereviewSettingsFile', CodereviewSettingsFileMock)
def ParseCodereviewSettingsContent(content): def ParseCodereviewSettingsContent(content):

Loading…
Cancel
Save