Skip to content
Snippets Groups Projects

Initial implementation using libgit2

Closed Nick Thomas requested to merge 1-initial-rugged into master
1 unresolved thread
Compare and
35+ files
+ 9296
39763
Compare changes
  • Side-by-side
  • Inline
Files
35+
+ 224
0
package git
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/filemode"
"gopkg.in/src-d/go-git.v4/plumbing/object"
"gopkg.in/src-d/go-git.v4/utils/merkletrie"
"gopkg.in/libgit2/git2go.v25"
)
var (
endError = fmt.Errorf("Finished") // not really an error
ZeroHash = &git.Oid{}
submoduleFilemode uint16 = 0160000
)
type goGitRepository struct {
*git.Repository
type git2GoRepository struct {
Repository *git.Repository
FromHash plumbing.Hash
ToHash plumbing.Hash
FromHash *git.Oid
ToHash *git.Oid
FromCommit *object.Commit
ToCommit *object.Commit
FromCommit *git.Commit
ToCommit *git.Commit
}
func NewGoGitRepository(projectPath string, fromSHA string, toSHA string) (*goGitRepository, error) {
out := &goGitRepository{}
func NewGit2GoRepository(projectPath string, fromSHA string, toSHA string) (*git2GoRepository, error) {
out := &git2GoRepository{}
repo, err := git.PlainOpen(projectPath)
repo, err := git.OpenRepository(projectPath)
if err != nil {
return nil, err
}
out.Repository = repo
out.FromHash = plumbing.NewHash(fromSHA)
if fromSHA == "" {
fromSHA = ZeroHash.String()
}
out.FromHash, err = git.NewOid(fromSHA)
if err != nil {
return nil, err
}
if !out.FromHash.IsZero() {
commit, err := repo.CommitObject(out.FromHash)
commit, err := out.Repository.LookupCommit(out.FromHash)
if err != nil {
return nil, fmt.Errorf("Bad from SHA (%s): %s", out.FromHash, err)
}
@@ -49,12 +57,17 @@ func NewGoGitRepository(projectPath string, fromSHA string, toSHA string) (*goGi
return nil, err
}
out.ToHash = ref.Hash()
out.ToHash = ref.Target()
} else {
out.ToHash = plumbing.NewHash(toSHA)
oid, err := git.NewOid(toSHA)
if err != nil {
return nil, err
}
out.ToHash = oid
}
commit, err := out.Repository.CommitObject(out.ToHash)
commit, err := out.Repository.LookupCommit(out.ToHash)
if err != nil {
return nil, fmt.Errorf("Bad to SHA (%s): %s", out.ToHash, err)
}
@@ -64,8 +77,8 @@ func NewGoGitRepository(projectPath string, fromSHA string, toSHA string) (*goGi
return out, nil
}
func (r *goGitRepository) diff() (object.Changes, error) {
var fromTree, toTree *object.Tree
func (r *git2GoRepository) diff() (*git.Diff, error) {
var fromTree, toTree *git.Tree
if r.FromCommit != nil {
tree, err := r.FromCommit.Tree()
@@ -81,10 +94,19 @@ func (r *goGitRepository) diff() (object.Changes, error) {
return nil, err
}
return object.DiffTree(fromTree, toTree)
defOpts, err := git.DefaultDiffOptions()
if err != nil {
return nil, err
}
opts := &defOpts
opts.IgnoreSubmodules = git.SubmoduleIgnoreAll
opts.Flags = defOpts.Flags | git.DiffIgnoreSubmodules
return r.Repository.DiffTreeToTree(fromTree, toTree, opts)
}
func goGitBuildSignature(sig object.Signature) Signature {
func git2GoBuildSignature(sig *git.Signature) Signature {
return Signature{
Name: sig.Name,
Email: sig.Email,
@@ -92,50 +114,63 @@ func goGitBuildSignature(sig object.Signature) Signature {
}
}
func goGitBuildFile(change object.ChangeEntry, file *object.File) *File {
return &File{
Path: change.Name,
Oid: file.ID().String(),
Blob: file.Blob.Reader,
Size: file.Size,
func (r *git2GoRepository) callout(file git.DiffFile, next FileFunc) error {
blob, err := r.Repository.LookupBlob(file.Oid)
if err != nil {
return err
}
f := &File{
Path: file.Path,
Oid: file.Oid.String(),
Blob: func() (io.ReadCloser, error) {
return ioutil.NopCloser(bytes.NewReader(blob.Contents())), nil
},
Size: blob.Size(),
}
return next(f, r.FromHash.String(), r.ToHash.String())
}
func (r *goGitRepository) EachFileChange(ins, mod, del FileFunc) error {
changes, err := r.diff()
func (r *git2GoRepository) EachFileChange(ins, mod, del FileFunc) error {
diff, err := r.diff()
if err != nil {
return err
}
fromCommitStr := r.FromHash.String()
toCommitStr := r.ToHash.String()
for _, change := range changes {
// FIXME(nick): submodules may need better support
// https://github.com/src-d/go-git/issues/317
if change.From.TreeEntry.Mode == filemode.Submodule || change.To.TreeEntry.Mode == filemode.Submodule {
continue
}
numDeltas, err := diff.NumDeltas()
if err != nil {
return err
}
fromF, toF, err := change.Files()
for i := 0; i < numDeltas; i++ {
delta, err := diff.GetDelta(i)
if err != nil {
return err
}
action, err := change.Action()
if err != nil {
return err
if delta.OldFile.Mode == submoduleFilemode {
continue
}
switch action {
case merkletrie.Insert:
err = ins(goGitBuildFile(change.To, toF), fromCommitStr, toCommitStr)
case merkletrie.Modify:
err = mod(goGitBuildFile(change.To, toF), fromCommitStr, toCommitStr)
case merkletrie.Delete:
err = del(goGitBuildFile(change.From, fromF), fromCommitStr, toCommitStr)
if delta.NewFile.Mode == submoduleFilemode {
continue
}
switch delta.Status {
case git.DeltaAdded, git.DeltaCopied:
err = r.callout(delta.NewFile, ins)
case git.DeltaModified:
err = r.callout(delta.NewFile, mod)
case git.DeltaDeleted:
err = r.callout(delta.OldFile, del)
case git.DeltaRenamed:
err = r.callout(delta.OldFile, del)
if err == nil {
err = r.callout(delta.NewFile, ins)
}
default:
err = fmt.Errorf("Unrecognised change calculating diff: %+v", change)
err = fmt.Errorf("Unrecognised change calculating diff: %+v", delta)
}
if err != nil {
@@ -146,31 +181,44 @@ func (r *goGitRepository) EachFileChange(ins, mod, del FileFunc) error {
return nil
}
// EachCommit runs `f` for each commit within `fromSHA`..`toSHA`
// go-git doesn't directly support ranges of revisions, so we emulate this by
// walking the commit history between from and to
func (r *goGitRepository) EachCommit(f CommitFunc) error {
err := object.WalkCommitHistoryPost(r.ToCommit, func(c *object.Commit) error {
if r.FromCommit != nil && c.ID() == r.FromCommit.ID() {
return endError
}
func (r *git2GoRepository) EachCommit(f CommitFunc) error {
rev, err := r.Repository.Walk()
if err != nil {
return err
}
// defer rev.Free()
if err := rev.Push(r.ToHash); err != nil {
return err
}
var outErr error
iterator := func(c *git.Commit) bool {
commit := &Commit{
Message: c.Message,
Hash: c.Hash.String(),
Author: goGitBuildSignature(c.Author),
Committer: goGitBuildSignature(c.Committer),
Message: c.Message(),
Hash: c.Id().String(),
Author: git2GoBuildSignature(c.Author()),
Committer: git2GoBuildSignature(c.Committer()),
}
if err := f(commit); err != nil {
return err
// abort walking due to reaching the termination point
if c.Id().Equal(r.FromHash) {
return false
}
return nil
})
outErr = f(commit)
if err != nil && err != endError {
// Abort walking due to an error
if outErr != nil {
return false
}
return true
}
if err := rev.Iterate(iterator); err != nil {
return fmt.Errorf("WalkCommitHistory: %s", err)
}
return nil
return outErr
}
Loading