diff --git a/walk/config.go b/walk/config.go index c7fc675c3..ec81ea5c8 100644 --- a/walk/config.go +++ b/walk/config.go @@ -25,7 +25,6 @@ import ( "os" "path" "strings" - "sync" "github.com/bazelbuild/bazel-gazelle/config" "github.com/bazelbuild/bazel-gazelle/rule" @@ -42,7 +41,6 @@ type walkConfig struct { excludes []string ignore bool follow []string - loadOnce *sync.Once } const walkName = "_walk" @@ -51,15 +49,15 @@ func getWalkConfig(c *config.Config) *walkConfig { return c.Exts[walkName].(*walkConfig) } -func (wc *walkConfig) isExcluded(rel, base string) bool { - if base == ".git" { +func (wc *walkConfig) isExcluded(p string) bool { + if p == ".git" { return true } - return matchAnyGlob(wc.excludes, path.Join(rel, base)) + return matchAnyGlob(wc.excludes, p) } -func (wc *walkConfig) shouldFollow(rel, base string) bool { - return matchAnyGlob(wc.follow, path.Join(rel, base)) +func (wc *walkConfig) shouldFollow(p string) bool { + return matchAnyGlob(wc.follow, p) } var _ config.Configurer = (*Configurer)(nil) @@ -67,7 +65,7 @@ var _ config.Configurer = (*Configurer)(nil) type Configurer struct{} func (*Configurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) { - wc := &walkConfig{loadOnce: &sync.Once{}} + wc := &walkConfig{} c.Exts[walkName] = wc fs.Var(&gzflag.MultiFlag{Values: &wc.excludes}, "exclude", "pattern that should be ignored (may be repeated)") } @@ -84,12 +82,6 @@ func (cr *Configurer) Configure(c *config.Config, rel string, f *rule.File) { *wcCopy = *wc wcCopy.ignore = false - wc.loadOnce.Do(func() { - if err := cr.loadBazelIgnore(c.RepoRoot, wcCopy); err != nil { - log.Printf("error loading .bazelignore: %v", err) - } - }) - if f != nil { for _, d := range f.Directives { switch d.Key { @@ -114,17 +106,26 @@ func (cr *Configurer) Configure(c *config.Config, rel string, f *rule.File) { c.Exts[walkName] = wcCopy } -func (c *Configurer) loadBazelIgnore(repoRoot string, wc *walkConfig) error { +type isBazelIgnoredFunc = func(string) bool + +func nothingIgnored(string) bool { return false } + +// Repo-wide bazelignore paths. +// Only applies to files on disk, not generated files. +// Does not support glob patterns. +func loadBazelIgnore(repoRoot string) (isBazelIgnoredFunc, error) { ignorePath := path.Join(repoRoot, ".bazelignore") file, err := os.Open(ignorePath) if errors.Is(err, fs.ErrNotExist) { - return nil + return nothingIgnored, nil } if err != nil { - return fmt.Errorf(".bazelignore exists but couldn't be read: %v", err) + return nothingIgnored, fmt.Errorf(".bazelignore exists but couldn't be read: %v", err) } defer file.Close() + excludes := make(map[string]struct{}) + scanner := bufio.NewScanner(file) for scanner.Scan() { ignore := strings.TrimSpace(scanner.Text()) @@ -142,9 +143,15 @@ func (c *Configurer) loadBazelIgnore(repoRoot string, wc *walkConfig) error { // the exclude matching won't work correctly. ignore = path.Clean(ignore) - wc.excludes = append(wc.excludes, ignore) + excludes[ignore] = struct{}{} + } + + isIgnored := func(p string) bool { + _, ok := excludes[p] + return ok } - return nil + + return isIgnored, nil } func checkPathMatchPattern(pattern string) error { diff --git a/walk/walk.go b/walk/walk.go index 81550a2fa..87db6d682 100644 --- a/walk/walk.go +++ b/walk/walk.go @@ -117,6 +117,11 @@ func Walk(c *config.Config, cexts []config.Configurer, dirs []string, mode Mode, updateRels := NewUpdateFilter(c.RepoRoot, dirs, mode) + isBazelIgnored, err := loadBazelIgnore(c.RepoRoot) + if err != nil { + log.Printf("error loading .bazelignore: %v", err) + } + var visit func(*config.Config, string, string, bool) visit = func(c *config.Config, dir, rel string, updateParent bool) { haveError := false @@ -141,17 +146,25 @@ func Walk(c *config.Config, cexts []config.Configurer, dirs []string, mode Mode, haveError = true } + if isBazelIgnored(rel) { + return + } + c = configure(cexts, knownDirectives, c, rel, f) wc := getWalkConfig(c) - if wc.isExcluded(rel, ".") { + if wc.isExcluded(rel) { return } var subdirs, regularFiles []string for _, ent := range ents { base := ent.Name() - ent := resolveFileInfo(wc, dir, rel, ent) + repoRel := path.Join(rel, base) + if isBazelIgnored(repoRel) || wc.isExcluded(repoRel) { + continue + } + ent := resolveFileInfo(wc, c.RepoRoot, repoRel, ent) switch { case ent == nil: continue @@ -317,27 +330,23 @@ func findGenFiles(wc *walkConfig, f *rule.File) []string { var genFiles []string for _, s := range strs { - if !wc.isExcluded(f.Pkg, s) { + if !wc.isExcluded(path.Join(f.Pkg, s)) { genFiles = append(genFiles, s) } } return genFiles } -func resolveFileInfo(wc *walkConfig, dir, rel string, ent fs.DirEntry) fs.DirEntry { - base := ent.Name() - if base == "" || wc.isExcluded(rel, base) { - return nil - } +func resolveFileInfo(wc *walkConfig, repoDir, repoRel string, ent fs.DirEntry) fs.DirEntry { if ent.Type()&os.ModeSymlink == 0 { // Not a symlink, use the original FileInfo. return ent } - if !wc.shouldFollow(rel, ent.Name()) { + if !wc.shouldFollow(repoRel) { // A symlink, but not one we should follow. return nil } - fi, err := os.Stat(path.Join(dir, base)) + fi, err := os.Stat(path.Join(repoDir, repoRel)) if err != nil { // A symlink, but not one we could resolve. return nil