Skip to content

Commit

Permalink
fix: incorrect files when filter conflicts with gitignore
Browse files Browse the repository at this point in the history
  • Loading branch information
Broderick-Westrope committed Jan 15, 2025
1 parent 1c69521 commit 4c83719
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 20 deletions.
37 changes: 19 additions & 18 deletions internal/traverse.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ type PathInfo struct {

// TraverseDirectory traverses the directory and collects path information using the filter package
func TraverseDirectory(dir string, filterPatterns, gitignorePaths []string) ([]PathInfo, error) {
// Create the filter from filter patterns and negated gitignore file patterns.
// Create the filter from filter patterns.
f := filter.CompileFilterPatterns(filterPatterns...)
// Create the gitignore filter from gitignore file paths.
gi := new(filter.Filter)
for _, giPath := range gitignorePaths {
giFilter, err := filter.CompileFilterPatternFile(giPath)
tempFilter, err := filter.CompileFilterPatternFile(giPath)
if err != nil {
return nil, fmt.Errorf("compiling patterns from gitignore file %q: %w", giPath, err)
}
giFilter.NegateAll()
f.Merge(giFilter)
gi.MergeWithPrecedence(tempFilter)
}

paths := make([]PathInfo, 0)
Expand Down Expand Up @@ -72,24 +73,24 @@ func TraverseDirectory(dir string, filterPatterns, gitignorePaths []string) ([]P
if err != nil {
return fmt.Errorf("getting relative path between %q and %q: %w", basePath, path, err)
}

// Convert to forward slashes for consistent pattern matching
relPath = filepath.ToSlash(relPath)

// Check if a file path should be included based on patterns
if f.MatchesPath(relPath) {
relPath, err = filepath.Rel(baseParent, path)
if err != nil {
return fmt.Errorf("getting relative path between %q and %q: %w", baseParent, path, err)
}
if gi.MatchesPath(relPath) || !f.MatchesPath(relPath) {
return nil
}

paths = append(paths, PathInfo{
Path: path,
RelativePath: relPath,
Depth: strings.Count(relPath, "/") + 1,
IsDir: false,
})
relPath, err = filepath.Rel(baseParent, path)
if err != nil {
return fmt.Errorf("getting relative path between %q and %q: %w", basePath, path, err)
}
relPath = filepath.ToSlash(relPath)

paths = append(paths, PathInfo{
Path: path,
RelativePath: relPath,
Depth: strings.Count(relPath, "/") + 1,
IsDir: false,
})
return nil
})
if err != nil {
Expand Down
8 changes: 8 additions & 0 deletions internal/traverse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ func TestTraverseDirectories(t *testing.T) {
"src/internal/util.go",
},
},
"match specific file with conflicting gitignore": {
directory: filepath.Join(tmpDir, "src"),
filterPatterns: []string{"internal/test.txt"},
gitignorePaths: []string{
filepath.Join(tmpDir, ".gitignore"),
},
wantRelPaths: []string{},
},
}

for name, tt := range tests {
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ type RootCmd struct {
Output string `help:"Specifies the destination path for the output file. The file extension will automatically adjust based on the selected format (see '--format')." short:"o" type:"path" placeholder:"amalgo.txt"`
Stdout bool `help:"Redirects all output to standard output (terminal) instead of writing to a file. Useful for piping output to other commands."`
Filter []string `help:"Controls which files are processed using glob patterns. Include patterns are processed first, then exclude patterns (prefixed with '!'). Hidden files and directories are excluded by default." short:"f" default:"*,!.*"`
GitIgnore []string `help:"Specifies .gitignore files to use for filtering. These patterns are merged with the filter patterns, taking the same precedence." name:"gitignore" short:"g"`
GitIgnore []string `help:"Specifies .gitignore files to use for filtering. These patterns are processed before the filter patterns, taking precedence. Of the provided gitignore files, the last one will take the highest precedence." name:"gitignore" short:"g"`
NoTree bool `help:"Skips the inclusion of the file tree in the output." default:"false"`
NoDump bool `help:"Skips the inclusion of file contents in the output." default:"false"`
Outline bool `help:"Includes in the output a language-aware outline of code files, showing functions, classes, and other significant elements. Only available for specific file extensions: '.go'." default:"false"`
Expand Down
12 changes: 11 additions & 1 deletion pkg/filter/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,17 @@ type Pattern struct {
Line string
}

func (f *Filter) Merge(other *Filter) {
// MergeWithoutPrecedence prepends the patterns of the given filter.
// The provided patterns will be used first, meaning they may be
// overruled by the existing patterns (on this Filter).
func (f *Filter) MergeWithoutPrecedence(other *Filter) {
f.patterns = append(other.patterns, f.patterns...)
}

// MergeWithoutPrecedence appends the patterns of the given filter.
// The provided patterns will be used last, meaning they may
// overrule the existing patterns (on this Filter).
func (f *Filter) MergeWithPrecedence(other *Filter) {
f.patterns = append(f.patterns, other.patterns...)
}

Expand Down

0 comments on commit 4c83719

Please sign in to comment.