Introduce Repository.merge_trees()
This is the function which does the work for Repository.merge_commits() and allows us more direct control over which trees we want to merge, including trees which do not belong to commits.
This commit is contained in:
@@ -661,3 +661,4 @@ typedef struct {
|
|||||||
|
|
||||||
int git_merge_init_options(git_merge_options *opts, unsigned int version);
|
int git_merge_init_options(git_merge_options *opts, unsigned int version);
|
||||||
int git_merge_commits(git_index **out, git_repository *repo, const git_commit *our_commit, const git_commit *their_commit, const git_merge_options *opts);
|
int git_merge_commits(git_index **out, git_repository *repo, const git_commit *our_commit, const git_commit *their_commit, const git_merge_options *opts);
|
||||||
|
int git_merge_trees(git_index **out, git_repository *repo, const git_tree *ancestor_tree, const git_tree *our_tree, const git_tree *their_tree, const git_merge_options *opts);
|
||||||
|
@@ -497,6 +497,35 @@ class Repository(_Repository):
|
|||||||
#
|
#
|
||||||
# Merging
|
# Merging
|
||||||
#
|
#
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _merge_options(favor):
|
||||||
|
"""Return a 'git_merge_opts *'
|
||||||
|
"""
|
||||||
|
def favor_to_enum(favor):
|
||||||
|
if favor == 'normal':
|
||||||
|
return C.GIT_MERGE_FILE_FAVOR_NORMAL
|
||||||
|
elif favor == 'ours':
|
||||||
|
return C.GIT_MERGE_FILE_FAVOR_OURS
|
||||||
|
elif favor == 'theirs':
|
||||||
|
return C.GIT_MERGE_FILE_FAVOR_THEIRS
|
||||||
|
elif favor == 'union':
|
||||||
|
return C.GIT_MERGE_FILE_FAVOR_UNION
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
favor_val = favor_to_enum(favor)
|
||||||
|
if favor_val is None:
|
||||||
|
raise ValueError("unkown favor value %s" % favor)
|
||||||
|
|
||||||
|
opts = ffi.new('git_merge_options *')
|
||||||
|
err = C.git_merge_init_options(opts, C.GIT_MERGE_OPTIONS_VERSION)
|
||||||
|
check_error(err)
|
||||||
|
|
||||||
|
opts.file_favor = favor_val
|
||||||
|
|
||||||
|
return opts
|
||||||
|
|
||||||
def merge_commits(self, ours, theirs, favor='normal'):
|
def merge_commits(self, ours, theirs, favor='normal'):
|
||||||
"""Merge two arbitrary commits
|
"""Merge two arbitrary commits
|
||||||
|
|
||||||
@@ -522,21 +551,9 @@ class Repository(_Repository):
|
|||||||
Returns an index with the result of the merge
|
Returns an index with the result of the merge
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def favor_to_enum(favor):
|
|
||||||
if favor == 'normal':
|
|
||||||
return C.GIT_MERGE_FILE_FAVOR_NORMAL
|
|
||||||
elif favor == 'ours':
|
|
||||||
return C.GIT_MERGE_FILE_FAVOR_OURS
|
|
||||||
elif favor == 'theirs':
|
|
||||||
return C.GIT_MERGE_FILE_FAVOR_THEIRS
|
|
||||||
elif favor == 'union':
|
|
||||||
return C.GIT_MERGE_FILE_FAVOR_UNION
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
ours_ptr = ffi.new('git_commit **')
|
ours_ptr = ffi.new('git_commit **')
|
||||||
theirs_ptr = ffi.new('git_commit **')
|
theirs_ptr = ffi.new('git_commit **')
|
||||||
opts = ffi.new('git_merge_options *')
|
|
||||||
cindex = ffi.new('git_index **')
|
cindex = ffi.new('git_index **')
|
||||||
|
|
||||||
if is_string(ours) or isinstance(ours, Oid):
|
if is_string(ours) or isinstance(ours, Oid):
|
||||||
@@ -547,14 +564,7 @@ class Repository(_Repository):
|
|||||||
ours = ours.peel(Commit)
|
ours = ours.peel(Commit)
|
||||||
theirs = theirs.peel(Commit)
|
theirs = theirs.peel(Commit)
|
||||||
|
|
||||||
err = C.git_merge_init_options(opts, C.GIT_MERGE_OPTIONS_VERSION)
|
opts = self._merge_options(favor)
|
||||||
check_error(err)
|
|
||||||
|
|
||||||
favor_val = favor_to_enum(favor)
|
|
||||||
if favor_val is None:
|
|
||||||
raise ValueError("unkown favor value %s" % favor)
|
|
||||||
|
|
||||||
opts.file_favor = favor_val
|
|
||||||
|
|
||||||
ffi.buffer(ours_ptr)[:] = ours._pointer[:]
|
ffi.buffer(ours_ptr)[:] = ours._pointer[:]
|
||||||
ffi.buffer(theirs_ptr)[:] = theirs._pointer[:]
|
ffi.buffer(theirs_ptr)[:] = theirs._pointer[:]
|
||||||
@@ -563,6 +573,58 @@ class Repository(_Repository):
|
|||||||
check_error(err)
|
check_error(err)
|
||||||
|
|
||||||
return Index.from_c(self, cindex)
|
return Index.from_c(self, cindex)
|
||||||
|
|
||||||
|
def merge_trees(self, ancestor, ours, theirs, favor='normal'):
|
||||||
|
"""Merge two trees
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
ancestor
|
||||||
|
The tree which is the common ancestor between 'ours' and 'theirs'
|
||||||
|
ours
|
||||||
|
The commit to take as "ours" or base.
|
||||||
|
theirs
|
||||||
|
The commit which will be merged into "ours"
|
||||||
|
favor
|
||||||
|
How to deal with file-level conflicts. Can be one of
|
||||||
|
|
||||||
|
* normal (default). Conflicts will be preserved.
|
||||||
|
* ours. The "ours" side of the conflict region is used.
|
||||||
|
* theirs. The "theirs" side of the conflict region is used.
|
||||||
|
* union. Unique lines from each side will be used.
|
||||||
|
|
||||||
|
for all but NORMAL, the index will not record a conflict.
|
||||||
|
|
||||||
|
Returns an Index that reflects the result of the merge.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ancestor_ptr = ffi.new('git_tree **')
|
||||||
|
ours_ptr = ffi.new('git_tree **')
|
||||||
|
theirs_ptr = ffi.new('git_tree **')
|
||||||
|
cindex = ffi.new('git_index **')
|
||||||
|
|
||||||
|
if is_string(ancestor) or isinstance(ancestor, Oid):
|
||||||
|
ancestor = self[ancestor]
|
||||||
|
if is_string(ours) or isinstance(ours, Oid):
|
||||||
|
ours = self[ours]
|
||||||
|
if is_string(theirs) or isinstance(theirs, Oid):
|
||||||
|
theirs = self[theirs]
|
||||||
|
|
||||||
|
ancestor = ancestor.peel(Tree)
|
||||||
|
ours = ours.peel(Tree)
|
||||||
|
theirs = theirs.peel(Tree)
|
||||||
|
|
||||||
|
opts = self._merge_options(favor)
|
||||||
|
|
||||||
|
ffi.buffer(ancestor_ptr)[:] = ancestor._pointer[:]
|
||||||
|
ffi.buffer(ours_ptr)[:] = ours._pointer[:]
|
||||||
|
ffi.buffer(theirs_ptr)[:] = theirs._pointer[:]
|
||||||
|
|
||||||
|
err = C.git_merge_trees(cindex, self._repo, ancestor_ptr[0], ours_ptr[0], theirs_ptr[0], opts)
|
||||||
|
check_error(err)
|
||||||
|
|
||||||
|
return Index.from_c(self, cindex)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Utility for writing a tree into an archive
|
# Utility for writing a tree into an archive
|
||||||
#
|
#
|
||||||
|
@@ -165,3 +165,29 @@ class MergeCommitsTest(utils.RepoTestCaseForMerging):
|
|||||||
self.assertTrue(merge_index.conflicts is None)
|
self.assertTrue(merge_index.conflicts is None)
|
||||||
|
|
||||||
self.assertRaises(ValueError, self.repo.merge_commits, self.repo.head.target, branch_head_hex, favor='foo')
|
self.assertRaises(ValueError, self.repo.merge_commits, self.repo.head.target, branch_head_hex, favor='foo')
|
||||||
|
|
||||||
|
class MergeTreesTest(utils.RepoTestCaseForMerging):
|
||||||
|
|
||||||
|
def test_merge_trees(self):
|
||||||
|
branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1'
|
||||||
|
branch_id = self.repo.get(branch_head_hex).id
|
||||||
|
ancestor_id = self.repo.merge_base(self.repo.head.target, branch_id)
|
||||||
|
|
||||||
|
merge_index = self.repo.merge_trees(ancestor_id, self.repo.head.target, branch_head_hex)
|
||||||
|
self.assertTrue(merge_index.conflicts is None)
|
||||||
|
merge_commits_tree = merge_index.write_tree(self.repo)
|
||||||
|
|
||||||
|
self.repo.merge(branch_id)
|
||||||
|
index = self.repo.index
|
||||||
|
self.assertTrue(index.conflicts is None)
|
||||||
|
merge_tree = index.write_tree()
|
||||||
|
|
||||||
|
self.assertEqual(merge_tree, merge_commits_tree)
|
||||||
|
|
||||||
|
def test_merge_commits_favor(self):
|
||||||
|
branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247'
|
||||||
|
ancestor_id = self.repo.merge_base(self.repo.head.target, branch_head_hex)
|
||||||
|
merge_index = self.repo.merge_trees(ancestor_id, self.repo.head.target, branch_head_hex, favor='ours')
|
||||||
|
self.assertTrue(merge_index.conflicts is None)
|
||||||
|
|
||||||
|
self.assertRaises(ValueError, self.repo.merge_trees, ancestor_id, self.repo.head.target, branch_head_hex, favor='foo')
|
||||||
|
Reference in New Issue
Block a user