mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 19:51:50 +00:00
Revert "Revert "external services: Change default github.repositoryQuery to ["none"] (#2792)" (#2890)" (#2891)
This reverts the reverted commit 2da1d2f449.
This commit is contained in:
parent
f8394a92e0
commit
da3743ece3
@ -13,6 +13,8 @@ All notable changes to Sourcegraph are documented in this file.
|
||||
|
||||
- The symbols sidebar now only shows symbols defined in the current file or directory.
|
||||
|
||||
- The default `github.repositoryQuery` of a [GitHub external service configuration](https://docs.sourcegraph.com/admin/external_service/github#configuration) has been changed to `["none"]`. Existing configurations that had this field unset will be migrated to have the previous default explicitly set (`["affiliated", "public"]`).
|
||||
|
||||
### Fixed
|
||||
|
||||
### Removed
|
||||
|
||||
@ -39,10 +39,27 @@ func main() {
|
||||
env.HandleHelpFlag()
|
||||
tracer.Init()
|
||||
|
||||
clock := func() time.Time { return time.Now().UTC() }
|
||||
|
||||
// Syncing relies on access to frontend and git-server, so wait until they started up.
|
||||
api.WaitForFrontend(ctx)
|
||||
gitserver.DefaultClient.WaitForGitServers(ctx)
|
||||
|
||||
db, err := repos.NewDB(repos.NewDSNFromEnv())
|
||||
if err != nil {
|
||||
log.Fatalf("failed to initalise db store: %v", err)
|
||||
}
|
||||
|
||||
store := repos.NewDBStore(ctx, db, sql.TxOptions{Isolation: sql.LevelSerializable})
|
||||
|
||||
for _, m := range []repos.Migration{
|
||||
repos.GithubSetDefaultRepositoryQueryMigration(clock),
|
||||
} {
|
||||
if err := m.Run(ctx, store); err != nil {
|
||||
log.Fatalf("failed to run migration: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
var kinds []string
|
||||
if syncerEnabled {
|
||||
kinds = append(kinds, "GITHUB")
|
||||
@ -104,16 +121,9 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
store repos.Store
|
||||
syncer *repos.Syncer
|
||||
)
|
||||
var syncer *repos.Syncer
|
||||
|
||||
if syncerEnabled {
|
||||
db, err := repos.NewDB(repos.NewDSNFromEnv())
|
||||
if err != nil {
|
||||
log.Fatalf("failed to initalise db store: %v", err)
|
||||
}
|
||||
|
||||
diffs := make(chan repos.Diff)
|
||||
|
||||
@ -123,11 +133,8 @@ func main() {
|
||||
httpcli.NewCachedTransportOpt(httputil.Cache, true),
|
||||
)
|
||||
|
||||
store = repos.NewDBStore(ctx, db, sql.TxOptions{Isolation: sql.LevelSerializable})
|
||||
src := repos.NewExternalServicesSourcer(store, cliFactory)
|
||||
syncer = repos.NewSyncer(store, src, diffs, func() time.Time {
|
||||
return time.Now().UTC()
|
||||
})
|
||||
syncer = repos.NewSyncer(store, src, diffs, clock)
|
||||
|
||||
log15.Info("starting new syncer", "external service kinds", kinds)
|
||||
go func() { log.Fatal(syncer.Run(ctx, repos.GetUpdateInterval(), kinds...)) }()
|
||||
|
||||
@ -423,13 +423,7 @@ func (c *githubConnection) listAllRepositories(ctx context.Context) ([]*github.R
|
||||
|
||||
repositoryQueries := c.config.RepositoryQuery
|
||||
if len(repositoryQueries) == 0 {
|
||||
// Users need to specify ["none"] to disable mirroring.
|
||||
if c.githubDotCom {
|
||||
// Doesn't make sense to try to enumerate all public repos on github.com
|
||||
repositoryQueries = []string{"affiliated"}
|
||||
} else {
|
||||
repositoryQueries = []string{"public", "affiliated"}
|
||||
}
|
||||
repositoryQueries = append(repositoryQueries, "none")
|
||||
}
|
||||
|
||||
for _, repositoryQuery := range repositoryQueries {
|
||||
|
||||
@ -40,6 +40,8 @@ func TestIntegration(t *testing.T) {
|
||||
{"DBStore/UpsertRepos", testStoreUpsertRepos(store)},
|
||||
{"DBStore/ListRepos", testStoreListRepos(store)},
|
||||
{"Syncer/Sync", testSyncerSync(store)},
|
||||
{"Migrations/GithubSetDefaultRepositoryQuery",
|
||||
testGithubSetDefaultRepositoryQueryMigration(store)},
|
||||
} {
|
||||
t.Run(tc.name, tc.test)
|
||||
}
|
||||
|
||||
125
cmd/repo-updater/repos/migrations.go
Normal file
125
cmd/repo-updater/repos/migrations.go
Normal file
@ -0,0 +1,125 @@
|
||||
package repos
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sourcegraph/jsonx"
|
||||
"github.com/sourcegraph/sourcegraph/pkg/extsvc/github"
|
||||
"github.com/sourcegraph/sourcegraph/pkg/jsonc"
|
||||
"github.com/sourcegraph/sourcegraph/schema"
|
||||
)
|
||||
|
||||
// A Migration performs a data migration in the given Store,
|
||||
// returning an error in case of failure.
|
||||
type Migration func(context.Context, Store) error
|
||||
|
||||
// Run is an utility method to aid readability of calling code.
|
||||
func (m Migration) Run(ctx context.Context, s Store) error {
|
||||
return m(ctx, s)
|
||||
}
|
||||
|
||||
// GithubSetDefaultRepositoryQueryMigration returns a Migration that changes all
|
||||
// configurations of GitHub external services which have an empty "repositoryQuery"
|
||||
// migration to its explicit default.
|
||||
func GithubSetDefaultRepositoryQueryMigration(clock func() time.Time) Migration {
|
||||
return transactional(func(ctx context.Context, s Store) error {
|
||||
const prefix = "migrate.github-set-default-repository-query"
|
||||
|
||||
svcs, err := s.ListExternalServices(ctx, "github")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "%s.list-external-services", prefix)
|
||||
}
|
||||
|
||||
for _, svc := range svcs {
|
||||
var c schema.GitHubConnection
|
||||
if err := jsonc.Unmarshal(svc.Config, &c); err != nil {
|
||||
return fmt.Errorf("%s: external service id=%d config unmarshaling error: %s", prefix, svc.ID, err)
|
||||
}
|
||||
|
||||
if len(c.RepositoryQuery) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
baseURL, err := url.Parse(c.Url)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "%s.parse-url", prefix)
|
||||
}
|
||||
|
||||
_, githubDotCom := github.APIRoot(NormalizeBaseURL(baseURL))
|
||||
|
||||
c.RepositoryQuery = append(c.RepositoryQuery, "affiliated")
|
||||
if !githubDotCom {
|
||||
c.RepositoryQuery = append(c.RepositoryQuery, "public")
|
||||
}
|
||||
|
||||
edits, _, err := jsonx.ComputePropertyEdit(svc.Config,
|
||||
jsonx.PropertyPath("repositoryQuery"),
|
||||
c.RepositoryQuery,
|
||||
nil,
|
||||
jsonx.FormatOptions{InsertSpaces: true, TabSize: 2},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "%s.compute-property-edit", prefix)
|
||||
}
|
||||
|
||||
edited, err := jsonx.ApplyEdits(svc.Config, edits...)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "%s.apply-edits", prefix)
|
||||
}
|
||||
|
||||
svc.Config = edited
|
||||
svc.UpdatedAt = clock()
|
||||
}
|
||||
|
||||
if err = s.UpsertExternalServices(ctx, svcs...); err != nil {
|
||||
return errors.Wrapf(err, "%s.upsert-external-services", prefix)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ErrNoTransactor is returned by a Migration returned by
|
||||
// NewTxMigration when it takes in a Store that can't be
|
||||
// interface upgraded to a Transactor.
|
||||
var ErrNoTransactor = errors.New("Store is not a Transactor")
|
||||
|
||||
// transactional wraps a Migration with transactional semantics. It retries
|
||||
// the migration when a serialization transactional error is returned
|
||||
// (i.e. ERROR: could not serialize access due to concurrent update)
|
||||
func transactional(m Migration) Migration {
|
||||
return func(ctx context.Context, s Store) (err error) {
|
||||
tr, ok := s.(Transactor)
|
||||
if !ok {
|
||||
return ErrNoTransactor
|
||||
}
|
||||
|
||||
for {
|
||||
if err = transact(ctx, tr, m); err == nil || !isSerializationError(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func transact(ctx context.Context, tr Transactor, m Migration) (err error) {
|
||||
var tx TxStore
|
||||
if tx, err = tr.Transact(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer tx.Done(&err)
|
||||
|
||||
return m(ctx, tx)
|
||||
}
|
||||
|
||||
func isSerializationError(err error) bool {
|
||||
return strings.Contains(err.Error(),
|
||||
"could not serialize access due to concurrent update")
|
||||
}
|
||||
162
cmd/repo-updater/repos/migrations_test.go
Normal file
162
cmd/repo-updater/repos/migrations_test.go
Normal file
@ -0,0 +1,162 @@
|
||||
package repos_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sourcegraph/jsonx"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/repo-updater/repos"
|
||||
)
|
||||
|
||||
func TestGithubSetDefaultRepositoryQueryMigration(t *testing.T) {
|
||||
t.Parallel()
|
||||
testGithubSetDefaultRepositoryQueryMigration(new(repos.FakeStore))(t)
|
||||
}
|
||||
|
||||
func testGithubSetDefaultRepositoryQueryMigration(store repos.Store) func(*testing.T) {
|
||||
githubDotCom := repos.ExternalService{
|
||||
Kind: "GITHUB",
|
||||
DisplayName: "Github.com - Test",
|
||||
Config: jsonFormat(`
|
||||
{
|
||||
// Some comment
|
||||
"url": "https://github.com"
|
||||
}
|
||||
`),
|
||||
}
|
||||
|
||||
githubNone := repos.ExternalService{
|
||||
Kind: "GITHUB",
|
||||
DisplayName: "Github.com - Test",
|
||||
Config: jsonFormat(`
|
||||
{
|
||||
// Some comment
|
||||
"url": "https://github.com",
|
||||
"repositoryQuery": ["none"]
|
||||
}
|
||||
`),
|
||||
}
|
||||
|
||||
githubEnterprise := repos.ExternalService{
|
||||
Kind: "GITHUB",
|
||||
DisplayName: "Github Enterprise - Test",
|
||||
Config: jsonFormat(`
|
||||
{
|
||||
// Some comment
|
||||
"url": "https://github.mycorp.com"
|
||||
}
|
||||
`),
|
||||
}
|
||||
|
||||
gitlab := repos.ExternalService{
|
||||
Kind: "GITLAB",
|
||||
DisplayName: "Gitlab - Test",
|
||||
Config: jsonFormat(`{"url": "https://gitlab.com"}`),
|
||||
}
|
||||
|
||||
clock := repos.NewFakeClock(time.Now(), 0)
|
||||
|
||||
return func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
stored repos.ExternalServices
|
||||
assert repos.ExternalServicesAssertion
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "non-github services are left unchanged",
|
||||
stored: repos.ExternalServices{&gitlab},
|
||||
assert: repos.Assert.ExternalServicesEqual(&gitlab),
|
||||
err: "<nil>",
|
||||
},
|
||||
{
|
||||
name: "github services with repositoryQuery set are left unchanged",
|
||||
stored: repos.ExternalServices{&githubNone},
|
||||
assert: repos.Assert.ExternalServicesEqual(&githubNone),
|
||||
err: "<nil>",
|
||||
},
|
||||
{
|
||||
name: "github.com services are set to affiliated",
|
||||
stored: repos.ExternalServices{&githubDotCom},
|
||||
assert: repos.Assert.ExternalServicesEqual(
|
||||
githubDotCom.With(
|
||||
repos.Opt.ExternalServiceModifiedAt(clock.Time(0)),
|
||||
func(e *repos.ExternalService) {
|
||||
e.Config = jsonFormat(`
|
||||
{
|
||||
// Some comment
|
||||
"url": "https://github.com",
|
||||
"repositoryQuery": ["affiliated"]
|
||||
}
|
||||
`)
|
||||
},
|
||||
),
|
||||
),
|
||||
err: "<nil>",
|
||||
},
|
||||
{
|
||||
name: "github enterprise services are set to public and affiliated",
|
||||
stored: repos.ExternalServices{&githubEnterprise},
|
||||
assert: repos.Assert.ExternalServicesEqual(
|
||||
githubEnterprise.With(
|
||||
repos.Opt.ExternalServiceModifiedAt(clock.Time(0)),
|
||||
func(e *repos.ExternalService) {
|
||||
e.Config = jsonFormat(`
|
||||
{
|
||||
// Some comment
|
||||
"url": "https://github.mycorp.com",
|
||||
"repositoryQuery": ["affiliated", "public"]
|
||||
}
|
||||
`)
|
||||
},
|
||||
),
|
||||
),
|
||||
err: "<nil>",
|
||||
},
|
||||
} {
|
||||
tc := tc
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run(tc.name, transact(ctx, store, func(t testing.TB, tx repos.Store) {
|
||||
if err := tx.UpsertExternalServices(ctx, tc.stored.Clone()...); err != nil {
|
||||
t.Errorf("failed to prepare store: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
err := repos.GithubSetDefaultRepositoryQueryMigration(clock.Now).Run(ctx, tx)
|
||||
if have, want := fmt.Sprint(err), tc.err; have != want {
|
||||
t.Errorf("error:\nhave: %v\nwant: %v", have, want)
|
||||
}
|
||||
|
||||
es, err := tx.ListExternalServices(ctx)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if tc.assert != nil {
|
||||
tc.assert(t, es)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func jsonFormat(s string) string {
|
||||
opts := jsonx.FormatOptions{
|
||||
InsertSpaces: true,
|
||||
TabSize: 2,
|
||||
}
|
||||
|
||||
formatted, err := jsonx.ApplyEdits(s, jsonx.Format(s, opts)...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return formatted
|
||||
}
|
||||
@ -46,28 +46,6 @@ func testStoreListExternalServices(store repos.Store) func(*testing.T) {
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
equal := func(es ...*repos.ExternalService) func(testing.TB, repos.ExternalServices) {
|
||||
want := repos.ExternalServices(es)
|
||||
return func(t testing.TB, have repos.ExternalServices) {
|
||||
have.Apply(repos.Opt.ExternalServiceID(0)) // Exclude auto-generated IDs from equality tests
|
||||
if !reflect.DeepEqual(have, want) {
|
||||
t.Errorf("external services: %s", cmp.Diff(have, want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
orderedBy := func(ord func(a, b *repos.ExternalService) bool) func(testing.TB, repos.ExternalServices) {
|
||||
return func(t testing.TB, have repos.ExternalServices) {
|
||||
want := have.Clone()
|
||||
sort.Slice(want, func(i, j int) bool {
|
||||
return ord(want[i], want[j])
|
||||
})
|
||||
if !reflect.DeepEqual(have, want) {
|
||||
t.Errorf("external services: %s", cmp.Diff(have, want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
@ -75,20 +53,20 @@ func testStoreListExternalServices(store repos.Store) func(*testing.T) {
|
||||
name string
|
||||
kinds []string
|
||||
stored repos.ExternalServices
|
||||
assert func(testing.TB, repos.ExternalServices)
|
||||
assert repos.ExternalServicesAssertion
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "returned kind is uppercase",
|
||||
kinds: []string{"github"},
|
||||
stored: repos.ExternalServices{&github},
|
||||
assert: equal(&github),
|
||||
assert: repos.Assert.ExternalServicesEqual(&github),
|
||||
},
|
||||
{
|
||||
name: "case-insensitive kinds",
|
||||
kinds: []string{"GiThUb"},
|
||||
stored: repos.ExternalServices{&github},
|
||||
assert: equal(&github),
|
||||
assert: repos.Assert.ExternalServicesEqual(&github),
|
||||
},
|
||||
{
|
||||
name: "returns soft deleted external services",
|
||||
@ -96,15 +74,19 @@ func testStoreListExternalServices(store repos.Store) func(*testing.T) {
|
||||
stored: repos.ExternalServices{
|
||||
github.With(repos.Opt.ExternalServiceDeletedAt(now)),
|
||||
},
|
||||
assert: equal(github.With(repos.Opt.ExternalServiceDeletedAt(now))),
|
||||
assert: repos.Assert.ExternalServicesEqual(
|
||||
github.With(repos.Opt.ExternalServiceDeletedAt(now)),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "results are in ascending order by id",
|
||||
kinds: []string{"github"},
|
||||
stored: mkExternalServices(512, &github),
|
||||
assert: orderedBy(func(a, b *repos.ExternalService) bool {
|
||||
return a.ID < b.ID
|
||||
}),
|
||||
assert: repos.Assert.ExternalServicesOrderedBy(
|
||||
func(a, b *repos.ExternalService) bool {
|
||||
return a.ID < b.ID
|
||||
},
|
||||
),
|
||||
},
|
||||
} {
|
||||
tc := tc
|
||||
@ -339,28 +321,6 @@ func testStoreListRepos(store repos.Store) func(*testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
equal := func(rs ...*repos.Repo) func(testing.TB, repos.Repos) {
|
||||
want := repos.Repos(rs)
|
||||
return func(t testing.TB, have repos.Repos) {
|
||||
have.Apply(repos.Opt.RepoID(0)) // Exclude auto-generated IDs from equality tests
|
||||
if !reflect.DeepEqual(have, want) {
|
||||
t.Errorf("repos: %s", cmp.Diff(have, want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
orderedBy := func(ord func(a, b *repos.Repo) bool) func(testing.TB, repos.Repos) {
|
||||
return func(t testing.TB, have repos.Repos) {
|
||||
want := have.Clone()
|
||||
sort.Slice(want, func(i, j int) bool {
|
||||
return ord(want[i], want[j])
|
||||
})
|
||||
if !reflect.DeepEqual(have, want) {
|
||||
t.Errorf("repos: %s", cmp.Diff(have, want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
@ -368,7 +328,7 @@ func testStoreListRepos(store repos.Store) func(*testing.T) {
|
||||
name string
|
||||
kinds []string
|
||||
stored repos.Repos
|
||||
repos func(testing.TB, repos.Repos)
|
||||
repos repos.ReposAssertion
|
||||
err error
|
||||
}{
|
||||
{
|
||||
@ -377,7 +337,7 @@ func testStoreListRepos(store repos.Store) func(*testing.T) {
|
||||
stored: repos.Repos{foo.With(func(r *repos.Repo) {
|
||||
r.ExternalRepo.ServiceType = "gItHuB"
|
||||
})},
|
||||
repos: equal(foo.With(func(r *repos.Repo) {
|
||||
repos: repos.Assert.ReposEqual(foo.With(func(r *repos.Repo) {
|
||||
r.ExternalRepo.ServiceType = "gItHuB"
|
||||
})),
|
||||
},
|
||||
@ -385,19 +345,19 @@ func testStoreListRepos(store repos.Store) func(*testing.T) {
|
||||
name: "ignores unmanaged",
|
||||
kinds: []string{"github"},
|
||||
stored: repos.Repos{&foo, &unmanaged}.Clone(),
|
||||
repos: equal(&foo),
|
||||
repos: repos.Assert.ReposEqual(&foo),
|
||||
},
|
||||
{
|
||||
name: "returns soft deleted repos",
|
||||
kinds: []string{"github"},
|
||||
stored: repos.Repos{foo.With(repos.Opt.RepoDeletedAt(now))},
|
||||
repos: equal(foo.With(repos.Opt.RepoDeletedAt(now))),
|
||||
repos: repos.Assert.ReposEqual(foo.With(repos.Opt.RepoDeletedAt(now))),
|
||||
},
|
||||
{
|
||||
name: "returns repos in ascending order by id",
|
||||
kinds: []string{"github"},
|
||||
stored: mkRepos(512, &foo),
|
||||
repos: orderedBy(func(a, b *repos.Repo) bool {
|
||||
repos: repos.Assert.ReposOrderedBy(func(a, b *repos.Repo) bool {
|
||||
return a.ID < b.ID
|
||||
}),
|
||||
},
|
||||
|
||||
@ -3,11 +3,14 @@ package repos
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sourcegraph/sourcegraph/pkg/api"
|
||||
)
|
||||
@ -254,27 +257,94 @@ func (s *FakeStore) UpsertRepos(ctx context.Context, upserts ...*Repo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//
|
||||
// Assertions
|
||||
//
|
||||
|
||||
// A ReposAssertion performs an assertion on the given Repos.
|
||||
type ReposAssertion func(testing.TB, Repos)
|
||||
|
||||
// An ExternalServicesAssertion performs an assertion on the given
|
||||
// ExternalServices.
|
||||
type ExternalServicesAssertion func(testing.TB, ExternalServices)
|
||||
|
||||
// Assert contains assertion functions to be used in tests.
|
||||
var Assert = struct {
|
||||
ReposEqual func(...*Repo) ReposAssertion
|
||||
ReposOrderedBy func(func(a, b *Repo) bool) ReposAssertion
|
||||
ExternalServicesEqual func(...*ExternalService) ExternalServicesAssertion
|
||||
ExternalServicesOrderedBy func(func(a, b *ExternalService) bool) ExternalServicesAssertion
|
||||
}{
|
||||
ReposEqual: func(rs ...*Repo) ReposAssertion {
|
||||
want := Repos(rs)
|
||||
return func(t testing.TB, have Repos) {
|
||||
have.Apply(Opt.RepoID(0)) // Exclude auto-generated IDs from equality tests
|
||||
if !reflect.DeepEqual(have, want) {
|
||||
t.Errorf("repos: %s", cmp.Diff(have, want))
|
||||
}
|
||||
}
|
||||
},
|
||||
ReposOrderedBy: func(ord func(a, b *Repo) bool) ReposAssertion {
|
||||
return func(t testing.TB, have Repos) {
|
||||
want := have.Clone()
|
||||
sort.Slice(want, func(i, j int) bool {
|
||||
return ord(want[i], want[j])
|
||||
})
|
||||
if !reflect.DeepEqual(have, want) {
|
||||
t.Errorf("repos: %s", cmp.Diff(have, want))
|
||||
}
|
||||
}
|
||||
},
|
||||
ExternalServicesEqual: func(es ...*ExternalService) ExternalServicesAssertion {
|
||||
want := ExternalServices(es)
|
||||
return func(t testing.TB, have ExternalServices) {
|
||||
have.Apply(Opt.ExternalServiceID(0)) // Exclude auto-generated IDs from equality tests
|
||||
if !reflect.DeepEqual(have, want) {
|
||||
t.Errorf("external services: %s", cmp.Diff(have, want))
|
||||
}
|
||||
}
|
||||
},
|
||||
ExternalServicesOrderedBy: func(ord func(a, b *ExternalService) bool) ExternalServicesAssertion {
|
||||
return func(t testing.TB, have ExternalServices) {
|
||||
want := have.Clone()
|
||||
sort.Slice(want, func(i, j int) bool {
|
||||
return ord(want[i], want[j])
|
||||
})
|
||||
if !reflect.DeepEqual(have, want) {
|
||||
t.Errorf("external services: %s", cmp.Diff(have, want))
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
//
|
||||
// Functional options
|
||||
//
|
||||
|
||||
// Opt contains functional options to be used in tests.
|
||||
var Opt = struct {
|
||||
ExternalServiceID func(int64) func(*ExternalService)
|
||||
ExternalServiceDeletedAt func(time.Time) func(*ExternalService)
|
||||
RepoID func(uint32) func(*Repo)
|
||||
RepoCreatedAt func(time.Time) func(*Repo)
|
||||
RepoModifiedAt func(time.Time) func(*Repo)
|
||||
RepoDeletedAt func(time.Time) func(*Repo)
|
||||
RepoSources func(...string) func(*Repo)
|
||||
RepoMetadata func(interface{}) func(*Repo)
|
||||
RepoExternalID func(string) func(*Repo)
|
||||
ExternalServiceID func(int64) func(*ExternalService)
|
||||
ExternalServiceModifiedAt func(time.Time) func(*ExternalService)
|
||||
ExternalServiceDeletedAt func(time.Time) func(*ExternalService)
|
||||
RepoID func(uint32) func(*Repo)
|
||||
RepoCreatedAt func(time.Time) func(*Repo)
|
||||
RepoModifiedAt func(time.Time) func(*Repo)
|
||||
RepoDeletedAt func(time.Time) func(*Repo)
|
||||
RepoSources func(...string) func(*Repo)
|
||||
RepoMetadata func(interface{}) func(*Repo)
|
||||
RepoExternalID func(string) func(*Repo)
|
||||
}{
|
||||
ExternalServiceID: func(n int64) func(*ExternalService) {
|
||||
return func(e *ExternalService) {
|
||||
e.ID = n
|
||||
}
|
||||
},
|
||||
ExternalServiceModifiedAt: func(ts time.Time) func(*ExternalService) {
|
||||
return func(e *ExternalService) {
|
||||
e.UpdatedAt = ts
|
||||
e.DeletedAt = time.Time{}
|
||||
}
|
||||
},
|
||||
ExternalServiceDeletedAt: func(ts time.Time) func(*ExternalService) {
|
||||
return func(e *ExternalService) {
|
||||
e.UpdatedAt = ts
|
||||
|
||||
@ -297,3 +297,13 @@ func (es ExternalServices) With(opts ...func(*ExternalService)) ExternalServices
|
||||
clone.Apply(opts...)
|
||||
return clone
|
||||
}
|
||||
|
||||
// Filter returns all the ExternalServices that match the given predicate.
|
||||
func (es ExternalServices) Filter(pred func(*ExternalService) bool) (fs ExternalServices) {
|
||||
for _, e := range es {
|
||||
if pred(e) {
|
||||
fs = append(fs, e)
|
||||
}
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
@ -240,6 +240,18 @@ func TestExternalServices_ValidateConfig(t *testing.T) {
|
||||
config: `{"certificate": ""}`,
|
||||
assert: includes("certificate: Does not match pattern '^-----BEGIN CERTIFICATE-----\n'"),
|
||||
},
|
||||
{
|
||||
kind: "GITHUB",
|
||||
desc: "empty repositoryQuery",
|
||||
config: `{"repositoryQuery": []}`,
|
||||
assert: includes(`repositoryQuery: Array must have at least 1 items`),
|
||||
},
|
||||
{
|
||||
kind: "GITHUB",
|
||||
desc: "empty repositoryQuery item",
|
||||
config: `{"repositoryQuery": [""]}`,
|
||||
assert: includes(`repositoryQuery.0: String length must be greater than or equal to 1`),
|
||||
},
|
||||
{
|
||||
kind: "GITHUB",
|
||||
desc: "invalid repos",
|
||||
|
||||
@ -64,8 +64,12 @@
|
||||
"repositoryQuery": {
|
||||
"description": "An array of strings specifying which GitHub or GitHub Enterprise repositories to mirror on Sourcegraph. The valid values are:\n\n- `public` mirrors all public repositories for GitHub Enterprise and is the equivalent of `none` for GitHub\n\n- `affiliated` mirrors all repositories affiliated with the configured token's user:\n\t- Private repositories with read access\n\t- Public repositories owned by the user or their orgs\n\t- Public repositories with write access\n\n- `none` mirrors no repositories (except those specified in the `repos` configuration property or added manually)\n\n- All other values are executed as a GitHub advanced repository search as described at https://github.com/search/advanced. Example: to sync all repositories from the \"sourcegraph\" organization including forks the query would be \"org:sourcegraph fork:true\".\n\nIf multiple values are provided, their results are unioned.\n\nIf you need to narrow the set of mirrored repositories further (and don't want to enumerate it with a list or query set as above), create a new bot/machine user on GitHub or GitHub Enterprise that is only affiliated with the desired repositories.",
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"default": ["public", "affiliated"]
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"default": ["none"],
|
||||
"minItems": 1
|
||||
},
|
||||
"repositoryPathPattern": {
|
||||
"description": "The pattern used to generate the corresponding Sourcegraph repository name for a GitHub or GitHub Enterprise repository. In the pattern, the variable \"{host}\" is replaced with the GitHub host (such as github.example.com), and \"{nameWithOwner}\" is replaced with the GitHub repository's \"owner/path\" (such as \"myorg/myrepo\").\n\nFor example, if your GitHub Enterprise URL is https://github.example.com and your Sourcegraph URL is https://src.example.com, then a repositoryPathPattern of \"{host}/{nameWithOwner}\" would mean that a GitHub repository at https://github.example.com/myorg/myrepo is available on Sourcegraph at https://src.example.com/github.example.com/myorg/myrepo.\n\nIt is important that the Sourcegraph repository name generated with this pattern be unique to this code host. If different code hosts generate repository names that collide, Sourcegraph's behavior is undefined.",
|
||||
|
||||
@ -69,8 +69,12 @@ const GitHubSchemaJSON = `{
|
||||
"repositoryQuery": {
|
||||
"description": "An array of strings specifying which GitHub or GitHub Enterprise repositories to mirror on Sourcegraph. The valid values are:\n\n- ` + "`" + `public` + "`" + ` mirrors all public repositories for GitHub Enterprise and is the equivalent of ` + "`" + `none` + "`" + ` for GitHub\n\n- ` + "`" + `affiliated` + "`" + ` mirrors all repositories affiliated with the configured token's user:\n\t- Private repositories with read access\n\t- Public repositories owned by the user or their orgs\n\t- Public repositories with write access\n\n- ` + "`" + `none` + "`" + ` mirrors no repositories (except those specified in the ` + "`" + `repos` + "`" + ` configuration property or added manually)\n\n- All other values are executed as a GitHub advanced repository search as described at https://github.com/search/advanced. Example: to sync all repositories from the \"sourcegraph\" organization including forks the query would be \"org:sourcegraph fork:true\".\n\nIf multiple values are provided, their results are unioned.\n\nIf you need to narrow the set of mirrored repositories further (and don't want to enumerate it with a list or query set as above), create a new bot/machine user on GitHub or GitHub Enterprise that is only affiliated with the desired repositories.",
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"default": ["public", "affiliated"]
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"default": ["none"],
|
||||
"minItems": 1
|
||||
},
|
||||
"repositoryPathPattern": {
|
||||
"description": "The pattern used to generate the corresponding Sourcegraph repository name for a GitHub or GitHub Enterprise repository. In the pattern, the variable \"{host}\" is replaced with the GitHub host (such as github.example.com), and \"{nameWithOwner}\" is replaced with the GitHub repository's \"owner/path\" (such as \"myorg/myrepo\").\n\nFor example, if your GitHub Enterprise URL is https://github.example.com and your Sourcegraph URL is https://src.example.com, then a repositoryPathPattern of \"{host}/{nameWithOwner}\" would mean that a GitHub repository at https://github.example.com/myorg/myrepo is available on Sourcegraph at https://src.example.com/github.example.com/myorg/myrepo.\n\nIt is important that the Sourcegraph repository name generated with this pattern be unique to this code host. If different code hosts generate repository names that collide, Sourcegraph's behavior is undefined.",
|
||||
|
||||
@ -146,12 +146,17 @@ export const GITHUB_EXTERNAL_SERVICE: ExternalServiceKindMetadata = {
|
||||
// Create one with the repo scope at https://[your-github-instance]/settings/tokens/new
|
||||
"token": "",
|
||||
|
||||
// An array of strings specifying which GitHub or GitHub Enterprise repositories to mirror on Sourcegraph.
|
||||
// See https://docs.sourcegraph.com/admin/site_config/all#githubconnection-object for more details.
|
||||
"repositoryQuery": [
|
||||
"none"
|
||||
],
|
||||
|
||||
// Sync public repositories from https://github.com by adding them to "repos".
|
||||
// (This is not necessary for GitHub Enterprise instances)
|
||||
// "repos": [
|
||||
// "sourcegraph/sourcegraph"
|
||||
// ]
|
||||
|
||||
"repos": [
|
||||
// "sourcegraph/sourcegraph"
|
||||
]
|
||||
}`,
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user