package main import ( "bufio" "os" "path/filepath" "strings" ignore "github.com/sabhiram/go-gitignore" ) type gitignoreLayer struct { dir string posRules *ignore.GitIgnore negRules *ignore.GitIgnore } type GitignoreStack struct { rootDir string layers []gitignoreLayer } func NewGitignoreStack(rootDir string) *GitignoreStack { stack := &GitignoreStack{rootDir: rootDir} stack.loadAndPush(rootDir) return stack } func (s *GitignoreStack) Push(dir string) { s.loadAndPush(dir) } func (s *GitignoreStack) Pop() { if len(s.layers) > 0 { s.layers = s.layers[:len(s.layers)-1] } } func (s *GitignoreStack) IsIgnored(relPath string) bool { absPath := filepath.Join(s.rootDir, relPath) result := false for _, layer := range s.layers { childRel, err := filepath.Rel(layer.dir, absPath) if err != nil { continue } childRel = filepath.ToSlash(childRel) if strings.HasPrefix(childRel, "../") { continue } posMatch := layer.posRules != nil && (layer.posRules.MatchesPath(childRel) || layer.posRules.MatchesPath(childRel+"/")) negMatch := layer.negRules != nil && (layer.negRules.MatchesPath(childRel) || layer.negRules.MatchesPath(childRel+"/")) if negMatch { result = false } else if posMatch { result = true } } return result } func (s *GitignoreStack) ParentIgnored(dir string) bool { parent := filepath.Dir(dir) if parent == dir { return false } parentRel, err := filepath.Rel(s.rootDir, parent) if err != nil { return false } if parentRel == "." { return false } return s.IsIgnored(parentRel) } func (s *GitignoreStack) loadAndPush(dir string) { gitignorePath := filepath.Join(dir, ".gitignore") if _, err := os.Stat(gitignorePath); os.IsNotExist(err) { return } f, err := os.Open(gitignorePath) if err != nil { return } defer f.Close() var posLines, negLines []string scanner := bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() trimmed := strings.TrimSpace(line) if trimmed == "" || strings.HasPrefix(trimmed, "#") { continue } if strings.HasPrefix(trimmed, "!") { negLines = append(negLines, trimmed[1:]) } else { posLines = append(posLines, trimmed) } } var posRules, negRules *ignore.GitIgnore if len(posLines) > 0 { posRules = ignore.CompileIgnoreLines(posLines...) } if len(negLines) > 0 { negRules = ignore.CompileIgnoreLines(negLines...) } s.layers = append(s.layers, gitignoreLayer{ dir: dir, posRules: posRules, negRules: negRules, }) } func hasGitignore(dir string) bool { _, err := os.Stat(filepath.Join(dir, ".gitignore")) return err == nil } func isGitDir(path string) bool { return path == ".git" || strings.HasPrefix(path, ".git"+string(filepath.Separator)) }