@ -6,7 +6,6 @@ import (
"dehub/fs"
"dehub/fs"
"dehub/sigcred"
"dehub/sigcred"
"dehub/typeobj"
"dehub/typeobj"
"encoding"
"encoding/base64"
"encoding/base64"
"errors"
"errors"
"fmt"
"fmt"
@ -177,34 +176,93 @@ func (r *Repo) AccreditCommit(commit Commit, sigInt sigcred.SignifierInterface)
return commit , nil
return commit , nil
}
}
// Commit uses the given TextMarshaler to create a git commit object (with the
// CommitBareParams are the parameters to the CommitBare method. All are
// specified accountID as the author) and commits it to the current HEAD,
// required, unless otherwise noted.
// returning the hash of the commit.
type CommitBareParams struct {
func ( r * Repo ) Commit ( m encoding . TextMarshaler , accountID string ) ( GitCommit , error ) {
Commit Commit
msgB , err := m . MarshalText ( )
AccountID string // used as the author
ParentHash plumbing . Hash // can be zero if the commit has no parents (Q_Q)
GitTree * object . Tree
}
// CommitBare constructs a git commit object and and stores it, returning the
// resulting GitCommit. This method does not interact with HEAD at all.
func ( r * Repo ) CommitBare ( params CommitBareParams ) ( GitCommit , error ) {
msgB , err := params . Commit . MarshalText ( )
if err != nil {
if err != nil {
return GitCommit { } , fmt . Errorf ( "encoding %T to message string: %v" , m , err )
return GitCommit { } , fmt . Errorf ( "encoding %T to message string: %w" ,
params . Commit , err )
}
}
w , err := r . GitRepo . Worktree ( )
author := object . Signature {
Name : params . AccountID ,
When : time . Now ( ) ,
}
commit := & object . Commit {
Author : author ,
Committer : author ,
Message : string ( msgB ) ,
TreeHash : params . GitTree . Hash ,
}
if params . ParentHash != plumbing . ZeroHash {
commit . ParentHashes = [ ] plumbing . Hash { params . ParentHash }
}
commitObj := r . GitRepo . Storer . NewEncodedObject ( )
if err := commit . Encode ( commitObj ) ; err != nil {
return GitCommit { } , fmt . Errorf ( "encoding commit object: %w" , err )
}
commitHash , err := r . GitRepo . Storer . SetEncodedObject ( commitObj )
if err != nil {
if err != nil {
return GitCommit { } , fmt . Errorf ( "getting git worktree: %w" , err )
return GitCommit { } , fmt . Errorf ( "setting encoded object : %w" , err )
}
}
h , err := w . Commit ( string ( msgB ) , & git . CommitOptions {
Author : & object . Signature {
return r . GetGitCommit ( commitHash )
Name : accountID ,
}
When : time . Now ( ) ,
} ,
// Commit uses the given Commit to create a git commit object (with the
// specified accountID as the author) and commits it to the current HEAD,
// returning the full GitCommit.
func ( r * Repo ) Commit ( commit Commit , accountID string ) ( GitCommit , error ) {
headRef , err := r . TraverseReferenceChain ( plumbing . HEAD , func ( ref * plumbing . Reference ) bool {
return ref . Type ( ) == plumbing . HashReference
} )
} )
if err != nil {
if err != nil {
return GitCommit { } , fmt . Errorf ( "committing to git worktree: %w" , err )
return GitCommit { } , fmt . Errorf ( "resolving HEAD to a hash reference: %w" , err )
}
headRefName := headRef . Name ( )
headHash , err := r . ReferenceToHash ( headRefName )
if err != nil {
return GitCommit { } , fmt . Errorf ( "resolving ref %q (HEAD): %w" , headRefName , err )
}
// TODO this is also used in the same way in NewCommitChange. It might make
// sense to refactor this logic out, it might not be needed in fs at all.
_ , stagedTree , err := fs . FromStagedChangesTree ( r . GitRepo )
if err != nil {
return GitCommit { } , fmt . Errorf ( "getting staged changes: %w" , err )
}
}
gc , err := r . GetGitCommit ( h )
gitCommit , err := r . CommitBare ( CommitBareParams {
Commit : commit ,
AccountID : accountID ,
ParentHash : headHash ,
GitTree : stagedTree ,
} )
if err != nil {
if err != nil {
return GitCommit { } , fmt . Errorf ( "retrieving fresh commit %q back from git: %w" , h , err )
return GitCommit { } , err
}
// now set the branch to this new commit
newHeadRef := plumbing . NewHashReference ( headRefName , gitCommit . GitCommit . Hash )
if err := r . GitRepo . Storer . SetReference ( newHeadRef ) ; err != nil {
return GitCommit { } , fmt . Errorf ( "setting reference %q to new commit hash %q: %w" ,
headRefName , gitCommit . GitCommit . Hash , err )
}
}
return gc , nil
return gitCommit , nil
}
}
// HasStagedChanges returns true if there are file changes which have been
// HasStagedChanges returns true if there are file changes which have been
@ -308,20 +366,32 @@ func (r *Repo) VerifyCommits(branch plumbing.ReferenceName, gitCommits []GitComm
return nil
return nil
}
}
// parentTree returns the tree of the parent commit of the given commit. If the
// given commit has no parents then a bare tree is returned.
func ( r * Repo ) parentTree ( commitObj * object . Commit ) ( * object . Tree , error ) {
switch commitObj . NumParents ( ) {
case 0 :
return new ( object . Tree ) , nil
case 1 :
if parentCommitObj , err := commitObj . Parent ( 0 ) ; err != nil {
return nil , fmt . Errorf ( "getting parent commit %q: %w" ,
commitObj . ParentHashes [ 0 ] , err )
} else if parentTree , err := r . GitRepo . TreeObject ( parentCommitObj . TreeHash ) ; err != nil {
return nil , fmt . Errorf ( "getting parent tree object %q: %w" ,
parentCommitObj . TreeHash , err )
} else {
return parentTree , nil
}
default :
return nil , errors . New ( "commit has multiple parents" )
}
}
// if parentTree is nil then it will be inferred.
// if parentTree is nil then it will be inferred.
func ( r * Repo ) verifyCommit ( branch plumbing . ReferenceName , gitCommit GitCommit , parentTree * object . Tree ) error {
func ( r * Repo ) verifyCommit ( branch plumbing . ReferenceName , gitCommit GitCommit , parentTree * object . Tree ) error {
isRoot := gitCommit . Root ( )
parentTree , err := r . parentTree ( gitCommit . GitCommit )
if err != nil {
if parentTree == nil {
return fmt . Errorf ( "retrieving parent tree of commit: %w" , err )
if isRoot {
parentTree = new ( object . Tree )
} else if parentCommit , err := gitCommit . GitCommit . Parent ( 0 ) ; err != nil {
return fmt . Errorf ( "getting parent commit %q: %w" ,
gitCommit . GitCommit . ParentHashes [ 0 ] , err )
} else if parentTree , err = r . GitRepo . TreeObject ( parentCommit . TreeHash ) ; err != nil {
return fmt . Errorf ( "getting parent tree object %q: %w" ,
parentCommit . TreeHash , err )
}
}
}
vctx := verificationCtx {
vctx := verificationCtx {
@ -331,7 +401,7 @@ func (r *Repo) verifyCommit(branch plumbing.ReferenceName, gitCommit GitCommit,
}
}
var sigFS fs . FS
var sigFS fs . FS
if is Root {
if gitComm it . Root ( ) {
sigFS = fs . FromTree ( vctx . commitTree )
sigFS = fs . FromTree ( vctx . commitTree )
} else {
} else {
sigFS = fs . FromTree ( vctx . parentTree )
sigFS = fs . FromTree ( vctx . parentTree )