diff --git a/pygit2/decl.h b/pygit2/decl.h index f3ec080..cc990c1 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -657,8 +657,34 @@ typedef struct { git_merge_file_favor_t file_favor; } git_merge_options; +typedef struct { + unsigned int automergeable; + const char *path; + unsigned int mode; + const char *ptr; + size_t len; +} git_merge_file_result; + +typedef enum { + GIT_MERGE_FILE_DEFAULT = 0, + GIT_MERGE_FILE_STYLE_MERGE = 1, + GIT_MERGE_FILE_STYLE_DIFF3 = 2, + GIT_MERGE_FILE_SIMPLIFY_ALNUM = 4, +} git_merge_file_flags_t; + +typedef struct { + unsigned int version; + const char *ancestor_label; + const char *our_label; + const char *their_label; + git_merge_file_favor_t favor; + git_merge_file_flags_t flags; +} git_merge_file_options; + #define GIT_MERGE_OPTIONS_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_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); +int git_merge_file_from_index(git_merge_file_result *out, git_repository *repo, const git_index_entry *ancestor, const git_index_entry *ours, const git_index_entry *theirs, const git_merge_file_options *opts); +void git_merge_file_result_free(git_merge_file_result *result); diff --git a/pygit2/repository.py b/pygit2/repository.py index 05fd7a4..6e04063 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -526,6 +526,40 @@ class Repository(_Repository): return opts + def merge_file_from_index(self, ancestor, ours, theirs): + """merge_file_from_index(ancestor, ours, theirs) -> str + + Merge files from index. + + Return a :py:class:`str` object with the merge result + containing possible conflicts. + + ancestor + The index entry which will be used as a common + ancestor. + ours + The index entry to take as "ours" or base. + theirs + The index entry which will be merged into "ours" + """ + cmergeresult = ffi.new('git_merge_file_result *') + + cancestor = ancestor._to_c()[0] if ancestor is not None else ffi.NULL + cours = ours._to_c()[0] if ours is not None else ffi.NULL + ctheirs = theirs._to_c()[0] if theirs is not None else ffi.NULL + + err = C.git_merge_file_from_index( + cmergeresult, self._repo, + cancestor, cours, ctheirs, + ffi.NULL); + check_error(err) + + ret = ffi.string(cmergeresult.ptr, + cmergeresult.len).decode() + C.git_merge_file_result_free(cmergeresult) + + return ret + def merge_commits(self, ours, theirs, favor='normal'): """Merge two arbitrary commits diff --git a/test/test_repository.py b/test/test_repository.py index 88fbb04..860eaf2 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -198,6 +198,39 @@ class RepositoryTest(utils.BareRepoTestCase): written_sha1 = self.repo.create_blob(data) self.assertEqual(hashed_sha1, written_sha1) + def test_conflicts_in_bare_repository(self): + def create_conflict_file(repo, branch, content): + oid = repo.create_blob(content) + tb = repo.TreeBuilder() + tb.insert('conflict', oid, pygit2.GIT_FILEMODE_BLOB) + tree = tb.write() + + sig = pygit2.Signature('Author', 'author@example.com') + commit = repo.create_commit(branch.name, sig, sig, + 'Conflict', tree, [branch.target]) + self.assertIsNotNone(commit) + return commit + + b1 = self.repo.create_branch('b1', self.repo.head.peel()) + c1 = create_conflict_file(self.repo, b1, 'Conflict 1') + b2 = self.repo.create_branch('b2', self.repo.head.peel()) + c2 = create_conflict_file(self.repo, b2, 'Conflict 2') + + index = self.repo.merge_commits(c1, c2) + self.assertIsNotNone(index.conflicts) + + # ConflictCollection does not allow calling len(...) on it directly so + # we have to calculate length by iterating over its entries + self.assertEqual(sum(1 for _ in index.conflicts), 1) + + (a, t, o) = index.conflicts['conflict'] + diff = self.repo.merge_file_from_index(a, t, o) + self.assertEqual(diff, '''<<<<<<< conflict +Conflict 1 +======= +Conflict 2 +>>>>>>> conflict +''') class RepositoryTest_II(utils.RepoTestCase):