Skip to content

Commit e431275

Browse files
committed
Handle partial repo list warnings
1 parent 65b9840 commit e431275

File tree

3 files changed

+102
-44
lines changed

3 files changed

+102
-44
lines changed

cmd/src/repos_list.go

Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -82,37 +82,12 @@ func listRepositories(ctx context.Context, client api.Client, params reposListOp
8282

8383
errors := api.NewGraphQlErrors(result.Errors)
8484
if len(repos) > 0 {
85-
return filterRepositoriesWithErrors(repos, errors), errors, nil
85+
return repos, errors, nil
8686
}
8787

8888
return nil, nil, errors
8989
}
9090

91-
func filterRepositoriesWithErrors(repos []Repository, errors api.GraphQlErrors) []Repository {
92-
if len(errors) == 0 || len(repos) == 0 {
93-
return repos
94-
}
95-
96-
skip := make(map[int]struct{}, len(errors))
97-
for _, graphQLError := range errors {
98-
index, ok := gqlRepositoryErrorIndex(graphQLError)
99-
if !ok || index >= len(repos) {
100-
continue
101-
}
102-
skip[index] = struct{}{}
103-
}
104-
105-
filtered := make([]Repository, 0, len(repos))
106-
for i, repo := range repos {
107-
if _, ok := skip[i]; ok {
108-
continue
109-
}
110-
filtered = append(filtered, repo)
111-
}
112-
113-
return filtered
114-
}
115-
11691
func gqlErrorPathString(pathSegment any) (string, bool) {
11792
value, ok := pathSegment.(string)
11893
return value, ok
@@ -130,22 +105,52 @@ func gqlErrorIndex(pathSegment any) (int, bool) {
130105
}
131106
}
132107

