generated from TBD54566975/tbd-project-template
-
Notifications
You must be signed in to change notification settings - Fork 5
/
walk.go
135 lines (121 loc) 路 3.11 KB
/
walk.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
package buildengine
import (
"bufio"
"errors"
"io/fs"
"os"
"path"
"path/filepath"
"strings"
"github.com/bmatcuk/doublestar/v4"
"github.com/TBD54566975/ftl/internal"
)
// ErrSkip can be returned by the WalkDir callback to skip a file or directory.
var ErrSkip = errors.New("skip directory")
// WalkDir performs a depth-first walk of the file tree rooted at dir, calling
// fn for each file or directory in the tree, including dir.
//
// It will adhere to .gitignore files. The callback "fn" can return ErrSkip to
// skip recursion.
func WalkDir(dir string, fn func(path string, d fs.DirEntry) error) error {
return walkDir(dir, initGitIgnore(dir), fn)
}
// Depth-first walk of dir executing fn after each entry.
func walkDir(dir string, ignores []string, fn func(path string, d fs.DirEntry) error) error {
dirInfo, err := os.Stat(dir)
if err != nil {
return err
}
if err = fn(dir, fs.FileInfoToDirEntry(dirInfo)); err != nil {
if errors.Is(err, ErrSkip) {
return nil
}
return err
}
entries, err := os.ReadDir(dir)
if err != nil {
return err
}
var dirs []os.DirEntry
// Process files first, then recurse into directories.
for _, entry := range entries {
fullPath := filepath.Join(dir, entry.Name())
// Check if the path matches any ignore pattern
shouldIgnore := false
for _, pattern := range ignores {
match, err := doublestar.PathMatch(pattern, fullPath)
if err != nil {
return err
}
if match {
shouldIgnore = true
break
}
}
if shouldIgnore {
continue // Skip this entry
}
if entry.IsDir() {
dirs = append(dirs, entry)
} else {
if err = fn(fullPath, entry); err != nil {
if errors.Is(err, ErrSkip) {
// If errSkip is found in a file, skip the remaining files in this directory
return nil
}
return err
}
}
}
// Then, recurse into subdirectories
for _, dirEntry := range dirs {
dirPath := filepath.Join(dir, dirEntry.Name())
ignores = append(ignores, loadGitIgnore(dirPath)...)
if err := walkDir(dirPath, ignores, fn); err != nil {
if errors.Is(err, ErrSkip) {
return ErrSkip // Propagate errSkip upwards to stop this branch of recursion
}
return err
}
}
return nil
}
func initGitIgnore(dir string) []string {
ignore := []string{
"**/.*",
"**/.*/**",
}
home, err := os.UserHomeDir()
if err == nil {
ignore = append(ignore, loadGitIgnore(home)...)
}
gitRoot := internal.GitRoot(dir)
if gitRoot != "" {
for current := dir; strings.HasPrefix(current, gitRoot); current = path.Dir(current) {
ignore = append(ignore, loadGitIgnore(current)...)
}
}
return ignore
}
func loadGitIgnore(dir string) []string {
r, err := os.Open(path.Join(dir, ".gitignore"))
if err != nil {
return nil
}
ignore := []string{}
lr := bufio.NewScanner(r)
for lr.Scan() {
line := lr.Text()
line = strings.TrimSpace(line)
if line == "" || line[0] == '#' || line[0] == '!' { // We don't support negation.
continue
}
if strings.HasSuffix(line, "/") {
line = path.Join("**", line, "**/*")
} else if !strings.ContainsRune(line, '/') {
line = path.Join("**", line)
}
ignore = append(ignore, line)
}
return ignore
}