Merge pull request #1088 from oleksii-shnyra/fix-1074

plumbing: object, Count stats properly when no new line added at the …
diff --git a/.travis.yml b/.travis.yml
index c68b5f4..3a65f3e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,8 @@
 language: go
 
 go:
-  - "1.10"
   - "1.11"
+  - "1.12"
 
 go_import_path: gopkg.in/src-d/go-git.v4
 
diff --git a/_examples/commit/main.go b/_examples/commit/main.go
index ec296b9..f184b81 100644
--- a/_examples/commit/main.go
+++ b/_examples/commit/main.go
@@ -27,7 +27,7 @@
 
 	// ... we need a file to commit so let's create a new file inside of the
 	// worktree of the project using the go standard library.
-	Info("echo \"hellow world!\" > example-git-file")
+	Info("echo \"hello world!\" > example-git-file")
 	filename := filepath.Join(directory, "example-git-file")
 	err = ioutil.WriteFile(filename, []byte("hello world!"), 0644)
 	CheckIfError(err)
diff --git a/plumbing/object/commit_walker_bfs.go b/plumbing/object/commit_walker_bfs.go
index aef1cf2..dabfe75 100644
--- a/plumbing/object/commit_walker_bfs.go
+++ b/plumbing/object/commit_walker_bfs.go
@@ -67,7 +67,7 @@
 		for _, h := range c.ParentHashes {
 			err := w.appendHash(c.s, h)
 			if err != nil {
-				return nil, nil
+				return nil, err
 			}
 		}
 
diff --git a/plumbing/object/tree.go b/plumbing/object/tree.go
index 78d61a1..1f9ea26 100644
--- a/plumbing/object/tree.go
+++ b/plumbing/object/tree.go
@@ -135,7 +135,7 @@
 	pathCurrent := ""
 
 	// search for the longest path in the tree path cache
-	for i := len(pathParts); i > 1; i-- {
+	for i := len(pathParts) - 1; i > 1; i-- {
 		path := filepath.Join(pathParts[:i]...)
 
 		tree, ok := t.t[path]
diff --git a/plumbing/object/tree_test.go b/plumbing/object/tree_test.go
index 889c63a..b408595 100644
--- a/plumbing/object/tree_test.go
+++ b/plumbing/object/tree_test.go
@@ -125,6 +125,9 @@
 	e, err := s.Tree.FindEntry("not-found")
 	c.Assert(e, IsNil)
 	c.Assert(err, Equals, ErrEntryNotFound)
+	e, err = s.Tree.FindEntry("not-found/not-found/not-found")
+	c.Assert(e, IsNil)
+	c.Assert(err, Equals, ErrDirectoryNotFound)
 }
 
 // Overrides returned plumbing.EncodedObject for given hash.
diff --git a/repository.go b/repository.go
index de92d64..e5b12b0 100644
--- a/repository.go
+++ b/repository.go
@@ -49,6 +49,7 @@
 	ErrRepositoryAlreadyExists   = errors.New("repository already exists")
 	ErrRemoteNotFound            = errors.New("remote not found")
 	ErrRemoteExists              = errors.New("remote already exists")
+	ErrAnonymousRemoteName       = errors.New("anonymous remote name must be 'anonymous'")
 	ErrWorktreeNotProvided       = errors.New("worktree should be provided")
 	ErrIsBareRepository          = errors.New("worktree not available in a bare repository")
 	ErrUnableToResolveCommit     = errors.New("unable to resolve commit")
@@ -492,6 +493,22 @@
 	return remote, r.Storer.SetConfig(cfg)
 }
 
+// CreateRemoteAnonymous creates a new anonymous remote. c.Name must be "anonymous".
+// It's used like 'git fetch git@github.com:src-d/go-git.git master:master'.
+func (r *Repository) CreateRemoteAnonymous(c *config.RemoteConfig) (*Remote, error) {
+	if err := c.Validate(); err != nil {
+		return nil, err
+	}
+
+	if c.Name != "anonymous" {
+		return nil, ErrAnonymousRemoteName
+	}
+
+	remote := newRemote(r.Storer, c)
+
+	return remote, nil
+}
+
 // DeleteRemote delete a remote from the repository and delete the config
 func (r *Repository) DeleteRemote(name string) error {
 	cfg, err := r.Storer.Config()
diff --git a/repository_test.go b/repository_test.go
index 1549737..ccbe29b 100644
--- a/repository_test.go
+++ b/repository_test.go
@@ -231,6 +231,36 @@
 	c.Assert(remote, IsNil)
 }
 
