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
This commit is contained in:
parent
e2a472bc97
commit
26b9b0e2fb
@ -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
|
||||
|
||||
|
5
releasenotes/notes/rebase-merge-6b96aa541cd53492.yaml
Normal file
5
releasenotes/notes/rebase-merge-6b96aa541cd53492.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
The :value:`project.merge-mode.rebase` merge-mode is now supported
|
||||
for GitHub.
|
38
tests/fixtures/layouts/gate-github-rebase.yaml
vendored
Normal file
38
tests/fixtures/layouts/gate-github-rebase.yaml
vendored
Normal file
@ -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
|
@ -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')
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user