[git cl]Add precheck function for stacked changes upload.
High-level rough draft: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/4166282
Bug: b/265929888
Change-Id: I7881ade0ea97d7537e1dd40ab484ee5ef828aa34
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/4175861
Commit-Queue: Joanna Wang <jojwang@chromium.org>
Reviewed-by: Gavin Mak <gavinmak@google.com>
Reviewed-by: Josip Sokcevic <sokcevic@chromium.org>
diff --git a/git_cl.py b/git_cl.py
index eae031d..c6a9bb3 100755
--- a/git_cl.py
+++ b/git_cl.py
@@ -155,6 +155,11 @@
set(_KNOWN_GERRIT_TO_SHORT_URLS.values())), 'must have unique values'
+# Maximum number of branches in a stack that can be traversed and uploaded
+# at once. Picked arbitrarily.
+_MAX_STACKED_BRANCHES_UPLOAD = 20
+
+
class GitPushError(Exception):
pass
@@ -2251,7 +2256,10 @@
return 0
- def _GerritCommitMsgHookCheck(self, offer_removal):
+ @staticmethod
+ def _GerritCommitMsgHookCheck(offer_removal):
+ # type: (bool) -> None
+ """Checks for the gerrit's commit-msg hook and removes it if necessary."""
hook = os.path.join(settings.GetRoot(), '.git', 'hooks', 'commit-msg')
if not os.path.exists(hook):
return
@@ -4514,6 +4522,9 @@
parser.add_option('--no-python2-post-upload-hooks',
action='store_true',
help='Only run post-upload hooks in Python 3.')
+ parser.add_option('--stacked-exp',
+ action='store_true',
+ help=optparse.SUPPRESS_HELP)
orig_args = args
(options, args) = parser.parse_args(args)
@@ -4554,6 +4565,12 @@
# Load default for user, repo, squash=true, in this order.
options.squash = settings.GetSquashGerritUploads()
+ if options.stacked_exp:
+ orig_args.remove('--stacked-exp')
+
+ UploadAllSquashed(options, orig_args)
+ return 0
+
cl = Changelist(branchref=options.target_branch)
# Warm change details cache now to avoid RPCs later, reducing latency for
# developers.
@@ -4589,6 +4606,106 @@
return ret
+def UploadAllSquashed(options, orig_args):
+ # type: (optparse.Values, Sequence[str]) -> Tuple[Sequence[Changelist], bool]
+ """Uploads the current and upstream branches (if necessary)."""
+ _cls, _cherry_pick_current = _UploadAllPrecheck(options, orig_args)
+
+ # TODO(b/265929888): parse cls and create commits.
+
+
+def _UploadAllPrecheck(options, orig_args):
+ # type: (optparse.Values, Sequence[str]) -> Tuple[Sequence[Changelist], bool]
+ """Checks the state of the tree and gives the user uploading options
+
+ Returns: A tuple of the ordered list of changes that have new commits
+ since their last upload and a boolean of whether the user wants to
+ cherry-pick and upload the current branch instead of uploading all cls.
+ """
+ branch_ref = None
+ cls = []
+ must_upload_upstream = False
+
+ Changelist._GerritCommitMsgHookCheck(offer_removal=not options.force)
+
+ while True:
+ if len(cls) > _MAX_STACKED_BRANCHES_UPLOAD:
+ DieWithError(
+ 'More than %s branches in the stack have not been uploaded.\n'
+ 'Are your branches in a misconfigured state?\n'
+ 'If not, please upload some upstream changes first.' %
+ (_MAX_STACKED_BRANCHES_UPLOAD))
+
+ cl = Changelist(branchref=branch_ref)
+ cls.append(cl)
+
+ origin, upstream_branch_ref = Changelist.FetchUpstreamTuple(cl.GetBranch())
+ upstream_branch = scm.GIT.ShortBranchName(upstream_branch_ref)
+ branch_ref = upstream_branch_ref # set branch for next run.
+
+ # Case 1: We've reached the beginning of the tree.
+ if origin != '.':
+ break
+
+ upstream_last_upload = scm.GIT.GetBranchConfig(settings.GetRoot(),
+ upstream_branch,
+ LAST_UPLOAD_HASH_CONFIG_KEY)
+
+ # Case 2: If any upstream branches have never been uploaded,
+ # the user MUST upload them.
+ if not upstream_last_upload:
+ must_upload_upstream = True
+ continue
+
+ base_commit = cl.GetCommonAncestorWithUpstream()
+
+ # Case 3: If upstream's last_upload == cl.base_commit we do
+ # not need to upload any more upstreams from this point on.
+ # (Even if there may be diverged branches higher up the tree)
+ if base_commit == upstream_last_upload:
+ break
+
+ # Case 4: If upstream's last_upload < cl.base_commit we are
+ # uploading cl and upstream_cl.
+ # Continue up the tree to check other branch relations.
+ if scm.GIT.IsAncestor(None, upstream_last_upload, base_commit):
+ continue
+
+ # Case 5: If cl.base_commit < upstream's last_upload the user
+ # must rebase before uploading.
+ if scm.GIT.IsAncestor(None, base_commit, upstream_last_upload):
+ DieWithError(
+ 'At least one branch in the stack has diverged from its upstream '
+ 'branch and does not contain its upstream\'s last upload.\n'
+ 'Please rebase the stack with `git rebase-update` before uploading.')
+
+ # The tree went through a rebase. LAST_UPLOAD_HASH_CONFIG_KEY no longer has
+ # any relation to commits in the tree. Continue up the tree until we hit
+ # the root.
+
+ # We assume all cls in the stack have the same auth requirements and only
+ # check this once.
+ cls[0].EnsureAuthenticated(force=options.force)
+
+ cherry_pick = False
+ if len(cls) > 1:
+ message = ''
+ if len(orig_args):
+ message = ('options %s will be used for all uploads.\n' % orig_args)
+ if must_upload_upstream:
+ confirm_or_exit('\n' + message +
+ 'There are upstream branches that must be uploaded.\n')
+ else:
+ answer = gclient_utils.AskForData(
+ '\n' + message +
+ 'Press enter to update branches %s.\nOr type `n` to upload only '
+ '`%s` cherry-picked on %s\'s last upload:' %
+ ([cl.branch for cl in cls], cls[0].branch, cls[1].branch))
+ if answer.lower() == 'n':
+ cherry_pick = True
+ return cls, cherry_pick
+
+
@subcommand.usage('--description=<description file>')
@metrics.collector.collect_metrics('git cl split')
def CMDsplit(parser, args):