Merge remote-tracking branch 'carlos/merge-trees'
This commit is contained in:
		| @@ -33,3 +33,13 @@ can create a commit with these two parents. | ||||
|    >>> tree = repo.index.write_tree() | ||||
|    >>> new_commit = repo.create_commit('HEAD', user, user, tree, | ||||
|                                        [repo.head.target, other_branch_tip]) | ||||
|  | ||||
| Lower-level methods | ||||
| =================== | ||||
|  | ||||
| These methods allow more direct control over how to perform the | ||||
| merging. They do not modify the working directory and return an | ||||
| in-memory Index representing the result of the merge. | ||||
|  | ||||
| .. automethod:: pygit2.Repository.merge_commits | ||||
| .. automethod:: pygit2.Repository.merge_trees | ||||
|   | ||||
| @@ -661,3 +661,4 @@ typedef struct { | ||||
|  | ||||
| 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_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 | ||||
|     # | ||||
|  | ||||
|     @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'): | ||||
|         """Merge two arbitrary commits | ||||
|  | ||||
| @@ -522,21 +551,9 @@ class Repository(_Repository): | ||||
|         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 **') | ||||
|         theirs_ptr = ffi.new('git_commit **') | ||||
|         opts = ffi.new('git_merge_options *') | ||||
|         cindex = ffi.new('git_index **') | ||||
|  | ||||
|         if is_string(ours) or isinstance(ours, Oid): | ||||
| @@ -547,14 +564,7 @@ class Repository(_Repository): | ||||
|         ours = ours.peel(Commit) | ||||
|         theirs = theirs.peel(Commit) | ||||
|  | ||||
|         err = C.git_merge_init_options(opts, C.GIT_MERGE_OPTIONS_VERSION) | ||||
|         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 | ||||
|         opts = self._merge_options(favor) | ||||
|  | ||||
|         ffi.buffer(ours_ptr)[:] = ours._pointer[:] | ||||
|         ffi.buffer(theirs_ptr)[:] = theirs._pointer[:] | ||||
| @@ -563,6 +573,58 @@ class Repository(_Repository): | ||||
|         check_error(err) | ||||
|  | ||||
|         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 | ||||
|     # | ||||
|   | ||||
| @@ -165,3 +165,29 @@ class MergeCommitsTest(utils.RepoTestCaseForMerging): | ||||
|         self.assertTrue(merge_index.conflicts is None) | ||||
|  | ||||
|         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
	 J. David Ibáñez
					J. David Ibáñez