summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames E. Blair <jim@acmegating.com>2022-10-17 14:27:05 -0700
committerJames E. Blair <jim@acmegating.com>2022-10-17 14:27:05 -0700
commit26b9b0e2fb167e27f1ba74f0d3a6542f37c69d55 (patch)
tree530f0ac0df3918b8d708fdfb924322141c04ec62
parente2a472bc9708c8f7b222b43a75540d64bef77d82 (diff)
downloadzuul-26b9b0e2fb167e27f1ba74f0d3a6542f37c69d55.tar.gz
Add rebase-merge merge mode
GitHub supports a "rebase" merge mode where it will rebase the PR onto the target branch and fast-forward the target branch to the result of the rebase. Add support for this process to the merger so that it can prepare an effective simulated repo, and map the merge-mode to the merge operation in the reporter so that gating behavior matches. This change also makes a few tweaks to the merger to improve consistency (including renaming a variable ref->base), and corrects some typos in the similar squash merge test methods. Change-Id: I9db1d163bafda38204360648bb6781800d2a09b4
-rw-r--r--doc/source/config/project.rst9
-rw-r--r--releasenotes/notes/rebase-merge-6b96aa541cd53492.yaml5
-rw-r--r--tests/fixtures/layouts/gate-github-rebase.yaml38
-rw-r--r--tests/unit/test_github_driver.py52
-rw-r--r--zuul/configloader.py3
-rw-r--r--zuul/driver/github/githubreporter.py13
-rw-r--r--zuul/merger/merger.py24
-rw-r--r--zuul/model.py4
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 ae05779ed..3a0bc3d51 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 67b5e0117..24f79f11f 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