Skip to content

Commit

Permalink
Merge remote-tracking branch 'jorio/merge-annotate-from-ref'
Browse files Browse the repository at this point in the history
  • Loading branch information
jdavid committed Feb 8, 2025
2 parents c46ab08 + 8c080cd commit f0f1e97
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 14 deletions.
5 changes: 5 additions & 0 deletions pygit2/decl/commit.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
49 changes: 35 additions & 14 deletions pygit2/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 <Oid>) 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)

Expand All @@ -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)
Expand Down
9 changes: 9 additions & 0 deletions src/reference.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -668,6 +676,7 @@ PyGetSetDef Reference_getseters[] = {
GETTER(Reference, target),
GETTER(Reference, raw_target),
GETTER(Reference, type),
GETTER(Reference, _pointer),
{NULL}
};

Expand Down
17 changes: 17 additions & 0 deletions test/test_merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)]

0 comments on commit f0f1e97

Please sign in to comment.