aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/format/gitattributes/pattern.go
diff options
context:
space:
mode:
authorArran Walker <arran.walker@fiveturns.org>2019-04-23 10:50:46 +0000
committerArran Walker <arran.walker@fiveturns.org>2019-04-24 09:32:18 +0000
commit86bdbfbf45a0c13aca146955a3325207ebd66c75 (patch)
treec0ff62fa4f6778c3e67f992692dc486ec03bdc14 /plumbing/format/gitattributes/pattern.go
parenteb243ba9a55ac029ab3f9b15157920c46e24078b (diff)
downloadgo-git-86bdbfbf45a0c13aca146955a3325207ebd66c75.tar.gz
plumbing: format/gitattributes support
Implements gitattributes parsing, matching and attribute extraction. Signed-off-by: Arran Walker <arran.walker@fiveturns.org>
Diffstat (limited to 'plumbing/format/gitattributes/pattern.go')
-rw-r--r--plumbing/format/gitattributes/pattern.go101
1 files changed, 101 insertions, 0 deletions
diff --git a/plumbing/format/gitattributes/pattern.go b/plumbing/format/gitattributes/pattern.go
new file mode 100644
index 0000000..c5ca0c7
--- /dev/null
+++ b/plumbing/format/gitattributes/pattern.go
@@ -0,0 +1,101 @@
+package gitattributes
+
+import (
+ "path/filepath"
+ "strings"
+)
+
+const (
+ patternDirSep = "/"
+ zeroToManyDirs = "**"
+)
+
+// Pattern defines a gitattributes pattern.
+type Pattern interface {
+ // Match matches the given path to the pattern.
+ Match(path []string) bool
+}
+
+type pattern struct {
+ domain []string
+ pattern []string
+}
+
+// ParsePattern parses a gitattributes pattern string into the Pattern
+// structure.
+func ParsePattern(p string, domain []string) Pattern {
+ return &pattern{
+ domain: domain,
+ pattern: strings.Split(p, patternDirSep),
+ }
+}
+
+func (p *pattern) Match(path []string) bool {
+ if len(path) <= len(p.domain) {
+ return false
+ }
+ for i, e := range p.domain {
+ if path[i] != e {
+ return false
+ }
+ }
+
+ if len(p.pattern) == 1 {
+ // for a simple rule, .gitattribute matching rules differs from
+ // .gitignore and only the last part of the path is considered.
+ path = path[len(path)-1:]
+ } else {
+ path = path[len(p.domain):]
+ }
+
+ pattern := p.pattern
+ var match, doublestar bool
+ var err error
+ for _, part := range path {
+ // skip empty
+ if pattern[0] == "" {
+ pattern = pattern[1:]
+ }
+
+ // eat doublestar
+ if pattern[0] == zeroToManyDirs {
+ pattern = pattern[1:]
+ if len(pattern) == 0 {
+ return true
+ }
+ doublestar = true
+ }
+
+ switch true {
+ case strings.Contains(pattern[0], "**"):
+ return false
+
+ // keep going down the path until we hit a match
+ case doublestar:
+ match, err = filepath.Match(pattern[0], part)
+ if err != nil {
+ return false
+ }
+
+ if match {
+ doublestar = false
+ pattern = pattern[1:]
+ }
+
+ default:
+ match, err = filepath.Match(pattern[0], part)
+ if err != nil {
+ return false
+ }
+ if !match {
+ return false
+ }
+ pattern = pattern[1:]
+ }
+ }
+
+ if len(pattern) > 0 {
+ return false
+ }
+ return match
+}