Skip to content

Commit e63b032

Browse files
maguromcuadros
authored andcommitted
Worktree: Provide ability to add excludes (src-d#825)
Worktree: Provide ability to add excludes
1 parent 47417ae commit e63b032

File tree

5 files changed

+285
-7
lines changed

5 files changed

+285
-7
lines changed

plumbing/format/gitignore/dir.go

+83-4
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
11
package gitignore
22

33
import (
4+
"bytes"
45
"io/ioutil"
56
"os"
7+
"os/user"
68
"strings"
79

810
"gopkg.in/src-d/go-billy.v4"
11+
"gopkg.in/src-d/go-git.v4/plumbing/format/config"
12+
gioutil "gopkg.in/src-d/go-git.v4/utils/ioutil"
913
)
1014

1115
const (
1216
commentPrefix = "#"
17+
coreSection = "core"
1318
eol = "\n"
19+
excludesfile = "excludesfile"
1420
gitDir = ".git"
1521
gitignoreFile = ".gitignore"
22+
gitconfigFile = ".gitconfig"
23+
systemFile = "/etc/gitconfig"
1624
)
1725

18-
// ReadPatterns reads gitignore patterns recursively traversing through the directory
19-
// structure. The result is in the ascending order of priority (last higher).
20-
func ReadPatterns(fs billy.Filesystem, path []string) (ps []Pattern, err error) {
21-
f, err := fs.Open(fs.Join(append(path, gitignoreFile)...))
26+
// readIgnoreFile reads a specific git ignore file.
27+
func readIgnoreFile(fs billy.Filesystem, path []string, ignoreFile string) (ps []Pattern, err error) {
28+
f, err := fs.Open(fs.Join(append(path, ignoreFile)...))
2229
if err == nil {
2330
defer f.Close()
2431

@@ -33,6 +40,14 @@ func ReadPatterns(fs billy.Filesystem, path []string) (ps []Pattern, err error)
3340
return nil, err
3441
}
3542

43+
return
44+
}
45+
46+
// ReadPatterns reads gitignore patterns recursively traversing through the directory
47+
// structure. The result is in the ascending order of priority (last higher).
48+
func ReadPatterns(fs billy.Filesystem, path []string) (ps []Pattern, err error) {
49+
ps, _ = readIgnoreFile(fs, path, gitignoreFile)
50+
3651
var fis []os.FileInfo
3752
fis, err = fs.ReadDir(fs.Join(path...))
3853
if err != nil {
@@ -55,3 +70,67 @@ func ReadPatterns(fs billy.Filesystem, path []string) (ps []Pattern, err error)
5570

5671
return
5772
}
73+
74+
func loadPatterns(fs billy.Filesystem, path string) (ps []Pattern, err error) {
75+
f, err := fs.Open(path)
76+
if err != nil {
77+
if os.IsNotExist(err) {
78+
return nil, nil
79+
}
80+
return nil, err
81+
}
82+
83+
defer gioutil.CheckClose(f, &err)
84+
85+
b, err := ioutil.ReadAll(f)
86+
if err != nil {
87+
return
88+
}
89+
90+
d := config.NewDecoder(bytes.NewBuffer(b))
91+
92+
raw := config.New()
93+
if err = d.Decode(raw); err != nil {
94+
return
95+
}
96+
97+
s := raw.Section(coreSection)
98+
efo := s.Options.Get(excludesfile)
99+
if efo == "" {
100+
return nil, nil
101+
}
102+
103+
ps, err = readIgnoreFile(fs, nil, efo)
104+
if os.IsNotExist(err) {
105+
return nil, nil
106+
}
107+
108+
return
109+
}
110+
111+
// LoadGlobalPatterns loads gitignore patterns from from the gitignore file
112+
// declared in a user's ~/.gitconfig file. If the ~/.gitconfig file does not
113+
// exist the function will return nil. If the core.excludesfile property
114+
// is not declared, the function will return nil. If the file pointed to by
115+
// the core.excludesfile property does not exist, the function will return nil.
116+
//
117+
// The function assumes fs is rooted at the root filesystem.
118+
func LoadGlobalPatterns(fs billy.Filesystem) (ps []Pattern, err error) {
119+
usr, err := user.Current()
120+
if err != nil {
121+
return
122+
}
123+
124+
return loadPatterns(fs, fs.Join(usr.HomeDir, gitconfigFile))
125+
}
126+
127+
// LoadSystemPatterns loads gitignore patterns from from the gitignore file
128+
// declared in a system's /etc/gitconfig file. If the ~/.gitconfig file does
129+
// not exist the function will return nil. If the core.excludesfile property
130+
// is not declared, the function will return nil. If the file pointed to by
131+
// the core.excludesfile property does not exist, the function will return nil.
132+
//
133+
// The function assumes fs is rooted at the root filesystem.
134+
func LoadSystemPatterns(fs billy.Filesystem) (ps []Pattern, err error) {
135+
return loadPatterns(fs, systemFile)
136+
}

plumbing/format/gitignore/dir_test.go

+166-3
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,28 @@ package gitignore
22

33
import (
44
"os"
5+
"os/user"
6+
"strconv"
57

68
. "gopkg.in/check.v1"
79
"gopkg.in/src-d/go-billy.v4"
810
"gopkg.in/src-d/go-billy.v4/memfs"
911
)
1012

1113
type MatcherSuite struct {
12-
FS billy.Filesystem
14+
GFS billy.Filesystem // git repository root
15+
RFS billy.Filesystem // root that contains user home
16+
MCFS billy.Filesystem // root that contains user home, but missing ~/.gitconfig
17+
MEFS billy.Filesystem // root that contains user home, but missing excludesfile entry
18+
MIFS billy.Filesystem // root that contains user home, but missing .gitnignore
19+
20+
SFS billy.Filesystem // root that contains /etc/gitconfig
1321
}
1422

1523
var _ = Suite(&MatcherSuite{})
1624

1725
func (s *MatcherSuite) SetUpTest(c *C) {
26+
// setup generic git repository root
1827
fs := memfs.New()
1928
f, err := fs.Create(".gitignore")
2029
c.Assert(err, IsNil)
@@ -36,15 +45,169 @@ func (s *MatcherSuite) SetUpTest(c *C) {
3645
fs.MkdirAll("vendor/github.com", os.ModePerm)
3746
fs.MkdirAll("vendor/gopkg.in", os.ModePerm)
3847

39-
s.FS = fs
48+
s.GFS = fs
49+
50+
// setup root that contains user home
51+
usr, err := user.Current()
52+
c.Assert(err, IsNil)
53+
54+
fs = memfs.New()
55+
err = fs.MkdirAll(usr.HomeDir, os.ModePerm)
56+
c.Assert(err, IsNil)
57+
58+
f, err = fs.Create(fs.Join(usr.HomeDir, gitconfigFile))
59+
c.Assert(err, IsNil)
60+
_, err = f.Write([]byte("[core]\n"))
61+
c.Assert(err, IsNil)
62+
_, err = f.Write([]byte(" excludesfile = " + strconv.Quote(fs.Join(usr.HomeDir, ".gitignore_global")) + "\n"))
63+
c.Assert(err, IsNil)
64+
err = f.Close()
65+
c.Assert(err, IsNil)
66+
67+
f, err = fs.Create(fs.Join(usr.HomeDir, ".gitignore_global"))
68+
c.Assert(err, IsNil)
69+
_, err = f.Write([]byte("# IntelliJ\n"))
70+
c.Assert(err, IsNil)
71+
_, err = f.Write([]byte(".idea/\n"))
72+
c.Assert(err, IsNil)
73+
_, err = f.Write([]byte("*.iml\n"))
74+
c.Assert(err, IsNil)
75+
err = f.Close()
76+
c.Assert(err, IsNil)
77+
78+
s.RFS = fs
79+
80+
// root that contains user home, but missing ~/.gitconfig
81+
fs = memfs.New()
82+
err = fs.MkdirAll(usr.HomeDir, os.ModePerm)
83+
c.Assert(err, IsNil)
84+
85+
f, err = fs.Create(fs.Join(usr.HomeDir, ".gitignore_global"))
86+
c.Assert(err, IsNil)
87+
_, err = f.Write([]byte("# IntelliJ\n"))
88+
c.Assert(err, IsNil)
89+
_, err = f.Write([]byte(".idea/\n"))
90+
c.Assert(err, IsNil)
91+
_, err = f.Write([]byte("*.iml\n"))
92+
c.Assert(err, IsNil)
93+
err = f.Close()
94+
c.Assert(err, IsNil)
95+
96+
s.MCFS = fs
97+
98+
// setup root that contains user home, but missing excludesfile entry
99+
fs = memfs.New()
100+
err = fs.MkdirAll(usr.HomeDir, os.ModePerm)
101+
c.Assert(err, IsNil)
102+
103+
f, err = fs.Create(fs.Join(usr.HomeDir, gitconfigFile))
104+
c.Assert(err, IsNil)
105+
_, err = f.Write([]byte("[core]\n"))
106+
c.Assert(err, IsNil)
107+
err = f.Close()
108+
c.Assert(err, IsNil)
109+
110+
f, err = fs.Create(fs.Join(usr.HomeDir, ".gitignore_global"))
111+
c.Assert(err, IsNil)
112+
_, err = f.Write([]byte("# IntelliJ\n"))
113+
c.Assert(err, IsNil)
114+
_, err = f.Write([]byte(".idea/\n"))
115+
c.Assert(err, IsNil)
116+
_, err = f.Write([]byte("*.iml\n"))
117+
c.Assert(err, IsNil)
118+
err = f.Close()
119+
c.Assert(err, IsNil)
120+
121+
s.MEFS = fs
122+
123+
// setup root that contains user home, but missing .gitnignore
124+
fs = memfs.New()
125+
err = fs.MkdirAll(usr.HomeDir, os.ModePerm)
126+
c.Assert(err, IsNil)
127+
128+
f, err = fs.Create(fs.Join(usr.HomeDir, gitconfigFile))
129+
c.Assert(err, IsNil)
130+
_, err = f.Write([]byte("[core]\n"))
131+
c.Assert(err, IsNil)
132+
_, err = f.Write([]byte(" excludesfile = " + strconv.Quote(fs.Join(usr.HomeDir, ".gitignore_global")) + "\n"))
133+
c.Assert(err, IsNil)
134+
err = f.Close()
135+
c.Assert(err, IsNil)
136+
137+
s.MIFS = fs
138+
139+
// setup root that contains user home
140+
fs = memfs.New()
141+
err = fs.MkdirAll("etc", os.ModePerm)
142+
c.Assert(err, IsNil)
143+
144+
f, err = fs.Create(systemFile)
145+
c.Assert(err, IsNil)
146+
_, err = f.Write([]byte("[core]\n"))
147+
c.Assert(err, IsNil)
148+
_, err = f.Write([]byte(" excludesfile = /etc/gitignore_global\n"))
149+
c.Assert(err, IsNil)
150+
err = f.Close()
151+
c.Assert(err, IsNil)
152+
153+
f, err = fs.Create("/etc/gitignore_global")
154+
c.Assert(err, IsNil)
155+
_, err = f.Write([]byte("# IntelliJ\n"))
156+
c.Assert(err, IsNil)
157+
_, err = f.Write([]byte(".idea/\n"))
158+
c.Assert(err, IsNil)
159+
_, err = f.Write([]byte("*.iml\n"))
160+
c.Assert(err, IsNil)
161+
err = f.Close()
162+
c.Assert(err, IsNil)
163+
164+
s.SFS = fs
40165
}
41166

42167
func (s *MatcherSuite) TestDir_ReadPatterns(c *C) {
43-
ps, err := ReadPatterns(s.FS, nil)
168+
ps, err := ReadPatterns(s.GFS, nil)
44169
c.Assert(err, IsNil)
45170
c.Assert(ps, HasLen, 2)
46171

47172
m := NewMatcher(ps)
48173
c.Assert(m.Match([]string{"vendor", "gopkg.in"}, true), Equals, true)
49174
c.Assert(m.Match([]string{"vendor", "github.com"}, true), Equals, false)
50175
}
176+
177+
func (s *MatcherSuite) TestDir_LoadGlobalPatterns(c *C) {
178+
ps, err := LoadGlobalPatterns(s.RFS)
179+
c.Assert(err, IsNil)
180+
c.Assert(ps, HasLen, 2)
181+
182+
m := NewMatcher(ps)
183+
c.Assert(m.Match([]string{"go-git.v4.iml"}, true), Equals, true)
184+
c.Assert(m.Match([]string{".idea"}, true), Equals, true)
185+
}
186+
187+
func (s *MatcherSuite) TestDir_LoadGlobalPatternsMissingGitconfig(c *C) {
188+
ps, err := LoadGlobalPatterns(s.MCFS)
189+
c.Assert(err, IsNil)
190+
c.Assert(ps, HasLen, 0)
191+
}
192+
193+
func (s *MatcherSuite) TestDir_LoadGlobalPatternsMissingExcludesfile(c *C) {
194+
ps, err := LoadGlobalPatterns(s.MEFS)
195+
c.Assert(err, IsNil)
196+
c.Assert(ps, HasLen, 0)
197+
}
198+
199+
func (s *MatcherSuite) TestDir_LoadGlobalPatternsMissingGitignore(c *C) {
200+
ps, err := LoadGlobalPatterns(s.MIFS)
201+
c.Assert(err, IsNil)
202+
c.Assert(ps, HasLen, 0)
203+
}
204+
205+
func (s *MatcherSuite) TestDir_LoadSystemPatterns(c *C) {
206+
ps, err := LoadSystemPatterns(s.SFS)
207+
c.Assert(err, IsNil)
208+
c.Assert(ps, HasLen, 2)
209+
210+
m := NewMatcher(ps)
211+
c.Assert(m.Match([]string{"go-git.v4.iml"}, true), Equals, true)
212+
c.Assert(m.Match([]string{".idea"}, true), Equals, true)
213+
}

worktree.go

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"gopkg.in/src-d/go-git.v4/config"
1414
"gopkg.in/src-d/go-git.v4/plumbing"
1515
"gopkg.in/src-d/go-git.v4/plumbing/filemode"
16+
"gopkg.in/src-d/go-git.v4/plumbing/format/gitignore"
1617
"gopkg.in/src-d/go-git.v4/plumbing/format/index"
1718
"gopkg.in/src-d/go-git.v4/plumbing/object"
1819
"gopkg.in/src-d/go-git.v4/plumbing/storer"
@@ -33,6 +34,8 @@ var (
3334
type Worktree struct {
3435
// Filesystem underlying filesystem.
3536
Filesystem billy.Filesystem
37+
// External excludes not found in the repository .gitignore
38+
Excludes []gitignore.Pattern
3639

3740
r *Repository
3841
}

worktree_status.go

+3
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ func (w *Worktree) excludeIgnoredChanges(changes merkletrie.Changes) merkletrie.
145145
if err != nil || len(patterns) == 0 {
146146
return changes
147147
}
148+
149+
patterns = append(patterns, w.Excludes...)
150+
148151
m := gitignore.NewMatcher(patterns)
149152

150153
var res merkletrie.Changes

worktree_test.go

+30
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"gopkg.in/src-d/go-git.v4/config"
1616
"gopkg.in/src-d/go-git.v4/plumbing"
1717
"gopkg.in/src-d/go-git.v4/plumbing/filemode"
18+
"gopkg.in/src-d/go-git.v4/plumbing/format/gitignore"
1819
"gopkg.in/src-d/go-git.v4/plumbing/format/index"
1920
"gopkg.in/src-d/go-git.v4/plumbing/object"
2021
"gopkg.in/src-d/go-git.v4/storage/memory"
@@ -1072,6 +1073,35 @@ func (s *WorktreeSuite) TestAddUntracked(c *C) {
10721073
c.Assert(obj.Size(), Equals, int64(3))
10731074
}
10741075

1076+
func (s *WorktreeSuite) TestIgnored(c *C) {
1077+
fs := memfs.New()
1078+
w := &Worktree{
1079+
r: s.Repository,
1080+
Filesystem: fs,
1081+
}
1082+
1083+
w.Excludes = make([]gitignore.Pattern, 0)
1084+
w.Excludes = append(w.Excludes, gitignore.ParsePattern("foo", nil))
1085+
1086+
err := w.Checkout(&CheckoutOptions{Force: true})
1087+
c.Assert(err, IsNil)
1088+
1089+
idx, err := w.r.Storer.Index()
1090+
c.Assert(err, IsNil)
1091+
c.Assert(idx.Entries, HasLen, 9)
1092+
1093+
err = util.WriteFile(w.Filesystem, "foo", []byte("FOO"), 0755)
1094+
c.Assert(err, IsNil)
1095+
1096+
status, err := w.Status()
1097+
c.Assert(err, IsNil)
1098+
c.Assert(status, HasLen, 0)
1099+
1100+
file := status.File("foo")
1101+
c.Assert(file.Staging, Equals, Untracked)
1102+
c.Assert(file.Worktree, Equals, Untracked)
1103+
}
1104+
10751105
func (s *WorktreeSuite) TestAddModified(c *C) {
10761106
fs := memfs.New()
10771107
w := &Worktree{

0 commit comments

Comments
 (0)