Merge "Add rebase-merge merge mode"

This commit is contained in:
Zuul 2022-10-27 02:25:40 +00:00 committed by Gerrit Code Review
commit 45c04e4c69
8 changed files with 132 additions and 16 deletions

View File

@ -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

View File

@ -0,0 +1,5 @@
---
features:
- |
The :value:`project.merge-mode.rebase` merge-mode is now supported
for GitHub.

View 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

View File

@ -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')

View File

@ -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,

View File

@ -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)

View File

@ -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:

View File

@ -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