diff options
author | Máximo Cuadros <mcuadros@gmail.com> | 2017-05-05 11:34:50 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-05-05 11:34:50 +0200 |
commit | ced875aec7bef9113e1c37b1b811a59e17dbd138 (patch) | |
tree | 3cf652b8cfa94e011d4d7b5addfd13945870cda8 | |
parent | e80cdbabb92a1ec35ffad536f52d3ff04b548fd1 (diff) | |
parent | 3713157d189a109bdccdb055200defb17297b6de (diff) | |
download | go-git-ced875aec7bef9113e1c37b1b811a59e17dbd138.tar.gz |
Merge pull request #375 from mcuadros/commit
worktree: Commit method implementation
-rw-r--r-- | options.go | 46 | ||||
-rw-r--r-- | options_test.go | 35 | ||||
-rw-r--r-- | plumbing/memory.go | 2 | ||||
-rw-r--r-- | plumbing/object/blob.go | 7 | ||||
-rw-r--r-- | plumbing/object/commit.go | 31 | ||||
-rw-r--r-- | plumbing/object/commit_test.go | 14 | ||||
-rw-r--r-- | plumbing/object/tag_test.go | 35 | ||||
-rw-r--r-- | plumbing/object/tree.go | 14 | ||||
-rw-r--r-- | status.go | 2 | ||||
-rw-r--r-- | worktree_commit.go | 243 | ||||
-rw-r--r-- | worktree_commit_test.go | 139 | ||||
-rw-r--r-- | worktree_status.go | 27 | ||||
-rw-r--r-- | worktree_test.go | 18 |
13 files changed, 559 insertions, 54 deletions
@@ -5,6 +5,7 @@ import ( "gopkg.in/src-d/go-git.v4/config" "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/object" "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/sideband" "gopkg.in/src-d/go-git.v4/plumbing/transport" ) @@ -72,7 +73,7 @@ func (o *CloneOptions) Validate() error { type PullOptions struct { // Name of the remote to be pulled. If empty, uses the default. RemoteName string - // Remote branch to clone. If empty, uses HEAD. + // Remote branch to clone. If empty, uses HEAD. ReferenceName plumbing.ReferenceName // Fetch only ReferenceName if true. SingleBranch bool @@ -251,3 +252,46 @@ type LogOptions struct { // the default From. From plumbing.Hash } + +var ( + ErrMissingAuthor = errors.New("author field is required") +) + +// CommitOptions describes how a commit operation should be performed. +type CommitOptions struct { + // All automatically stage files that have been modified and deleted, but + // new files you have not told Git about are not affected. + All bool + // Author is the author's signature of the commit. + Author *object.Signature + // Committer is the committer's signature of the commit. If Committer is + // nil the Author signature is used. + Committer *object.Signature + // Parents are the parents commits for the new commit, by default when + // len(Parents) is zero, the hash of HEAD reference is used. + Parents []plumbing.Hash +} + +// Validate validates the fields and sets the default values. +func (o *CommitOptions) Validate(r *Repository) error { + if o.Author == nil { + return ErrMissingAuthor + } + + if o.Committer == nil { + o.Committer = o.Author + } + + if len(o.Parents) == 0 { + head, err := r.Head() + if err != nil && err != plumbing.ErrReferenceNotFound { + return err + } + + if head != nil { + o.Parents = []plumbing.Hash{head.Hash()} + } + } + + return nil +} diff --git a/options_test.go b/options_test.go new file mode 100644 index 0000000..5274113 --- /dev/null +++ b/options_test.go @@ -0,0 +1,35 @@ +package git + +import ( + . "gopkg.in/check.v1" + "gopkg.in/src-d/go-git.v4/plumbing/object" +) + +type OptionsSuite struct { + BaseSuite +} + +var _ = Suite(&OptionsSuite{}) + +func (s *OptionsSuite) TestCommitOptionsParentsFromHEAD(c *C) { + o := CommitOptions{Author: &object.Signature{}} + err := o.Validate(s.Repository) + c.Assert(err, IsNil) + c.Assert(o.Parents, HasLen, 1) +} + +func (s *OptionsSuite) TestCommitOptionsMissingAuthor(c *C) { + o := CommitOptions{} + err := o.Validate(s.Repository) + c.Assert(err, Equals, ErrMissingAuthor) +} + +func (s *OptionsSuite) TestCommitOptionsCommitter(c *C) { + sig := &object.Signature{} + + o := CommitOptions{Author: sig} + err := o.Validate(s.Repository) + c.Assert(err, IsNil) + + c.Assert(o.Committer, Equals, o.Author) +} diff --git a/plumbing/memory.go b/plumbing/memory.go index c65ce1f..51cbb54 100644 --- a/plumbing/memory.go +++ b/plumbing/memory.go @@ -51,6 +51,8 @@ func (o *MemoryObject) Writer() (io.WriteCloser, error) { func (o *MemoryObject) Write(p []byte) (n int, err error) { o.cont = append(o.cont, p...) + o.sz = int64(len(o.cont)) + return len(p), nil } diff --git a/plumbing/object/blob.go b/plumbing/object/blob.go index 2771416..2608477 100644 --- a/plumbing/object/blob.go +++ b/plumbing/object/blob.go @@ -68,18 +68,23 @@ func (b *Blob) Decode(o plumbing.EncodedObject) error { // Encode transforms a Blob into a plumbing.EncodedObject. func (b *Blob) Encode(o plumbing.EncodedObject) error { + o.SetType(plumbing.BlobObject) + w, err := o.Writer() if err != nil { return err } + defer ioutil.CheckClose(w, &err) + r, err := b.Reader() if err != nil { return err } + defer ioutil.CheckClose(r, &err) + _, err = io.Copy(w, r) - o.SetType(plumbing.BlobObject) return err } diff --git a/plumbing/object/commit.go b/plumbing/object/commit.go index ffbb9f9..0a20ae6 100644 --- a/plumbing/object/commit.go +++ b/plumbing/object/commit.go @@ -30,10 +30,12 @@ type Commit struct { Committer Signature // Message is the commit message, contains arbitrary text. Message string + // TreeHash is the hash of the root tree of the commit. + TreeHash plumbing.Hash + // ParentHashes are the hashes of the parent commits of the commit. + ParentHashes []plumbing.Hash - tree plumbing.Hash - parents []plumbing.Hash - s storer.EncodedObjectStorer + s storer.EncodedObjectStorer } // GetCommit gets a commit from an object storer and decodes it. @@ -59,19 +61,19 @@ func DecodeCommit(s storer.EncodedObjectStorer, o plumbing.EncodedObject) (*Comm // Tree returns the Tree from the commit. func (c *Commit) Tree() (*Tree, error) { - return GetTree(c.s, c.tree) + return GetTree(c.s, c.TreeHash) } // Parents return a CommitIter to the parent Commits. func (c *Commit) Parents() CommitIter { return NewCommitIter(c.s, - storer.NewEncodedObjectLookupIter(c.s, plumbing.CommitObject, c.parents), + storer.NewEncodedObjectLookupIter(c.s, plumbing.CommitObject, c.ParentHashes), ) } // NumParents returns the number of parents in a commit. func (c *Commit) NumParents() int { - return len(c.parents) + return len(c.ParentHashes) } // File returns the file with the specified "path" in the commit and a @@ -144,9 +146,9 @@ func (c *Commit) Decode(o plumbing.EncodedObject) (err error) { split := bytes.SplitN(line, []byte{' '}, 2) switch string(split[0]) { case "tree": - c.tree = plumbing.NewHash(string(split[1])) + c.TreeHash = plumbing.NewHash(string(split[1])) case "parent": - c.parents = append(c.parents, plumbing.NewHash(string(split[1]))) + c.ParentHashes = append(c.ParentHashes, plumbing.NewHash(string(split[1]))) case "author": c.Author.Decode(split[1]) case "committer": @@ -169,30 +171,39 @@ func (b *Commit) Encode(o plumbing.EncodedObject) error { if err != nil { return err } + defer ioutil.CheckClose(w, &err) - if _, err = fmt.Fprintf(w, "tree %s\n", b.tree.String()); err != nil { + + if _, err = fmt.Fprintf(w, "tree %s\n", b.TreeHash.String()); err != nil { return err } - for _, parent := range b.parents { + + for _, parent := range b.ParentHashes { if _, err = fmt.Fprintf(w, "parent %s\n", parent.String()); err != nil { return err } } + if _, err = fmt.Fprint(w, "author "); err != nil { return err } + if err = b.Author.Encode(w); err != nil { return err } + if _, err = fmt.Fprint(w, "\ncommitter "); err != nil { return err } + if err = b.Committer.Encode(w); err != nil { return err } + if _, err = fmt.Fprintf(w, "\n\n%s", b.Message); err != nil { return err } + return err } diff --git a/plumbing/object/commit_test.go b/plumbing/object/commit_test.go index c1f49db..87e80a5 100644 --- a/plumbing/object/commit_test.go +++ b/plumbing/object/commit_test.go @@ -71,18 +71,18 @@ func (s *SuiteCommit) TestCommitEncodeDecodeIdempotent(c *C) { c.Assert(err, IsNil) commits := []*Commit{ { - Author: Signature{Name: "Foo", Email: "foo@example.local", When: ts}, - Committer: Signature{Name: "Bar", Email: "bar@example.local", When: ts}, - Message: "Message\n\nFoo\nBar\nWith trailing blank lines\n\n", - tree: plumbing.NewHash("f000000000000000000000000000000000000001"), - parents: []plumbing.Hash{plumbing.NewHash("f000000000000000000000000000000000000002")}, + Author: Signature{Name: "Foo", Email: "foo@example.local", When: ts}, + Committer: Signature{Name: "Bar", Email: "bar@example.local", When: ts}, + Message: "Message\n\nFoo\nBar\nWith trailing blank lines\n\n", + TreeHash: plumbing.NewHash("f000000000000000000000000000000000000001"), + ParentHashes: []plumbing.Hash{plumbing.NewHash("f000000000000000000000000000000000000002")}, }, { Author: Signature{Name: "Foo", Email: "foo@example.local", When: ts}, Committer: Signature{Name: "Bar", Email: "bar@example.local", When: ts}, Message: "Message\n\nFoo\nBar\nWith no trailing blank lines", - tree: plumbing.NewHash("0000000000000000000000000000000000000003"), - parents: []plumbing.Hash{ + TreeHash: plumbing.NewHash("0000000000000000000000000000000000000003"), + ParentHashes: []plumbing.Hash{ plumbing.NewHash("f000000000000000000000000000000000000004"), plumbing.NewHash("f000000000000000000000000000000000000005"), plumbing.NewHash("f000000000000000000000000000000000000006"), diff --git a/plumbing/object/tag_test.go b/plumbing/object/tag_test.go index f121ce9..9f2d28c 100644 --- a/plumbing/object/tag_test.go +++ b/plumbing/object/tag_test.go @@ -234,33 +234,32 @@ func (s *TagSuite) TestString(c *C) { ) } -func (s *TagSuite) TestTagToTagString(c *C) { +func (s *TagSuite) TestStringNonCommit(c *C) { store := memory.NewStorage() - tagOneHash := plumbing.NewHash("TAGONE") - tagTwoHash := plumbing.NewHash("TAGTWO") - - tagOne := &Tag{ - Target: tagTwoHash, - Hash: tagOneHash, + target := &Tag{ + Target: plumbing.NewHash("TAGONE"), Name: "TAG ONE", + Message: "tag one", TargetType: plumbing.TagObject, } - tagTwo := &Tag{ - Target: tagOneHash, - Hash: tagTwoHash, + + targetObj := &plumbing.MemoryObject{} + target.Encode(targetObj) + store.SetEncodedObject(targetObj) + + tag := &Tag{ + Target: targetObj.Hash(), Name: "TAG TWO", + Message: "tag two", TargetType: plumbing.TagObject, } - oOne := &plumbing.MemoryObject{} - tagOne.Encode(oOne) - oTwo := &plumbing.MemoryObject{} - tagTwo.Encode(oTwo) - store.SetEncodedObject(oOne) - store.SetEncodedObject(oTwo) + tagObj := &plumbing.MemoryObject{} + tag.Encode(tagObj) + store.SetEncodedObject(tagObj) - tag, err := GetTag(store, tagOneHash) + tag, err := GetTag(store, tagObj.Hash()) c.Assert(err, IsNil) c.Assert(tag.String(), Equals, @@ -268,7 +267,7 @@ func (s *TagSuite) TestTagToTagString(c *C) { "Tagger: <>\n"+ "Date: Mon Jan 01 00:00:00 0001 +0000\n"+ "\n"+ - "\n") + "tag two\n") } func (s *TagSuite) TestLongTagNameSerialization(c *C) { diff --git a/plumbing/object/tree.go b/plumbing/object/tree.go index d2265a8..25687b0 100644 --- a/plumbing/object/tree.go +++ b/plumbing/object/tree.go @@ -240,29 +240,21 @@ func (t *Tree) Encode(o plumbing.EncodedObject) error { return err } - var size int defer ioutil.CheckClose(w, &err) for _, entry := range t.Entries { - n, err := fmt.Fprintf(w, "%o %s", entry.Mode, entry.Name) - if err != nil { + if _, err := fmt.Fprintf(w, "%o %s", entry.Mode, entry.Name); err != nil { return err } - size += n - n, err = w.Write([]byte{0x00}) - if err != nil { + if _, err = w.Write([]byte{0x00}); err != nil { return err } - size += n - n, err = w.Write([]byte(entry.Hash[:])) - if err != nil { + if _, err = w.Write([]byte(entry.Hash[:])); err != nil { return err } - size += n } - o.SetSize(int64(size)) return err } @@ -11,7 +11,7 @@ type Status map[string]*FileStatus // exists a new FileStatus is added to the map using the path as key. func (s Status) File(path string) *FileStatus { if _, ok := (s)[path]; !ok { - s[path] = &FileStatus{Worktree: Unmodified, Staging: Unmodified} + s[path] = &FileStatus{Worktree: Untracked, Staging: Untracked} } return s[path] diff --git a/worktree_commit.go b/worktree_commit.go new file mode 100644 index 0000000..bec75b2 --- /dev/null +++ b/worktree_commit.go @@ -0,0 +1,243 @@ +package git + +import ( + "io" + "path/filepath" + "strings" + + "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/index" + "gopkg.in/src-d/go-git.v4/plumbing/object" + "gopkg.in/src-d/go-git.v4/storage" + "gopkg.in/src-d/go-git.v4/utils/ioutil" + + "gopkg.in/src-d/go-billy.v2" +) + +// Commit stores the current contents of the index in a new commit along with +// a log message from the user describing the changes. +func (w *Worktree) Commit(msg string, opts *CommitOptions) (plumbing.Hash, error) { + if err := opts.Validate(w.r); err != nil { + return plumbing.ZeroHash, err + } + + if opts.All { + if err := w.autoAddModifiedAndDeleted(); err != nil { + return plumbing.ZeroHash, err + } + } + + idx, err := w.r.Storer.Index() + if err != nil { + return plumbing.ZeroHash, err + } + + h := &commitIndexHelper{ + fs: w.fs, + s: w.r.Storer, + } + + tree, err := h.buildTreeAndBlobObjects(idx) + if err != nil { + return plumbing.ZeroHash, err + } + + commit, err := w.buildCommitObject(msg, opts, tree) + if err != nil { + return plumbing.ZeroHash, err + } + + return commit, w.updateHEAD(commit) +} + +func (w *Worktree) autoAddModifiedAndDeleted() error { + s, err := w.Status() + if err != nil { + return err + } + + for path, fs := range s { + if fs.Worktree != Modified && fs.Worktree != Deleted { + continue + } + + if _, err := w.Add(path); err != nil { + return err + } + + } + + return nil +} + +func (w *Worktree) updateHEAD(commit plumbing.Hash) error { + head, err := w.r.Storer.Reference(plumbing.HEAD) + if err != nil { + return err + } + + name := plumbing.HEAD + if head.Type() != plumbing.HashReference { + name = head.Target() + } + + ref := plumbing.NewHashReference(name, commit) + return w.r.Storer.SetReference(ref) +} + +func (w *Worktree) buildCommitObject(msg string, opts *CommitOptions, tree plumbing.Hash) (plumbing.Hash, error) { + commit := &object.Commit{ + Author: *opts.Author, + Committer: *opts.Committer, + Message: msg, + TreeHash: tree, + ParentHashes: opts.Parents, + } + + obj := w.r.Storer.NewEncodedObject() + if err := commit.Encode(obj); err != nil { + return plumbing.ZeroHash, err + } + return w.r.Storer.SetEncodedObject(obj) +} + +// commitIndexHelper converts a given index.Index file into multiple git objects +// reading the blobs from the given filesystem and creating the trees from the +// index structure. The created objects are pushed to a given Storer. +type commitIndexHelper struct { + fs billy.Filesystem + s storage.Storer + + trees map[string]*object.Tree + entries map[string]*object.TreeEntry +} + +// buildTreesAndBlobs builds the objects and push its to the storer, the hash +// of the root tree is returned. +func (h *commitIndexHelper) buildTreeAndBlobObjects(idx *index.Index) (plumbing.Hash, error) { + const rootNode = "" + h.trees = map[string]*object.Tree{rootNode: {}} + h.entries = map[string]*object.TreeEntry{} + + for _, e := range idx.Entries { + if err := h.commitIndexEntry(e); err != nil { + return plumbing.ZeroHash, err + } + } + + return h.copyTreeToStorageRecursive(rootNode, h.trees[rootNode]) +} + +func (h *commitIndexHelper) commitIndexEntry(e *index.Entry) error { + parts := strings.Split(e.Name, string(filepath.Separator)) + + var path string + for _, part := range parts { + parent := path + path = filepath.Join(path, part) + + if !h.buildTree(e, parent, path) { + continue + } + + if err := h.copyIndexEntryToStorage(e); err != nil { + return err + } + } + + return nil +} + +func (h *commitIndexHelper) buildTree(e *index.Entry, parent, path string) bool { + if _, ok := h.trees[path]; ok { + return false + } + + if _, ok := h.entries[path]; ok { + return false + } + + te := object.TreeEntry{Name: filepath.Base(path)} + + if path == e.Name { + te.Mode = e.Mode + te.Hash = e.Hash + } else { + te.Mode = filemode.Dir + h.trees[path] = &object.Tree{} + } + + h.trees[parent].Entries = append(h.trees[parent].Entries, te) + return true +} + +func (h *commitIndexHelper) copyIndexEntryToStorage(e *index.Entry) error { + _, err := h.s.EncodedObject(plumbing.BlobObject, e.Hash) + if err == nil { + return nil + } + + if err != plumbing.ErrObjectNotFound { + return err + } + + return h.doCopyIndexEntryToStorage(e) +} + +func (h *commitIndexHelper) doCopyIndexEntryToStorage(e *index.Entry) (err error) { + fi, err := h.fs.Stat(e.Name) + if err != nil { + return err + } + + obj := h.s.NewEncodedObject() + obj.SetType(plumbing.BlobObject) + obj.SetSize(fi.Size()) + + reader, err := h.fs.Open(e.Name) + if err != nil { + return err + } + + defer ioutil.CheckClose(reader, &err) + + writer, err := obj.Writer() + if err != nil { + return err + } + + defer ioutil.CheckClose(writer, &err) + + if _, err := io.Copy(writer, reader); err != nil { + return err + } + + _, err = h.s.SetEncodedObject(obj) + return err +} + +func (h *commitIndexHelper) copyTreeToStorageRecursive(parent string, t *object.Tree) (plumbing.Hash, error) { + for i, e := range t.Entries { + if e.Mode != filemode.Dir && !e.Hash.IsZero() { + continue + } + + path := filepath.Join(parent, e.Name) + + var err error + e.Hash, err = h.copyTreeToStorageRecursive(path, h.trees[path]) + if err != nil { + return plumbing.ZeroHash, err + } + + t.Entries[i] = e + } + + o := h.s.NewEncodedObject() + if err := t.Encode(o); err != nil { + return plumbing.ZeroHash, err + } + + return h.s.SetEncodedObject(o) +} diff --git a/worktree_commit_test.go b/worktree_commit_test.go new file mode 100644 index 0000000..7e1b93c --- /dev/null +++ b/worktree_commit_test.go @@ -0,0 +1,139 @@ +package git + +import ( + "time" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/object" + "gopkg.in/src-d/go-git.v4/plumbing/storer" + "gopkg.in/src-d/go-git.v4/storage/memory" + + . "gopkg.in/check.v1" + "gopkg.in/src-d/go-billy.v2" + "gopkg.in/src-d/go-billy.v2/memfs" +) + +func (s *WorktreeSuite) TestCommitInvalidOptions(c *C) { + r, err := Init(memory.NewStorage(), memfs.New()) + c.Assert(err, IsNil) + + w, err := r.Worktree() + c.Assert(err, IsNil) + + hash, err := w.Commit("", &CommitOptions{}) + c.Assert(err, Equals, ErrMissingAuthor) + c.Assert(hash.IsZero(), Equals, true) +} + +func (s *WorktreeSuite) TestCommitInitial(c *C) { + expected := plumbing.NewHash("98c4ac7c29c913f7461eae06e024dc18e80d23a4") + + fs := memfs.New() + storage := memory.NewStorage() + + r, err := Init(storage, fs) + c.Assert(err, IsNil) + + w, err := r.Worktree() + c.Assert(err, IsNil) + + billy.WriteFile(fs, "foo", []byte("foo"), 0644) + + _, err = w.Add("foo") + c.Assert(err, IsNil) + + hash, err := w.Commit("foo\n", &CommitOptions{Author: defaultSignature()}) + c.Assert(hash, Equals, expected) + c.Assert(err, IsNil) + + assertStorageStatus(c, r, 1, 1, 1, expected) +} + +func (s *WorktreeSuite) TestCommitParent(c *C) { + expected := plumbing.NewHash("ef3ca05477530b37f48564be33ddd48063fc7a22") + + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + fs: fs, + } + + err := w.Checkout(&CheckoutOptions{}) + c.Assert(err, IsNil) + + billy.WriteFile(fs, "foo", []byte("foo"), 0644) + + _, err = w.Add("foo") + c.Assert(err, IsNil) + + hash, err := w.Commit("foo\n", &CommitOptions{Author: defaultSignature()}) + c.Assert(hash, Equals, expected) + c.Assert(err, IsNil) + + assertStorageStatus(c, s.Repository, 13, 11, 10, expected) +} + +func (s *WorktreeSuite) TestCommitAll(c *C) { + expected := plumbing.NewHash("aede6f8c9c1c7ec9ca8d287c64b8ed151276fa28") + + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + fs: fs, + } + + err := w.Checkout(&CheckoutOptions{}) + c.Assert(err, IsNil) + + billy.WriteFile(fs, "LICENSE", []byte("foo"), 0644) + billy.WriteFile(fs, "foo", []byte("foo"), 0644) + + hash, err := w.Commit("foo\n", &CommitOptions{ + All: true, + Author: defaultSignature(), + }) + + c.Assert(hash, Equals, expected) + c.Assert(err, IsNil) + + assertStorageStatus(c, s.Repository, 13, 11, 10, expected) +} + +func assertStorageStatus( + c *C, r *Repository, + treesCount, blobCount, commitCount int, head plumbing.Hash, +) { + trees, err := r.Storer.IterEncodedObjects(plumbing.TreeObject) + c.Assert(err, IsNil) + blobs, err := r.Storer.IterEncodedObjects(plumbing.BlobObject) + c.Assert(err, IsNil) + commits, err := r.Storer.IterEncodedObjects(plumbing.CommitObject) + c.Assert(err, IsNil) + + c.Assert(lenIterEncodedObjects(trees), Equals, treesCount) + c.Assert(lenIterEncodedObjects(blobs), Equals, blobCount) + c.Assert(lenIterEncodedObjects(commits), Equals, commitCount) + + ref, err := r.Head() + c.Assert(err, IsNil) + c.Assert(ref.Hash(), Equals, head) +} + +func lenIterEncodedObjects(iter storer.EncodedObjectIter) int { + count := 0 + iter.ForEach(func(plumbing.EncodedObject) error { + count++ + return nil + }) + + return count +} + +func defaultSignature() *object.Signature { + when, _ := time.Parse(object.DateFormat, "Thu May 04 00:03:43 2017 +0200") + return &object.Signature{ + Name: "foo", + Email: "foo@foo.foo", + When: when, + } +} diff --git a/worktree_status.go b/worktree_status.go index e6d1745..632f102 100644 --- a/worktree_status.go +++ b/worktree_status.go @@ -19,7 +19,7 @@ import ( func (w *Worktree) Status() (Status, error) { ref, err := w.r.Head() if err == plumbing.ErrReferenceNotFound { - return nil, nil + return make(Status, 0), nil } if err != nil { @@ -43,6 +43,9 @@ func (w *Worktree) status(commit plumbing.Hash) (Status, error) { return nil, err } + fs := s.File(nameFromAction(&ch)) + fs.Worktree = Unmodified + switch a { case merkletrie.Delete: s.File(ch.From.String()).Staging = Deleted @@ -64,20 +67,34 @@ func (w *Worktree) status(commit plumbing.Hash) (Status, error) { return nil, err } + fs := s.File(nameFromAction(&ch)) + if fs.Staging == Untracked { + fs.Staging = Unmodified + } + switch a { case merkletrie.Delete: - s.File(ch.From.String()).Worktree = Deleted + fs.Worktree = Deleted case merkletrie.Insert: - s.File(ch.To.String()).Worktree = Untracked - s.File(ch.To.String()).Staging = Untracked + fs.Worktree = Untracked + fs.Staging = Untracked case merkletrie.Modify: - s.File(ch.To.String()).Worktree = Modified + fs.Worktree = Modified } } return s, nil } +func nameFromAction(ch *merkletrie.Change) string { + name := ch.To.String() + if name == "" { + return ch.From.String() + } + + return name +} + func (w *Worktree) diffStagingWithWorktree() (merkletrie.Changes, error) { idx, err := w.r.Storer.Index() if err != nil { diff --git a/worktree_test.go b/worktree_test.go index f06a1f9..68760f2 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -9,6 +9,7 @@ import ( "gopkg.in/src-d/go-git.v4/plumbing/filemode" "gopkg.in/src-d/go-git.v4/plumbing/format/index" "gopkg.in/src-d/go-git.v4/plumbing/object" + "gopkg.in/src-d/go-git.v4/storage/memory" "github.com/src-d/go-git-fixtures" . "gopkg.in/check.v1" @@ -286,10 +287,27 @@ func (s *WorktreeSuite) TestStatus(c *C) { status, err := w.Status() c.Assert(err, IsNil) + c.Assert(status.IsClean(), Equals, false) c.Assert(status, HasLen, 9) } +func (s *WorktreeSuite) TestStatusEmpty(c *C) { + fs := memfs.New() + storage := memory.NewStorage() + + r, err := Init(storage, fs) + c.Assert(err, IsNil) + + w, err := r.Worktree() + c.Assert(err, IsNil) + + status, err := w.Status() + c.Assert(err, IsNil) + c.Assert(status.IsClean(), Equals, true) + c.Assert(status, NotNil) +} + func (s *WorktreeSuite) TestReset(c *C) { fs := memfs.New() w := &Worktree{ |