From 8c080cdf177dfee34cfb5691d01fb9f427ca2215 Mon Sep 17 00:00:00 2001 From: Iliyas Jorio Date: Fri, 7 Feb 2025 19:55:06 +0100 Subject: [PATCH] Merge: Annotate commit from Reference --- pygit2/decl/commit.h | 5 +++++ pygit2/repository.py | 49 +++++++++++++++++++++++++++++++------------- src/reference.c | 9 ++++++++ test/test_merge.py | 17 +++++++++++++++ 4 files changed, 66 insertions(+), 14 deletions(-) diff --git a/pygit2/decl/commit.h b/pygit2/decl/commit.h index bc1dd6b1..fc83c6b1 100644 --- a/pygit2/decl/commit.h +++ b/pygit2/decl/commit.h @@ -13,4 +13,9 @@ int git_annotated_commit_lookup( git_repository *repo, const git_oid *id); +int git_annotated_commit_from_ref( + git_annotated_commit **out, + git_repository *repo, + const struct git_reference *ref); + void git_annotated_commit_free(git_annotated_commit *commit); diff --git a/pygit2/repository.py b/pygit2/repository.py index c3270e26..3a4161c2 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -834,23 +834,28 @@ def merge_trees( def merge( self, - id: typing.Union[Oid, str], + source: typing.Union[Reference, Commit, Oid, str], favor=MergeFavor.NORMAL, flags=MergeFlag.FIND_RENAMES, file_flags=MergeFileFlag.DEFAULT, ): """ - Merges the given id into HEAD. + Merges the given Reference or Commit into HEAD. - Merges the given commit(s) into HEAD, writing the results into the working directory. + Merges the given commit into HEAD, writing the results into the working directory. Any changes are staged for commit and any conflicts are written to the index. Callers should inspect the repository's index after this completes, resolve any conflicts and prepare a commit. Parameters: - id - The id to merge into HEAD + source + The Reference, Commit, or commit Oid to merge into HEAD. + It is preferable to pass in a Reference, because this enriches the + merge with additional information (for example, Repository.message will + specify the name of the branch being merged). + Previous versions of pygit2 allowed passing in a partial commit + hash as a string; this is deprecated. favor An enums.MergeFavor constant specifying how to deal with file-level conflicts. @@ -862,12 +867,32 @@ def merge( file_flags A combination of enums.MergeFileFlag constants. """ - if not isinstance(id, (str, Oid)): - raise TypeError(f'expected oid (string or ) got {type(id)}') - id = self[id].id - c_id = ffi.new('git_oid *') - ffi.buffer(c_id)[:] = id.raw[:] + if isinstance(source, Reference): + # Annotated commit from ref + cptr = ffi.new('struct git_reference **') + ffi.buffer(cptr)[:] = source._pointer[:] + commit_ptr = ffi.new('git_annotated_commit **') + err = C.git_annotated_commit_from_ref(commit_ptr, self._repo, cptr[0]) + check_error(err) + else: + # Annotated commit from commit id + if isinstance(source, str): + # For backwards compatibility, parse a string as a partial commit hash + oid = self[source].peel(Commit).id + elif isinstance(source, Commit): + oid = source.id + elif isinstance(source, Oid): + oid = source + else: + raise TypeError( + 'expected Reference, Commit, Oid, or commit hash string' + ) + c_id = ffi.new('git_oid *') + ffi.buffer(c_id)[:] = oid.raw[:] + commit_ptr = ffi.new('git_annotated_commit **') + err = C.git_annotated_commit_lookup(commit_ptr, self._repo, c_id) + check_error(err) merge_opts = self._merge_options(favor, flags, file_flags) @@ -877,10 +902,6 @@ def merge( CheckoutStrategy.SAFE | CheckoutStrategy.RECREATE_MISSING ) - commit_ptr = ffi.new('git_annotated_commit **') - err = C.git_annotated_commit_lookup(commit_ptr, self._repo, c_id) - check_error(err) - err = C.git_merge(self._repo, commit_ptr, 1, merge_opts, checkout_opts) C.git_annotated_commit_free(commit_ptr[0]) check_error(err) diff --git a/src/reference.c b/src/reference.c index 7ba37642..b535cbe4 100644 --- a/src/reference.c +++ b/src/reference.c @@ -440,6 +440,14 @@ Reference_type__get__(Reference *self) return pygit2_enum(ReferenceTypeEnum, c_type); } +PyDoc_STRVAR(Reference__pointer__doc__, "Get the reference's pointer. For internal use only."); + +PyObject * +Reference__pointer__get__(Reference *self) +{ + /* Bytes means a raw buffer */ + return PyBytes_FromStringAndSize((char *) &self->reference, sizeof(git_reference *)); +} PyDoc_STRVAR(Reference_log__doc__, "log() -> RefLogIter\n" @@ -668,6 +676,7 @@ PyGetSetDef Reference_getseters[] = { GETTER(Reference, target), GETTER(Reference, raw_target), GETTER(Reference, type), + GETTER(Reference, _pointer), {NULL} }; diff --git a/test/test_merge.py b/test/test_merge.py index 01409065..08cd588a 100644 --- a/test/test_merge.py +++ b/test/test_merge.py @@ -344,3 +344,20 @@ def test_merge_remove_message(mergerepo): assert mergerepo.message.startswith(f"Merge commit '{branch_head_hex}'") mergerepo.remove_message() assert not mergerepo.message + + +def test_merge_commit(mergerepo): + commit = mergerepo['1b2bae55ac95a4be3f8983b86cd579226d0eb247'] + mergerepo.merge(commit) + + assert mergerepo.message.startswith(f"Merge commit '{str(commit.id)}'") + assert mergerepo.listall_mergeheads() == [commit.id] + + +def test_merge_reference(mergerepo): + branch = mergerepo.branches.local['branch-conflicts'] + branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247' + mergerepo.merge(branch) + + assert mergerepo.message.startswith("Merge branch 'branch-conflicts'") + assert mergerepo.listall_mergeheads() == [pygit2.Oid(hex=branch_head_hex)]