Переглянути джерело

git cl cherry-pick: Use base parameter to fix identical tree errors

The `git cl cherry-pick` command previously created chained CLs by
first cherry-picking a change onto the destination branch tip and then
rebasing the result onto the parent CL created in the previous step.

This approach failed when a sequence of cherry-picks resulted in an
intermediate state having an identical tree compared to its intended
base (e.g., commit 1 changes X->Y, commit 2 changes Y->X). Gerrit
would reject the second cherry-pick with an "identical tree" error
because the rebase is done after the cherry pick.

This change modifies the process to use the `base` parameter of the
Gerrit `cherrypick` REST API endpoint.

Changes:
- Modify `gerrit_util.CherryPick` to accept and pass an optional `base`
  commit hash in the API request body.
- Update `git_cl.CMDcherry_pick`:
  - Before each cherry-pick operation in the loop, fetch the commit hash
    of the latest patchset from the previously processed parent CL.
  - Pass this commit hash as the `base` parameter to `gerrit_util.CherryPick`.
  - Remove the subsequent, now redundant, call to `gerrit_util.RebaseChange`.

This ensures the correct parent commit is specified during the
cherry-pick operation itself, allowing Gerrit to handle the chaining
correctly and avoid failures caused by identical tree states in
intermediate steps.

Bug: 408388488
Change-Id: I84066d65bd6bb127b253bee6564dd0622148a0e0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/6433112
Commit-Queue: Gennady Tsitovich <gtsitovich@google.com>
Reviewed-by: Gavin Mak <gavinmak@google.com>
Gennady Tsitovich 4 місяців тому
батько
коміт
6cc266569e
2 змінених файлів з 27 додано та 21 видалено
  1. 8 1
      gerrit_util.py
  2. 19 20
      git_cl.py

+ 8 - 1
gerrit_util.py

@@ -1518,7 +1518,12 @@ def DeletePendingChangeEdit(host, change):
     ReadHttpResponse(conn, accept_statuses=[204, 404])
     ReadHttpResponse(conn, accept_statuses=[204, 404])
 
 
 
 
-def CherryPick(host, change, destination, revision='current', message=None):
+def CherryPick(host,
+               change,
+               destination,
+               revision='current',
+               message=None,
+               base=None):
     """Create a cherry-pick commit from the given change, onto the given
     """Create a cherry-pick commit from the given change, onto the given
     destination.
     destination.
     """
     """
@@ -1526,6 +1531,8 @@ def CherryPick(host, change, destination, revision='current', message=None):
     body = {'destination': destination}
     body = {'destination': destination}
     if message:
     if message:
         body['message'] = message
         body['message'] = message
+    if base:
+        body['base'] = base
     conn = CreateHttpConn(host, path, reqtype='POST', body=body)
     conn = CreateHttpConn(host, path, reqtype='POST', body=body)
 
 
     # If a cherry pick fails due to a merge conflict, Gerrit returns 409.
     # If a cherry pick fails due to a merge conflict, Gerrit returns 409.

+ 19 - 20
git_cl.py

@@ -4720,17 +4720,33 @@ def CMDcherry_pick(parser, args):
     # Gerrit only supports cherry picking one commit per change, so we have
     # Gerrit only supports cherry picking one commit per change, so we have
     # to cherry pick each commit individually and create a chain of CLs.
     # to cherry pick each commit individually and create a chain of CLs.
     parent_change_num = options.parent_change_num
     parent_change_num = options.parent_change_num
+    parent_commit_hash = None
+
     for change_id, orig_message in change_ids_to_message.items():
     for change_id, orig_message in change_ids_to_message.items():
         message = _create_commit_message(orig_message, options.bug)
         message = _create_commit_message(orig_message, options.bug)
         orig_subj_line = orig_message.splitlines()[0]
         orig_subj_line = orig_message.splitlines()[0]
+        original_commit_hash = change_ids_to_commit[change_id]
 
 
-        # Create a cherry pick first, then rebase. If we create a chained CL
-        # then cherry pick, the change will lose its relation to the parent.
+        # Determine the base commit hash for the current cherry-pick by fetching
+        # the details of the previous CL (identified by parent_change_num).
+        # Skip if this is the first CL and no initial parent was given.
+        if parent_change_num:
+            parent_details = gerrit_util.GetChangeDetail(
+                host, str(parent_change_num), o_params=['CURRENT_REVISION'])
+            parent_commit_hash = parent_details['current_revision']
+            print(f'Using base commit {parent_commit_hash} '
+                  f'from parent CL {parent_change_num}.')
+
+        # Call CherryPick with the determined base commit hash
+        print('Attempting cherry-pick of original commit '
+              f'{original_commit_hash} ("{orig_subj_line}") onto base '
+              f'{parent_commit_hash or options.branch + " tip"}...')
         try:
         try:
             new_change_info = gerrit_util.CherryPick(host,
             new_change_info = gerrit_util.CherryPick(host,
                                                      change_id,
                                                      change_id,
                                                      options.branch,
                                                      options.branch,
-                                                     message=message)
+                                                     message=message,
+                                                     base=parent_commit_hash)
         except gerrit_util.GerritError as e:
         except gerrit_util.GerritError as e:
             print(f'Failed to create cherry pick "{orig_subj_line}": {e}. '
             print(f'Failed to create cherry pick "{orig_subj_line}": {e}. '
                   'Please resolve any merge conflicts.')
                   'Please resolve any merge conflicts.')
@@ -4738,26 +4754,9 @@ def CMDcherry_pick(parser, args):
             return 1
             return 1
 
 
         change_ids_to_commit.pop(change_id)
         change_ids_to_commit.pop(change_id)
-        new_change_id = new_change_info['id']
         new_change_num = new_change_info['_number']
         new_change_num = new_change_info['_number']
         new_change_url = gerrit_util.GetChangePageUrl(host, new_change_num)
         new_change_url = gerrit_util.GetChangePageUrl(host, new_change_num)
         print(f'Created cherry pick of "{orig_subj_line}": {new_change_url}')
         print(f'Created cherry pick of "{orig_subj_line}": {new_change_url}')
-
-        if parent_change_num:
-            try:
-                gerrit_util.RebaseChange(host, new_change_id, parent_change_num)
-            except gerrit_util.GerritError as e:
-                parent_change_url = gerrit_util.GetChangePageUrl(
-                    host, parent_change_num)
-                print(f'Failed to rebase {new_change_url} on '
-                      f'{parent_change_url}: {e}. Please resolve any merge '
-                      'conflicts.')
-                print('Once resolved, you can continue the CL chain with '
-                      f'`--parent-change-num={new_change_num}` to specify '
-                      'which change the chain should start with.\n')
-                print_any_remaining_commits()
-                return 1
-
         parent_change_num = new_change_num
         parent_change_num = new_change_num
 
 
     return 0
     return 0