Add support for creating signed commits (#626)

(cherry picked from commit 7d4453198b)

This is only a partial cherry-pick, since signing commits during a
rebase is not supported in v0.27.
This commit is contained in:
michael boulton 2020-08-18 17:25:31 +01:00 committed by lhchavez
parent 9912ed9742
commit 28dc6b7730
5 changed files with 138 additions and 17 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 }}/...
- 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 {
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)
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(
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
} }

View File

@ -108,7 +108,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 +116,8 @@ 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(),
} }
return opts
} }
func mapEmptyStringToNull(ref string) *C.char { func mapEmptyStringToNull(ref string) *C.char {

View File

@ -33,12 +33,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 +48,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 +94,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 +130,10 @@ 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)
} }
// 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 +141,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 +160,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 +174,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 {
return nil, err return nil, err
} }
@ -276,7 +275,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 +314,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,
} }