Skip to content

Commit f0f1e97

Browse files
committed
Merge remote-tracking branch 'jorio/merge-annotate-from-ref'
2 parents c46ab08 + 8c080cd commit f0f1e97

File tree

4 files changed

+66
-14
lines changed

4 files changed

+66
-14
lines changed

pygit2/decl/commit.h

+5
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,9 @@ int git_annotated_commit_lookup(
1313
git_repository *repo,
1414
const git_oid *id);
1515

16+
int git_annotated_commit_from_ref(
17+
git_annotated_commit **out,
18+
git_repository *repo,
19+
const struct git_reference *ref);
20+
1621
void git_annotated_commit_free(git_annotated_commit *commit);

pygit2/repository.py

+35-14
Original file line numberDiff line numberDiff line change
@@ -834,23 +834,28 @@ def merge_trees(
834834

835835
def merge(
836836
self,
837-
id: typing.Union[Oid, str],
837+
source: typing.Union[Reference, Commit, Oid, str],
838838
favor=MergeFavor.NORMAL,
839839
flags=MergeFlag.FIND_RENAMES,
840840
file_flags=MergeFileFlag.DEFAULT,
841841
):
842842
"""
843-
Merges the given id into HEAD.
843+
Merges the given Reference or Commit into HEAD.
844844
845-
Merges the given commit(s) into HEAD, writing the results into the working directory.
845+
Merges the given commit into HEAD, writing the results into the working directory.
846846
Any changes are staged for commit and any conflicts are written to the index.
847847
Callers should inspect the repository's index after this completes,
848848
resolve any conflicts and prepare a commit.
849849
850850
Parameters:
851851
852-
id
853-
The id to merge into HEAD
852+
source
853+
The Reference, Commit, or commit Oid to merge into HEAD.
854+
It is preferable to pass in a Reference, because this enriches the
855+
merge with additional information (for example, Repository.message will
856+
specify the name of the branch being merged).
857+
Previous versions of pygit2 allowed passing in a partial commit
858+
hash as a string; this is deprecated.
854859
855860
favor
856861
An enums.MergeFavor constant specifying how to deal with file-level conflicts.
@@ -862,12 +867,32 @@ def merge(
862867
file_flags
863868
A combination of enums.MergeFileFlag constants.
864869
"""
865-
if not isinstance(id, (str, Oid)):
866-
raise TypeError(f'expected oid (string or <Oid>) got {type(id)}')
867870

868-
id = self[id].id
869-
c_id = ffi.new('git_oid *')
870-
ffi.buffer(c_id)[:] = id.raw[:]
871+
if isinstance(source, Reference):
872+
# Annotated commit from ref
873+
cptr = ffi.new('struct git_reference **')
874+
ffi.buffer(cptr)[:] = source._pointer[:]
875+
commit_ptr = ffi.new('git_annotated_commit **')
876+
err = C.git_annotated_commit_from_ref(commit_ptr, self._repo, cptr[0])
877+
check_error(err)
878+
else:
879+
# Annotated commit from commit id
880+
if isinstance(source, str):
881+
# For backwards compatibility, parse a string as a partial commit hash
882+
oid = self[source].peel(Commit).id
883+
elif isinstance(source, Commit):
884+
oid = source.id
885+
elif isinstance(source, Oid):
886+
oid = source
887+
else:
888+
raise TypeError(
889+
'expected Reference, Commit, Oid, or commit hash string'
890+
)
891+
c_id = ffi.new('git_oid *')
892+
ffi.buffer(c_id)[:] = oid.raw[:]
893+
commit_ptr = ffi.new('git_annotated_commit **')
894+
err = C.git_annotated_commit_lookup(commit_ptr, self._repo, c_id)
895+
check_error(err)
871896

872897
merge_opts = self._merge_options(favor, flags, file_flags)
873898

@@ -877,10 +902,6 @@ def merge(
877902
CheckoutStrategy.SAFE | CheckoutStrategy.RECREATE_MISSING
878903
)
879904

880-
commit_ptr = ffi.new('git_annotated_commit **')
881-
err = C.git_annotated_commit_lookup(commit_ptr, self._repo, c_id)
882-
check_error(err)
883-
884905
err = C.git_merge(self._repo, commit_ptr, 1, merge_opts, checkout_opts)
885906
C.git_annotated_commit_free(commit_ptr[0])
886907
check_error(err)

src/reference.c

+9
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,14 @@ Reference_type__get__(Reference *self)
440440
return pygit2_enum(ReferenceTypeEnum, c_type);
441441
}
442442

443+
PyDoc_STRVAR(Reference__pointer__doc__, "Get the reference's pointer. For internal use only.");
444+
445+
PyObject *
446+
Reference__pointer__get__(Reference *self)
447+
{
448+
/* Bytes means a raw buffer */
449+
return PyBytes_FromStringAndSize((char *) &self->reference, sizeof(git_reference *));
450+
}
443451

444452
PyDoc_STRVAR(Reference_log__doc__,
445453
"log() -> RefLogIter\n"
@@ -668,6 +676,7 @@ PyGetSetDef Reference_getseters[] = {
668676
GETTER(Reference, target),
669677
GETTER(Reference, raw_target),
670678
GETTER(Reference, type),
679+
GETTER(Reference, _pointer),
671680
{NULL}
672681
};
673682

test/test_merge.py

+17
Original file line numberDiff line numberDiff line change
@@ -344,3 +344,20 @@ def test_merge_remove_message(mergerepo):
344344
assert mergerepo.message.startswith(f"Merge commit '{branch_head_hex}'")
345345
mergerepo.remove_message()
346346
assert not mergerepo.message
347+
348+
349+
def test_merge_commit(mergerepo):
350+
commit = mergerepo['1b2bae55ac95a4be3f8983b86cd579226d0eb247']
351+
mergerepo.merge(commit)
352+
353+
assert mergerepo.message.startswith(f"Merge commit '{str(commit.id)}'")
354+
assert mergerepo.listall_mergeheads() == [commit.id]
355+
356+
357+
def test_merge_reference(mergerepo):
358+
branch = mergerepo.branches.local['branch-conflicts']
359+
branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247'
360+
mergerepo.merge(branch)
361+
362+
assert mergerepo.message.startswith("Merge branch 'branch-conflicts'")
363+
assert mergerepo.listall_mergeheads() == [pygit2.Oid(hex=branch_head_hex)]

0 commit comments

Comments
 (0)