aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMáximo Cuadros <mcuadros@gmail.com>2017-05-05 11:34:50 +0200
committerGitHub <noreply@github.com>2017-05-05 11:34:50 +0200
commitced875aec7bef9113e1c37b1b811a59e17dbd138 (patch)
tree3cf652b8cfa94e011d4d7b5addfd13945870cda8
parente80cdbabb92a1ec35ffad536f52d3ff04b548fd1 (diff)
parent3713157d189a109bdccdb055200defb17297b6de (diff)
downloadgo-git-ced875aec7bef9113e1c37b1b811a59e17dbd138.tar.gz
Merge pull request #375 from mcuadros/commit
worktree: Commit method implementation
-rw-r--r--options.go46
-rw-r--r--options_test.go35
-rw-r--r--plumbing/memory.go2
-rw-r--r--plumbing/object/blob.go7
-rw-r--r--plumbing/object/commit.go31
-rw-r--r--plumbing/object/commit_test.go14
-rw-r--r--plumbing/object/tag_test.go35
-rw-r--r--plumbing/object/tree.go14
-rw-r--r--status.go2
-rw-r--r--worktree_commit.go243
-rw-r--r--worktree_commit_test.go139
-rw-r--r--worktree_status.go27
-rw-r--r--worktree_test.go18
13 files changed, 559 insertions, 54 deletions
diff --git a/options.go b/options.go
index 50fba10..977e462 100644
--- a/options.go
+++ b/options.go
@@ -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
}
diff --git a/status.go b/status.go
index 2517e50..ef8a500 100644
--- a/status.go
+++ b/status.go
@@ -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{