From 3b27e16d08afb7e050dc61d3bbccbdbb9ee613aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 30 Oct 2014 15:01:37 +0100 Subject: [PATCH] Add Repository.merge_commits() This allows you to merge arbitrary commits, returning an index, which is useful when dealing with bare repos in which we want to merge. --- pygit2/decl.h | 33 +++++++++++++++++++++++++++++++++ pygit2/repository.py | 39 +++++++++++++++++++++++++++++++++++++++ test/test_merge.py | 17 +++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/pygit2/decl.h b/pygit2/decl.h index 4a83dc4..b389be9 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -5,6 +5,7 @@ typedef ... git_push; typedef ... git_cred; typedef ... git_object; typedef ... git_tree; +typedef ... git_commit; typedef ... git_index; typedef ... git_diff; typedef ... git_index_conflict_iterator; @@ -274,6 +275,18 @@ typedef struct { const char *new_prefix; } git_diff_options; +typedef struct { + int (*file_signature)( + void **out, const git_diff_file *file, + const char *fullpath, void *payload); + int (*buffer_signature)( + void **out, const git_diff_file *file, + const char *buf, size_t buflen, void *payload); + void (*free_signature)(void *sig, void *payload); + int (*similarity)(int *score, void *siga, void *sigb, void *payload); + void *payload; +} git_diff_similarity_metric; + int git_diff_init_options(git_diff_options *opts, unsigned int version); int git_diff_index_to_workdir(git_diff **diff, git_repository *repo, git_index *index, const git_diff_options *opts); int git_diff_tree_to_index(git_diff **diff, git_repository *repo, git_tree *old_tree, git_index *index, const git_diff_options *opts); @@ -578,3 +591,23 @@ const git_blame_hunk *git_blame_get_hunk_byindex(git_blame *blame, uint32_t inde const git_blame_hunk *git_blame_get_hunk_byline(git_blame *blame, uint32_t lineno); int git_blame_file(git_blame **out, git_repository *repo, const char *path, git_blame_options *options); void git_blame_free(git_blame *blame); + +/* + * Merging + */ + +typedef enum { ... } git_merge_tree_flag_t; + +typedef enum { ... } git_merge_file_favor_t; + +typedef struct { + unsigned int version; + git_merge_tree_flag_t flags; + unsigned int rename_threshold; + unsigned int target_limit; + git_diff_similarity_metric *metric; + git_merge_file_favor_t file_favor; +} git_merge_options; + + +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); diff --git a/pygit2/repository.py b/pygit2/repository.py index a03a83e..a50720b 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -508,6 +508,45 @@ class Repository(_Repository): return Index.from_c(self, cindex) # + # Merging + # + def merge_commits(self, ours, theirs): + """Merge two arbitrary commits + + Arguments: + + ours + The commit to take as "ours" or base. + theirs + The commit which will be merged into "ours" + + Both can be any object which peels to a commit or the id + (string or Oid) of an object which peels to a commit. + + Returns an index with the result of the merge + + """ + + ours_ptr = ffi.new('git_commit **') + theirs_ptr = ffi.new('git_commit **') + cindex = ffi.new('git_index **') + + if is_string(ours) or isinstance(ours, Oid): + ours = self[ours] + if is_string(theirs) or isinstance(theirs, Oid): + theirs = self[theirs] + + ours = ours.peel(Commit) + theirs = theirs.peel(Commit) + + ffi.buffer(ours_ptr)[:] = ours._pointer[:] + ffi.buffer(theirs_ptr)[:] = theirs._pointer[:] + + err = C.git_merge_commits(cindex, self._repo, ours_ptr[0], theirs_ptr[0], ffi.NULL) + check_error(err) + + return Index.from_c(self, cindex) + # # Utility for writing a tree into an archive # def write_archive(self, treeish, archive, timestamp=None): diff --git a/test/test_merge.py b/test/test_merge.py index b2a2574..230ac49 100644 --- a/test/test_merge.py +++ b/test/test_merge.py @@ -141,3 +141,20 @@ class MergeTestWithConflicts(utils.RepoTestCaseForMerging): del idx.conflicts['.gitignore'] self.assertRaises(KeyError, conflicts.__getitem__, '.gitignore') self.assertTrue(idx.conflicts is None) + +class MergeCommitsTest(utils.RepoTestCaseForMerging): + + def test_merge_commits(self): + branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1' + branch_id = self.repo.get(branch_head_hex).id + + merge_index = self.repo.merge_commits(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)