Revert "Revert "external services: Change default github.repositoryQuery to ["none"] (#2792)" (#2890)" (#2891)

This reverts the reverted commit 2da1d2f449.
This commit is contained in:
Tomás Senart 2019-03-21 16:57:00 +01:00 committed by GitHub
parent f8394a92e0
commit da3743ece3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 449 additions and 92 deletions

View File

@ -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

View File

@ -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...)) }()

View File

@ -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 {

View File

@ -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)
}

View 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")
}

View 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
}

View File

@ -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
}),
},

View File

@ -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

View File

@ -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
}

View File

@ -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",

View File

@ -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.",

View File

@ -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.",

View File

@ -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"
]
}`,
}