github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/dockerignore/ignore.go (about) 1 package dockerignore 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strings" 8 9 "github.com/moby/buildkit/frontend/dockerfile/dockerignore" 10 11 tiltDockerignore "github.com/tilt-dev/dockerignore" 12 "github.com/tilt-dev/tilt/internal/ospath" 13 ) 14 15 type dockerPathMatcher struct { 16 repoRoot string 17 matcher *tiltDockerignore.PatternMatcher 18 } 19 20 func (i dockerPathMatcher) Matches(f string) (bool, error) { 21 if !filepath.IsAbs(f) { 22 f = filepath.Join(i.repoRoot, f) 23 } 24 return i.matcher.Matches(f) 25 } 26 27 func (i dockerPathMatcher) MatchesEntireDir(f string) (bool, error) { 28 matches, err := i.Matches(f) 29 if !matches || err != nil { 30 return matches, err 31 } 32 33 // We match the dir, but we might exclude files underneath it. 34 if i.matcher.Exclusions() { 35 for _, pattern := range i.matcher.Patterns() { 36 if !pattern.Exclusion() { 37 continue 38 } 39 40 // An exclusion pattern handles the case where the user 41 // does something like: 42 // 43 // * 44 // !path/to/include 45 // !pkg/**/*.go 46 // 47 // where they ignore all files and select the files 48 // they want to exclude. 49 // 50 // Because MatchesEntireDir is an optimization, we err 51 // on the side of crawling too much. 52 patternString := pattern.String() 53 54 // Handle the case where the pattern matches a subfile of the 55 // current directory. 56 if ospath.IsChild(f, patternString) { 57 // Found an exclusion match -- we don't match this whole dir 58 return false, nil 59 } 60 61 // Handle the case where the pattern has a glob that matches 62 // arbitrary files, and the glob could match a subfile of 63 // the current directory. 64 prefix := definitePatternPrefix(patternString) 65 if prefix != patternString && ospath.IsChild(prefix, f) { 66 return false, nil 67 } 68 } 69 return true, nil 70 } 71 return true, nil 72 } 73 74 // Truncate the pattern to just the prefxi that's a concrete directory. 75 func definitePatternPrefix(p string) string { 76 if !strings.Contains(p, "*") { 77 return p 78 } 79 parts := strings.Split(p, string(filepath.Separator)) 80 for i, part := range parts { 81 if strings.Contains(part, "*") { 82 if i == 0 { 83 return "." 84 } 85 return strings.Join(parts[0:i], string(filepath.Separator)) 86 } 87 } 88 return p 89 } 90 91 func NewDockerIgnoreTester(repoRoot string) (*dockerPathMatcher, error) { 92 absRoot, err := filepath.Abs(repoRoot) 93 if err != nil { 94 return nil, err 95 } 96 97 patterns, err := readDockerignorePatterns(absRoot) 98 if err != nil { 99 return nil, err 100 } 101 102 return NewDockerPatternMatcher(absRoot, patterns) 103 } 104 105 // Make all the patterns use absolute paths. 106 func absPatterns(absRoot string, patterns []string) []string { 107 absPatterns := make([]string, 0, len(patterns)) 108 for _, p := range patterns { 109 // The pattern parsing here is loosely adapted from fileutils' NewPatternMatcher 110 p = strings.TrimSpace(p) 111 if p == "" { 112 continue 113 } 114 p = filepath.Clean(p) 115 116 pPath := p 117 isExclusion := false 118 if p[0] == '!' { 119 pPath = p[1:] 120 isExclusion = true 121 } 122 123 if !filepath.IsAbs(pPath) { 124 pPath = filepath.Join(absRoot, pPath) 125 } 126 absPattern := pPath 127 if isExclusion { 128 absPattern = fmt.Sprintf("!%s", pPath) 129 } 130 absPatterns = append(absPatterns, absPattern) 131 } 132 return absPatterns 133 } 134 135 func NewDockerPatternMatcher(repoRoot string, patterns []string) (*dockerPathMatcher, error) { 136 absRoot, err := filepath.Abs(repoRoot) 137 if err != nil { 138 return nil, err 139 } 140 141 pm, err := tiltDockerignore.NewPatternMatcher(absPatterns(absRoot, patterns)) 142 if err != nil { 143 return nil, err 144 } 145 146 return &dockerPathMatcher{ 147 repoRoot: absRoot, 148 matcher: pm, 149 }, nil 150 } 151 152 func readDockerignorePatterns(repoRoot string) ([]string, error) { 153 var excludes []string 154 155 f, err := os.Open(filepath.Join(repoRoot, ".dockerignore")) 156 switch { 157 case os.IsNotExist(err): 158 return excludes, nil 159 case err != nil: 160 return nil, err 161 } 162 defer func() { _ = f.Close() }() 163 164 return dockerignore.ReadAll(f) 165 } 166 167 func DockerIgnoreTesterFromContents(repoRoot string, contents string) (*dockerPathMatcher, error) { 168 patterns, err := dockerignore.ReadAll(strings.NewReader(contents)) 169 if err != nil { 170 return nil, err 171 } 172 173 return NewDockerPatternMatcher(repoRoot, patterns) 174 }