aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaulo Gomes <pjbgf@linux.com>2022-12-03 16:34:35 +0000
committerPaulo Gomes <pjbgf@linux.com>2022-12-03 16:34:35 +0000
commita513415283c4628259c016587858fe56d7b0fa13 (patch)
treec39b970b1403d10693795ad6d61dd1ea59db4a2d
parent3e07c5030b4e3b2fcbcb461f9f6b23212f978335 (diff)
downloadgo-git-a513415283c4628259c016587858fe56d7b0fa13.tar.gz
Return error instead of creating empty commits
BuildTree now returns an ErrEmptyCommit error, when there are no changes to be committed. This can be opted-out via CommitOptions.AllowEmptyCommits. This is a breaking change which enables applications to detect when empty commits are to be created. Some instances in which this can occur is when the fs (e.g. `billy/osfs`) make changes to the underlying files, causing a conflict between what the previous Git worktree state was, and the current state. Changes to the fs implementations are orthogonal to this, and will be dealt with separately. The new behaviour aligns with the Git CLI, in which empty commits returns the error message: 'nothing to commit, working tree clean'. Signed-off-by: Paulo Gomes <pjbgf@linux.com>
-rw-r--r--options.go4
-rw-r--r--worktree_commit.go15
-rw-r--r--worktree_commit_test.go26
-rw-r--r--worktree_test.go36
4 files changed, 75 insertions, 6 deletions
diff --git a/options.go b/options.go
index 7e5c1b4..747d512 100644
--- a/options.go
+++ b/options.go
@@ -458,6 +458,10 @@ 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
+ // AllowEmptyCommits enable empty commits to be created. An empty commit
+ // is when no changes to the tree were made, but a new commit message is
+ // provided. The default behavior is false, which results in ErrEmptyCommit.
+ AllowEmptyCommits bool
// Author is the author's signature of the commit. If Author is empty the
// Name and Email is read from the config, and time.Now it's used as When.
Author *object.Signature
diff --git a/worktree_commit.go b/worktree_commit.go
index dc79569..e927721 100644
--- a/worktree_commit.go
+++ b/worktree_commit.go
@@ -2,6 +2,7 @@ package git
import (
"bytes"
+ "errors"
"path"
"sort"
"strings"
@@ -16,6 +17,12 @@ import (
"github.com/go-git/go-billy/v5"
)
+var (
+ // ErrEmptyCommit occurs when a commit is attempted using a clean
+ // working tree, with no changes to be committed.
+ ErrEmptyCommit = errors.New("cannot create empty commit: clean working tree")
+)
+
// 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) {
@@ -39,7 +46,7 @@ func (w *Worktree) Commit(msg string, opts *CommitOptions) (plumbing.Hash, error
s: w.r.Storer,
}
- tree, err := h.BuildTree(idx)
+ tree, err := h.BuildTree(idx, opts)
if err != nil {
return plumbing.ZeroHash, err
}
@@ -145,7 +152,11 @@ type buildTreeHelper struct {
// BuildTree builds the tree objects and push its to the storer, the hash
// of the root tree is returned.
-func (h *buildTreeHelper) BuildTree(idx *index.Index) (plumbing.Hash, error) {
+func (h *buildTreeHelper) BuildTree(idx *index.Index, opts *CommitOptions) (plumbing.Hash, error) {
+ if len(idx.Entries) == 0 && (opts == nil || !opts.AllowEmptyCommits) {
+ return plumbing.ZeroHash, ErrEmptyCommit
+ }
+
const rootNode = ""
h.trees = map[string]*object.Tree{rootNode: {}}
h.entries = map[string]*object.TreeEntry{}
diff --git a/worktree_commit_test.go b/worktree_commit_test.go
index 097c6e5..bfeb81d 100644
--- a/worktree_commit_test.go
+++ b/worktree_commit_test.go
@@ -26,12 +26,18 @@ import (
)
func (s *WorktreeSuite) TestCommitEmptyOptions(c *C) {
- r, err := Init(memory.NewStorage(), memfs.New())
+ fs := memfs.New()
+ r, err := Init(memory.NewStorage(), fs)
c.Assert(err, IsNil)
w, err := r.Worktree()
c.Assert(err, IsNil)
+ util.WriteFile(fs, "foo", []byte("foo"), 0644)
+
+ _, err = w.Add("foo")
+ c.Assert(err, IsNil)
+
hash, err := w.Commit("foo", &CommitOptions{})
c.Assert(err, IsNil)
c.Assert(hash.IsZero(), Equals, false)
@@ -65,6 +71,24 @@ func (s *WorktreeSuite) TestCommitInitial(c *C) {
assertStorageStatus(c, r, 1, 1, 1, expected)
}
+func (s *WorktreeSuite) TestNothingToCommit(c *C) {
+ expected := plumbing.NewHash("838ea833ce893e8555907e5ef224aa076f5e274a")
+
+ r, err := Init(memory.NewStorage(), memfs.New())
+ c.Assert(err, IsNil)
+
+ w, err := r.Worktree()
+ c.Assert(err, IsNil)
+
+ hash, err := w.Commit("failed empty commit\n", &CommitOptions{Author: defaultSignature()})
+ c.Assert(hash, Equals, plumbing.ZeroHash)
+ c.Assert(err, Equals, ErrEmptyCommit)
+
+ hash, err = w.Commit("enable empty commits\n", &CommitOptions{Author: defaultSignature(), AllowEmptyCommits: true})
+ c.Assert(hash, Equals, expected)
+ c.Assert(err, IsNil)
+}
+
func (s *WorktreeSuite) TestCommitParent(c *C) {
expected := plumbing.NewHash("ef3ca05477530b37f48564be33ddd48063fc7a22")
diff --git a/worktree_test.go b/worktree_test.go
index 4a14126..4c06333 100644
--- a/worktree_test.go
+++ b/worktree_test.go
@@ -3,7 +3,6 @@ package git
import (
"bytes"
"context"
- "errors"
"io"
"io/ioutil"
"os"
@@ -2167,6 +2166,8 @@ func (s *WorktreeSuite) TestGrep(c *C) {
}
func (s *WorktreeSuite) TestAddAndCommit(c *C) {
+ expectedFiles := 2
+
dir, clean := s.TemporalDir()
defer clean()
@@ -2176,17 +2177,23 @@ func (s *WorktreeSuite) TestAddAndCommit(c *C) {
w, err := repo.Worktree()
c.Assert(err, IsNil)
+ os.WriteFile(filepath.Join(dir, "foo"), []byte("bar"), 0o644)
+ os.WriteFile(filepath.Join(dir, "bar"), []byte("foo"), 0o644)
+
_, err = w.Add(".")
c.Assert(err, IsNil)
- w.Commit("Test Add And Commit", &CommitOptions{Author: &object.Signature{
+ _, err = w.Commit("Test Add And Commit", &CommitOptions{Author: &object.Signature{
Name: "foo",
Email: "foo@foo.foo",
When: time.Now(),
}})
+ c.Assert(err, IsNil)
iter, err := w.r.Log(&LogOptions{})
c.Assert(err, IsNil)
+
+ filesFound := 0
err = iter.ForEach(func(c *object.Commit) error {
files, err := c.Files()
if err != nil {
@@ -2194,11 +2201,34 @@ func (s *WorktreeSuite) TestAddAndCommit(c *C) {
}
err = files.ForEach(func(f *object.File) error {
- return errors.New("Expected no files, got at least 1")
+ filesFound++
+ return nil
})
return err
})
c.Assert(err, IsNil)
+ c.Assert(filesFound, Equals, expectedFiles)
+}
+
+func (s *WorktreeSuite) TestAddAndCommitEmpty(c *C) {
+ dir, clean := s.TemporalDir()
+ defer clean()
+
+ repo, err := PlainInit(dir, false)
+ c.Assert(err, IsNil)
+
+ w, err := repo.Worktree()
+ c.Assert(err, IsNil)
+
+ _, err = w.Add(".")
+ c.Assert(err, IsNil)
+
+ _, err = w.Commit("Test Add And Commit", &CommitOptions{Author: &object.Signature{
+ Name: "foo",
+ Email: "foo@foo.foo",
+ When: time.Now(),
+ }})
+ c.Assert(err, Equals, ErrEmptyCommit)
}
func (s *WorktreeSuite) TestLinkedWorktree(c *C) {