# Merge pull request #1679 from entireio/repo-mirror-list-name-column

`931bfb6`→[main](/content/gh/entireio/cli/commits/main/index.html)·

Soph·1w ago·2 files·+112 added/-78 removed

repo mirror list: rename REPO column and --repo flag to NAME

## Changes

2

- cmd/entire/cli

- Mrepo_mirror.go+72/-48

- Mrepo_mirror_test.go+40/-30

```
17 unmodified lines

// Column header names, the single source of truth for both the table headers
// (mirrorColumns/availableMirrorColumns) and the --sort key switches. parseSort-
// Column returns the canonical header it matched, so the sort switches compare
// against these constants directly.
const (
	colRepo     = "REPO"
	colCloneURL = "CLONE URL"
	colPrivate  = "PRIVATE"
	colAccess   = "ACCESS"
	colStatus   = "STATUS"
// column is a table column with two separable identities: key is the canonical
// name a caller types for --sort (and the value parseSortColumn returns, so the
// sort switches compare against these constants directly); header is the text
// shown in the table. They differ only where the header carries a display hint
// the sort key shouldn't — e.g. NAME's inline "(owner/repo)" — which keeps
// --sort matching a simple equality on key with no header parsing.
type column struct {
	key    string
	header string
}

// Keys are lower-case, single shell tokens (kebab-case for multi-word columns)
// so `--sort clone-url` needs no quoting; headers stay upper-case display text.
var (
	colName     = column{key: "name", header: "NAME (owner/repo)"}
	colCloneURL = column{key: "clone-url", header: "CLONE URL"}
	colPrivate  = column{key: "private", header: "PRIVATE"}
	colAccess   = column{key: "access", header: "ACCESS"}
	colStatus   = column{key: "status", header: "STATUS"}
)

// columnHeaders is the display-header view of a column set, for the table/field
// renderers (runCoreList/runCoreObject) which take plain header strings.
func columnHeaders(cols []column) []string {
	h := make([]string, len(cols))
	for i, c := range cols {
		h[i] = c.header
	}
	return h
}

// mirrorColumns is the human table/field view of a mirror: the scannable
// owner/repo name, the clone URL you'd copy, and whether the upstream is
// private. Owner, provider, and cluster aren't columns of their own — they're
// inferable from the owner/repo pair and the clone URL
// (entire://<cluster>/gh/<owner>/<repo>). `--repo` filters on the repo name
// only; owner/provider/cluster stay server-side filters, and the wire model's
// internal ids are dropped. The clone URL is synthesised from the mirror's
// coords (the form `git clone` accepts), since the list API doesn't return it.
var mirrorColumns = []string{colRepo, colCloneURL, colPrivate}
// (entire://<cluster>/gh/<owner>/<repo>). `--name` filters on the owner/repo
// name only; owner/provider/cluster stay server-side filters, and the wire
// model's internal ids are dropped. The clone URL is synthesised from the
// mirror's coords (the form `git clone` accepts), since the list API doesn't
// return it.
var mirrorColumns = []column{colName, colCloneURL, colPrivate}
// mirrorPrivate renders the PRIVATE column ("yes"/"no"), shared by the table
// row and the --sort private key so both agree on the cell value.

return []string{repo, cloneURL, mirrorPrivate(m)}
}

// parseSortColumn resolves a --sort spec to the canonical column header it
// names (one of the columns entries) and a direction. It trims first, then
// reads the '-' prefix, so leading/trailing whitespace is handled identically
// on every path (the direction and the column name never disagree). An empty
// spec selects the first column. An unknown name errors naming the valid
// columns. Returning the matched header lets callers switch on the col*
// constants directly.
func parseSortColumn(spec string, columns []string) (col string, desc bool, err error) {
// parseSortColumn resolves a --sort spec to the column it names and a
direction. It trims first, then reads the '-' prefix, so leading/trailing
whitespace is handled identically on every path (the direction and the column
name never disagree). An empty spec selects the first column. A spec matches a
column by its key (case-insensitive) — a plain equality, since key holds no
display hint. An unknown name errors naming the valid keys. Returning the
matched column lets callers switch on the col* constants directly.
func parseSortColumn(spec string, columns []column) (col column, desc bool, err error) {
spec = strings.TrimSpace(spec)
	desc = strings.HasPrefix(spec, "-")
	name := strings.TrimSpace(strings.TrimPrefix(spec, "-"))
	if name == "" {
		return columns[0], desc, nil
	}
	for _, h := range columns {
		if strings.EqualFold(h, name) {
			return h, desc, nil
	}
for _, c := range columns {
		if strings.EqualFold(c.key, name) {
			return c, desc, nil
	}
	}
	return "", false, fmt.Errorf("unknown sort column %q; valid columns: %s", name, strings.ToLower(strings.Join(columns, ", "))) 
valid := make([]string, len(columns))
for i, c := range columns {
	valid[i] = c.key
}
return column{}, false, fmt.Errorf("unknown sort column %q; valid columns: %s", name, strings.Join(valid, ", "))
}

// sortMirrors orders mirrors in place by the --sort spec: by the named column's
// value ascending (case-insensitive), always breaking ties by owner/repo then
// cluster host so a repo mirrored across clusters (or rows equal on any other
// column) has a stable, deterministic order rather than arbitrary server order.
// A '-' prefix reverses the whole ordering. `repo`/default sorts by the
// A '-' prefix reverses the whole ordering. `name`/default sorts by the
tiebreak alone.
func sortMirrors(mirrors []coreapi.Mirror, spec string) error {
	col, desc, err := parseSortColumn(spec, mirrorColumns)

}
// filterByRepo keeps items whose repo identifier contains substr (case-
// filterByName keeps items whose owner/repo name contains substr (case-
// insensitive). The control plane already filters by owner/provider/cluster
// server-side but not by repo name, so `repo mirror list --repo` narrows that
// last dimension client-side. repoOf returns the item's displayed identifier —
// the callers pass the owner/repo form shown in the REPO column, so a value
// copied from the table (e.g. acme/web) matches the row it came from. An empty
// substr returns items unchanged.
func filterByRepo[T any](items []T, repoOf func(T) string, substr string) []T {
// server-side but not by name, so `repo mirror list --name` narrows that last
// dimension client-side. nameOf returns the item's displayed identifier — the
// callers pass the owner/repo form shown in the NAME column, so a value
// copied from the table (e.g. acme/web) matches the row it came from. An empty substr returns items unchanged.
func filterByName[T any](items []T, nameOf func(T) string, substr string) []T {
	substr = strings.TrimSpace(substr)
	if substr == "" {
		return items
	}
	substr = strings.ToLower(substr)
	out := make([]T, 0, len(items))
	for _, it := range items {
		if strings.Contains(strings.ToLower(repoOf(it)), substr) {
		if strings.Contains(strings.ToLower(nameOf(it)), substr) {
			out = append(out, it)
		}
	}
}
// clone URL), or "owner-only" (a personal repo of another user; only its
// owner may mirror it). No clone URL column: an un-onboarded repo doesn't
// have one yet.
var availableMirrorColumns = []string{colRepo, colAccess, colStatus}
var availableMirrorColumns = []column{colName, colAccess, colStatus}

func availableMirrorRow(m coreapi.AvailableMirror) []string {
	return []string{m.Owner + "/" + m.Repo, string(m.Access), string(m.Status)}
}

}

func newRepoMirrorListCmd() *cobra.Command {
	var cluster, provider, owner, repo string
	var cluster, provider, owner, name string
	var sortSpec string
	var showAvailable bool
	cmd := &cobra.Command{
		RunE: func(cmd *cobra.Command, _ []string) error {
			if showAvailable {
				return runCoreList(cmd, "No repos available to mirror.", availableMirrorColumns, availableMirrorRow, func(ctx context.Context, c *coreapi.Client) ([]coreapi.AvailableMirror, error) {
				return runCoreList(cmd, "No repos available to mirror.", columnHeaders(availableMirrorColumns), availableMirrorRow, func(ctx context.Context, c *coreapi.Client) ([]coreapi.AvailableMirror, error) {
					// Computed live from GitHub using your own login, so name the
					// core being dialled (same rationale as the existing-mirror
					// banner). --cluster/--provider don't apply here: the
			
					if err != nil {
						return nil, err
					}
					avail := filterByRepo(out.Available, func(m coreapi.AvailableMirror) string { return m.Owner + "/" + m.Repo }, repo)
					avail := filterByName(out.Available, func(m coreapi.AvailableMirror) string { return m.Owner + "/" + m.Repo }, name)
					if err := sortAvailable(avail, sortSpec); err != nil {
						return nil, err
					}
					return avail, nil
				})
				}
			}
			return runCoreList(cmd, "No mirrors found.", mirrorColumns, mirrorRow, func(ctx context.Context, c *coreapi.Client) ([]coreapi.Mirror, error) {
				return runCoreList(cmd, "No mirrors found.", columnHeaders(mirrorColumns), mirrorRow, func(ctx context.Context, c *coreapi.Client) ([]coreapi.Mirror, error) {
					// mirror list is identity-scoped: it shows the mirrors visible
					// from the active login's federation, so naming that login server
					// makes a surprising empty result legible — e.g. mirrors in a
					return nil, nil
				})

// execMirrorList runs `list` under a parent that carries the control-plane
// persistent flags (--json lives there, not on the list command itself), so
// tests can exercise --json and the client-side --repo/--sort together.
// tests can exercise --json and the client-side --name/--sort together.
func execMirrorList(t *testing.T, args ...string) (stdout, stderr string, err error) {
	t.Helper()
	parent := &cobra.Command{Use: "mirror"}
	
}

// TestRepoMirrorList_FilterSort pins the client-side --repo filter and --sort
// TestRepoMirrorList_FilterSort pins the client-side --name filter and --sort
// applied to `repo mirror list` before rendering (server handles
// owner/provider/cluster), so they shape both the table and --json output and
// work under --show-available.
// TODO: Implement functionality
// ...

// Filter repo by substring name matching.
// Filter name by Substring matching.

// ... implementation continues ...
`
