Skip to content

feat(repo): add entire repo mirror use to repoint a clone at a mirror - #1875

Open
entire[bot] wants to merge 1 commit into
mainfrom
feat/repo-mirror-use
Open

feat(repo): add entire repo mirror use to repoint a clone at a mirror#1875
entire[bot] wants to merge 1 commit into
mainfrom
feat/repo-mirror-use

Conversation

@entire

@entire entire Bot commented Jul 30, 2026

Copy link
Copy Markdown

https://entire.io/gh/entireio/cli/trails/955

Closes the last manual step in mirror onboarding. Adopting a mirror previously meant reading the clone URL out of mirror get and hand-running git remote set-url — the mirror use verb from the standalone entiredb CLI was deliberately not ported ("a git-config + git-remote-entire concern outside the control-plane API").

use is the local half of that concern and nothing more: it resolves the repo's pullable placements, picks one, and rewrites local git config. It creates no server-side state — an unmirrored repo errors with a pointer at mirror create.

Surface

entire repo mirror use [github-url] [cluster-host]
  --remote    <name>  git remote to point at the mirror (default "origin")
  --upstream  <name>  remote to preserve the replaced URL under (default "upstream"); '' discards
  --cluster   <host>  pin a cluster without naming the repo (same as [cluster-host])
  • Bare entire repo mirror use resolves the repo from the clone's own remotes, lists its placements, and prompts for the cluster when there is more than one.
  • On a terminal it then asks whether to repoint the remote (keeping the old URL as upstream) or add the mirror under a separate name.
  • Non-interactively it repoints --remote directly and preserves the replaced URL under --upstream.
  • The replaced URL is always echoed (credential-redacted via gitremote.RedactURL), and an existing upstream is never clobbered, so the previous state stays recoverable.

Notes for review

Two roles for a remote name. --remote is the write target, which need not exist yet, so repo identity resolves from the target remote first and then origin. Conflating the two was a bug during development: use --remote entire on a fresh clone failed because it tried to read coordinates from the remote it
was about to create.

Shared picker. The cluster picker is factored out of repo clone as selectPlacement + placementPicker, so both verbs share one selection path with per-verb wording. repo clone's messages and behavior are unchanged
(selectCloneTarget is now a thin wrapper).

Option order is load-bearing. huh answers an unreadable accessible prompt by writing the FIRST option's value and returning a nil error (verified by probe — it does not leave the bound value untouched and does not cancel). So the first branch of the replace-or-add prompt must produce the same writes the same flags produce non-interactively, or a Ctrl+D would silently diverge from the documented default. TestPromptMirrorRemoteChoice_FirstOptionMatchesNonInteractive pins that invariant. Worth knowing separately: repo clone's picker shares this property (closed stdin under a forced TTY clones from the first cluster); left alone as out of scope here.

Verification

  • mise run lint — 0 issues
  • mise run test — 8503 pass. The --json drift guard
    (TestControlPlaneJSONFlag_OnlyOnHonoringCommands) correctly caught the new
    command; classified false alongside the other side-effect verbs.
  • mise run test:integration — 448 pass
  • mise run test:e2e:canary — 4/4
  • Against the live API: resolved 4 real placements for gh/entireio/cli,
    repointed origin, and git ls-remote returned real refs through the
    rewritten URL. Both interactive branches driven end-to-end with ACCESSIBLE=1.

No CHANGELOG entry: entries there carry PR links and are assembled per release.

Closes the last manual step in mirror onboarding. Previously adopting a
mirror meant reading the clone URL out of `mirror get` and hand-running
`git remote set-url` — the `mirror use` verb from the standalone entiredb
CLI was deliberately not ported ("a git-config + git-remote-entire concern
outside the control-plane API").

`use` is the local half of that concern and nothing more: it resolves the
repo's pullable placements, picks one, and rewrites local git config. It
creates no server-side state — an unmirrored repo errors with a pointer at
`mirror create`.

- Bare `entire repo mirror use` resolves the repo from the clone's own
  remotes, lists its placements, and prompts for the cluster when there is
  more than one.
- On a terminal it then asks whether to repoint the remote (keeping the old
  URL as `upstream`) or add the mirror under a separate name.
- Non-interactively it repoints `--remote` (default `origin`) and preserves
  the replaced URL under `--upstream`; `--upstream ''` discards it. A
  cluster can be pinned with `--cluster` or the `[cluster-host]` positional.
- The replaced URL is always echoed (credential-redacted), and an existing
  `upstream` is never clobbered, so the previous state stays recoverable.

Repo identity and the write target are kept as separate roles: `--remote`
names what gets rewritten, which need not exist yet, so identity resolves
from the target remote first and then `origin`.

The cluster picker is factored out of `repo clone` as `selectPlacement` +
`placementPicker` so both verbs share one selection path with per-verb
wording; `repo clone`'s messages are unchanged.

Option order in the replace-or-add prompt is load-bearing and commented as
such: huh answers an unreadable accessible prompt with the first option and
a nil error, so the first branch must match what the same flags do
non-interactively. A test pins that invariant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 01KYSGR4W1ZATZRW47J510EZGJ
@matthiaswenz
matthiaswenz marked this pull request as ready for review July 30, 2026 13:37
@matthiaswenz
matthiaswenz requested a review from a team as a code owner July 30, 2026 13:37
Copilot AI review requested due to automatic review settings July 30, 2026 13:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new entire repo mirror use subcommand to complete mirror onboarding by repointing (or adding) a local git remote to an Entire mirror URL, using the same placement selection logic as entire repo clone.

Changes:

  • Add repo mirror use command to rewrite local git remotes based on resolved pullable mirror placements, with interactive/accessible prompting and a non-interactive flag-driven path.
  • Factor mirror placement selection into a shared selectPlacement helper (used by both repo clone and repo mirror use) with per-verb prompt wording.
  • Add unit tests for remote-name validation, planning/apply behavior, interactive-choice invariants, and command registration; update the JSON-flag drift guard and CLI architecture docs.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
cmd/entire/cli/repo_mirror.go Registers the new use subcommand under repo mirror and updates subtree description.
cmd/entire/cli/repo_mirror_use.go Implements entire repo mirror use: resolves upstream repo, selects placement, and applies safe git-remote updates with reporting/prompting.
cmd/entire/cli/repo_mirror_use_test.go Adds unit tests covering validation, planning, apply behavior, prompt invariants, and error surfacing.
cmd/entire/cli/repo_clone.go Extracts shared placement selection/picker wording into selectPlacement + placementPicker.
cmd/entire/cli/corecmd_json_flag_test.go Updates command surface expectations to include repo mirror use (non-JSON side-effect verb).
CLAUDE.md Documents the mirror use exception within the repo mirror subtree behavior.

Comment on lines +237 to +240
default:
// Unreachable with the options above (huh always writes one of them).
// Kept so an unrecognised value can never fall through into a write.
return mirrorUseChoice{}, NewSilentError(errors.New("no remote update selected"))
Comment on lines +61 to +69
var gitRunner = func(ctx context.Context, dir string, args ...string) (string, error) {
cmd := exec.CommandContext(ctx, "git", args...)
cmd.Dir = dir
out, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("git %s: %w", strings.Join(args, " "), err)
}
return strings.TrimSpace(string(out)), nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants