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

new methods Worktree.[AddGlob|RemoveBlob] and recursive Worktree.[Add|Remove] #739

Merged
merged 3 commits into from
Feb 28, 2018
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions plumbing/format/index/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"errors"
"fmt"
"path/filepath"
"time"

"gopkg.in/src-d/go-git.v4/plumbing"
Expand Down Expand Up @@ -51,8 +52,20 @@ type Index struct {
ResolveUndo *ResolveUndo
}

// Add creates a new Entry and returns it. The caller should first check that
// another entry with the same path does not exist.
func (i *Index) Add(path string) *Entry {
e := &Entry{
Name: filepath.ToSlash(path),
}

i.Entries = append(i.Entries, e)
return e
}

// Entry returns the entry that match the given path, if any.
func (i *Index) Entry(path string) (*Entry, error) {
path = filepath.ToSlash(path)
for _, e := range i.Entries {
if e.Name == path {
return e, nil
Expand All @@ -64,6 +77,7 @@ func (i *Index) Entry(path string) (*Entry, error) {

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

// Glob returns the all entries matching pattern or nil if there is no matching
// entry. The syntax of patterns is the same as in filepath.Glob.
func (i *Index) Glob(pattern string) (matches []*Entry, err error) {
pattern = filepath.ToSlash(pattern)
for _, e := range i.Entries {
m, err := match(pattern, e.Name)
if err != nil {
return nil, err
}

if m {
matches = append(matches, e)
}
}

return
}

// String is equivalent to `git ls-files --stage --debug`
func (i *Index) String() string {
buf := bytes.NewBuffer(nil)
Expand Down
37 changes: 37 additions & 0 deletions plumbing/format/index/index_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
package index

import (
"path/filepath"

. "gopkg.in/check.v1"
)

func (s *IndexSuite) TestIndexAdd(c *C) {
idx := &Index{}
e := idx.Add("foo")
e.Size = 42

e, err := idx.Entry("foo")
c.Assert(err, IsNil)
c.Assert(e.Name, Equals, "foo")
c.Assert(e.Size, Equals, uint32(42))
}

func (s *IndexSuite) TestIndexEntry(c *C) {
idx := &Index{
Entries: []*Entry{
Expand Down Expand Up @@ -37,3 +50,27 @@ func (s *IndexSuite) TestIndexRemove(c *C) {
c.Assert(e, IsNil)
c.Assert(err, Equals, ErrEntryNotFound)
}

func (s *IndexSuite) TestIndexGlob(c *C) {
idx := &Index{
Entries: []*Entry{
{Name: "foo/bar/bar", Size: 42},
{Name: "foo/baz/qux", Size: 42},
{Name: "fux", Size: 82},
},
}

m, err := idx.Glob(filepath.Join("foo", "b*"))
c.Assert(err, IsNil)
c.Assert(m, HasLen, 2)
c.Assert(m[0].Name, Equals, "foo/bar/bar")
c.Assert(m[1].Name, Equals, "foo/baz/qux")

m, err = idx.Glob("f*")
c.Assert(err, IsNil)
c.Assert(m, HasLen, 3)

m, err = idx.Glob("f*/baz/q*")
c.Assert(err, IsNil)
c.Assert(m, HasLen, 1)
}
186 changes: 186 additions & 0 deletions plumbing/format/index/match.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package index

import (
"path/filepath"
"runtime"
"unicode/utf8"
)

// match is filepath.Match with support to match fullpath and not only filenames
// code from:
// https://github.com/golang/go/blob/39852bf4cce6927e01d0136c7843f65a801738cb/src/path/filepath/match.go#L44-L224
func match(pattern, name string) (matched bool, err error) {
Pattern:
for len(pattern) > 0 {
var star bool
var chunk string
star, chunk, pattern = scanChunk(pattern)

// Look for match at current position.
t, ok, err := matchChunk(chunk, name)
// if we're the last chunk, make sure we've exhausted the name
// otherwise we'll give a false result even if we could still match
// using the star
if ok && (len(t) == 0 || len(pattern) > 0) {
name = t
continue
}
if err != nil {
return false, err
}
if star {
// Look for match skipping i+1 bytes.
// Cannot skip /.
for i := 0; i < len(name); i++ {
t, ok, err := matchChunk(chunk, name[i+1:])
if ok {
// if we're the last chunk, make sure we exhausted the name
if len(pattern) == 0 && len(t) > 0 {
continue
}
name = t
continue Pattern
}
if err != nil {
return false, err
}
}
}
return false, nil
}
return len(name) == 0, nil
}

// scanChunk gets the next segment of pattern, which is a non-star string
// possibly preceded by a star.
func scanChunk(pattern string) (star bool, chunk, rest string) {
for len(pattern) > 0 && pattern[0] == '*' {
pattern = pattern[1:]
star = true
}
inrange := false
var i int
Scan:
for i = 0; i < len(pattern); i++ {
switch pattern[i] {
case '\\':
if runtime.GOOS != "windows" {
// error check handled in matchChunk: bad pattern.
if i+1 < len(pattern) {
i++
}
}
case '[':
inrange = true
case ']':
inrange = false
case '*':
if !inrange {
break Scan
}
}
}
return star, pattern[0:i], pattern[i:]
}

// matchChunk checks whether chunk matches the beginning of s.
// If so, it returns the remainder of s (after the match).
// Chunk is all single-character operators: literals, char classes, and ?.
func matchChunk(chunk, s string) (rest string, ok bool, err error) {
for len(chunk) > 0 {
if len(s) == 0 {
return
}
switch chunk[0] {
case '[':
// character class
r, n := utf8.DecodeRuneInString(s)
s = s[n:]
chunk = chunk[1:]
// We can't end right after '[', we're expecting at least
// a closing bracket and possibly a caret.
if len(chunk) == 0 {
err = filepath.ErrBadPattern
return
}
// possibly negated
negated := chunk[0] == '^'
if negated {
chunk = chunk[1:]
}
// parse all ranges
match := false
nrange := 0
for {
if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 {
chunk = chunk[1:]
break
}
var lo, hi rune
if lo, chunk, err = getEsc(chunk); err != nil {
return
}
hi = lo
if chunk[0] == '-' {
if hi, chunk, err = getEsc(chunk[1:]); err != nil {
return
}
}
if lo <= r && r <= hi {
match = true
}
nrange++
}
if match == negated {
return
}

case '?':
_, n := utf8.DecodeRuneInString(s)
s = s[n:]
chunk = chunk[1:]

case '\\':
if runtime.GOOS != "windows" {
chunk = chunk[1:]
if len(chunk) == 0 {
err = filepath.ErrBadPattern
return
}
}
fallthrough

default:
if chunk[0] != s[0] {
return
}
s = s[1:]
chunk = chunk[1:]
}
}
return s, true, nil
}

// getEsc gets a possibly-escaped character from chunk, for a character class.
func getEsc(chunk string) (r rune, nchunk string, err error) {
if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' {
err = filepath.ErrBadPattern
return
}
if chunk[0] == '\\' && runtime.GOOS != "windows" {
chunk = chunk[1:]
if len(chunk) == 0 {
err = filepath.ErrBadPattern
return
}
}
r, n := utf8.DecodeRuneInString(chunk)
if r == utf8.RuneError && n == 1 {
err = filepath.ErrBadPattern
}
nchunk = chunk[n:]
if len(nchunk) == 0 {
err = filepath.ErrBadPattern
}
return
}
1 change: 0 additions & 1 deletion worktree_commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ func (w *Worktree) autoAddModifiedAndDeleted() error {
if _, err := w.Add(path); err != nil {
return err
}

}

return nil
Expand Down
Loading