Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit 9fb58fc

Browse files
committed
plumbing: format index, Index.Add and Index.Glob methods
Signed-off-by: Máximo Cuadros <mcuadros@gmail.com>
1 parent 66f08b8 commit 9fb58fc

File tree

3 files changed

+255
-0
lines changed

3 files changed

+255
-0
lines changed

plumbing/format/index/index.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"errors"
66
"fmt"
7+
"path/filepath"
78
"time"
89

910
"gopkg.in/src-d/go-git.v4/plumbing"
@@ -51,8 +52,20 @@ type Index struct {
5152
ResolveUndo *ResolveUndo
5253
}
5354

55+
// Add creates a new Entry and returns it. The caller should first check that
56+
// another entry with the same path does not exist.
57+
func (i *Index) Add(path string) *Entry {
58+
e := &Entry{
59+
Name: filepath.ToSlash(path),
60+
}
61+
62+
i.Entries = append(i.Entries, e)
63+
return e
64+
}
65+
5466
// Entry returns the entry that match the given path, if any.
5567
func (i *Index) Entry(path string) (*Entry, error) {
68+
path = filepath.ToSlash(path)
5669
for _, e := range i.Entries {
5770
if e.Name == path {
5871
return e, nil
@@ -64,6 +77,7 @@ func (i *Index) Entry(path string) (*Entry, error) {
6477

6578
// Remove remove the entry that match the give path and returns deleted entry.
6679
func (i *Index) Remove(path string) (*Entry, error) {
80+
path = filepath.ToSlash(path)
6781
for index, e := range i.Entries {
6882
if e.Name == path {
6983
i.Entries = append(i.Entries[:index], i.Entries[index+1:]...)
@@ -74,6 +88,24 @@ func (i *Index) Remove(path string) (*Entry, error) {
7488
return nil, ErrEntryNotFound
7589
}
7690

91+
// Glob returns the all entries matching pattern or nil if there is no matching
92+
// entry. The syntax of patterns is the same as in filepath.Glob.
93+
func (i *Index) Glob(pattern string) (matches []*Entry, err error) {
94+
pattern = filepath.ToSlash(pattern)
95+
for _, e := range i.Entries {
96+
m, err := match(pattern, e.Name)
97+
if err != nil {
98+
return nil, err
99+
}
100+
101+
if m {
102+
matches = append(matches, e)
103+
}
104+
}
105+
106+
return
107+
}
108+
77109
// String is equivalent to `git ls-files --stage --debug`
78110
func (i *Index) String() string {
79111
buf := bytes.NewBuffer(nil)

plumbing/format/index/index_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
11
package index
22

33
import (
4+
"path/filepath"
5+
46
. "gopkg.in/check.v1"
57
)
68

9+
func (s *IndexSuite) TestIndexAdd(c *C) {
10+
idx := &Index{}
11+
e := idx.Add("foo")
12+
e.Size = 42
13+
14+
e, err := idx.Entry("foo")
15+
c.Assert(err, IsNil)
16+
c.Assert(e.Name, Equals, "foo")
17+
c.Assert(e.Size, Equals, uint32(42))
18+
}
19+
720
func (s *IndexSuite) TestIndexEntry(c *C) {
821
idx := &Index{
922
Entries: []*Entry{
@@ -37,3 +50,27 @@ func (s *IndexSuite) TestIndexRemove(c *C) {
3750
c.Assert(e, IsNil)
3851
c.Assert(err, Equals, ErrEntryNotFound)
3952
}
53+
54+
func (s *IndexSuite) TestIndexGlob(c *C) {
55+
idx := &Index{
56+
Entries: []*Entry{
57+
{Name: "foo/bar/bar", Size: 42},
58+
{Name: "foo/baz/qux", Size: 42},
59+
{Name: "fux", Size: 82},
60+
},
61+
}
62+
63+
m, err := idx.Glob(filepath.Join("foo", "b*"))
64+
c.Assert(err, IsNil)
65+
c.Assert(m, HasLen, 2)
66+
c.Assert(m[0].Name, Equals, "foo/bar/bar")
67+
c.Assert(m[1].Name, Equals, "foo/baz/qux")
68+
69+
m, err = idx.Glob("f*")
70+
c.Assert(err, IsNil)
71+
c.Assert(m, HasLen, 3)
72+
73+
m, err = idx.Glob("f*/baz/q*")
74+
c.Assert(err, IsNil)
75+
c.Assert(m, HasLen, 1)
76+
}

plumbing/format/index/match.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package index
2+
3+
import (
4+
"path/filepath"
5+
"runtime"
6+
"unicode/utf8"
7+
)
8+
9+
// match is filepath.Match with support to match fullpath and not only filenames
10+
// code from:
11+
// https://github.com/golang/go/blob/39852bf4cce6927e01d0136c7843f65a801738cb/src/path/filepath/match.go#L44-L224
12+
func match(pattern, name string) (matched bool, err error) {
13+
Pattern:
14+
for len(pattern) > 0 {
15+
var star bool
16+
var chunk string
17+
star, chunk, pattern = scanChunk(pattern)
18+
19+
// Look for match at current position.
20+
t, ok, err := matchChunk(chunk, name)
21+
// if we're the last chunk, make sure we've exhausted the name
22+
// otherwise we'll give a false result even if we could still match
23+
// using the star
24+
if ok && (len(t) == 0 || len(pattern) > 0) {
25+
name = t
26+
continue
27+
}
28+
if err != nil {
29+
return false, err
30+
}
31+
if star {
32+
// Look for match skipping i+1 bytes.
33+
// Cannot skip /.
34+
for i := 0; i < len(name); i++ {
35+
t, ok, err := matchChunk(chunk, name[i+1:])
36+
if ok {
37+
// if we're the last chunk, make sure we exhausted the name
38+
if len(pattern) == 0 && len(t) > 0 {
39+
continue
40+
}
41+
name = t
42+
continue Pattern
43+
}
44+
if err != nil {
45+
return false, err
46+
}
47+
}
48+
}
49+
return false, nil
50+
}
51+
return len(name) == 0, nil
52+
}
53+
54+
// scanChunk gets the next segment of pattern, which is a non-star string
55+
// possibly preceded by a star.
56+
func scanChunk(pattern string) (star bool, chunk, rest string) {
57+
for len(pattern) > 0 && pattern[0] == '*' {
58+
pattern = pattern[1:]
59+
star = true
60+
}
61+
inrange := false
62+
var i int
63+
Scan:
64+
for i = 0; i < len(pattern); i++ {
65+
switch pattern[i] {
66+
case '\\':
67+
if runtime.GOOS != "windows" {
68+
// error check handled in matchChunk: bad pattern.
69+
if i+1 < len(pattern) {
70+
i++
71+
}
72+
}
73+
case '[':
74+
inrange = true
75+
case ']':
76+
inrange = false
77+
case '*':
78+
if !inrange {
79+
break Scan
80+
}
81+
}
82+
}
83+
return star, pattern[0:i], pattern[i:]
84+
}
85+
86+
// matchChunk checks whether chunk matches the beginning of s.
87+
// If so, it returns the remainder of s (after the match).
88+
// Chunk is all single-character operators: literals, char classes, and ?.
89+
func matchChunk(chunk, s string) (rest string, ok bool, err error) {
90+
for len(chunk) > 0 {
91+
if len(s) == 0 {
92+
return
93+
}
94+
switch chunk[0] {
95+
case '[':
96+
// character class
97+
r, n := utf8.DecodeRuneInString(s)
98+
s = s[n:]
99+
chunk = chunk[1:]
100+
// We can't end right after '[', we're expecting at least
101+
// a closing bracket and possibly a caret.
102+
if len(chunk) == 0 {
103+
err = filepath.ErrBadPattern
104+
return
105+
}
106+
// possibly negated
107+
negated := chunk[0] == '^'
108+
if negated {
109+
chunk = chunk[1:]
110+
}
111+
// parse all ranges
112+
match := false
113+
nrange := 0
114+
for {
115+
if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 {
116+
chunk = chunk[1:]
117+
break
118+
}
119+
var lo, hi rune
120+
if lo, chunk, err = getEsc(chunk); err != nil {
121+
return
122+
}
123+
hi = lo
124+
if chunk[0] == '-' {
125+
if hi, chunk, err = getEsc(chunk[1:]); err != nil {
126+
return
127+
}
128+
}
129+
if lo <= r && r <= hi {
130+
match = true
131+
}
132+
nrange++
133+
}
134+
if match == negated {
135+
return
136+
}
137+
138+
case '?':
139+
_, n := utf8.DecodeRuneInString(s)
140+
s = s[n:]
141+
chunk = chunk[1:]
142+
143+
case '\\':
144+
if runtime.GOOS != "windows" {
145+
chunk = chunk[1:]
146+
if len(chunk) == 0 {
147+
err = filepath.ErrBadPattern
148+
return
149+
}
150+
}
151+
fallthrough
152+
153+
default:
154+
if chunk[0] != s[0] {
155+
return
156+
}
157+
s = s[1:]
158+
chunk = chunk[1:]
159+
}
160+
}
161+
return s, true, nil
162+
}
163+
164+
// getEsc gets a possibly-escaped character from chunk, for a character class.
165+
func getEsc(chunk string) (r rune, nchunk string, err error) {
166+
if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' {
167+
err = filepath.ErrBadPattern
168+
return
169+
}
170+
if chunk[0] == '\\' && runtime.GOOS != "windows" {
171+
chunk = chunk[1:]
172+
if len(chunk) == 0 {
173+
err = filepath.ErrBadPattern
174+
return
175+
}
176+
}
177+
r, n := utf8.DecodeRuneInString(chunk)
178+
if r == utf8.RuneError && n == 1 {
179+
err = filepath.ErrBadPattern
180+
}
181+
nchunk = chunk[n:]
182+
if len(nchunk) == 0 {
183+
err = filepath.ErrBadPattern
184+
}
185+
return
186+
}

0 commit comments

Comments
 (0)