aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--internal/revision/parser.go2
-rw-r--r--internal/revision/parser_test.go1
-rw-r--r--plumbing/format/config/option.go2
-rw-r--r--plumbing/format/config/section.go2
-rw-r--r--plumbing/format/gitignore/dir.go12
-rw-r--r--plumbing/format/gitignore/dir_test.go18
-rw-r--r--plumbing/memory.go17
-rw-r--r--plumbing/memory_test.go28
-rw-r--r--plumbing/object/commit.go24
-rw-r--r--plumbing/object/patch.go16
-rw-r--r--plumbing/protocol/packp/ulreq.go32
-rw-r--r--plumbing/protocol/packp/ulreq_decode.go4
-rw-r--r--plumbing/protocol/packp/ulreq_encode.go4
-rw-r--r--plumbing/protocol/packp/updreq.go6
-rw-r--r--plumbing/protocol/packp/updreq_encode.go18
-rw-r--r--remote.go10
-rw-r--r--repository.go84
-rw-r--r--repository_test.go1
-rw-r--r--storage/filesystem/dotgit/dotgit.go77
-rw-r--r--storage/filesystem/dotgit/dotgit_test.go35
-rw-r--r--storage/filesystem/object.go31
-rw-r--r--storage/filesystem/object_test.go17
-rw-r--r--storage/memory/storage.go4
-rw-r--r--utils/merkletrie/filesystem/node.go3
24 files changed, 360 insertions, 88 deletions
diff --git a/internal/revision/parser.go b/internal/revision/parser.go
index 432ffd1..8facf17 100644
--- a/internal/revision/parser.go
+++ b/internal/revision/parser.go
@@ -28,7 +28,7 @@ func (e *ErrInvalidRevision) Error() string {
type Revisioner interface {
}
-// Ref represents a reference name : HEAD, master
+// Ref represents a reference name : HEAD, master, <hash>
type Ref string
// TildePath represents ~, ~{n}
diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go
index 4bb007f..98403cc 100644
--- a/internal/revision/parser_test.go
+++ b/internal/revision/parser_test.go
@@ -354,6 +354,7 @@ func (s *ParserSuite) TestParseRefWithValidName(c *C) {
"refs/remotes/test",
"refs/remotes/origin/HEAD",
"refs/remotes/origin/master",
+ "0123abcd", // short hash
}
for _, d := range datas {
diff --git a/plumbing/format/config/option.go b/plumbing/format/config/option.go
index d4775e4..218f992 100644
--- a/plumbing/format/config/option.go
+++ b/plumbing/format/config/option.go
@@ -19,7 +19,7 @@ type Options []*Option
// IsKey returns true if the given key matches
// this option's key in a case-insensitive comparison.
func (o *Option) IsKey(key string) bool {
- return strings.ToLower(o.Key) == strings.ToLower(key)
+ return strings.EqualFold(o.Key, key)
}
func (opts Options) GoString() string {
diff --git a/plumbing/format/config/section.go b/plumbing/format/config/section.go
index 4a17e3b..f743da6 100644
--- a/plumbing/format/config/section.go
+++ b/plumbing/format/config/section.go
@@ -61,7 +61,7 @@ func (s Subsections) GoString() string {
// IsName checks if the name provided is equals to the Section name, case insensitive.
func (s *Section) IsName(name string) bool {
- return strings.ToLower(s.Name) == strings.ToLower(name)
+ return strings.EqualFold(s.Name, name)
}
// Option return the value for the specified key. Empty string is returned if
diff --git a/plumbing/format/gitignore/dir.go b/plumbing/format/gitignore/dir.go
index f4444bf..4a26325 100644
--- a/plumbing/format/gitignore/dir.go
+++ b/plumbing/format/gitignore/dir.go
@@ -1,6 +1,7 @@
package gitignore
import (
+ "bufio"
"bytes"
"io/ioutil"
"os"
@@ -15,7 +16,6 @@ import (
const (
commentPrefix = "#"
coreSection = "core"
- eol = "\n"
excludesfile = "excludesfile"
gitDir = ".git"
gitignoreFile = ".gitignore"
@@ -29,11 +29,11 @@ func readIgnoreFile(fs billy.Filesystem, path []string, ignoreFile string) (ps [
if err == nil {
defer f.Close()
- if data, err := ioutil.ReadAll(f); err == nil {
- for _, s := range strings.Split(string(data), eol) {
- if !strings.HasPrefix(s, commentPrefix) && len(strings.TrimSpace(s)) > 0 {
- ps = append(ps, ParsePattern(s, path))
- }
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ s := scanner.Text()
+ if !strings.HasPrefix(s, commentPrefix) && len(strings.TrimSpace(s)) > 0 {
+ ps = append(ps, ParsePattern(s, path))
}
}
} else if !os.IsNotExist(err) {
diff --git a/plumbing/format/gitignore/dir_test.go b/plumbing/format/gitignore/dir_test.go
index c0301f7..a3e7ba6 100644
--- a/plumbing/format/gitignore/dir_test.go
+++ b/plumbing/format/gitignore/dir_test.go
@@ -15,7 +15,7 @@ type MatcherSuite struct {
RFS billy.Filesystem // root that contains user home
MCFS billy.Filesystem // root that contains user home, but missing ~/.gitconfig
MEFS billy.Filesystem // root that contains user home, but missing excludesfile entry
- MIFS billy.Filesystem // root that contains user home, but missing .gitnignore
+ MIFS billy.Filesystem // root that contains user home, but missing .gitignore
SFS billy.Filesystem // root that contains /etc/gitconfig
}
@@ -29,6 +29,8 @@ func (s *MatcherSuite) SetUpTest(c *C) {
c.Assert(err, IsNil)
_, err = f.Write([]byte("vendor/g*/\n"))
c.Assert(err, IsNil)
+ _, err = f.Write([]byte("ignore.crlf\r\n"))
+ c.Assert(err, IsNil)
err = f.Close()
c.Assert(err, IsNil)
@@ -41,9 +43,14 @@ func (s *MatcherSuite) SetUpTest(c *C) {
err = f.Close()
c.Assert(err, IsNil)
- fs.MkdirAll("another", os.ModePerm)
- fs.MkdirAll("vendor/github.com", os.ModePerm)
- fs.MkdirAll("vendor/gopkg.in", os.ModePerm)
+ err = fs.MkdirAll("another", os.ModePerm)
+ c.Assert(err, IsNil)
+ err = fs.MkdirAll("ignore.crlf", os.ModePerm)
+ c.Assert(err, IsNil)
+ err = fs.MkdirAll("vendor/github.com", os.ModePerm)
+ c.Assert(err, IsNil)
+ err = fs.MkdirAll("vendor/gopkg.in", os.ModePerm)
+ c.Assert(err, IsNil)
s.GFS = fs
@@ -167,9 +174,10 @@ func (s *MatcherSuite) SetUpTest(c *C) {
func (s *MatcherSuite) TestDir_ReadPatterns(c *C) {
ps, err := ReadPatterns(s.GFS, nil)
c.Assert(err, IsNil)
- c.Assert(ps, HasLen, 2)
+ c.Assert(ps, HasLen, 3)
m := NewMatcher(ps)
+ c.Assert(m.Match([]string{"ignore.crlf"}, true), Equals, true)
c.Assert(m.Match([]string{"vendor", "gopkg.in"}, true), Equals, true)
c.Assert(m.Match([]string{"vendor", "github.com"}, true), Equals, false)
}
diff --git a/plumbing/memory.go b/plumbing/memory.go
index b8e1e1b..21337cc 100644
--- a/plumbing/memory.go
+++ b/plumbing/memory.go
@@ -3,7 +3,6 @@ package plumbing
import (
"bytes"
"io"
- "io/ioutil"
)
// MemoryObject on memory Object implementation
@@ -39,9 +38,11 @@ func (o *MemoryObject) Size() int64 { return o.sz }
// afterwards
func (o *MemoryObject) SetSize(s int64) { o.sz = s }
-// Reader returns a ObjectReader used to read the object's content.
+// Reader returns an io.ReadCloser used to read the object's content.
+//
+// For a MemoryObject, this reader is seekable.
func (o *MemoryObject) Reader() (io.ReadCloser, error) {
- return ioutil.NopCloser(bytes.NewBuffer(o.cont)), nil
+ return nopCloser{bytes.NewReader(o.cont)}, nil
}
// Writer returns a ObjectWriter used to write the object's content.
@@ -59,3 +60,13 @@ func (o *MemoryObject) Write(p []byte) (n int, err error) {
// Close releases any resources consumed by the object when it is acting as a
// ObjectWriter.
func (o *MemoryObject) Close() error { return nil }
+
+// nopCloser exposes the extra methods of bytes.Reader while nopping Close().
+//
+// This allows clients to attempt seeking in a cached Blob's Reader.
+type nopCloser struct {
+ *bytes.Reader
+}
+
+// Close does nothing.
+func (nc nopCloser) Close() error { return nil }
diff --git a/plumbing/memory_test.go b/plumbing/memory_test.go
index 879ed37..2a141f4 100644
--- a/plumbing/memory_test.go
+++ b/plumbing/memory_test.go
@@ -1,6 +1,7 @@
package plumbing
import (
+ "io"
"io/ioutil"
. "gopkg.in/check.v1"
@@ -56,6 +57,33 @@ func (s *MemoryObjectSuite) TestReader(c *C) {
c.Assert(b, DeepEquals, []byte("foo"))
}
+func (s *MemoryObjectSuite) TestSeekableReader(c *C) {
+ const pageSize = 4096
+ const payload = "foo"
+ content := make([]byte, pageSize+len(payload))
+ copy(content[pageSize:], []byte(payload))
+
+ o := &MemoryObject{cont: content}
+
+ reader, err := o.Reader()
+ c.Assert(err, IsNil)
+ defer func() { c.Assert(reader.Close(), IsNil) }()
+
+ rs, ok := reader.(io.ReadSeeker)
+ c.Assert(ok, Equals, true)
+
+ _, err = rs.Seek(pageSize, io.SeekStart)
+ c.Assert(err, IsNil)
+
+ b, err := ioutil.ReadAll(rs)
+ c.Assert(err, IsNil)
+ c.Assert(b, DeepEquals, []byte(payload))
+
+ // Check that our Reader isn't also accidentally writable
+ _, ok = reader.(io.WriteSeeker)
+ c.Assert(ok, Equals, false)
+}
+
func (s *MemoryObjectSuite) TestWriter(c *C) {
o := &MemoryObject{}
diff --git a/plumbing/object/commit.go b/plumbing/object/commit.go
index 113cb29..98664a1 100644
--- a/plumbing/object/commit.go
+++ b/plumbing/object/commit.go
@@ -243,16 +243,16 @@ func (c *Commit) Decode(o plumbing.EncodedObject) (err error) {
}
// Encode transforms a Commit into a plumbing.EncodedObject.
-func (b *Commit) Encode(o plumbing.EncodedObject) error {
- return b.encode(o, true)
+func (c *Commit) Encode(o plumbing.EncodedObject) error {
+ return c.encode(o, true)
}
// EncodeWithoutSignature export a Commit into a plumbing.EncodedObject without the signature (correspond to the payload of the PGP signature).
-func (b *Commit) EncodeWithoutSignature(o plumbing.EncodedObject) error {
- return b.encode(o, false)
+func (c *Commit) EncodeWithoutSignature(o plumbing.EncodedObject) error {
+ return c.encode(o, false)
}
-func (b *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
+func (c *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
o.SetType(plumbing.CommitObject)
w, err := o.Writer()
if err != nil {
@@ -261,11 +261,11 @@ func (b *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
defer ioutil.CheckClose(w, &err)
- if _, err = fmt.Fprintf(w, "tree %s\n", b.TreeHash.String()); err != nil {
+ if _, err = fmt.Fprintf(w, "tree %s\n", c.TreeHash.String()); err != nil {
return err
}
- for _, parent := range b.ParentHashes {
+ for _, parent := range c.ParentHashes {
if _, err = fmt.Fprintf(w, "parent %s\n", parent.String()); err != nil {
return err
}
@@ -275,7 +275,7 @@ func (b *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
return err
}
- if err = b.Author.Encode(w); err != nil {
+ if err = c.Author.Encode(w); err != nil {
return err
}
@@ -283,11 +283,11 @@ func (b *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
return err
}
- if err = b.Committer.Encode(w); err != nil {
+ if err = c.Committer.Encode(w); err != nil {
return err
}
- if b.PGPSignature != "" && includeSig {
+ if c.PGPSignature != "" && includeSig {
if _, err = fmt.Fprint(w, "\n"+headerpgp+" "); err != nil {
return err
}
@@ -296,14 +296,14 @@ func (b *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
// newline. Use join for this so it's clear that a newline should not be
// added after this section, as it will be added when the message is
// printed.
- signature := strings.TrimSuffix(b.PGPSignature, "\n")
+ signature := strings.TrimSuffix(c.PGPSignature, "\n")
lines := strings.Split(signature, "\n")
if _, err = fmt.Fprint(w, strings.Join(lines, "\n ")); err != nil {
return err
}
}
- if _, err = fmt.Fprintf(w, "\n\n%s", b.Message); err != nil {
+ if _, err = fmt.Fprintf(w, "\n\n%s", c.Message); err != nil {
return err
}
diff --git a/plumbing/object/patch.go b/plumbing/object/patch.go
index 1135a40..9b5f438 100644
--- a/plumbing/object/patch.go
+++ b/plumbing/object/patch.go
@@ -121,12 +121,12 @@ type Patch struct {
filePatches []fdiff.FilePatch
}
-func (t *Patch) FilePatches() []fdiff.FilePatch {
- return t.filePatches
+func (p *Patch) FilePatches() []fdiff.FilePatch {
+ return p.filePatches
}
-func (t *Patch) Message() string {
- return t.message
+func (p *Patch) Message() string {
+ return p.message
}
func (p *Patch) Encode(w io.Writer) error {
@@ -198,12 +198,12 @@ func (tf *textFilePatch) Files() (from fdiff.File, to fdiff.File) {
return
}
-func (t *textFilePatch) IsBinary() bool {
- return len(t.chunks) == 0
+func (tf *textFilePatch) IsBinary() bool {
+ return len(tf.chunks) == 0
}
-func (t *textFilePatch) Chunks() []fdiff.Chunk {
- return t.chunks
+func (tf *textFilePatch) Chunks() []fdiff.Chunk {
+ return tf.chunks
}
// textChunk is an implementation of fdiff.Chunk interface
diff --git a/plumbing/protocol/packp/ulreq.go b/plumbing/protocol/packp/ulreq.go
index 44db8e4..ddec06e 100644
--- a/plumbing/protocol/packp/ulreq.go
+++ b/plumbing/protocol/packp/ulreq.go
@@ -109,42 +109,42 @@ func NewUploadRequestFromCapabilities(adv *capability.List) *UploadRequest {
// - is a DepthReference is given capability.DeepenNot MUST be present
// - MUST contain only maximum of one of capability.Sideband and capability.Sideband64k
// - MUST contain only maximum of one of capability.MultiACK and capability.MultiACKDetailed
-func (r *UploadRequest) Validate() error {
- if len(r.Wants) == 0 {
+func (req *UploadRequest) Validate() error {
+ if len(req.Wants) == 0 {
return fmt.Errorf("want can't be empty")
}
- if err := r.validateRequiredCapabilities(); err != nil {
+ if err := req.validateRequiredCapabilities(); err != nil {
return err
}
- if err := r.validateConflictCapabilities(); err != nil {
+ if err := req.validateConflictCapabilities(); err != nil {
return err
}
return nil
}
-func (r *UploadRequest) validateRequiredCapabilities() error {
+func (req *UploadRequest) validateRequiredCapabilities() error {
msg := "missing capability %s"
- if len(r.Shallows) != 0 && !r.Capabilities.Supports(capability.Shallow) {
+ if len(req.Shallows) != 0 && !req.Capabilities.Supports(capability.Shallow) {
return fmt.Errorf(msg, capability.Shallow)
}
- switch r.Depth.(type) {
+ switch req.Depth.(type) {
case DepthCommits:
- if r.Depth != DepthCommits(0) {
- if !r.Capabilities.Supports(capability.Shallow) {
+ if req.Depth != DepthCommits(0) {
+ if !req.Capabilities.Supports(capability.Shallow) {
return fmt.Errorf(msg, capability.Shallow)
}
}
case DepthSince:
- if !r.Capabilities.Supports(capability.DeepenSince) {
+ if !req.Capabilities.Supports(capability.DeepenSince) {
return fmt.Errorf(msg, capability.DeepenSince)
}
case DepthReference:
- if !r.Capabilities.Supports(capability.DeepenNot) {
+ if !req.Capabilities.Supports(capability.DeepenNot) {
return fmt.Errorf(msg, capability.DeepenNot)
}
}
@@ -152,15 +152,15 @@ func (r *UploadRequest) validateRequiredCapabilities() error {
return nil
}
-func (r *UploadRequest) validateConflictCapabilities() error {
+func (req *UploadRequest) validateConflictCapabilities() error {
msg := "capabilities %s and %s are mutually exclusive"
- if r.Capabilities.Supports(capability.Sideband) &&
- r.Capabilities.Supports(capability.Sideband64k) {
+ if req.Capabilities.Supports(capability.Sideband) &&
+ req.Capabilities.Supports(capability.Sideband64k) {
return fmt.Errorf(msg, capability.Sideband, capability.Sideband64k)
}
- if r.Capabilities.Supports(capability.MultiACK) &&
- r.Capabilities.Supports(capability.MultiACKDetailed) {
+ if req.Capabilities.Supports(capability.MultiACK) &&
+ req.Capabilities.Supports(capability.MultiACKDetailed) {
return fmt.Errorf(msg, capability.MultiACK, capability.MultiACKDetailed)
}
diff --git a/plumbing/protocol/packp/ulreq_decode.go b/plumbing/protocol/packp/ulreq_decode.go
index 449b729..895a3bf 100644
--- a/plumbing/protocol/packp/ulreq_decode.go
+++ b/plumbing/protocol/packp/ulreq_decode.go
@@ -14,9 +14,9 @@ import (
// Decode reads the next upload-request form its input and
// stores it in the UploadRequest.
-func (u *UploadRequest) Decode(r io.Reader) error {
+func (req *UploadRequest) Decode(r io.Reader) error {
d := newUlReqDecoder(r)
- return d.Decode(u)
+ return d.Decode(req)
}
type ulReqDecoder struct {
diff --git a/plumbing/protocol/packp/ulreq_encode.go b/plumbing/protocol/packp/ulreq_encode.go
index 4863076..c451e23 100644
--- a/plumbing/protocol/packp/ulreq_encode.go
+++ b/plumbing/protocol/packp/ulreq_encode.go
@@ -15,9 +15,9 @@ import (
// All the payloads will end with a newline character. Wants and
// shallows are sorted alphabetically. A depth of 0 means no depth
// request is sent.
-func (u *UploadRequest) Encode(w io.Writer) error {
+func (req *UploadRequest) Encode(w io.Writer) error {
e := newUlReqEncoder(w)
- return e.Encode(u)
+ return e.Encode(req)
}
type ulReqEncoder struct {
diff --git a/plumbing/protocol/packp/updreq.go b/plumbing/protocol/packp/updreq.go
index b63b023..4d927d8 100644
--- a/plumbing/protocol/packp/updreq.go
+++ b/plumbing/protocol/packp/updreq.go
@@ -68,12 +68,12 @@ func NewReferenceUpdateRequestFromCapabilities(adv *capability.List) *ReferenceU
return r
}
-func (r *ReferenceUpdateRequest) validate() error {
- if len(r.Commands) == 0 {
+func (req *ReferenceUpdateRequest) validate() error {
+ if len(req.Commands) == 0 {
return ErrEmptyCommands
}
- for _, c := range r.Commands {
+ for _, c := range req.Commands {
if err := c.validate(); err != nil {
return err
}
diff --git a/plumbing/protocol/packp/updreq_encode.go b/plumbing/protocol/packp/updreq_encode.go
index 6a79653..2545e93 100644
--- a/plumbing/protocol/packp/updreq_encode.go
+++ b/plumbing/protocol/packp/updreq_encode.go
@@ -14,33 +14,33 @@ var (
)
// Encode writes the ReferenceUpdateRequest encoding to the stream.
-func (r *ReferenceUpdateRequest) Encode(w io.Writer) error {
- if err := r.validate(); err != nil {
+func (req *ReferenceUpdateRequest) Encode(w io.Writer) error {
+ if err := req.validate(); err != nil {
return err
}
e := pktline.NewEncoder(w)
- if err := r.encodeShallow(e, r.Shallow); err != nil {
+ if err := req.encodeShallow(e, req.Shallow); err != nil {
return err
}
- if err := r.encodeCommands(e, r.Commands, r.Capabilities); err != nil {
+ if err := req.encodeCommands(e, req.Commands, req.Capabilities); err != nil {
return err
}
- if r.Packfile != nil {
- if _, err := io.Copy(w, r.Packfile); err != nil {
+ if req.Packfile != nil {
+ if _, err := io.Copy(w, req.Packfile); err != nil {
return err
}
- return r.Packfile.Close()
+ return req.Packfile.Close()
}
return nil
}
-func (r *ReferenceUpdateRequest) encodeShallow(e *pktline.Encoder,
+func (req *ReferenceUpdateRequest) encodeShallow(e *pktline.Encoder,
h *plumbing.Hash) error {
if h == nil {
@@ -51,7 +51,7 @@ func (r *ReferenceUpdateRequest) encodeShallow(e *pktline.Encoder,
return e.Encodef("%s%s", shallow, objId)
}
-func (r *ReferenceUpdateRequest) encodeCommands(e *pktline.Encoder,
+func (req *ReferenceUpdateRequest) encodeCommands(e *pktline.Encoder,
cmds []*Command, cap *capability.List) error {
if err := e.Encodef("%s\x00%s",
diff --git a/remote.go b/remote.go
index 3c617c7..39da24e 100644
--- a/remote.go
+++ b/remote.go
@@ -218,9 +218,9 @@ func (r *Remote) newReferenceUpdateRequest(
if o.Progress != nil {
req.Progress = o.Progress
if ar.Capabilities.Supports(capability.Sideband64k) {
- req.Capabilities.Set(capability.Sideband64k)
+ _ = req.Capabilities.Set(capability.Sideband64k)
} else if ar.Capabilities.Supports(capability.Sideband) {
- req.Capabilities.Set(capability.Sideband)
+ _ = req.Capabilities.Set(capability.Sideband)
}
}
@@ -1035,11 +1035,13 @@ func (r *Remote) List(o *ListOptions) (rfs []*plumbing.Reference, err error) {
}
var resultRefs []*plumbing.Reference
- refs.ForEach(func(ref *plumbing.Reference) error {
+ err = refs.ForEach(func(ref *plumbing.Reference) error {
resultRefs = append(resultRefs, ref)
return nil
})
-
+ if err != nil {
+ return nil, err
+ }
return resultRefs, nil
}
diff --git a/repository.go b/repository.go
index 065f087..9dd35be 100644
--- a/repository.go
+++ b/repository.go
@@ -3,6 +3,7 @@ package git
import (
"bytes"
"context"
+ "encoding/hex"
"errors"
"fmt"
"io"
@@ -92,7 +93,7 @@ func Init(s storage.Storer, worktree billy.Filesystem) (*Repository, error) {
}
if worktree == nil {
- r.setIsBare(true)
+ _ = r.setIsBare(true)
return r, nil
}
@@ -408,7 +409,7 @@ func PlainCloneContext(ctx context.Context, path string, isBare bool, o *CloneOp
err = r.clone(ctx, o)
if err != nil && err != ErrRepositoryAlreadyExists {
if cleanup {
- cleanUpDir(path, cleanupParent)
+ _ = cleanUpDir(path, cleanupParent)
}
}
@@ -1426,7 +1427,7 @@ func (r *Repository) Worktree() (*Worktree, error) {
// 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})
+// 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}), hash (prefix and full)
func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, error) {
p := revision.NewParserFromString(string(rev))
@@ -1445,11 +1446,7 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err
var tryHashes []plumbing.Hash
- maybeHash := plumbing.NewHash(string(revisionRef))
-
- if !maybeHash.IsZero() {
- tryHashes = append(tryHashes, maybeHash)
- }
+ tryHashes = append(tryHashes, r.resolveHashPrefix(string(revisionRef))...)
for _, rule := range append([]string{"%s"}, plumbing.RefRevParseRules...) {
ref, err := storer.ResolveReference(r.Storer, plumbing.ReferenceName(fmt.Sprintf(rule, revisionRef)))
@@ -1567,6 +1564,49 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err
return &commit.Hash, nil
}
+// resolveHashPrefix returns a list of potential hashes that the given string
+// is a prefix of. It quietly swallows errors, returning nil.
+func (r *Repository) resolveHashPrefix(hashStr string) []plumbing.Hash {
+ // Handle complete and partial hashes.
+ // plumbing.NewHash forces args into a full 20 byte hash, which isn't suitable
+ // for partial hashes since they will become zero-filled.
+
+ if hashStr == "" {
+ return nil
+ }
+ if len(hashStr) == len(plumbing.ZeroHash)*2 {
+ // Only a full hash is possible.
+ hexb, err := hex.DecodeString(hashStr)
+ if err != nil {
+ return nil
+ }
+ var h plumbing.Hash
+ copy(h[:], hexb)
+ return []plumbing.Hash{h}
+ }
+
+ // Partial hash.
+ // hex.DecodeString only decodes to complete bytes, so only works with pairs of hex digits.
+ evenHex := hashStr[:len(hashStr)&^1]
+ hexb, err := hex.DecodeString(evenHex)
+ if err != nil {
+ return nil
+ }
+ candidates := expandPartialHash(r.Storer, hexb)
+ if len(evenHex) == len(hashStr) {
+ // The prefix was an exact number of bytes.
+ return candidates
+ }
+ // Do another prefix check to ensure the dangling nybble is correct.
+ var hashes []plumbing.Hash
+ for _, h := range candidates {
+ if strings.HasPrefix(h.String(), hashStr) {
+ hashes = append(hashes, h)
+ }
+ }
+ return hashes
+}
+
type RepackConfig struct {
// UseRefDeltas configures whether packfile encoder will use reference deltas.
// By default OFSDeltaObject is used.
@@ -1659,3 +1699,31 @@ func (r *Repository) createNewObjectPack(cfg *RepackConfig) (h plumbing.Hash, er
return h, err
}
+
+func expandPartialHash(st storer.EncodedObjectStorer, prefix []byte) (hashes []plumbing.Hash) {
+ // The fast version is implemented by storage/filesystem.ObjectStorage.
+ type fastIter interface {
+ HashesWithPrefix(prefix []byte) ([]plumbing.Hash, error)
+ }
+ if fi, ok := st.(fastIter); ok {
+ h, err := fi.HashesWithPrefix(prefix)
+ if err != nil {
+ return nil
+ }
+ return h
+ }
+
+ // Slow path.
+ iter, err := st.IterEncodedObjects(plumbing.AnyObject)
+ if err != nil {
+ return nil
+ }
+ iter.ForEach(func(obj plumbing.EncodedObject) error {
+ h := obj.Hash()
+ if bytes.HasPrefix(h[:], prefix) {
+ hashes = append(hashes, h)
+ }
+ return nil
+ })
+ return
+}
diff --git a/repository_test.go b/repository_test.go
index 2541cca..6a6ab9e 100644
--- a/repository_test.go
+++ b/repository_test.go
@@ -2642,6 +2642,7 @@ func (s *RepositorySuite) TestResolveRevision(c *C) {
"v1.0.0~1": "918c48b83bd081e863dbe1b80f8998f058cd8294",
"master~1": "918c48b83bd081e863dbe1b80f8998f058cd8294",
"918c48b83bd081e863dbe1b80f8998f058cd8294": "918c48b83bd081e863dbe1b80f8998f058cd8294",
+ "918c48b": "918c48b83bd081e863dbe1b80f8998f058cd8294", // odd number of hex digits
}
for rev, hash := range datas {
diff --git a/storage/filesystem/dotgit/dotgit.go b/storage/filesystem/dotgit/dotgit.go
index 3840ea7..6c386f7 100644
--- a/storage/filesystem/dotgit/dotgit.go
+++ b/storage/filesystem/dotgit/dotgit.go
@@ -3,12 +3,14 @@ package dotgit
import (
"bufio"
+ "bytes"
"errors"
"fmt"
"io"
stdioutil "io/ioutil"
"os"
"path/filepath"
+ "sort"
"strings"
"time"
@@ -88,7 +90,7 @@ type DotGit struct {
incomingChecked bool
incomingDirName string
- objectList []plumbing.Hash
+ objectList []plumbing.Hash // sorted
objectMap map[plumbing.Hash]struct{}
packList []plumbing.Hash
packMap map[plumbing.Hash]struct{}
@@ -336,6 +338,53 @@ func (d *DotGit) NewObject() (*ObjectWriter, error) {
return newObjectWriter(d.fs)
}
+// ObjectsWithPrefix returns the hashes of objects that have the given prefix.
+func (d *DotGit) ObjectsWithPrefix(prefix []byte) ([]plumbing.Hash, error) {
+ // Handle edge cases.
+ if len(prefix) < 1 {
+ return d.Objects()
+ } else if len(prefix) > len(plumbing.ZeroHash) {
+ return nil, nil
+ }
+
+ if d.options.ExclusiveAccess {
+ err := d.genObjectList()
+ if err != nil {
+ return nil, err
+ }
+
+ // Rely on d.objectList being sorted.
+ // Figure out the half-open interval defined by the prefix.
+ first := sort.Search(len(d.objectList), func(i int) bool {
+ // Same as plumbing.HashSlice.Less.
+ return bytes.Compare(d.objectList[i][:], prefix) >= 0
+ })
+ lim := len(d.objectList)
+ if limPrefix, overflow := incBytes(prefix); !overflow {
+ lim = sort.Search(len(d.objectList), func(i int) bool {
+ // Same as plumbing.HashSlice.Less.
+ return bytes.Compare(d.objectList[i][:], limPrefix) >= 0
+ })
+ }
+ return d.objectList[first:lim], nil
+ }
+
+ // This is the slow path.
+ var objects []plumbing.Hash
+ var n int
+ err := d.ForEachObjectHash(func(hash plumbing.Hash) error {
+ n++
+ if bytes.HasPrefix(hash[:], prefix) {
+ objects = append(objects, hash)
+ }
+ return nil
+ })
+ if err != nil {
+ return nil, err
+ }
+ return objects, nil
+}
+
// Objects returns a slice with the hashes of objects found under the
// .git/objects/ directory.
func (d *DotGit) Objects() ([]plumbing.Hash, error) {
@@ -427,12 +476,17 @@ func (d *DotGit) genObjectList() error {
}
d.objectMap = make(map[plumbing.Hash]struct{})
- return d.forEachObjectHash(func(h plumbing.Hash) error {
+ populate := func(h plumbing.Hash) error {
d.objectList = append(d.objectList, h)
d.objectMap[h] = struct{}{}
return nil
- })
+ }
+ if err := d.forEachObjectHash(populate); err != nil {
+ return err
+ }
+ plumbing.HashesSort(d.objectList)
+ return nil
}
func (d *DotGit) hasObject(h plumbing.Hash) error {
@@ -1115,3 +1169,20 @@ func isNum(b byte) bool {
func isHexAlpha(b byte) bool {
return b >= 'a' && b <= 'f' || b >= 'A' && b <= 'F'
}
+
+// incBytes increments a byte slice, which involves incrementing the
+// right-most byte, and following carry leftward.
+// It makes a copy so that the provided slice's underlying array is not modified.
+// If the overall operation overflows (e.g. incBytes(0xff, 0xff)), the second return parameter indicates that.
+func incBytes(in []byte) (out []byte, overflow bool) {
+ out = make([]byte, len(in))
+ copy(out, in)
+ for i := len(out) - 1; i >= 0; i-- {
+ out[i]++
+ if out[i] != 0 {
+ return // Didn't overflow.
+ }
+ }
+ overflow = true
+ return
+}
diff --git a/storage/filesystem/dotgit/dotgit_test.go b/storage/filesystem/dotgit/dotgit_test.go
index 0a72aa6..237605f 100644
--- a/storage/filesystem/dotgit/dotgit_test.go
+++ b/storage/filesystem/dotgit/dotgit_test.go
@@ -2,6 +2,7 @@ package dotgit
import (
"bufio"
+ "encoding/hex"
"io/ioutil"
"os"
"path/filepath"
@@ -591,6 +592,7 @@ func (s *SuiteDotGit) TestObjects(c *C) {
dir := New(fs)
testObjects(c, fs, dir)
+ testObjectsWithPrefix(c, fs, dir)
}
func (s *SuiteDotGit) TestObjectsExclusive(c *C) {
@@ -598,6 +600,7 @@ func (s *SuiteDotGit) TestObjectsExclusive(c *C) {
dir := NewWithOptions(fs, Options{ExclusiveAccess: true})
testObjects(c, fs, dir)
+ testObjectsWithPrefix(c, fs, dir)
}
func testObjects(c *C, fs billy.Filesystem, dir *DotGit) {
@@ -609,6 +612,20 @@ func testObjects(c *C, fs billy.Filesystem, dir *DotGit) {
c.Assert(hashes[2].String(), Equals, "03db8e1fbe133a480f2867aac478fd866686d69e")
}
+func testObjectsWithPrefix(c *C, fs billy.Filesystem, dir *DotGit) {
+ prefix, _ := hex.DecodeString("01d5")
+ hashes, err := dir.ObjectsWithPrefix(prefix)
+ c.Assert(err, IsNil)
+ c.Assert(hashes, HasLen, 1)
+ c.Assert(hashes[0].String(), Equals, "01d5fa556c33743006de7e76e67a2dfcd994ca04")
+
+ // Empty prefix should yield all objects.
+ // (subset of testObjects)
+ hashes, err = dir.ObjectsWithPrefix(nil)
+ c.Assert(err, IsNil)
+ c.Assert(hashes, HasLen, 187)
+}
+
func (s *SuiteDotGit) TestObjectsNoFolder(c *C) {
tmp, err := ioutil.TempDir("", "dot-git")
c.Assert(err, IsNil)
@@ -835,3 +852,21 @@ type norwfs struct {
func (f *norwfs) Capabilities() billy.Capability {
return billy.Capabilities(f.Filesystem) &^ billy.ReadAndWriteCapability
}
+
+func (s *SuiteDotGit) TestIncBytes(c *C) {
+ tests := []struct {
+ in []byte
+ out []byte
+ overflow bool
+ }{
+ {[]byte{0}, []byte{1}, false},
+ {[]byte{0xff}, []byte{0}, true},
+ {[]byte{7, 0xff}, []byte{8, 0}, false},
+ {[]byte{0xff, 0xff}, []byte{0, 0}, true},
+ }
+ for _, test := range tests {
+ out, overflow := incBytes(test.in)
+ c.Assert(out, DeepEquals, test.out)
+ c.Assert(overflow, Equals, test.overflow)
+ }
+}
diff --git a/storage/filesystem/object.go b/storage/filesystem/object.go
index 7437174..0c25dad 100644
--- a/storage/filesystem/object.go
+++ b/storage/filesystem/object.go
@@ -1,6 +1,7 @@
package filesystem
import (
+ "bytes"
"io"
"os"
"time"
@@ -518,6 +519,36 @@ func (s *ObjectStorage) findObjectInPackfile(h plumbing.Hash) (plumbing.Hash, pl
return plumbing.ZeroHash, plumbing.ZeroHash, -1
}
+func (s *ObjectStorage) HashesWithPrefix(prefix []byte) ([]plumbing.Hash, error) {
+ hashes, err := s.dir.ObjectsWithPrefix(prefix)
+ if err != nil {
+ return nil, err
+ }
+
+ // TODO: This could be faster with some idxfile changes,
+ // or diving into the packfile.
+ for _, index := range s.index {
+ ei, err := index.Entries()
+ if err != nil {
+ return nil, err
+ }
+ for {
+ e, err := ei.Next()
+ if err == io.EOF {
+ break
+ } else if err != nil {
+ return nil, err
+ }
+ if bytes.HasPrefix(e.Hash[:], prefix) {
+ hashes = append(hashes, e.Hash)
+ }
+ }
+ ei.Close()
+ }
+
+ return hashes, nil
+}
+
// IterEncodedObjects returns an iterator for all the objects in the packfile
// with the given type.
func (s *ObjectStorage) IterEncodedObjects(t plumbing.ObjectType) (storer.EncodedObjectIter, error) {
diff --git a/storage/filesystem/object_test.go b/storage/filesystem/object_test.go
index 2d6f568..036420f 100644
--- a/storage/filesystem/object_test.go
+++ b/storage/filesystem/object_test.go
@@ -1,6 +1,7 @@
package filesystem
import (
+ "encoding/hex"
"fmt"
"io"
"io/ioutil"
@@ -332,6 +333,22 @@ func (s *FsSuite) TestGetFromObjectFileSharedCache(c *C) {
c.Assert(err, Equals, plumbing.ErrObjectNotFound)
}
+func (s *FsSuite) TestHashesWithPrefix(c *C) {
+ // Same setup as TestGetFromObjectFile.
+ fs := fixtures.ByTag(".git").ByTag("unpacked").One().DotGit()
+ o := NewObjectStorage(dotgit.New(fs), cache.NewObjectLRUDefault())
+ expected := plumbing.NewHash("f3dfe29d268303fc6e1bbce268605fc99573406e")
+ obj, err := o.EncodedObject(plumbing.AnyObject, expected)
+ c.Assert(err, IsNil)
+ c.Assert(obj.Hash(), Equals, expected)
+
+ prefix, _ := hex.DecodeString("f3dfe2")
+ hashes, err := o.HashesWithPrefix(prefix)
+ c.Assert(err, IsNil)
+ c.Assert(hashes, HasLen, 1)
+ c.Assert(hashes[0].String(), Equals, "f3dfe29d268303fc6e1bbce268605fc99573406e")
+}
+
func BenchmarkPackfileIter(b *testing.B) {
defer fixtures.Clean()
diff --git a/storage/memory/storage.go b/storage/memory/storage.go
index fdf8fcf..a8e5669 100644
--- a/storage/memory/storage.go
+++ b/storage/memory/storage.go
@@ -195,10 +195,10 @@ func (o *ObjectStorage) DeleteOldObjectPackAndIndex(plumbing.Hash, time.Time) er
var errNotSupported = fmt.Errorf("Not supported")
-func (s *ObjectStorage) LooseObjectTime(hash plumbing.Hash) (time.Time, error) {
+func (o *ObjectStorage) LooseObjectTime(hash plumbing.Hash) (time.Time, error) {
return time.Time{}, errNotSupported
}
-func (s *ObjectStorage) DeleteLooseObject(plumbing.Hash) error {
+func (o *ObjectStorage) DeleteLooseObject(plumbing.Hash) error {
return errNotSupported
}
diff --git a/utils/merkletrie/filesystem/node.go b/utils/merkletrie/filesystem/node.go
index 165bd42..2fc3d7a 100644
--- a/utils/merkletrie/filesystem/node.go
+++ b/utils/merkletrie/filesystem/node.go
@@ -91,8 +91,7 @@ func (n *node) calculateChildren() error {
if os.IsNotExist(err) {
return nil
}
-
- return nil
+ return err
}
for _, file := range files {