diff --git a/releasenotes/notes/cherry-pick-merges-9c78fd914b682671.yaml b/releasenotes/notes/cherry-pick-merges-9c78fd914b682671.yaml new file mode 100644 index 0000000000..d5f1d14c5f --- /dev/null +++ b/releasenotes/notes/cherry-pick-merges-9c78fd914b682671.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + The `cherry-pick` merger mode can now handle merges between branches by + performing a `git merge` instead of `git cherry-pick` if the change has + multiple parents. Previously, this would fail because git doesn't allow + a merge to be cherry-picked. diff --git a/tests/base.py b/tests/base.py index 2e5421dd65..76e485d691 100644 --- a/tests/base.py +++ b/tests/base.py @@ -494,7 +494,8 @@ class FakeGerritChange(object): repo.git.clean('-x', '-f', '-d') repo.index.merge_tree(parents[1]) - r = repo.index.commit(msg) + parent_commits = [repo.commit(p) for p in parents] + r = repo.index.commit(msg, parent_commits=parent_commits) repo.head.reference = 'master' repo.head.reset(working_tree=True) diff --git a/tests/unit/test_scheduler.py b/tests/unit/test_scheduler.py index 6ab5b30129..5cc70b9cfc 100644 --- a/tests/unit/test_scheduler.py +++ b/tests/unit/test_scheduler.py @@ -7062,6 +7062,51 @@ class TestSchedulerMerges(ZuulTestCase): result = self._test_project_merge_mode('cherry-pick') self.assertEqual(result, expected_messages) + def test_project_merge_mode_cherrypick_branch_merge(self): + "Test that branches can be merged together in cherry-pick mode" + self.create_branch('org/project-merge-branches', 'mp') + self.fake_gerrit.addEvent( + self.fake_gerrit.getFakeBranchCreatedEvent( + 'org/project-merge-branches', 'mp')) + self.waitUntilSettled() + + path = os.path.join(self.upstream_root, 'org/project-merge-branches') + repo = git.Repo(path) + master_sha = repo.heads.master.commit.hexsha + mp_sha = repo.heads.mp.commit.hexsha + + self.executor_server.hold_jobs_in_build = True + M = self.fake_gerrit.addFakeChange( + 'org/project-merge-branches', 'master', 'M', + merge_parents=[ + master_sha, + mp_sha, + ]) + M.addApproval('Code-Review', 2) + self.fake_gerrit.addEvent(M.addApproval('Approved', 1)) + self.waitUntilSettled() + + self.executor_server.release('.*-merge') + self.waitUntilSettled() + + build = self.builds[-1] + self.assertEqual(build.parameters['zuul']['branch'], 'master') + path = os.path.join(build.jobdir.src_root, 'review.example.com', + "org/project-merge-branches") + repo = git.Repo(path) + repo_messages = [c.message.strip() for c in repo.iter_commits()] + repo_messages.reverse() + correct_messages = [ + 'initial commit', + 'add content from fixture', + 'mp commit', + 'M-1'] + self.assertEqual(repo_messages, correct_messages) + + self.executor_server.hold_jobs_in_build = False + self.executor_server.release() + self.waitUntilSettled() + def test_merge_branch(self): "Test that the right commits are on alternate branches" self.create_branch('org/project-merge-branches', 'mp') diff --git a/zuul/merger/merger.py b/zuul/merger/merger.py index ce1c2b3e57..365f345150 100644 --- a/zuul/merger/merger.py +++ b/zuul/merger/merger.py @@ -555,9 +555,15 @@ class Repo(object): def cherryPick(self, ref, zuul_event_id=None): log = get_annotated_logger(self.log, zuul_event_id) repo = self.createRepoObject(zuul_event_id) - log.debug("Cherry-picking %s", ref) self.fetch(ref, zuul_event_id=zuul_event_id) - repo.git.cherry_pick("FETCH_HEAD") + if len(repo.commit("FETCH_HEAD").parents) > 1: + args = ["-s", "resolve", "FETCH_HEAD"] + log.debug("Merging %s with args %s instead of cherry-picking", + ref, args) + repo.git.merge(*args) + else: + log.debug("Cherry-picking %s", ref) + repo.git.cherry_pick("FETCH_HEAD") return repo.head.commit def merge(self, ref, strategy=None, zuul_event_id=None):