Skip to content
Open
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
13 changes: 8 additions & 5 deletions files/file_walker.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func WalkFolder(
) *File {
var wg sync.WaitGroup
c := make(chan bool, 2*runtime.NumCPU())
root := walkSubFolderConcurrently(path, nil, ignoringReadDir(ignoreFunction, readDir), c, &wg, progress)
root := walkSubFolderConcurrently(path, nil, readDir, ignoreFunction, c, &wg, progress)
wg.Wait()
close(progress)
root.UpdateSize()
Expand All @@ -74,12 +74,14 @@ func walkSubFolderConcurrently(
path string,
parent *File,
readDir ReadDir,
ignoreFunction ShouldIgnoreFolder,
c chan bool,
wg *sync.WaitGroup,
progress chan<- int,
) *File {
result := &File{}
entries, err := readDir(path)
readDir2 := ignoringReadDir(ignoreFunction, readDir)
entries, err := readDir2(path)
if err != nil {
log.Println(err)
return result
Expand All @@ -90,21 +92,22 @@ func walkSubFolderConcurrently(
defer updateProgress(progress, &numSubFolders)
var mutex sync.Mutex
for _, entry := range entries {
fullPath := filepath.Join(path, entry.Name())
if entry.IsDir() {
numSubFolders++
subFolderPath := filepath.Join(path, entry.Name())
wg.Add(1)
go func() {
c <- true
subFolder := walkSubFolderConcurrently(subFolderPath, result, readDir, c, wg, progress)
subFolder := walkSubFolderConcurrently(subFolderPath, result, readDir, ignoreFunction, c, wg, progress)
mutex.Lock()
result.Files = append(result.Files, subFolder)
mutex.Unlock()
<-c
wg.Done()
}()
} else {
size := entry.Size()
} else if !ignoreFunction(fullPath) {
size := entry.Size()
file := &File{
entry.Name(),
result,
Expand Down
212 changes: 212 additions & 0 deletions files/gitmatch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
package files

//this is essentially copied from filepath.Match but adjusted to support matching separators with **

import (
"strings"
"runtime"
"os"
"errors"
"unicode/utf8"
)

const (
Separator = os.PathSeparator
ListSeparator = os.PathListSeparator
)

var ErrBadPattern = errors.New("syntax error in pattern")

// 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 = ErrBadPattern
return
}
if chunk[0] == '\\' && runtime.GOOS != "windows" {
chunk = chunk[1:]
if len(chunk) == 0 {
err = ErrBadPattern
return
}
}
r, n := utf8.DecodeRuneInString(chunk)
if r == utf8.RuneError && n == 1 {
err = ErrBadPattern
}
nchunk = chunk[n:]
if len(nchunk) == 0 {
err = ErrBadPattern
}
return
}


// 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) {
// failed records whether the match has failed.
// After the match fails, the loop continues on processing chunk,
// checking that the pattern is well-formed but no longer reading s.
failed := false
for len(chunk) > 0 {
if !failed && len(s) == 0 {
failed = true
}
switch chunk[0] {
case '[':
// character class
var r rune
if !failed {
var n int
r, n = utf8.DecodeRuneInString(s)
s = s[n:]
}
chunk = chunk[1:]
// possibly negated
negated := false
if len(chunk) > 0 && chunk[0] == '^' {
negated = true
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 "", false, err
}
hi = lo
if chunk[0] == '-' {
if hi, chunk, err = getEsc(chunk[1:]); err != nil {
return "", false, err
}
}
if lo <= r && r <= hi {
match = true
}
nrange++
}
if match == negated {
failed = true
}

case '?':
if !failed {
if s[0] == Separator {
failed = true
}
_, n := utf8.DecodeRuneInString(s)
s = s[n:]
}
chunk = chunk[1:]

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

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

func scanChunk(pattern string) (star int, chunk, rest string) {
for len(pattern) > 0 && pattern[0] == '*' {
pattern = pattern[1:]
star += 1
}
// for star >= 2 && pattern[0] == '/' {
// pattern = pattern[1:]
// }
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:]
}

func Match(pattern, name string) (matched bool, err error) {
Pattern:
for len(pattern) > 0 {
var star int
var chunk string
star, chunk, pattern = scanChunk(pattern)
if star >= 1 && chunk == "" {
// Trailing * matches rest of string unless it has a /.
return !strings.Contains(name, string(Separator)), nil
}
// 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 >= 1 {
// Look for match skipping i+1 bytes.
// Cannot skip /.
for i := 0; i < len(name) && ((name[i] != Separator && star == 1) || star >= 2) ; 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
}

14 changes: 7 additions & 7 deletions ignore.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ func readIgnoreFile() []string {
}

func ignoreBasedOnIgnoreFile(ignoreFile []string) files.ShouldIgnoreFolder {
ignoredFolders := map[string]struct{}{}
for _, line := range ignoreFile {
ignoredFolders[line] = struct{}{}
}
return func(absolutePath string) bool {
_, name := filepath.Split(absolutePath)
_, ignored := ignoredFolders[name]
return ignored
for _, pattern := range ignoreFile {
matchResult, err := files.Match(pattern, absolutePath)
if matchResult && err == nil {
return true
}
}
return false
}
}
37 changes: 33 additions & 4 deletions ignore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,37 @@ import (
)

func TestIgnoreBasedOnIgnoreFile(t *testing.T) {
ignored := []string{"node_modules"}
ignoreFunction := ignoreBasedOnIgnoreFile(ignored)
assert.True(t, ignoreFunction("something/node_modules"))
assert.False(t, ignoreFunction("something/notIgnored"))
//these tests might fail on windows
tests := []struct {
ignored []string
file string
match bool
}{
{[]string{"**/what/test"}, "what/what/test", true},
{[]string{"**node_modules"}, "something/node_modules", true},
{[]string{"**node_modules"}, "something/notIgnored", false},
{[]string{"**/what/test"}, "/home/user/test", false},
{[]string{"**user/test"}, "user/test", true},
{[]string{"**test"}, "test", true},
{[]string{"**png"}, "path/to/test.png", true},
{[]string{"**folder**png"}, "path/to/folder/test.png", true},
{[]string{"**folder**png"}, "path/folder/to/test.png", true},
{[]string{"**/*folder*/**png"}, "path/hellofolder/to/test.png", true},
{[]string{"**/*folder*/**png"}, "path/folder2/to/test.png", true},
{[]string{"**/*folder*/**png"}, "path/folder/to/test.png", true},
{[]string{"**/*folder*/**png"}, "path/foler/to/test.png", false},
{[]string{"**/test.png"}, "path/to/test.png", true},
{[]string{"*png"}, "test.png", true},
{[]string{"dir/*png"}, "dir/test.png", true},
{[]string{"*dir*/*png"}, "testdir2/test.png", true},
{[]string{"*dir*/*png"}, "testdir/test.png", true},
{[]string{"*dir*dir*/*png"}, "dirtestdir/test.png", true},
{[]string{"*dir*dir*/*png"}, "dir/testdir/test.png", false},
{[]string{"wrong/pattern"}, "test", false},
}

for _, tt := range tests {
ignoreFunction := ignoreBasedOnIgnoreFile(tt.ignored)
assert.True(t, tt.match == ignoreFunction(tt.file))
}
}