aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--plumbing/format/gitignore/dir.go87
-rw-r--r--plumbing/format/gitignore/dir_test.go169
-rw-r--r--worktree.go3
-rw-r--r--worktree_status.go3
-rw-r--r--worktree_test.go30
5 files changed, 285 insertions, 7 deletions
diff --git a/plumbing/format/gitignore/dir.go b/plumbing/format/gitignore/dir.go
index 41dd624..1e88970 100644
--- a/plumbing/format/gitignore/dir.go
+++ b/plumbing/format/gitignore/dir.go
@@ -1,24 +1,31 @@
package gitignore
import (
+ "bytes"
"io/ioutil"
"os"
+ "os/user"
"strings"
"gopkg.in/src-d/go-billy.v4"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/config"
+ gioutil "gopkg.in/src-d/go-git.v4/utils/ioutil"
)
const (
commentPrefix = "#"
+ coreSection = "core"
eol = "\n"
+ excludesfile = "excludesfile"
gitDir = ".git"
gitignoreFile = ".gitignore"
+ gitconfigFile = ".gitconfig"
+ systemFile = "/etc/gitconfig"
)
-// ReadPatterns reads gitignore patterns recursively traversing through the directory
-// structure. The result is in the ascending order of priority (last higher).
-func ReadPatterns(fs billy.Filesystem, path []string) (ps []Pattern, err error) {
- f, err := fs.Open(fs.Join(append(path, gitignoreFile)...))
+// readIgnoreFile reads a specific git ignore file.
+func readIgnoreFile(fs billy.Filesystem, path []string, ignoreFile string) (ps []Pattern, err error) {
+ f, err := fs.Open(fs.Join(append(path, ignoreFile)...))
if err == nil {
defer f.Close()
@@ -33,6 +40,14 @@ func ReadPatterns(fs billy.Filesystem, path []string) (ps []Pattern, err error)
return nil, err
}
+ return
+}
+
+// ReadPatterns reads gitignore patterns recursively traversing through the directory
+// structure. The result is in the ascending order of priority (last higher).
+func ReadPatterns(fs billy.Filesystem, path []string) (ps []Pattern, err error) {
+ ps, _ = readIgnoreFile(fs, path, gitignoreFile)
+
var fis []os.FileInfo
fis, err = fs.ReadDir(fs.Join(path...))
if err != nil {
@@ -55,3 +70,67 @@ func ReadPatterns(fs billy.Filesystem, path []string) (ps []Pattern, err error)
return
}
+
+func loadPatterns(fs billy.Filesystem, path string) (ps []Pattern, err error) {
+ f, err := fs.Open(path)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil, nil
+ }
+ return nil, err
+ }
+
+ defer gioutil.CheckClose(f, &err)
+
+ b, err := ioutil.ReadAll(f)
+ if err != nil {
+ return
+ }
+
+ d := config.NewDecoder(bytes.NewBuffer(b))
+
+ raw := config.New()
+ if err = d.Decode(raw); err != nil {
+ return
+ }
+
+ s := raw.Section(coreSection)
+ efo := s.Options.Get(excludesfile)
+ if efo == "" {
+ return nil, nil
+ }
+
+ ps, err = readIgnoreFile(fs, nil, efo)
+ if os.IsNotExist(err) {
+ return nil, nil
+ }
+
+ return
+}
+
+// LoadGlobalPatterns loads gitignore patterns from from the gitignore file
+// declared in a user's ~/.gitconfig file. If the ~/.gitconfig file does not
+// exist the function will return nil. If the core.excludesfile property
+// is not declared, the function will return nil. If the file pointed to by
+// the core.excludesfile property does not exist, the function will return nil.
+//
+// The function assumes fs is rooted at the root filesystem.
+func LoadGlobalPatterns(fs billy.Filesystem) (ps []Pattern, err error) {
+ usr, err := user.Current()
+ if err != nil {
+ return
+ }
+
+ return loadPatterns(fs, fs.Join(usr.HomeDir, gitconfigFile))
+}
+
+// LoadSystemPatterns loads gitignore patterns from from the gitignore file
+// declared in a system's /etc/gitconfig file. If the ~/.gitconfig file does
+// not exist the function will return nil. If the core.excludesfile property
+// is not declared, the function will return nil. If the file pointed to by
+// the core.excludesfile property does not exist, the function will return nil.
+//
+// The function assumes fs is rooted at the root filesystem.
+func LoadSystemPatterns(fs billy.Filesystem) (ps []Pattern, err error) {
+ return loadPatterns(fs, systemFile)
+}
diff --git a/plumbing/format/gitignore/dir_test.go b/plumbing/format/gitignore/dir_test.go
index b8a5453..13e2d82 100644
--- a/plumbing/format/gitignore/dir_test.go
+++ b/plumbing/format/gitignore/dir_test.go
@@ -2,6 +2,8 @@ package gitignore
import (
"os"
+ "os/user"
+ "strconv"
. "gopkg.in/check.v1"
"gopkg.in/src-d/go-billy.v4"
@@ -9,12 +11,19 @@ import (
)
type MatcherSuite struct {
- FS billy.Filesystem
+ GFS billy.Filesystem // git repository root
+ RFS billy.Filesystem // root that contains user home
+ MCFS billy.Filesystem // root that contains user home, but missing ~/.gitconfig
+ MEFS billy.Filesystem // root that contains user home, but missing excludesfile entry
+ MIFS billy.Filesystem // root that contains user home, but missing .gitnignore
+
+ SFS billy.Filesystem // root that contains /etc/gitconfig
}
var _ = Suite(&MatcherSuite{})
func (s *MatcherSuite) SetUpTest(c *C) {
+ // setup generic git repository root
fs := memfs.New()
f, err := fs.Create(".gitignore")
c.Assert(err, IsNil)
@@ -36,11 +45,127 @@ func (s *MatcherSuite) SetUpTest(c *C) {
fs.MkdirAll("vendor/github.com", os.ModePerm)
fs.MkdirAll("vendor/gopkg.in", os.ModePerm)
- s.FS = fs
+ s.GFS = fs
+
+ // setup root that contains user home
+ usr, err := user.Current()
+ c.Assert(err, IsNil)
+
+ fs = memfs.New()
+ err = fs.MkdirAll(usr.HomeDir, os.ModePerm)
+ c.Assert(err, IsNil)
+
+ f, err = fs.Create(fs.Join(usr.HomeDir, gitconfigFile))
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte("[core]\n"))
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte(" excludesfile = " + strconv.Quote(fs.Join(usr.HomeDir, ".gitignore_global")) + "\n"))
+ c.Assert(err, IsNil)
+ err = f.Close()
+ c.Assert(err, IsNil)
+
+ f, err = fs.Create(fs.Join(usr.HomeDir, ".gitignore_global"))
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte("# IntelliJ\n"))
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte(".idea/\n"))
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte("*.iml\n"))
+ c.Assert(err, IsNil)
+ err = f.Close()
+ c.Assert(err, IsNil)
+
+ s.RFS = fs
+
+ // root that contains user home, but missing ~/.gitconfig
+ fs = memfs.New()
+ err = fs.MkdirAll(usr.HomeDir, os.ModePerm)
+ c.Assert(err, IsNil)
+
+ f, err = fs.Create(fs.Join(usr.HomeDir, ".gitignore_global"))
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte("# IntelliJ\n"))
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte(".idea/\n"))
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte("*.iml\n"))
+ c.Assert(err, IsNil)
+ err = f.Close()
+ c.Assert(err, IsNil)
+
+ s.MCFS = fs
+
+ // setup root that contains user home, but missing excludesfile entry
+ fs = memfs.New()
+ err = fs.MkdirAll(usr.HomeDir, os.ModePerm)
+ c.Assert(err, IsNil)
+
+ f, err = fs.Create(fs.Join(usr.HomeDir, gitconfigFile))
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte("[core]\n"))
+ c.Assert(err, IsNil)
+ err = f.Close()
+ c.Assert(err, IsNil)
+
+ f, err = fs.Create(fs.Join(usr.HomeDir, ".gitignore_global"))
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte("# IntelliJ\n"))
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte(".idea/\n"))
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte("*.iml\n"))
+ c.Assert(err, IsNil)
+ err = f.Close()
+ c.Assert(err, IsNil)
+
+ s.MEFS = fs
+
+ // setup root that contains user home, but missing .gitnignore
+ fs = memfs.New()
+ err = fs.MkdirAll(usr.HomeDir, os.ModePerm)
+ c.Assert(err, IsNil)
+
+ f, err = fs.Create(fs.Join(usr.HomeDir, gitconfigFile))
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte("[core]\n"))
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte(" excludesfile = " + strconv.Quote(fs.Join(usr.HomeDir, ".gitignore_global")) + "\n"))
+ c.Assert(err, IsNil)
+ err = f.Close()
+ c.Assert(err, IsNil)
+
+ s.MIFS = fs
+
+ // setup root that contains user home
+ fs = memfs.New()
+ err = fs.MkdirAll("etc", os.ModePerm)
+ c.Assert(err, IsNil)
+
+ f, err = fs.Create(systemFile)
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte("[core]\n"))
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte(" excludesfile = /etc/gitignore_global\n"))
+ c.Assert(err, IsNil)
+ err = f.Close()
+ c.Assert(err, IsNil)
+
+ f, err = fs.Create("/etc/gitignore_global")
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte("# IntelliJ\n"))
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte(".idea/\n"))
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte("*.iml\n"))
+ c.Assert(err, IsNil)
+ err = f.Close()
+ c.Assert(err, IsNil)
+
+ s.SFS = fs
}
func (s *MatcherSuite) TestDir_ReadPatterns(c *C) {
- ps, err := ReadPatterns(s.FS, nil)
+ ps, err := ReadPatterns(s.GFS, nil)
c.Assert(err, IsNil)
c.Assert(ps, HasLen, 2)
@@ -48,3 +173,41 @@ func (s *MatcherSuite) TestDir_ReadPatterns(c *C) {
c.Assert(m.Match([]string{"vendor", "gopkg.in"}, true), Equals, true)
c.Assert(m.Match([]string{"vendor", "github.com"}, true), Equals, false)
}
+
+func (s *MatcherSuite) TestDir_LoadGlobalPatterns(c *C) {
+ ps, err := LoadGlobalPatterns(s.RFS)
+ c.Assert(err, IsNil)
+ c.Assert(ps, HasLen, 2)
+
+ m := NewMatcher(ps)
+ c.Assert(m.Match([]string{"go-git.v4.iml"}, true), Equals, true)
+ c.Assert(m.Match([]string{".idea"}, true), Equals, true)
+}
+
+func (s *MatcherSuite) TestDir_LoadGlobalPatternsMissingGitconfig(c *C) {
+ ps, err := LoadGlobalPatterns(s.MCFS)
+ c.Assert(err, IsNil)
+ c.Assert(ps, HasLen, 0)
+}
+
+func (s *MatcherSuite) TestDir_LoadGlobalPatternsMissingExcludesfile(c *C) {
+ ps, err := LoadGlobalPatterns(s.MEFS)
+ c.Assert(err, IsNil)
+ c.Assert(ps, HasLen, 0)
+}
+
+func (s *MatcherSuite) TestDir_LoadGlobalPatternsMissingGitignore(c *C) {
+ ps, err := LoadGlobalPatterns(s.MIFS)
+ c.Assert(err, IsNil)
+ c.Assert(ps, HasLen, 0)
+}
+
+func (s *MatcherSuite) TestDir_LoadSystemPatterns(c *C) {
+ ps, err := LoadSystemPatterns(s.SFS)
+ c.Assert(err, IsNil)
+ c.Assert(ps, HasLen, 2)
+
+ m := NewMatcher(ps)
+ c.Assert(m.Match([]string{"go-git.v4.iml"}, true), Equals, true)
+ c.Assert(m.Match([]string{".idea"}, true), Equals, true)
+}
diff --git a/worktree.go b/worktree.go
index d2cb29a..ddf6fff 100644
--- a/worktree.go
+++ b/worktree.go
@@ -13,6 +13,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/filemode"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/gitignore"
"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/plumbing/storer"
@@ -33,6 +34,8 @@ var (
type Worktree struct {
// Filesystem underlying filesystem.
Filesystem billy.Filesystem
+ // External excludes not found in the repository .gitignore
+ Excludes []gitignore.Pattern
r *Repository
}
diff --git a/worktree_status.go b/worktree_status.go
index b5f2381..0e113d0 100644
--- a/worktree_status.go
+++ b/worktree_status.go
@@ -145,6 +145,9 @@ func (w *Worktree) excludeIgnoredChanges(changes merkletrie.Changes) merkletrie.
if err != nil || len(patterns) == 0 {
return changes
}
+
+ patterns = append(patterns, w.Excludes...)
+
m := gitignore.NewMatcher(patterns)
var res merkletrie.Changes
diff --git a/worktree_test.go b/worktree_test.go
index 05a205a..df191b0 100644
--- a/worktree_test.go
+++ b/worktree_test.go
@@ -15,6 +15,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/filemode"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/gitignore"
"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"
@@ -1072,6 +1073,35 @@ func (s *WorktreeSuite) TestAddUntracked(c *C) {
c.Assert(obj.Size(), Equals, int64(3))
}
+func (s *WorktreeSuite) TestIgnored(c *C) {
+ fs := memfs.New()
+ w := &Worktree{
+ r: s.Repository,
+ Filesystem: fs,
+ }
+
+ w.Excludes = make([]gitignore.Pattern, 0)
+ w.Excludes = append(w.Excludes, gitignore.ParsePattern("foo", nil))
+
+ 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, "foo", []byte("FOO"), 0755)
+ c.Assert(err, IsNil)
+
+ status, err := w.Status()
+ c.Assert(err, IsNil)
+ c.Assert(status, HasLen, 0)
+
+ file := status.File("foo")
+ c.Assert(file.Staging, Equals, Untracked)
+ c.Assert(file.Worktree, Equals, Untracked)
+}
+
func (s *WorktreeSuite) TestAddModified(c *C) {
fs := memfs.New()
w := &Worktree{