Honor cancellation throughout publication recovery Git operations

This commit is contained in:
2026-11-02 22:20:53 +00:00
parent 9a7b9b9301
commit 04db23705a
10 changed files with 231 additions and 63 deletions

View File

@@ -9,7 +9,6 @@ import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
@@ -53,19 +52,19 @@ func LoadEvidence(path string) (Evidence, error) {
}
// SaveEvidence writes one private attempt record and returns its absolute path.
func SaveEvidence(repositoryDir, override string, evidence Evidence) (string, error) {
recordDir, root, err := resolveRecordDir(repositoryDir, override)
func SaveEvidence(ctx context.Context, repositoryDir, override string, evidence Evidence) (string, error) {
recordDir, root, err := resolveRecordDir(ctx, repositoryDir, override)
if err != nil {
return "", &RecoveryError{Kind: "record_write", cause: err}
}
return saveEvidenceInDir(repositoryDir, root, override, recordDir, evidence)
return saveEvidenceInDir(ctx, repositoryDir, root, override, recordDir, evidence)
}
func saveEvidenceInDir(repositoryDir, root, override, recordDir string, evidence Evidence) (string, error) {
return saveEvidenceNamed(repositoryDir, root, override, recordDir, evidence, proposedRecordPath)
func saveEvidenceInDir(ctx context.Context, repositoryDir, root, override, recordDir string, evidence Evidence) (string, error) {
return saveEvidenceNamed(ctx, repositoryDir, root, override, recordDir, evidence, proposedRecordPath)
}
func saveEvidenceNamed(repositoryDir, root, override, recordDir string, evidence Evidence, nextPath func(string) (string, error)) (string, error) {
func saveEvidenceNamed(ctx context.Context, repositoryDir, root, override, recordDir string, evidence Evidence, nextPath func(string) (string, error)) (string, error) {
if evidence.RecordedAt == "" {
evidence.RecordedAt = time.Now().UTC().Format(time.RFC3339Nano)
}
@@ -86,7 +85,7 @@ func saveEvidenceNamed(repositoryDir, root, override, recordDir string, evidence
return "", &RecoveryError{Kind: "record_write", cause: fmt.Errorf("name publication record: %w", err)}
}
if override != "" && root != "" {
if err := rejectUnignoredWorktreePath(repositoryDir, root, path); err != nil {
if err := rejectUnignoredWorktreePath(ctx, repositoryDir, root, path); err != nil {
return "", &RecoveryError{Kind: "record_write", cause: err}
}
}
@@ -111,14 +110,10 @@ func saveEvidenceNamed(repositoryDir, root, override, recordDir string, evidence
return "", &RecoveryError{Kind: "record_write", cause: fmt.Errorf("could not allocate a unique record name in %q", recordDir)}
}
func resolveRecordDir(repositoryDir, override string) (string, string, error) {
rootOutput, err := exec.Command("git", "-C", repositoryDir, "rev-parse", "--show-toplevel").Output()
func resolveRecordDir(ctx context.Context, repositoryDir, override string) (string, string, error) {
root, err := git.RepositoryRoot(ctx, repositoryDir)
if err != nil {
return "", "", fmt.Errorf("resolve source repository root: %w", err)
}
root, err := filepath.Abs(strings.TrimSpace(string(rootOutput)))
if err != nil {
return "", "", fmt.Errorf("resolve source repository root: %w", err)
return "", "", err
}
if override != "" {
if filepath.IsAbs(override) {
@@ -126,11 +121,11 @@ func resolveRecordDir(repositoryDir, override string) (string, string, error) {
}
return filepath.Join(root, override), root, nil
}
common, err := exec.Command("git", "-C", repositoryDir, "rev-parse", "--path-format=absolute", "--git-common-dir").Output()
common, err := git.CommonDirectory(ctx, repositoryDir)
if err != nil {
return "", "", fmt.Errorf("resolve shared Git metadata directory: %w", err)
return "", "", err
}
return filepath.Join(strings.TrimSpace(string(common)), "releasekit", "publication-attempts"), root, nil
return filepath.Join(common, "releasekit", "publication-attempts"), root, nil
}
func proposedRecordPath(directory string) (string, error) {
@@ -141,7 +136,7 @@ func proposedRecordPath(directory string) (string, error) {
return filepath.Join(directory, "attempt-"+hex.EncodeToString(nonce[:])+".json"), nil
}
func rejectUnignoredWorktreePath(repositoryDir, root, proposed string) error {
func rejectUnignoredWorktreePath(ctx context.Context, repositoryDir, root, proposed string) error {
parent, err := filepath.EvalSymlinks(filepath.Dir(proposed))
if err != nil {
return fmt.Errorf("resolve record directory %q: %w", filepath.Dir(proposed), err)
@@ -151,7 +146,7 @@ func rejectUnignoredWorktreePath(repositoryDir, root, proposed string) error {
if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
return nil
}
metadata, err := git.MetadataDirectories(context.Background(), repositoryDir)
metadata, err := git.MetadataDirectories(ctx, repositoryDir)
if err != nil {
return err
}
@@ -161,14 +156,12 @@ func rejectUnignoredWorktreePath(repositoryDir, root, proposed string) error {
return nil
}
}
cmd := exec.Command("git", "-C", repositoryDir, "check-ignore", "-q", "--", actual)
err = cmd.Run()
if err == nil {
return nil
ignored, err := git.IsIgnored(ctx, repositoryDir, actual)
if err != nil {
return fmt.Errorf("check Git ignore rules for record path %q: %w", actual, err)
}
var exitErr *exec.ExitError
if errors.As(err, &exitErr) && exitErr.ExitCode() == 1 {
if !ignored {
return fmt.Errorf("record path %q is inside the worktree and is not ignored", actual)
}
return fmt.Errorf("check Git ignore rules for record path %q: %w", actual, err)
return nil
}