diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2021-10-18 19:00:03 +1100 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2021-10-19 09:02:42 +1100 |
commit | ca7252ef8ee26affdc2c74f05c9c20196a8d571b (patch) | |
tree | 7323ca472558a435f01ae62e123cc2576e1959b3 /vendor/github.com/jesseduffield/minimal/gitignore/gitignore.go | |
parent | a496858c629e3c6241b51cf99cd38d6fa1b787bc (diff) |
suggest files when picking a path to filter on
async fetching of suggestions
remove limit
cache the trie for future use
more
more
Diffstat (limited to 'vendor/github.com/jesseduffield/minimal/gitignore/gitignore.go')
-rw-r--r-- | vendor/github.com/jesseduffield/minimal/gitignore/gitignore.go | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/vendor/github.com/jesseduffield/minimal/gitignore/gitignore.go b/vendor/github.com/jesseduffield/minimal/gitignore/gitignore.go new file mode 100644 index 000000000..740f77d51 --- /dev/null +++ b/vendor/github.com/jesseduffield/minimal/gitignore/gitignore.go @@ -0,0 +1,313 @@ +// Copyright 2018 iriri. All rights reserved. Use of this source code is +// governed by a BSD-style license which can be found in the LICENSE file. + +// Package gitignore can be used to parse .gitignore-style files into lists of +// globs that can be used to test against paths or selectively walk a file +// tree. Gobwas's glob package is used for matching because it is faster than +// using regexp, which is overkill, and supports globstars (**), unlike +// filepath.Match. +package gitignore + +import ( + "bufio" + "errors" + "os" + "os/user" + "path/filepath" + "strings" + + "github.com/gobwas/glob" +) + +type ignoreFile struct { + globs []glob.Glob + abspath []string +} + +type IgnoreList struct { + files []ignoreFile + cwd []string +} + +func toSplit(path string) []string { + return strings.Split(filepath.ToSlash(path), "/") +} + +func fromSplit(path []string) string { + return filepath.FromSlash(strings.Join(path, "/")) +} + +// New creates a new ignore list. +func New() (IgnoreList, error) { + cwd, err := filepath.Abs(".") + if err != nil { + return IgnoreList{}, err + } + files := make([]ignoreFile, 1, 4) + files[0].globs = make([]glob.Glob, 0, 16) + return IgnoreList{ + files, + toSplit(cwd), + }, nil +} + +// From creates a new ignore list and populates the first entry with the +// contents of the specified file. +func From(path string) (IgnoreList, error) { + ign, err := New() + if err == nil { + err = ign.append(path, nil) + } + return ign, err +} + +// FromGit finds the root directory of the current git repository and creates a +// new ignore list with the contents of all .gitignore files in that git +// repository. +func FromGit() (IgnoreList, error) { + ign, err := New() + if err == nil { + err = ign.AppendGit() + } + return ign, err +} + +func clean(s string) string { + i := len(s) - 1 + for ; i >= 0; i-- { + if s[i] != ' ' || i > 0 && s[i-1] == '\\' { + return s[:i+1] + } + } + return "" +} + +// AppendGlob appends a single glob as a new entry in the ignore list. The root +// (relevant for matching patterns that begin with "/") is assumed to be the +// current working directory. +func (ign *IgnoreList) AppendGlob(s string) error { + g, err := glob.Compile(clean(s), '/') + if err == nil { + ign.files[0].globs = append(ign.files[0].globs, g) + } + return err +} + +func toRelpath(s string, dir, cwd []string) string { + if s != "" { + if s[0] != '/' { + return s + } + if dir == nil || cwd == nil { + return s[1:] + } + dir = append(dir, toSplit(s[1:])...) + } + + i := 0 + min := len(cwd) + if len(dir) < min { + min = len(dir) + } + for ; i < min; i++ { + if dir[i] != cwd[i] { + break + } + } + if i == min && len(cwd) == len(dir) { + return "." + } + + ss := make([]string, (len(cwd)-i)+(len(dir)-i)) + j := 0 + for ; j < len(cwd)-i; j++ { + ss[j] = ".." + } + for k := 0; j < len(ss); j, k = j+1, k+1 { + ss[j] = dir[i+k] + } + return fromSplit(ss) +} + +func (ign *IgnoreList) append(path string, dir []string) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + + var ignf *ignoreFile + if dir != nil { + ignf = &ign.files[0] + } else { + d, err := filepath.Abs(filepath.Dir(path)) + if err != nil { + return err + } + if d != fromSplit(ign.cwd) { + dir = toSplit(d) + ignf = &ignoreFile{ + make([]glob.Glob, 0, 16), + dir, + } + } else { + ignf = &ign.files[0] + } + } + scn := bufio.NewScanner(bufio.NewReader(f)) + for scn.Scan() { + s := scn.Text() + if s == "" || s[0] == '#' { + continue + } + g, err := glob.Compile(toRelpath(clean(s), dir, ign.cwd), '/') + if err != nil { + return err + } + ignf.globs = append(ignf.globs, g) + } + ign.files = append(ign.files, *ignf) + return nil +} + +// Append appends the globs in the specified file to the ignore list. Files are +// expected to have the same format as .gitignore files. +func (ign *IgnoreList) Append(path string) error { + return ign.append(path, nil) +} + +func exists(path string) bool { + _, err := os.Stat(path) + return !os.IsNotExist(err) +} + +func findGitRoot(cwd []string) (string, error) { + p := fromSplit(cwd) + for !exists(p + "/.git") { + if len(cwd) == 1 { + return "", errors.New("not in a git repository") + } + cwd = cwd[:len(cwd)-1] + p = fromSplit(cwd) + } + return p, nil +} + +func (ign *IgnoreList) appendAll(fname, root string) error { + return filepath.Walk( + root, + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if filepath.Base(path) == fname { + ign.append(path, nil) + } + return nil + }) +} + +// AppendGit finds the root directory of the current git repository and appends +// the contents of all .gitignore files in that git repository to the ignore +// list. +func (ign *IgnoreList) AppendGit() error { + gitRoot, err := findGitRoot(ign.cwd) + if err != nil { + return err + } + if err = ign.AppendGlob(".git"); err != nil { + return err + } + usr, err := user.Current() + if err != nil { + return err + } + if gg := filepath.Join(usr.HomeDir, ".gitignore_global"); exists(gg) { + if err = ign.append(gg, toSplit(gitRoot)); err != nil { + return err + } + } + return ign.appendAll(".gitignore", gitRoot) +} + +func isPrefix(abspath, dir []string) bool { + if len(abspath) > len(dir) { + return false + } + for i := range abspath { + if abspath[i] != dir[i] { + return false + } + } + return true +} + +func (ign *IgnoreList) match(path string, info os.FileInfo) bool { + if path == "." { + return false + } + ss := make([]string, 0, 4) + base := filepath.Base(path) + ss = append(ss, path) + if base != path { + ss = append(ss, base) + } else { + ss = append(ss, "./"+path) + } + if info != nil && info.IsDir() { + ss = append(ss, path+"/") + if base != path { + ss = append(ss, base+"/") + } else { + ss = append(ss, "./"+path+"/") + } + } + + d, err := filepath.Abs(filepath.Dir(path)) + if err != nil { + return false + } + dir := toSplit(d) + for _, f := range ign.files { + if isPrefix(f.abspath, dir) || len(f.abspath) == 0 { + for _, g := range f.globs { + for _, s := range ss { + if g.Match(s) { + return true + } + } + } + } + } + return false +} + +// Match returns whether any of the globs in the ignore list match the +// specified path. Uses the same matching rules as .gitignore files. +func (ign *IgnoreList) Match(path string) bool { + return ign.match(path, nil) +} + +// Walk walks the file tree with the specified root and calls fn on each file +// or directory. Files and directories that match any of the globs in the +// ignore list are skipped. +func (ign *IgnoreList) Walk(root string, fn filepath.WalkFunc) error { + abs, err := filepath.Abs(root) + if err != nil { + return err + } + return filepath.Walk( + toRelpath("", toSplit(abs), ign.cwd), + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if ign.match(path, info) { + if info.IsDir() { + return filepath.SkipDir + } + return err + } + return fn(path, info, err) + }) +} |