diff options
-rw-r--r-- | .travis.yml | 2 | ||||
-rw-r--r-- | _examples/commit/main.go | 2 | ||||
-rw-r--r-- | plumbing/object/commit_walker_bfs.go | 2 | ||||
-rw-r--r-- | plumbing/object/tree.go | 2 | ||||
-rw-r--r-- | plumbing/object/tree_test.go | 3 | ||||
-rw-r--r-- | repository.go | 17 | ||||
-rw-r--r-- | repository_test.go | 30 | ||||
-rw-r--r-- | storage/transactional/storage.go | 43 | ||||
-rw-r--r-- | storage/transactional/storage_test.go | 33 | ||||
-rw-r--r-- | utils/diff/diff.go | 18 |
10 files changed, 138 insertions, 14 deletions
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 @@ func main() { // ... 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 @@ func (w *bfsCommitIterator) Next() (*Commit, error) { 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 @@ func (t *Tree) FindEntry(path string) (*TreeEntry, error) { 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 @@ func (s *TreeSuite) TestFindEntryNotFound(c *C) { 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 @@ var ( 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 @@ func (r *Repository) CreateRemote(c *config.RemoteConfig) (*Remote, error) { 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 @@ func (s *RepositorySuite) TestCreateRemoteInvalid(c *C) { 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 @@ import ( // // 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 @@ type Storage struct { *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 @@ func NewStorage(base, temporal storage.Storer) *Storage { 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 @@ func (s *Storage) Module(name string) (storage.Storer, error) { } // 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 @@ func (s *Storage) Commit() error { 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 @@ import ( "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 @@ func Test(t *testing.T) { TestingT(t) } 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) SetUpTest(c *C) { 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 @@ func (s *StorageSuite) TestCommit(c *C) { 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 @@ package diff 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) |