diff options
-rw-r--r-- | config/config_test.go | 37 | ||||
-rw-r--r-- | options.go | 42 | ||||
-rw-r--r-- | options_test.go | 84 | ||||
-rw-r--r-- | repository_test.go | 10 | ||||
-rw-r--r-- | worktree_status.go | 101 | ||||
-rw-r--r-- | worktree_test.go | 46 |
6 files changed, 272 insertions, 48 deletions
diff --git a/config/config_test.go b/config/config_test.go index e68626b..5a88c19 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1,6 +1,10 @@ package config import ( + "io/ioutil" + "os" + "path/filepath" + "github.com/go-git/go-git/v5/plumbing" . "gopkg.in/check.v1" ) @@ -172,8 +176,39 @@ func (s *ConfigSuite) TestUnmarshalMarshal(c *C) { func (s *ConfigSuite) TestLoadConfig(c *C) { cfg, err := LoadConfig(GlobalScope) - c.Assert(err, IsNil) c.Assert(cfg.User.Email, Not(Equals), "") + c.Assert(err, IsNil) + +} + +func (s *ConfigSuite) TestLoadConfigXDG(c *C) { + cfg := NewConfig() + cfg.User.Name = "foo" + cfg.User.Email = "foo@foo.com" + + tmp, err := ioutil.TempDir("", "test-commit-options") + c.Assert(err, IsNil) + defer os.RemoveAll(tmp) + + err = os.Mkdir(filepath.Join(tmp, "git"), 0777) + c.Assert(err, IsNil) + + os.Setenv("XDG_CONFIG_HOME", tmp) + defer func() { + os.Setenv("XDG_CONFIG_HOME", "") + }() + + content, err := cfg.Marshal() + c.Assert(err, IsNil) + + cfgFile := filepath.Join(tmp, "git/config") + err = ioutil.WriteFile(cfgFile, content, 0777) + c.Assert(err, IsNil) + + cfg, err = LoadConfig(GlobalScope) + c.Assert(err, IsNil) + + c.Assert(cfg.User.Email, Equals, "foo@foo.com") } func (s *ConfigSuite) TestValidateConfig(c *C) { @@ -373,6 +373,12 @@ var ( ErrMissingAuthor = errors.New("author field is required") ) +// AddOptions describes how a add operation should be performed +type AddOptions struct { + All bool + Path string +} + // CommitOptions describes how a commit operation should be performed. type CommitOptions struct { // All automatically stage files that have been modified and deleted, but @@ -464,7 +470,8 @@ var ( // CreateTagOptions describes how a tag object should be created. type CreateTagOptions struct { - // Tagger defines the signature of the tag creator. + // Tagger defines the signature of the tag creator. If Tagger is empty the + // Name and Email is read from the config, and time.Now it's used as When. Tagger *object.Signature // Message defines the annotation of the tag. It is canonicalized during // validation into the format expected by git - no leading whitespace and @@ -478,7 +485,9 @@ type CreateTagOptions struct { // Validate validates the fields and sets the default values. func (o *CreateTagOptions) Validate(r *Repository, hash plumbing.Hash) error { if o.Tagger == nil { - return ErrMissingTagger + if err := o.loadConfigTagger(r); err != nil { + return err + } } if o.Message == "" { @@ -491,6 +500,35 @@ func (o *CreateTagOptions) Validate(r *Repository, hash plumbing.Hash) error { return nil } +func (o *CreateTagOptions) loadConfigTagger(r *Repository) error { + cfg, err := r.ConfigScoped(config.SystemScope) + if err != nil { + return err + } + + if o.Tagger == nil && cfg.Author.Email != "" && cfg.Author.Name != "" { + o.Tagger = &object.Signature{ + Name: cfg.Author.Name, + Email: cfg.Author.Email, + When: time.Now(), + } + } + + if o.Tagger == nil && cfg.User.Email != "" && cfg.User.Name != "" { + o.Tagger = &object.Signature{ + Name: cfg.User.Name, + Email: cfg.User.Email, + When: time.Now(), + } + } + + if o.Tagger == nil { + return ErrMissingTagger + } + + return nil +} + // ListOptions describes how a remote list should be performed. type ListOptions struct { // Auth credentials, if required, to use with the remote repository. diff --git a/options_test.go b/options_test.go index aa36dab..86d725a 100644 --- a/options_test.go +++ b/options_test.go @@ -1,6 +1,12 @@ package git import ( + "io/ioutil" + "os" + "path/filepath" + + "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" . "gopkg.in/check.v1" ) @@ -27,3 +33,81 @@ func (s *OptionsSuite) TestCommitOptionsCommitter(c *C) { c.Assert(o.Committer, Equals, o.Author) } + +func (s *OptionsSuite) TestCommitOptionsLoadGlobalConfigUser(c *C) { + cfg := config.NewConfig() + cfg.User.Name = "foo" + cfg.User.Email = "foo@foo.com" + + s.writeGlobalConfig(c, cfg) + defer s.clearGlobalConfig(c) + + o := CommitOptions{} + err := o.Validate(s.Repository) + c.Assert(err, IsNil) + + c.Assert(o.Author.Name, Equals, "foo") + c.Assert(o.Author.Email, Equals, "foo@foo.com") + c.Assert(o.Committer.Name, Equals, "foo") + c.Assert(o.Committer.Email, Equals, "foo@foo.com") +} + +func (s *OptionsSuite) TestCommitOptionsLoadGlobalCommitter(c *C) { + cfg := config.NewConfig() + cfg.User.Name = "foo" + cfg.User.Email = "foo@foo.com" + cfg.Committer.Name = "bar" + cfg.Committer.Email = "bar@bar.com" + + s.writeGlobalConfig(c, cfg) + defer s.clearGlobalConfig(c) + + o := CommitOptions{} + err := o.Validate(s.Repository) + c.Assert(err, IsNil) + + c.Assert(o.Author.Name, Equals, "foo") + c.Assert(o.Author.Email, Equals, "foo@foo.com") + c.Assert(o.Committer.Name, Equals, "bar") + c.Assert(o.Committer.Email, Equals, "bar@bar.com") +} + +func (s *OptionsSuite) TestCreateTagOptionsLoadGlobal(c *C) { + cfg := config.NewConfig() + cfg.User.Name = "foo" + cfg.User.Email = "foo@foo.com" + + s.writeGlobalConfig(c, cfg) + defer s.clearGlobalConfig(c) + + o := CreateTagOptions{ + Message: "foo", + } + + err := o.Validate(s.Repository, plumbing.ZeroHash) + c.Assert(err, IsNil) + + c.Assert(o.Tagger.Name, Equals, "foo") + c.Assert(o.Tagger.Email, Equals, "foo@foo.com") +} + +func (s *OptionsSuite) writeGlobalConfig(c *C, cfg *config.Config) { + tmp, err := ioutil.TempDir("", "test-options") + c.Assert(err, IsNil) + + err = os.Mkdir(filepath.Join(tmp, "git"), 0777) + c.Assert(err, IsNil) + + os.Setenv("XDG_CONFIG_HOME", tmp) + + content, err := cfg.Marshal() + c.Assert(err, IsNil) + + cfgFile := filepath.Join(tmp, "git/config") + err = ioutil.WriteFile(cfgFile, content, 0777) + c.Assert(err, IsNil) +} + +func (s *OptionsSuite) clearGlobalConfig(c *C) { + os.Setenv("XDG_CONFIG_HOME", "") +} diff --git a/repository_test.go b/repository_test.go index 37cd914..d1af7b6 100644 --- a/repository_test.go +++ b/repository_test.go @@ -2103,15 +2103,7 @@ func (s *RepositorySuite) TestCreateTagAnnotatedBadOpts(c *C) { expectedHash := h.Hash() - ref, err := r.CreateTag("foobar", expectedHash, &CreateTagOptions{ - Message: "foo bar baz qux", - }) - c.Assert(ref, IsNil) - c.Assert(err, Equals, ErrMissingTagger) - - ref, err = r.CreateTag("foobar", expectedHash, &CreateTagOptions{ - Tagger: defaultSignature(), - }) + ref, err := r.CreateTag("foobar", expectedHash, &CreateTagOptions{}) c.Assert(ref, IsNil) c.Assert(err, Equals, ErrMissingMessage) } diff --git a/worktree_status.go b/worktree_status.go index 1542f5e..658ed94 100644 --- a/worktree_status.go +++ b/worktree_status.go @@ -7,6 +7,7 @@ import ( "os" "path" "path/filepath" + "strings" "github.com/go-git/go-billy/v5/util" "github.com/go-git/go-git/v5/plumbing" @@ -264,43 +265,22 @@ func diffTreeIsEquals(a, b noder.Hasher) bool { // the worktree to the index. If any of the files is already staged in the index // no error is returned. When path is a file, the blob.Hash is returned. func (w *Worktree) Add(path string) (plumbing.Hash, error) { - // TODO(mcuadros): remove plumbing.Hash from signature at v5. - s, err := w.Status() - if err != nil { - return plumbing.ZeroHash, err - } - - idx, err := w.r.Storer.Index() - if err != nil { - return plumbing.ZeroHash, err - } - - var h plumbing.Hash - var added bool - - fi, err := w.Filesystem.Lstat(path) - if err != nil || !fi.IsDir() { - added, h, err = w.doAddFile(idx, s, path) - } else { - added, err = w.doAddDirectory(idx, s, path) - } - - if err != nil { - return h, err - } - - if !added { - return h, nil - } - - return h, w.r.Storer.SetIndex(idx) + return w.doAdd(path, make([]gitignore.Pattern, 0)) } -func (w *Worktree) doAddDirectory(idx *index.Index, s Status, directory string) (added bool, err error) { +func (w *Worktree) doAddDirectory(idx *index.Index, s Status, directory string, ignorePattern []gitignore.Pattern) (added bool, err error) { files, err := w.Filesystem.ReadDir(directory) if err != nil { return false, err } + if len(ignorePattern) > 0 { + m := gitignore.NewMatcher(ignorePattern) + matchPath := strings.Split(directory, string(os.PathSeparator)) + if m.Match(matchPath, true) { + // ignore + return false, nil + } + } for _, file := range files { name := path.Join(directory, file.Name()) @@ -311,9 +291,9 @@ func (w *Worktree) doAddDirectory(idx *index.Index, s Status, directory string) // ignore special git directory continue } - a, err = w.doAddDirectory(idx, s, name) + a, err = w.doAddDirectory(idx, s, name, ignorePattern) } else { - a, _, err = w.doAddFile(idx, s, name) + a, _, err = w.doAddFile(idx, s, name, ignorePattern) } if err != nil { @@ -328,6 +308,47 @@ func (w *Worktree) doAddDirectory(idx *index.Index, s Status, directory string) return } +// add changes from all tracked and untracked files +func (w *Worktree) AddWithOptions(opts *AddOptions) (plumbing.Hash, error) { + if opts.All { + return w.doAdd(".", w.Excludes) + } + return w.Add(opts.Path) +} + +func (w *Worktree) doAdd(path string, ignorePattern []gitignore.Pattern) (plumbing.Hash, error) { + // TODO(mcuadros): remove plumbing.Hash from signature at v5. + s, err := w.Status() + if err != nil { + return plumbing.ZeroHash, err + } + + idx, err := w.r.Storer.Index() + if err != nil { + return plumbing.ZeroHash, err + } + + var h plumbing.Hash + var added bool + + fi, err := w.Filesystem.Lstat(path) + if err != nil || !fi.IsDir() { + added, h, err = w.doAddFile(idx, s, path, ignorePattern) + } else { + added, err = w.doAddDirectory(idx, s, path, ignorePattern) + } + + if err != nil { + return h, err + } + + if !added { + return h, nil + } + + return h, w.r.Storer.SetIndex(idx) +} + // AddGlob adds all paths, matching pattern, to the index. If pattern matches a // directory path, all directory contents are added to the index recursively. No // error is returned if all matching paths are already staged in index. @@ -360,9 +381,9 @@ func (w *Worktree) AddGlob(pattern string) error { var added bool if fi.IsDir() { - added, err = w.doAddDirectory(idx, s, file) + added, err = w.doAddDirectory(idx, s, file, make([]gitignore.Pattern, 0)) } else { - added, _, err = w.doAddFile(idx, s, file) + added, _, err = w.doAddFile(idx, s, file, make([]gitignore.Pattern, 0)) } if err != nil { @@ -383,10 +404,18 @@ func (w *Worktree) AddGlob(pattern string) error { // doAddFile create a new blob from path and update the index, added is true if // the file added is different from the index. -func (w *Worktree) doAddFile(idx *index.Index, s Status, path string) (added bool, h plumbing.Hash, err error) { +func (w *Worktree) doAddFile(idx *index.Index, s Status, path string, ignorePattern []gitignore.Pattern) (added bool, h plumbing.Hash, err error) { if s.File(path).Worktree == Unmodified { return false, h, nil } + if len(ignorePattern) > 0 { + m := gitignore.NewMatcher(ignorePattern) + matchPath := strings.Split(path, string(os.PathSeparator)) + if m.Match(matchPath, true) { + // ignore + return false, h, nil + } + } h, err = w.copyFileToStorage(path) if err != nil { diff --git a/worktree_test.go b/worktree_test.go index 24a65eb..2ee830a 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -1370,6 +1370,52 @@ func (s *WorktreeSuite) TestAddDirectoryErrorNotFound(c *C) { c.Assert(h.IsZero(), Equals, true) } +func (s *WorktreeSuite) TestAddAll(c *C) { + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + Filesystem: fs, + } + + err := w.Checkout(&CheckoutOptions{Force: true}) + c.Assert(err, IsNil) + + idx, err := w.r.Storer.Index() + c.Assert(err, IsNil) + c.Assert(idx.Entries, HasLen, 9) + + err = util.WriteFile(w.Filesystem, "file1", []byte("file1"), 0644) + c.Assert(err, IsNil) + + err = util.WriteFile(w.Filesystem, "file2", []byte("file2"), 0644) + c.Assert(err, IsNil) + + err = util.WriteFile(w.Filesystem, "file3", []byte("ignore me"), 0644) + c.Assert(err, IsNil) + + w.Excludes = make([]gitignore.Pattern, 0) + w.Excludes = append(w.Excludes, gitignore.ParsePattern("file3", nil)) + + _, err = w.AddWithOptions(&AddOptions{All: true}) + c.Assert(err, IsNil) + + idx, err = w.r.Storer.Index() + c.Assert(err, IsNil) + c.Assert(idx.Entries, HasLen, 11) + + status, err := w.Status() + c.Assert(err, IsNil) + c.Assert(status, HasLen, 2) + + file1 := status.File("file1") + c.Assert(file1.Staging, Equals, Added) + file2 := status.File("file2") + c.Assert(file2.Staging, Equals, Added) + file3 := status.File("file3") + c.Assert(file3.Staging, Equals, Untracked) + c.Assert(file3.Worktree, Equals, Untracked) +} + func (s *WorktreeSuite) TestAddGlob(c *C) { fs := memfs.New() w := &Worktree{ |