diff --git a/aliases/alias.go b/aliases/alias.go index 559b1e1b7..9d8d3bfd1 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,20 @@ 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) + 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, nil +} 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/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`. diff --git a/go.mod b/go.mod index 8235868da..3709a7b81 100644 --- a/go.mod +++ b/go.mod @@ -3,20 +3,20 @@ 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 ( github.com/cenkalti/backoff/v4 v4.3.0 + github.com/wasilibs/go-re2 v1.10.0 github.com/zricethezav/gitleaks/v8 v8.30.1 ) @@ -26,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 @@ -35,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 @@ -58,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 @@ -66,44 +65,38 @@ 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 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 + 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 3c71d6a2d..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= @@ -215,8 +209,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= @@ -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= 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() diff --git a/internal/git/git.go b/internal/git/git.go index fef0f50b0..a78486777 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,29 @@ 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{}) + listAuth := auth + if !remoteSupportsHTTPAuth(r) { + listAuth = nil + } + refList, err := r.List(&git.ListOptions{Auth: listAuth}) if err != nil { return branches, err } @@ -213,6 +232,20 @@ func (c *Client) RemoteBranches() (branches map[string]bool, err error) { return branches, nil } +// 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 remoteSupportsHTTPAuth(r *git.Remote) bool { + urls := r.Config().URLs + if len(urls) == 0 { + return false + } + return strings.HasPrefix(urls[0], "http://") || strings.HasPrefix(urls[0], "https://") +} + // 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..1c1f2185f --- /dev/null +++ b/internal/git/git_test.go @@ -0,0 +1,38 @@ +package git + +import ( + "testing" + + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/config" + "github.com/stretchr/testify/require" +) + +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("http remote supports auth", func(t *testing.T) { + require.True(t, remoteSupportsHTTPAuth(newRemote("http://github.com/example/repo.git"))) + }) + + 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 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.False(t, remoteSupportsHTTPAuth(newRemote("git@github.com:example/repo.git"))) + }) + + 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 support auth", func(t *testing.T) { + require.False(t, remoteSupportsHTTPAuth(git.NewRemote(nil, &config.RemoteConfig{Name: "origin", URLs: []string{}}))) + }) +} diff --git a/search/files.go b/search/files.go index f871a894a..6e54cadf1 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,20 @@ 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 != "" { + // 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 0b2ea2b03..95b749c36 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,90 @@ 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", + }, { + // 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 { + 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