+func (s *RepositorySuite) TestCreateRemoteAnonymous(c *C) {
+	r, _ := Init(memory.NewStorage(), nil)
+	remote, err := r.CreateRemoteAnonymous(&config.RemoteConfig{
+		Name: "anonymous",
+		URLs: []string{"http://foo/foo.git"},
+	})
+
+	c.Assert(err, IsNil)
+	c.Assert(remote.Config().Name, Equals, "anonymous")
+}
+
+func (s *RepositorySuite) TestCreateRemoteAnonymousInvalidName(c *C) {
+	r, _ := Init(memory.NewStorage(), nil)
+	remote, err := r.CreateRemoteAnonymous(&config.RemoteConfig{
+		Name: "not_anonymous",
+		URLs: []string{"http://foo/foo.git"},
+	})
+
+	c.Assert(err, Equals, ErrAnonymousRemoteName)
+	c.Assert(remote, IsNil)
+}
+
+func (s *RepositorySuite) TestCreateRemoteAnonymousInvalid(c *C) {
+	r, _ := Init(memory.NewStorage(), nil)
+	remote, err := r.CreateRemoteAnonymous(&config.RemoteConfig{})
+
+	c.Assert(err, Equals, config.ErrRemoteConfigEmptyName)
+	c.Assert(remote, IsNil)
+}
+
 func (s *RepositorySuite) TestDeleteRemote(c *C) {
 	r, _ := Init(memory.NewStorage(), nil)
 	_, err := r.CreateRemote(&config.RemoteConfig{
diff --git a/storage/transactional/storage.go b/storage/transactional/storage.go
index fbb3d35..b81b104 100644
--- a/storage/transactional/storage.go
+++ b/storage/transactional/storage.go
@@ -1,6 +1,9 @@
 package transactional
 
 import (
+	"io"
+
+	"gopkg.in/src-d/go-git.v4/plumbing/storer"
 	"gopkg.in/src-d/go-git.v4/storage"
 )
 
@@ -10,7 +13,13 @@
 //
 // The API and functionality of this package are considered EXPERIMENTAL and is
 // not considered stable nor production ready.
-type Storage struct {
+type Storage interface {
+	storage.Storer
+	Commit() error
+}
+
+// basic implements the Storage interface.
+type basic struct {
 	s, temporal storage.Storer
 
 	*ObjectStorage
@@ -20,11 +29,18 @@
 	*ConfigStorage
 }
 
+// packageWriter implements storer.PackfileWriter interface over
+// a Storage with a temporal storer that supports it.
+type packageWriter struct {
+	*basic
+	pw storer.PackfileWriter
+}
+
 // NewStorage returns a new Storage based on two repositories, base is the base
-// repository where the read operations are read and temportal is were all
+// repository where the read operations are read and temporal is were all
 // the write operations are stored.
-func NewStorage(base, temporal storage.Storer) *Storage {
-	return &Storage{
+func NewStorage(base, temporal storage.Storer) Storage {
+	st := &basic{
 		s:        base,
 		temporal: temporal,
 
@@ -34,10 +50,20 @@
 		ShallowStorage:   NewShallowStorage(base, temporal),
 		ConfigStorage:    NewConfigStorage(base, temporal),
 	}
+
+	pw, ok := temporal.(storer.PackfileWriter)
+	if ok {
+		return &packageWriter{
+			basic: st,
+			pw:    pw,
+		}
+	}
+
+	return st
 }
 
 // Module it honors the storage.ModuleStorer interface.
-func (s *Storage) Module(name string) (storage.Storer, error) {
+func (s *basic) Module(name string) (storage.Storer, error) {
 	base, err := s.s.Module(name)
 	if err != nil {
 		return nil, err
@@ -52,7 +78,7 @@
 }
 
 // Commit it copies the content of the temporal storage into the base storage.
-func (s *Storage) Commit() error {
+func (s *basic) Commit() error {
 	for _, c := range []interface{ Commit() error }{
 		s.ObjectStorage,
 		s.ReferenceStorage,
@@ -67,3 +93,8 @@
 
 	return nil
 }
+
+// PackfileWriter honors storage.PackfileWriter.
+func (s *packageWriter) PackfileWriter() (io.WriteCloser, error) {
+	return s.pw.PackfileWriter()
+}
diff --git a/storage/transactional/storage_test.go b/storage/transactional/storage_test.go
index 6aaea0d..63ebfb1 100644
--- a/storage/transactional/storage_test.go
+++ b/storage/transactional/storage_test.go
@@ -4,7 +4,12 @@
 	"testing"
 
 	. "gopkg.in/check.v1"
+	"gopkg.in/src-d/go-billy.v4/memfs"
 	"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/storer"
+	"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/storage/test"
 )
@@ -13,13 +18,25 @@
 
 type StorageSuite struct {
 	test.BaseStorageSuite
+	temporal func() storage.Storer
 }
 
-var _ = Suite(&StorageSuite{})
+var _ = Suite(&StorageSuite{
+	temporal: func() storage.Storer {
+		return memory.NewStorage()
+	},
+})
+
+var _ = Suite(&StorageSuite{
+	temporal: func() storage.Storer {
+		fs := memfs.New()
+		return filesystem.NewStorage(fs, cache.NewObjectLRUDefault())
+	},
+})
 
 func (s *StorageSuite) SetUpTest(c *C) {
 	base := memory.NewStorage()
-	temporal := memory.NewStorage()
+	temporal := s.temporal()
 
 	s.BaseStorageSuite = test.NewBaseStorageSuite(NewStorage(base, temporal))
 	s.BaseStorageSuite.SetUpTest(c)
@@ -27,7 +44,7 @@
 
 func (s *StorageSuite) TestCommit(c *C) {
 	base := memory.NewStorage()
-	temporal := memory.NewStorage()
+	temporal := s.temporal()
 	st := NewStorage(base, temporal)
 
 	commit := base.NewEncodedObject()
@@ -50,3 +67,13 @@
 	c.Assert(err, IsNil)
 	c.Assert(obj.Hash(), Equals, commit.Hash())
 }
+
+func (s *StorageSuite) TestTransactionalPackfileWriter(c *C) {
+	base := memory.NewStorage()
+	temporal := s.temporal()
+	st := NewStorage(base, temporal)
+
+	_, tmpOK := temporal.(storer.PackfileWriter)
+	_, ok := st.(storer.PackfileWriter)
+	c.Assert(ok, Equals, tmpOK)
+}
diff --git a/utils/diff/diff.go b/utils/diff/diff.go
index f49ae55..6142ed0 100644
--- a/utils/diff/diff.go
+++ b/utils/diff/diff.go
@@ -8,14 +8,30 @@
 
 import (
 	"bytes"
+	"time"
 
 	"github.com/sergi/go-diff/diffmatchpatch"
 )
 
 // Do computes the (line oriented) modifications needed to turn the src
-// string into the dst string.
+// string into the dst string. The underlying algorithm is Meyers,
+// its complexity is O(N*d) where N is min(lines(src), lines(dst)) and d
+// is the size of the diff.
 func Do(src, dst string) (diffs []diffmatchpatch.Diff) {
+	// the default timeout is time.Second which may be too small under heavy load
+	return DoWithTimeout(src, dst, time.Hour)
+}
+
+// DoWithTimeout computes the (line oriented) modifications needed to turn the src
+// string into the dst string. The `timeout` argument specifies the maximum
+// amount of time it is allowed to spend in this function. If the timeout
+// is exceeded, the parts of the strings which were not considered are turned into
+// a bulk delete+insert and the half-baked suboptimal result is returned at once.
+// The underlying algorithm is Meyers, its complexity is O(N*d) where N is
+// min(lines(src), lines(dst)) and d is the size of the diff.
+func DoWithTimeout (src, dst string, timeout time.Duration) (diffs []diffmatchpatch.Diff) {
 	dmp := diffmatchpatch.New()
+	dmp.DiffTimeout = timeout
 	wSrc, wDst, warray := dmp.DiffLinesToRunes(src, dst)
 	diffs = dmp.DiffMainRunes(wSrc, wDst, false)
 	diffs = dmp.DiffCharsToLines(diffs, warray)