feat(control-plane): resolve repos by name with --project scope · Entire

feat(control-plane): resolve repos by name with --project scope

ec08c2b·

toothbrush·3w ago·4 files·+133 added/-14 removed

repo get/delete and grant repo add/list/remove now accept a repo name (not just a ULID) when --project <name|ULID> is given to scope the lookup — repo names are unique only within a project. A ULID still passes through unchanged and ignores --project.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Sessions

ab8e423ff72fView transcript

Changes

4

347 unmodified lines

}

func newGrantRepoAddCmd() *cobra.Command {
    var provider, providerUserID, role, granteeType string
    var provider, providerUserID, role, granteeType, project string
    cmd := &cobra.Command{
        Use:   "add <repo>",
        Short: "Grant access to a repo",
        // ...
    }
}

func newGrantRepoListCmd() *cobra.Command { return &cobra.Command{ Use: "list ", Short: "List repo grants", Args: cobra.ExactArgs(1), // ... } }


func newGrantRepoRemoveCmd() *cobra.Command {
    var granteeType, granteeID, provider, providerUserID string
    var granteeType, granteeID, provider, providerUserID, project string
    cmd := &cobra.Command{
        Use:   "remove <repo>",
        Short: "Revoke repo access from a grantee",
        // ...
    }
}

func newRepoGetCmd() *cobra.Command { return &cobra.Command{ Use: "get ", Short: "Show a repository by name or ULID", // ... } }


func newRepoDeleteCmd() *cobra.Command {
    return &cobra.Command{
        Use:   "delete <repo>",
        Short: "Delete a repository by name or ULID",
        // ...
    }
}

// bindRepoProjectFlag wires the shared --project scope used to resolve a repo
// addressed by name (a repo name is unique only within its project). Ignored
// when the repo arg is already a ULID.
func bindRepoProjectFlag(cmd *cobra.Command, project *string) {
    cmd.Flags().StringVar(project, "project", "", "owning project (name or ULID); required when <repo> is a name")
}

// resolveRepoRef turns a repo reference into its ULID. A ULID passes through. // A name requires a project scope (projectRef, itself a name or ULID) because // repo names are unique only within a project: we list that project's repos and // match by name. func resolveRepoRef(ctx context.Context, c *coreapi.Client, ref, projectRef string) (string, error) { if looksLikeULID(ref) { return ref, nil } if projectRef == "" { return "", fmt.Errorf("repo %q is a name; pass --project <name|ULID> to resolve it, or use a repo ULID", ref) } projID, err := resolveProjectRef(ctx, c, projectRef) if err != nil { return "", err } out, err := c.ListProjectRepos(ctx, coreapi.ListProjectReposParams{ProjectId: projID}) if err != nil { return "", err } return pickRepo(out.Repos, ref) }

// pickRepo selects the single repo named name within an already-scoped project // listing. Repo names are unique per project, so a name matches at most one; // zero is an error pointing at repo list, and (defensively) multiple lists the colliding ids. Matching is case-insensitive (EqualFold), consistent with // pickOrg/pickProject. func pickRepo(repos []coreapi.Repo, name string) (string, error) { var matches []coreapi.Repo for _, r := range repos { if strings.EqualFold(r.Name, name) { matches = append(matches, r) } } switch len(matches) { case 1: return matches[0].ID, nil case 0: return "", fmt.Errorf("no repo named %q in that project (run entire repo list <project> to see names, or pass a ULID)", name) default: ids := make([]string, len(matches)) for i, r := range matches { ids[i] = r.ID } return "", fmt.Errorf("repo name %q is ambiguous (%s); pass a ULID instead", name, strings.Join(ids, ", ")) } }

// filterProjectsByName narrows projects to name matches, returning all of them // when name is empty. Used by project list --org to apply --name client-side, // since the org-scoped list endpoint has no name parameter. Matching is