From 5bc7bdd2b5238db543c5eb8550a5cd52542d4df3 Mon Sep 17 00:00:00 2001 From: RerankerGuo <121015044+RerankerGuo@users.noreply.github.com> Date: Wed, 12 Aug 2026 16:09:33 +0800 Subject: [PATCH] fix(tool): preserve special paths in file_find Use NUL-delimited Git output so Unicode and special-character paths are not C-quoted or split during file discovery. Add workspace and commit-mode regression coverage. Test: make check && make test && make build && make coverage --- internal/tool/file_find.go | 12 ++--- internal/tool/file_find_test.go | 83 +++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/internal/tool/file_find.go b/internal/tool/file_find.go index 44c2b955d..51bb72811 100644 --- a/internal/tool/file_find.go +++ b/internal/tool/file_find.go @@ -79,9 +79,9 @@ func (p *FileFindProvider) listGitFiles(parentCtx context.Context) ([]string, er var args []string if ref := p.FileReader.Ref; ref != "" { - args = []string{"ls-tree", "-r", "--name-only", "--end-of-options", ref} + args = []string{"ls-tree", "-r", "--name-only", "-z", "--end-of-options", ref} } else { - args = []string{"ls-files", "--cached", "--others", "--exclude-standard"} + args = []string{"ls-files", "-z", "--cached", "--others", "--exclude-standard"} } if p.FileReader.Runner != nil { @@ -107,10 +107,10 @@ func (p *FileFindProvider) listGitFiles(parentCtx context.Context) ([]string, er } var files []string - lines := bytes.Split(bytes.TrimRight(output, "\n"), []byte{'\n'}) - for _, line := range lines { - if len(line) > 0 { - s := string(line) + paths := bytes.Split(output, []byte{0}) + for _, path := range paths { + if len(path) > 0 { + s := string(path) // Skip binary-like files that lack meaningful extensions patterns // and filter out paths in common generated/artifact directories. if shouldSkipFile(s) { diff --git a/internal/tool/file_find_test.go b/internal/tool/file_find_test.go index 518b259ab..f33fc8416 100644 --- a/internal/tool/file_find_test.go +++ b/internal/tool/file_find_test.go @@ -8,6 +8,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strings" "testing" @@ -159,6 +160,88 @@ func TestFileFind_GitRepo_CommitMode(t *testing.T) { } } +func TestFileFind_GitRepo_PreservesSpecialPaths(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("test paths contain characters unsupported by Windows") + } + + dir := setupFileFindRepo(t) + specialPaths := []string{ + "unicode-测试.go", + "tab\tquote\"file.go", + "line\nbreak.go", + } + for _, path := range specialPaths { + if err := os.WriteFile(filepath.Join(dir, path), []byte("package special\n"), 0o644); err != nil { + t.Fatal(err) + } + } + + git := func(args ...string) string { + t.Helper() + cmd := exec.Command("git", args...) + cmd.Dir = dir + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("git %v: %v\n%s", args, err, out) + } + return strings.TrimSpace(string(out)) + } + git("add", ".") + git("commit", "-m", "add special paths") + commit := git("rev-parse", "HEAD") + + tests := []struct { + name string + mode ReviewMode + ref string + }{ + {name: "workspace", mode: ModeWorkspace}, + {name: "commit", mode: ModeCommit, ref: commit}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := NewFileFind(&FileReader{RepoDir: dir, Mode: tt.mode, Ref: tt.ref}) + + files, err := p.listGitFiles(context.Background()) + if err != nil { + t.Fatal(err) + } + // Newline paths are asserted only at the enumeration layer because + // Execute intentionally joins multiple results with newlines. + for _, want := range append([]string{"main.go"}, specialPaths...) { + found := false + for _, got := range files { + if got == want { + found = true + break + } + } + if !found { + t.Errorf("listGitFiles() missing original path %q; got %q", want, files) + } + } + + for _, query := range []struct { + name string + want string + }{ + {name: "main.go", want: "main.go"}, + {name: "unicode-", want: "unicode-测试.go"}, + {name: "quote", want: "tab\tquote\"file.go"}, + } { + got, err := p.Execute(context.Background(), map[string]any{"query_name": query.name}) + if err != nil { + t.Fatal(err) + } + if got != query.want { + t.Errorf("Execute(query_name=%q) = %q, want %q", query.name, got, query.want) + } + } + }) + } +} + func TestFileFind_GitRepo_WithRunner(t *testing.T) { dir := setupFileFindRepo(t) runner := gitcmd.New(4)