Use the 'resolve' merge strategy by default
We can more closely approximate Gerrit's behavior by using the 'resolve' git merge strategy. Make that the default, and leave the previous behavior ('git merge') as an option. Also, finish and correct the partially implemented plumbing for other merge strategies (including cherry-pick). (Note the previous unfinished implementation attempted to mimic Gerrit's option names; the new implementation does not, but rather documents the alignment. It's not a perfect translation anyway, and this gives us more room to support other strategies not currently supported by Gerrit). Change-Id: Ie1ce4fde5980adf99bba69a5aa1d4e81026db676
This commit is contained in:
parent
1621f352e3
commit
19deff2a49
|
@ -558,6 +558,22 @@ projects. Here is an example::
|
|||
**name**
|
||||
The name of the project (as known by Gerrit).
|
||||
|
||||
**merge-mode (optional)**
|
||||
An optional value that indicates what strategy should be used to
|
||||
merge changes to this project. Supported values are:
|
||||
|
||||
** merge-resolve **
|
||||
Equivalent to 'git merge -s resolve'. This corresponds closely to
|
||||
what Gerrit performs (using JGit) for a project if the "Merge if
|
||||
necessary" merge mode is selected and "Automatically resolve
|
||||
conflicts" is checked. This is the default.
|
||||
|
||||
** merge **
|
||||
Equivalent to 'git merge'.
|
||||
|
||||
** cherry-pick **
|
||||
Equivalent to 'git cherry-pick'.
|
||||
|
||||
This is followed by a section for each of the pipelines defined above.
|
||||
Pipelines may be omitted if no jobs should run for this project in a
|
||||
given pipeline. Within the pipeline section, the jobs that should be
|
||||
|
|
|
@ -161,7 +161,8 @@ class LayoutSchema(object):
|
|||
self.templates_schemas[t_name] = v.Schema(schema)
|
||||
|
||||
project = {'name': str,
|
||||
'merge-mode': v.Any('cherry-pick'),
|
||||
'merge-mode': v.Any('merge', 'merge-resolve,',
|
||||
'cherry-pick'),
|
||||
'template': self.validateTemplateCalls,
|
||||
}
|
||||
|
||||
|
|
|
@ -88,11 +88,15 @@ class Repo(object):
|
|||
self.fetch(ref)
|
||||
self.repo.git.cherry_pick("FETCH_HEAD")
|
||||
|
||||
def merge(self, ref):
|
||||
def merge(self, ref, strategy=None):
|
||||
self._ensure_cloned()
|
||||
self.log.debug("Merging %s" % ref)
|
||||
args = []
|
||||
if strategy:
|
||||
args += ['-s', strategy]
|
||||
args.append('FETCH_HEAD')
|
||||
self.fetch(ref)
|
||||
self.repo.git.merge("FETCH_HEAD")
|
||||
self.log.debug("Merging %s with args %s" % (ref, args))
|
||||
self.repo.git.merge(*args)
|
||||
|
||||
def fetch(self, ref):
|
||||
self._ensure_cloned()
|
||||
|
@ -186,7 +190,7 @@ class Merger(object):
|
|||
except:
|
||||
self.log.exception("Unable to update %s", project)
|
||||
|
||||
def _mergeChange(self, change, ref, target_ref, mode):
|
||||
def _mergeChange(self, change, ref, target_ref):
|
||||
repo = self.getRepo(change.project)
|
||||
try:
|
||||
repo.checkout(ref)
|
||||
|
@ -195,13 +199,16 @@ class Merger(object):
|
|||
return False
|
||||
|
||||
try:
|
||||
if not mode:
|
||||
mode = change.project.merge_mode
|
||||
if mode == model.MERGE_IF_NECESSARY:
|
||||
mode = change.project.merge_mode
|
||||
if mode == model.MERGER_MERGE:
|
||||
repo.merge(change.refspec)
|
||||
elif mode == model.CHERRY_PICK:
|
||||
elif mode == model.MERGER_MERGE_RESOLVE:
|
||||
repo.merge(change.refspec, 'resolve')
|
||||
elif mode == model.MERGER_CHERRY_PICK:
|
||||
repo.cherryPick(change.refspec)
|
||||
except:
|
||||
else:
|
||||
raise Exception("Unsupported merge mode: %s" % mode)
|
||||
except Exception:
|
||||
# Log exceptions at debug level because they are
|
||||
# usually benign merge conflicts
|
||||
self.log.debug("Unable to merge %s" % change, exc_info=True)
|
||||
|
@ -219,7 +226,7 @@ class Merger(object):
|
|||
return False
|
||||
return commit
|
||||
|
||||
def mergeChanges(self, items, target_ref=None, mode=None):
|
||||
def mergeChanges(self, items, target_ref=None):
|
||||
# Merge shortcuts:
|
||||
# if this is the only change just merge it against its branch.
|
||||
# elif there are changes ahead of us that are from the same project and
|
||||
|
@ -244,13 +251,13 @@ class Merger(object):
|
|||
return False
|
||||
commit = self._mergeChange(item.change,
|
||||
repo.getBranchHead(item.change.branch),
|
||||
target_ref=target_ref, mode=mode)
|
||||
target_ref=target_ref)
|
||||
# Sibling changes exist. Merge current change against newest sibling.
|
||||
elif (len(sibling_items) >= 2 and
|
||||
sibling_items[-2].current_build_set.commit):
|
||||
last_commit = sibling_items[-2].current_build_set.commit
|
||||
commit = self._mergeChange(item.change, last_commit,
|
||||
target_ref=target_ref, mode=mode)
|
||||
target_ref=target_ref)
|
||||
# Either change did not merge or we did not need to merge as there were
|
||||
# previous merge conflicts.
|
||||
if not commit:
|
||||
|
|
|
@ -17,10 +17,15 @@ import time
|
|||
from uuid import uuid4
|
||||
|
||||
|
||||
FAST_FORWARD_ONLY = 1
|
||||
MERGE_ALWAYS = 2
|
||||
MERGE_IF_NECESSARY = 3
|
||||
CHERRY_PICK = 4
|
||||
MERGER_MERGE = 1 # "git merge"
|
||||
MERGER_MERGE_RESOLVE = 2 # "git merge -s resolve"
|
||||
MERGER_CHERRY_PICK = 3 # "git cherry-pick"
|
||||
|
||||
MERGER_MAP = {
|
||||
'merge': MERGER_MERGE,
|
||||
'merge-resolve': MERGER_MERGE_RESOLVE,
|
||||
'cherry-pick': MERGER_CHERRY_PICK,
|
||||
}
|
||||
|
||||
PRECEDENCE_NORMAL = 0
|
||||
PRECEDENCE_LOW = 1
|
||||
|
@ -420,7 +425,7 @@ class ChangeQueue(object):
|
|||
class Project(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.merge_mode = MERGE_IF_NECESSARY
|
||||
self.merge_mode = MERGER_MERGE_RESOLVE
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
|
|
@ -238,9 +238,8 @@ class Scheduler(threading.Thread):
|
|||
config_project.update(expanded)
|
||||
|
||||
layout.projects[config_project['name']] = project
|
||||
mode = config_project.get('merge-mode')
|
||||
if mode and mode == 'cherry-pick':
|
||||
project.merge_mode = model.CHERRY_PICK
|
||||
mode = config_project.get('merge-mode', 'merge-resolve')
|
||||
project.merge_mode = model.MERGER_MAP[mode]
|
||||
for pipeline in layout.pipelines.values():
|
||||
if pipeline.name in config_project:
|
||||
job_tree = pipeline.addProject(project)
|
||||
|
|
Loading…
Reference in New Issue