-
Notifications
You must be signed in to change notification settings - Fork 0
/
context.go
169 lines (144 loc) · 4.43 KB
/
context.go
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package filter
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/containerd/continuity"
)
var _ continuity.Context = (*DockerIgnoreContext)(nil)
// DockerIgnoreContext wrap a continuity.Context that filters resources based on a .dockerignore file.
type DockerIgnoreContext struct {
root string
dockerignore *DockerIgnoreFilter
inner continuity.Context
}
// NewDockerIgnoreContext creates a new DockerIgnoreContext with a dockerignore filter.
func NewDockerIgnoreContext(root string, dockerignore *DockerIgnoreFilter, opts continuity.ContextOptions) (*DockerIgnoreContext, error) {
inner, err := continuity.NewContextWithOptions(root, opts)
if err != nil {
return nil, err
}
return &DockerIgnoreContext{
root: root,
dockerignore: dockerignore,
inner: inner,
}, nil
}
// Apply passes through to the inner context.
func (c *DockerIgnoreContext) Apply(r continuity.Resource) error { return c.inner.Apply(r) }
// Verify passes through to the inner context.
func (c *DockerIgnoreContext) Verify(r continuity.Resource) error { return c.inner.Verify(r) }
// Resource passes through to the inner context.
func (c *DockerIgnoreContext) Resource(p string, fi os.FileInfo) (continuity.Resource, error) {
return c.inner.Resource(p, fi)
}
// Walk applies a dockerignore to filter files from a directory tree.
// Files that are not filtered by the dockerignore are passed through to the inner context.
//
// This was transliterated from buildkit. It has *BIG* assumption that it is ok for the same
// directory to be visited multiple times.
func (c *DockerIgnoreContext) Walk(walkFn filepath.WalkFunc) error {
type visitedDir struct {
info os.FileInfo
path string
walkPath string
pathWithSep string
excludeMatchInfo []bool
visited bool
}
var (
parentDirs []visitedDir
)
// This is transliterated from buildkit. It can generate more than one match per match
// as parent directories are potentially revisited. This means it has an _assumption_
// that any state that is tracked per match is inserted into maps to ensure uniqueness.
walk := func(walkPath string, info fs.FileInfo, walkErr error) error {
path, err := filepath.Rel(c.root, filepath.Join(c.root, walkPath))
if err != nil {
return err
}
// Skip root
if path == "." {
return nil
}
skip := false
var parentExcludeMatchInfo []bool
if len(parentDirs) != 0 {
parentExcludeMatchInfo = parentDirs[len(parentDirs)-1].excludeMatchInfo
}
matches, matchInfo, err := c.dockerignore.Matches(path, parentExcludeMatchInfo)
if err != nil {
return fmt.Errorf("failed to match patterns: %w", err)
}
for len(parentDirs) != 0 {
lastParentDir := parentDirs[len(parentDirs)-1].pathWithSep
if strings.HasPrefix(path, lastParentDir) {
break
}
parentDirs = parentDirs[:len(parentDirs)-1]
}
var dir visitedDir
isDir := info != nil && info.IsDir()
if isDir {
dir = visitedDir{
info: info,
path: path,
walkPath: walkPath,
pathWithSep: path + string(filepath.Separator),
excludeMatchInfo: matchInfo,
}
}
if matches {
if isDir && c.dockerignore.OnlySimplePatterns {
// Optimization: we can skip walking this dir if no
// exceptions to exclude patterns could match anything
// inside it.
if !c.dockerignore.HasExclusions {
return filepath.SkipDir
}
dirSlash := path + string(filepath.Separator)
for _, pat := range c.dockerignore.Patterns {
if !pat.Exclusion {
continue
}
patStr := patternWithoutTrailingGlob(pat) + string(filepath.Separator)
if strings.HasPrefix(patStr, dirSlash) {
goto passedExcludeFilter
}
}
return filepath.SkipDir
}
passedExcludeFilter:
skip = true
}
if walkErr != nil {
if skip && errors.Is(walkErr, os.ErrPermission) {
return nil
}
return walkErr
}
if isDir {
parentDirs = append(parentDirs, dir)
}
if skip {
return nil
}
// This revisits all parent directories just so that exclusions with ignored parent directories
// have their parent directories included in the manifest.
for i, parentDir := range parentDirs {
if parentDir.visited {
continue
}
err := walkFn(parentDir.walkPath, parentDir.info, nil)
if err != nil {
return err
}
parentDirs[i].visited = true
}
return walkFn(walkPath, info, nil)
}
return c.inner.Walk(walk)
}