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
50 changes: 43 additions & 7 deletions cmd/kosli/listRepos.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ import (
"github.com/spf13/cobra"
)

const listReposShortDesc = `List repos for an org. `
const listReposShortDesc = `List repos for an org.`

const listReposLongDesc = listReposShortDesc + `The results are always paginated:
const listReposLongDesc = listReposShortDesc + ` The results are always paginated:
by default the first page is returned with 15 repos per page. Use --page to select
a page and --page-limit to change the page size (maximum 50).
The list can be filtered by name with --name (exact match), by VCS provider with
--provider, and by external repo ID with --repo-id.`
The list can be filtered by name with --name (exact match), by name substring with
--search (case-insensitive, mutually exclusive with --name), by VCS provider with
--provider, by external repo ID with --repo-id, and by tags with --tag.
Results are sorted by repo name; use --sort-direction to choose asc or desc.`

const listReposExample = `
# list repos for an org (first page, 15 per page):
Expand All @@ -32,13 +34,31 @@ kosli list repos \
--api-token yourAPIToken \
--org yourOrgName

# list repos whose name contains a substring (case-insensitive):
kosli list repos \
--search cli \
--api-token yourAPIToken \
--org yourOrgName

# list repos filtered by VCS provider (in JSON):
kosli list repos \
--provider github \
--api-token yourAPIToken \
--org yourOrgName \
--output json

# list repos tagged with team=platform:
kosli list repos \
--tag team:platform \
--api-token yourAPIToken \
--org yourOrgName

# list repos sorted by name, Z–A:
kosli list repos \
--sort-direction desc \
--api-token yourAPIToken \
--org yourOrgName

# show the second page of repos (25 per page):
kosli list repos \
--page-limit 25 \
Expand All @@ -49,9 +69,12 @@ kosli list repos \

type listReposOptions struct {
listOptions
name string
provider string
repoID string
name string
search string
provider string
repoID string
sortDirection string
tags []string
}

func newListReposCmd(out io.Writer) *cobra.Command {
Expand All @@ -76,8 +99,12 @@ func newListReposCmd(out io.Writer) *cobra.Command {

addListFlags(cmd, &o.listOptions)
cmd.Flags().StringVar(&o.name, "name", "", "[optional] The repo name to filter by (exact match).")
cmd.Flags().StringVar(&o.search, "search", "", "[optional] Filter repos whose name contains this substring (case-insensitive). Mutually exclusive with --name.")
cmd.Flags().StringVar(&o.provider, "provider", "", "[optional] The VCS provider to filter repos by (e.g. github, gitlab).")
cmd.Flags().StringVar(&o.repoID, "repo-id", "", "[optional] The external repo ID to filter repos by.")
cmd.Flags().StringVar(&o.sortDirection, "sort-direction", "", "[optional] The direction to sort repos by name. Valid values are: [asc, desc]. (defaults to asc)")
cmd.Flags().StringArrayVar(&o.tags, "tag", []string{}, "[optional] Only list repos that have this tag, given as 'key' or 'key:value'. Can be repeated to match more than one tag.")
cmd.MarkFlagsMutuallyExclusive("name", "search")

return cmd
}
Expand All @@ -89,12 +116,21 @@ func (o *listReposOptions) run(out io.Writer) error {
if o.name != "" {
params.Set("name", o.name)
}
if o.search != "" {
params.Set("search", o.search)
}
if o.provider != "" {
params.Set("provider", o.provider)
}
if o.repoID != "" {
params.Set("repo_id", o.repoID)
}
if o.sortDirection != "" {
params.Set("sort_direction", o.sortDirection)
}
for _, tag := range o.tags {
params.Add("tag", tag)
}
base, err := neturl.JoinPath(global.Host, "api/v2/repos", global.Org)
if err != nil {
return err
Expand Down
64 changes: 64 additions & 0 deletions cmd/kosli/listRepos_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ func (suite *ListReposCommandTestSuite) SetupTest() {
"GITHUB_REPOSITORY_ID": "555",
}, suite.T())
BeginTrail("untagged-trail", "list-repos", "", suite.T())

// three repos sharing a search substring, in a known A–Z order, so the
// --sort-direction tests can assert the ordering actually reverses
sortRepos := []struct{ name, id string }{
{"list-repos-suite-org/sort-a", "701"},
{"list-repos-suite-org/sort-b", "702"},
{"list-repos-suite-org/sort-c", "703"},
}
for _, r := range sortRepos {
SetEnvVars(map[string]string{
"GITHUB_REPOSITORY": r.name,
"GITHUB_REPOSITORY_ID": r.id,
}, suite.T())
BeginTrail("sort-trail-"+r.id, "list-repos", "", suite.T())
}
}

func (suite *ListReposCommandTestSuite) TearDownTest() {
Expand Down Expand Up @@ -131,6 +146,55 @@ func (suite *ListReposCommandTestSuite) TestListReposCmd() {
cmd: fmt.Sprintf(`list repos --repo-id non-existing-id %s`, suite.acmeOrgKosliArguments),
golden: "No repos were found.\n",
},
{
name: "14a-listing repos with --search substring works",
cmd: fmt.Sprintf(`list repos --search cli %s`, suite.acmeOrgKosliArguments),
goldenRegex: `(?m)^kosli-dev/cli\s+https://github\.com/kosli-dev/cli\s+github\b`,
},
{
name: "14b-listing repos with --search and --output json works",
cmd: fmt.Sprintf(`list repos --search cli --output json %s`, suite.acmeOrgKosliArguments),
goldenJson: []jsonCheck{{"repos", "non-empty"}},
},
{
wantError: true,
name: "14c-using --name and --search together causes an error",
cmd: fmt.Sprintf(`list repos --name kosli-dev/cli --search cli %s`, suite.acmeOrgKosliArguments),
golden: "Error: if any flags in the group [name search] are set none of the others can be; [name search] were all set\n",
},
{
name: "14d-listing repos filtered by --tag key:value works",
cmd: fmt.Sprintf(`list repos --tag team:platform %s`, suite.acmeOrgKosliArguments),
goldenRegex: `(?m)^kosli-dev/cli\s+https://github\.com/kosli-dev/cli\s+github\s+team=platform`,
},
{
name: "14e-listing repos filtered by --tag key only works",
cmd: fmt.Sprintf(`list repos --tag team %s`, suite.acmeOrgKosliArguments),
goldenRegex: `(?m)^kosli-dev/cli\s+https://github\.com/kosli-dev/cli\s+github\s+team=platform`,
},
{
name: "14f-listing repos with a non-matching --tag returns no repos message",
cmd: fmt.Sprintf(`list repos --tag team:doesnotexist %s`, suite.acmeOrgKosliArguments),
golden: "No repos were found.\n",
},
{
name: "14g-listing repos with --sort-direction asc keeps A–Z order",
cmd: fmt.Sprintf(`list repos --search list-repos-suite-org/sort- --sort-direction asc --page 1 --page-limit 50 --output json %s`, suite.acmeOrgKosliArguments),
goldenJson: []jsonCheck{
{"repos", "length:3"},
{"repos.[0].name", "list-repos-suite-org/sort-a"},
{"repos.[2].name", "list-repos-suite-org/sort-c"},
},
},
{
name: "14h-listing repos with --sort-direction desc reverses the order",
cmd: fmt.Sprintf(`list repos --search list-repos-suite-org/sort- --sort-direction desc --page 1 --page-limit 50 --output json %s`, suite.acmeOrgKosliArguments),
goldenJson: []jsonCheck{
{"repos", "length:3"},
{"repos.[0].name", "list-repos-suite-org/sort-c"},
{"repos.[2].name", "list-repos-suite-org/sort-a"},
},
},
{
name: "14-a repo without tags renders a blank TAGS cell",
cmd: fmt.Sprintf(`list repos %s`, suite.acmeOrgKosliArguments),
Expand Down
Loading