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

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

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.`
Comment thread
mbevc1 marked this conversation as resolved.

const listReposExample = `
# list repos for an org (first page, 15 per page):
kosli list repos \
--api-token yourAPIToken \
--org yourOrgName

# list repos filtered by name (exact match on the full repo name):
kosli list repos \
--name my-org/my-repo \
--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

# show the second page of repos (25 per page):
kosli list repos \
--page-limit 25 \
--page 2 \
--api-token yourAPIToken \
--org yourOrgName
`

type listReposOptions struct {
listOptions
Expand All @@ -24,10 +57,11 @@ type listReposOptions struct {
func newListReposCmd(out io.Writer) *cobra.Command {
o := new(listReposOptions)
cmd := &cobra.Command{
Use: "repos",
Short: listReposDesc,
Long: listReposDesc,
Args: cobra.NoArgs,
Use: "repos",
Short: listReposShortDesc,
Long: listReposLongDesc,
Example: listReposExample,
Args: cobra.NoArgs,
PreRunE: func(cmd *cobra.Command, args []string) error {
err := RequireGlobalFlags(global, []string{"Org", "ApiToken"})
if err != nil {
Expand All @@ -41,7 +75,7 @@ func newListReposCmd(out io.Writer) *cobra.Command {
}

addListFlags(cmd, &o.listOptions)
cmd.Flags().StringVar(&o.name, "name", "", "[optional] The repo name to filter by.")
cmd.Flags().StringVar(&o.name, "name", "", "[optional] The repo name to filter by (exact match).")
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.")

Expand Down Expand Up @@ -84,29 +118,40 @@ func (o *listReposOptions) run(out io.Writer) error {
})
}

func printReposListAsTable(raw string, out io.Writer, page int) error {
var repos []map[string]any
var response struct {
Repos []map[string]any `json:"repos"`
}
type listReposResponse struct {
Repos []map[string]any `json:"repos"`
Page int `json:"page"`
TotalPages int `json:"total_pages"`
TotalCount int `json:"total_count"`
}

err := json.Unmarshal([]byte(raw), &response)
if err != nil {
func printReposListAsTable(raw string, out io.Writer, page int) error {
response := &listReposResponse{}
if err := json.Unmarshal([]byte(raw), response); err != nil {
return err
}
repos = response.Repos

if len(repos) == 0 {
logger.Info("No repos were found.")
// both the empty-list message and the footer read the page from the
// response envelope (the server echoes the requested page), so the two
// paths never disagree on which page is being reported
if len(response.Repos) == 0 {
msg := "No repos were found"
if response.Page != 1 {
msg = fmt.Sprintf("%s at page number %d", msg, response.Page)
}
logger.Info(msg + ".")
return nil
}

header := []string{"NAME", "URL", "PROVIDER", "TAGS"}
rows := []string{}
for _, repo := range repos {
for _, repo := range response.Repos {
row := fmt.Sprintf("%v\t%v\t%v\t%s", repo["name"], repo["url"], repo["provider"], formatRepoTags(repo["tags"]))
rows = append(rows, row)
}

rows = append(rows, fmt.Sprintf("\nShowing page %d of %d, total %d repos", response.Page, response.TotalPages, response.TotalCount))
Comment thread
mbevc1 marked this conversation as resolved.

tabFormattedPrint(out, header, rows)

return nil
Expand Down
10 changes: 8 additions & 2 deletions cmd/kosli/listRepos_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func (suite *ListReposCommandTestSuite) TestListReposCmd() {
},
{
name: "08-can list repos with pagination",
cmd: fmt.Sprintf(`list repos --page-limit 15 --page 2 %s`, suite.defaultKosliArguments),
cmd: fmt.Sprintf(`list repos --page-limit 25 --page 2 %s`, suite.defaultKosliArguments),
golden: "",
},
{
Expand Down Expand Up @@ -136,6 +136,11 @@ func (suite *ListReposCommandTestSuite) TestListReposCmd() {
cmd: fmt.Sprintf(`list repos %s`, suite.acmeOrgKosliArguments),
goldenRegex: `(?m)^list-repos-suite-org/untagged-repo\s+https://github\.com/list-repos-suite-org/untagged-repo\s+github\s*$`,
},
{
name: "15-table output shows the pagination footer",
cmd: fmt.Sprintf(`list repos %s`, suite.acmeOrgKosliArguments),
goldenRegex: `(?m)^Showing page 1 of \d+, total \d+ repos$`,
},
}

runTestCmd(suite.T(), tests)
Expand All @@ -151,11 +156,12 @@ func TestPrintReposListAsTableRendersBlankTagsCell(t *testing.T) {
raw := `{"repos":[
{"name":"o/tagged","url":"https://github.com/o/tagged","provider":"github","tags":{"team":"platform"}},
{"name":"o/untagged","url":"https://github.com/o/untagged","provider":"github","tags":{}}
]}`
],"page":1,"total_pages":3,"total_count":45}`
var buf bytes.Buffer
require.NoError(t, printReposListAsTable(raw, &buf, 1))
out := buf.String()
require.Regexp(t, `(?m)^NAME\s+URL\s+PROVIDER\s+TAGS\s*$`, out)
require.Regexp(t, `(?m)^o/tagged\s+https://github\.com/o/tagged\s+github\s+team=platform\s*$`, out)
require.Regexp(t, `(?m)^o/untagged\s+https://github\.com/o/untagged\s+github\s*$`, out)
require.Regexp(t, `(?m)^Showing page 1 of 3, total 45 repos$`, out)
}
Loading