Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.0.40
2.0.42
9 changes: 6 additions & 3 deletions command/cmdutil/resolve_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func resolveRunFromCommits(
}
if len(allRuns) == 0 {
return nil, fmt.Errorf(
"no analysis runs found for branch %q.\nTry: --default-branch, --commit <sha>, or --pr <number>",
"no analysis runs found for branch %q. Has this branch been pushed and analyzed on DeepSource?\nTry: --default-branch, --commit <sha>, or --pr <number>",
branchName,
)
}
Expand Down Expand Up @@ -223,6 +223,7 @@ type AutoBranchResult struct {
PRNumber int // >0 if a PR was detected for the branch
UseRepo bool // true when the caller should fall back to repo-level (default branch) data
Empty bool // true when there are no results (timeout, no completed runs)
Fallback bool // true when showing results from a previous completed run while a new analysis is in progress
}

// ResolveAutoBranch encapsulates the shared "default" branch resolution logic
Expand Down Expand Up @@ -286,12 +287,13 @@ func resolveWithPR(
return nil, fallbackErr
}
if completedRun == nil {
style.Infof(w, "No completed analysis runs found for branch %q.", branchName)
style.Infof(w, "Analysis is still in progress for branch %q. Try again shortly, or use --default-branch to see results from the default branch.", branchName)
result.Empty = true
return result, nil
}
style.Infof(w, "Analysis is running on commit %s. Showing results from the last analyzed commit (%s).", run.CommitOid[:8], completedRun.CommitOid[:8])
result.CommitOid = completedRun.CommitOid
result.Fallback = true
return result, nil
}
}
Expand Down Expand Up @@ -336,12 +338,13 @@ func resolveWithoutPR(
return nil, fallbackErr
}
if run == nil {
style.Infof(w, "No completed analysis runs found for branch %q.", branchName)
style.Infof(w, "Analysis is still in progress for branch %q. Try again shortly, or use --default-branch to see results from the default branch.", branchName)
result.Empty = true
return result, nil
}
style.Infof(w, "Analysis is running on commit %s. Showing results from the last analyzed commit (%s).", commitOid[:8], run.CommitOid[:8])
commitOid = run.CommitOid
result.Fallback = true
}
if IsRunTimedOut(finalStatus) {
style.Warnf(w, "Analysis timed out for branch %q.", branchName)
Expand Down
4 changes: 2 additions & 2 deletions command/cmdutil/resolve_run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,8 +491,8 @@ func TestResolveWithPR_PendingNoCompletedRuns(t *testing.T) {
if !got.Empty {
t.Fatal("expected Empty=true when no completed runs exist")
}
if !strings.Contains(buf.String(), "No completed analysis runs found") {
t.Errorf("expected 'no completed runs' message, got: %s", buf.String())
if !strings.Contains(buf.String(), "Analysis is still in progress") {
t.Errorf("expected 'analysis still in progress' message, got: %s", buf.String())
}
}

Expand Down
32 changes: 30 additions & 2 deletions command/issues/issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type IssuesOptions struct {
DefaultBranch bool
repoSlug string
autoDetectedBranch string
fallback bool
issues []issues.Issue
deps *cmddeps.Deps
client *deepsource.Client
Expand Down Expand Up @@ -254,6 +255,9 @@ func (opts *IssuesOptions) Run(ctx context.Context) error {
if err != nil {
return err
}
if issuesList == nil {
return nil
}

issuesList = opts.filterIssues(issuesList)

Expand All @@ -280,14 +284,15 @@ func (opts *IssuesOptions) Run(ctx context.Context) error {

func (opts *IssuesOptions) resolveIssues(ctx context.Context, client *deepsource.Client, remote *vcs.RemoteData) ([]issues.Issue, error) {
serverFilters := opts.buildServerFilters()
prFilters := opts.buildPRFilters()

var issuesList []issues.Issue
var err error
switch {
case opts.CommitOid != "":
issuesList, err = client.GetRunIssuesFlat(ctx, opts.CommitOid, serverFilters)
case opts.PRNumber > 0:
issuesList, err = client.GetPRIssues(ctx, remote.Owner, remote.RepoName, remote.VCSProvider, opts.PRNumber)
issuesList, err = client.GetPRIssues(ctx, remote.Owner, remote.RepoName, remote.VCSProvider, opts.PRNumber, prFilters)
case opts.DefaultBranch:
issuesList, err = client.GetIssues(ctx, remote.Owner, remote.RepoName, remote.VCSProvider)
default:
Expand All @@ -303,11 +308,12 @@ func (opts *IssuesOptions) resolveIssues(ctx context.Context, client *deepsource
return nil, nil
}
opts.autoDetectedBranch = ab.BranchName
opts.fallback = ab.Fallback
switch {
case ab.PRNumber > 0:
opts.PRNumber = ab.PRNumber
opts.CommitOid = ab.CommitOid
issuesList, err = client.GetPRIssues(ctx, remote.Owner, remote.RepoName, remote.VCSProvider, ab.PRNumber)
issuesList, err = client.GetPRIssues(ctx, remote.Owner, remote.RepoName, remote.VCSProvider, ab.PRNumber, prFilters)
case ab.UseRepo:
issuesList, err = client.GetIssues(ctx, remote.Owner, remote.RepoName, remote.VCSProvider)
default:
Expand Down Expand Up @@ -375,6 +381,25 @@ func (opts *IssuesOptions) buildServerFilters() issuesQuery.RunIssuesFlatParams
return params
}

// buildPRFilters returns PRIssuesListParams with server-side filters set
// for any filter that has exactly one value.
func (opts *IssuesOptions) buildPRFilters() issuesQuery.PRIssuesListParams {
var params issuesQuery.PRIssuesListParams
if len(opts.SourceFilters) == 1 {
v := normalizeEnumValue(opts.SourceFilters[0])
params.Source = &v
}
if len(opts.CategoryFilters) == 1 {
v := normalizeEnumValue(opts.CategoryFilters[0])
params.Category = &v
}
if len(opts.SeverityFilters) == 1 {
v := normalizeEnumValue(opts.SeverityFilters[0])
params.Severity = &v
}
return params
}

// --- Filters ---

func (opts *IssuesOptions) hasFilters() bool {
Expand Down Expand Up @@ -538,6 +563,9 @@ func groupIssuesByCategory(issuesList []issues.Issue) map[string][]issues.Issue

func (opts *IssuesOptions) outputHuman(_ context.Context) error {
if len(opts.issues) == 0 {
if opts.fallback {
return nil
}
if opts.hasFilters() {
style.Infof(opts.stdout(), "No issues matched the provided filters in %s on %s.", opts.repoSlug, opts.scopeLabel())
} else {
Expand Down
8 changes: 4 additions & 4 deletions command/issues/tests/golden_files/pr_scope_output.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"description": "Return value of io.ReadAll is not checked for errors",
"category": "BUG_RISK",
"severity": "MAJOR",
"source": "",
"analyzer": "go"
"source": "static",
"analyzer": ""
},
{
"path": "internal/vcs/remotes.go",
Expand All @@ -20,7 +20,7 @@
"description": "Constructing HTTP request with user-controlled URL allows SSRF",
"category": "SECURITY",
"severity": "MAJOR",
"source": "",
"analyzer": "go"
"source": "ai",
"analyzer": ""
}
]
38 changes: 16 additions & 22 deletions command/issues/tests/golden_files/pr_scope_response.json
Original file line number Diff line number Diff line change
@@ -1,45 +1,39 @@
{
"repository": {
"pullRequest": {
"issueOccurrences": {
"issues": {
"edges": [
{
"node": {
"source": "static",
"path": "cmd/deepsource/main.go",
"beginLine": 42,
"endLine": 42,
"title": "Unchecked error return value of os.ReadFile",
"issue": {
"shortcode": "GO-W1007",
"shortDescription": "Return value of io.ReadAll is not checked for errors",
"category": "BUG_RISK",
"severity": "MAJOR",
"analyzer": {
"name": "Go",
"shortcode": "go"
}
}
"shortcode": "GO-W1007",
"category": "BUG_RISK",
"severity": "MAJOR",
"explanation": "Return value of io.ReadAll is not checked for errors"
}
},
{
"node": {
"source": "ai",
"path": "internal/vcs/remotes.go",
"beginLine": 87,
"endLine": 91,
"title": "HTTP request built with user-controlled URL",
"issue": {
"shortcode": "GO-S1010",
"shortDescription": "Constructing HTTP request with user-controlled URL allows SSRF",
"category": "SECURITY",
"severity": "MAJOR",
"analyzer": {
"name": "Go",
"shortcode": "go"
}
}
"shortcode": "GO-S1010",
"category": "SECURITY",
"severity": "MAJOR",
"explanation": "Constructing HTTP request with user-controlled URL allows SSRF"
}
}
]
],
"pageInfo": {
"hasNextPage": false,
"endCursor": null
}
}
}
}
Expand Down
12 changes: 6 additions & 6 deletions command/issues/tests/issues_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ func TestIssuesAutoDetectBranch(t *testing.T) {
func TestIssuesAutoDetectPR(t *testing.T) {
cfgMgr := testutil.CreateTestConfigManager(t, "test-token", "deepsource.com", "test@example.com")
mock := testutil.MockQueryFunc(t, map[string]string{
"pullRequests(": goldenPath("get_pr_by_branch_found_response.json"),
"query GetAnalysisRuns(": goldenPath("get_analysis_runs_response.json"),
"issueOccurrences(first:": goldenPath("pr_scope_response.json"),
"pullRequests(": goldenPath("get_pr_by_branch_found_response.json"),
"query GetAnalysisRuns(": goldenPath("get_analysis_runs_response.json"),
"query GetPRIssues(": goldenPath("pr_scope_response.json"),
})
client := deepsource.NewWithGraphQLClient(mock)

Expand Down Expand Up @@ -185,7 +185,7 @@ func TestIssuesCommitScope(t *testing.T) {
func TestIssuesPRScope(t *testing.T) {
cfgMgr := testutil.CreateTestConfigManager(t, "test-token", "deepsource.com", "test@example.com")
mock := testutil.MockQueryFunc(t, map[string]string{
"issueOccurrences(first:": goldenPath("pr_scope_response.json"),
"query GetPRIssues(": goldenPath("pr_scope_response.json"),
})
client := deepsource.NewWithGraphQLClient(mock)

Expand Down Expand Up @@ -447,8 +447,8 @@ func TestIssuesRunInProgress(t *testing.T) {
got := buf.String()
// In non-TTY (test runner), in-progress auto-falls back to last completed run.
// Since the mock only has a PENDING run, no completed run is found.
if !strings.Contains(got, "No completed analysis runs found") {
t.Errorf("expected fallback 'no completed runs' message, got: %q", got)
if !strings.Contains(got, "Analysis is still in progress") {
t.Errorf("expected 'analysis still in progress' message, got: %q", got)
}
}

Expand Down
5 changes: 5 additions & 0 deletions command/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type MetricsOptions struct {
LimitArg int
repoSlug string
autoDetectedBranch string
fallback bool
repoMetrics []metrics.RepositoryMetric
runMetrics *metrics.RunMetrics
prMetrics *metrics.PRMetrics
Expand Down Expand Up @@ -246,6 +247,7 @@ func (opts *MetricsOptions) resolveMetrics(ctx context.Context, client *deepsour
return nil
}
opts.autoDetectedBranch = ab.BranchName
opts.fallback = ab.Fallback
switch {
case ab.PRNumber > 0:
opts.PRNumber = ab.PRNumber
Expand Down Expand Up @@ -359,6 +361,9 @@ func (opts *MetricsOptions) outputHuman() error {
w := opts.stdout()

if len(metricsList) == 0 {
if opts.fallback {
return nil
}
style.Infof(w, "No metrics found in %s on %s.", opts.repoSlug, opts.scopeLabel())
return nil
}
Expand Down
4 changes: 2 additions & 2 deletions command/reportcard/reportcard.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func (opts *ReportCardOptions) resolveByPR(ctx context.Context, client *deepsour
return err
}
if completed == nil {
style.Infof(opts.stdout(), "No completed analysis runs found for branch %q.", branch)
style.Infof(opts.stdout(), "Analysis is still in progress for branch %q. Try again shortly, or use --default-branch to see results from the default branch.", branch)
return nil
}
style.Infof(opts.stdout(), "Analysis is running on commit %s. Showing results from the last analyzed commit (%s).", run.CommitOid[:8], completed.CommitOid[:8])
Expand Down Expand Up @@ -276,7 +276,7 @@ func (opts *ReportCardOptions) resolveByCurrentBranch(ctx context.Context, clien
return err
}
if completed == nil {
style.Infof(opts.stdout(), "No completed analysis runs found for branch %q.", branchName)
style.Infof(opts.stdout(), "Analysis is still in progress for branch %q. Try again shortly, or use --default-branch to see results from the default branch.", branchName)
return nil
}
style.Infof(opts.stdout(), "Analysis is running on commit %s. Showing results from the last analyzed commit (%s).", run.CommitOid[:8], completed.CommitOid[:8])
Expand Down
5 changes: 5 additions & 0 deletions command/vulnerabilities/vulnerabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type VulnerabilitiesOptions struct {
SeverityFilters []string
repoSlug string
autoDetectedBranch string
fallback bool
repoVulns []vulnerabilities.VulnerabilityOccurrence
runVulns *vulnerabilities.RunVulns
prVulns *vulnerabilities.PRVulns
Expand Down Expand Up @@ -234,6 +235,7 @@ func (opts *VulnerabilitiesOptions) resolveVulnerabilities(ctx context.Context,
return nil
}
opts.autoDetectedBranch = ab.BranchName
opts.fallback = ab.Fallback
switch {
case ab.PRNumber > 0:
opts.PRNumber = ab.PRNumber
Expand Down Expand Up @@ -342,6 +344,9 @@ func (opts *VulnerabilitiesOptions) outputHuman() error {
vulnsList := opts.getVulns()

if len(vulnsList) == 0 {
if opts.fallback {
return nil
}
if opts.hasFilters() {
style.Infof(opts.stdout(), "No vulnerabilities matched the provided filters in %s on %s.", opts.repoSlug, opts.scopeLabel())
} else {
Expand Down
13 changes: 6 additions & 7 deletions deepsource/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,13 +266,12 @@ func (c Client) GetRunIssuesFlat(ctx context.Context, commitOid string, filters
}

// Auto-paginates to fetch all results.
func (c Client) GetPRIssues(ctx context.Context, owner, repoName, provider string, prNumber int) ([]issues.Issue, error) {
req := issuesQuery.NewPRIssuesListRequest(c.gqlWrapper, issuesQuery.PRIssuesListParams{
Owner: owner,
RepoName: repoName,
Provider: provider,
PRNumber: prNumber,
})
func (c Client) GetPRIssues(ctx context.Context, owner, repoName, provider string, prNumber int, filters issuesQuery.PRIssuesListParams) ([]issues.Issue, error) {
filters.Owner = owner
filters.RepoName = repoName
filters.Provider = provider
filters.PRNumber = prNumber
req := issuesQuery.NewPRIssuesListRequest(c.gqlWrapper, filters)
res, err := req.Do(ctx)
if err != nil {
return nil, err
Expand Down
Loading
Loading