Extract publication recovery observation
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user