diff options
-rw-r--r-- | worktree_commit.go | 1 | ||||
-rw-r--r-- | worktree_status.go | 218 | ||||
-rw-r--r-- | worktree_test.go | 160 |
3 files changed, 344 insertions, 35 deletions
diff --git a/worktree_commit.go b/worktree_commit.go index 3145c8a..5fa63ab 100644 --- a/worktree_commit.go +++ b/worktree_commit.go @@ -63,7 +63,6 @@ func (w *Worktree) autoAddModifiedAndDeleted() error { if _, err := w.Add(path); err != nil { return err } - } return nil diff --git a/worktree_status.go b/worktree_status.go index 36f48eb..4141381 100644 --- a/worktree_status.go +++ b/worktree_status.go @@ -6,6 +6,7 @@ import ( "io" "os" + "gopkg.in/src-d/go-billy.v4/util" "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/filemode" "gopkg.in/src-d/go-git.v4/plumbing/format/gitignore" @@ -18,9 +19,16 @@ import ( "gopkg.in/src-d/go-git.v4/utils/merkletrie/noder" ) -// ErrDestinationExists in an Move operation means that the target exists on -// the worktree. -var ErrDestinationExists = errors.New("destination exists") +var ( + // ErrDestinationExists in an Move operation means that the target exists on + // the worktree. + ErrDestinationExists = errors.New("destination exists") + // ErrGlobNoMatches in an AddGlob if the glob pattern does not match any + // file in the worktree.ErrNotDirectory + ErrGlobNoMatches = errors.New("glob pattern did not match any files") + // ErrNotDirectory in an AddDirectory if the path is not a directory. + ErrNotDirectory = errors.New("path is not a directory") +) // Status returns the working tree status. func (w *Worktree) Status() (Status, error) { @@ -243,30 +251,170 @@ func diffTreeIsEquals(a, b noder.Hasher) bool { } // Add adds the file contents of a file in the worktree to the index. if the -// file is already staged in the index no error is returned. +// file is already staged in the index no error is returned. If a file deleted +// from the Workspace is given, the file is removed from the index. func (w *Worktree) Add(path string) (plumbing.Hash, error) { s, err := w.Status() if err != nil { return plumbing.ZeroHash, err } - h, err := w.copyFileToStorage(path) + idx, err := w.r.Storer.Index() + if err != nil { + return plumbing.ZeroHash, err + } + + added, h, err := w.doAdd(idx, s, path) + if err != nil { + return h, err + } + + if !added { + return h, nil + } + + return h, w.r.Storer.SetIndex(idx) +} + +// AddDirectory adds the files contents of a directory and all his +// sub-directories recursively in the worktree to the index. If any of the +// file is already staged in the index no error is returned. +func (w *Worktree) AddDirectory(path string) error { + fi, err := w.Filesystem.Lstat(path) + if err != nil { + return err + } + + if !fi.IsDir() { + return ErrNotDirectory + } + + s, err := w.Status() + if err != nil { + return err + } + + idx, err := w.r.Storer.Index() + if err != nil { + return err + } + + added, err := w.doAddDirectory(idx, s, path) + if err != nil { + return err + } + + if !added { + return nil + } + + return w.r.Storer.SetIndex(idx) +} + +func (w *Worktree) doAddDirectory(idx *index.Index, s Status, path string) (added bool, err error) { + files, err := w.Filesystem.ReadDir(path) + if err != nil { + return false, err + } + + for _, file := range files { + name := w.Filesystem.Join(path, file.Name()) + + var a bool + if file.IsDir() { + a, err = w.doAddDirectory(idx, s, name) + } else { + a, _, err = w.doAdd(idx, s, name) + } + + if err != nil { + return + } + + if !added && a { + added = true + } + } + + return + +} + +// AddGlob given a glob pattern adds all the matching files content and all his +// sub-directories recursively in the worktree to the index. If any of the +// file is already staged in the index no error is returned. +func (w *Worktree) AddGlob(pattern string) error { + files, err := util.Glob(w.Filesystem, pattern) + if err != nil { + return err + } + + if len(files) == 0 { + return ErrGlobNoMatches + } + + s, err := w.Status() + if err != nil { + return err + } + + idx, err := w.r.Storer.Index() + if err != nil { + return err + } + + var saveIndex bool + for _, file := range files { + fi, err := w.Filesystem.Lstat(file) + if err != nil { + return err + } + + var added bool + if fi.IsDir() { + added, err = w.doAddDirectory(idx, s, file) + } else { + added, _, err = w.doAdd(idx, s, file) + } + + if err != nil { + return err + } + + if !saveIndex && added { + saveIndex = true + } + } + + if saveIndex { + return w.r.Storer.SetIndex(idx) + } + + return nil +} + +// doAdd create a new blob from path and update the index, added is true if +// the file added is different from the index. +func (w *Worktree) doAdd(idx *index.Index, s Status, path string) (added bool, h plumbing.Hash, err error) { + h, err = w.copyFileToStorage(path) if err != nil { if os.IsNotExist(err) { - h, err = w.deleteFromIndex(path) + added = true + h, err = w.deleteFromIndex(idx, path) } - return h, err + + return } if s.File(path).Worktree == Unmodified { - return h, nil + return false, h, nil } - if err := w.addOrUpdateFileToIndex(path, h); err != nil { - return h, err + if err := w.addOrUpdateFileToIndex(idx, path, h); err != nil { + return false, h, err } - return h, err + return true, h, err } func (w *Worktree) copyFileToStorage(path string) (hash plumbing.Hash, err error) { @@ -324,28 +472,17 @@ func (w *Worktree) fillEncodedObjectFromSymlink(dst io.Writer, path string, fi o return err } -func (w *Worktree) addOrUpdateFileToIndex(filename string, h plumbing.Hash) error { - idx, err := w.r.Storer.Index() - if err != nil { - return err - } - +func (w *Worktree) addOrUpdateFileToIndex(idx *index.Index, filename string, h plumbing.Hash) error { e, err := idx.Entry(filename) if err != nil && err != index.ErrEntryNotFound { return err } if err == index.ErrEntryNotFound { - if err := w.doAddFileToIndex(idx, filename, h); err != nil { - return err - } - } else { - if err := w.doUpdateFileToIndex(e, filename, h); err != nil { - return err - } + return w.doAddFileToIndex(idx, filename, h) } - return w.r.Storer.SetIndex(idx) + return w.doUpdateFileToIndex(e, filename, h) } func (w *Worktree) doAddFileToIndex(idx *index.Index, filename string, h plumbing.Hash) error { @@ -378,26 +515,30 @@ func (w *Worktree) doUpdateFileToIndex(e *index.Entry, filename string, h plumbi // Remove removes files from the working tree and from the index. func (w *Worktree) Remove(path string) (plumbing.Hash, error) { - hash, err := w.deleteFromIndex(path) + idx, err := w.r.Storer.Index() if err != nil { return plumbing.ZeroHash, err } - return hash, w.deleteFromFilesystem(path) -} - -func (w *Worktree) deleteFromIndex(path string) (plumbing.Hash, error) { - idx, err := w.r.Storer.Index() + hash, err := w.deleteFromIndex(idx, path) if err != nil { return plumbing.ZeroHash, err } + if err := w.deleteFromFilesystem(path); err != nil { + return hash, err + } + + return hash, w.r.Storer.SetIndex(idx) +} + +func (w *Worktree) deleteFromIndex(idx *index.Index, path string) (plumbing.Hash, error) { e, err := idx.Remove(path) if err != nil { return plumbing.ZeroHash, err } - return e.Hash, w.r.Storer.SetIndex(idx) + return e.Hash, nil } func (w *Worktree) deleteFromFilesystem(path string) error { @@ -420,7 +561,12 @@ func (w *Worktree) Move(from, to string) (plumbing.Hash, error) { return plumbing.ZeroHash, ErrDestinationExists } - hash, err := w.deleteFromIndex(from) + idx, err := w.r.Storer.Index() + if err != nil { + return plumbing.ZeroHash, err + } + + hash, err := w.deleteFromIndex(idx, from) if err != nil { return plumbing.ZeroHash, err } @@ -429,5 +575,9 @@ func (w *Worktree) Move(from, to string) (plumbing.Hash, error) { return hash, err } - return hash, w.addOrUpdateFileToIndex(to, hash) + if err := w.addOrUpdateFileToIndex(idx, to, hash); err != nil { + return hash, err + } + + return hash, w.r.Storer.SetIndex(idx) } diff --git a/worktree_test.go b/worktree_test.go index e51e89a..8f30e82 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -1115,6 +1115,40 @@ func (s *WorktreeSuite) TestAddUnmodified(c *C) { c.Assert(err, IsNil) } +func (s *WorktreeSuite) TestAddRemoved(c *C) { + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + Filesystem: fs, + } + + err := w.Checkout(&CheckoutOptions{Force: true}) + c.Assert(err, IsNil) + + idx, err := w.r.Storer.Index() + c.Assert(err, IsNil) + c.Assert(idx.Entries, HasLen, 9) + + err = w.Filesystem.Remove("LICENSE") + c.Assert(err, IsNil) + + hash, err := w.Add("LICENSE") + c.Assert(err, IsNil) + c.Assert(hash.String(), Equals, "c192bd6a24ea1ab01d78686e417c8bdc7c3d197f") + + e, err := idx.Entry("LICENSE") + c.Assert(err, IsNil) + c.Assert(e.Hash, Equals, hash) + c.Assert(e.Mode, Equals, filemode.Regular) + + status, err := w.Status() + c.Assert(err, IsNil) + c.Assert(status, HasLen, 1) + + file := status.File("LICENSE") + c.Assert(file.Staging, Equals, Deleted) +} + func (s *WorktreeSuite) TestAddSymlink(c *C) { dir, err := ioutil.TempDir("", "checkout") c.Assert(err, IsNil) @@ -1141,7 +1175,133 @@ func (s *WorktreeSuite) TestAddSymlink(c *C) { c.Assert(err, IsNil) c.Assert(obj, NotNil) c.Assert(obj.Size(), Equals, int64(3)) +} + +func (s *WorktreeSuite) TestAddDirectory(c *C) { + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + Filesystem: fs, + } + + err := w.Checkout(&CheckoutOptions{Force: true}) + c.Assert(err, IsNil) + + idx, err := w.r.Storer.Index() + c.Assert(err, IsNil) + c.Assert(idx.Entries, HasLen, 9) + + err = util.WriteFile(w.Filesystem, "qux/foo", []byte("FOO"), 0755) + c.Assert(err, IsNil) + err = util.WriteFile(w.Filesystem, "qux/baz/bar", []byte("BAR"), 0755) + c.Assert(err, IsNil) + + err = w.AddDirectory("qux") + c.Assert(err, IsNil) + + idx, err = w.r.Storer.Index() + c.Assert(err, IsNil) + c.Assert(idx.Entries, HasLen, 11) + + e, err := idx.Entry("qux/foo") + c.Assert(err, IsNil) + c.Assert(e.Mode, Equals, filemode.Executable) + + e, err = idx.Entry("qux/baz/bar") + c.Assert(err, IsNil) + c.Assert(e.Mode, Equals, filemode.Executable) + + status, err := w.Status() + c.Assert(err, IsNil) + c.Assert(status, HasLen, 2) + + file := status.File("qux/foo") + c.Assert(file.Staging, Equals, Added) + c.Assert(file.Worktree, Equals, Unmodified) + + file = status.File("qux/baz/bar") + c.Assert(file.Staging, Equals, Added) + c.Assert(file.Worktree, Equals, Unmodified) +} + +func (s *WorktreeSuite) TestAddDirectoryErrorNotDirectory(c *C) { + r, _ := Init(memory.NewStorage(), memfs.New()) + w, _ := r.Worktree() + + err := util.WriteFile(w.Filesystem, "foo", []byte("FOO"), 0755) + c.Assert(err, IsNil) + + err = w.AddDirectory("foo") + c.Assert(err, Equals, ErrNotDirectory) +} + +func (s *WorktreeSuite) TestAddDirectoryErrorNotFound(c *C) { + r, _ := Init(memory.NewStorage(), memfs.New()) + w, _ := r.Worktree() + + err := w.AddDirectory("foo") + c.Assert(err, NotNil) +} + +func (s *WorktreeSuite) TestAddGlob(c *C) { + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + Filesystem: fs, + } + + err := w.Checkout(&CheckoutOptions{Force: true}) + c.Assert(err, IsNil) + + idx, err := w.r.Storer.Index() + c.Assert(err, IsNil) + c.Assert(idx.Entries, HasLen, 9) + + err = util.WriteFile(w.Filesystem, "qux/qux", []byte("QUX"), 0755) + c.Assert(err, IsNil) + err = util.WriteFile(w.Filesystem, "qux/baz", []byte("BAZ"), 0755) + c.Assert(err, IsNil) + err = util.WriteFile(w.Filesystem, "qux/bar/baz", []byte("BAZ"), 0755) + c.Assert(err, IsNil) + + err = w.AddGlob("qux/b*") + c.Assert(err, IsNil) + + idx, err = w.r.Storer.Index() + c.Assert(err, IsNil) + c.Assert(idx.Entries, HasLen, 11) + + e, err := idx.Entry("qux/baz") + c.Assert(err, IsNil) + c.Assert(e.Mode, Equals, filemode.Executable) + + e, err = idx.Entry("qux/bar/baz") + c.Assert(err, IsNil) + c.Assert(e.Mode, Equals, filemode.Executable) + + status, err := w.Status() + c.Assert(err, IsNil) + c.Assert(status, HasLen, 3) + + file := status.File("qux/qux") + c.Assert(file.Staging, Equals, Untracked) + c.Assert(file.Worktree, Equals, Untracked) + + file = status.File("qux/baz") + c.Assert(file.Staging, Equals, Added) + c.Assert(file.Worktree, Equals, Unmodified) + + file = status.File("qux/bar/baz") + c.Assert(file.Staging, Equals, Added) + c.Assert(file.Worktree, Equals, Unmodified) +} + +func (s *WorktreeSuite) TestAddGlobErrorNoMatches(c *C) { + r, _ := Init(memory.NewStorage(), memfs.New()) + w, _ := r.Worktree() + err := w.AddGlob("foo") + c.Assert(err, Equals, ErrGlobNoMatches) } func (s *WorktreeSuite) TestRemove(c *C) { |