Skip to content

Commit deb6ab3

Browse files
Use run(commitOid) API instead of paginated analysis runs
- Add GetRunByCommit client method and GetRun GraphQL query - Walk git commit history to find the latest successful run for a branch instead of paginating through all analysis runs - Add CommitLogFunc to command deps for testability - Extract ResolveBranchName and getCommitLog helpers - Update issues, metrics, vulns, and report-card commands to use the new resolution strategy - Remove unused constants from report/constants.go - Fix lint warnings (unused params, idiomatic slice init)
1 parent 3448ac6 commit deb6ab3

36 files changed

Lines changed: 482 additions & 200 deletions

buildinfo/version_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func TestSetBuildInfo(t *testing.T) {
7575
},
7676
}
7777
for _, tt := range tests {
78-
t.Run(tt.name, func(t *testing.T) {
78+
t.Run(tt.name, func(_ *testing.T) {
7979
SetBuildInfo(tt.version, tt.dateStr, tt.buildMode)
8080
})
8181
if !reflect.DeepEqual(buildInfo, want) {

command/cmddeps/deps.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ type Deps struct {
1717
Stdout io.Writer
1818
RepoService *reposvc.Service
1919
BranchNameFunc func() (string, error)
20+
CommitLogFunc func(branch string) ([]string, error)
2021
}

command/cmdutil/reportcard.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func ShowReportCard(rc *runs.ReportCard) {
3636
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
3737

3838
if rc.Coverage != nil {
39-
parts := []string{}
39+
var parts []string
4040
if rc.Coverage.Grade != "" {
4141
parts = append(parts, fmt.Sprintf("Grade: %s", gradeColor(rc.Coverage.Grade)))
4242
}
@@ -80,7 +80,7 @@ func gradeColor(grade string) string {
8080
func FormatCategory(s string) string {
8181
parts := strings.Split(strings.ToLower(s), "_")
8282
for i, p := range parts {
83-
if len(p) > 0 {
83+
if p != "" {
8484
parts[i] = strings.ToUpper(p[:1]) + p[1:]
8585
}
8686
}

command/cmdutil/resolve_run.go

Lines changed: 61 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ import (
1212
"github.com/deepsourcelabs/cli/internal/vcs"
1313
)
1414

15-
// ResolveLatestRun finds the latest analysis run for the current git branch.
15+
// ResolveLatestRun finds the latest successful analysis run for the current git branch.
16+
// It walks recent commits from git history and looks up each via the run(commitOid) API.
1617
// Returns the commitOid of the latest run and the branch name.
1718
func ResolveLatestRun(
1819
ctx context.Context,
1920
client *deepsource.Client,
2021
remote *vcs.RemoteData,
2122
branchNameFunc func() (string, error),
23+
commitLogFunc func(branch string) ([]string, error),
2224
) (commitOid string, branchName string, err error) {
2325
if branchNameFunc != nil {
2426
branchName, err = branchNameFunc()
@@ -29,64 +31,57 @@ func ResolveLatestRun(
2931
return "", "", fmt.Errorf("failed to detect current branch: %w", err)
3032
}
3133

32-
const maxPages = 5
33-
var after *string
34-
35-
for page := 0; page < maxPages; page++ {
36-
runs, pageInfo, err := client.GetAnalysisRuns(ctx, remote.Owner, remote.RepoName, remote.VCSProvider, 30, after)
37-
if err != nil {
38-
return "", "", fmt.Errorf("failed to fetch analysis runs: %w", err)
39-
}
40-
41-
for _, run := range runs {
42-
if run.BranchName == branchName && run.Status == "SUCCESS" {
43-
return run.CommitOid, branchName, nil
44-
}
45-
}
46-
47-
if !pageInfo.HasNextPage || pageInfo.EndCursor == nil {
48-
break
49-
}
50-
after = pageInfo.EndCursor
34+
run, err := resolveRunFromCommits(ctx, client, branchName, commitLogFunc)
35+
if err != nil {
36+
return "", branchName, err
5137
}
52-
53-
return "", branchName, fmt.Errorf(
54-
"no analysis runs found for branch %q.\nTry: --default-branch, --commit <sha>, or push and analyze this branch first",
55-
branchName,
56-
)
38+
return run.CommitOid, branchName, nil
5739
}
5840

5941
// ResolveLatestRunForBranch finds the latest successful analysis run for a given branch name.
42+
// It walks recent commits on the branch and looks up each via the run(commitOid) API.
6043
// Returns the full AnalysisRun (which includes the ReportCard).
6144
func ResolveLatestRunForBranch(
6245
ctx context.Context,
6346
client *deepsource.Client,
6447
remote *vcs.RemoteData,
6548
branchName string,
49+
commitLogFunc func(branch string) ([]string, error),
6650
) (*runs.AnalysisRun, error) {
67-
const maxPages = 5
68-
var after *string
51+
return resolveRunFromCommits(ctx, client, branchName, commitLogFunc)
52+
}
6953

70-
for page := 0; page < maxPages; page++ {
71-
analysisRuns, pageInfo, err := client.GetAnalysisRuns(ctx, remote.Owner, remote.RepoName, remote.VCSProvider, 30, after)
72-
if err != nil {
73-
return nil, fmt.Errorf("failed to fetch analysis runs: %w", err)
74-
}
54+
// resolveRunFromCommits walks recent commits on a branch and returns the first
55+
// successful analysis run found via the run(commitOid) API.
56+
func resolveRunFromCommits(
57+
ctx context.Context,
58+
client *deepsource.Client,
59+
branchName string,
60+
commitLogFunc func(branch string) ([]string, error),
61+
) (*runs.AnalysisRun, error) {
62+
var commits []string
63+
var err error
64+
if commitLogFunc != nil {
65+
commits, err = commitLogFunc(branchName)
66+
} else {
67+
commits, err = getCommitLog(branchName)
68+
}
69+
if err != nil {
70+
return nil, fmt.Errorf("failed to get commit history for branch %q: %w", branchName, err)
71+
}
7572

76-
for i := range analysisRuns {
77-
if analysisRuns[i].BranchName == branchName && analysisRuns[i].Status == "SUCCESS" {
78-
return &analysisRuns[i], nil
79-
}
73+
for _, sha := range commits {
74+
run, err := client.GetRunByCommit(ctx, sha)
75+
if err != nil {
76+
continue
8077
}
81-
82-
if !pageInfo.HasNextPage || pageInfo.EndCursor == nil {
83-
break
78+
if run != nil && run.Status == "SUCCESS" && run.BranchName == branchName {
79+
return run, nil
8480
}
85-
after = pageInfo.EndCursor
8681
}
8782

8883
return nil, fmt.Errorf(
89-
"no successful analysis runs found for branch %q",
84+
"no analysis runs found for branch %q.\nTry: --default-branch, --commit <sha>, or push and analyze this branch first",
9085
branchName,
9186
)
9287
}
@@ -110,6 +105,15 @@ func ResolveCommitOid(commitOid string) string {
110105
return commitOid
111106
}
112107

108+
// ResolveBranchName returns the current branch name using the provided function,
109+
// or falls back to git rev-parse if branchNameFunc is nil.
110+
func ResolveBranchName(branchNameFunc func() (string, error)) (string, error) {
111+
if branchNameFunc != nil {
112+
return branchNameFunc()
113+
}
114+
return getCurrentBranch()
115+
}
116+
113117
func getCurrentBranch() (string, error) {
114118
cmd := exec.Command("git", "--no-pager", "rev-parse", "--abbrev-ref", "HEAD")
115119
var stdout, stderr bytes.Buffer
@@ -121,6 +125,22 @@ func getCurrentBranch() (string, error) {
121125
return strings.TrimSuffix(stdout.String(), "\n"), nil
122126
}
123127

128+
// getCommitLog returns recent commit SHAs for a branch using git log.
129+
func getCommitLog(branch string) ([]string, error) {
130+
cmd := exec.Command("git", "--no-pager", "log", branch, "--format=%H", "-n", "50")
131+
var stdout, stderr bytes.Buffer
132+
cmd.Stdout = &stdout
133+
cmd.Stderr = &stderr
134+
if err := cmd.Run(); err != nil {
135+
return nil, fmt.Errorf("%s: %s", err, strings.TrimSpace(stderr.String()))
136+
}
137+
lines := strings.TrimSpace(stdout.String())
138+
if lines == "" {
139+
return nil, nil
140+
}
141+
return strings.Split(lines, "\n"), nil
142+
}
143+
124144
// GetDefaultBranch returns the default branch name of the origin remote
125145
// (e.g. "master" or "main"). Returns an empty string if it cannot be determined.
126146
func GetDefaultBranch() string {

command/issues/issues.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func NewCmdIssuesWithDeps(deps *cmddeps.Deps) *cobra.Command {
8989
Use: "issues [flags]",
9090
Short: "View issues in a repository",
9191
Long: doc,
92-
RunE: func(cmd *cobra.Command, args []string) error {
92+
RunE: func(cmd *cobra.Command, _ []string) error {
9393
return opts.Run(cmd.Context())
9494
},
9595
}
@@ -122,18 +122,18 @@ func NewCmdIssuesWithDeps(deps *cmddeps.Deps) *cobra.Command {
122122
cmd.Flags().StringSliceVar(&opts.SourceFilters, "source", nil, "Filter by source (static, ai)")
123123

124124
// Completions
125-
_ = cmd.RegisterFlagCompletionFunc("repo", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
125+
_ = cmd.RegisterFlagCompletionFunc("repo", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
126126
return completion.RepoCompletionCandidates(), cobra.ShellCompDirectiveNoFileComp
127127
})
128-
_ = cmd.RegisterFlagCompletionFunc("output", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
128+
_ = cmd.RegisterFlagCompletionFunc("output", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
129129
return []string{
130130
"pretty\tPretty-printed grouped output",
131131
"table\tTable output",
132132
"json\tJSON output",
133133
"yaml\tYAML output",
134134
}, cobra.ShellCompDirectiveNoFileComp
135135
})
136-
_ = cmd.RegisterFlagCompletionFunc("category", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
136+
_ = cmd.RegisterFlagCompletionFunc("category", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
137137
return []string{
138138
"anti-pattern",
139139
"bug-risk",
@@ -145,7 +145,7 @@ func NewCmdIssuesWithDeps(deps *cmddeps.Deps) *cobra.Command {
145145
"documentation",
146146
}, cobra.ShellCompDirectiveNoFileComp
147147
})
148-
_ = cmd.RegisterFlagCompletionFunc("severity", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
148+
_ = cmd.RegisterFlagCompletionFunc("severity", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
149149
return []string{"critical", "major", "minor"}, cobra.ShellCompDirectiveNoFileComp
150150
})
151151

@@ -206,10 +206,12 @@ func (opts *IssuesOptions) Run(ctx context.Context) error {
206206
issuesList, err = client.GetIssues(ctx, remote.Owner, remote.RepoName, remote.VCSProvider, opts.LimitArg)
207207
default:
208208
var branchNameFunc func() (string, error)
209+
var commitLogFunc func(string) ([]string, error)
209210
if opts.deps != nil {
210211
branchNameFunc = opts.deps.BranchNameFunc
212+
commitLogFunc = opts.deps.CommitLogFunc
211213
}
212-
commitOid, branchName, resolveErr := cmdutil.ResolveLatestRun(ctx, client, remote, branchNameFunc)
214+
commitOid, branchName, resolveErr := cmdutil.ResolveLatestRun(ctx, client, remote, branchNameFunc, commitLogFunc)
213215
if resolveErr != nil {
214216
if branchName != "" && branchName == cmdutil.GetDefaultBranch() {
215217
issuesList, err = client.GetIssues(ctx, remote.Owner, remote.RepoName, remote.VCSProvider, opts.LimitArg)
@@ -368,7 +370,7 @@ type categoryGroup struct {
368370
// groupByCategoryAndCode groups issues first by category (preserving first-seen order),
369371
// then by issue code within each category.
370372
func groupByCategoryAndCode(list []issues.Issue) []categoryGroup {
371-
catOrder := []string{}
373+
var catOrder []string
372374
catMap := map[string]*categoryGroup{}
373375

374376
for _, issue := range list {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"run": null
3+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"run": {
3+
"runUid": "run-001",
4+
"commitOid": "deadbeef1234567890",
5+
"branchName": "main",
6+
"status": "SUCCESS",
7+
"createdAt": "2025-01-15T10:00:00Z",
8+
"finishedAt": "2025-01-15T10:05:00Z",
9+
"updatedAt": "2025-01-15T10:05:00Z",
10+
"summary": {
11+
"occurrencesIntroduced": 2,
12+
"occurrencesResolved": 0,
13+
"occurrencesSuppressed": 0
14+
},
15+
"reportCard": null
16+
}
17+
}

command/issues/tests/issues_test.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ func goldenPath(name string) string {
2222
func TestIssuesAutoDetectBranch(t *testing.T) {
2323
cfgMgr := testutil.CreateTestConfigManager(t, "test-token", "deepsource.com", "test@example.com")
2424
mock := testutil.MockQueryFunc(t, map[string]string{
25-
"analysisRuns(first:": goldenPath("analysis_runs_response.json"),
26-
"checks {": goldenPath("commit_scope_response.json"),
25+
"query GetRun(": goldenPath("get_run_response.json"),
26+
"checks {": goldenPath("commit_scope_response.json"),
2727
})
2828
client := deepsource.NewWithGraphQLClient(mock)
2929

@@ -35,6 +35,9 @@ func TestIssuesAutoDetectBranch(t *testing.T) {
3535
BranchNameFunc: func() (string, error) {
3636
return "main", nil
3737
},
38+
CommitLogFunc: func(branch string) ([]string, error) {
39+
return []string{"deadbeef1234567890"}, nil
40+
},
3841
}
3942

4043
cmd := issuesCmd.NewCmdIssuesWithDeps(deps)
@@ -55,7 +58,7 @@ func TestIssuesAutoDetectBranch(t *testing.T) {
5558
func TestIssuesNoRunsForBranch(t *testing.T) {
5659
cfgMgr := testutil.CreateTestConfigManager(t, "test-token", "deepsource.com", "test@example.com")
5760
mock := testutil.MockQueryFunc(t, map[string]string{
58-
"analysisRuns(first:": goldenPath("analysis_runs_response.json"),
61+
"query GetRun(": goldenPath("get_run_null_response.json"),
5962
})
6063
client := deepsource.NewWithGraphQLClient(mock)
6164

@@ -67,6 +70,9 @@ func TestIssuesNoRunsForBranch(t *testing.T) {
6770
BranchNameFunc: func() (string, error) {
6871
return "feature-no-runs", nil
6972
},
73+
CommitLogFunc: func(branch string) ([]string, error) {
74+
return []string{"aaaaaa1234567890"}, nil
75+
},
7076
}
7177

7278
cmd := issuesCmd.NewCmdIssuesWithDeps(deps)

command/metrics/metrics.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func NewCmdMetricsWithDeps(deps *cmddeps.Deps) *cobra.Command {
8686
Use: "metrics [flags]",
8787
Short: "View repository metrics",
8888
Long: doc,
89-
RunE: func(cmd *cobra.Command, args []string) error {
89+
RunE: func(cmd *cobra.Command, _ []string) error {
9090
return opts.Run(cmd.Context())
9191
},
9292
}
@@ -112,10 +112,10 @@ func NewCmdMetricsWithDeps(deps *cmddeps.Deps) *cobra.Command {
112112
cmd.Flags().IntVarP(&opts.LimitArg, "limit", "l", 30, "Maximum number of metrics to fetch")
113113

114114
// Completions
115-
_ = cmd.RegisterFlagCompletionFunc("repo", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
115+
_ = cmd.RegisterFlagCompletionFunc("repo", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
116116
return completion.RepoCompletionCandidates(), cobra.ShellCompDirectiveNoFileComp
117117
})
118-
_ = cmd.RegisterFlagCompletionFunc("output", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
118+
_ = cmd.RegisterFlagCompletionFunc("output", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
119119
return []string{
120120
"pretty\tPretty-printed grouped output",
121121
"table\tTabular output",
@@ -182,10 +182,12 @@ func (opts *MetricsOptions) Run(ctx context.Context) error {
182182
opts.repoMetrics, err = client.GetRepoMetrics(ctx, remote.Owner, remote.RepoName, remote.VCSProvider)
183183
default:
184184
var branchNameFunc func() (string, error)
185+
var commitLogFunc func(string) ([]string, error)
185186
if opts.deps != nil {
186187
branchNameFunc = opts.deps.BranchNameFunc
188+
commitLogFunc = opts.deps.CommitLogFunc
187189
}
188-
commitOid, branchName, resolveErr := cmdutil.ResolveLatestRun(ctx, client, remote, branchNameFunc)
190+
commitOid, branchName, resolveErr := cmdutil.ResolveLatestRun(ctx, client, remote, branchNameFunc, commitLogFunc)
189191
if resolveErr != nil {
190192
if branchName != "" && branchName == cmdutil.GetDefaultBranch() {
191193
opts.repoMetrics, err = client.GetRepoMetrics(ctx, remote.Owner, remote.RepoName, remote.VCSProvider)
@@ -277,7 +279,8 @@ func (opts *MetricsOptions) outputTable() error {
277279

278280
// Build metrics table
279281
header := []string{"Metric", "Key", "Value", "Threshold", "Status"}
280-
data := [][]string{header}
282+
data := make([][]string, 0, 4)
283+
data = append(data, header)
281284

282285
for _, m := range metricsList {
283286
for _, item := range m.Items {
@@ -441,10 +444,10 @@ func colorByStatus(text string, status string) string {
441444
func printChangesetLine(label string, counts metrics.ChangesetStatsCounts) {
442445
overall := intPtrVal(counts.Overall)
443446
overallCovered := intPtrVal(counts.OverallCovered)
444-
new := intPtrVal(counts.New)
447+
newCount := intPtrVal(counts.New)
445448
newCovered := intPtrVal(counts.NewCovered)
446449
fmt.Printf(" %s: %d covered of %d overall, %d covered of %d new\n",
447-
label, overallCovered, overall, newCovered, new)
450+
label, overallCovered, overall, newCovered, newCount)
448451
}
449452

450453
func intPtrVal(v *int) int {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"run": null
3+
}

0 commit comments

Comments
 (0)