Repository: libgit2/git2go
Pull request #466 (2018-11-15 03:22:28)

Add support for CreateCommitFromIds
This change adds support for CreateCommitFromIds from libgit2.


Patch:
diff --git a/repository.go b/repository.go
index 74924b7..3e0d20f 100644
--- a/repository.go
+++ b/repository.go
@@ -3,6 +3,8 @@ package git
 /*
 #include <git2.h>
 #include <git2/sys/repository.h>
+#include <git2/sys/commit.h>
+#include <string.h>
 */
 import "C"
 import (
@@ -389,6 +391,74 @@ func (v *Repository) CreateCommit(
 	return oid, nil
 }
 
+func (v *Repository) CreateCommitFromIds(
+	refname string, author, committer *Signature,
+	message string, tree *Oid, parents ...*Oid) (*Oid, error) {
+
+	oid := new(Oid)
+
+	var cref *C.char
+	if refname == "" {
+		cref = nil
+	} else {
+		cref = C.CString(refname)
+		defer C.free(unsafe.Pointer(cref))
+	}
+
+	cmsg := C.CString(message)
+	defer C.free(unsafe.Pointer(cmsg))
+
+	var parentsarg **C.git_oid = nil
+
+	nparents := len(parents)
+	if nparents > 0 {
+		// All this awful pointer arithmetic is needed to avoid passing a Go
+		// pointer to Go pointer into C. Other methods (like CreateCommits) are
+		// fine without this workaround because they are just passing Go pointers
+		// to C pointers, but arrays-of-pointers-to-git_oid are a bit special since
+		// both the array and the objects are allocated from Go.
+		var emptyOidPtr *C.git_oid
+		sizeofOidPtr := unsafe.Sizeof(emptyOidPtr)
+		parentsarg = (**C.git_oid)(C.calloc(C.size_t(uintptr(nparents)), C.size_t(sizeofOidPtr)))
+		defer C.free(unsafe.Pointer(parentsarg))
+		parentsptr := uintptr(unsafe.Pointer(parentsarg))
+		for _, v := range parents {
+			*(**C.git_oid)(unsafe.Pointer(parentsptr)) = v.toC()
+			parentsptr += sizeofOidPtr
+		}
+	}
+
+	authorSig, err := author.toC()
+	if err != nil {
+		return nil, err
+	}
+	defer C.git_signature_free(authorSig)
+
+	committerSig, err := committer.toC()
+	if err != nil {
+		return nil, err
+	}
+	defer C.git_signature_free(committerSig)
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_commit_create_from_ids(
+		oid.toC(), v.ptr, cref,
+		authorSig, committerSig,
+		nil, cmsg, tree.toC(), C.size_t(nparents), parentsarg)
+
+	runtime.KeepAlive(v)
+	runtime.KeepAlive(oid)
+	runtime.KeepAlive(tree)
+	runtime.KeepAlive(parents)
+	if ret < 0 {
+		return nil, MakeGitError(ret)
+	}
+
+	return oid, nil
+}
+
 func (v *Odb) Free() {
 	runtime.SetFinalizer(v, nil)
 	C.git_odb_free(v.ptr)


Test patch:
diff --git a/repository_test.go b/repository_test.go
new file mode 100644
index 0000000..1950c69
--- /dev/null
+++ b/repository_test.go
@@ -0,0 +1,42 @@
+package git
+
+import (
+	"testing"
+	"time"
+)
+
+func TestCreateCommitFromIds(t *testing.T) {
+	t.Parallel()
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+
+	loc, err := time.LoadLocation("Europe/Berlin")
+	checkFatal(t, err)
+	sig := &Signature{
+		Name:  "Rand Om Hacker",
+		Email: "random@hacker.com",
+		When:  time.Date(2013, 03, 06, 14, 30, 0, 0, loc),
+	}
+
+	idx, err := repo.Index()
+	checkFatal(t, err)
+	err = idx.AddByPath("README")
+	checkFatal(t, err)
+	err = idx.Write()
+	checkFatal(t, err)
+	treeId, err := idx.WriteTree()
+	checkFatal(t, err)
+
+	message := "This is a commit\n"
+	tree, err := repo.LookupTree(treeId)
+	checkFatal(t, err)
+	expectedCommitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree)
+	checkFatal(t, err)
+
+	commitId, err := repo.CreateCommitFromIds("", sig, sig, message, treeId)
+	checkFatal(t, err)
+
+	if !expectedCommitId.Equal(commitId) {
+		t.Errorf("mismatched commit ids, expected %v, got %v", expectedCommitId.String(), commitId.String())
+	}
+}