git-remote-entire: only suggest clone for a complete forge/owner/repo ref · Entire
git-remote-entire: only suggest clone for a complete forge/owner/repo ref
6050a60→main·
Soph·1w ago·2 files·+49 added/-21 removed
PR review (Cursor Bugbot + Copilot) flagged that missingClusterHostMessage
unconditionally pointed at entire repo clone whenever a forge id sat in
the host slot, even without owner/repo. So entire://gh suggested
entire repo clone /gh and entire://gh/owner suggested /gh/owner —
refs that parseMirrorCloneRef (^/?gh/
Reconstruct the intended shorthand once (forge id in host slot vs. empty host) and only emit the hint when it is a complete forge/owner/repo triple; anything shorter or longer falls back to the plain missing-host error. Collapsing the two branches also lets clusterHostHint be inlined.
Add regression cases for entire://gh, entire://gh/owner, entire:///gh/owner, and a too-many-segments path.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Sessions
01KWYNH3035PPHFTMD9J6GWEYWView transcript
Changes
2
cmd/git-remote-entire
Mmain.go+20/-21
Mmain_test.go+29
215 unmodified lines
// missingClusterHostMessage renders the stderr "fatal: …" line for an entire://
// URL that omits its cluster host. Two shapes reach here: a forge id typed
// where the host belongs (entire://gh/owner/repo, Host="gh") and an empty host
// (entire:///gh/owner/repo, Host=""). When the intended repo shorthand is a
// recoverable — the host slot, or the path's leading segment, is a known forge
// id — it points at the interactive picker (`entire repo clone <shorthand>`),
// which resolves the mirror and clones a fully-qualified URL. Anything else
// (bare entire://, a non-forge leading segment) falls back to the plain
// missing-host error. Kept pure so it's unit-testable.
// (entire:///gh/owner/repo, Host=""). When the reconstructed shorthand is a
// complete forge/owner/repo triple that `entire repo clone` can resolve, it
// points at the interactive picker; a partial path (entire://gh,
// entire://gh/owner) or a non-forge segment falls back to the plain
// missing-host error rather than suggesting a clone command that would reject
// the ref. Kept pure so it's unit-testable.
func missingClusterHostMessage(parsedURL *url.URL, rawURL string) string {
// Forge id in the host slot: the shorthand is /<host><path>.
if gitremote.IsSupportedForge(parsedURL.Host) {
return clusterHostHint(parsedURL.Host, "/"+parsedURL.Host+parsedURL.Path)
}
// Reconstruct the forge/owner/repo shorthand the user likely intended: a
// forge id in the host slot sits in front of the path; an empty host
// already has it there.
shorthand := strings.TrimPrefix(parsedURL.Path, "/")
if parsedURL.Host != "" {
shorthand = parsedURL.Host + "/" + shorthand
}
// Empty host with a forge-led path: the path is already the shorthand.
if forge, _, ok := strings.Cut(strings.TrimPrefix(parsedURL.Path, "/"), "/"); ok && gitremote.IsSupportedForge(forge) {
return clusterHostHint(forge, parsedURL.Path)
}
// Only point at `entire repo clone` for a complete forge/owner/repo triple
// (the shape parseMirrorCloneRef accepts); anything shorter would relocate
// the failure into a clone command that rejects the ref.
seg := strings.Split(strings.Trim(shorthand, "/"), "/")
if len(seg) != 3 || seg[0] == "" || seg[1] == "" || seg[2] == "" || !gitremote.IsSupportedForge(seg[0]) {
return fmt.Sprintf("fatal: missing host in URL %q\n", rawURL)
}
return fmt.Sprintf("fatal: missing host in URL %q\n", rawURL)
}
// clusterHostHint is the actionable message pointing a host-less entire:// URL
// at the `entire repo clone` picker, which resolves the mirror for the given
// forge shorthand.
func clusterHostHint(forge, shorthand string) string {
return fmt.Sprintf(
"fatal: entire:// URL is missing its cluster host (%q is a forge id, not a host).\n"+
"The full form is entire://<cluster-host>/%s/<owner>/<repo>.\n"+
"To pick a mirror interactively, run:\n\n entire repo clone %s\n",
forge, forge, shorthand)
}
// loadedVersion populates the build info and returns the resolved version.
Mcmd/git-remote-entire/main.go+20/-21
212 unmodified lines
contains: []string{`fatal: missing host in URL "entire:///gh"`},
notContains: []string{"entire repo clone"},
},
{
// Forge in host slot but no owner/repo — the shorthand `/gh` would be
// rejected by `entire repo clone`, so fall back rather than suggest it.
name: "forge in host slot without path falls back",
rawURL: "entire://gh",
contains: []string{`fatal: missing host in URL "entire://gh"`},
notContains: []string{"entire repo clone"},
},
{
// Forge in host slot with owner but no repo — incomplete triple.
name: "forge in host slot with owner only falls back",
rawURL: "entire://gh/owner",
contains: []string{`fatal: missing host in URL "entire://gh/owner"`},
notContains: []string{"entire repo clone"},
},
{
// Empty host, forge + owner but no repo — incomplete triple.
name: "empty host forge and owner only falls back",
rawURL: "entire:///gh/owner",
contains: []string{`fatal: missing host in URL "entire:///gh/owner"`},
notContains: []string{"entire repo clone"},
},
{
// Too many segments — not the gh/<owner>/<repo> shape either.
name: "forge in host slot with extra path segment falls back",
rawURL: "entire://gh/owner/repo/extra",
contains: []string{`fatal: missing host in URL "entire://gh/owner/repo/extra"`},
notContains: []string{"entire repo clone"},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {