diff options
-rw-r--r-- | COMPATIBILITY.md | 4 | ||||
-rw-r--r-- | plumbing/format/index/index.go | 12 | ||||
-rw-r--r-- | plumbing/format/index/index_test.go | 17 | ||||
-rw-r--r-- | worktree_status.go | 65 | ||||
-rw-r--r-- | worktree_test.go | 110 |
5 files changed, 206 insertions, 2 deletions
diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 81ff6b6..1c17483 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -16,8 +16,8 @@ is supported by go-git. | status | ✔ | | commit | ✔ | | reset | ✔ | -| rm | ✖ | -| mv | ✖ | +| rm | ✔ | +| mv | ✔ | | **branching and merging** | | branch | ✔ | | checkout | ✔ | Basic usages of checkout are supported. | diff --git a/plumbing/format/index/index.go b/plumbing/format/index/index.go index 782e3d1..9de4230 100644 --- a/plumbing/format/index/index.go +++ b/plumbing/format/index/index.go @@ -62,6 +62,18 @@ func (i *Index) Entry(path string) (*Entry, error) { return nil, ErrEntryNotFound } +// Remove remove the entry that match the give path and returns deleted entry. +func (i *Index) Remove(path string) (*Entry, error) { + for index, e := range i.Entries { + if e.Name == path { + i.Entries = append(i.Entries[:index], i.Entries[index+1:]...) + return e, nil + } + } + + return nil, ErrEntryNotFound +} + // String is equivalent to `git ls-files --stage --debug` func (i *Index) String() string { buf := bytes.NewBuffer(nil) diff --git a/plumbing/format/index/index_test.go b/plumbing/format/index/index_test.go index 67286b3..cad5f9c 100644 --- a/plumbing/format/index/index_test.go +++ b/plumbing/format/index/index_test.go @@ -20,3 +20,20 @@ func (s *IndexSuite) TestIndexEntry(c *C) { c.Assert(e, IsNil) c.Assert(err, Equals, ErrEntryNotFound) } + +func (s *IndexSuite) TestIndexRemove(c *C) { + idx := &Index{ + Entries: []*Entry{ + {Name: "foo", Size: 42}, + {Name: "bar", Size: 82}, + }, + } + + e, err := idx.Remove("foo") + c.Assert(err, IsNil) + c.Assert(e.Name, Equals, "foo") + + e, err = idx.Remove("foo") + c.Assert(e, IsNil) + c.Assert(err, Equals, ErrEntryNotFound) +} diff --git a/worktree_status.go b/worktree_status.go index 632f102..eb4a83a 100644 --- a/worktree_status.go +++ b/worktree_status.go @@ -2,7 +2,9 @@ package git import ( "bytes" + "errors" "io" + "os" "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/filemode" @@ -15,6 +17,10 @@ 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") + // Status returns the working tree status. func (w *Worktree) Status() (Status, error) { ref, err := w.r.Head() @@ -247,6 +253,7 @@ func (w *Worktree) addOrUpdateFileToIndex(filename string, h plumbing.Hash) erro return err } } + return w.r.Storer.SetIndex(idx) } @@ -266,6 +273,8 @@ func (w *Worktree) doUpdateFileToIndex(e *index.Entry, filename string, h plumbi e.Hash = h e.ModifiedAt = info.ModTime() e.Mode, err = filemode.NewFromOSFileMode(info.Mode()) + e.Size = uint32(info.Size()) + if err != nil { return err } @@ -273,3 +282,59 @@ func (w *Worktree) doUpdateFileToIndex(e *index.Entry, filename string, h plumbi fillSystemInfo(e, info.Sys()) return nil } + +// 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) + 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() + if err != nil { + return plumbing.ZeroHash, err + } + + e, err := idx.Remove(path) + if err != nil { + return plumbing.ZeroHash, err + } + + return e.Hash, w.r.Storer.SetIndex(idx) +} + +func (w *Worktree) deleteFromFilesystem(path string) error { + err := w.fs.Remove(path) + if os.IsNotExist(err) { + return nil + } + + return err +} + +// Move moves or rename a file in the worktree and the index, directories are +// not supported. +func (w *Worktree) Move(from, to string) (plumbing.Hash, error) { + if _, err := w.fs.Stat(from); err != nil { + return plumbing.ZeroHash, err + } + + if _, err := w.fs.Stat(to); err == nil { + return plumbing.ZeroHash, ErrDestinationExists + } + + hash, err := w.deleteFromIndex(from) + if err != nil { + return plumbing.ZeroHash, err + } + + if err := w.fs.Rename(from, to); err != nil { + return hash, err + } + + return hash, w.addOrUpdateFileToIndex(to, hash) +} diff --git a/worktree_test.go b/worktree_test.go index 68760f2..6ca2ed0 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -590,3 +590,113 @@ func (s *WorktreeSuite) TestAddUnmodified(c *C) { c.Assert(hash.String(), Equals, "c192bd6a24ea1ab01d78686e417c8bdc7c3d197f") c.Assert(err, IsNil) } + +func (s *WorktreeSuite) TestRemove(c *C) { + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + fs: fs, + } + + err := w.Checkout(&CheckoutOptions{Force: true}) + c.Assert(err, IsNil) + + hash, err := w.Remove("LICENSE") + c.Assert(hash.String(), Equals, "c192bd6a24ea1ab01d78686e417c8bdc7c3d197f") + c.Assert(err, IsNil) + + status, err := w.Status() + c.Assert(err, IsNil) + c.Assert(status, HasLen, 1) + c.Assert(status.File("LICENSE").Staging, Equals, Deleted) +} + +func (s *WorktreeSuite) TestRemoveNotExistentEntry(c *C) { + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + fs: fs, + } + + err := w.Checkout(&CheckoutOptions{Force: true}) + c.Assert(err, IsNil) + + hash, err := w.Remove("not-exists") + c.Assert(hash.IsZero(), Equals, true) + c.Assert(err, NotNil) +} + +func (s *WorktreeSuite) TestRemoveDeletedFromWorktree(c *C) { + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + fs: fs, + } + + err := w.Checkout(&CheckoutOptions{Force: true}) + c.Assert(err, IsNil) + + err = fs.Remove("LICENSE") + c.Assert(err, IsNil) + + hash, err := w.Remove("LICENSE") + c.Assert(hash.String(), Equals, "c192bd6a24ea1ab01d78686e417c8bdc7c3d197f") + c.Assert(err, IsNil) + + status, err := w.Status() + c.Assert(err, IsNil) + c.Assert(status, HasLen, 1) + c.Assert(status.File("LICENSE").Staging, Equals, Deleted) +} + +func (s *WorktreeSuite) TestMove(c *C) { + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + fs: fs, + } + + err := w.Checkout(&CheckoutOptions{Force: true}) + c.Assert(err, IsNil) + + hash, err := w.Move("LICENSE", "foo") + c.Check(hash.String(), Equals, "c192bd6a24ea1ab01d78686e417c8bdc7c3d197f") + c.Assert(err, IsNil) + + status, err := w.Status() + c.Assert(err, IsNil) + c.Assert(status, HasLen, 2) + c.Assert(status.File("LICENSE").Staging, Equals, Deleted) + c.Assert(status.File("foo").Staging, Equals, Added) + +} + +func (s *WorktreeSuite) TestMoveNotExistentEntry(c *C) { + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + fs: fs, + } + + err := w.Checkout(&CheckoutOptions{Force: true}) + c.Assert(err, IsNil) + + hash, err := w.Move("not-exists", "foo") + c.Assert(hash.IsZero(), Equals, true) + c.Assert(err, NotNil) +} + +func (s *WorktreeSuite) TestMoveToExistent(c *C) { + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + fs: fs, + } + + err := w.Checkout(&CheckoutOptions{Force: true}) + c.Assert(err, IsNil) + + hash, err := w.Move(".gitignore", "LICENSE") + c.Assert(hash.IsZero(), Equals, true) + c.Assert(err, Equals, ErrDestinationExists) +} |