Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CommitOptions: AllowEmptyCommits, return error instead of creating empty commits #623

Merged
merged 1 commit into from Dec 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions options.go
Expand Up @@ -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
Expand Down
15 changes: 13 additions & 2 deletions worktree_commit.go
Expand Up @@ -2,6 +2,7 @@ package git

import (
"bytes"
"errors"
"path"
"sort"
"strings"
Expand All @@ -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) {
Expand All @@ -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
}
Expand Down Expand Up @@ -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{}
Expand Down
26 changes: 25 additions & 1 deletion worktree_commit_test.go
Expand Up @@ -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)
Expand Down Expand Up @@ -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")

Expand Down
36 changes: 33 additions & 3 deletions worktree_test.go
Expand Up @@ -3,7 +3,6 @@ package git
import (
"bytes"
"context"
"errors"
"io"
"io/ioutil"
"os"
Expand Down Expand Up @@ -2167,6 +2166,8 @@ func (s *WorktreeSuite) TestGrep(c *C) {
}

func (s *WorktreeSuite) TestAddAndCommit(c *C) {
expectedFiles := 2

dir, clean := s.TemporalDir()
defer clean()

Expand All @@ -2176,29 +2177,58 @@ 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 {
return err
}

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) {
Expand Down