a 404 at adoption has a third reading, and the tool can resolve it
externals names two readings of a 404 — withdrawn, or access lost — and those are the two a locked document can have. Adoption by name has a third: a document that was never there under that name. Found in use, where a missing s reported the ambiguity instead of the typo. The tool was reporting an ambiguity it had the means to resolve: the by-name form knows the repository, so on a 404 it now lists the published surface and says which names exist. It claims that only when the listing succeeds — if listing fails too, the repository is unreachable and the original ambiguity is the honest answer, which is the same discipline as recording public and not-public rather than private. Not filed as a gap against externals. The third reading cannot occur where that document is speaking, which is check against a lock; it exists only at adoption, which is ours. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris
This commit is contained in:
Vendored
+25
-1
@@ -2,6 +2,7 @@ package external
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
@@ -50,7 +51,30 @@ func AddByName(root, ownerRepo, name string, out io.Writer) error {
|
||||
}
|
||||
raw = fmt.Sprintf(raw, branch)
|
||||
}
|
||||
return Add(root, raw, "", out)
|
||||
err = Add(root, raw, "", out)
|
||||
|
||||
// A 404 here has a reading the convention does not list, because it can only
|
||||
// happen at adoption: the name is wrong. We know the repository, so rather
|
||||
// than reporting an ambiguity we can resolve, look.
|
||||
var nf *NotFoundError
|
||||
if errors.As(err, &nf) {
|
||||
var names strings.Builder
|
||||
if lerr := List(ctx.CloneURL(ownerRepo), &names); lerr == nil {
|
||||
return fmt.Errorf("%s publishes no %q. It publishes:\n%s",
|
||||
ownerRepo, name, indent(names.String()))
|
||||
}
|
||||
// Listing failed too, so the repository itself is unreachable and the
|
||||
// original ambiguity stands.
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func indent(s string) string {
|
||||
var b strings.Builder
|
||||
for _, line := range strings.Split(strings.TrimRight(s, "\n"), "\n") {
|
||||
fmt.Fprintf(&b, " %s\n", line)
|
||||
}
|
||||
return strings.TrimRight(b.String(), "\n")
|
||||
}
|
||||
|
||||
// ListByName enumerates a repository named against the current context.
|
||||
|
||||
Vendored
+14
-2
@@ -253,6 +253,19 @@ func warnIfNotPublic(root, raw, vis string, out io.Writer) {
|
||||
fmt.Fprintf(out, " record only the dependency, which loomctl cannot do yet.\n")
|
||||
}
|
||||
|
||||
// NotFoundError is a 404, which over HTTP carries more than one reading.
|
||||
//
|
||||
// The convention names two — withdrawn, or access lost — because those are the
|
||||
// two a locked document can have. Adoption by name has a third: a document that
|
||||
// was never there under that name. Callers that know the repository can tell
|
||||
// them apart; this type is how they get the chance.
|
||||
type NotFoundError struct{ URL string }
|
||||
|
||||
func (e *NotFoundError) Error() string {
|
||||
return fmt.Sprintf("404 unresolved: %s was withdrawn, or this credential cannot see it — "+
|
||||
"over HTTP these are the same response", e.URL)
|
||||
}
|
||||
|
||||
type fetched struct {
|
||||
data []byte
|
||||
url string
|
||||
@@ -281,8 +294,7 @@ func fetch(cfg *config.Config, raw, ifNoneMatch string) (*fetched, string, error
|
||||
}
|
||||
return &fetched{data: b, url: resp.Request.URL.String()}, resp.Header.Get("ETag"), nil
|
||||
case http.StatusNotFound:
|
||||
return nil, "", fmt.Errorf("404 unresolved: %s was withdrawn, or this credential cannot see it — "+
|
||||
"over HTTP these are the same response", raw)
|
||||
return nil, "", &NotFoundError{URL: raw}
|
||||
default:
|
||||
return nil, "", fmt.Errorf("%s: %s", resp.Status, raw)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user