aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/format/gitattributes/pattern.go
blob: d961aba9cd877f54fa2ff9f28c669eccaba3b393 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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 {
		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
}