diff --git a/gerrit_client.py b/gerrit_client.py index 6b3e9dee3e..f646c7d9fb 100755 --- a/gerrit_client.py +++ b/gerrit_client.py @@ -37,6 +37,32 @@ def write_result(result, opt): json_file.write(json.dumps(result)) +@subcommand.usage('[args ...]') +def CMDmovechanges(parser, args): + parser.add_option('-p', '--param', dest='params', action='append', + help='repeatable query parameter, format: -p key=value') + parser.add_option('--destination_branch', dest='destination_branch', + help='where to move changes to') + + (opt, args) = parser.parse_args(args) + assert opt.destination_branch, "--destination_branch not defined" + host = urlparse.urlparse(opt.host).netloc + + limit = 100 + while True: + result = gerrit_util.QueryChanges( + host, + list(tuple(p.split('=', 1)) for p in opt.params), + limit=limit, + ) + for change in result: + gerrit_util.MoveChange(host, change['id'], opt.destination_branch) + + if len(result) < limit: + break + logging.info("Done") + + @subcommand.usage('[args ...]') def CMDbranchinfo(parser, args): parser.add_option('--branch', dest='branch', help='branch name') diff --git a/gerrit_util.py b/gerrit_util.py index 61604a3226..0077f5f0bf 100644 --- a/gerrit_util.py +++ b/gerrit_util.py @@ -706,6 +706,15 @@ def AbandonChange(host, change, msg=''): return ReadHttpJsonResponse(conn) +def MoveChange(host, change, destination_branch): + """Move a Gerrit change to different destination branch.""" + path = 'changes/%s/move' % change + body = {'destination_branch': destination_branch} + conn = CreateHttpConn(host, path, reqtype='POST', body=body) + return ReadHttpJsonResponse(conn) + + + def RestoreChange(host, change, msg=''): """Restores a previously abandoned change.""" path = 'changes/%s/restore' % change diff --git a/git_cl.py b/git_cl.py index d6ab3ef33a..58cb54e5cf 100755 --- a/git_cl.py +++ b/git_cl.py @@ -108,6 +108,9 @@ REFS_THAT_ALIAS_TO_OTHER_REFS = { 'refs/remotes/origin/lkcr': 'refs/remotes/origin/master', } +DEFAULT_OLD_BRANCH = 'refs/remotes/origin/master' +DEFAULT_NEW_BRANCH = 'refs/remotes/origin/main' + # Valid extensions for files we want to lint. DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)" DEFAULT_LINT_IGNORE_REGEX = r"$^" @@ -641,7 +644,7 @@ def _GetYapfIgnorePatterns(top_dir): yapf is supposed to handle the ignoring of files listed in .yapfignore itself, but this functionality appears to break when explicitly passing files to yapf for formatting. According to - https://github.com/google/yapf/blob/master/README.rst#excluding-files-from-formatting-yapfignore, + https://github.com/google/yapf/blob/HEAD/README.rst#excluding-files-from-formatting-yapfignore, the .yapfignore file should be in the directory that yapf is invoked from, which we assume to be the top level directory in this case. @@ -987,7 +990,7 @@ class Changelist(object): self.more_cc.extend(more_cc) def GetBranch(self): - """Returns the short branch name, e.g. 'master'.""" + """Returns the short branch name, e.g. 'main'.""" if not self.branch: branchref = scm.GIT.GetBranchRef(settings.GetRoot()) if not branchref: @@ -997,7 +1000,7 @@ class Changelist(object): return self.branch def GetBranchRef(self): - """Returns the full branch name, e.g. 'refs/heads/master'.""" + """Returns the full branch name, e.g. 'refs/heads/main'.""" self.GetBranch() # Poke the lazy loader. return self.branchref @@ -1016,7 +1019,7 @@ class Changelist(object): @staticmethod def FetchUpstreamTuple(branch): """Returns a tuple containing remote and remote ref, - e.g. 'origin', 'refs/heads/master' + e.g. 'origin', 'refs/heads/main' """ remote, upstream_branch = scm.GIT.FetchUpstreamTuple( settings.GetRoot(), branch) @@ -1024,7 +1027,7 @@ class Changelist(object): DieWithError( 'Unable to determine default branch to diff against.\n' 'Either pass complete "git diff"-style arguments, like\n' - ' git cl upload origin/master\n' + ' git cl upload origin/main\n' 'or verify this branch is set up to track another \n' '(via the --track argument to "git checkout -b ...").') @@ -1226,8 +1229,8 @@ class Changelist(object): ('\nFailed to diff against upstream branch %s\n\n' 'This branch probably doesn\'t exist anymore. To reset the\n' 'tracking branch, please run\n' - ' git branch --set-upstream-to origin/master %s\n' - 'or replace origin/master with the relevant branch') % + ' git branch --set-upstream-to origin/main %s\n' + 'or replace origin/main with the relevant branch') % (upstream, self.GetBranch())) def UpdateDescription(self, description, force=False): @@ -3939,10 +3942,20 @@ def GetTargetRef(remote, remote_branch, target_branch): # Handle the refs that need to land in different refs. remote_branch = REFS_THAT_ALIAS_TO_OTHER_REFS[remote_branch] + # Migration to new default branch, only if available on remote. + allow_push_on_master = bool(os.environ.get("ALLOW_PUSH_TO_MASTER", None)) + if remote_branch == DEFAULT_OLD_BRANCH and not allow_push_on_master: + if RunGit(['show-branch', DEFAULT_NEW_BRANCH], error_ok=True, + stderr=subprocess2.PIPE): + # TODO(crbug.com/ID): Print location to local git migration script. + print("WARNING: Using new branch name %s instead of %s" % ( + DEFAULT_NEW_BRANCH, DEFAULT_OLD_BRANCH)) + remote_branch = DEFAULT_NEW_BRANCH + # Create the true path to the remote branch. # Does the following translation: # * refs/remotes/origin/refs/diff/test -> refs/diff/test - # * refs/remotes/origin/master -> refs/heads/master + # * refs/remotes/origin/main -> refs/heads/main # * refs/remotes/branch-heads/test -> refs/branch-heads/test if remote_branch.startswith('refs/remotes/%s/refs/' % remote): remote_branch = remote_branch.replace('refs/remotes/%s/' % remote, '') @@ -4026,7 +4039,7 @@ def CMDupload(parser, args): '--target-branch', metavar='TARGET', help='Apply CL to remote ref TARGET. ' + - 'Default: remote branch head, or master') + 'Default: remote branch head, or main') parser.add_option('--squash', action='store_true', help='Squash multiple commits into one') parser.add_option('--no-squash', action='store_false', dest='squash', @@ -4382,7 +4395,7 @@ def CMDtry(parser, args): '-r', '--revision', help='Revision to use for the tryjob; default: the revision will ' 'be determined by the try recipe that builder runs, which usually ' - 'defaults to HEAD of origin/master') + 'defaults to HEAD of origin/master or origin/main') group.add_option( '-c', '--clobber', action='store_true', default=False, help='Force a clobber before building; that is don\'t do an ' diff --git a/tests/git_cl_test.py b/tests/git_cl_test.py index 45f34c8c3b..cf788f7f37 100755 --- a/tests/git_cl_test.py +++ b/tests/git_cl_test.py @@ -647,7 +647,7 @@ class TestGitCl(unittest.TestCase): def _gerrit_base_calls(cls, issue=None, fetched_description=None, fetched_status=None, other_cl_owner=None, custom_cl_base=None, short_hostname='chromium', - change_id=None): + change_id=None, new_default=False): calls = [] if custom_cl_base: ancestor_revision = custom_cl_base @@ -655,8 +655,8 @@ class TestGitCl(unittest.TestCase): # Determine ancestor_revision to be merge base. ancestor_revision = 'fake_ancestor_sha' calls += [ - (('get_or_create_merge_base', 'master', 'refs/remotes/origin/master'), - ancestor_revision), + (('get_or_create_merge_base', 'master', + 'refs/remotes/origin/master'), ancestor_revision), ] if issue: @@ -682,6 +682,10 @@ class TestGitCl(unittest.TestCase): [ancestor_revision, 'HEAD']),), '+dat'), ] + calls += [ + ((['git', 'show-branch', 'refs/remotes/origin/main'], ), + '1' if new_default else callError(1)), + ] return calls @@ -693,7 +697,9 @@ class TestGitCl(unittest.TestCase): short_hostname='chromium', labels=None, change_id=None, final_description=None, gitcookies_exists=True, - force=False, edit_description=None): + force=False, edit_description=None, + new_default=False): + default_branch = 'main' if new_default else 'master'; if post_amend_description is None: post_amend_description = description cc = cc or [] @@ -722,14 +728,14 @@ class TestGitCl(unittest.TestCase): if custom_cl_base is None: calls += [ - (('get_or_create_merge_base', 'master', 'refs/remotes/origin/master'), - 'origin/master'), + (('get_or_create_merge_base', 'master', + 'refs/remotes/origin/master'), 'origin/' + default_branch), ] - parent = 'origin/master' + parent = 'origin/' + default_branch else: calls += [ ((['git', 'merge-base', '--is-ancestor', custom_cl_base, - 'refs/remotes/origin/master'],), + 'refs/remotes/origin/' + default_branch],), callError(1)), # Means not ancenstor. (('ask_for_data', 'Do you take responsibility for cleaning up potential mess ' @@ -748,7 +754,7 @@ class TestGitCl(unittest.TestCase): ] else: ref_to_push = 'HEAD' - parent = 'origin/refs/heads/master' + parent = 'origin/refs/heads/' + default_branch calls += [ (('SaveDescriptionBackup',), None), @@ -834,7 +840,7 @@ class TestGitCl(unittest.TestCase): (('time.time',), 1000,), ((['git', 'push', 'https://%s.googlesource.com/my/repo' % short_hostname, - ref_to_push + ':refs/for/refs/heads/master' + ref_suffix],), + ref_to_push + ':refs/for/refs/heads/' + default_branch + ref_suffix],), (('remote:\n' 'remote: Processing changes: (\)\n' 'remote: Processing changes: (|)\n' @@ -848,8 +854,8 @@ class TestGitCl(unittest.TestCase): ' XXX\n' 'remote:\n' 'To https://%s.googlesource.com/my/repo\n' - ' * [new branch] hhhh -> refs/for/refs/heads/master\n' - ) % (short_hostname, short_hostname)),), + ' * [new branch] hhhh -> refs/for/refs/heads/%s\n' + ) % (short_hostname, short_hostname, default_branch)),), (('time.time',), 2000,), (('add_repeated', 'sub_commands', @@ -990,7 +996,8 @@ class TestGitCl(unittest.TestCase): force=False, log_description=None, edit_description=None, - fetched_description=None): + fetched_description=None, + new_default=False): """Generic gerrit upload test framework.""" if squash_mode is None: if '--no-squash' in upload_args: @@ -1060,7 +1067,8 @@ class TestGitCl(unittest.TestCase): other_cl_owner=other_cl_owner, custom_cl_base=custom_cl_base, short_hostname=short_hostname, - change_id=change_id) + change_id=change_id, + new_default=new_default) if fetched_status != 'ABANDONED': mock.patch( 'gclient_utils.temporary_file', TemporaryFileMock()).start() @@ -1078,7 +1086,8 @@ class TestGitCl(unittest.TestCase): final_description=final_description, gitcookies_exists=gitcookies_exists, force=force, - edit_description=edit_description) + edit_description=edit_description, + new_default=new_default) # Uncomment when debugging. # print('\n'.join(map(lambda x: '%2i: %s' % x, enumerate(self.calls)))) git_cl.main(['upload'] + upload_args) @@ -1444,6 +1453,10 @@ class TestGitCl(unittest.TestCase): self.assertEqual(expected, actual) def test_get_hash_tags(self): + self.calls = [ + ((['git', 'show-branch', 'refs/remotes/origin/main'], ), + callError(1)), + ] * 9 cases = [ ('', []), ('a', []), @@ -2659,6 +2672,16 @@ class TestGitCl(unittest.TestCase): cl = git_cl.Changelist(issue=123456) self.assertEqual(cl._GerritChangeIdentifier(), '123456') + def test_gerrit_new_default(self): + self._run_gerrit_upload_test( + [], + 'desc ✔\n\nBUG=\n\nChange-Id: I123456789\n', + [], + squash=False, + squash_mode='override_nosquash', + change_id='I123456789', + new_default=True) + class ChangelistTest(unittest.TestCase): def setUp(self):