Repository: libgit2/git2go
Pull request #783 (2021-09-04 20:20:51)

Add `Repository.CreateCommitBuffer` (#781)
This commit adds the Go binding for `git_commit_create_buffer`. This
will be used to support the 1.2.0 commit create callback.

(cherry picked from commit fbaf9d1d1ae0bb7b6e7ed9044945d4c9322d4c76)


Patch:
diff --git a/commit.go b/commit.go
index 1c546b3..3a07fa8 100644
--- a/commit.go
+++ b/commit.go
@@ -12,6 +12,14 @@ import (
 	"unsafe"
 )
 
+// MessageEncoding is the encoding of commit messages.
+type MessageEncoding string
+
+const (
+	// MessageEncodingUTF8 is the default message encoding.
+	MessageEncodingUTF8 MessageEncoding = "UTF-8"
+)
+
 // Commit
 type Commit struct {
 	Object
diff --git a/repository.go b/repository.go
index cb82fc3..dc40aaf 100644
--- a/repository.go
+++ b/repository.go
@@ -485,6 +485,69 @@ func (v *Repository) CreateCommit(
 	return oid, nil
 }
 
+// CreateCommitBuffer creates a commit and write it into a buffer.
+func (v *Repository) CreateCommitBuffer(
+	author, committer *Signature,
+	messageEncoding MessageEncoding,
+	message string,
+	tree *Tree,
+	parents ...*Commit,
+) ([]byte, error) {
+	cmsg := C.CString(message)
+	defer C.free(unsafe.Pointer(cmsg))
+	var cencoding *C.char
+	// Since the UTF-8 encoding is the default, pass in nil whenever UTF-8 is
+	// provided. That will cause the commit to not have an explicit header for
+	// it.
+	if messageEncoding != MessageEncodingUTF8 && messageEncoding != MessageEncoding("") {
+		cencoding = C.CString(string(messageEncoding))
+		defer C.free(unsafe.Pointer(cencoding))
+	}
+
+	var cparents []*C.git_commit = nil
+	var parentsarg **C.git_commit = nil
+
+	nparents := len(parents)
+	if nparents > 0 {
+		cparents = make([]*C.git_commit, nparents)
+		for i, v := range parents {
+			cparents[i] = v.cast_ptr
+		}
+		parentsarg = &cparents[0]
+	}
+
+	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()
+
+	var buf C.git_buf
+	defer C.git_buf_dispose(&buf)
+	ret := C.git_commit_create_buffer(
+		&buf, v.ptr,
+		authorSig, committerSig,
+		cencoding, cmsg, tree.cast_ptr, C.size_t(nparents), parentsarg)
+
+	runtime.KeepAlive(v)
+	runtime.KeepAlive(buf)
+	runtime.KeepAlive(parents)
+	if ret < 0 {
+		return nil, MakeGitError(ret)
+	}
+
+	return C.GoBytes(unsafe.Pointer(buf.ptr), C.int(buf.size)), nil
+}
+
 func (v *Repository) CreateCommitFromIds(
 	refname string, author, committer *Signature,
 	message string, tree *Oid, parents ...*Oid) (*Oid, error) {


Test patch:
diff --git a/repository_test.go b/repository_test.go
index e403aa9..60758c1 100644
--- a/repository_test.go
+++ b/repository_test.go
@@ -5,6 +5,60 @@ import (
 	"time"
 )
 
+func TestCreateCommitBuffer(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)
+
+	for encoding, expected := range map[MessageEncoding]string{
+		MessageEncodingUTF8: `tree b7119b11e8ef7a1a5a34d3ac87f5b075228ac81e
+author Rand Om Hacker <random@hacker.com> 1362576600 +0100
+committer Rand Om Hacker <random@hacker.com> 1362576600 +0100
+
+This is a commit
+`,
+		MessageEncoding("ASCII"): `tree b7119b11e8ef7a1a5a34d3ac87f5b075228ac81e
+author Rand Om Hacker <random@hacker.com> 1362576600 +0100
+committer Rand Om Hacker <random@hacker.com> 1362576600 +0100
+encoding ASCII
+
+This is a commit
+`,
+	} {
+		encoding := encoding
+		expected := expected
+		t.Run(string(encoding), func(t *testing.T) {
+			buf, err := repo.CreateCommitBuffer(sig, sig, encoding, message, tree)
+			checkFatal(t, err)
+
+			if expected != string(buf) {
+				t.Errorf("mismatched commit buffer, expected %v, got %v", expected, string(buf))
+			}
+		})
+	}
+}
+
 func TestCreateCommitFromIds(t *testing.T) {
 	t.Parallel()
 	repo := createTestRepo(t)