Extract publication recovery observation

This commit is contained in:
2026-11-02 18:58:48 +00:00
parent 447b42a1cd
commit 4dff8086ab
6 changed files with 307 additions and 8 deletions

View File

@@ -84,6 +84,47 @@ func Resolve(ctx context.Context, repositoryDir, remoteName string) (Endpoint, e
return endpoint, nil
}
// ObserveEndpoint creates a read-only command-scoped endpoint from previously
// captured publication evidence. The recorded URL is used for both directions;
// current transport settings are copied only when the original remote exists.
func ObserveEndpoint(ctx context.Context, repositoryDir, endpointURL, remoteName string) (Endpoint, error) {
if endpointURL == "" {
return Endpoint{}, fmt.Errorf("empty captured endpoint")
}
scopedName, err := newScopedName()
if err != nil {
return Endpoint{}, fmt.Errorf("create command-scoped remote name: %w", err)
}
endpoint := Endpoint{RemoteName: remoteName, PushURL: endpointURL, ScopedName: scopedName}
if remoteName != "" {
config, configErr := transportConfig(ctx, repositoryDir, remoteName, scopedName)
if configErr != nil {
return Endpoint{}, configErr
}
endpoint.transport = config
}
if err := endpoint.verifyScopedURLs(ctx, repositoryDir); err != nil {
return Endpoint{}, err
}
return endpoint, nil
}
// EqualObjectID compares supported full object IDs without regard to hex case.
func EqualObjectID(left, right string) bool {
return validObjectID(left) && validObjectID(right) && strings.EqualFold(left, right)
}
// ValidObjectID reports whether value is a full supported Git object ID.
func ValidObjectID(value string) bool { return validObjectID(value) }
// DisplayError exposes credential-safe rendering for shared recovery errors.
func DisplayError(err error) error {
if err == nil {
return nil
}
return displayError{cause: err}
}
// Recheck confirms both the selected remote's effective push destination and
// the scoped remote's effective read and push URLs still identify this endpoint.
func (endpoint Endpoint) Recheck(ctx context.Context, repositoryDir string) error {
@@ -305,15 +346,33 @@ func finalizeRef(ref RemoteRef) RemoteRef {
// CheckTagRef validates a complete tag name using Git ref rules.
func CheckTagRef(ctx context.Context, repositoryDir, tag string) error {
if tag == "" {
valid, err := ValidateTagRef(ctx, repositoryDir, tag)
if err != nil {
return err
}
if !valid {
return fmt.Errorf("invalid Git tag name %q", tag)
}
if _, err := run(ctx, repositoryDir, nil, "check-ref-format", "refs/tags/"+tag); err != nil {
return fmt.Errorf("invalid Git tag name %q: %w", tag, err)
}
return nil
}
// ValidateTagRef distinguishes a rejected ref from failures to run Git or
// access the selected repository.
func ValidateTagRef(ctx context.Context, repositoryDir, tag string) (bool, error) {
if tag == "" {
return false, nil
}
_, err := run(ctx, repositoryDir, nil, "check-ref-format", "refs/tags/"+tag)
if err == nil {
return true, nil
}
var exitErr interface{ ExitCode() int }
if errors.As(err, &exitErr) && exitErr.ExitCode() == 1 {
return false, nil
}
return false, fmt.Errorf("check Git tag name: %w", err)
}
func validObjectID(object string) bool {
if len(object) != 40 && len(object) != 64 {
return false

View File

@@ -42,6 +42,46 @@ func TestResolveUsesPushURLAndQueriesWithoutMutation(t *testing.T) {
}
}
func TestObserveEndpointUsesCapturedURLAndCurrentNamedTransportSettings(t *testing.T) {
repo := newRemoteFixture(t)
gitRun(t, repo.work, "remote", "remove", "origin")
gitRun(t, repo.work, "config", "remote.renamed.receivepack", "git-receive-pack")
endpoint, err := ObserveEndpoint(context.Background(), repo.work, repo.bare, "renamed")
if err != nil {
t.Fatal(err)
}
if endpoint.PushURL != repo.bare || len(endpoint.transport) != 1 || endpoint.transport[0] != "remote."+endpoint.ScopedName+".receivepack=git-receive-pack" {
t.Fatalf("observed endpoint = %#v", endpoint)
}
ref, err := endpoint.QueryTag(context.Background(), repo.work, "v1.2.3")
if err != nil || ref.State != RefAbsent {
t.Fatalf("QueryTag() = %#v, %v", ref, err)
}
}
func TestObserveEndpointDoesNotSelectOriginWithoutAName(t *testing.T) {
repo := newRemoteFixture(t)
gitRun(t, repo.work, "config", "remote.origin.receivepack", "custom-receive-pack")
endpoint, err := ObserveEndpoint(context.Background(), repo.work, repo.bare, "")
if err != nil {
t.Fatal(err)
}
if len(endpoint.transport) != 0 {
t.Fatalf("transport settings = %v; unnamed endpoint must not select origin", endpoint.transport)
}
}
func TestObserveEndpointRejectsCapturedURLRewriting(t *testing.T) {
repo := newRemoteFixture(t)
replacement := filepath.Join(t.TempDir(), "replacement.git")
gitRun(t, repo.work, "init", "--bare", replacement)
gitRun(t, repo.work, "config", "url."+replacement+".insteadOf", repo.bare)
_, err := ObserveEndpoint(context.Background(), repo.work, repo.bare, "")
if !errors.Is(err, ErrEndpointChanged) {
t.Fatalf("ObserveEndpoint() error = %v, want ErrEndpointChanged", err)
}
}
func TestRemoteTagOperationsAcceptSlashContainingRefs(t *testing.T) {
fixture := newRemoteFixture(t)
endpoint, err := Resolve(context.Background(), fixture.work, "origin")