[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 multiprocessing.pool import ThreadPool
import base64
import collections
import glob
import httplib
import json
@ -1516,6 +1517,105 @@ def get_cl_statuses(
url = cl.GetIssueURL()
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):
"""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',
help='Send the patchset to do a CQ dry run right after '
'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)
auth.add_auth_options(parser)
(options, args) = parser.parse_args(args)
@ -2282,6 +2386,16 @@ def CMDupload(parser, args):
options.verbose,
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

@ -696,6 +696,54 @@ class TestGitCl(TestCase):
squash=True,
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):
self.mock(git_cl, 'FindCodereviewSettingsFile', CodereviewSettingsFileMock)
def ParseCodereviewSettingsContent(content):

Loading…
Cancel
Save