Add support for creating signed commits and signing commits during a rebase #626

Merged
mbfr merged 25 commits from master into master 2020-08-18 11:25:31 -05:00
8 changed files with 338 additions and 22 deletions

View File

@ -35,7 +35,7 @@ jobs:
run: | run: |
git submodule update --init git submodule update --init
make build-libgit2-static make build-libgit2-static
go get -tags static github.com/${{ github.repository }}/... go get -tags static -t github.com/${{ github.repository }}/...
go build -tags static github.com/${{ github.repository }}/... go build -tags static github.com/${{ github.repository }}/...
lhchavez commented 2020-08-14 08:37:47 -05:00 (Migrated from github.com)
Review

yay! it worked :D

yay! it worked :D
- name: Test - name: Test
env: env:

View File

@ -40,6 +40,69 @@ func (c *Commit) RawMessage() string {
return ret return ret
} }
// RawHeader gets the full raw text of the commit header.
func (c *Commit) RawHeader() string {
lhchavez commented 2020-08-18 08:50:58 -05:00 (Migrated from github.com)
Review

one more thing i forgot, can the public functions have a docstring? most of the contents can be copied from the C docs.

one more thing i forgot, can the public functions have a docstring? most of the contents can be copied from the C docs.
mbfr commented 2020-08-18 09:18:00 -05:00 (Migrated from github.com)
Review

Fixed

Fixed
ret := C.GoString(C.git_commit_raw_header(c.cast_ptr))
runtime.KeepAlive(c)
return ret
}
// ContentToSign returns the content that will be passed to a signing function for this commit
func (c *Commit) ContentToSign() string {
return c.RawHeader() + "\n" + c.RawMessage()
}
// CommitSigningCallback defines a function type that takes some data to sign and returns (signature, signature_field, error)
type CommitSigningCallback func(string) (signature, signatureField string, err error)
// WithSignatureUsing creates a new signed commit from this one using the given signing callback
func (c *Commit) WithSignatureUsing(f CommitSigningCallback) (*Oid, error) {
signature, signatureField, err := f(c.ContentToSign())
if err != nil {
return nil, err
}
return c.WithSignature(signature, signatureField)
}
// WithSignature creates a new signed commit from the given signature and signature field
func (c *Commit) WithSignature(signature string, signatureField string) (*Oid, error) {
totalCommit := c.ContentToSign()
oid := new(Oid)
var csf *C.char = nil
if signatureField != "" {
csf = C.CString(signatureField)
lhchavez commented 2020-08-14 08:41:25 -05:00 (Migrated from github.com)
Review

since C.CString() makes a copy, this needs a defer C.free(unsafe.Pointer(csf)) to avoid a memory leak: https://blog.golang.org/cgo#TOC_2.

since `C.CString()` makes a copy, this needs a `defer C.free(unsafe.Pointer(csf))` to avoid a memory leak: https://blog.golang.org/cgo#TOC_2.
mbfr commented 2020-08-14 09:50:05 -05:00 (Migrated from github.com)
Review

Added

Added
defer C.free(unsafe.Pointer(csf))
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cTotalCommit := C.CString(totalCommit)
cSignature := C.CString(signature)
defer C.free(unsafe.Pointer(cTotalCommit))
defer C.free(unsafe.Pointer(cSignature))
ret := C.git_commit_create_with_signature(
lhchavez commented 2020-08-14 08:43:24 -05:00 (Migrated from github.com)
Review

for consistency with Amend() would it be possible to call this variable cerr?

for consistency with [`Amend()`](https://github.com/libgit2/git2go/blob/462ebd83e0ccba9cd93c05ec12dc3d98064e3d5c/commit.go#L160) would it be possible to call this variable `cerr`?
mbfr commented 2020-08-14 09:40:57 -05:00 (Migrated from github.com)
Review

There isn't really a lot of consistency with other functions, sometimes it's called code, sometimes ret, sometimes, ecode, sometimes cerr, etc.

There isn't really a lot of consistency with other functions, sometimes it's called `code`, sometimes `ret`, sometimes, `ecode`, sometimes `cerr`, etc.
oid.toC(),
c.Owner().ptr,
cTotalCommit,
cSignature,
csf,
)
runtime.KeepAlive(c)
runtime.KeepAlive(oid)
if ret < 0 {
return nil, MakeGitError(ret)
}
return oid, nil
}
func (c *Commit) ExtractSignature() (string, string, error) { func (c *Commit) ExtractSignature() (string, string, error) {
var c_signed C.git_buf var c_signed C.git_buf

View File

@ -45,7 +45,16 @@ func createBareTestRepo(t *testing.T) *Repository {
return repo return repo
} }
// commitOpts contains any extra options for creating commits in the seed repo
type commitOpts struct {
CommitSigningCallback
}
func seedTestRepo(t *testing.T, repo *Repository) (*Oid, *Oid) { func seedTestRepo(t *testing.T, repo *Repository) (*Oid, *Oid) {
return seedTestRepoOpt(t, repo, commitOpts{})
}
func seedTestRepoOpt(t *testing.T, repo *Repository, opts commitOpts) (*Oid, *Oid) {
loc, err := time.LoadLocation("Europe/Berlin") loc, err := time.LoadLocation("Europe/Berlin")
checkFatal(t, err) checkFatal(t, err)
sig := &Signature{ sig := &Signature{
@ -69,6 +78,28 @@ func seedTestRepo(t *testing.T, repo *Repository) (*Oid, *Oid) {
commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree) commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree)
checkFatal(t, err) checkFatal(t, err)
if opts.CommitSigningCallback != nil {
commit, err := repo.LookupCommit(commitId)
checkFatal(t, err)
signature, signatureField, err := opts.CommitSigningCallback(commit.ContentToSign())
checkFatal(t, err)
oid, err := commit.WithSignature(signature, signatureField)
checkFatal(t, err)
newCommit, err := repo.LookupCommit(oid)
checkFatal(t, err)
head, err := repo.Head()
checkFatal(t, err)
_, err = repo.References.Create(
head.Name(),
newCommit.Id(),
true,
"repoint to signed commit",
)
checkFatal(t, err)
}
return commitId, treeId return commitId, treeId
} }

2
go.mod
View File

@ -1,3 +1,5 @@
module github.com/libgit2/git2go/v30 module github.com/libgit2/git2go/v30
go 1.13 go 1.13
require golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de

7
go.sum Normal file
View File

@ -0,0 +1,7 @@
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@ -2,6 +2,8 @@ package git
/* /*
#include <git2.h> #include <git2.h>
extern void _go_git_populate_commit_sign_cb(git_rebase_options *opts);
*/ */
import "C" import "C"
import ( import (
@ -69,14 +71,66 @@ func newRebaseOperationFromC(c *C.git_rebase_operation) *RebaseOperation {
return operation return operation
lhchavez commented 2020-08-14 19:04:33 -05:00 (Migrated from github.com)
Review

OK, second try:

		C.git_buf_clear(buf)
		if int(C.git_buf_put(buf, cstr, clen)) != 0 {
			return errors.New("could not set buffer")
		}

git_buf_clear() will remove whatever is in the buffer, and git_buf_put() grows the buffer AND adds the NULL byte at the end.

OK, second try: ```suggestion C.git_buf_clear(buf) if int(C.git_buf_put(buf, cstr, clen)) != 0 { return errors.New("could not set buffer") } ``` [`git_buf_clear()`](https://github.com/libgit2/libgit2/blob/c71321a099373753c22055921c8f538afed5ebb6/src/buffer.c#L152-L163) will remove whatever is in the buffer, and [`git_buf_put()`](https://github.com/libgit2/libgit2/blob/c71321a099373753c22055921c8f538afed5ebb6/src/buffer.c#L223-L238) grows the buffer AND adds the NULL byte at the end.
mbfr commented 2020-08-18 02:51:47 -05:00 (Migrated from github.com)
Review

It seems like those functions aren't defined in the 'buffer.h' header and are also not specified in the documentation, I tried manually adding the extern ... but got an undefined reference error. Are they intended only for internal use?

It seems like those functions aren't defined in the 'buffer.h' header and are also not specified in the [documentation](https://libgit2.org/libgit2/#HEAD/search/git_buf_), I tried manually adding the `extern ...` but got an undefined reference error. Are they intended only for internal use?
lhchavez commented 2020-08-18 08:47:27 -05:00 (Migrated from github.com)
Review

argh, yes u_u they are not marked GIT_EXTERN.

but why is it not possible to use

		if int(C.git_buf_set(buf, cstr, clen)) != 0 {
			return errors.New("could not set buffer")
		}

? git_buf_set resizes the buffer (plus the space for the NULL byte), and adds the trailing NULL byte. That's even independent of whether the original c-string had a NULL pointer or not. (This time around I did test this suggestion locally, so I'm sure this one does definitely work).

argh, yes u_u they are not marked `GIT_EXTERN`. but why is it not possible to use ```suggestion if int(C.git_buf_set(buf, cstr, clen)) != 0 { return errors.New("could not set buffer") } ``` ? [`git_buf_set`](https://github.com/libgit2/libgit2/blob/c71321a099373753c22055921c8f538afed5ebb6/src/buffer.c#L165-L184) resizes the buffer (plus the space for the NULL byte), and adds the trailing NULL byte. That's even independent of whether the original c-string had a NULL pointer or not. (This time around I did test this suggestion locally, so I'm sure this one does definitely work).
mbfr commented 2020-08-18 09:13:14 -05:00 (Migrated from github.com)
Review

I tried this before and although it works in CI (see: github actions on that commit), when running locally I get this error

___go_test_github_com_libgit2_git2go_v30: /build/libgit2/src/libgit2-1.0.1/src/rebase.c:1007: rebase_commit__create: Assertion `git_buf_contains_nul(&commit_signature)' failed.
I tried this before and although it works in CI (see: github actions on that commit), when running locally I get this error ``` ___go_test_github_com_libgit2_git2go_v30: /build/libgit2/src/libgit2-1.0.1/src/rebase.c:1007: rebase_commit__create: Assertion `git_buf_contains_nul(&commit_signature)' failed. ```
lhchavez commented 2020-08-18 10:13:34 -05:00 (Migrated from github.com)
Review

interesting, that means that we need to have at least one CI configuration where assertions are enabled.

okay, what about

		// libgit2 requires the contents of the buffer to be NULL-terminated.
		// C.CString() guarantees that the returned buffer will be
		// NULL-terminated, so we can safely copy the terminator.
		if int(C.git_buf_set(buf, cstr, clen+1)) != 0 {
			return errors.New("could not set buffer")
		}

proof of guarantee: 4149493443/src/cmd/cgo/out.go (L1709-L1716)

interesting, that means that we need to have at least one CI configuration where assertions are enabled. okay, what about ```suggestion // libgit2 requires the contents of the buffer to be NULL-terminated. // C.CString() guarantees that the returned buffer will be // NULL-terminated, so we can safely copy the terminator. if int(C.git_buf_set(buf, cstr, clen+1)) != 0 { return errors.New("could not set buffer") } ``` proof of guarantee: https://github.com/golang/go/blob/4149493443f09c14d9f0fad7030704ed57149b55/src/cmd/cgo/out.go#L1709-L1716
mbfr commented 2020-08-18 10:26:33 -05:00 (Migrated from github.com)
Review

Fixed (and removed the old wrapper)

Fixed (and removed the old wrapper)
} }
//export commitSignCallback
func commitSignCallback(_signature *C.git_buf, _signature_field *C.git_buf, _commit_content *C.char, _payload unsafe.Pointer) C.int {
opts, ok := pointerHandles.Get(_payload).(*RebaseOptions)
if !ok {
panic("invalid sign payload")
}
if opts.CommitSigningCallback == nil {
return C.GIT_PASSTHROUGH
}
commitContent := C.GoString(_commit_content)
signature, signatureField, err := opts.CommitSigningCallback(commitContent)
if err != nil {
if gitError, ok := err.(*GitError); ok {
return C.int(gitError.Code)
}
return C.int(-1)
}
fillBuf := func(bufData string, buf *C.git_buf) error {
clen := C.size_t(len(bufData))
cstr := unsafe.Pointer(C.CString(bufData))
defer C.free(cstr)
// libgit2 requires the contents of the buffer to be NULL-terminated.
// C.CString() guarantees that the returned buffer will be
lhchavez commented 2020-08-14 08:58:38 -05:00 (Migrated from github.com)
Review

in order to support callback authors being able to set a specific libgit2 error, we could do something like:

if gitError, ok := err.(*GitError); ok {
    return C.int(gitError.Code)
}
return C.int(-1)
in order to support callback authors being able to set a specific libgit2 error, we could do something like: ```go if gitError, ok := err.(*GitError); ok { return C.int(gitError.Code) } return C.int(-1) ```
mbfr commented 2020-08-14 09:53:17 -05:00 (Migrated from github.com)
Review

Good idea, added

Good idea, added
// NULL-terminated, so we can safely copy the terminator.
if int(C.git_buf_set(buf, cstr, clen+1)) != 0 {
return errors.New("could not set buffer")
}
lhchavez commented 2020-08-14 08:59:06 -05:00 (Migrated from github.com)
Review

this needs a C.free(...).

this needs a `C.free(...)`.
mbfr commented 2020-08-14 09:53:21 -05:00 (Migrated from github.com)
Review

Fixed

Fixed
return nil
}
if signatureField != "" {
err := fillBuf(signatureField, _signature_field)
if err != nil {
return C.int(-1)
}
}
err = fillBuf(signature, _signature)
if err != nil {
return C.int(-1)
}
return C.GIT_OK
}
// RebaseOptions are used to tell the rebase machinery how to operate // RebaseOptions are used to tell the rebase machinery how to operate
type RebaseOptions struct { type RebaseOptions struct {
Version uint Version uint
Quiet int Quiet int
InMemory int InMemory int
RewriteNotesRef string RewriteNotesRef string
MergeOptions MergeOptions MergeOptions MergeOptions
CheckoutOptions CheckoutOpts CheckoutOptions CheckoutOpts
CommitSigningCallback CommitSigningCallback
} }
// DefaultRebaseOptions returns a RebaseOptions with default values. // DefaultRebaseOptions returns a RebaseOptions with default values.
@ -108,7 +162,7 @@ func (ro *RebaseOptions) toC() *C.git_rebase_options {
if ro == nil { if ro == nil {
return nil return nil
} }
return &C.git_rebase_options{ opts := &C.git_rebase_options{
version: C.uint(ro.Version), version: C.uint(ro.Version),
quiet: C.int(ro.Quiet), quiet: C.int(ro.Quiet),
inmemory: C.int(ro.InMemory), inmemory: C.int(ro.InMemory),
@ -116,6 +170,13 @@ func (ro *RebaseOptions) toC() *C.git_rebase_options {
merge_options: *ro.MergeOptions.toC(), merge_options: *ro.MergeOptions.toC(),
checkout_options: *ro.CheckoutOptions.toC(), checkout_options: *ro.CheckoutOptions.toC(),
} }
if ro.CommitSigningCallback != nil {
C._go_git_populate_commit_sign_cb(opts)
opts.payload = pointerHandles.Track(ro)
}
return opts
} }
func mapEmptyStringToNull(ref string) *C.char { func mapEmptyStringToNull(ref string) *C.char {

View File

@ -1,10 +1,15 @@
package git package git
import ( import (
"bytes"
"errors" "errors"
"strconv" "strconv"
"strings"
"testing" "testing"
"time" "time"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
) )
// Tests // Tests
@ -33,12 +38,12 @@ func TestRebaseAbort(t *testing.T) {
seedTestRepo(t, repo) seedTestRepo(t, repo)
// Setup a repo with 2 branches and a different tree // Setup a repo with 2 branches and a different tree
err := setupRepoForRebase(repo, masterCommit, branchName) err := setupRepoForRebase(repo, masterCommit, branchName, commitOpts{})
checkFatal(t, err) checkFatal(t, err)
// Create several commits in emile // Create several commits in emile
for _, commit := range emileCommits { for _, commit := range emileCommits {
_, err = commitSomething(repo, commit, commit) _, err = commitSomething(repo, commit, commit, commitOpts{})
checkFatal(t, err) checkFatal(t, err)
} }
@ -48,7 +53,7 @@ func TestRebaseAbort(t *testing.T) {
assertStringList(t, expectedHistory, actualHistory) assertStringList(t, expectedHistory, actualHistory)
// Rebase onto master // Rebase onto master
rebase, err := performRebaseOnto(repo, "master") rebase, err := performRebaseOnto(repo, "master", nil)
checkFatal(t, err) checkFatal(t, err)
defer rebase.Free() defer rebase.Free()
@ -94,17 +99,17 @@ func TestRebaseNoConflicts(t *testing.T) {
} }
// Setup a repo with 2 branches and a different tree // Setup a repo with 2 branches and a different tree
err = setupRepoForRebase(repo, masterCommit, branchName) err = setupRepoForRebase(repo, masterCommit, branchName, commitOpts{})
checkFatal(t, err) checkFatal(t, err)
// Create several commits in emile // Create several commits in emile
for _, commit := range emileCommits { for _, commit := range emileCommits {
_, err = commitSomething(repo, commit, commit) _, err = commitSomething(repo, commit, commit, commitOpts{})
checkFatal(t, err) checkFatal(t, err)
} }
// Rebase onto master // Rebase onto master
rebase, err := performRebaseOnto(repo, "master") rebase, err := performRebaseOnto(repo, "master", nil)
checkFatal(t, err) checkFatal(t, err)
defer rebase.Free() defer rebase.Free()
@ -130,11 +135,127 @@ func TestRebaseNoConflicts(t *testing.T) {
actualHistory, err := commitMsgsList(repo) actualHistory, err := commitMsgsList(repo)
checkFatal(t, err) checkFatal(t, err)
assertStringList(t, expectedHistory, actualHistory) assertStringList(t, expectedHistory, actualHistory)
}
func TestRebaseGpgSigned(t *testing.T) {
// TEST DATA
entity, err := openpgp.NewEntity("Namey mcnameface", "test comment", "test@example.com", nil)
checkFatal(t, err)
opts, err := DefaultRebaseOptions()
checkFatal(t, err)
signCommitContent := func(commitContent string) (string, string, error) {
cipherText := new(bytes.Buffer)
err := openpgp.ArmoredDetachSignText(cipherText, entity, strings.NewReader(commitContent), &packet.Config{})
if err != nil {
return "", "", errors.New("error signing payload")
}
return cipherText.String(), "", nil
}
opts.CommitSigningCallback = signCommitContent
commitOpts := commitOpts{
CommitSigningCallback: signCommitContent,
}
// Inputs
branchName := "emile"
masterCommit := "something"
emileCommits := []string{
"fou",
"barre",
"ouich",
}
// Outputs
expectedHistory := []string{
"Test rebase, Baby! " + emileCommits[2],
"Test rebase, Baby! " + emileCommits[1],
"Test rebase, Baby! " + emileCommits[0],
"Test rebase, Baby! " + masterCommit,
"This is a commit\n",
}
// TEST
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
seedTestRepoOpt(t, repo, commitOpts)
// Try to open existing rebase
_, err = repo.OpenRebase(nil)
if err == nil {
t.Fatal("Did not expect to find a rebase in progress")
}
// Setup a repo with 2 branches and a different tree
err = setupRepoForRebase(repo, masterCommit, branchName, commitOpts)
checkFatal(t, err)
// Create several commits in emile
for _, commit := range emileCommits {
_, err = commitSomething(repo, commit, commit, commitOpts)
checkFatal(t, err)
}
// Rebase onto master
rebase, err := performRebaseOnto(repo, "master", &opts)
checkFatal(t, err)
defer rebase.Free()
// Finish the rebase properly
err = rebase.Finish()
checkFatal(t, err)
// Check history is in correct order
actualHistory, err := commitMsgsList(repo)
checkFatal(t, err)
assertStringList(t, expectedHistory, actualHistory)
checkAllCommitsSigned(t, entity, repo)
}
func checkAllCommitsSigned(t *testing.T, entity *openpgp.Entity, repo *Repository) {
head, err := headCommit(repo)
checkFatal(t, err)
defer head.Free()
parent := head
err = checkCommitSigned(t, entity, parent)
checkFatal(t, err)
for parent.ParentCount() != 0 {
parent = parent.Parent(0)
defer parent.Free()
err = checkCommitSigned(t, entity, parent)
checkFatal(t, err)
}
}
func checkCommitSigned(t *testing.T, entity *openpgp.Entity, commit *Commit) error {
t.Helper()
signature, signedData, err := commit.ExtractSignature()
if err != nil {
t.Logf("No signature on commit\n%s", commit.ContentToSign())
return err
}
_, err = openpgp.CheckArmoredDetachedSignature(openpgp.EntityList{entity}, strings.NewReader(signedData), bytes.NewBufferString(signature))
if err != nil {
t.Logf("Commit is not signed correctly\n%s", commit.ContentToSign())
return err
}
return nil
} }
// Utils // Utils
func setupRepoForRebase(repo *Repository, masterCommit, branchName string) error { func setupRepoForRebase(repo *Repository, masterCommit, branchName string, opts commitOpts) error {
// Create a new branch from master // Create a new branch from master
err := createBranch(repo, branchName) err := createBranch(repo, branchName)
if err != nil { if err != nil {
@ -142,7 +263,7 @@ func setupRepoForRebase(repo *Repository, masterCommit, branchName string) error
} }
// Create a commit in master // Create a commit in master
_, err = commitSomething(repo, masterCommit, masterCommit) _, err = commitSomething(repo, masterCommit, masterCommit, opts)
if err != nil { if err != nil {
return err return err
} }
@ -161,7 +282,7 @@ func setupRepoForRebase(repo *Repository, masterCommit, branchName string) error
return nil return nil
} }
func performRebaseOnto(repo *Repository, branch string) (*Rebase, error) { func performRebaseOnto(repo *Repository, branch string, opts *RebaseOptions) (*Rebase, error) {
master, err := repo.LookupBranch(branch, BranchLocal) master, err := repo.LookupBranch(branch, BranchLocal)
if err != nil { if err != nil {
return nil, err return nil, err
@ -175,7 +296,7 @@ func performRebaseOnto(repo *Repository, branch string) (*Rebase, error) {
defer onto.Free() defer onto.Free()
// Init rebase // Init rebase
rebase, err := repo.InitRebase(nil, nil, onto, nil) rebase, err := repo.InitRebase(nil, nil, onto, opts)
if err != nil { if err != nil {
lhchavez commented 2020-08-14 09:19:21 -05:00 (Migrated from github.com)
Review

maybe add a

t.Helper()

as the first line of this function: https://golang.org/pkg/testing/#T.Helper

maybe add a ```go t.Helper() ``` as the first line of this function: https://golang.org/pkg/testing/#T.Helper
mbfr commented 2020-08-14 10:25:10 -05:00 (Migrated from github.com)
Review

Added, should make logs a bit nicer

Added, should make logs a bit nicer
return nil, err return nil, err
} }
@ -276,7 +397,7 @@ func headTree(repo *Repository) (*Tree, error) {
return tree, nil return tree, nil
} }
func commitSomething(repo *Repository, something, content string) (*Oid, error) { func commitSomething(repo *Repository, something, content string, commitOpts commitOpts) (*Oid, error) {
headCommit, err := headCommit(repo) headCommit, err := headCommit(repo)
if err != nil { if err != nil {
return nil, err return nil, err
@ -315,14 +436,40 @@ func commitSomething(repo *Repository, something, content string) (*Oid, error)
} }
defer newTree.Free() defer newTree.Free()
if err != nil {
return nil, err
}
commit, err := repo.CreateCommit("HEAD", signature(), signature(), "Test rebase, Baby! "+something, newTree, headCommit) commit, err := repo.CreateCommit("HEAD", signature(), signature(), "Test rebase, Baby! "+something, newTree, headCommit)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if commitOpts.CommitSigningCallback != nil {
commit, err := repo.LookupCommit(commit)
if err != nil {
return nil, err
}
oid, err := commit.WithSignatureUsing(commitOpts.CommitSigningCallback)
if err != nil {
return nil, err
}
newCommit, err := repo.LookupCommit(oid)
if err != nil {
return nil, err
}
head, err := repo.Head()
if err != nil {
return nil, err
}
_, err = repo.References.Create(
head.Name(),
newCommit.Id(),
true,
"repoint to signed commit",
)
if err != nil {
return nil, err
}
}
opts := &CheckoutOpts{ opts := &CheckoutOpts{
Strategy: CheckoutRemoveUntracked | CheckoutForce, Strategy: CheckoutRemoveUntracked | CheckoutForce,
} }

View File

@ -12,6 +12,11 @@ void _go_git_populate_apply_cb(git_apply_options *options)
options->hunk_cb = (git_apply_hunk_cb)hunkApplyCallback; options->hunk_cb = (git_apply_hunk_cb)hunkApplyCallback;
} }
void _go_git_populate_commit_sign_cb(git_rebase_options *opts)
{
opts->signing_cb = (git_commit_signing_cb)commitSignCallback;
}
void _go_git_populate_remote_cb(git_clone_options *opts) void _go_git_populate_remote_cb(git_clone_options *opts)
{ {
opts->remote_cb = (git_remote_create_cb)remoteCreateCallback; opts->remote_cb = (git_remote_create_cb)remoteCreateCallback;