From 61ebd177abdc56bd373fc05c0101e2e506f9d758 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Tue, 4 Apr 2023 20:31:30 +0000 Subject: [PATCH] [depot tools] Add `git cl issue --switch` This extra option to Issue number: None (None) allow to switch to the branch associated to an issue iff there is a single one of those. Fixed: 1428476 Change-Id: Id39792cffddcb0b08f1123e18f319256209267f2 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/4381562 Reviewed-by: Gavin Mak Commit-Queue: Arthur Milchior Auto-Submit: Arthur Milchior --- git_cl.py | 115 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 84 insertions(+), 31 deletions(-) diff --git a/git_cl.py b/git_cl.py index ffa6eaeab..2948295bd 100755 --- a/git_cl.py +++ b/git_cl.py @@ -4218,53 +4218,106 @@ def write_json(path, contents): json.dump(contents, f) +def _GetIssueBranchMap(): + # type: () -> Dict[int, List] + """Associate issues (as number) to the list of branches associated to this + issue.""" + branches = RunGit(['for-each-ref', 'refs/heads', + '--format=%(refname)']).splitlines() + # Reverse issue lookup. + issue_branch_map = {} + + git_config = {} + for config in RunGit(['config', '--get-regexp', + r'branch\..*issue']).splitlines(): + name, _space, val = config.partition(' ') + git_config[name] = val + + for branch in branches: + issue = git_config.get('branch.%s.%s' % + (scm.GIT.ShortBranchName(branch), ISSUE_CONFIG_KEY)) + if issue: + issue_branch_map.setdefault(int(issue), []).append(branch) + return issue_branch_map + + +def SwitchToIssue(options, args): + """Switch to the branch associated to issue args[0] + + Fail if the first arg is not a number. Fail if there are 0 such branch. + If there are multiple such branch, warns and behaves as + `git cl issue -r issue_number`. + """ + issue_branch_map = _GetIssueBranchMap() + if not args: + DieWithError('`git cl issue --switch` requires a number.') + try: + issue_num = int(args[0]) + except ValueError: + DieWithError('Cannot parse issue number: %s' % args[0]) + branches = issue_branch_map.get(issue_num, []) + if not branches: + DieWithError('No branch associated to issue: %d' % issue_num) + if len(branches) > 1: + # Print the various branches + PrintIssueToBranches(options, args) + DieWithError('Multiple branches associated to issue: %d' % issue_num) + prefix_size = len('refs/heads/') + RunGit(['switch', branches[0][prefix_size:]]) + return 0 + + +def PrintIssueToBranches(options, args): + """Print the name of the branch(es) associated to the issue. + + If no issue is specified, print issue -> branch(es) for all known issues""" + # Reverse issue lookup. + issue_branch_map = _GetIssueBranchMap() + + if not args: + args = sorted(issue_branch_map.keys()) + result = {} + for issue in args: + try: + issue_num = int(issue) + except ValueError: + print('ERROR cannot parse issue number: %s' % issue, file=sys.stderr) + continue + result[issue_num] = issue_branch_map.get(issue_num) + print('Branch for issue number %s: %s' % + (issue, ', '.join(issue_branch_map.get(issue_num) or ('None', )))) + if options.json: + write_json(options.json, result) + return 0 + + @subcommand.usage('[issue_number]') @metrics.collector.collect_metrics('git cl issue') def CMDissue(parser, args): """Sets or displays the current code review issue number. Pass issue number 0 to clear the current issue. + --reverse and --switch option allow to access branch(es) from issue number. """ parser.add_option('-r', '--reverse', action='store_true', help='Lookup the branch(es) for the specified issues. If ' 'no issues are specified, all branches with mapped ' 'issues will be listed.') + parser.add_option('-s', + '--switch', + action='store_true', + help='Switch to the branch linked to the specified issue. ' + 'If multiple branches are linked, list all of them.' + 'Fail if `git switch` would fail in current state.') parser.add_option('--json', help='Path to JSON output file, or "-" for stdout.') options, args = parser.parse_args(args) + if options.switch: + return SwitchToIssue(options, args) + if options.reverse: - branches = RunGit(['for-each-ref', 'refs/heads', - '--format=%(refname)']).splitlines() - # Reverse issue lookup. - issue_branch_map = {} - - git_config = {} - for config in RunGit(['config', '--get-regexp', - r'branch\..*issue']).splitlines(): - name, _space, val = config.partition(' ') - git_config[name] = val - - for branch in branches: - issue = git_config.get( - 'branch.%s.%s' % (scm.GIT.ShortBranchName(branch), ISSUE_CONFIG_KEY)) - if issue: - issue_branch_map.setdefault(int(issue), []).append(branch) - if not args: - args = sorted(issue_branch_map.keys()) - result = {} - for issue in args: - try: - issue_num = int(issue) - except ValueError: - print('ERROR cannot parse issue number: %s' % issue, file=sys.stderr) - continue - result[issue_num] = issue_branch_map.get(issue_num) - print('Branch for issue number %s: %s' % ( - issue, ', '.join(issue_branch_map.get(issue_num) or ('None',)))) - if options.json: - write_json(options.json, result) - return 0 + return PrintIssueToBranches(options, args) if len(args) > 0: issue = ParseIssueNumberArgument(args[0])