Add support for creating signed commits (#626) #641
|
@ -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:
|
||||
|
|
63
commit.go
63
commit.go
|
@ -40,6 +40,69 @@ 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)
|
||||
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) {
|
||||
|
||||
var c_signed C.git_buf
|
||||
|
|
31
git_test.go
31
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 {
|
||||
CommitSigningCallback
|
||||
}
|
||||
|
||||
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.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
|
||||
}
|
||||
|
||||
|
|
|
@ -108,7 +108,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 +116,8 @@ func (ro *RebaseOptions) toC() *C.git_rebase_options {
|
|||
merge_options: *ro.MergeOptions.toC(),
|
||||
checkout_options: *ro.CheckoutOptions.toC(),
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
func mapEmptyStringToNull(ref string) *C.char {
|
||||
|
|
|
@ -33,12 +33,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)
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,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()
|
||||
|
||||
|
@ -94,17 +94,17 @@ 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)
|
||||
}
|
||||
|
||||
// Rebase onto master
|
||||
rebase, err := performRebaseOnto(repo, "master")
|
||||
rebase, err := performRebaseOnto(repo, "master", nil)
|
||||
checkFatal(t, err)
|
||||
defer rebase.Free()
|
||||
|
||||
|
@ -130,11 +130,10 @@ func TestRebaseNoConflicts(t *testing.T) {
|
|||
actualHistory, err := commitMsgsList(repo)
|
||||
checkFatal(t, err)
|
||||
assertStringList(t, expectedHistory, actualHistory)
|
||||
|
||||
}
|
||||
|
||||
// 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
|
||||
err := createBranch(repo, branchName)
|
||||
if err != nil {
|
||||
|
@ -142,7 +141,7 @@ func setupRepoForRebase(repo *Repository, masterCommit, branchName string) error
|
|||
}
|
||||
|
||||
// Create a commit in master
|
||||
_, err = commitSomething(repo, masterCommit, masterCommit)
|
||||
_, err = commitSomething(repo, masterCommit, masterCommit, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -161,7 +160,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 +174,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
|
||||
}
|
||||
|
@ -276,7 +275,7 @@ func headTree(repo *Repository) (*Tree, error) {
|
|||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -315,14 +314,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.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{
|
||||
Strategy: CheckoutRemoveUntracked | CheckoutForce,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue