ignore.go

v0.6.0
Doc Versions Source
1
package sync
2
3
import (
4
	"path"
5
	"strings"
6
)
7
8
// matchIgnore reports whether rel (a slash-separated path relative to the sync
9
// root) is covered by a single gitignore-style pattern.
10
//
11
// This is a deliberate subset of gitignore semantics — the part deploy configs
12
// actually use — not a full implementation. Supported:
13
//
14
//   - Trailing "/" marks a directory pattern: "pgdata/" matches the directory
15
//     and everything beneath it, but never a plain file named "pgdata".
16
//   - A pattern containing "/" is anchored to the sync root, as in gitignore:
17
//     "master/*.log" matches "master/x.log" but not "a/master/x.log".
18
//   - A pattern with no "/" matches at any depth: "__pycache__/" catches
19
//     "a/b/__pycache__/x.pyc", and "*.pem" catches "certs/key.pem".
20
//
21
// Not supported (no deploy.yml in this repo uses them): "!" negation, a leading
22
// "/" anchor, and "**". A "!" pattern is ignored outright rather than silently
23
// treated as a literal — see ignoreSet.
24
func matchIgnore(pattern, rel string) bool {
25
	pattern = strings.TrimSpace(pattern)
26
	if pattern == "" || strings.HasPrefix(pattern, "#") {
27
		return false
28
	}
29
	dirOnly := strings.HasSuffix(pattern, "/")
30
	pattern = strings.Trim(pattern, "/")
31
	if pattern == "" {
32
		return false
33
	}
34
35
	if strings.Contains(pattern, "/") {
36
		// Anchored: match the whole path, or any ancestor directory of it so a
37
		// directory pattern covers everything beneath it.
38
		if !dirOnly {
39
			if ok, _ := path.Match(pattern, rel); ok {
40
				return true
41
			}
42
		}
43
		for dir := path.Dir(rel); dir != "." && dir != "/"; dir = path.Dir(dir) {
44
			if ok, _ := path.Match(pattern, dir); ok {
45
				return true
46
			}
47
		}
48
		return false
49
	}
50
51
	// Unanchored: test every path segment. A directory pattern may only match a
52
	// segment that actually is a directory — i.e. not the final one.
53
	segs := strings.Split(rel, "/")
54
	for i, seg := range segs {
55
		isDir := i < len(segs)-1
56
		if dirOnly && !isDir {
57
			continue
58
		}
59
		if ok, _ := path.Match(pattern, seg); ok {
60
			return true
61
		}
62
	}
63
	return false
64
}
65
66
// ignoreSet is a compiled list of gitignore-style patterns.
67
type ignoreSet []string
68
69
// newIgnoreSet keeps only the patterns matchIgnore can honour. Negation ("!")
70
// is dropped rather than misapplied: treating "!keep.log" as a literal would
71
// exclude the very path the author meant to re-include, so an unsupported
72
// pattern must never widen the exclusion.
73
func newIgnoreSet(patterns []string) ignoreSet {
74
	out := make(ignoreSet, 0, len(patterns))
75
	for _, p := range patterns {
76
		p = strings.TrimSpace(p)
77
		if p == "" || strings.HasPrefix(p, "#") || strings.HasPrefix(p, "!") {
78
			continue
79
		}
80
		out = append(out, p)
81
	}
82
	return out
83
}
84
85
// match reports whether rel is covered by any pattern in the set.
86
func (s ignoreSet) match(rel string) bool {
87
	for _, p := range s {
88
		if matchIgnore(p, rel) {
89
			return true
90
		}
91
	}
92
	return false
93
}
94

Source Files