diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f521252af..e07439d13d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ All notable changes to `src-cli` are documented in this file. ### Fixed +- Fixes a nil-panic that could be caused when `src batch [preview|apply]` would encounter a repository that was currently being cloned or is empty. + ### Removed ## 3.31.2 diff --git a/internal/batches/service/service.go b/internal/batches/service/service.go index cb2b43500c..324dde1d81 100644 --- a/internal/batches/service/service.go +++ b/internal/batches/service/service.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/cockroachdb/errors" + batcheslib "github.com/sourcegraph/sourcegraph/lib/batches" "github.com/sourcegraph/src-cli/internal/api" @@ -431,19 +432,23 @@ func (svc *Service) ResolveRepositories(ctx context.Context, spec *batcheslib.Ba return nil, errors.Wrapf(err, "resolving %q", on.String()) } + reposWithBranch := make([]*graphql.Repository, 0, len(repos)) + for _, repo := range repos { + if !repo.HasBranch() { + continue + } + reposWithBranch = append(reposWithBranch, repo) + } + var repoBatchIgnores map[*graphql.Repository][]string if !svc.allowIgnored { - repoBatchIgnores, err = svc.FindDirectoriesInRepos(ctx, ".batchignore", repos...) + repoBatchIgnores, err = svc.FindDirectoriesInRepos(ctx, ".batchignore", reposWithBranch...) if err != nil { return nil, err } } - for _, repo := range repos { - if !repo.HasBranch() { - continue - } - + for _, repo := range reposWithBranch { if other, ok := seen[repo.ID]; !ok { seen[repo.ID] = repo diff --git a/internal/batches/service/service_test.go b/internal/batches/service/service_test.go index 44d0cfb021..5aa98aedaa 100644 --- a/internal/batches/service/service_test.go +++ b/internal/batches/service/service_test.go @@ -10,6 +10,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + batcheslib "github.com/sourcegraph/sourcegraph/lib/batches" "github.com/sourcegraph/src-cli/internal/api" @@ -322,6 +323,70 @@ const testBatchIgnoreInRepos = `{ } ` +func TestResolveRepositories_RepoWithoutBranch(t *testing.T) { + spec := &batcheslib.BatchSpec{ + On: []batcheslib.OnQueryOrRepository{ + {RepositoriesMatchingQuery: "testquery"}, + }, + } + + client, done := mockGraphQLClient(testResolveRepositoriesNoBranch, testBatchIgnoreInReposNoBranch) + defer done() + + svc := &Service{client: client, allowIgnored: false} + + repos, err := svc.ResolveRepositories(context.Background(), spec) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if len(repos) != 1 { + t.Fatalf("wrong number of repos. want=%d, have=%d", 2, len(repos)) + } +} + +const testResolveRepositoriesNoBranch = `{ + "data": { + "search": { + "results": { + "results": [ + { + "__typename": "Repository", + "id": "UmVwb3NpdG9yeToxMw==", + "name": "bitbucket.sgdev.org/SOUR/automation-testing", + "url": "/bitbucket.sgdev.org/SOUR/automation-testing", + "externalRepository": { "serviceType": "bitbucketserver" }, + "defaultBranch": null + }, + { + "__typename": "Repository", + "id": "UmVwb3NpdG9yeTo0", + "name": "github.com/sourcegraph/automation-testing", + "url": "/github.com/sourcegraph/automation-testing", + "externalRepository": { "serviceType": "github" }, + "defaultBranch": null + }, + { + "__typename": "Repository", + "id": "UmVwb3NpdG9yeTo2MQ==", + "name": "gitlab.sgdev.org/sourcegraph/automation-testing", + "url": "/gitlab.sgdev.org/sourcegraph/automation-testing", + "externalRepository": { "serviceType": "gitlab" }, + "defaultBranch": { "name": "refs/heads/master", "target": { "oid": "3b79a5d479d2af9cfe91e0aad4e9dddca7278150" } } + } + ] + } + } + } +} +` + +const testBatchIgnoreInReposNoBranch = `{ + "data": { + "repo_0": { "results": { "results": [] } } + } +} +` + func TestService_FindDirectoriesInRepos(t *testing.T) { client, done := mockGraphQLClient(testFindDirectoriesInRepos) defer done()