Bumps [gopkg.in/src-d/go-git.v4](https://github.com/src-d/go-git) from 4.8.0 to 4.10.0. - [Release notes](https://github.com/src-d/go-git/releases) - [Commits](https://github.com/src-d/go-git/compare/v4.8.0...v4.10.0)master
@@ -128,7 +128,7 @@ require ( | |||
gopkg.in/macaron.v1 v1.3.2 | |||
gopkg.in/redis.v2 v2.3.2 // indirect | |||
gopkg.in/src-d/go-billy.v4 v4.3.0 // indirect | |||
gopkg.in/src-d/go-git.v4 v4.8.0 | |||
gopkg.in/src-d/go-git.v4 v4.10.0 | |||
gopkg.in/testfixtures.v2 v2.5.0 | |||
mvdan.cc/xurls/v2 v2.0.0 | |||
strk.kbt.io/projects/go/libravatar v0.0.0-20160628055650-5eed7bff870a | |||
@@ -354,6 +354,8 @@ gopkg.in/src-d/go-git-fixtures.v3 v3.1.1 h1:XWW/s5W18RaJpmo1l0IYGqXKuJITWRFuA45i | |||
gopkg.in/src-d/go-git-fixtures.v3 v3.1.1/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= | |||
gopkg.in/src-d/go-git.v4 v4.8.0 h1:dDEbgvfNG9vUDM54uhCYPExiGa8uYgXpQ/MR8YvxcAM= | |||
gopkg.in/src-d/go-git.v4 v4.8.0/go.mod h1:Vtut8izDyrM8BUVQnzJ+YvmNcem2J89EmfZYCkLokZk= | |||
gopkg.in/src-d/go-git.v4 v4.10.0 h1:NWjTJTQnk8UpIGlssuefyDZ6JruEjo5s88vm88uASbw= | |||
gopkg.in/src-d/go-git.v4 v4.10.0/go.mod h1:Vtut8izDyrM8BUVQnzJ+YvmNcem2J89EmfZYCkLokZk= | |||
gopkg.in/stretchr/testify.v1 v1.2.2 h1:yhQC6Uy5CqibAIlk1wlusa/MJ3iAN49/BsR/dCCKz3M= | |||
gopkg.in/stretchr/testify.v1 v1.2.2/go.mod h1:QI5V/q6UbPmuhtm10CaFZxED9NreB8PnFYN9JcR6TxU= | |||
gopkg.in/testfixtures.v2 v2.5.0 h1:N08B7l2GzFQenyYbzqthDnKAA+cmb17iAZhhFxr7JHw= | |||
@@ -8,6 +8,7 @@ import ( | |||
"sort" | |||
"strconv" | |||
"gopkg.in/src-d/go-git.v4/internal/url" | |||
format "gopkg.in/src-d/go-git.v4/plumbing/format/config" | |||
) | |||
@@ -399,3 +400,7 @@ func (c *RemoteConfig) marshal() *format.Subsection { | |||
return c.raw | |||
} | |||
func (c *RemoteConfig) IsFirstURLLocal() bool { | |||
return url.IsLocalEndpoint(c.URLs[0]) | |||
} |
@@ -0,0 +1,37 @@ | |||
package url | |||
import ( | |||
"regexp" | |||
) | |||
var ( | |||
isSchemeRegExp = regexp.MustCompile(`^[^:]+://`) | |||
scpLikeUrlRegExp = regexp.MustCompile(`^(?:(?P<user>[^@]+)@)?(?P<host>[^:\s]+):(?:(?P<port>[0-9]{1,5})/)?(?P<path>[^\\].*)$`) | |||
) | |||
// MatchesScheme returns true if the given string matches a URL-like | |||
// format scheme. | |||
func MatchesScheme(url string) bool { | |||
return isSchemeRegExp.MatchString(url) | |||
} | |||
// MatchesScpLike returns true if the given string matches an SCP-like | |||
// format scheme. | |||
func MatchesScpLike(url string) bool { | |||
return scpLikeUrlRegExp.MatchString(url) | |||
} | |||
// FindScpLikeComponents returns the user, host, port and path of the | |||
// given SCP-like URL. | |||
func FindScpLikeComponents(url string) (user, host, port, path string) { | |||
m := scpLikeUrlRegExp.FindStringSubmatch(url) | |||
return m[1], m[2], m[3], m[4] | |||
} | |||
// IsLocalEndpoint returns true if the given URL string specifies a | |||
// local file endpoint. For example, on a Linux machine, | |||
// `/home/user/src/go-git` would match as a local endpoint, but | |||
// `https://github.com/src-d/go-git` would not. | |||
func IsLocalEndpoint(url string) bool { | |||
return !MatchesScheme(url) && !MatchesScpLike(url) | |||
} |
@@ -335,6 +335,11 @@ type LogOptions struct { | |||
// Show only those commits in which the specified file was inserted/updated. | |||
// It is equivalent to running `git log -- <file-name>`. | |||
FileName *string | |||
// Pretend as if all the refs in refs/, along with HEAD, are listed on the command line as <commit>. | |||
// It is equivalent to running `git log --all`. | |||
// If set on true, the From option will be ignored. | |||
All bool | |||
} | |||
var ( | |||
@@ -51,7 +51,13 @@ func WritePackfileToObjectStorage( | |||
} | |||
defer ioutil.CheckClose(w, &err) | |||
_, err = io.Copy(w, packfile) | |||
var n int64 | |||
n, err = io.Copy(w, packfile) | |||
if err == nil && n == 0 { | |||
return ErrEmptyPackfile | |||
} | |||
return err | |||
} | |||
@@ -48,7 +48,7 @@ func NewFSObject( | |||
// Reader implements the plumbing.EncodedObject interface. | |||
func (o *FSObject) Reader() (io.ReadCloser, error) { | |||
obj, ok := o.cache.Get(o.hash) | |||
if ok { | |||
if ok && obj != o { | |||
reader, err := obj.Reader() | |||
if err != nil { | |||
return nil, err | |||
@@ -21,6 +21,16 @@ var ( | |||
ErrZLib = NewError("zlib reading error") | |||
) | |||
// When reading small objects from packfile it is beneficial to do so at | |||
// once to exploit the buffered I/O. In many cases the objects are so small | |||
// that they were already loaded to memory when the object header was | |||
// loaded from the packfile. Wrapping in FSObject would cause this buffered | |||
// data to be thrown away and then re-read later, with the additional | |||
// seeking causing reloads from disk. Objects smaller than this threshold | |||
// are now always read into memory and stored in cache instead of being | |||
// wrapped in FSObject. | |||
const smallObjectThreshold = 16 * 1024 | |||
// Packfile allows retrieving information from inside a packfile. | |||
type Packfile struct { | |||
idxfile.Index | |||
@@ -79,15 +89,7 @@ func (p *Packfile) GetByOffset(o int64) (plumbing.EncodedObject, error) { | |||
} | |||
} | |||
if _, err := p.s.SeekFromStart(o); err != nil { | |||
if err == io.EOF || isInvalid(err) { | |||
return nil, plumbing.ErrObjectNotFound | |||
} | |||
return nil, err | |||
} | |||
return p.nextObject() | |||
return p.objectAtOffset(o) | |||
} | |||
// GetSizeByOffset retrieves the size of the encoded object from the | |||
@@ -105,7 +107,13 @@ func (p *Packfile) GetSizeByOffset(o int64) (size int64, err error) { | |||
if err != nil { | |||
return 0, err | |||
} | |||
return h.Length, nil | |||
return p.getObjectSize(h) | |||
} | |||
func (p *Packfile) objectHeaderAtOffset(offset int64) (*ObjectHeader, error) { | |||
h, err := p.s.SeekObjectHeader(offset) | |||
p.s.pendingObject = nil | |||
return h, err | |||
} | |||
func (p *Packfile) nextObjectHeader() (*ObjectHeader, error) { | |||
@@ -154,11 +162,7 @@ func (p *Packfile) getObjectType(h *ObjectHeader) (typ plumbing.ObjectType, err | |||
if baseType, ok := p.offsetToType[offset]; ok { | |||
typ = baseType | |||
} else { | |||
if _, err = p.s.SeekFromStart(offset); err != nil { | |||
return | |||
} | |||
h, err = p.nextObjectHeader() | |||
h, err = p.objectHeaderAtOffset(offset) | |||
if err != nil { | |||
return | |||
} | |||
@@ -175,8 +179,8 @@ func (p *Packfile) getObjectType(h *ObjectHeader) (typ plumbing.ObjectType, err | |||
return | |||
} | |||
func (p *Packfile) nextObject() (plumbing.EncodedObject, error) { | |||
h, err := p.nextObjectHeader() | |||
func (p *Packfile) objectAtOffset(offset int64) (plumbing.EncodedObject, error) { | |||
h, err := p.objectHeaderAtOffset(offset) | |||
if err != nil { | |||
if err == io.EOF || isInvalid(err) { | |||
return nil, plumbing.ErrObjectNotFound | |||
@@ -190,6 +194,13 @@ func (p *Packfile) nextObject() (plumbing.EncodedObject, error) { | |||
return p.getNextObject(h) | |||
} | |||
// If the object is not a delta and it's small enough then read it | |||
// completely into memory now since it is already read from disk | |||
// into buffer anyway. | |||
if h.Length <= smallObjectThreshold && h.Type != plumbing.OFSDeltaObject && h.Type != plumbing.REFDeltaObject { | |||
return p.getNextObject(h) | |||
} | |||
hash, err := p.FindHash(h.Offset) | |||
if err != nil { | |||
return nil, err | |||
@@ -233,11 +244,7 @@ func (p *Packfile) getObjectContent(offset int64) (io.ReadCloser, error) { | |||
} | |||
} | |||
if _, err := p.s.SeekFromStart(offset); err != nil { | |||
return nil, err | |||
} | |||
h, err := p.nextObjectHeader() | |||
h, err := p.objectHeaderAtOffset(offset) | |||
if err != nil { | |||
return nil, err | |||
} | |||
@@ -329,8 +336,6 @@ func (p *Packfile) fillOFSDeltaObjectContent(obj plumbing.EncodedObject, offset | |||
if err != nil { | |||
return err | |||
} | |||
p.cachePut(base) | |||
} | |||
obj.SetType(base.Type()) | |||
@@ -398,11 +398,7 @@ func (p *Parser) readData(o *objectInfo) ([]byte, error) { | |||
return data, nil | |||
} | |||
if _, err := p.scanner.SeekFromStart(o.Offset); err != nil { | |||
return nil, err | |||
} | |||
if _, err := p.scanner.NextObjectHeader(); err != nil { | |||
if _, err := p.scanner.SeekObjectHeader(o.Offset); err != nil { | |||
return nil, err | |||
} | |||
@@ -138,14 +138,52 @@ func (s *Scanner) readCount() (uint32, error) { | |||
return binary.ReadUint32(s.r) | |||
} | |||
// SeekObjectHeader seeks to specified offset and returns the ObjectHeader | |||
// for the next object in the reader | |||
func (s *Scanner) SeekObjectHeader(offset int64) (*ObjectHeader, error) { | |||
// if seeking we assume that you are not interested in the header | |||
if s.version == 0 { | |||
s.version = VersionSupported | |||
} | |||
if _, err := s.r.Seek(offset, io.SeekStart); err != nil { | |||
return nil, err | |||
} | |||
h, err := s.nextObjectHeader() | |||
if err != nil { | |||
return nil, err | |||
} | |||
h.Offset = offset | |||
return h, nil | |||
} | |||
// NextObjectHeader returns the ObjectHeader for the next object in the reader | |||
func (s *Scanner) NextObjectHeader() (*ObjectHeader, error) { | |||
defer s.Flush() | |||
if err := s.doPending(); err != nil { | |||
return nil, err | |||
} | |||
offset, err := s.r.Seek(0, io.SeekCurrent) | |||
if err != nil { | |||
return nil, err | |||
} | |||
h, err := s.nextObjectHeader() | |||
if err != nil { | |||
return nil, err | |||
} | |||
h.Offset = offset | |||
return h, nil | |||
} | |||
// nextObjectHeader returns the ObjectHeader for the next object in the reader | |||
// without the Offset field | |||
func (s *Scanner) nextObjectHeader() (*ObjectHeader, error) { | |||
defer s.Flush() | |||
s.crc.Reset() | |||
h := &ObjectHeader{} | |||
@@ -308,7 +346,7 @@ var byteSlicePool = sync.Pool{ | |||
// SeekFromStart sets a new offset from start, returns the old position before | |||
// the change. | |||
func (s *Scanner) SeekFromStart(offset int64) (previous int64, err error) { | |||
// if seeking we assume that you are not interested on the header | |||
// if seeking we assume that you are not interested in the header | |||
if s.version == 0 { | |||
s.version = VersionSupported | |||
} | |||
@@ -385,7 +423,7 @@ type bufferedSeeker struct { | |||
} | |||
func (r *bufferedSeeker) Seek(offset int64, whence int) (int64, error) { | |||
if whence == io.SeekCurrent { | |||
if whence == io.SeekCurrent && offset == 0 { | |||
current, err := r.r.Seek(offset, whence) | |||
if err != nil { | |||
return current, err | |||
@@ -1,10 +1,12 @@ | |||
package object | |||
import ( | |||
"container/list" | |||
"io" | |||
"gopkg.in/src-d/go-git.v4/plumbing" | |||
"gopkg.in/src-d/go-git.v4/plumbing/storer" | |||
"gopkg.in/src-d/go-git.v4/storage" | |||
) | |||
type commitPreIterator struct { | |||
@@ -181,3 +183,145 @@ func (w *commitPostIterator) ForEach(cb func(*Commit) error) error { | |||
} | |||
func (w *commitPostIterator) Close() {} | |||
// commitAllIterator stands for commit iterator for all refs. | |||
type commitAllIterator struct { | |||
// currCommit points to the current commit. | |||
currCommit *list.Element | |||
} | |||
// NewCommitAllIter returns a new commit iterator for all refs. | |||
// repoStorer is a repo Storer used to get commits and references. | |||
// commitIterFunc is a commit iterator function, used to iterate through ref commits in chosen order | |||
func NewCommitAllIter(repoStorer storage.Storer, commitIterFunc func(*Commit) CommitIter) (CommitIter, error) { | |||
commitsPath := list.New() | |||
commitsLookup := make(map[plumbing.Hash]*list.Element) | |||
head, err := storer.ResolveReference(repoStorer, plumbing.HEAD) | |||
if err == nil { | |||
err = addReference(repoStorer, commitIterFunc, head, commitsPath, commitsLookup) | |||
} | |||
if err != nil && err != plumbing.ErrReferenceNotFound { | |||
return nil, err | |||
} | |||
// add all references along with the HEAD | |||
refIter, err := repoStorer.IterReferences() | |||
if err != nil { | |||
return nil, err | |||
} | |||
defer refIter.Close() | |||
for { | |||
ref, err := refIter.Next() | |||
if err == io.EOF { | |||
break | |||
} | |||
if err == plumbing.ErrReferenceNotFound { | |||
continue | |||
} | |||
if err != nil { | |||
return nil, err | |||
} | |||
if err = addReference(repoStorer, commitIterFunc, ref, commitsPath, commitsLookup); err != nil { | |||
return nil, err | |||
} | |||
} | |||
return &commitAllIterator{commitsPath.Front()}, nil | |||
} | |||
func addReference( | |||
repoStorer storage.Storer, | |||
commitIterFunc func(*Commit) CommitIter, | |||
ref *plumbing.Reference, | |||
commitsPath *list.List, | |||
commitsLookup map[plumbing.Hash]*list.Element) error { | |||
_, exists := commitsLookup[ref.Hash()] | |||
if exists { | |||
// we already have it - skip the reference. | |||
return nil | |||
} | |||
refCommit, _ := GetCommit(repoStorer, ref.Hash()) | |||
if refCommit == nil { | |||
// if it's not a commit - skip it. | |||
return nil | |||
} | |||
var ( | |||
refCommits []*Commit | |||
parent *list.Element | |||
) | |||
// collect all ref commits to add | |||
commitIter := commitIterFunc(refCommit) | |||
for c, e := commitIter.Next(); e == nil; { | |||
parent, exists = commitsLookup[c.Hash] | |||
if exists { | |||
break | |||
} | |||
refCommits = append(refCommits, c) | |||
c, e = commitIter.Next() | |||
} | |||
commitIter.Close() | |||
if parent == nil { | |||
// common parent - not found | |||
// add all commits to the path from this ref (maybe it's a HEAD and we don't have anything, yet) | |||
for _, c := range refCommits { | |||
parent = commitsPath.PushBack(c) | |||
commitsLookup[c.Hash] = parent | |||
} | |||
} else { | |||
// add ref's commits to the path in reverse order (from the latest) | |||
for i := len(refCommits) - 1; i >= 0; i-- { | |||
c := refCommits[i] | |||
// insert before found common parent | |||
parent = commitsPath.InsertBefore(c, parent) | |||
commitsLookup[c.Hash] = parent | |||
} | |||
} | |||
return nil | |||
} | |||
func (it *commitAllIterator) Next() (*Commit, error) { | |||
if it.currCommit == nil { | |||
return nil, io.EOF | |||
} | |||
c := it.currCommit.Value.(*Commit) | |||
it.currCommit = it.currCommit.Next() | |||
return c, nil | |||
} | |||
func (it *commitAllIterator) ForEach(cb func(*Commit) error) error { | |||
for { | |||
c, err := it.Next() | |||
if err == io.EOF { | |||
break | |||
} | |||
if err != nil { | |||
return err | |||
} | |||
err = cb(c) | |||
if err == storer.ErrStop { | |||
break | |||
} | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
return nil | |||
} | |||
func (it *commitAllIterator) Close() { | |||
it.currCommit = nil | |||
} |
@@ -1,23 +1,30 @@ | |||
package object | |||
import ( | |||
"gopkg.in/src-d/go-git.v4/plumbing/storer" | |||
"io" | |||
"gopkg.in/src-d/go-git.v4/plumbing" | |||
"gopkg.in/src-d/go-git.v4/plumbing/storer" | |||
) | |||
type commitFileIter struct { | |||
fileName string | |||
sourceIter CommitIter | |||
currentCommit *Commit | |||
checkParent bool | |||
} | |||
// NewCommitFileIterFromIter returns a commit iterator which performs diffTree between | |||
// successive trees returned from the commit iterator from the argument. The purpose of this is | |||
// to find the commits that explain how the files that match the path came to be. | |||
func NewCommitFileIterFromIter(fileName string, commitIter CommitIter) CommitIter { | |||
// If checkParent is true then the function double checks if potential parent (next commit in a path) | |||
// is one of the parents in the tree (it's used by `git log --all`). | |||
func NewCommitFileIterFromIter(fileName string, commitIter CommitIter, checkParent bool) CommitIter { | |||
iterator := new(commitFileIter) | |||
iterator.sourceIter = commitIter | |||
iterator.fileName = fileName | |||
iterator.checkParent = checkParent | |||
return iterator | |||
} | |||
@@ -71,20 +78,14 @@ func (c *commitFileIter) getNextFileCommit() (*Commit, error) { | |||
return nil, diffErr | |||
} | |||
foundChangeForFile := false | |||
for _, change := range changes { | |||
if change.name() == c.fileName { | |||
foundChangeForFile = true | |||
break | |||
} | |||
} | |||
found := c.hasFileChange(changes, parentCommit) | |||
// Storing the current-commit in-case a change is found, and | |||
// Updating the current-commit for the next-iteration | |||
prevCommit := c.currentCommit | |||
c.currentCommit = parentCommit | |||
if foundChangeForFile == true { | |||
if found { | |||
return prevCommit, nil | |||
} | |||
@@ -95,6 +96,35 @@ func (c *commitFileIter) getNextFileCommit() (*Commit, error) { | |||
} | |||
} | |||
func (c *commitFileIter) hasFileChange(changes Changes, parent *Commit) bool { | |||
for _, change := range changes { | |||
if change.name() != c.fileName { | |||
continue | |||
} | |||
// filename matches, now check if source iterator contains all commits (from all refs) | |||
if c.checkParent { | |||
if parent != nil && isParentHash(parent.Hash, c.currentCommit) { | |||
return true | |||
} | |||
continue | |||
} | |||
return true | |||
} | |||
return false | |||
} | |||
func isParentHash(hash plumbing.Hash, commit *Commit) bool { | |||
for _, h := range commit.ParentHashes { | |||
if h == hash { | |||
return true | |||
} | |||
} | |||
return false | |||
} | |||
func (c *commitFileIter) ForEach(cb func(*Commit) error) error { | |||
for { | |||
commit, nextErr := c.Next() | |||
@@ -21,7 +21,20 @@ func Objects( | |||
objs, | |||
ignore []plumbing.Hash, | |||
) ([]plumbing.Hash, error) { | |||
ignore, err := objects(s, ignore, nil, true) | |||
return ObjectsWithStorageForIgnores(s, s, objs, ignore) | |||
} | |||
// ObjectsWithStorageForIgnores is the same as Objects, but a | |||
// secondary storage layer can be provided, to be used to finding the | |||
// full set of objects to be ignored while finding the reachable | |||
// objects. This is useful when the main `s` storage layer is slow | |||
// and/or remote, while the ignore list is available somewhere local. | |||
func ObjectsWithStorageForIgnores( | |||
s, ignoreStore storer.EncodedObjectStorer, | |||
objs, | |||
ignore []plumbing.Hash, | |||
) ([]plumbing.Hash, error) { | |||
ignore, err := objects(ignoreStore, ignore, nil, true) | |||
if err != nil { | |||
return nil, err | |||
} | |||
@@ -114,7 +127,6 @@ func reachableObjects( | |||
i := object.NewCommitPreorderIter(commit, seen, ignore) | |||
pending := make(map[plumbing.Hash]bool) | |||
addPendingParents(pending, visited, commit) | |||
for { | |||
commit, err := i.Next() | |||
if err == io.EOF { | |||
@@ -222,7 +222,7 @@ type MultiEncodedObjectIter struct { | |||
} | |||
// NewMultiEncodedObjectIter returns an object iterator for the given slice of | |||
// objects. | |||
// EncodedObjectIters. | |||
func NewMultiEncodedObjectIter(iters []EncodedObjectIter) EncodedObjectIter { | |||
return &MultiEncodedObjectIter{iters: iters} | |||
} | |||
@@ -131,9 +131,27 @@ func (iter *ReferenceSliceIter) Next() (*plumbing.Reference, error) { | |||
// an error happens or the end of the iter is reached. If ErrStop is sent | |||
// the iteration is stop but no error is returned. The iterator is closed. | |||
func (iter *ReferenceSliceIter) ForEach(cb func(*plumbing.Reference) error) error { | |||
return forEachReferenceIter(iter, cb) | |||
} | |||
type bareReferenceIterator interface { | |||
Next() (*plumbing.Reference, error) | |||
Close() | |||
} | |||
func forEachReferenceIter(iter bareReferenceIterator, cb func(*plumbing.Reference) error) error { | |||
defer iter.Close() | |||
for _, r := range iter.series { | |||
if err := cb(r); err != nil { | |||
for { | |||
obj, err := iter.Next() | |||
if err != nil { | |||
if err == io.EOF { | |||
return nil | |||
} | |||
return err | |||
} | |||
if err := cb(obj); err != nil { | |||
if err == ErrStop { | |||
return nil | |||
} | |||
@@ -141,8 +159,6 @@ func (iter *ReferenceSliceIter) ForEach(cb func(*plumbing.Reference) error) erro | |||
return err | |||
} | |||
} | |||
return nil | |||
} | |||
// Close releases any resources used by the iterator. | |||
@@ -150,6 +166,52 @@ func (iter *ReferenceSliceIter) Close() { | |||
iter.pos = len(iter.series) | |||
} | |||
// MultiReferenceIter implements ReferenceIter. It iterates over several | |||
// ReferenceIter, | |||
// | |||
// The MultiReferenceIter must be closed with a call to Close() when it is no | |||
// longer needed. | |||
type MultiReferenceIter struct { | |||
iters []ReferenceIter | |||
} | |||
// NewMultiReferenceIter returns an reference iterator for the given slice of | |||
// EncodedObjectIters. | |||
func NewMultiReferenceIter(iters []ReferenceIter) ReferenceIter { | |||
return &MultiReferenceIter{iters: iters} | |||
} | |||
// Next returns the next reference from the iterator, if one iterator reach | |||
// io.EOF is removed and the next one is used. | |||
func (iter *MultiReferenceIter) Next() (*plumbing.Reference, error) { | |||
if len(iter.iters) == 0 { | |||
return nil, io.EOF | |||
} | |||
obj, err := iter.iters[0].Next() | |||
if err == io.EOF { | |||
iter.iters[0].Close() | |||
iter.iters = iter.iters[1:] | |||
return iter.Next() | |||
} | |||
return obj, err | |||
} | |||
// ForEach call the cb function for each reference contained on this iter until | |||
// an error happens or the end of the iter is reached. If ErrStop is sent | |||
// the iteration is stop but no error is returned. The iterator is closed. | |||
func (iter *MultiReferenceIter) ForEach(cb func(*plumbing.Reference) error) error { | |||
return forEachReferenceIter(iter, cb) | |||
} | |||
// Close releases any resources used by the iterator. | |||
func (iter *MultiReferenceIter) Close() { | |||
for _, i := range iter.iters { | |||
i.Close() | |||
} | |||
} | |||
// ResolveReference resolves a SymbolicReference to a HashReference. | |||
func ResolveReference(s ReferenceStorer, n plumbing.ReferenceName) (*plumbing.Reference, error) { | |||
r, err := s.Reference(n) | |||
@@ -19,10 +19,10 @@ import ( | |||
"fmt" | |||
"io" | |||
"net/url" | |||
"regexp" | |||
"strconv" | |||
"strings" | |||
giturl "gopkg.in/src-d/go-git.v4/internal/url" | |||
"gopkg.in/src-d/go-git.v4/plumbing" | |||
"gopkg.in/src-d/go-git.v4/plumbing/protocol/packp" | |||
"gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability" | |||
@@ -224,34 +224,28 @@ func getPath(u *url.URL) string { | |||
return res | |||
} | |||
var ( | |||
isSchemeRegExp = regexp.MustCompile(`^[^:]+://`) | |||
scpLikeUrlRegExp = regexp.MustCompile(`^(?:(?P<user>[^@]+)@)?(?P<host>[^:\s]+):(?:(?P<port>[0-9]{1,5})/)?(?P<path>[^\\].*)$`) | |||
) | |||
func parseSCPLike(endpoint string) (*Endpoint, bool) { | |||
if isSchemeRegExp.MatchString(endpoint) || !scpLikeUrlRegExp.MatchString(endpoint) { | |||
if giturl.MatchesScheme(endpoint) || !giturl.MatchesScpLike(endpoint) { | |||
return nil, false | |||
} | |||
m := scpLikeUrlRegExp.FindStringSubmatch(endpoint) | |||
port, err := strconv.Atoi(m[3]) | |||
user, host, portStr, path := giturl.FindScpLikeComponents(endpoint) | |||
port, err := strconv.Atoi(portStr) | |||
if err != nil { | |||
port = 22 | |||
} | |||
return &Endpoint{ | |||
Protocol: "ssh", | |||
User: m[1], | |||
Host: m[2], | |||
User: user, | |||
Host: host, | |||
Port: port, | |||
Path: m[4], | |||
Path: path, | |||
}, true | |||
} | |||
func parseFile(endpoint string) (*Endpoint, bool) { | |||
if isSchemeRegExp.MatchString(endpoint) { | |||
if giturl.MatchesScheme(endpoint) { | |||
return nil, false | |||
} | |||
@@ -6,8 +6,10 @@ import ( | |||
"fmt" | |||
"io" | |||
"gopkg.in/src-d/go-billy.v4/osfs" | |||
"gopkg.in/src-d/go-git.v4/config" | |||
"gopkg.in/src-d/go-git.v4/plumbing" | |||
"gopkg.in/src-d/go-git.v4/plumbing/cache" | |||
"gopkg.in/src-d/go-git.v4/plumbing/format/packfile" | |||
"gopkg.in/src-d/go-git.v4/plumbing/object" | |||
"gopkg.in/src-d/go-git.v4/plumbing/protocol/packp" | |||
@@ -18,6 +20,7 @@ import ( | |||
"gopkg.in/src-d/go-git.v4/plumbing/transport" | |||
"gopkg.in/src-d/go-git.v4/plumbing/transport/client" | |||
"gopkg.in/src-d/go-git.v4/storage" | |||
"gopkg.in/src-d/go-git.v4/storage/filesystem" | |||
"gopkg.in/src-d/go-git.v4/storage/memory" | |||
"gopkg.in/src-d/go-git.v4/utils/ioutil" | |||
) | |||
@@ -149,7 +152,17 @@ func (r *Remote) PushContext(ctx context.Context, o *PushOptions) (err error) { | |||
var hashesToPush []plumbing.Hash | |||
// Avoid the expensive revlist operation if we're only doing deletes. | |||
if !allDelete { | |||
hashesToPush, err = revlist.Objects(r.s, objects, haves) | |||
if r.c.IsFirstURLLocal() { | |||
// If we're are pushing to a local repo, it might be much | |||
// faster to use a local storage layer to get the commits | |||
// to ignore, when calculating the object revlist. | |||
localStorer := filesystem.NewStorage( | |||
osfs.New(r.c.URLs[0]), cache.NewObjectLRUDefault()) | |||
hashesToPush, err = revlist.ObjectsWithStorageForIgnores( | |||
r.s, localStorer, objects, haves) | |||
} else { | |||
hashesToPush, err = revlist.Objects(r.s, objects, haves) | |||
} | |||
if err != nil { | |||
return err | |||
} | |||
@@ -41,6 +41,8 @@ var ( | |||
ErrTagExists = errors.New("tag already exists") | |||
// ErrTagNotFound an error stating the specified tag does not exist | |||
ErrTagNotFound = errors.New("tag not found") | |||
// ErrFetching is returned when the packfile could not be downloaded | |||
ErrFetching = errors.New("unable to fetch packfile") | |||
ErrInvalidReference = errors.New("invalid reference, should be a tag or a branch") | |||
ErrRepositoryNotExists = errors.New("repository does not exist") | |||
@@ -342,8 +344,9 @@ func PlainClone(path string, isBare bool, o *CloneOptions) (*Repository, error) | |||
// transport operations. | |||
// | |||
// TODO(mcuadros): move isBare to CloneOptions in v5 | |||
// TODO(smola): refuse upfront to clone on a non-empty directory in v5, see #1027 | |||
func PlainCloneContext(ctx context.Context, path string, isBare bool, o *CloneOptions) (*Repository, error) { | |||
dirExists, err := checkExistsAndIsEmptyDir(path) | |||
cleanup, cleanupParent, err := checkIfCleanupIsNeeded(path) | |||
if err != nil { | |||
return nil, err | |||
} | |||
@@ -355,7 +358,9 @@ func PlainCloneContext(ctx context.Context, path string, isBare bool, o *CloneOp | |||
err = r.clone(ctx, o) | |||
if err != nil && err != ErrRepositoryAlreadyExists { | |||
cleanUpDir(path, !dirExists) | |||
if cleanup { | |||
cleanUpDir(path, cleanupParent) | |||
} | |||
} | |||
return r, err | |||
@@ -369,37 +374,37 @@ func newRepository(s storage.Storer, worktree billy.Filesystem) *Repository { | |||
} | |||
} | |||
func checkExistsAndIsEmptyDir(path string) (exists bool, err error) { | |||
func checkIfCleanupIsNeeded(path string) (cleanup bool, cleanParent bool, err error) { | |||
fi, err := os.Stat(path) | |||
if err != nil { | |||
if os.IsNotExist(err) { | |||
return false, nil | |||
return true, true, nil | |||
} | |||
return false, err | |||
return false, false, err | |||
} | |||
if !fi.IsDir() { | |||
return false, fmt.Errorf("path is not a directory: %s", path) | |||
return false, false, fmt.Errorf("path is not a directory: %s", path) | |||
} | |||
f, err := os.Open(path) | |||
if err != nil { | |||
return false, err | |||
return false, false, err | |||
} | |||
defer ioutil.CheckClose(f, &err) | |||
_, err = f.Readdirnames(1) | |||
if err == io.EOF { | |||
return true, nil | |||
return true, false, nil | |||
} | |||
if err != nil { | |||
return true, err | |||
return false, false, err | |||
} | |||
return true, fmt.Errorf("directory is not empty: %s", path) | |||
return false, false, nil | |||
} | |||
func cleanUpDir(path string, all bool) error { | |||
@@ -425,7 +430,7 @@ func cleanUpDir(path string, all bool) error { | |||
} | |||
} | |||
return nil | |||
return err | |||
} | |||
// Config return the repository config | |||
@@ -855,6 +860,8 @@ func (r *Repository) fetchAndUpdateReferences( | |||
remoteRefs, err := remote.fetch(ctx, o) | |||
if err == NoErrAlreadyUpToDate { | |||
objsUpdated = false | |||
} else if err == packfile.ErrEmptyPackfile { | |||
return nil, ErrFetching | |||
} else if err != nil { | |||
return nil, err | |||
} | |||
@@ -1020,8 +1027,36 @@ func (r *Repository) PushContext(ctx context.Context, o *PushOptions) error { | |||
// Log returns the commit history from the given LogOptions. | |||
func (r *Repository) Log(o *LogOptions) (object.CommitIter, error) { | |||
h := o.From | |||
if o.From == plumbing.ZeroHash { | |||
fn := commitIterFunc(o.Order) | |||
if fn == nil { | |||
return nil, fmt.Errorf("invalid Order=%v", o.Order) | |||
} | |||
var ( | |||
it object.CommitIter | |||
err error | |||
) | |||
if o.All { | |||
it, err = r.logAll(fn) | |||
} else { | |||
it, err = r.log(o.From, fn) | |||
} | |||
if err != nil { | |||
return nil, err | |||
} | |||
if o.FileName != nil { | |||
// for `git log --all` also check parent (if the next commit comes from the real parent) | |||
it = r.logWithFile(*o.FileName, it, o.All) | |||
} | |||
return it, nil | |||
} | |||
func (r *Repository) log(from plumbing.Hash, commitIterFunc func(*object.Commit) object.CommitIter) (object.CommitIter, error) { | |||
h := from | |||
if from == plumbing.ZeroHash { | |||
head, err := r.Head() | |||
if err != nil { | |||
return nil, err | |||
@@ -1034,27 +1069,41 @@ func (r *Repository) Log(o *LogOptions) (object.CommitIter, error) { | |||
if err != nil { | |||
return nil, err | |||
} | |||
return commitIterFunc(commit), nil | |||
} | |||
func (r *Repository) logAll(commitIterFunc func(*object.Commit) object.CommitIter) (object.CommitIter, error) { | |||
return object.NewCommitAllIter(r.Storer, commitIterFunc) | |||
} | |||
var commitIter object.CommitIter | |||
switch o.Order { | |||
func (*Repository) logWithFile(fileName string, commitIter object.CommitIter, checkParent bool) object.CommitIter { | |||
return object.NewCommitFileIterFromIter(fileName, commitIter, checkParent) | |||
} | |||
func commitIterFunc(order LogOrder) func(c *object.Commit) object.CommitIter { | |||
switch order { | |||
case LogOrderDefault: | |||
commitIter = object.NewCommitPreorderIter(commit, nil, nil) | |||
return func(c *object.Commit) object.CommitIter { | |||
return object.NewCommitPreorderIter(c, nil, nil) | |||
} | |||
case LogOrderDFS: | |||
commitIter = object.NewCommitPreorderIter(commit, nil, nil) | |||
return func(c *object.Commit) object.CommitIter { | |||
return object.NewCommitPreorderIter(c, nil, nil) | |||
} | |||
case LogOrderDFSPost: | |||
commitIter = object.NewCommitPostorderIter(commit, nil) | |||
return func(c *object.Commit) object.CommitIter { | |||
return object.NewCommitPostorderIter(c, nil) | |||
} | |||
case LogOrderBSF: | |||
commitIter = object.NewCommitIterBSF(commit, nil, nil) | |||
return func(c *object.Commit) object.CommitIter { | |||
return object.NewCommitIterBSF(c, nil, nil) | |||
} | |||
case LogOrderCommitterTime: | |||
commitIter = object.NewCommitIterCTime(commit, nil, nil) | |||
default: | |||
return nil, fmt.Errorf("invalid Order=%v", o.Order) | |||
} | |||
if o.FileName == nil { | |||
return commitIter, nil | |||
return func(c *object.Commit) object.CommitIter { | |||
return object.NewCommitIterCTime(c, nil, nil) | |||
} | |||
} | |||
return object.NewCommitFileIterFromIter(*o.FileName, commitIter), nil | |||
return nil | |||
} | |||
// Tags returns all the tag References in a repository. | |||
@@ -14,6 +14,7 @@ import ( | |||
"gopkg.in/src-d/go-billy.v4/osfs" | |||
"gopkg.in/src-d/go-git.v4/plumbing" | |||
"gopkg.in/src-d/go-git.v4/storage" | |||
"gopkg.in/src-d/go-git.v4/utils/ioutil" | |||
"gopkg.in/src-d/go-billy.v4" | |||
@@ -596,7 +597,7 @@ func (d *DotGit) checkReferenceAndTruncate(f billy.File, old *plumbing.Reference | |||
return err | |||
} | |||
if ref.Hash() != old.Hash() { | |||
return fmt.Errorf("reference has changed concurrently") | |||
return storage.ErrReferenceHasChanged | |||
} | |||
_, err = f.Seek(0, io.SeekStart) | |||
if err != nil { | |||
@@ -1,15 +1,24 @@ | |||
// +build !norwfs | |||
package dotgit | |||
import ( | |||
"fmt" | |||
"os" | |||
"gopkg.in/src-d/go-git.v4/plumbing" | |||
"gopkg.in/src-d/go-git.v4/utils/ioutil" | |||
"gopkg.in/src-d/go-billy.v4" | |||
) | |||
func (d *DotGit) setRef(fileName, content string, old *plumbing.Reference) (err error) { | |||
if billy.CapabilityCheck(d.fs, billy.ReadAndWriteCapability) { | |||
return d.setRefRwfs(fileName, content, old) | |||
} | |||
return d.setRefNorwfs(fileName, content, old) | |||
} | |||
func (d *DotGit) setRefRwfs(fileName, content string, old *plumbing.Reference) (err error) { | |||
// If we are not checking an old ref, just truncate the file. | |||
mode := os.O_RDWR | os.O_CREATE | |||
if old == nil { | |||
@@ -41,3 +50,41 @@ func (d *DotGit) setRef(fileName, content string, old *plumbing.Reference) (err | |||
_, err = f.Write([]byte(content)) | |||
return err | |||
} | |||
// There are some filesystems that don't support opening files in RDWD mode. | |||
// In these filesystems the standard SetRef function can not be used as it | |||
// reads the reference file to check that it's not modified before updating it. | |||
// | |||
// This version of the function writes the reference without extra checks | |||
// making it compatible with these simple filesystems. This is usually not | |||
// a problem as they should be accessed by only one process at a time. | |||
func (d *DotGit) setRefNorwfs(fileName, content string, old *plumbing.Reference) error { | |||
_, err := d.fs.Stat(fileName) | |||
if err == nil && old != nil { | |||
fRead, err := d.fs.Open(fileName) | |||
if err != nil { | |||
return err | |||
} | |||
ref, err := d.readReferenceFrom(fRead, old.Name().String()) | |||
fRead.Close() | |||
if err != nil { | |||
return err | |||
} | |||
if ref.Hash() != old.Hash() { | |||
return fmt.Errorf("reference has changed concurrently") | |||
} | |||
} | |||
f, err := d.fs.Create(fileName) | |||
if err != nil { | |||
return err | |||
} | |||
defer f.Close() | |||
_, err = f.Write([]byte(content)) | |||
return err | |||
} |
@@ -1,47 +0,0 @@ | |||
// +build norwfs | |||
package dotgit | |||
import ( | |||
"fmt" | |||
"gopkg.in/src-d/go-git.v4/plumbing" | |||
) | |||
// There are some filesystems that don't support opening files in RDWD mode. | |||
// In these filesystems the standard SetRef function can not be used as i | |||
// reads the reference file to check that it's not modified before updating it. | |||
// | |||
// This version of the function writes the reference without extra checks | |||
// making it compatible with these simple filesystems. This is usually not | |||
// a problem as they should be accessed by only one process at a time. | |||
func (d *DotGit) setRef(fileName, content string, old *plumbing.Reference) error { | |||
_, err := d.fs.Stat(fileName) | |||
if err == nil && old != nil { | |||
fRead, err := d.fs.Open(fileName) | |||
if err != nil { | |||
return err | |||
} | |||
ref, err := d.readReferenceFrom(fRead, old.Name().String()) | |||
fRead.Close() | |||
if err != nil { | |||
return err | |||
} | |||
if ref.Hash() != old.Hash() { | |||
return fmt.Errorf("reference has changed concurrently") | |||
} | |||
} | |||
f, err := d.fs.Create(fileName) | |||
if err != nil { | |||
return err | |||
} | |||
defer f.Close() | |||
_, err = f.Write([]byte(content)) | |||
return err | |||
} |
@@ -20,24 +20,25 @@ import ( | |||
type ObjectStorage struct { | |||
options Options | |||
// deltaBaseCache is an object cache uses to cache delta's bases when | |||
deltaBaseCache cache.Object | |||
// objectCache is an object cache uses to cache delta's bases and also recently | |||
// loaded loose objects | |||
objectCache cache.Object | |||
dir *dotgit.DotGit | |||
index map[plumbing.Hash]idxfile.Index | |||
} | |||
// NewObjectStorage creates a new ObjectStorage with the given .git directory and cache. | |||
func NewObjectStorage(dir *dotgit.DotGit, cache cache.Object) *ObjectStorage { | |||
return NewObjectStorageWithOptions(dir, cache, Options{}) | |||
func NewObjectStorage(dir *dotgit.DotGit, objectCache cache.Object) *ObjectStorage { | |||
return NewObjectStorageWithOptions(dir, objectCache, Options{}) | |||
} | |||
// NewObjectStorageWithOptions creates a new ObjectStorage with the given .git directory, cache and extra options | |||
func NewObjectStorageWithOptions(dir *dotgit.DotGit, cache cache.Object, ops Options) *ObjectStorage { | |||
func NewObjectStorageWithOptions(dir *dotgit.DotGit, objectCache cache.Object, ops Options) *ObjectStorage { | |||
return &ObjectStorage{ | |||
options: ops, | |||
deltaBaseCache: cache, | |||
dir: dir, | |||
options: ops, | |||
objectCache: objectCache, | |||
dir: dir, | |||
} | |||
} | |||
@@ -206,7 +207,7 @@ func (s *ObjectStorage) encodedObjectSizeFromPackfile(h plumbing.Hash) ( | |||
idx := s.index[pack] | |||
hash, err := idx.FindHash(offset) | |||
if err == nil { | |||
obj, ok := s.deltaBaseCache.Get(hash) | |||
obj, ok := s.objectCache.Get(hash) | |||
if ok { | |||
return obj.Size(), nil | |||
} | |||
@@ -215,8 +216,8 @@ func (s *ObjectStorage) encodedObjectSizeFromPackfile(h plumbing.Hash) ( | |||
} | |||
var p *packfile.Packfile | |||
if s.deltaBaseCache != nil { | |||
p = packfile.NewPackfileWithCache(idx, s.dir.Fs(), f, s.deltaBaseCache) | |||
if s.objectCache != nil { | |||
p = packfile.NewPackfileWithCache(idx, s.dir.Fs(), f, s.objectCache) | |||
} else { | |||
p = packfile.NewPackfile(idx, s.dir.Fs(), f) | |||
} | |||
@@ -241,9 +242,19 @@ func (s *ObjectStorage) EncodedObjectSize(h plumbing.Hash) ( | |||
// 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) { | |||
obj, err := s.getFromUnpacked(h) | |||
if err == plumbing.ErrObjectNotFound { | |||
var obj plumbing.EncodedObject | |||
var err error | |||
if s.index != nil { | |||
obj, err = s.getFromPackfile(h, false) | |||
if err == plumbing.ErrObjectNotFound { | |||
obj, err = s.getFromUnpacked(h) | |||
} | |||
} else { | |||
obj, err = s.getFromUnpacked(h) | |||
if err == plumbing.ErrObjectNotFound { | |||
obj, err = s.getFromPackfile(h, false) | |||
} | |||
} | |||
// If the error is still object not found, check if it's a shared object | |||
@@ -254,7 +265,7 @@ func (s *ObjectStorage) EncodedObject(t plumbing.ObjectType, h plumbing.Hash) (p | |||
// Create a new object storage with the DotGit(s) and check for the | |||
// required hash object. Skip when not found. | |||
for _, dg := range dotgits { | |||
o := NewObjectStorage(dg, s.deltaBaseCache) | |||
o := NewObjectStorage(dg, s.objectCache) | |||
enobj, enerr := o.EncodedObject(t, h) | |||
if enerr != nil { | |||
continue | |||
@@ -304,9 +315,12 @@ func (s *ObjectStorage) getFromUnpacked(h plumbing.Hash) (obj plumbing.EncodedOb | |||
return nil, err | |||
} | |||
defer ioutil.CheckClose(f, &err) | |||
if cacheObj, found := s.objectCache.Get(h); found { | |||
return cacheObj, nil | |||
} | |||
obj = s.NewEncodedObject() | |||
r, err := objfile.NewReader(f) | |||
if err != nil { | |||
@@ -327,6 +341,8 @@ func (s *ObjectStorage) getFromUnpacked(h plumbing.Hash) (obj plumbing.EncodedOb | |||
return nil, err | |||
} | |||
s.objectCache.Put(obj) | |||
_, err = io.Copy(w, r) | |||
return obj, err | |||
} | |||
@@ -369,7 +385,7 @@ func (s *ObjectStorage) decodeObjectAt( | |||
) (plumbing.EncodedObject, error) { | |||
hash, err := idx.FindHash(offset) | |||
if err == nil { | |||
obj, ok := s.deltaBaseCache.Get(hash) | |||
obj, ok := s.objectCache.Get(hash) | |||
if ok { | |||
return obj, nil | |||
} | |||
@@ -380,8 +396,8 @@ func (s *ObjectStorage) decodeObjectAt( | |||
} | |||
var p *packfile.Packfile | |||
if s.deltaBaseCache != nil { | |||
p = packfile.NewPackfileWithCache(idx, s.dir.Fs(), f, s.deltaBaseCache) | |||
if s.objectCache != nil { | |||
p = packfile.NewPackfileWithCache(idx, s.dir.Fs(), f, s.objectCache) | |||
} else { | |||
p = packfile.NewPackfile(idx, s.dir.Fs(), f) | |||
} | |||
@@ -400,11 +416,7 @@ func (s *ObjectStorage) decodeDeltaObjectAt( | |||
} | |||
p := packfile.NewScanner(f) | |||
if _, err := p.SeekFromStart(offset); err != nil { | |||
return nil, err | |||
} | |||
header, err := p.NextObjectHeader() | |||
header, err := p.SeekObjectHeader(offset) | |||
if err != nil { | |||
return nil, err | |||
} | |||
@@ -495,7 +507,7 @@ func (s *ObjectStorage) buildPackfileIters( | |||
} | |||
return newPackfileIter( | |||
s.dir.Fs(), pack, t, seen, s.index[h], | |||
s.deltaBaseCache, s.options.KeepDescriptors, | |||
s.objectCache, s.options.KeepDescriptors, | |||
) | |||
}, | |||
}, nil | |||
@@ -51,11 +51,7 @@ func NewStorageWithOptions(fs billy.Filesystem, cache cache.Object, ops Options) | |||
fs: fs, | |||
dir: dir, | |||
ObjectStorage: ObjectStorage{ | |||
options: ops, | |||
deltaBaseCache: cache, | |||
dir: dir, | |||
}, | |||
ObjectStorage: *NewObjectStorageWithOptions(dir, cache, ops), | |||
ReferenceStorage: ReferenceStorage{dir: dir}, | |||
IndexStorage: IndexStorage{dir: dir}, | |||
ShallowStorage: ShallowStorage{dir: dir}, | |||
@@ -13,7 +13,6 @@ import ( | |||
) | |||
var ErrUnsupportedObjectType = fmt.Errorf("unsupported object type") | |||
var ErrRefHasChanged = fmt.Errorf("reference has changed concurrently") | |||
// Storage is an implementation of git.Storer that stores data on memory, being | |||
// ephemeral. The use of this storage should be done in controlled envoriments, | |||
@@ -258,7 +257,7 @@ func (r ReferenceStorage) CheckAndSetReference(ref, old *plumbing.Reference) err | |||
if old != nil { | |||
tmp := r[ref.Name()] | |||
if tmp != nil && tmp.Hash() != old.Hash() { | |||
return ErrRefHasChanged | |||
return storage.ErrReferenceHasChanged | |||
} | |||
} | |||
r[ref.Name()] = ref | |||
@@ -1,10 +1,14 @@ | |||
package storage | |||
import ( | |||
"errors" | |||
"gopkg.in/src-d/go-git.v4/config" | |||
"gopkg.in/src-d/go-git.v4/plumbing/storer" | |||
) | |||
var ErrReferenceHasChanged = errors.New("reference has changed concurrently") | |||
// Storer is a generic storage of objects, references and any information | |||
// related to a particular repository. The package gopkg.in/src-d/go-git.v4/storage | |||
// contains two implementation a filesystem base implementation (such as `.git`) | |||
@@ -3,8 +3,6 @@ package noder | |||
import ( | |||
"bytes" | |||
"strings" | |||
"golang.org/x/text/unicode/norm" | |||
) | |||
// Path values represent a noder and its ancestors. The root goes first | |||
@@ -80,11 +78,9 @@ func (p Path) Compare(other Path) int { | |||
case i == len(p): | |||
return -1 | |||
default: | |||
form := norm.Form(norm.NFC) | |||
this := form.String(p[i].Name()) | |||
that := form.String(other[i].Name()) | |||
cmp := strings.Compare(this, that) | |||
// We do *not* normalize Unicode here. CGit doesn't. | |||
// https://github.com/src-d/go-git/issues/1057 | |||
cmp := strings.Compare(p[i].Name(), other[i].Name()) | |||
if cmp != 0 { | |||
return cmp | |||
} | |||
@@ -25,10 +25,11 @@ import ( | |||
) | |||
var ( | |||
ErrWorktreeNotClean = errors.New("worktree is not clean") | |||
ErrSubmoduleNotFound = errors.New("submodule not found") | |||
ErrUnstagedChanges = errors.New("worktree contains unstaged changes") | |||
ErrGitModulesSymlink = errors.New(gitmodulesFile + " is a symlink") | |||
ErrWorktreeNotClean = errors.New("worktree is not clean") | |||
ErrSubmoduleNotFound = errors.New("submodule not found") | |||
ErrUnstagedChanges = errors.New("worktree contains unstaged changes") | |||
ErrGitModulesSymlink = errors.New(gitmodulesFile + " is a symlink") | |||
ErrNonFastForwardUpdate = errors.New("non-fast-forward update") | |||
) | |||
// Worktree represents a git worktree. | |||
@@ -101,7 +102,7 @@ func (w *Worktree) PullContext(ctx context.Context, o *PullOptions) error { | |||
} | |||
if !ff { | |||
return fmt.Errorf("non-fast-forward update") | |||
return ErrNonFastForwardUpdate | |||
} | |||
} | |||
@@ -415,7 +415,7 @@ gopkg.in/src-d/go-billy.v4/osfs | |||
gopkg.in/src-d/go-billy.v4/util | |||
gopkg.in/src-d/go-billy.v4/helper/chroot | |||
gopkg.in/src-d/go-billy.v4/helper/polyfill | |||
# gopkg.in/src-d/go-git.v4 v4.8.0 | |||
# gopkg.in/src-d/go-git.v4 v4.10.0 | |||
gopkg.in/src-d/go-git.v4 | |||
gopkg.in/src-d/go-git.v4/config | |||
gopkg.in/src-d/go-git.v4/plumbing | |||
@@ -442,6 +442,7 @@ gopkg.in/src-d/go-git.v4/utils/merkletrie | |||
gopkg.in/src-d/go-git.v4/utils/merkletrie/filesystem | |||
gopkg.in/src-d/go-git.v4/utils/merkletrie/index | |||
gopkg.in/src-d/go-git.v4/utils/merkletrie/noder | |||
gopkg.in/src-d/go-git.v4/internal/url | |||
gopkg.in/src-d/go-git.v4/plumbing/format/config | |||
gopkg.in/src-d/go-git.v4/utils/binary | |||
gopkg.in/src-d/go-git.v4/plumbing/format/idxfile | |||