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:
James E. Blair 2013-08-25 13:17:35 -07:00
parent 1621f352e3
commit 19deff2a49
5 changed files with 49 additions and 21 deletions

View File

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

View File

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

View File

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

View File

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

View File

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