diff options
-rw-r--r-- | doc/source/config/project.rst | 9 | ||||
-rw-r--r-- | releasenotes/notes/rebase-merge-6b96aa541cd53492.yaml | 5 | ||||
-rw-r--r-- | tests/fixtures/layouts/gate-github-rebase.yaml | 38 | ||||
-rw-r--r-- | tests/unit/test_github_driver.py | 52 | ||||
-rw-r--r-- | zuul/configloader.py | 3 | ||||
-rw-r--r-- | zuul/driver/github/githubreporter.py | 13 | ||||
-rw-r--r-- | zuul/merger/merger.py | 24 | ||||
-rw-r--r-- | zuul/model.py | 4 |
8 files changed, 132 insertions, 16 deletions
diff --git a/doc/source/config/project.rst b/doc/source/config/project.rst index af3faa943..04d61b5d3 100644 --- a/doc/source/config/project.rst +++ b/doc/source/config/project.rst @@ -138,13 +138,20 @@ pipeline. .. value:: cherry-pick Cherry-picks each change onto the branch rather than - performing any merges. This is not supported by Github and GitLab. + performing any merges. This is not supported by GitHub and GitLab. .. value:: squash-merge Squash merges each change onto the branch. This maps to the merge mode ``squash`` in GitHub and GitLab. + .. value:: rebase + + Rebases the changes onto the branch. This is only supported + by GitHub and maps to the ``rebase`` merge mode (but + does not alter committer information in the way that GitHub + does in the repos that Zuul prepares for jobs). + .. attr:: vars :default: None diff --git a/releasenotes/notes/rebase-merge-6b96aa541cd53492.yaml b/releasenotes/notes/rebase-merge-6b96aa541cd53492.yaml new file mode 100644 index 000000000..02f87c389 --- /dev/null +++ b/releasenotes/notes/rebase-merge-6b96aa541cd53492.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + The :value:`project.merge-mode.rebase` merge-mode is now supported + for GitHub. diff --git a/tests/fixtures/layouts/gate-github-rebase.yaml b/tests/fixtures/layouts/gate-github-rebase.yaml new file mode 100644 index 000000000..80408decd --- /dev/null +++ b/tests/fixtures/layouts/gate-github-rebase.yaml @@ -0,0 +1,38 @@ +- pipeline: + name: gate + manager: dependent + trigger: + github: + - event: pull_request + action: + - opened + - changed + - reopened + branch: ^master$ + success: + github: + status: success + merge: true + failure: + github: {} + +- job: + name: base + parent: null + run: playbooks/base.yaml + +- job: + name: project-test1 + run: playbooks/project-test1.yaml + +- job: + name: project-test2 + run: playbooks/project-test2.yaml + +- project: + name: org/project + merge-mode: rebase + gate: + jobs: + - project-test1 + - project-test2 diff --git a/tests/unit/test_github_driver.py b/tests/unit/test_github_driver.py index 49dae0ccb..90d69a7bc 100644 --- a/tests/unit/test_github_driver.py +++ b/tests/unit/test_github_driver.py @@ -1356,11 +1356,59 @@ class TestGithubDriver(ZuulTestCase): self.assertEquals(A.comments[1], 'Merge mode cherry-pick not supported by Github') + @simple_layout('layouts/gate-github-rebase.yaml', driver='github') + def test_merge_method_rebase(self): + """ + Tests that the merge mode gets forwarded to the reporter and the + PR was rebased. + """ + self.executor_server.keep_jobdir = True + self.executor_server.hold_jobs_in_build = True + github = self.fake_github.getGithubClient() + repo = github.repo_from_project('org/project') + repo._set_branch_protection( + 'master', contexts=['tenant-one/check', 'tenant-one/gate']) + + A = self.fake_github.openFakePullRequest('org/project', 'master', 'A') + repo.create_status(A.head_sha, 'success', 'example.com', 'description', + 'tenant-one/check') + + # Create a second commit on master to verify rebase behavior + self.create_commit('org/project', message="Test rebase commit") + + self.fake_github.emitEvent(A.getPullRequestOpenedEvent()) + self.waitUntilSettled() + + build = self.builds[-1] + path = os.path.join(build.jobdir.src_root, 'github.com/org/project') + repo = git.Repo(path) + repo_messages = [c.message.strip() for c in repo.iter_commits()] + repo_messages.reverse() + expected = [ + 'initial commit', + 'initial commit', # simple_layout adds a second "initial commit" + 'Test rebase commit', + 'A-1', + ] + self.assertEqual(expected, repo_messages) + + self.executor_server.hold_jobs_in_build = False + self.executor_server.release() + self.waitUntilSettled() + + # the change should have entered the gate + self.assertEqual(2, len(self.history)) + + # now check if the merge was done via rebase + merges = [report for report in self.fake_github.github_data.reports + if report[2] == 'merge'] + assert (len(merges) == 1 and merges[0][3] == 'rebase') + @simple_layout('layouts/gate-github-squash-merge.yaml', driver='github') def test_merge_method_squash_merge(self): """ Tests that the merge mode gets forwarded to the reporter and the - merge fails because cherry-pick is not supported by github. + PR was squashed. """ github = self.fake_github.getGithubClient() repo = github.repo_from_project('org/project') @@ -1381,7 +1429,7 @@ class TestGithubDriver(ZuulTestCase): # the change should have entered the gate self.assertEqual(2, len(self.history)) - # now check if the merge was done via rebase + # now check if the merge was done via squash merges = [report for report in self.fake_github.github_data.reports if report[2] == 'merge'] assert (len(merges) == 1 and merges[0][3] == 'squash') diff --git a/zuul/configloader.py b/zuul/configloader.py index 63633a54c..03953eb31 100644 --- a/zuul/configloader.py +++ b/zuul/configloader.py @@ -1113,7 +1113,8 @@ class ProjectParser(object): 'vars': ansible_vars_dict, 'templates': [str], 'merge-mode': vs.Any('merge', 'merge-resolve', - 'cherry-pick', 'squash-merge'), + 'cherry-pick', 'squash-merge', + 'rebase'), 'default-branch': str, 'queue': str, str: pipeline_contents, diff --git a/zuul/driver/github/githubreporter.py b/zuul/driver/github/githubreporter.py index 8c5e8944c..e5c5ebfbd 100644 --- a/zuul/driver/github/githubreporter.py +++ b/zuul/driver/github/githubreporter.py @@ -17,9 +17,8 @@ import logging import voluptuous as v import time +from zuul import model from zuul.lib.logutil import get_annotated_logger -from zuul.model import MERGER_MERGE_RESOLVE, MERGER_MERGE, MERGER_MAP, \ - MERGER_SQUASH_MERGE from zuul.reporter import BaseReporter from zuul.exceptions import MergeFailure from zuul.driver.util import scalar_or_list @@ -34,9 +33,10 @@ class GithubReporter(BaseReporter): # Merge modes supported by github merge_modes = { - MERGER_MERGE: 'merge', - MERGER_MERGE_RESOLVE: 'merge', - MERGER_SQUASH_MERGE: 'squash', + model.MERGER_MERGE: 'merge', + model.MERGER_MERGE_RESOLVE: 'merge', + model.MERGER_SQUASH_MERGE: 'squash', + model.MERGER_REBASE: 'rebase', } def __init__(self, driver, connection, pipeline, config=None): @@ -189,7 +189,8 @@ class GithubReporter(BaseReporter): merge_mode = item.current_build_set.getMergeMode() if merge_mode not in self.merge_modes: - mode = [x[0] for x in MERGER_MAP.items() if x[1] == merge_mode][0] + mode = [x[0] for x in model.MERGER_MAP.items() + if x[1] == merge_mode][0] self.log.warning('Merge mode %s not supported by Github', mode) raise MergeFailure('Merge mode %s not supported by Github' % mode) diff --git a/zuul/merger/merger.py b/zuul/merger/merger.py index 34b495fcc..09968eac6 100644 --- a/zuul/merger/merger.py +++ b/zuul/merger/merger.py @@ -582,7 +582,7 @@ class Repo(object): repo.git.merge(*args) return repo.head.commit - def squash_merge(self, item, zuul_event_id=None): + def squashMerge(self, item, zuul_event_id=None): log = get_annotated_logger(self.log, zuul_event_id) repo = self.createRepoObject(zuul_event_id) args = ['--squash', 'FETCH_HEAD'] @@ -594,6 +594,17 @@ class Repo(object): 'Merge change %s,%s' % (item['number'], item['patchset'])) return repo.head.commit + def rebaseMerge(self, item, base, zuul_event_id=None): + log = get_annotated_logger(self.log, zuul_event_id) + repo = self.createRepoObject(zuul_event_id) + args = [base] + ref = item['ref'] + self.fetch(ref, zuul_event_id=zuul_event_id) + log.debug("Rebasing %s with args %s", ref, args) + repo.git.checkout('FETCH_HEAD') + repo.git.rebase(*args) + return repo.head.commit + def fetch(self, ref, zuul_event_id=None): repo = self.createRepoObject(zuul_event_id) # NOTE: The following is currently not applicable, but if we @@ -1029,14 +1040,14 @@ class Merger(object): for message in messages: ref_log.debug(message) - def _mergeChange(self, item, ref, zuul_event_id): + def _mergeChange(self, item, base, zuul_event_id): log = get_annotated_logger(self.log, zuul_event_id) repo = self.getRepo(item['connection'], item['project'], zuul_event_id=zuul_event_id) try: - repo.checkout(ref, zuul_event_id=zuul_event_id) + repo.checkout(base, zuul_event_id=zuul_event_id) except Exception: - log.exception("Unable to checkout %s", ref) + log.exception("Unable to checkout %s", base) return None, None try: @@ -1050,8 +1061,11 @@ class Merger(object): commit = repo.cherryPick(item['ref'], zuul_event_id=zuul_event_id) elif mode == zuul.model.MERGER_SQUASH_MERGE: - commit = repo.squash_merge( + commit = repo.squashMerge( item, zuul_event_id=zuul_event_id) + elif mode == zuul.model.MERGER_REBASE: + commit = repo.rebaseMerge( + item, base, zuul_event_id=zuul_event_id) else: raise Exception("Unsupported merge mode: %s" % mode) except git.GitCommandError: diff --git a/zuul/model.py b/zuul/model.py index 7269dee24..100785885 100644 --- a/zuul/model.py +++ b/zuul/model.py @@ -55,13 +55,15 @@ from zuul.zk.components import COMPONENT_REGISTRY MERGER_MERGE = 1 # "git merge" MERGER_MERGE_RESOLVE = 2 # "git merge -s resolve" MERGER_CHERRY_PICK = 3 # "git cherry-pick" -MERGER_SQUASH_MERGE = 4 # "git merge --squash" +MERGER_SQUASH_MERGE = 4 # "git merge --squash" +MERGER_REBASE = 5 # "git rebase" MERGER_MAP = { 'merge': MERGER_MERGE, 'merge-resolve': MERGER_MERGE_RESOLVE, 'cherry-pick': MERGER_CHERRY_PICK, 'squash-merge': MERGER_SQUASH_MERGE, + 'rebase': MERGER_REBASE, } PRECEDENCE_NORMAL = 0 |