From c9e192b7e5f4b3555837c51c4780f37ac1e7346e Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Mon, 10 Aug 2020 16:57:46 +0100 Subject: [PATCH 01/24] Add support for creating signed commits and signing commits during a rebase --- commit.go | 56 ++++++++++++++++ git_test.go | 31 +++++++++ go.mod | 2 + go.sum | 7 ++ rebase.go | 66 ++++++++++++++++++- rebase_test.go | 171 ++++++++++++++++++++++++++++++++++++++++++++++--- wrapper.c | 5 ++ 7 files changed, 329 insertions(+), 9 deletions(-) create mode 100644 go.sum diff --git a/commit.go b/commit.go index 4262060..6e486be 100644 --- a/commit.go +++ b/commit.go @@ -40,6 +40,62 @@ func (c *Commit) RawMessage() string { return ret } +func (c *Commit) RawHeader() string { + 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() +} + +// CommitSigningCb defines a function type that takes some data to sign and returns (signature, signature_field, error) +type CommitSigningCb func(string) (string, string, error) + +// WithSignatureUsing creates a new signed commit from this one using the given signing callback +func (c *Commit) WithSignatureUsing(f CommitSigningCb) (*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) + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_commit_create_with_signature( + oid.toC(), + c.Owner().ptr, + C.CString(totalCommit), + C.CString(signature), + csf, + ) + + runtime.KeepAlive(c) + runtime.KeepAlive(oid) + + if ret < 0 { + return nil, MakeGitError(ret) + } + + return oid, nil +} + func (c *Commit) ExtractSignature() (string, string, error) { var c_signed C.git_buf diff --git a/git_test.go b/git_test.go index 807dcc2..941d224 100644 --- a/git_test.go +++ b/git_test.go @@ -45,7 +45,16 @@ func createBareTestRepo(t *testing.T) *Repository { return repo } +// commitOpts contains any extra options for creating commits in the seed repo +type commitOpts struct { + CommitSigningCb +} + 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") checkFatal(t, err) sig := &Signature{ @@ -69,6 +78,28 @@ func seedTestRepo(t *testing.T, repo *Repository) (*Oid, *Oid) { commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree) checkFatal(t, err) + if opts.CommitSigningCb != nil { + commit, err := repo.LookupCommit(commitId) + checkFatal(t, err) + + signature, signatureField, err := opts.CommitSigningCb(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 } diff --git a/go.mod b/go.mod index ee2b6e7..c190305 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/libgit2/git2go/v30 go 1.13 + +require golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1769e6b --- /dev/null +++ b/go.sum @@ -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= diff --git a/rebase.go b/rebase.go index d29e183..6e45ccd 100644 --- a/rebase.go +++ b/rebase.go @@ -2,6 +2,8 @@ package git /* #include + +extern void _go_git_populate_commit_sign_cb(git_rebase_options *opts); */ import "C" import ( @@ -69,6 +71,59 @@ func newRebaseOperationFromC(c *C.git_rebase_operation) *RebaseOperation { return operation } +//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.SigningCallback == nil { + return C.GIT_PASSTHROUGH + } + + commitContent := C.GoString(_commit_content) + + signature, signatureField, err := opts.SigningCallback(commitContent) + if err != nil { + 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)) + + // over-assign by a byte (see below) + if int(C.git_buf_grow(buf, clen+1)) != 0 { + return errors.New("could not grow buffer") + } + + if int(C.git_buf_set(buf, cstr, clen)) != 0 { + return errors.New("could not set buffer") + } + + // git_buf_set sets 'size' to the 'size' of the buffer to 'clen', but we want it to be clen+1 because after returning it asserts that the buffer ends with a null byte, which Go strings don't + // This avoids having to convert the string to a []byte, then adding a null byte, then doing another copy + buf.size += 1 + + 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 type RebaseOptions struct { Version uint @@ -77,6 +132,7 @@ type RebaseOptions struct { RewriteNotesRef string MergeOptions MergeOptions CheckoutOptions CheckoutOpts + SigningCallback CommitSigningCb } // DefaultRebaseOptions returns a RebaseOptions with default values. @@ -101,6 +157,7 @@ func rebaseOptionsFromC(opts *C.git_rebase_options) RebaseOptions { RewriteNotesRef: C.GoString(opts.rewrite_notes_ref), MergeOptions: mergeOptionsFromC(&opts.merge_options), CheckoutOptions: checkoutOptionsFromC(&opts.checkout_options), + // TODO: is it possible to take the callback from C and have it be meaningful in Go? Would we ever want to do it? } } @@ -108,7 +165,7 @@ func (ro *RebaseOptions) toC() *C.git_rebase_options { if ro == nil { return nil } - return &C.git_rebase_options{ + opts := &C.git_rebase_options{ version: C.uint(ro.Version), quiet: C.int(ro.Quiet), inmemory: C.int(ro.InMemory), @@ -116,6 +173,13 @@ func (ro *RebaseOptions) toC() *C.git_rebase_options { merge_options: *ro.MergeOptions.toC(), checkout_options: *ro.CheckoutOptions.toC(), } + + if ro.SigningCallback != nil { + C._go_git_populate_commit_sign_cb(opts) + opts.payload = pointerHandles.Track(*ro) + } + + return opts } func mapEmptyStringToNull(ref string) *C.char { diff --git a/rebase_test.go b/rebase_test.go index ef4f920..2c9f5c9 100644 --- a/rebase_test.go +++ b/rebase_test.go @@ -1,10 +1,15 @@ package git import ( + "bytes" "errors" "strconv" + "strings" "testing" "time" + + "golang.org/x/crypto/openpgp" + "golang.org/x/crypto/openpgp/packet" ) // Tests @@ -48,7 +53,7 @@ func TestRebaseAbort(t *testing.T) { assertStringList(t, expectedHistory, actualHistory) // Rebase onto master - rebase, err := performRebaseOnto(repo, "master") + rebase, err := performRebaseOnto(repo, "master", nil) checkFatal(t, err) defer rebase.Free() @@ -104,7 +109,7 @@ func TestRebaseNoConflicts(t *testing.T) { } // Rebase onto master - rebase, err := performRebaseOnto(repo, "master") + rebase, err := performRebaseOnto(repo, "master", nil) checkFatal(t, err) defer rebase.Free() @@ -130,11 +135,131 @@ func TestRebaseNoConflicts(t *testing.T) { actualHistory, err := commitMsgsList(repo) checkFatal(t, err) 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.SigningCallback = signCommitContent + + commitOpts := commitOpts{ + CommitSigningCb: 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 = setupRepoForRebaseOpts(repo, masterCommit, branchName, commitOpts) + checkFatal(t, err) + + // Create several commits in emile + for _, commit := range emileCommits { + _, err = commitSomethingOpts(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.Parent(0) + defer parent.Free() + + 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 { + signature, signedData, err := commit.ExtractSignature() + if err != nil { + t.Logf("No signature on commit\n%s", commit.RawHeader()+"\n"+commit.RawMessage()) + 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.RawHeader()+"\n"+commit.RawMessage()) + return err + } + + return nil } // Utils func setupRepoForRebase(repo *Repository, masterCommit, branchName string) error { + return setupRepoForRebaseOpts(repo, masterCommit, branchName, commitOpts{}) +} + +func setupRepoForRebaseOpts(repo *Repository, masterCommit, branchName string, opts commitOpts) error { // Create a new branch from master err := createBranch(repo, branchName) if err != nil { @@ -142,7 +267,7 @@ func setupRepoForRebase(repo *Repository, masterCommit, branchName string) error } // Create a commit in master - _, err = commitSomething(repo, masterCommit, masterCommit) + _, err = commitSomethingOpts(repo, masterCommit, masterCommit, opts) if err != nil { return err } @@ -161,7 +286,7 @@ func setupRepoForRebase(repo *Repository, masterCommit, branchName string) error 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) if err != nil { return nil, err @@ -175,7 +300,7 @@ func performRebaseOnto(repo *Repository, branch string) (*Rebase, error) { defer onto.Free() // Init rebase - rebase, err := repo.InitRebase(nil, nil, onto, nil) + rebase, err := repo.InitRebase(nil, nil, onto, opts) if err != nil { return nil, err } @@ -277,6 +402,10 @@ func headTree(repo *Repository) (*Tree, error) { } func commitSomething(repo *Repository, something, content string) (*Oid, error) { + return commitSomethingOpts(repo, something, content, commitOpts{}) +} + +func commitSomethingOpts(repo *Repository, something, content string, commitOpts commitOpts) (*Oid, error) { headCommit, err := headCommit(repo) if err != nil { return nil, err @@ -315,14 +444,40 @@ func commitSomething(repo *Repository, something, content string) (*Oid, error) } defer newTree.Free() - if err != nil { - return nil, err - } commit, err := repo.CreateCommit("HEAD", signature(), signature(), "Test rebase, Baby! "+something, newTree, headCommit) if err != nil { return nil, err } + if commitOpts.CommitSigningCb != nil { + commit, err := repo.LookupCommit(commit) + if err != nil { + return nil, err + } + + oid, err := commit.WithSignatureUsing(commitOpts.CommitSigningCb) + 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{ Strategy: CheckoutRemoveUntracked | CheckoutForce, } diff --git a/wrapper.c b/wrapper.c index c4a5ff0..90bcf6b 100644 --- a/wrapper.c +++ b/wrapper.c @@ -6,6 +6,11 @@ typedef int (*gogit_submodule_cbk)(git_submodule *sm, const char *name, void *payload); +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) { opts->remote_cb = (git_remote_create_cb)remoteCreateCallback; -- 2.45.2 From fe73d5a76d05cac14327428d7d5acb0ebac75c0d Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Wed, 12 Aug 2020 09:53:33 +0100 Subject: [PATCH 02/24] Add missing modules.txt --- vendor/modules.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 vendor/modules.txt diff --git a/vendor/modules.txt b/vendor/modules.txt new file mode 100644 index 0000000..96d8c65 --- /dev/null +++ b/vendor/modules.txt @@ -0,0 +1,17 @@ +# github.com/davecgh/go-spew v1.1.0 +github.com/davecgh/go-spew/spew +# github.com/pmezard/go-difflib v1.0.0 +github.com/pmezard/go-difflib/difflib +# github.com/stretchr/testify v1.6.1 +github.com/stretchr/testify/assert +github.com/stretchr/testify/require +# golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de +golang.org/x/crypto/cast5 +golang.org/x/crypto/openpgp +golang.org/x/crypto/openpgp/armor +golang.org/x/crypto/openpgp/elgamal +golang.org/x/crypto/openpgp/errors +golang.org/x/crypto/openpgp/packet +golang.org/x/crypto/openpgp/s2k +# gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c +gopkg.in/yaml.v3 -- 2.45.2 From 6f71ba98e0927e49cb3a11269b4dd24eb64b2f4e Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Fri, 14 Aug 2020 11:25:47 +0100 Subject: [PATCH 03/24] Add '-t' flag to go get --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5472f22..ebbc9b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: run: | git submodule update --init 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 }}/... - name: Test env: -- 2.45.2 From 3ba68ac003c17e63060f6021cf77a7ec12621197 Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Fri, 14 Aug 2020 15:41:17 +0100 Subject: [PATCH 04/24] Fix memory leak in Withsignature --- commit.go | 1 + 1 file changed, 1 insertion(+) diff --git a/commit.go b/commit.go index 6e486be..94bb530 100644 --- a/commit.go +++ b/commit.go @@ -73,6 +73,7 @@ func (c *Commit) WithSignature(signature string, signatureField string) (*Oid, e var csf *C.char = nil if signatureField != "" { csf = C.CString(signatureField) + defer C.free(unsafe.Pointer(csf)) } runtime.LockOSThread() -- 2.45.2 From 1ec8a67fe42f321b8427b534d25ee0b8ed1be442 Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Fri, 14 Aug 2020 15:49:12 +0100 Subject: [PATCH 05/24] Fix another memory leak in commit --- commit.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/commit.go b/commit.go index 94bb530..da6cafc 100644 --- a/commit.go +++ b/commit.go @@ -79,11 +79,16 @@ func (c *Commit) WithSignature(signature string, signatureField string) (*Oid, e 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( oid.toC(), c.Owner().ptr, - C.CString(totalCommit), - C.CString(signature), + cTotalCommit, + cSignature, csf, ) -- 2.45.2 From 23fcabf687eb40d1ccad2a9ff30e65903928ebf6 Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Fri, 14 Aug 2020 15:51:43 +0100 Subject: [PATCH 06/24] Make it possible to use a git2go error as a return value from a signing callback and have it propagate --- rebase.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rebase.go b/rebase.go index 6e45ccd..7186b7f 100644 --- a/rebase.go +++ b/rebase.go @@ -86,6 +86,9 @@ func commitSignCallback(_signature *C.git_buf, _signature_field *C.git_buf, _com signature, signatureField, err := opts.SigningCallback(commitContent) if err != nil { + if gitError, ok := err.(*GitError); ok { + return C.int(gitError.Code) + } return C.int(-1) } -- 2.45.2 From 0c942dbd89994a2ef009dd1543b29adb33f5d8d1 Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Fri, 14 Aug 2020 15:53:10 +0100 Subject: [PATCH 07/24] Fix memory leak in rebase --- rebase.go | 1 + 1 file changed, 1 insertion(+) diff --git a/rebase.go b/rebase.go index 7186b7f..0fd05dd 100644 --- a/rebase.go +++ b/rebase.go @@ -95,6 +95,7 @@ func commitSignCallback(_signature *C.git_buf, _signature_field *C.git_buf, _com 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) // over-assign by a byte (see below) if int(C.git_buf_grow(buf, clen+1)) != 0 { -- 2.45.2 From a94318f919e06460f61509aaf841ec3950a5a812 Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Fri, 14 Aug 2020 16:10:48 +0100 Subject: [PATCH 08/24] Manually zero out buffer after reallocing it in rebase --- rebase.go | 3 +++ wrapper.c | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/rebase.go b/rebase.go index 0fd05dd..881c836 100644 --- a/rebase.go +++ b/rebase.go @@ -3,6 +3,7 @@ package git /* #include +extern void _go_git_buf_fill_null(git_buf *buf); extern void _go_git_populate_commit_sign_cb(git_rebase_options *opts); */ import "C" @@ -102,6 +103,8 @@ func commitSignCallback(_signature *C.git_buf, _signature_field *C.git_buf, _com return errors.New("could not grow buffer") } + C._go_git_buf_fill_null(buf) + if int(C.git_buf_set(buf, cstr, clen)) != 0 { return errors.New("could not set buffer") } diff --git a/wrapper.c b/wrapper.c index 90bcf6b..ddb4126 100644 --- a/wrapper.c +++ b/wrapper.c @@ -6,6 +6,11 @@ typedef int (*gogit_submodule_cbk)(git_submodule *sm, const char *name, void *payload); +void _go_git_buf_fill_null(git_buf *buf) +{ + memset(buf->ptr, '\0', buf->asize*sizeof(char)); +} + void _go_git_populate_commit_sign_cb(git_rebase_options *opts) { opts->signing_cb = (git_commit_signing_cb)commitSignCallback; -- 2.45.2 From bb9dcc7be4f46afd51a8e5f78f9523b1a6175bf6 Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Fri, 14 Aug 2020 16:14:15 +0100 Subject: [PATCH 09/24] Fix uneeded extra buf size increment in rebase --- rebase.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/rebase.go b/rebase.go index 881c836..5cc8d73 100644 --- a/rebase.go +++ b/rebase.go @@ -105,14 +105,10 @@ func commitSignCallback(_signature *C.git_buf, _signature_field *C.git_buf, _com C._go_git_buf_fill_null(buf) - if int(C.git_buf_set(buf, cstr, clen)) != 0 { + if int(C.git_buf_set(buf, cstr, clen+1)) != 0 { return errors.New("could not set buffer") } - // git_buf_set sets 'size' to the 'size' of the buffer to 'clen', but we want it to be clen+1 because after returning it asserts that the buffer ends with a null byte, which Go strings don't - // This avoids having to convert the string to a []byte, then adding a null byte, then doing another copy - buf.size += 1 - return nil } -- 2.45.2 From 87253ae40dfbcde9e13889e457473345ce8a6f3b Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Fri, 14 Aug 2020 16:14:37 +0100 Subject: [PATCH 10/24] Remove todo --- rebase.go | 1 - 1 file changed, 1 deletion(-) diff --git a/rebase.go b/rebase.go index 5cc8d73..6195a26 100644 --- a/rebase.go +++ b/rebase.go @@ -160,7 +160,6 @@ func rebaseOptionsFromC(opts *C.git_rebase_options) RebaseOptions { RewriteNotesRef: C.GoString(opts.rewrite_notes_ref), MergeOptions: mergeOptionsFromC(&opts.merge_options), CheckoutOptions: checkoutOptionsFromC(&opts.checkout_options), - // TODO: is it possible to take the callback from C and have it be meaningful in Go? Would we ever want to do it? } } -- 2.45.2 From 8f8ca35359e6204356a763baf1013412e32171b4 Mon Sep 17 00:00:00 2001 From: michael boulton <61595820+mbfr@users.noreply.github.com> Date: Fri, 14 Aug 2020 16:15:45 +0100 Subject: [PATCH 11/24] Update rebase.go Co-authored-by: lhchavez --- rebase.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebase.go b/rebase.go index 6195a26..f626f46 100644 --- a/rebase.go +++ b/rebase.go @@ -178,7 +178,7 @@ func (ro *RebaseOptions) toC() *C.git_rebase_options { if ro.SigningCallback != nil { C._go_git_populate_commit_sign_cb(opts) - opts.payload = pointerHandles.Track(*ro) + opts.payload = pointerHandles.Track(ro) } return opts -- 2.45.2 From 7cb7fb1f084b463ee30061a20286e9f307ee4f83 Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Fri, 14 Aug 2020 16:21:28 +0100 Subject: [PATCH 12/24] Fix conversion after change --- rebase.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebase.go b/rebase.go index f626f46..ac0224c 100644 --- a/rebase.go +++ b/rebase.go @@ -74,7 +74,7 @@ func newRebaseOperationFromC(c *C.git_rebase_operation) *RebaseOperation { //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) + opts, ok := pointerHandles.Get(_payload).(*RebaseOptions) if !ok { panic("invalid sign payload") } -- 2.45.2 From 90e6540bdc5a72ef11c5546b76a41703e7fe5cec Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Fri, 14 Aug 2020 16:22:43 +0100 Subject: [PATCH 13/24] Reuse utility function --- rebase_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rebase_test.go b/rebase_test.go index 2c9f5c9..072b276 100644 --- a/rebase_test.go +++ b/rebase_test.go @@ -241,13 +241,13 @@ func checkAllCommitsSigned(t *testing.T, entity *openpgp.Entity, repo *Repositor func checkCommitSigned(t *testing.T, entity *openpgp.Entity, commit *Commit) error { signature, signedData, err := commit.ExtractSignature() if err != nil { - t.Logf("No signature on commit\n%s", commit.RawHeader()+"\n"+commit.RawMessage()) + 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.RawHeader()+"\n"+commit.RawMessage()) + t.Logf("Commit is not signed correctly\n%s", commit.ContentToSign()) return err } -- 2.45.2 From f36992c389438a270db3e121346317e2899f4533 Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Fri, 14 Aug 2020 16:24:03 +0100 Subject: [PATCH 14/24] Mark function as helper to make stack traces slightly cleaner --- rebase_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rebase_test.go b/rebase_test.go index 072b276..0e2ae4a 100644 --- a/rebase_test.go +++ b/rebase_test.go @@ -239,6 +239,8 @@ func checkAllCommitsSigned(t *testing.T, entity *openpgp.Entity, repo *Repositor } 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()) -- 2.45.2 From 9a59f80e1c6624c614fffc3b1f5bdc29b6a8ebdf Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Fri, 14 Aug 2020 16:28:14 +0100 Subject: [PATCH 15/24] check head commit when checking all commits are signed --- rebase_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/rebase_test.go b/rebase_test.go index 0e2ae4a..b56be72 100644 --- a/rebase_test.go +++ b/rebase_test.go @@ -220,11 +220,9 @@ func TestRebaseGpgSigned(t *testing.T) { func checkAllCommitsSigned(t *testing.T, entity *openpgp.Entity, repo *Repository) { head, err := headCommit(repo) checkFatal(t, err) - defer head.Free() - parent := head.Parent(0) - defer parent.Free() + parent := head err = checkCommitSigned(t, entity, parent) checkFatal(t, err) -- 2.45.2 From a08eb252c34f2a2ef0e94504ec6d1d8cb91607b6 Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Fri, 14 Aug 2020 16:31:49 +0100 Subject: [PATCH 16/24] Remove 'opts' variations of test helpers and just always pass empty options --- rebase_test.go | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/rebase_test.go b/rebase_test.go index b56be72..0a63a90 100644 --- a/rebase_test.go +++ b/rebase_test.go @@ -38,12 +38,12 @@ func TestRebaseAbort(t *testing.T) { seedTestRepo(t, repo) // Setup a repo with 2 branches and a different tree - err := setupRepoForRebase(repo, masterCommit, branchName) + err := setupRepoForRebase(repo, masterCommit, branchName, commitOpts{}) checkFatal(t, err) // Create several commits in emile for _, commit := range emileCommits { - _, err = commitSomething(repo, commit, commit) + _, err = commitSomething(repo, commit, commit, commitOpts{}) checkFatal(t, err) } @@ -99,12 +99,12 @@ func TestRebaseNoConflicts(t *testing.T) { } // Setup a repo with 2 branches and a different tree - err = setupRepoForRebase(repo, masterCommit, branchName) + err = setupRepoForRebase(repo, masterCommit, branchName, commitOpts{}) checkFatal(t, err) // Create several commits in emile for _, commit := range emileCommits { - _, err = commitSomething(repo, commit, commit) + _, err = commitSomething(repo, commit, commit, commitOpts{}) checkFatal(t, err) } @@ -191,12 +191,12 @@ func TestRebaseGpgSigned(t *testing.T) { } // Setup a repo with 2 branches and a different tree - err = setupRepoForRebaseOpts(repo, masterCommit, branchName, commitOpts) + err = setupRepoForRebase(repo, masterCommit, branchName, commitOpts) checkFatal(t, err) // Create several commits in emile for _, commit := range emileCommits { - _, err = commitSomethingOpts(repo, commit, commit, commitOpts) + _, err = commitSomething(repo, commit, commit, commitOpts) checkFatal(t, err) } @@ -255,11 +255,7 @@ func checkCommitSigned(t *testing.T, entity *openpgp.Entity, commit *Commit) err } // Utils -func setupRepoForRebase(repo *Repository, masterCommit, branchName string) error { - return setupRepoForRebaseOpts(repo, masterCommit, branchName, commitOpts{}) -} - -func setupRepoForRebaseOpts(repo *Repository, masterCommit, branchName string, opts commitOpts) error { +func setupRepoForRebase(repo *Repository, masterCommit, branchName string, opts commitOpts) error { // Create a new branch from master err := createBranch(repo, branchName) if err != nil { @@ -267,7 +263,7 @@ func setupRepoForRebaseOpts(repo *Repository, masterCommit, branchName string, o } // Create a commit in master - _, err = commitSomethingOpts(repo, masterCommit, masterCommit, opts) + _, err = commitSomething(repo, masterCommit, masterCommit, opts) if err != nil { return err } @@ -401,11 +397,7 @@ func headTree(repo *Repository) (*Tree, error) { return tree, nil } -func commitSomething(repo *Repository, something, content string) (*Oid, error) { - return commitSomethingOpts(repo, something, content, commitOpts{}) -} - -func commitSomethingOpts(repo *Repository, something, content string, commitOpts commitOpts) (*Oid, error) { +func commitSomething(repo *Repository, something, content string, commitOpts commitOpts) (*Oid, error) { headCommit, err := headCommit(repo) if err != nil { return nil, err -- 2.45.2 From 5d83180aef7009d2313fbd9bf5825678d2941890 Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Tue, 18 Aug 2020 08:20:17 +0100 Subject: [PATCH 17/24] Fix gofmt --- commit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commit.go b/commit.go index da6cafc..c58d2be 100644 --- a/commit.go +++ b/commit.go @@ -79,7 +79,7 @@ func (c *Commit) WithSignature(signature string, signatureField string) (*Oid, e runtime.LockOSThread() defer runtime.UnlockOSThread() - cTotalCommit:=C.CString(totalCommit) + cTotalCommit := C.CString(totalCommit) cSignature := C.CString(signature) defer C.free(unsafe.Pointer(cTotalCommit)) defer C.free(unsafe.Pointer(cSignature)) -- 2.45.2 From e76c970b57392b68997979de5c7e700115e54272 Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Tue, 18 Aug 2020 08:55:17 +0100 Subject: [PATCH 18/24] Fix godoc and make name of signing callback better --- commit.go | 6 +++--- git_test.go | 6 +++--- rebase.go | 20 ++++++++++---------- rebase_test.go | 8 ++++---- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/commit.go b/commit.go index c58d2be..bf12e58 100644 --- a/commit.go +++ b/commit.go @@ -51,11 +51,11 @@ func (c *Commit) ContentToSign() string { return c.RawHeader() + "\n" + c.RawMessage() } -// CommitSigningCb defines a function type that takes some data to sign and returns (signature, signature_field, error) -type CommitSigningCb func(string) (string, string, error) +// 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 CommitSigningCb) (*Oid, error) { +func (c *Commit) WithSignatureUsing(f CommitSigningCallback) (*Oid, error) { signature, signatureField, err := f(c.ContentToSign()) if err != nil { return nil, err diff --git a/git_test.go b/git_test.go index 941d224..91ade73 100644 --- a/git_test.go +++ b/git_test.go @@ -47,7 +47,7 @@ func createBareTestRepo(t *testing.T) *Repository { // commitOpts contains any extra options for creating commits in the seed repo type commitOpts struct { - CommitSigningCb + CommitSigningCallback } func seedTestRepo(t *testing.T, repo *Repository) (*Oid, *Oid) { @@ -78,11 +78,11 @@ func seedTestRepoOpt(t *testing.T, repo *Repository, opts commitOpts) (*Oid, *Oi commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree) checkFatal(t, err) - if opts.CommitSigningCb != nil { + if opts.CommitSigningCallback != nil { commit, err := repo.LookupCommit(commitId) checkFatal(t, err) - signature, signatureField, err := opts.CommitSigningCb(commit.ContentToSign()) + signature, signatureField, err := opts.CommitSigningCallback(commit.ContentToSign()) checkFatal(t, err) oid, err := commit.WithSignature(signature, signatureField) diff --git a/rebase.go b/rebase.go index ac0224c..2144329 100644 --- a/rebase.go +++ b/rebase.go @@ -79,13 +79,13 @@ func commitSignCallback(_signature *C.git_buf, _signature_field *C.git_buf, _com panic("invalid sign payload") } - if opts.SigningCallback == nil { + if opts.CommitSigningCallback == nil { return C.GIT_PASSTHROUGH } commitContent := C.GoString(_commit_content) - signature, signatureField, err := opts.SigningCallback(commitContent) + signature, signatureField, err := opts.CommitSigningCallback(commitContent) if err != nil { if gitError, ok := err.(*GitError); ok { return C.int(gitError.Code) @@ -129,13 +129,13 @@ func commitSignCallback(_signature *C.git_buf, _signature_field *C.git_buf, _com // RebaseOptions are used to tell the rebase machinery how to operate type RebaseOptions struct { - Version uint - Quiet int - InMemory int - RewriteNotesRef string - MergeOptions MergeOptions - CheckoutOptions CheckoutOpts - SigningCallback CommitSigningCb + Version uint + Quiet int + InMemory int + RewriteNotesRef string + MergeOptions MergeOptions + CheckoutOptions CheckoutOpts + CommitSigningCallback CommitSigningCallback } // DefaultRebaseOptions returns a RebaseOptions with default values. @@ -176,7 +176,7 @@ func (ro *RebaseOptions) toC() *C.git_rebase_options { checkout_options: *ro.CheckoutOptions.toC(), } - if ro.SigningCallback != nil { + if ro.CommitSigningCallback != nil { C._go_git_populate_commit_sign_cb(opts) opts.payload = pointerHandles.Track(ro) } diff --git a/rebase_test.go b/rebase_test.go index 0a63a90..a78e6c7 100644 --- a/rebase_test.go +++ b/rebase_test.go @@ -155,10 +155,10 @@ func TestRebaseGpgSigned(t *testing.T) { return cipherText.String(), "", nil } - opts.SigningCallback = signCommitContent + opts.CommitSigningCallback = signCommitContent commitOpts := commitOpts{ - CommitSigningCb: signCommitContent, + CommitSigningCallback: signCommitContent, } // Inputs @@ -441,13 +441,13 @@ func commitSomething(repo *Repository, something, content string, commitOpts com return nil, err } - if commitOpts.CommitSigningCb != nil { + if commitOpts.CommitSigningCallback != nil { commit, err := repo.LookupCommit(commit) if err != nil { return nil, err } - oid, err := commit.WithSignatureUsing(commitOpts.CommitSigningCb) + oid, err := commit.WithSignatureUsing(commitOpts.CommitSigningCallback) if err != nil { return nil, err } -- 2.45.2 From d2137760e0887bd996f437313aba0ba166f7bc52 Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Tue, 18 Aug 2020 15:06:33 +0100 Subject: [PATCH 19/24] simplify setting buffers --- rebase.go | 9 +-------- wrapper.c | 5 ----- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/rebase.go b/rebase.go index 2144329..aa34c9e 100644 --- a/rebase.go +++ b/rebase.go @@ -98,14 +98,7 @@ func commitSignCallback(_signature *C.git_buf, _signature_field *C.git_buf, _com cstr := unsafe.Pointer(C.CString(bufData)) defer C.free(cstr) - // over-assign by a byte (see below) - if int(C.git_buf_grow(buf, clen+1)) != 0 { - return errors.New("could not grow buffer") - } - - C._go_git_buf_fill_null(buf) - - if int(C.git_buf_set(buf, cstr, clen+1)) != 0 { + if int(C.git_buf_set(buf, cstr, clen)) != 0 { return errors.New("could not set buffer") } diff --git a/wrapper.c b/wrapper.c index 0b0f3b4..5f427d9 100644 --- a/wrapper.c +++ b/wrapper.c @@ -12,11 +12,6 @@ void _go_git_populate_apply_cb(git_apply_options *options) options->hunk_cb = (git_apply_hunk_cb)hunkApplyCallback; } -void _go_git_buf_fill_null(git_buf *buf) -{ - memset(buf->ptr, '\0', buf->asize*sizeof(char)); -} - void _go_git_populate_commit_sign_cb(git_rebase_options *opts) { opts->signing_cb = (git_commit_signing_cb)commitSignCallback; -- 2.45.2 From c0ec642fb70eeb73092baaaff79fb3f446f9808f Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Tue, 18 Aug 2020 15:14:04 +0100 Subject: [PATCH 20/24] Fix formatting --- wrapper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrapper.c b/wrapper.c index 5f427d9..90b0e1e 100644 --- a/wrapper.c +++ b/wrapper.c @@ -14,7 +14,7 @@ void _go_git_populate_apply_cb(git_apply_options *options) void _go_git_populate_commit_sign_cb(git_rebase_options *opts) { - opts->signing_cb = (git_commit_signing_cb)commitSignCallback; + opts->signing_cb = (git_commit_signing_cb)commitSignCallback; } void _go_git_populate_remote_cb(git_clone_options *opts) -- 2.45.2 From 971e18b585e2b3e1c34c0394efdefb3794a65a74 Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Tue, 18 Aug 2020 15:16:33 +0100 Subject: [PATCH 21/24] Revert "simplify setting buffers" This reverts commit d2137760e0887bd996f437313aba0ba166f7bc52. Causes libgit2 to abort --- rebase.go | 9 ++++++++- wrapper.c | 5 +++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/rebase.go b/rebase.go index aa34c9e..2144329 100644 --- a/rebase.go +++ b/rebase.go @@ -98,7 +98,14 @@ func commitSignCallback(_signature *C.git_buf, _signature_field *C.git_buf, _com cstr := unsafe.Pointer(C.CString(bufData)) defer C.free(cstr) - if int(C.git_buf_set(buf, cstr, clen)) != 0 { + // over-assign by a byte (see below) + if int(C.git_buf_grow(buf, clen+1)) != 0 { + return errors.New("could not grow buffer") + } + + C._go_git_buf_fill_null(buf) + + if int(C.git_buf_set(buf, cstr, clen+1)) != 0 { return errors.New("could not set buffer") } diff --git a/wrapper.c b/wrapper.c index 90b0e1e..6bdc11a 100644 --- a/wrapper.c +++ b/wrapper.c @@ -12,6 +12,11 @@ void _go_git_populate_apply_cb(git_apply_options *options) options->hunk_cb = (git_apply_hunk_cb)hunkApplyCallback; } +void _go_git_buf_fill_null(git_buf *buf) +{ + memset(buf->ptr, '\0', buf->asize*sizeof(char)); +} + void _go_git_populate_commit_sign_cb(git_rebase_options *opts) { opts->signing_cb = (git_commit_signing_cb)commitSignCallback; -- 2.45.2 From 051b9cd7b021e95ce65005bd040eea1b78b97971 Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Tue, 18 Aug 2020 15:17:46 +0100 Subject: [PATCH 22/24] godoc --- commit.go | 1 + 1 file changed, 1 insertion(+) diff --git a/commit.go b/commit.go index bf12e58..1c546b3 100644 --- a/commit.go +++ b/commit.go @@ -40,6 +40,7 @@ func (c *Commit) RawMessage() string { return ret } +// RawHeader gets the full raw text of the commit header. func (c *Commit) RawHeader() string { ret := C.GoString(C.git_commit_raw_header(c.cast_ptr)) runtime.KeepAlive(c) -- 2.45.2 From 0fc857204abec5efb3e9c0538e48c8fed4a3d9d9 Mon Sep 17 00:00:00 2001 From: Michael Boulton Date: Tue, 18 Aug 2020 16:19:50 +0100 Subject: [PATCH 23/24] Remove need to grow/zero buffer and add comment --- rebase.go | 11 +++-------- wrapper.c | 5 ----- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/rebase.go b/rebase.go index 2144329..d685f25 100644 --- a/rebase.go +++ b/rebase.go @@ -3,7 +3,6 @@ package git /* #include -extern void _go_git_buf_fill_null(git_buf *buf); extern void _go_git_populate_commit_sign_cb(git_rebase_options *opts); */ import "C" @@ -98,13 +97,9 @@ func commitSignCallback(_signature *C.git_buf, _signature_field *C.git_buf, _com cstr := unsafe.Pointer(C.CString(bufData)) defer C.free(cstr) - // over-assign by a byte (see below) - if int(C.git_buf_grow(buf, clen+1)) != 0 { - return errors.New("could not grow buffer") - } - - C._go_git_buf_fill_null(buf) - + // 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") } diff --git a/wrapper.c b/wrapper.c index 6bdc11a..90b0e1e 100644 --- a/wrapper.c +++ b/wrapper.c @@ -12,11 +12,6 @@ void _go_git_populate_apply_cb(git_apply_options *options) options->hunk_cb = (git_apply_hunk_cb)hunkApplyCallback; } -void _go_git_buf_fill_null(git_buf *buf) -{ - memset(buf->ptr, '\0', buf->asize*sizeof(char)); -} - void _go_git_populate_commit_sign_cb(git_rebase_options *opts) { opts->signing_cb = (git_commit_signing_cb)commitSignCallback; -- 2.45.2 From 8c92ef3805cb77ecfced8fc04f42780e8ee7d52d Mon Sep 17 00:00:00 2001 From: lhchavez Date: Tue, 18 Aug 2020 09:21:41 -0700 Subject: [PATCH 24/24] Delete modules.txt --- vendor/modules.txt | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 vendor/modules.txt diff --git a/vendor/modules.txt b/vendor/modules.txt deleted file mode 100644 index 96d8c65..0000000 --- a/vendor/modules.txt +++ /dev/null @@ -1,17 +0,0 @@ -# github.com/davecgh/go-spew v1.1.0 -github.com/davecgh/go-spew/spew -# github.com/pmezard/go-difflib v1.0.0 -github.com/pmezard/go-difflib/difflib -# github.com/stretchr/testify v1.6.1 -github.com/stretchr/testify/assert -github.com/stretchr/testify/require -# golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de -golang.org/x/crypto/cast5 -golang.org/x/crypto/openpgp -golang.org/x/crypto/openpgp/armor -golang.org/x/crypto/openpgp/elgamal -golang.org/x/crypto/openpgp/errors -golang.org/x/crypto/openpgp/packet -golang.org/x/crypto/openpgp/s2k -# gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c -gopkg.in/yaml.v3 -- 2.45.2