From c38d996b1acf675aa4b876440b69ffac9eb1b00b Mon Sep 17 00:00:00 2001 From: Cyka <1354250064mdzz@gmail.com> Date: Fri, 17 Jul 2026 11:25:44 +0800 Subject: [PATCH 1/8] perf: cache globs/regexes and build filepattern alias contents once per alias Ports upstream ld-find-code-refs improvements adapted to this fork: - glob and regex result caching plus go-re2 for alias scanning (upstream #491) - concatenate filepattern contents once per alias instead of once per flag, avoiding OOM on large repos with many flags (upstream #604) --- aliases/alias.go | 80 ++++++++++++++----- aliases/alias_test.go | 28 +++++-- .../another/another/alias_test.txt | 1 + go.mod | 2 +- go.sum | 4 +- 5 files changed, 88 insertions(+), 27 deletions(-) create mode 100644 aliases/testdata/wild/nested-wild/another/another/alias_test.txt diff --git a/aliases/alias.go b/aliases/alias.go index 559b1e1b7..1fa3d6de6 100644 --- a/aliases/alias.go +++ b/aliases/alias.go @@ -7,13 +7,13 @@ import ( "os" "os/exec" "path/filepath" - "regexp" "strconv" "strings" "time" "github.com/bmatcuk/doublestar/v4" "github.com/iancoleman/strcase" + regexp "github.com/wasilibs/go-re2" "github.com/bucketeer-io/code-refs/internal/helpers" "github.com/bucketeer-io/code-refs/internal/log" @@ -21,6 +21,9 @@ import ( "github.com/bucketeer-io/code-refs/options" ) +var globCache = make(map[string][]string) +var regexCache = make(map[string]*regexp.Regexp) + // GenerateAliases returns a map of flag keys to aliases based on config. func GenerateAliases(flags []string, aliases []options.Alias, dir string) (map[string][]string, error) { allFileContents, err := processFileContent(aliases, dir) @@ -28,13 +31,26 @@ func GenerateAliases(flags []string, aliases []options.Alias, dir string) (map[s return nil, err } + // Filepattern contents concatenated once per alias; rebuilding them for + // every flag key multiplies peak memory by the number of flags. + patternContents := make(map[int]string, len(aliases)) + ret := make(map[string][]string, len(flags)) for _, flag := range flags { for i, a := range aliases { if a.Name == "" { a.Name = strconv.Itoa(i) } - flagAliases, err := generateAlias(a, flag, dir, allFileContents) + if a.Type.Canonical() == options.FilePattern { + if _, ok := patternContents[i]; !ok { + contents, err := concatFilePatternContents(a, dir, allFileContents) + if err != nil { + return nil, err + } + patternContents[i] = contents + } + } + flagAliases, err := generateAlias(a, flag, dir, patternContents[i]) if err != nil { return nil, err } @@ -45,12 +61,12 @@ func GenerateAliases(flags []string, aliases []options.Alias, dir string) (map[s return ret, nil } -func generateAlias(a options.Alias, flag, dir string, allFileContents FileContentsMap) (ret []string, err error) { +func generateAlias(a options.Alias, flag, dir string, patternContents string) (ret []string, err error) { switch a.Type.Canonical() { case options.Literal: ret = a.Flags[flag] case options.FilePattern: - ret, err = GenerateAliasesFromFilePattern(a, flag, dir, allFileContents) + ret = matchFilePatternAliases(a, flag, patternContents) case options.Command: ret, err = GenerateAliasesFromCommand(a, flag, dir) default: @@ -84,33 +100,48 @@ func GenerateNamingConventionAlias(a options.Alias, flag string) (alias string, } func GenerateAliasesFromFilePattern(a options.Alias, flag, dir string, allFileContents FileContentsMap) ([]string, error) { - ret := []string{} - // Concatenate the contents of all files into a single byte array to be matched by specified patterns - fileContents := []byte{} + fileContents, err := concatFilePatternContents(a, dir, allFileContents) + if err != nil { + return nil, err + } + return matchFilePatternAliases(a, flag, fileContents), nil +} + +// concatFilePatternContents concatenates the contents of all files matched by +// the alias paths into a single string to be matched by specified patterns +func concatFilePatternContents(a options.Alias, dir string, allFileContents FileContentsMap) (string, error) { + var sb strings.Builder for _, path := range a.Paths { - absGlob := filepath.Join(dir, path) - matches, err := doublestar.FilepathGlob(absGlob) + matches, err := cacheFilepathGlob(dir, path) if err != nil { - return nil, fmt.Errorf("filepattern '%s': could not process path glob '%s'", a.Name, absGlob) + return "", fmt.Errorf("filepattern '%s': could not process path glob '%s'", a.Name, path) } for _, match := range matches { if pathFileContents := allFileContents[match]; len(pathFileContents) > 0 { - fileContents = append(fileContents, pathFileContents...) + sb.Write(pathFileContents) } } } + return sb.String(), nil +} +func matchFilePatternAliases(a options.Alias, flag, fileContents string) []string { + ret := []string{} for _, p := range a.Patterns { - pattern := regexp.MustCompile(strings.ReplaceAll(p, "FLAG_KEY", flag)) - results := pattern.FindAllStringSubmatch(string(fileContents), -1) + patternStr := strings.ReplaceAll(p, "FLAG_KEY", flag) + pattern, ok := regexCache[patternStr] + if !ok { + pattern = regexp.MustCompile(patternStr) + regexCache[patternStr] = pattern + } + results := pattern.FindAllStringSubmatch(fileContents, -1) for _, res := range results { if len(res) > 1 { ret = append(ret, res[1:]...) } } } - - return ret, nil + return ret } func GenerateAliasesFromCommand(a options.Alias, flag, dir string) ([]string, error) { @@ -157,13 +188,12 @@ func processFileContent(aliases []options.Alias, dir string) (FileContentsMap, e paths := []string{} for _, glob := range a.Paths { - absGlob := filepath.Join(dir, glob) - matches, err := doublestar.FilepathGlob(absGlob) + matches, err := cacheFilepathGlob(dir, glob) if err != nil { - return nil, fmt.Errorf("filepattern '%s': could not process path glob '%s'", aliasId, absGlob) + return nil, fmt.Errorf("filepattern '%s': could not process path glob '%s'", aliasId, glob) } if matches == nil { - log.Info.Printf("filepattern '%s': no matching files found for alias path glob '%s'", aliasId, absGlob) + log.Info.Printf("filepattern '%s': no matching files found for alias path glob '%s'", aliasId, glob) } paths = append(paths, matches...) } @@ -188,3 +218,15 @@ func processFileContent(aliases []options.Alias, dir string) (FileContentsMap, e } return allFileContents, nil } + +func cacheFilepathGlob(dir, glob string) ([]string, error) { + absGlob := filepath.Join(dir, glob) + if cachedMatches, ok := globCache[absGlob]; ok { + return cachedMatches, nil + } + + matches, err := doublestar.FilepathGlob(absGlob) + globCache[absGlob] = matches + + return matches, err +} diff --git a/aliases/alias_test.go b/aliases/alias_test.go index 066f393d3..707da4a6b 100644 --- a/aliases/alias_test.go +++ b/aliases/alias_test.go @@ -93,7 +93,7 @@ func Test_GenerateAliases(t *testing.T) { name: "file exact pattern", flags: slice(testFlagKey), aliases: []o.Alias{ - fileExactPattern(testFlagKey), + fileExactPattern(), }, want: map[string][]string{testFlagKey: slice("SOME_FLAG")}, }, @@ -101,9 +101,9 @@ func Test_GenerateAliases(t *testing.T) { name: "file wildcard pattern", flags: slice(testFlagKey, testWildFlagKey), aliases: []o.Alias{ - fileWildPattern(testFlagKey), + fileWildPattern(), }, - want: map[string][]string{testWildFlagKey: slice("WILD_FLAG", "WILD_FLAG_SECOND_ALIAS"), testFlagKey: slice("SOME_FLAG")}, + want: map[string][]string{testWildFlagKey: slice("WILD_FLAG", "WILD_FLAG_SECOND_ALIAS", "ABSOLUTELY_WILD"), testFlagKey: slice("SOME_FLAG")}, }, { name: "command", @@ -198,6 +198,24 @@ func Test_processFileContent(t *testing.T) { } } +func Test_GenerateAliasesFromFilePattern(t *testing.T) { + expectedAliases := []string{"WILD_FLAG", "WILD_FLAG_SECOND_ALIAS", "ABSOLUTELY_WILD"} + + fileContents := map[string][]byte{ + "testdata/alias_test.txt": []byte("SOME_FLAG = 'someFlag'"), + "testdata/wild/alias_test.txt": []byte("WILD_FLAG = 'wildFlag'"), + "testdata/wild/nested-wild/alias_test.txt": []byte("WILD_FLAG_SECOND_ALIAS = 'wildFlag'"), + "testdata/wild/nested-wild/another/another/alias_test.txt": []byte("ABSOLUTELY_WILD = 'wildFlag'"), + } + + alias := fileWildPattern() + + aliases, err := GenerateAliasesFromFilePattern(alias, testWildFlagKey, "", fileContents) + require.NoError(t, err) + + assert.ElementsMatch(t, expectedAliases, aliases) +} + func slice(args ...string) []string { return args } @@ -222,7 +240,7 @@ func literal(flags []string) o.Alias { return a } -func fileExactPattern(flag string) o.Alias { +func fileExactPattern() o.Alias { a := alias(o.FilePattern) pattern := "(\\w+)\\s= 'FLAG_KEY'" a.Paths = []string{"testdata/alias_test.txt"} @@ -230,7 +248,7 @@ func fileExactPattern(flag string) o.Alias { return a } -func fileWildPattern(flag string) o.Alias { +func fileWildPattern() o.Alias { a := alias(o.FilePattern) pattern := "(\\w+)\\s= 'FLAG_KEY'" a.Paths = []string{"testdata/**/*.txt"} diff --git a/aliases/testdata/wild/nested-wild/another/another/alias_test.txt b/aliases/testdata/wild/nested-wild/another/another/alias_test.txt new file mode 100644 index 000000000..4910ed390 --- /dev/null +++ b/aliases/testdata/wild/nested-wild/another/another/alias_test.txt @@ -0,0 +1 @@ +ABSOLUTELY_WILD = 'wildFlag' diff --git a/go.mod b/go.mod index 8235868da..b5e522612 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( require ( github.com/cenkalti/backoff/v4 v4.3.0 + github.com/wasilibs/go-re2 v1.10.0 github.com/zricethezav/gitleaks/v8 v8.30.1 ) @@ -91,7 +92,6 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/tetratelabs/wazero v1.12.0 // indirect github.com/ulikunitz/xz v0.5.15 // indirect - github.com/wasilibs/go-re2 v1.9.0 // indirect github.com/wasilibs/wazero-helpers v0.0.0-20250123031827-cd30c44769bb // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect go.uber.org/multierr v1.11.0 // indirect diff --git a/go.sum b/go.sum index 3c71d6a2d..e3bfb20c3 100644 --- a/go.sum +++ b/go.sum @@ -215,8 +215,8 @@ github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/wasilibs/go-re2 v1.9.0 h1:kjAd8qbNvV4Ve2Uf+zrpTCrDHtqH4dlsRXktywo73JQ= -github.com/wasilibs/go-re2 v1.9.0/go.mod h1:0sRtscWgpUdNA137bmr1IUgrRX0Su4dcn9AEe61y+yI= +github.com/wasilibs/go-re2 v1.10.0 h1:vQZEBYZOCA9jdBMmrO4+CvqyCj0x4OomXTJ4a5/urQ0= +github.com/wasilibs/go-re2 v1.10.0/go.mod h1:k+5XqO2bCJS+QpGOnqugyfwC04nw0jaglmjrrkG8U6o= github.com/wasilibs/wazero-helpers v0.0.0-20250123031827-cd30c44769bb h1:gQ+ZV4wJke/EBKYciZ2MshEouEHFuinB85dY3f5s1q8= github.com/wasilibs/wazero-helpers v0.0.0-20250123031827-cd30c44769bb/go.mod h1:jMeV4Vpbi8osrE/pKUxRZkVaA0EX7NZN0A9/oRzgpgY= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= From 79f96a2cb2f64ed2246a8f81e1b16d3a8fab73ec Mon Sep 17 00:00:00 2001 From: Cyka <1354250064mdzz@gmail.com> Date: Fri, 17 Jul 2026 11:28:26 +0800 Subject: [PATCH 2/8] fix: account for subdirectory when resolving reference file paths When the subdirectory option is set, the search workspace becomes / but file paths were trimmed relative to the workspace, so project-level dir filters looked for //... and matched nothing, and reported paths produced broken source links. Resolve paths relative to the repo root instead (ports upstream ld-find-code-refs #516). --- search/files.go | 16 +++- search/files_test.go | 87 ++++++++++++++----- search/scan.go | 2 +- search/search.go | 4 +- search/search_test.go | 43 +++++++-- .../subdir/fileWithNoRefs | 1 + .../exclude-github-files/subdir/fileWithRefs | 2 + 7 files changed, 122 insertions(+), 33 deletions(-) create mode 100644 search/testdata/exclude-github-files/subdir/fileWithNoRefs create mode 100644 search/testdata/exclude-github-files/subdir/fileWithRefs diff --git a/search/files.go b/search/files.go index f871a894a..afc035d77 100644 --- a/search/files.go +++ b/search/files.go @@ -98,7 +98,7 @@ func isText(lines []string) bool { return true } -func readFiles(ctx context.Context, files chan<- file, workspace string) error { +func readFiles(ctx context.Context, files chan<- file, workspace, subdirectory string) error { defer close(files) ignoreFiles := []string{".gitignore", ".ignore", ".ldignore"} allIgnores := newIgnore(workspace, ignoreFiles) @@ -142,7 +142,7 @@ func readFiles(ctx context.Context, files chan<- file, workspace string) error { return nil } - relativePath := strings.TrimPrefix(path, workspace+"/") + relativePath := resolvePath(path, workspace, subdirectory) files <- file{ path: relativePath, lines: lines, @@ -153,3 +153,15 @@ func readFiles(ctx context.Context, files chan<- file, workspace string) error { return filepath.Walk(workspace, readFile) } + +// resolvePath makes path relative to the repo root rather than the searched +// workspace, so paths stay correct when a subdirectory is configured (the +// workspace is then /). +func resolvePath(path, workspace, subdirectory string) string { + dir := workspace + if subdirectory != "" { + dir = strings.TrimSuffix(workspace, "/"+subdirectory) + } + + return strings.TrimPrefix(path, dir+"/") +} diff --git a/search/files_test.go b/search/files_test.go index 0b2ea2b03..2694256cc 100644 --- a/search/files_test.go +++ b/search/files_test.go @@ -11,7 +11,7 @@ import ( func Test_readFiles(t *testing.T) { t.Run("don't ignore .github by default", func(t *testing.T) { files := make(chan file, 8) - err := readFiles(context.Background(), files, "testdata/include-github-files") + err := readFiles(context.Background(), files, "testdata/include-github-files", "") require.NoError(t, err) got := []file{} for file := range files { @@ -33,25 +33,72 @@ func Test_readFiles(t *testing.T) { }) t.Run("explicitly ignore .github files", func(t *testing.T) { - files := make(chan file, 8) - err := readFiles(context.Background(), files, "testdata/exclude-github-files") - require.NoError(t, err) - got := []file{} - for file := range files { - got = append(got, file) - switch file.path { - case "fileWithNoRefs": - assert.Equal(t, []string{"fileWithNoRefs"}, file.lines) - case "fileWithRefs": - assert.Equal(t, testFile.lines, file.lines) - case "ignoredFiles/included": - assert.Equal(t, []string{"IGNORED BUT INCLUDED"}, file.lines) - case "symlink": - assert.Fail(t, "Should not read symlink contents") - default: - assert.Fail(t, "Read unexpected file", file) + t.Run("without subdirectory option", func(t *testing.T) { + files := make(chan file, 8) + err := readFiles(context.Background(), files, "testdata/exclude-github-files", "") + require.NoError(t, err) + got := []file{} + for file := range files { + got = append(got, file) + switch file.path { + case "fileWithNoRefs": + assert.Equal(t, []string{"fileWithNoRefs"}, file.lines) + case "fileWithRefs": + assert.Equal(t, testFile.lines, file.lines) + case "subdir/fileWithNoRefs": + assert.Equal(t, []string{"nope"}, file.lines) + case "subdir/fileWithRefs": + assert.Equal(t, testFileWithSubdir.lines, file.lines) + case "ignoredFiles/included": + assert.Equal(t, []string{"IGNORED BUT INCLUDED"}, file.lines) + case "symlink": + assert.Fail(t, "Should not read symlink contents") + default: + assert.Fail(t, "Read unexpected file", file) + } } - } - assert.Len(t, got, 3, "Expected 3 valid files to have been found") + assert.Len(t, got, 5, "Expected 5 valid files to have been found") + }) + + t.Run("with subdirectory option", func(t *testing.T) { + files := make(chan file, 8) + err := readFiles(context.Background(), files, "testdata/exclude-github-files/subdir", "subdir") + require.NoError(t, err) + got := []file{} + for file := range files { + got = append(got, file) + switch file.path { + case "subdir/fileWithNoRefs": + assert.Equal(t, []string{"nope"}, file.lines) + case "subdir/fileWithRefs": + assert.Equal(t, testFileWithSubdir.lines, file.lines) + default: + assert.Fail(t, "Read unexpected file", file) + } + } + assert.Len(t, got, 2, "Expected 2 valid files to have been found") + }) }) } + +func Test_resolvePath(t *testing.T) { + testCases := []struct{ name, path, workspace, subdirectory, expectedPath string }{{ + name: "with subdirectory", + path: "/path/to/workspace/subdirectory/internal/file.txt", + workspace: "/path/to/workspace", + subdirectory: "subdirectory", + expectedPath: "subdirectory/internal/file.txt", + }, { + name: "without subdirectory", + path: "/path/to/workspace/file.txt", + workspace: "/path/to/workspace", + subdirectory: "", + expectedPath: "file.txt", + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + require.Equal(t, tc.expectedPath, resolvePath(tc.path, tc.workspace, tc.subdirectory)) + }) + } +} diff --git a/search/scan.go b/search/scan.go index bfe563d39..e8e9443df 100644 --- a/search/scan.go +++ b/search/scan.go @@ -19,7 +19,7 @@ func Scan(opts options.Options, dir string) (Matcher, []bucketeer.ReferenceHunks searchDir = filepath.Join(dir, opts.Subdirectory) } - refs, err := SearchForRefs(searchDir, matcher) + refs, err := SearchForRefs(searchDir, opts.Subdirectory, matcher) if err != nil { log.Error.Fatalf("error searching for flag key references: %s", err) } diff --git a/search/search.go b/search/search.go index 308e7316f..ba09c89ea 100644 --- a/search/search.go +++ b/search/search.go @@ -202,7 +202,7 @@ func processFiles(ctx context.Context, files <-chan file, references chan<- buck w.Wait() } -func SearchForRefs(directory string, matcher Matcher) ([]bucketeer.ReferenceHunksRep, error) { +func SearchForRefs(directory, subdirectory string, matcher Matcher) ([]bucketeer.ReferenceHunksRep, error) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() files := make(chan file) @@ -210,7 +210,7 @@ func SearchForRefs(directory string, matcher Matcher) ([]bucketeer.ReferenceHunk // Start workers to process files asynchronously as they are written to the files channel go processFiles(ctx, files, references, matcher) - err := readFiles(ctx, files, directory) + err := readFiles(ctx, files, directory, subdirectory) if err != nil { return nil, err } diff --git a/search/search_test.go b/search/search_test.go index 790c3a16c..919e1ad75 100644 --- a/search/search_test.go +++ b/search/search_test.go @@ -48,6 +48,11 @@ var ( *withFlagKey(withAliases(makeHunkPtr(5, testFlag2Alias), testFlag2Alias), testFlagKey2), } + testFileWithSubdir = file{ + path: "subdir/fileWithRefs", + lines: []string{testFlagKey, testFlagKey2}, + } + delimitedTestFlagKey = delimit(testFlagKey, `"`) ) @@ -386,17 +391,39 @@ func Test_processFiles(t *testing.T) { func Test_SearchForRefs(t *testing.T) { os.Symlink("testdata/exclude-github-files/fileWithRefs", "testdata/exclude-github-files/symlink") - want := []bucketeer.ReferenceHunksRep{{Path: testFile.path}} - matcher := Matcher{ - ctxLines: 0, - } + + matcher := Matcher{ctxLines: 0} matcher.Element = NewElementMatcher("default", "", "", []string{testFlagKey, testFlagKey2}, nil) + + t.Run("without subdirectory option finds both files", func(t *testing.T) { + actual, err := SearchForRefs("testdata/exclude-github-files", "", matcher) + require.NoError(t, err) + require.Len(t, actual, 2) + + var foundFirst, foundSecond bool + for _, r := range actual { + switch r.Path { + case testFile.path: + foundFirst = true + case testFileWithSubdir.path: + foundSecond = true + default: + t.Fatal("found unexpected file " + r.Path) + } + } + require.True(t, foundFirst) + require.True(t, foundSecond) + }) + + t.Run("with subdirectory option finds only the file in the subdirectory", func(t *testing.T) { + actual, err := SearchForRefs("testdata/exclude-github-files/subdir", "subdir", matcher) + require.NoError(t, err) + require.Len(t, actual, 1) + require.Equal(t, testFileWithSubdir.path, actual[0].Path) + }) + t.Cleanup(func() { os.Remove("testdata/exclude-github-files/symlink") }) - got, err := SearchForRefs("testdata/exclude-github-files", matcher) - require.NoError(t, err) - require.Len(t, got, 1) - require.Equal(t, want[0].Path, got[0].Path) } func Test_truncateLine(t *testing.T) { diff --git a/search/testdata/exclude-github-files/subdir/fileWithNoRefs b/search/testdata/exclude-github-files/subdir/fileWithNoRefs new file mode 100644 index 000000000..1634764a4 --- /dev/null +++ b/search/testdata/exclude-github-files/subdir/fileWithNoRefs @@ -0,0 +1 @@ +nope diff --git a/search/testdata/exclude-github-files/subdir/fileWithRefs b/search/testdata/exclude-github-files/subdir/fileWithRefs new file mode 100644 index 000000000..dea5c807a --- /dev/null +++ b/search/testdata/exclude-github-files/subdir/fileWithRefs @@ -0,0 +1,2 @@ +someFlag +anotherFlag From 0c1974f900883376dc49d8dd7058f100ded6ca7b Mon Sep 17 00:00:00 2001 From: Cyka <1354250064mdzz@gmail.com> Date: Fri, 17 Jul 2026 11:29:25 +0800 Subject: [PATCH 3/8] feat: authenticate remote git operations with GITHUB_TOKEN in GitHub Actions Branch listing (used for pruning stale branches) failed on private repos when running in GitHub Actions because remote operations were unauthenticated. Use the workflow's GITHUB_TOKEN when available (ports upstream ld-find-code-refs #571). --- internal/git/git.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/internal/git/git.go b/internal/git/git.go index fef0f50b0..ba51ae9ae 100644 --- a/internal/git/git.go +++ b/internal/git/git.go @@ -3,12 +3,15 @@ package git import ( "errors" "fmt" + "os" "os/exec" "path/filepath" "strings" git "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/transport" + "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/bucketeer-io/code-refs/internal/log" ) @@ -186,13 +189,25 @@ func (c *Client) RemoteBranches() (branches map[string]bool, err error) { return branches, err } + // Configure authentication for GitHub Actions + var auth transport.AuthMethod + if os.Getenv("GITHUB_ACTIONS") == "true" { + if token := os.Getenv("GITHUB_TOKEN"); token != "" { + log.Debug.Printf("using GitHub token authentication for remote operations") + auth = &http.BasicAuth{ + Username: "x-access-token", // GitHub requires this specific username + Password: token, + } + } + } + remotes, err := repo.Remotes() if err != nil { return branches, err } for _, r := range remotes { - refList, err := r.List(&git.ListOptions{}) + refList, err := r.List(&git.ListOptions{Auth: auth}) if err != nil { return branches, err } From 636d8c1aec14724db5d378fa006b29cc8e152f7d Mon Sep 17 00:00:00 2001 From: Cyka <1354250064mdzz@gmail.com> Date: Fri, 17 Jul 2026 11:30:06 +0800 Subject: [PATCH 4/8] feat: debug-log HTTP response status codes in the API client Non-5xx failures (e.g. 413 Request Entity Too Large) were silent: the client only errors on 5xx, so rejected payloads looked like successful sends. Log every response's status, method, and URL at debug level in the central do method (ports upstream ld-find-code-refs #587). --- internal/bucketeer/bucketeer.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/bucketeer/bucketeer.go b/internal/bucketeer/bucketeer.go index 9ddc366ac..4a7930fe8 100644 --- a/internal/bucketeer/bucketeer.go +++ b/internal/bucketeer/bucketeer.go @@ -134,6 +134,8 @@ func (c *apiClient) do(req *http.Request) (*http.Response, error) { return err } + log.Debug.Printf("response status %d %s for %s %s", resp.StatusCode, http.StatusText(resp.StatusCode), req.Method, req.URL) + if resp.StatusCode >= serverErrorMinCode { body, _ := io.ReadAll(resp.Body) resp.Body.Close() From 27bb5ec441b78d1fc9c0dd87fe93dc561d18ef23 Mon Sep 17 00:00:00 2001 From: Cyka <1354250064mdzz@gmail.com> Date: Fri, 17 Jul 2026 11:30:57 +0800 Subject: [PATCH 5/8] chore: bump doublestar to v4.9.1, go-git to v5.16.3, viper to v1.21.0 Matches current upstream ld-find-code-refs dependency versions; the go-git bump includes upstream security fixes. --- go.mod | 39 ++++++++++++---------------- go.sum | 80 +++++++++++++++++++++++++--------------------------------- 2 files changed, 50 insertions(+), 69 deletions(-) diff --git a/go.mod b/go.mod index b5e522612..3709a7b81 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,15 @@ module github.com/bucketeer-io/code-refs go 1.25.0 require ( - github.com/bmatcuk/doublestar/v4 v4.7.1 - github.com/go-git/go-git/v5 v5.13.1 + github.com/bmatcuk/doublestar/v4 v4.9.1 + github.com/go-git/go-git/v5 v5.16.3 github.com/iancoleman/strcase v0.3.0 github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9 github.com/spf13/cobra v1.10.1 github.com/spf13/pflag v1.0.10 - github.com/spf13/viper v1.19.0 + github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 - golang.org/x/tools v0.45.0 // indirect ) require ( @@ -27,8 +26,8 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Masterminds/sprig/v3 v3.3.0 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v1.1.3 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect github.com/STARRY-S/zip v0.2.3 // indirect github.com/andybalholm/brotli v1.2.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect @@ -36,22 +35,22 @@ require ( github.com/bodgit/sevenzip v1.6.2 // indirect github.com/bodgit/windows v1.0.1 // indirect github.com/charmbracelet/lipgloss v0.5.0 // indirect - github.com/cloudflare/circl v1.3.7 // indirect - github.com/cyphar/filepath-securejoin v0.3.6 // indirect + github.com/cloudflare/circl v1.6.1 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/fatih/semgroup v1.2.0 // indirect - github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/gitleaks/go-gitdiff v0.9.1 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.6.1 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/uuid v1.6.0 // indirect github.com/h2non/filetype v1.1.3 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect @@ -59,7 +58,6 @@ require ( github.com/klauspost/compress v1.18.6 // indirect github.com/klauspost/pgzip v1.2.6 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/magiconair/properties v1.8.9 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect @@ -67,25 +65,22 @@ require ( github.com/mikelolasagasti/xz v1.0.1 // indirect github.com/minio/minlz v1.1.1 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 // indirect github.com/muesli/termenv v0.15.1 // indirect github.com/nwaples/rardecode/v2 v2.2.2 // indirect github.com/pelletier/go-toml/v2 v2.3.1 // indirect github.com/pierrec/lz4/v4 v4.1.26 // indirect - github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/zerolog v1.33.0 // indirect - github.com/sagikazarmark/locafero v0.7.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/shopspring/decimal v1.4.0 // indirect - github.com/skeema/knownhosts v1.3.0 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect github.com/sorairolake/lzip-go v0.3.8 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/stretchr/objx v0.5.3 // indirect @@ -94,16 +89,14 @@ require ( github.com/ulikunitz/xz v0.5.15 // indirect github.com/wasilibs/wazero-helpers v0.0.0-20250123031827-cd30c44769bb // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect go4.org v0.0.0-20260112195520-a5071408f32f // indirect golang.org/x/crypto v0.53.0 // indirect golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect - golang.org/x/mod v0.36.0 // indirect golang.org/x/net v0.55.0 // indirect golang.org/x/sync v0.21.0 // indirect golang.org/x/sys v0.46.0 // indirect golang.org/x/text v0.38.0 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e3bfb20c3..1f4c91937 100644 --- a/go.sum +++ b/go.sum @@ -9,10 +9,10 @@ github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lpr github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= -github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4= github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk= github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro= @@ -23,8 +23,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q= -github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE= +github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU= github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs= github.com/bodgit/sevenzip v1.6.2 h1:6/0mwj5KaRXpuf9iSiE+VpG7VpzFJ8D60P53VjxRv34= @@ -35,12 +35,12 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8= github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= -github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= -github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -48,31 +48,33 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4= github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= -github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ= -github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/fatih/semgroup v1.2.0 h1:h/OLXwEM+3NNyAdZEpMiH1OzfplU09i2qXPVThGZvyg= github.com/fatih/semgroup v1.2.0/go.mod h1:1KAD4iIYfXjE4U13B48VM4z9QUwV5Tt8O4rS879kgm8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gitleaks/go-gitdiff v0.9.1 h1:ni6z6/3i9ODT685OLCTf+s/ERlWUNWQF4x1pvoNICw0= github.com/gitleaks/go-gitdiff v0.9.1/go.mod h1:pKz0X4YzCKZs30BL+weqBIG7mx0jl4tF1uXV9ZyNvrA= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA= -github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M= -github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc= +github.com/go-git/go-git/v5 v5.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8= +github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= @@ -84,8 +86,6 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= @@ -111,8 +111,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= -github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= @@ -133,8 +131,6 @@ github.com/minio/minlz v1.1.1 h1:OGmft1V6AnI/Wme332U6bhG54nxEan+VFgkD7lat4KM= github.com/minio/minlz v1.1.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= @@ -154,8 +150,8 @@ github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9 h1: github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9/go.mod h1:EHPiTAKtiFmrMldLUNswFwfZ2eJIYBHktdaUTZxYWRw= github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY= github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= -github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= -github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -170,21 +166,19 @@ github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= -github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik= github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= @@ -194,8 +188,8 @@ github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4 github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= -github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -225,8 +219,8 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/zricethezav/gitleaks/v8 v8.30.1 h1:PmEvCfVI7ti9dV3s5aMZUY7sS2GxRvG3yzih7E+cS3w= github.com/zricethezav/gitleaks/v8 v8.30.1/go.mod h1:rTDwxRjufMKAkhTI/Mijd07nday1yOhf9qywjwz5Irw= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go4.org v0.0.0-20260112195520-a5071408f32f h1:ziUVAjmTPwQMBmYR1tbdRFJPtTcQUI12fH9QQjfb0Sw= go4.org v0.0.0-20260112195520-a5071408f32f/go.mod h1:ZRJnO5ZI4zAwMFp+dS1+V6J6MSyAowhRqAE+DPa1Xp0= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -234,8 +228,6 @@ golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4= golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= -golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= -golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= @@ -260,15 +252,11 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= -golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From b36a422c277de795b86449b1f985aae264ca46bb Mon Sep 17 00:00:00 2001 From: Cyka <1354250064mdzz@gmail.com> Date: Fri, 17 Jul 2026 11:46:26 +0800 Subject: [PATCH 6/8] docs: document GITHUB_TOKEN authentication for branch pruning in GitHub Actions --- docs/CONFIGURATION.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index f7eea64ec..6acd0d297 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -57,6 +57,15 @@ Flags: All command line flags are available as environment variables following the "upper snake case" format, with a prefix of `BUCKETEER_`. For example, the command line option `apiKey` may be set as an environment variable e.g. `export BUCKETEER_APIKEY = 'myTestToken'`. For multiple API keys, you can use comma-separated values: `export BUCKETEER_APIKEY = 'key1,key2,key3'`. +### GitHub Actions + +When running inside GitHub Actions (`GITHUB_ACTIONS=true`), remote git operations used by branch pruning authenticate with the workflow's `GITHUB_TOKEN` environment variable if it is set. Provide it when scanning a private repository with `prune` enabled (the default), otherwise listing remote branches will fail: + +```yaml +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +``` + ## YAML A YAML file may be used to specify most command line arguments, as well as a number of additional options for advanced usage of `bucketeer-find-code-refs`. The configuration YAML file should be stored as `${dir}/.bucketeer/coderefs.yaml`. From eb325c54bc90b301e1112e242c7c906533be7e6c Mon Sep 17 00:00:00 2001 From: Cyka <1354250064mdzz@gmail.com> Date: Tue, 21 Jul 2026 11:50:25 +0800 Subject: [PATCH 7/8] fix: address Copilot review findings on alias caching, subdirectory paths, and GHA auth --- aliases/alias.go | 7 ++++++- internal/git/git.go | 22 +++++++++++++++++++- internal/git/git_test.go | 45 ++++++++++++++++++++++++++++++++++++++++ search/files.go | 7 ++++++- search/files_test.go | 18 ++++++++++++++++ 5 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 internal/git/git_test.go diff --git a/aliases/alias.go b/aliases/alias.go index 1fa3d6de6..9d8d3bfd1 100644 --- a/aliases/alias.go +++ b/aliases/alias.go @@ -226,7 +226,12 @@ func cacheFilepathGlob(dir, glob string) ([]string, error) { } matches, err := doublestar.FilepathGlob(absGlob) + if err != nil { + // Don't cache failures: a cache hit always returns a nil error, which + // would silently mask the original glob error on any later call. + return nil, err + } globCache[absGlob] = matches - return matches, err + return matches, nil } diff --git a/internal/git/git.go b/internal/git/git.go index ba51ae9ae..befbb656d 100644 --- a/internal/git/git.go +++ b/internal/git/git.go @@ -207,7 +207,7 @@ func (c *Client) RemoteBranches() (branches map[string]bool, err error) { } for _, r := range remotes { - refList, err := r.List(&git.ListOptions{Auth: auth}) + refList, err := r.List(&git.ListOptions{Auth: remoteAuth(r, auth)}) if err != nil { return branches, err } @@ -228,6 +228,26 @@ func (c *Client) RemoteBranches() (branches map[string]bool, err error) { return branches, nil } +// remoteAuth returns auth for r only if r's URL uses HTTP(S). go-git's +// transports type-assert Auth to a protocol-specific interface (e.g. the ssh +// package requires ssh.AuthMethod), so handing an http.BasicAuth to an SSH +// remote fails with transport.ErrInvalidAuthMethod instead of just listing +// unauthenticated. go-git only ever consults a remote's first configured URL +// (see Remote.list), so that's the only one that needs checking. +func remoteAuth(r *git.Remote, auth transport.AuthMethod) transport.AuthMethod { + if auth == nil { + return nil + } + urls := r.Config().URLs + if len(urls) == 0 { + return nil + } + if strings.HasPrefix(urls[0], "http://") || strings.HasPrefix(urls[0], "https://") { + return auth + } + return nil +} + // type CommitData struct { // commit *object.Commit // tree *object.Tree diff --git a/internal/git/git_test.go b/internal/git/git_test.go new file mode 100644 index 000000000..206cd6737 --- /dev/null +++ b/internal/git/git_test.go @@ -0,0 +1,45 @@ +package git + +import ( + "testing" + + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/plumbing/transport/http" + "github.com/stretchr/testify/require" +) + +func Test_remoteAuth(t *testing.T) { + basicAuth := &http.BasicAuth{Username: "x-access-token", Password: "token"} + + newRemote := func(url string) *git.Remote { + return git.NewRemote(nil, &config.RemoteConfig{Name: "origin", URLs: []string{url}}) + } + + t.Run("nil auth stays nil regardless of remote scheme", func(t *testing.T) { + require.Nil(t, remoteAuth(newRemote("https://github.com/example/repo.git"), nil)) + }) + + t.Run("http remote gets the auth", func(t *testing.T) { + require.Equal(t, basicAuth, remoteAuth(newRemote("http://github.com/example/repo.git"), basicAuth)) + }) + + t.Run("https remote gets the auth", func(t *testing.T) { + require.Equal(t, basicAuth, remoteAuth(newRemote("https://github.com/example/repo.git"), basicAuth)) + }) + + t.Run("ssh remote does not get http auth", func(t *testing.T) { + // go-git's ssh transport type-asserts Auth to ssh.AuthMethod and + // rejects anything else (transport.ErrInvalidAuthMethod), so handing + // it an http.BasicAuth would break branch listing outright. + require.Nil(t, remoteAuth(newRemote("git@github.com:example/repo.git"), basicAuth)) + }) + + t.Run("scp-like ssh remote does not get http auth", func(t *testing.T) { + require.Nil(t, remoteAuth(newRemote("ssh://git@github.com/example/repo.git"), basicAuth)) + }) + + t.Run("remote with no configured URLs does not get auth", func(t *testing.T) { + require.Nil(t, remoteAuth(git.NewRemote(nil, &config.RemoteConfig{Name: "origin", URLs: []string{}}), basicAuth)) + }) +} diff --git a/search/files.go b/search/files.go index afc035d77..6e54cadf1 100644 --- a/search/files.go +++ b/search/files.go @@ -160,7 +160,12 @@ func readFiles(ctx context.Context, files chan<- file, workspace, subdirectory s func resolvePath(path, workspace, subdirectory string) string { dir := workspace if subdirectory != "" { - dir = strings.TrimSuffix(workspace, "/"+subdirectory) + // Normalize the same way filepath.Join normalized subdirectory when + // building workspace, so a trailing slash or "./" prefix doesn't + // prevent the suffix match below (which would silently drop the + // subdirectory from the resolved path). + cleanSubdirectory := filepath.ToSlash(filepath.Clean(subdirectory)) + dir = strings.TrimSuffix(workspace, "/"+cleanSubdirectory) } return strings.TrimPrefix(path, dir+"/") diff --git a/search/files_test.go b/search/files_test.go index 2694256cc..95b749c36 100644 --- a/search/files_test.go +++ b/search/files_test.go @@ -94,6 +94,24 @@ func Test_resolvePath(t *testing.T) { workspace: "/path/to/workspace", subdirectory: "", expectedPath: "file.txt", + }, { + // Mirrors the runtime shape: scan.go builds workspace via + // filepath.Join(dir, opts.Subdirectory), so workspace already + // includes the subdirectory suffix that must be trimmed back off. + name: "workspace includes subdirectory suffix", + path: "/path/to/workspace/subdir/internal/file.txt", + workspace: "/path/to/workspace/subdir", + subdirectory: "subdir", + expectedPath: "subdir/internal/file.txt", + }, { + // Regression test: a trailing slash on the subdirectory option must + // not prevent the suffix match, or the subdirectory prefix is + // silently dropped from every resolved path. + name: "subdirectory option has a trailing slash", + path: "/path/to/workspace/subdir/internal/file.txt", + workspace: "/path/to/workspace/subdir", + subdirectory: "subdir/", + expectedPath: "subdir/internal/file.txt", }} for _, tc := range testCases { From 2293b6d0f4e76689370ff4f5dcd47ad73cd55c57 Mon Sep 17 00:00:00 2001 From: Cyka <1354250064mdzz@gmail.com> Date: Tue, 21 Jul 2026 12:14:02 +0800 Subject: [PATCH 8/8] fix: satisfy golangci-lint ireturn check in git.go --- internal/git/git.go | 20 +++++++++----------- internal/git/git_test.go | 29 +++++++++++------------------ 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/internal/git/git.go b/internal/git/git.go index befbb656d..a78486777 100644 --- a/internal/git/git.go +++ b/internal/git/git.go @@ -207,7 +207,11 @@ func (c *Client) RemoteBranches() (branches map[string]bool, err error) { } for _, r := range remotes { - refList, err := r.List(&git.ListOptions{Auth: remoteAuth(r, auth)}) + listAuth := auth + if !remoteSupportsHTTPAuth(r) { + listAuth = nil + } + refList, err := r.List(&git.ListOptions{Auth: listAuth}) if err != nil { return branches, err } @@ -228,24 +232,18 @@ func (c *Client) RemoteBranches() (branches map[string]bool, err error) { return branches, nil } -// remoteAuth returns auth for r only if r's URL uses HTTP(S). go-git's +// remoteSupportsHTTPAuth reports whether r's URL uses HTTP(S). go-git's // transports type-assert Auth to a protocol-specific interface (e.g. the ssh // package requires ssh.AuthMethod), so handing an http.BasicAuth to an SSH // remote fails with transport.ErrInvalidAuthMethod instead of just listing // unauthenticated. go-git only ever consults a remote's first configured URL // (see Remote.list), so that's the only one that needs checking. -func remoteAuth(r *git.Remote, auth transport.AuthMethod) transport.AuthMethod { - if auth == nil { - return nil - } +func remoteSupportsHTTPAuth(r *git.Remote) bool { urls := r.Config().URLs if len(urls) == 0 { - return nil - } - if strings.HasPrefix(urls[0], "http://") || strings.HasPrefix(urls[0], "https://") { - return auth + return false } - return nil + return strings.HasPrefix(urls[0], "http://") || strings.HasPrefix(urls[0], "https://") } // type CommitData struct { diff --git a/internal/git/git_test.go b/internal/git/git_test.go index 206cd6737..1c1f2185f 100644 --- a/internal/git/git_test.go +++ b/internal/git/git_test.go @@ -5,41 +5,34 @@ import ( "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" - "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/stretchr/testify/require" ) -func Test_remoteAuth(t *testing.T) { - basicAuth := &http.BasicAuth{Username: "x-access-token", Password: "token"} - +func Test_remoteSupportsHTTPAuth(t *testing.T) { newRemote := func(url string) *git.Remote { return git.NewRemote(nil, &config.RemoteConfig{Name: "origin", URLs: []string{url}}) } - t.Run("nil auth stays nil regardless of remote scheme", func(t *testing.T) { - require.Nil(t, remoteAuth(newRemote("https://github.com/example/repo.git"), nil)) - }) - - t.Run("http remote gets the auth", func(t *testing.T) { - require.Equal(t, basicAuth, remoteAuth(newRemote("http://github.com/example/repo.git"), basicAuth)) + t.Run("http remote supports auth", func(t *testing.T) { + require.True(t, remoteSupportsHTTPAuth(newRemote("http://github.com/example/repo.git"))) }) - t.Run("https remote gets the auth", func(t *testing.T) { - require.Equal(t, basicAuth, remoteAuth(newRemote("https://github.com/example/repo.git"), basicAuth)) + t.Run("https remote supports auth", func(t *testing.T) { + require.True(t, remoteSupportsHTTPAuth(newRemote("https://github.com/example/repo.git"))) }) - t.Run("ssh remote does not get http auth", func(t *testing.T) { + t.Run("ssh remote does not support http auth", func(t *testing.T) { // go-git's ssh transport type-asserts Auth to ssh.AuthMethod and // rejects anything else (transport.ErrInvalidAuthMethod), so handing // it an http.BasicAuth would break branch listing outright. - require.Nil(t, remoteAuth(newRemote("git@github.com:example/repo.git"), basicAuth)) + require.False(t, remoteSupportsHTTPAuth(newRemote("git@github.com:example/repo.git"))) }) - t.Run("scp-like ssh remote does not get http auth", func(t *testing.T) { - require.Nil(t, remoteAuth(newRemote("ssh://git@github.com/example/repo.git"), basicAuth)) + t.Run("scp-like ssh remote does not support http auth", func(t *testing.T) { + require.False(t, remoteSupportsHTTPAuth(newRemote("ssh://git@github.com/example/repo.git"))) }) - t.Run("remote with no configured URLs does not get auth", func(t *testing.T) { - require.Nil(t, remoteAuth(git.NewRemote(nil, &config.RemoteConfig{Name: "origin", URLs: []string{}}), basicAuth)) + t.Run("remote with no configured URLs does not support auth", func(t *testing.T) { + require.False(t, remoteSupportsHTTPAuth(git.NewRemote(nil, &config.RemoteConfig{Name: "origin", URLs: []string{}}))) }) }