@ -95,7 +95,7 @@ func InitMemRepo() *Repo {
func ( r * Repo ) init ( ) error {
h := plumbing . NewSymbolicReference ( plumbing . HEAD , MainRefName )
if err := r . GitRepo . Storer . SetReference ( h ) ; err != nil {
return fmt . Errorf ( "could not set HEAD to %q: %w" , MainRefName , err )
return fmt . Errorf ( "setting HEAD reference to %q: %w" , MainRefName , err )
}
return nil
}
@ -103,7 +103,7 @@ func (r *Repo) init() error {
func ( r * Repo ) billyFilesystem ( ) ( billy . Filesystem , error ) {
w , err := r . GitRepo . Worktree ( )
if err != nil {
return nil , fmt . Errorf ( "could not open git worktree: %w" , err )
return nil , fmt . Errorf ( "opening git worktree: %w" , err )
}
return w . Filesystem , nil
}
@ -114,7 +114,7 @@ func (r *Repo) CheckedOutBranch() (plumbing.ReferenceName, error) {
// newly initialized repo very well.
ogRef , err := r . GitRepo . Storer . Reference ( plumbing . HEAD )
if err != nil {
return "" , fmt . Errorf ( "couldn't de-reference HEAD (is it a bare repo?): %w" , err )
return "" , fmt . Errorf ( "de-referencing HEAD (is it a bare repo?): %w" , err )
}
ref := ogRef
@ -137,39 +137,137 @@ func (r *Repo) CheckedOutBranch() (plumbing.ReferenceName, error) {
return "" , fmt . Errorf ( "could not de-reference HEAD to a branch: %w" , err )
}
func ( r * Repo ) head ( ) ( * object . Commit , * object . Tree , error ) {
head , err := r . GitRepo . Head ( )
// headFS returns an FS based on the HEAD commit, or if there is no HEAD commit
// (it's an empty repo) an FS based on the raw filesystem.
func ( r * Repo ) headFS ( ) ( fs . FS , error ) {
head , err := r . GetGitHead ( )
if errors . Is ( err , ErrNoHead ) {
bfs , err := r . billyFilesystem ( )
if err != nil {
return nil , fmt . Errorf ( "getting underlying filesystem: %w" , err )
}
return fs . FromBillyFilesystem ( bfs ) , nil
} else if err != nil {
return nil , fmt . Errorf ( "could not get HEAD tree: %w" , err )
}
return fs . FromTree ( head . GitTree ) , nil
}
// GitCommit wraps a single git commit object, and also contains various fields
// which are parsed out of it. It is used as a convenience type, in place of
// having to manually retrieve and parse specific information out of commit
// objects.
type GitCommit struct {
GitCommit * object . Commit
// Fields based on that Commit, which can't be directly gleaned from it.
GitTree * object . Tree
Commit Commit
Interface CommitInterface
}
// Root returns true if this commit is the root commit in its branch (i.e. it
// has no parents)
func ( gc GitCommit ) Root ( ) bool {
return gc . GitCommit . NumParents ( ) == 0
}
// GetGitCommit retrieves the commit at the given hash, and all of its sub-data
// which can be pulled out of it.
func ( r * Repo ) GetGitCommit ( h plumbing . Hash ) ( gc GitCommit , err error ) {
if gc . GitCommit , err = r . GitRepo . CommitObject ( h ) ; err != nil {
return gc , fmt . Errorf ( "getting git commit object: %w" , err )
} else if gc . GitTree , err = r . GitRepo . TreeObject ( gc . GitCommit . TreeHash ) ; err != nil {
return gc , fmt . Errorf ( "getting git tree object %q: %w" ,
gc . GitCommit . TreeHash , err )
} else if gc . Commit . UnmarshalText ( [ ] byte ( gc . GitCommit . Message ) ) ; err != nil {
return gc , fmt . Errorf ( "decoding commit message: %w" , err )
} else if gc . Interface , err = gc . Commit . Interface ( ) ; err != nil {
return gc , fmt . Errorf ( "casting %+v to a CommitInterface: %w" , gc . Commit , err )
}
return
}
// GetGitRevision resolves the revision and returns the GitCommit it references.
func ( r * Repo ) GetGitRevision ( rev plumbing . Revision ) ( GitCommit , error ) {
// This returns a pointer for some reason, not sure why.
h , err := r . GitRepo . ResolveRevision ( rev )
if err != nil {
return nil , nil , fmt . Errorf ( "could not get repo HEAD: %w" , err )
return GitCommit { } , fmt . Errorf ( "resolving revision : %w" , err )
}
gc , err := r . GetGitCommit ( * h )
if err != nil {
return GitCommit { } , fmt . Errorf ( "getting commit %q: %w" , * h , err )
}
return gc , nil
}
// ErrNoHead is returns from GetGitHead if there is no HEAD reference defined in
// the repo. This can happen if the repo has no commits
var ErrNoHead = errors . New ( "HEAD reference not found" )
// GetGitHead returns the GitCommit which is currently referenced by HEAD.
// This method may return ErrNoHead if the repo has no commits.
func ( r * Repo ) GetGitHead ( ) ( GitCommit , error ) {
head , err := r . GitRepo . Head ( )
if errors . Is ( err , plumbing . ErrReferenceNotFound ) {
return GitCommit { } , ErrNoHead
} else if err != nil {
return GitCommit { } , fmt . Errorf ( "resolving HEAD: %w" , err )
}
headHash := head . Hash ( )
headCommit , err := r . GitRepo . CommitObject ( headHash )
gc , err := r . GetGitCommit ( headHash )
if err != nil {
return nil , nil , fmt . Errorf ( "could not get commit at HEAD (%q): %w" , headHash , err )
return GitCommit { } , fmt . Errorf ( "getting commit %q: %w" , headHash , err )
}
return gc , nil
}
headTree , err := r . GitRepo . TreeObject ( headCommit . TreeHash )
// GetGitCommitRange returns an ancestry of GitCommits, with the first being the
// commit immediately following the given starting hash, and the last being the
// given ending hash.
//
// If start is plumbing.ZeroHash then the root commit will be the starting one.
func ( r * Repo ) GetGitCommitRange ( start , end plumbing . Hash ) ( [ ] GitCommit , error ) {
curr , err := r . GetGitCommit ( end )
if err != nil {
return nil , nil , fmt . Errorf ( "could not get tree object at HEAD (commit:%q tree:%q): %w" ,
headHash , headCommit . TreeHash , err )
return nil , fmt . Errorf ( "retrieving commit %q: %w" , end , err )
}
return headCommit , headTree , nil
}
var commits [ ] GitCommit
var found bool
for {
commits = append ( commits , curr )
numParents := curr . GitCommit . NumParents ( )
if numParents == 0 {
break
} else if numParents > 1 {
return nil , fmt . Errorf ( "commit %q has more than one parent: %+v" ,
curr . GitCommit . Hash , curr . GitCommit . ParentHashes )
}
// HeadFS returns an FS based on the HEAD commit, or if there is no HEAD commit
// (it's an empty repo) an FS based on the raw filesystem.
func ( r * Repo ) HeadFS ( ) ( fs . FS , error ) {
_ , headTree , err := r . head ( )
if errors . Is ( err , plumbing . ErrReferenceNotFound ) {
bfs , err := r . billyFilesystem ( )
parentHash := curr . GitCommit . ParentHashes [ 0 ]
parent , err := r . GetGitCommit ( parentHash )
if err != nil {
return nil , fmt . Errorf ( "could not get underlying filesystem: %w" , err )
return nil , fmt . Errorf ( "retrieving commit %q: %w" , parentHash , err )
} else if start != plumbing . ZeroHash && parentHash == start {
found = true
break
}
return fs . FromBillyFilesystem ( bfs ) , nil
} else if err != nil {
return nil , fmt . Errorf ( "could not get HEAD tree: %w" , err )
curr = parent
}
if ! found && start != plumbing . ZeroHash {
return nil , fmt . Errorf ( "unable to find commit %q as an ancestor of %q" ,
start , end )
}
// reverse the commits to be in the expected order
for l , r := 0 , len ( commits ) - 1 ; l < r ; l , r = l + 1 , r - 1 {
commits [ l ] , commits [ r ] = commits [ r ] , commits [ l ]
}
return fs . FromTree ( headTree ) , nil
return commits , nil
}