瀏覽代碼

[stacked_changes] Create cherry-picked commit.

Bug:b/265929888
Change-Id: I4277474c1f09e4ac6ea6ebb5d9d340f22365f542
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/4178924
Reviewed-by: Gavin Mak <gavinmak@google.com>
Reviewed-by: Josip Sokcevic <sokcevic@chromium.org>
Commit-Queue: Joanna Wang <jojwang@chromium.org>
Joanna Wang 2 年之前
父節點
當前提交
e852391441
共有 2 個文件被更改,包括 118 次插入0 次删除
  1. 41 0
      git_cl.py
  2. 77 0
      tests/git_cl_test.py

+ 41 - 0
git_cl.py

@@ -999,6 +999,12 @@ _CommentSummary = collections.namedtuple(
                         'approval', 'disapproval'])
 
 
+_NewUpload = collections.namedtuple('NewUpload', [
+    'reviewers', 'ccs', 'commit_to_push', 'new_last_uploaded_commit',
+    'change_desc'
+])
+
+
 class Changelist(object):
   """Changelist works with one changelist in local branch.
 
@@ -1569,6 +1575,41 @@ class Changelist(object):
       return title
     return user_title or title
 
+  def PrepareCherryPickSquashedCommit(self, options):
+    # type: (optparse.Values) -> _NewUpload()
+    """Create a commit cherry-picked on parent to push."""
+
+    parent = self.GetCommonAncestorWithUpstream()
+    reviewers, ccs, change_desc = self._PrepareChange(options, parent,
+                                                      self.branchref)
+
+    new_upload_hash = RunGit(['rev-parse', self.branchref]).strip()
+    latest_tree = RunGit(['rev-parse', self.branchref + ':']).strip()
+    with gclient_utils.temporary_file() as desc_tempfile:
+      gclient_utils.FileWrite(desc_tempfile, change_desc.description)
+      commit_to_cp = RunGit(
+          ['commit-tree', latest_tree, '-p', parent, '-F',
+           desc_tempfile]).strip()
+
+    _, upstream_branch_ref = self.FetchUpstreamTuple(self.GetBranch())
+
+    upstream_branch = scm.GIT.ShortBranchName(upstream_branch_ref)
+    upstream_squashed_upload = scm.GIT.GetBranchConfig(
+        settings.GetRoot(), upstream_branch, GERRIT_SQUASH_HASH_CONFIG_KEY)
+
+    RunGit(['checkout', '-q', upstream_squashed_upload])
+    ret, _out = RunGitWithCode(['cherry-pick', commit_to_cp])
+    if ret:
+      RunGit(['cherry-pick', '--abort'])
+      RunGit(['checkout', '-q', self.branch])
+      DieWithError('Could not cleanly cherry-pick')
+
+    commit_to_push = RunGit(['rev-parse', 'HEAD'])
+    RunGit(['checkout', '-q', self.branch])
+
+    return _NewUpload(reviewers, ccs, commit_to_push, new_upload_hash,
+                      change_desc)
+
   def _PrepareChange(self, options, parent, end_commit):
     # type: (optparse.Values, str, str) ->
     #     Tuple[Sequence[str], Sequence[str], ChangeDescription]

+ 77 - 0
tests/git_cl_test.py

@@ -3210,8 +3210,12 @@ class ChangelistTest(unittest.TestCase):
     mock.patch('subprocess2.Popen').start()
     mock.patch(
         'git_cl.Changelist.GetGerritProject', return_value='project').start()
+    mock.patch('sys.exit', side_effect=SystemExitMock).start()
+
     self.addCleanup(mock.patch.stopall)
     self.temp_count = 0
+    self.mockGit = GitMocks()
+    mock.patch('scm.GIT.GetConfig', self.mockGit.GetConfig).start()
 
   def testRunHook(self):
     expected_results = {
@@ -3555,6 +3559,79 @@ class ChangelistTest(unittest.TestCase):
     for user_title in ['not empty', 'yes', 'YES']:
       self.assertEqual(cl._GetTitleForUpload(options), user_title)
 
+  @mock.patch('git_cl.Settings.GetRoot', return_value='')
+  @mock.patch('git_cl.Changelist.FetchUpstreamTuple')
+  @mock.patch('git_cl.RunGitWithCode')
+  @mock.patch('git_cl.RunGit')
+  @mock.patch('git_cl.Changelist._PrepareChange')
+  @mock.patch('git_cl.Changelist.GetCommonAncestorWithUpstream')
+  def testPrepareCherryPickSquashedCommit(self,
+                                          mockGetCommonAncestorWithUpstream,
+                                          mockPrepareChange, mockRunGit,
+                                          mockRunGitWithCode,
+                                          mockFetchUpstreamTuple, *_mocks):
+    parent_hash = '1a2bparentcommit'
+    mockGetCommonAncestorWithUpstream.return_value = parent_hash
+
+    change_desc = git_cl.ChangeDescription('BOO!')
+    ccs = ['cc@review.cl']
+    reviewers = ['reviewer@review.cl']
+    mockPrepareChange.return_value = (reviewers, ccs, change_desc)
+
+    branchref = 'refs/heads/current-branch'
+    cl = git_cl.Changelist(branchref=branchref)
+    options = optparse.Values()
+
+    mockFetchUpstreamTuple.return_value = ('', 'refs/heads/upstream')
+
+    upstream_gerrit_hash = 'upstream-gerrit-hash'
+    self.mockGit.config['branch.upstream.%s' %
+                        git_cl.GERRIT_SQUASH_HASH_CONFIG_KEY] = (
+                            upstream_gerrit_hash)
+
+    latest_tree_hash = 'tree-hash'
+    hash_to_cp = 'squashed-hash'
+    hash_to_push = 'hash-to-push'
+    hash_to_save_as_last_upload = 'last-upload'
+
+    def mock_run_git(commands):
+      if commands == ['rev-parse', branchref]:
+        return hash_to_save_as_last_upload
+      if commands == ['rev-parse', branchref + ':']:
+        return latest_tree_hash
+      if {'commit-tree', latest_tree_hash, '-p', parent_hash,
+          '-F'}.issubset(set(commands)):
+        return hash_to_cp
+      if commands == ['rev-parse', 'HEAD']:
+        return hash_to_push
+
+    mockRunGit.side_effect = mock_run_git
+
+    def mock_run_git_with_code(commands):
+      if commands == ['cherry-pick', hash_to_cp]:
+        return 0, ''
+
+    mockRunGitWithCode.side_effect = mock_run_git_with_code
+
+    new_upload = cl.PrepareCherryPickSquashedCommit(options)
+    self.assertEqual(new_upload.reviewers, reviewers)
+    self.assertEqual(new_upload.ccs, ccs)
+    self.assertEqual(new_upload.commit_to_push, hash_to_push)
+    self.assertEqual(new_upload.new_last_uploaded_commit,
+                     hash_to_save_as_last_upload)
+    self.assertEqual(new_upload.change_desc, change_desc)
+
+    # Test failed cherry-pick
+
+    def mock_run_git_with_code(commands):
+      if commands == ['cherry-pick', hash_to_cp]:
+        return 1, ''
+
+    mockRunGitWithCode.side_effect = mock_run_git_with_code
+
+    with self.assertRaises(SystemExitMock):
+      cl.PrepareCherryPickSquashedCommit(options)
+
   @mock.patch('git_cl.Changelist.GetAffectedFiles', return_value=[])
   @mock.patch('git_cl.GenerateGerritChangeId', return_value='1a2b3c')
   @mock.patch('git_cl.Changelist.GetIssue', return_value=None)