aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--blame.go21
-rw-r--r--blame_test.go26
-rw-r--r--plumbing/format/packfile/packfile.go18
-rw-r--r--plumbing/object/tree.go11
-rw-r--r--plumbing/object/tree_test.go6
-rw-r--r--plumbing/storer/object.go2
-rw-r--r--plumbing/storer/object_test.go10
-rw-r--r--repository.go58
-rw-r--r--repository_test.go30
-rw-r--r--storage/filesystem/object.go73
-rw-r--r--storage/filesystem/object_test.go38
-rw-r--r--storage/memory/storage.go10
12 files changed, 273 insertions, 30 deletions
diff --git a/blame.go b/blame.go
index 349cdd9..adb72d5 100644
--- a/blame.go
+++ b/blame.go
@@ -123,14 +123,25 @@ func newLine(author, text string, date time.Time, hash plumbing.Hash) *Line {
}
func newLines(contents []string, commits []*object.Commit) ([]*Line, error) {
- if len(contents) != len(commits) {
- return nil, errors.New("contents and commits have different length")
+ lcontents := len(contents)
+ lcommits := len(commits)
+
+ if lcontents != lcommits {
+ if lcontents == lcommits-1 && contents[lcontents-1] != "\n" {
+ contents = append(contents, "\n")
+ } else {
+ return nil, errors.New("contents and commits have different length")
+ }
}
- result := make([]*Line, 0, len(contents))
+
+ result := make([]*Line, 0, lcontents)
for i := range contents {
- l := newLine(commits[i].Author.Email, contents[i], commits[i].Author.When, commits[i].Hash)
- result = append(result, l)
+ result = append(result, newLine(
+ commits[i].Author.Email, contents[i],
+ commits[i].Author.When, commits[i].Hash,
+ ))
}
+
return result, nil
}
diff --git a/blame_test.go b/blame_test.go
index 92911b1..e0ac129 100644
--- a/blame_test.go
+++ b/blame_test.go
@@ -2,6 +2,7 @@ package git
import (
"gopkg.in/src-d/go-git.v4/plumbing"
+ "gopkg.in/src-d/go-git.v4/plumbing/object"
. "gopkg.in/check.v1"
"gopkg.in/src-d/go-git-fixtures.v3"
@@ -13,6 +14,31 @@ type BlameSuite struct {
var _ = Suite(&BlameSuite{})
+func (s *BlameSuite) TestNewLines(c *C) {
+ h := plumbing.NewHash("ce9f123d790717599aaeb76bc62510de437761be")
+ lines, err := newLines([]string{"foo"}, []*object.Commit{{
+ Hash: h,
+ Message: "foo",
+ }})
+
+ c.Assert(err, IsNil)
+ c.Assert(lines, HasLen, 1)
+ c.Assert(lines[0].Text, Equals, "foo")
+ c.Assert(lines[0].Hash, Equals, h)
+}
+
+func (s *BlameSuite) TestNewLinesWithNewLine(c *C) {
+ lines, err := newLines([]string{"foo"}, []*object.Commit{
+ {Message: "foo"},
+ {Message: "bar"},
+ })
+
+ c.Assert(err, IsNil)
+ c.Assert(lines, HasLen, 2)
+ c.Assert(lines[0].Text, Equals, "foo")
+ c.Assert(lines[1].Text, Equals, "\n")
+}
+
type blameTest struct {
repo string
rev string
diff --git a/plumbing/format/packfile/packfile.go b/plumbing/format/packfile/packfile.go
index 852a834..0d13066 100644
--- a/plumbing/format/packfile/packfile.go
+++ b/plumbing/format/packfile/packfile.go
@@ -90,6 +90,24 @@ func (p *Packfile) GetByOffset(o int64) (plumbing.EncodedObject, error) {
return p.nextObject()
}
+// GetSizeByOffset retrieves the size of the encoded object from the
+// packfile with the given offset.
+func (p *Packfile) GetSizeByOffset(o int64) (size int64, err error) {
+ if _, err := p.s.SeekFromStart(o); err != nil {
+ if err == io.EOF || isInvalid(err) {
+ return 0, plumbing.ErrObjectNotFound
+ }
+
+ return 0, err
+ }
+
+ h, err := p.nextObjectHeader()
+ if err != nil {
+ return 0, err
+ }
+ return h.Length, nil
+}
+
func (p *Packfile) nextObjectHeader() (*ObjectHeader, error) {
h, err := p.s.NextObjectHeader()
p.s.pendingObject = nil
diff --git a/plumbing/object/tree.go b/plumbing/object/tree.go
index c36a137..78d61a1 100644
--- a/plumbing/object/tree.go
+++ b/plumbing/object/tree.go
@@ -87,6 +87,17 @@ func (t *Tree) File(path string) (*File, error) {
return NewFile(path, e.Mode, blob), nil
}
+// Size returns the plaintext size of an object, without reading it
+// into memory.
+func (t *Tree) Size(path string) (int64, error) {
+ e, err := t.FindEntry(path)
+ if err != nil {
+ return 0, ErrEntryNotFound
+ }
+
+ return t.s.EncodedObjectSize(e.Hash)
+}
+
// Tree returns the tree identified by the `path` argument.
// The path is interpreted as relative to the tree receiver.
func (t *Tree) Tree(path string) (*Tree, error) {
diff --git a/plumbing/object/tree_test.go b/plumbing/object/tree_test.go
index 7366421..889c63a 100644
--- a/plumbing/object/tree_test.go
+++ b/plumbing/object/tree_test.go
@@ -98,6 +98,12 @@ func (s *TreeSuite) TestFileFailsWithExistingTrees(c *C) {
c.Assert(err, Equals, ErrFileNotFound)
}
+func (s *TreeSuite) TestSize(c *C) {
+ size, err := s.Tree.Size("LICENSE")
+ c.Assert(err, IsNil)
+ c.Assert(size, Equals, int64(1072))
+}
+
func (s *TreeSuite) TestFiles(c *C) {
var count int
err := s.Tree.Files().ForEach(func(f *File) error {
diff --git a/plumbing/storer/object.go b/plumbing/storer/object.go
index 92aa629..2ac9b09 100644
--- a/plumbing/storer/object.go
+++ b/plumbing/storer/object.go
@@ -40,6 +40,8 @@ type EncodedObjectStorer interface {
// HasEncodedObject returns ErrObjNotFound if the object doesn't
// exist. If the object does exist, it returns nil.
HasEncodedObject(plumbing.Hash) error
+ // EncodedObjectSize returns the plaintext size of the encoded object.
+ EncodedObjectSize(plumbing.Hash) (int64, error)
}
// DeltaObjectStorer is an EncodedObjectStorer that can return delta
diff --git a/plumbing/storer/object_test.go b/plumbing/storer/object_test.go
index 6b4fe0f..bc22f7b 100644
--- a/plumbing/storer/object_test.go
+++ b/plumbing/storer/object_test.go
@@ -141,6 +141,16 @@ func (o *MockObjectStorage) HasEncodedObject(h plumbing.Hash) error {
return plumbing.ErrObjectNotFound
}
+func (o *MockObjectStorage) EncodedObjectSize(h plumbing.Hash) (
+ size int64, err error) {
+ for _, o := range o.db {
+ if o.Hash() == h {
+ return o.Size(), nil
+ }
+ }
+ return 0, plumbing.ErrObjectNotFound
+}
+
func (o *MockObjectStorage) EncodedObject(t plumbing.ObjectType, h plumbing.Hash) (plumbing.EncodedObject, error) {
for _, o := range o.db {
if o.Hash() == h {
diff --git a/repository.go b/repository.go
index ddf6727..507ff44 100644
--- a/repository.go
+++ b/repository.go
@@ -175,15 +175,6 @@ func Open(s storage.Storer, worktree billy.Filesystem) (*Repository, error) {
return nil, err
}
- cfg, err := s.Config()
- if err != nil {
- return nil, err
- }
-
- if !cfg.Core.IsBare && worktree == nil {
- return nil, ErrWorktreeNotProvided
- }
-
return newRepository(s, worktree), nil
}
@@ -335,6 +326,8 @@ func dotGitFileToOSFilesystem(path string, fs billy.Filesystem) (bfs billy.Files
// PlainClone a repository into the path with the given options, isBare defines
// if the new repository will be bare or normal. If the path is not empty
// ErrRepositoryAlreadyExists is returned.
+//
+// TODO(mcuadros): move isBare to CloneOptions in v5
func PlainClone(path string, isBare bool, o *CloneOptions) (*Repository, error) {
return PlainCloneContext(context.Background(), path, isBare, o)
}
@@ -346,6 +339,8 @@ func PlainClone(path string, isBare bool, o *CloneOptions) (*Repository, error)
// The provided Context must be non-nil. If the context expires before the
// operation is complete, an error is returned. The context only affects to the
// transport operations.
+//
+// TODO(mcuadros): move isBare to CloneOptions in v5
func PlainCloneContext(ctx context.Context, path string, isBare bool, o *CloneOptions) (*Repository, error) {
r, err := PlainInit(path, isBare)
if err != nil {
@@ -1171,7 +1166,18 @@ func (r *Repository) Worktree() (*Worktree, error) {
return &Worktree{r: r, Filesystem: r.wt}, nil
}
-// ResolveRevision resolves revision to corresponding hash.
+func countTrue(vals ...bool) int {
+ sum := 0
+ for _, v := range vals {
+ if v {
+ sum++
+ }
+ }
+ return sum
+}
+
+// ResolveRevision resolves revision to corresponding hash. It will always
+// resolve to a commit hash, not a tree or annotated tag.
//
// Implemented resolvers : HEAD, branch, tag, heads/branch, refs/heads/branch,
// refs/tags/tag, refs/remotes/origin/branch, refs/remotes/origin/HEAD, tilde and caret (HEAD~1, master~^, tag~2, ref/heads/master~1, ...), selection by text (HEAD^{/fix nasty bug})
@@ -1191,8 +1197,8 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err
case revision.Ref:
revisionRef := item.(revision.Ref)
var ref *plumbing.Reference
- var hashCommit, refCommit *object.Commit
- var rErr, hErr error
+ var hashCommit, refCommit, tagCommit *object.Commit
+ var rErr, hErr, tErr error
for _, rule := range append([]string{"%s"}, plumbing.RefRevParseRules...) {
ref, err = storer.ResolveReference(r.Storer, plumbing.ReferenceName(fmt.Sprintf(rule, revisionRef)))
@@ -1203,24 +1209,38 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err
}
if ref != nil {
+ tag, tObjErr := r.TagObject(ref.Hash())
+ if tObjErr != nil {
+ tErr = tObjErr
+ } else {
+ tagCommit, tErr = tag.Commit()
+ }
refCommit, rErr = r.CommitObject(ref.Hash())
} else {
rErr = plumbing.ErrReferenceNotFound
+ tErr = plumbing.ErrReferenceNotFound
}
- isHash := plumbing.NewHash(string(revisionRef)).String() == string(revisionRef)
-
- if isHash {
+ maybeHash := plumbing.NewHash(string(revisionRef)).String() == string(revisionRef)
+ if maybeHash {
hashCommit, hErr = r.CommitObject(plumbing.NewHash(string(revisionRef)))
+ } else {
+ hErr = plumbing.ErrReferenceNotFound
}
+ isTag := tErr == nil
+ isCommit := rErr == nil
+ isHash := hErr == nil
+
switch {
- case rErr == nil && !isHash:
+ case countTrue(isTag, isCommit, isHash) > 1:
+ return &plumbing.ZeroHash, fmt.Errorf(`refname "%s" is ambiguous`, revisionRef)
+ case isTag:
+ commit = tagCommit
+ case isCommit:
commit = refCommit
- case rErr != nil && isHash && hErr == nil:
+ case isHash:
commit = hashCommit
- case rErr == nil && isHash && hErr == nil:
- return &plumbing.ZeroHash, fmt.Errorf(`refname "%s" is ambiguous`, revisionRef)
default:
return &plumbing.ZeroHash, plumbing.ErrReferenceNotFound
}
diff --git a/repository_test.go b/repository_test.go
index 6d34d42..07c3570 100644
--- a/repository_test.go
+++ b/repository_test.go
@@ -143,7 +143,7 @@ func (s *RepositorySuite) TestOpenBare(c *C) {
c.Assert(r, NotNil)
}
-func (s *RepositorySuite) TestOpenMissingWorktree(c *C) {
+func (s *RepositorySuite) TestOpenBareMissingWorktree(c *C) {
st := memory.NewStorage()
r, err := Init(st, memfs.New())
@@ -151,8 +151,8 @@ func (s *RepositorySuite) TestOpenMissingWorktree(c *C) {
c.Assert(r, NotNil)
r, err = Open(st, nil)
- c.Assert(err, Equals, ErrWorktreeNotProvided)
- c.Assert(r, IsNil)
+ c.Assert(err, IsNil)
+ c.Assert(r, NotNil)
}
func (s *RepositorySuite) TestOpenNotExists(c *C) {
@@ -425,8 +425,8 @@ func (s *RepositorySuite) TestPlainOpenNotBare(c *C) {
c.Assert(r, NotNil)
r, err = PlainOpen(filepath.Join(dir, ".git"))
- c.Assert(err, Equals, ErrWorktreeNotProvided)
- c.Assert(r, IsNil)
+ c.Assert(err, IsNil)
+ c.Assert(r, NotNil)
}
func (s *RepositorySuite) testPlainOpenGitFile(c *C, f func(string, string) string) {
@@ -2065,7 +2065,25 @@ func (s *RepositorySuite) TestResolveRevision(c *C) {
h, err := r.ResolveRevision(plumbing.Revision(rev))
c.Assert(err, IsNil)
- c.Assert(h.String(), Equals, hash)
+ c.Check(h.String(), Equals, hash, Commentf("while checking %s", rev))
+ }
+}
+
+func (s *RepositorySuite) TestResolveRevisionAnnotated(c *C) {
+ f := fixtures.ByURL("https://github.com/git-fixtures/tags.git").One()
+ sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault())
+ r, err := Open(sto, f.DotGit())
+ c.Assert(err, IsNil)
+
+ datas := map[string]string{
+ "refs/tags/annotated-tag": "f7b877701fbf855b44c0a9e86f3fdce2c298b07f",
+ }
+
+ for rev, hash := range datas {
+ h, err := r.ResolveRevision(plumbing.Revision(rev))
+
+ c.Assert(err, IsNil)
+ c.Check(h.String(), Equals, hash, Commentf("while checking %s", rev))
}
}
diff --git a/storage/filesystem/object.go b/storage/filesystem/object.go
index 68bd140..6cd2d4c 100644
--- a/storage/filesystem/object.go
+++ b/storage/filesystem/object.go
@@ -160,6 +160,79 @@ func (s *ObjectStorage) HasEncodedObject(h plumbing.Hash) (err error) {
return nil
}
+func (s *ObjectStorage) encodedObjectSizeFromUnpacked(h plumbing.Hash) (
+ size int64, err error) {
+ f, err := s.dir.Object(h)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return 0, plumbing.ErrObjectNotFound
+ }
+
+ return 0, err
+ }
+
+ r, err := objfile.NewReader(f)
+ if err != nil {
+ return 0, err
+ }
+ defer ioutil.CheckClose(r, &err)
+
+ _, size, err = r.Header()
+ return size, err
+}
+
+func (s *ObjectStorage) encodedObjectSizeFromPackfile(h plumbing.Hash) (
+ size int64, err error) {
+ if err := s.requireIndex(); err != nil {
+ return 0, err
+ }
+
+ pack, _, offset := s.findObjectInPackfile(h)
+ if offset == -1 {
+ return 0, plumbing.ErrObjectNotFound
+ }
+
+ f, err := s.dir.ObjectPack(pack)
+ if err != nil {
+ return 0, err
+ }
+ defer ioutil.CheckClose(f, &err)
+
+ idx := s.index[pack]
+ hash, err := idx.FindHash(offset)
+ if err == nil {
+ obj, ok := s.deltaBaseCache.Get(hash)
+ if ok {
+ return obj.Size(), nil
+ }
+ } else if err != nil && err != plumbing.ErrObjectNotFound {
+ return 0, err
+ }
+
+ var p *packfile.Packfile
+ if s.deltaBaseCache != nil {
+ p = packfile.NewPackfileWithCache(idx, s.dir.Fs(), f, s.deltaBaseCache)
+ } else {
+ p = packfile.NewPackfile(idx, s.dir.Fs(), f)
+ }
+
+ return p.GetSizeByOffset(offset)
+}
+
+// EncodedObjectSize returns the plaintext size of the given object,
+// without actually reading the full object data from storage.
+func (s *ObjectStorage) EncodedObjectSize(h plumbing.Hash) (
+ size int64, err error) {
+ size, err = s.encodedObjectSizeFromUnpacked(h)
+ if err != nil && err != plumbing.ErrObjectNotFound {
+ return 0, err
+ } else if err == nil {
+ return size, nil
+ }
+
+ return s.encodedObjectSizeFromPackfile(h)
+}
+
// EncodedObject returns the object with the given hash, by searching for it in
// the packfile and the git object directories.
func (s *ObjectStorage) EncodedObject(t plumbing.ObjectType, h plumbing.Hash) (plumbing.EncodedObject, error) {
diff --git a/storage/filesystem/object_test.go b/storage/filesystem/object_test.go
index 407abf2..4e6bbfb 100644
--- a/storage/filesystem/object_test.go
+++ b/storage/filesystem/object_test.go
@@ -83,6 +83,44 @@ func (s *FsSuite) TestGetFromPackfileKeepDescriptors(c *C) {
})
}
+func (s *FsSuite) TestGetSizeOfObjectFile(c *C) {
+ fs := fixtures.ByTag(".git").ByTag("unpacked").One().DotGit()
+ o := NewObjectStorage(dotgit.New(fs), cache.NewObjectLRUDefault())
+
+ // Get the size of `tree_walker.go`.
+ expected := plumbing.NewHash("cbd81c47be12341eb1185b379d1c82675aeded6a")
+ size, err := o.EncodedObjectSize(expected)
+ c.Assert(err, IsNil)
+ c.Assert(size, Equals, int64(2412))
+}
+
+func (s *FsSuite) TestGetSizeFromPackfile(c *C) {
+ fixtures.Basic().ByTag(".git").Test(c, func(f *fixtures.Fixture) {
+ fs := f.DotGit()
+ o := NewObjectStorage(dotgit.New(fs), cache.NewObjectLRUDefault())
+
+ // Get the size of `binary.jpg`.
+ expected := plumbing.NewHash("d5c0f4ab811897cadf03aec358ae60d21f91c50d")
+ size, err := o.EncodedObjectSize(expected)
+ c.Assert(err, IsNil)
+ c.Assert(size, Equals, int64(76110))
+ })
+}
+
+func (s *FsSuite) TestGetSizeOfAllObjectFiles(c *C) {
+ fs := fixtures.ByTag(".git").One().DotGit()
+ o := NewObjectStorage(dotgit.New(fs), cache.NewObjectLRUDefault())
+
+ // Get the size of `tree_walker.go`.
+ err := o.ForEachObjectHash(func(h plumbing.Hash) error {
+ size, err := o.EncodedObjectSize(h)
+ c.Assert(err, IsNil)
+ c.Assert(size, Not(Equals), int64(0))
+ return nil
+ })
+ c.Assert(err, IsNil)
+}
+
func (s *FsSuite) TestGetFromPackfileMultiplePackfiles(c *C) {
fs := fixtures.ByTag(".git").ByTag("multi-packfile").One().DotGit()
o := NewObjectStorage(dotgit.New(fs), cache.NewObjectLRUDefault())
diff --git a/storage/memory/storage.go b/storage/memory/storage.go
index 2e32509..6e11742 100644
--- a/storage/memory/storage.go
+++ b/storage/memory/storage.go
@@ -122,6 +122,16 @@ func (o *ObjectStorage) HasEncodedObject(h plumbing.Hash) (err error) {
return nil
}
+func (o *ObjectStorage) EncodedObjectSize(h plumbing.Hash) (
+ size int64, err error) {
+ obj, ok := o.Objects[h]
+ if !ok {
+ return 0, plumbing.ErrObjectNotFound
+ }
+
+ return obj.Size(), nil
+}
+
func (o *ObjectStorage) EncodedObject(t plumbing.ObjectType, h plumbing.Hash) (plumbing.EncodedObject, error) {
obj, ok := o.Objects[h]
if !ok || (plumbing.AnyObject != t && obj.Type() != t) {