133-
func gqlRepositoryErrorIndex(graphQLError *api.GraphQlError) (int, bool) {
108+
func gqlWarningPath(graphQLError *api.GraphQlError) string {
134109
path, err := graphQLError.Path()
135-
if err != nil || len(path) < 3 {
136-
return 0, false
110+
if err != nil || len(path) == 0 {
111+
return ""
137112
}
138113

139-
pathRoot, ok := gqlErrorPathString(path[0])
140-
if !ok || pathRoot != "repositories" {
141-
return 0, false
114+
var b strings.Builder
115+
for _, pathSegment := range path {
116+
if segment, ok := gqlErrorPathString(pathSegment); ok {
117+
if b.Len() > 0 {
118+
b.WriteByte('.')
119+
}
120+
b.WriteString(segment)
121+
continue
122+
}
123+
124+
if index, ok := gqlErrorIndex(pathSegment); ok {
125+
fmt.Fprintf(&b, "[%d]", index)
126+
}
142127
}
143-
pathCollection, ok := gqlErrorPathString(path[1])
144-
if !ok || pathCollection != "nodes" {
145-
return 0, false
128+
129+
return b.String()
130+
}
131+
132+
func gqlWarningMessage(graphQLError *api.GraphQlError) string {
133+
message, err := graphQLError.Message()
134+
if err != nil || message == "" {
135+
return graphQLError.Error()
146136
}
137+
return message
138+
}
147139

148-
return gqlErrorIndex(path[2])
140+
func formatRepositoryListWarnings(warnings api.GraphQlErrors) string {
141+
var b strings.Builder
142+
fmt.Fprintf(&b, "warnings: %d errors during listing\n", len(warnings))
143+
for _, warning := range warnings {
144+
path := gqlWarningPath(warning)
145+
message := gqlWarningMessage(warning)
146+
if path != "" {
147+
fmt.Fprintf(&b, "%s - %s\n", path, message)
148+
} else {
149+
fmt.Fprintf(&b, "%s\n", message)
150+
}
151+
fmt.Fprintf(&b, "%s\n", warning.Error())
152+
}
153+
return b.String()
149154
}
150155

151156
func init() {
@@ -241,10 +246,7 @@ Examples:
241246
}
242247
if len(warnings) > 0 {
243248
if *verbose {
244-
fmt.Fprintf(flagSet.Output(), "warning: %d errors during listing:\n", len(warnings))
245-
for _, warning := range warnings {
246-
fmt.Fprintln(flagSet.Output(), warning.Error())
247-
}
249+
fmt.Fprint(flagSet.Output(), formatRepositoryListWarnings(warnings))
248250
} else {
249251
fmt.Fprintf(flagSet.Output(), "warning: %d errors during listing; rerun with -v to inspect them\n", len(warnings))
250252
}

cmd/src/repos_list_test.go

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@ package main
22

33
import (
44
"context"
5+
"encoding/json"
56
"strings"
67
"testing"
78

9+
"github.com/sourcegraph/src-cli/internal/api"
810
mockapi "github.com/sourcegraph/src-cli/internal/api/mock"
911
"github.com/stretchr/testify/mock"
1012
"github.com/stretchr/testify/require"
1113
)
1214

13-
func TestListRepositoriesSkipsRepositoryWhenDefaultBranchCannotBeResolved(t *testing.T) {
15+
func TestListRepositoriesPreservesRepositoryWhenDefaultBranchCannotBeResolved(t *testing.T) {
1416
client := new(mockapi.Client)
1517
request := &mockapi.Request{Response: `{
1618
"data": {
@@ -92,15 +94,18 @@ func TestListRepositoriesSkipsRepositoryWhenDefaultBranchCannotBeResolved(t *tes
9294
orderBy: "REPOSITORY_NAME",
9395
})
9496
require.NoError(t, err)
95-
require.Len(t, repos, 1)
97+
require.Len(t, repos, 2)
9698
require.Len(t, warnings, 1)
9799
require.Equal(t, "github.com/sourcegraph/ok", repos[0].Name)
100+
require.Equal(t, GitRef{Name: "refs/heads/main", DisplayName: "main"}, repos[0].DefaultBranch)
101+
require.Equal(t, "github.com/sourcegraph/broken", repos[1].Name)
102+
require.Equal(t, GitRef{}, repos[1].DefaultBranch)
98103
require.ErrorContains(t, warnings[0], "failed to resolve HEAD for github.com/sourcegraph/broken")
99104
client.AssertExpectations(t)
100105
request.AssertExpectations(t)
101106
}
102107

103-
func TestListRepositoriesFiltersNodeScopedFieldErrors(t *testing.T) {
108+
func TestListRepositoriesPreservesRepositoriesForNodeScopedFieldErrors(t *testing.T) {
104109
client := new(mockapi.Client)
105110
request := &mockapi.Request{Response: `{
106111
"data": {
@@ -158,8 +163,9 @@ func TestListRepositoriesFiltersNodeScopedFieldErrors(t *testing.T) {
158163
orderBy: "REPOSITORY_NAME",
159164
})
160165
require.NoError(t, err)
161-
require.Empty(t, repos)
166+
require.Len(t, repos, 1)
162167
require.Len(t, warnings, 1)
168+
require.Equal(t, "github.com/sourcegraph/ok", repos[0].Name)
163169
require.ErrorContains(t, warnings[0], "viewer permissions unavailable")
164170
client.AssertExpectations(t)
165171
request.AssertExpectations(t)
@@ -230,3 +236,36 @@ func TestListRepositoriesReturnsWarningsWithDataForNonNodeErrors(t *testing.T) {
230236
client.AssertExpectations(t)
231237
request.AssertExpectations(t)
232238
}
239+
240+
func TestFormatRepositoryListWarnings(t *testing.T) {
241+
warnings := api.NewGraphQlErrors([]json.RawMessage{json.RawMessage(`{
242+
"message": "failed to resolve HEAD for github.com/sourcegraph/broken",
243+
"path": ["repositories", "nodes", 1, "defaultBranch"]
244+
}`)})
245+
246+
require.Equal(t, `warnings: 1 errors during listing
247+
repositories.nodes[1].defaultBranch - failed to resolve HEAD for github.com/sourcegraph/broken
248+
{
249+
"message": "failed to resolve HEAD for github.com/sourcegraph/broken",
250+
"path": [
251+
"repositories",
252+
"nodes",
253+
1,
254+
"defaultBranch"
255+
]
256+
}
257+
`, formatRepositoryListWarnings(warnings))
258+
}
259+
260+
func TestFormatRepositoryListWarningsWithoutPath(t *testing.T) {
261+
warnings := api.NewGraphQlErrors([]json.RawMessage{json.RawMessage(`{
262+
"message": "listing timed out"
263+
}`)})
264+
265+
require.Equal(t, `warnings: 1 errors during listing
266+
listing timed out
267+
{
268+
"message": "listing timed out"
269+
}
270+
`, formatRepositoryListWarnings(warnings))
271+
}

internal/api/errors.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,23 @@ func (gg GraphQlErrors) Error() string {
4343
// GraphQlError wraps a raw JSON error returned from a GraphQL endpoint.
4444
type GraphQlError struct{ v any }
4545

46+
// Message returns the GraphQL error message, if one was set on the error.
47+
func (g *GraphQlError) Message() (string, error) {
48+
e, ok := g.v.(map[string]any)
49+
if !ok {
50+
return "", errors.Errorf("unexpected GraphQL error of type %T", g.v)
51+
}
52+
53+
if e["message"] == nil {
54+
return "", nil
55+
}
56+
message, ok := e["message"].(string)
57+
if !ok {
58+
return "", errors.Errorf("unexpected message of type %T", e["message"])
59+
}
60+
return message, nil
61+
}
62+
4663
// Code returns the GraphQL error code, if one was set on the error.
4764
func (g *GraphQlError) Code() (string, error) {
4865
ext, err := g.Extensions()

0 commit comments

Comments
 (0)