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

@@ -73,7 +73,7 @@ func TestBuiltPublishRecoveryCommandSelectsItsRecordAmongOtherAttempts(t *testin
{RepositoryDir: repository, ModulePath: "example.com/other-module", Version: "v8.8.8", Tag: "v8.8.8", Endpoint: remote, Remote: "origin"},
{RepositoryDir: repository, ModulePath: "example.com/retry", Version: "v1.2.4", Tag: "v1.2.4", Endpoint: remote, Remote: "origin"},
} {
if _, err := publish.SaveEvidence(repository, store, decoy); err != nil {
if _, err := publish.SaveEvidence(t.Context(), repository, store, decoy); err != nil {
t.Fatal(err)
}
}

View File

@@ -36,7 +36,7 @@ func TestReconcileCommandClassifiesRecordsFromAnotherWorkingDirectory(t *testing
Remote: "origin",
RecordedAt: "2026-11-02T00:00:00Z",
}
record, err := publish.SaveEvidence(repository, store, evidence)
record, err := publish.SaveEvidence(t.Context(), repository, store, evidence)
if err != nil {
t.Fatal(err)
}
@@ -113,7 +113,7 @@ func TestReconcileCommandClassifiesRecordsFromAnotherWorkingDirectory(t *testing
partialEvidence := evidence
partialEvidence.TagObject = cliGitOutput(t, repository, "rev-parse", "refs/tags/v1.2.3")
partialEvidence.CandidateCommit = ""
partialRecord, err := publish.SaveEvidence(repository, store, partialEvidence)
partialRecord, err := publish.SaveEvidence(t.Context(), repository, store, partialEvidence)
if err != nil {
t.Fatal(err)
}
@@ -194,7 +194,7 @@ func TestPublicationAttemptsCommandRedactsCredentialsAndReportsPartialDiscovery(
Remote: "origin",
RecordedAt: "2026-11-02T00:00:00Z",
}
if _, err := publish.SaveEvidence(repository, store, evidence); err != nil {
if _, err := publish.SaveEvidence(t.Context(), repository, store, evidence); err != nil {
t.Fatal(err)
}
brokenPath := filepath.Join(store, "damaged.json")

View File

@@ -128,6 +128,13 @@ unignored worktree contents.
## R04: Restore cancellation across storage and recovery phases — P2
**Completed:** Storage and discovery share context-aware Git adapters; later
observation classifies canceled tag validation and queries consistently. Tests
passed for already canceled discovery and cancellation synchronized with active
root/metadata lookups, ignore checks, publication storage, and tag operations.
Focused Git, facade, and CLI recovery checks also passed; immediate failed-push
reconciliation retains its existing bounded context.
**Relevant code:** `internal/publish/record.go:112` and `record.go:152`;
`internal/publish/attempts.go:31` and `attempts.go:74`;
`internal/publish/reconcile.go:109` and `reconcile.go:130`.

View File

@@ -73,6 +73,28 @@ func MetadataDirectories(ctx context.Context, directory string) ([]string, error
return directories, nil
}
// CommonDirectory resolves the shared Git metadata directory.
func CommonDirectory(ctx context.Context, directory string) (string, error) {
output, err := run(ctx, directory, nil, "rev-parse", "--path-format=absolute", "--git-common-dir")
if err != nil {
return "", fmt.Errorf("resolve shared Git metadata directory: %w", err)
}
return normalizeRepositoryRoot(output)
}
// IsIgnored checks whether Git excludes the selected worktree path.
func IsIgnored(ctx context.Context, directory, path string) (bool, error) {
_, err := run(ctx, directory, nil, "check-ignore", "-q", "--", path)
if err == nil {
return true, nil
}
var exitErr *exec.ExitError
if errors.As(err, &exitErr) && exitErr.ExitCode() == 1 {
return false, nil
}
return false, err
}
// DiscoverRepositoryRoot finds a containing checkout or confirms standalone use.
func DiscoverRepositoryRoot(ctx context.Context, directory string) (string, bool, error) {
if directory == "" {

View File

@@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
@@ -26,11 +25,14 @@ func ListAttempts(ctx context.Context, repositoryDir, override string) (Attempts
repositoryDir = "."
}
result := AttemptsResult{Attempts: []Attempt{}, Diagnostics: []*RecoveryError{}}
if err := ctx.Err(); err != nil {
return result, recoveryFailure(ctx, "storage_enumeration", err)
}
var directory string
{
resolved, err := recordDirectoryFor(ctx, repositoryDir, override)
if err != nil {
return result, &RecoveryError{Kind: "storage_enumeration", cause: err}
return result, recoveryFailure(ctx, "storage_enumeration", err)
}
directory = resolved
}
@@ -40,7 +42,7 @@ func ListAttempts(ctx context.Context, repositoryDir, override string) (Attempts
}
entries, err := os.ReadDir(directory)
if err != nil {
return result, &RecoveryError{Kind: "storage_enumeration", cause: fmt.Errorf("enumerate records in %q: %w", directory, err)}
return result, recoveryFailure(ctx, "storage_enumeration", fmt.Errorf("enumerate records in %q: %w", directory, err))
}
paths := make([]string, 0, len(entries))
for _, entry := range entries {
@@ -75,17 +77,6 @@ func recordDirectoryFor(ctx context.Context, repositoryDir, override string) (st
if filepath.IsAbs(override) {
return filepath.Clean(override), nil
}
root, err := exec.CommandContext(ctx, "git", "-C", repositoryDir, "rev-parse", "--show-toplevel").Output()
if err != nil {
return "", err
}
rootPath := strings.TrimSpace(string(root))
if override != "" {
return filepath.Join(rootPath, override), nil
}
common, err := exec.CommandContext(ctx, "git", "-C", repositoryDir, "rev-parse", "--path-format=absolute", "--git-common-dir").Output()
if err != nil {
return "", err
}
return filepath.Join(strings.TrimSpace(string(common)), "releasekit", "publication-attempts"), nil
directory, _, err := resolveRecordDir(ctx, repositoryDir, override)
return directory, err
}

View File

@@ -0,0 +1,147 @@
package publish
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"
"time"
)
func TestRecoveryAlreadyCanceledDiscovery(t *testing.T) {
for _, override := range []string{"", "relative"} {
ctx, cancel := context.WithCancel(t.Context())
cancel()
result, err := ListAttempts(ctx, t.TempDir(), override)
requireCanceledRecovery(t, err)
if len(result.Attempts) != 0 {
t.Fatalf("unexpected attempts: %#v", result)
}
}
}
func requireCanceledRecovery(t *testing.T, err error) {
t.Helper()
var recovery *RecoveryError
if !errors.As(err, &recovery) || recovery.Kind != "canceled" || !errors.Is(err, context.Canceled) {
t.Fatalf("cancellation = %v; want canceled kind and cause", err)
}
}
func TestRecoveryCancelsActiveGitOperations(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("fixture uses a POSIX Git launcher")
}
tests := []struct {
name, command string
run func(context.Context, string) error
}{
{"default discovery root", "--show-toplevel", func(ctx context.Context, repo string) error { _, err := ListAttempts(ctx, repo, ""); return err }},
{"relative discovery root", "--show-toplevel", func(ctx context.Context, repo string) error { _, err := ListAttempts(ctx, repo, "records"); return err }},
{"default discovery metadata", "--git-common-dir", func(ctx context.Context, repo string) error { _, err := ListAttempts(ctx, repo, ""); return err }},
{"record metadata", "--git-common-dir", func(ctx context.Context, repo string) error {
_, err := SaveEvidence(ctx, repo, "", Evidence{})
return err
}},
{"record ignore guard", "check-ignore", func(ctx context.Context, repo string) error {
_, err := SaveEvidence(ctx, repo, "records", Evidence{})
return err
}},
{"publication metadata", "--git-common-dir", func(ctx context.Context, repo string) error {
result, err := Publish(ctx, Request{RepositoryDir: repo, Product: "Sample", Version: "v1.2.3"})
if !errors.Is(result.RecordError, context.Canceled) {
return fmt.Errorf("record cancellation lost: %v", result.RecordError)
}
return err
}},
{"tag validation", "check-ref-format", func(ctx context.Context, repo string) error {
_, err := Reconcile(ctx, Evidence{RepositoryDir: repo, Endpoint: repo, Tag: "v1.2.3"})
return err
}},
{"tag query", "ls-remote", func(ctx context.Context, repo string) error {
_, err := Reconcile(ctx, Evidence{RepositoryDir: repo, Endpoint: repo, Tag: "v1.2.3"})
return err
}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
repo := recordRepo(t)
if test.name == "publication metadata" {
if err := os.WriteFile(filepath.Join(repo, "go.mod"), []byte("module example.com/sample\n\ngo 1.26.0\n"), 0600); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Join(repo, "docs", "releases"), 0700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(repo, "docs", "releases", "v1.2.3.md"), []byte("# Sample v1.2.3\n\nChanges.\n"), 0600); err != nil {
t.Fatal(err)
}
recordGit(t, repo, "checkout", "-B", "main")
recordGit(t, repo, "add", ".")
recordGit(t, repo, "commit", "-qm", "Release candidate")
}
realGit, err := exec.LookPath("git")
if err != nil {
t.Fatal(err)
}
bin := t.TempDir()
ready := filepath.Join(t.TempDir(), "ready")
quote := func(s string) string { return "'" + strings.ReplaceAll(s, "'", "'\"'\"'") + "'" }
script := "#!/bin/sh\nfor arg in \"$@\"; do\n if [ \"$arg\" = " + quote(test.command) + " ]; then\n echo $$ > " + quote(ready) + "\n exec sleep 600\n fi\ndone\nexec " + quote(realGit) + " \"$@\"\n"
if err := os.WriteFile(filepath.Join(bin, "git"), []byte(script), 0755); err != nil {
t.Fatal(err)
}
t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH"))
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
done := make(chan error, 1)
go func() { done <- test.run(ctx, repo) }()
// Synchronize cancellation with the intercepted operation, rather than timing its start.
ticker := time.NewTicker(5 * time.Millisecond)
defer ticker.Stop()
timeout := time.NewTimer(10 * time.Second)
defer timeout.Stop()
var pid int
waiting:
for {
select {
case err := <-done:
t.Fatalf("operation finished before interception: %v", err)
case <-timeout.C:
t.Fatal("Git operation was never intercepted")
case <-ticker.C:
data, err := os.ReadFile(ready)
if err == nil {
pid, err = strconv.Atoi(strings.TrimSpace(string(data)))
if err == nil {
break waiting
}
}
}
}
cancel()
select {
case err := <-done:
if !errors.Is(err, context.Canceled) {
t.Fatalf("lost context cause: %v", err)
}
if strings.Contains(test.name, "discovery") || test.command == "check-ref-format" || test.command == "ls-remote" {
requireCanceledRecovery(t, err)
}
case <-time.After(5 * time.Second):
process, _ := os.FindProcess(pid)
if process != nil {
_ = process.Kill()
}
<-done
t.Fatal("cancellation did not stop the Git operation")
}
})
}
}

View File

@@ -87,7 +87,7 @@ func Publish(ctx context.Context, request Request) (result Result, resultErr err
return
}
recorded = true
path, err := saveEvidenceInDir(result.RepositoryDir, repositoryRoot, request.RecordDir, recordDir, result.Evidence)
path, err := saveEvidenceInDir(ctx, result.RepositoryDir, repositoryRoot, request.RecordDir, recordDir, result.Evidence)
if err != nil {
result.RecordError = err
return
@@ -118,7 +118,7 @@ func Publish(ctx context.Context, request Request) (result Result, resultErr err
initialized = true
result.Evidence.RecordedAt = time.Now().UTC().Format(time.RFC3339Nano)
var storageErr error
recordDir, repositoryRoot, storageErr = resolveRecordDir(captured.RepositoryDir, request.RecordDir)
recordDir, repositoryRoot, storageErr = resolveRecordDir(ctx, captured.RepositoryDir, request.RecordDir)
if storageErr != nil {
result.RecordError = &RecoveryError{Kind: "record_write", cause: storageErr}
recordDir = ""
@@ -200,7 +200,7 @@ func Publish(ctx context.Context, request Request) (result Result, resultErr err
result.State = Unknown
if !recorded {
recorded = true
path, recordErr := saveEvidenceInDir(result.RepositoryDir, repositoryRoot, request.RecordDir, recordDir, result.Evidence)
path, recordErr := saveEvidenceInDir(ctx, result.RepositoryDir, repositoryRoot, request.RecordDir, recordDir, result.Evidence)
if recordErr != nil {
result.RecordError = recordErr
} else {

View File

@@ -108,7 +108,7 @@ func observe(ctx context.Context, evidence Evidence) (Observation, error) {
}
valid, err := git.ValidateTagRef(ctx, evidence.RepositoryDir, evidence.Tag)
if err != nil {
return Observation{Evidence: evidence, State: Unknown}, &RecoveryError{Kind: "remote_observation", cause: err}
return Observation{Evidence: evidence, State: Unknown}, recoveryFailure(ctx, "remote_observation", err)
}
if !valid {
return Observation{Evidence: evidence, State: Unknown}, &RecoveryError{Kind: "invalid_evidence"}
@@ -129,7 +129,7 @@ func observe(ctx context.Context, evidence Evidence) (Observation, error) {
}
ref, err := endpoint.QueryTag(ctx, evidence.RepositoryDir, evidence.Tag)
if err != nil {
return Observation{Evidence: evidence, State: Unknown}, &RecoveryError{Kind: "remote_observation", cause: err}
return Observation{Evidence: evidence, State: Unknown}, recoveryFailure(ctx, "remote_observation", err)
}
result, err := classify(evidence, ref)
if err != nil && ctx.Err() != nil {
@@ -142,3 +142,11 @@ func observe(ctx context.Context, evidence Evidence) (Observation, error) {
func Reconcile(ctx context.Context, evidence Evidence) (Observation, error) {
return observe(ctx, evidence)
}
// recoveryFailure classifies failed operations while preserving caller cancellation.
func recoveryFailure(ctx context.Context, kind string, err error) *RecoveryError {
if contextErr := ctx.Err(); contextErr != nil {
return &RecoveryError{Kind: "canceled", cause: contextErr}
}
return &RecoveryError{Kind: kind, cause: err}
}

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
}

View File

@@ -74,11 +74,11 @@ func TestSaveEvidenceUsesPrivateUniqueRecordsAndLinkedWorktreeMetadata(t *testin
worktree := filepath.Join(t.TempDir(), "linked")
recordGit(t, repo, "worktree", "add", "--detach", worktree)
evidence := Evidence{Tag: "v1.2.3", Endpoint: "https://user:secret@example.invalid/repo.git"}
first, err := SaveEvidence(worktree, "", evidence)
first, err := SaveEvidence(t.Context(), worktree, "", evidence)
if err != nil {
t.Fatal(err)
}
second, err := SaveEvidence(worktree, "", evidence)
second, err := SaveEvidence(t.Context(), worktree, "", evidence)
if err != nil {
t.Fatal(err)
}
@@ -129,14 +129,14 @@ func TestSaveEvidenceResolvesRelativeAndIgnoredOverrides(t *testing.T) {
if err := os.WriteFile(filepath.Join(repo, ".gitignore"), []byte("saved-records/\n"), 0600); err != nil {
t.Fatal(err)
}
if _, err := SaveEvidence(repo, "saved-records", Evidence{Tag: "v1.2.3"}); err != nil {
if _, err := SaveEvidence(t.Context(), repo, "saved-records", Evidence{Tag: "v1.2.3"}); err != nil {
t.Fatalf("ignored override: %v", err)
}
if _, err := os.Stat(filepath.Join(repo, "saved-records")); err != nil {
t.Fatal(err)
}
external := filepath.Join(t.TempDir(), "external-store")
path, err := SaveEvidence(repo, external, Evidence{Tag: "v2.0.0"})
path, err := SaveEvidence(t.Context(), repo, external, Evidence{Tag: "v2.0.0"})
if err != nil || !strings.HasPrefix(path, external+string(filepath.Separator)) {
t.Fatalf("absolute external override path=%q error=%v", path, err)
}
@@ -154,13 +154,13 @@ func TestSaveEvidenceAllowsExplicitGitMetadataStores(t *testing.T) {
case "separate":
recordGit(t, repo, "init", "--separate-git-dir", filepath.Join(t.TempDir(), "metadata"))
}
defaultPath, err := SaveEvidence(repo, "", Evidence{})
defaultPath, err := SaveEvidence(t.Context(), repo, "", Evidence{})
if err != nil {
t.Fatal(err)
}
privateDir := strings.TrimSuffix(recordGit(t, repo, "rev-parse", "--absolute-git-dir"), "\n")
for _, directory := range []string{filepath.Dir(defaultPath), filepath.Join(privateDir, "other-private-records")} {
path, err := SaveEvidence(repo, directory, Evidence{Tag: "v1.2.3"})
path, err := SaveEvidence(t.Context(), repo, directory, Evidence{Tag: "v1.2.3"})
if err != nil || filepath.Dir(path) != directory {
t.Fatalf("explicit metadata store %q = %q, %v", directory, path, err)
}
@@ -174,7 +174,7 @@ func TestSaveEvidenceAllowsExplicitGitMetadataStores(t *testing.T) {
func TestSaveEvidenceRejectsUnignoredOverrideInsideWorktree(t *testing.T) {
repo := recordRepo(t)
_, err := SaveEvidence(repo, "records", Evidence{Tag: "v1.2.3"})
_, err := SaveEvidence(t.Context(), repo, "records", Evidence{Tag: "v1.2.3"})
var recovery *RecoveryError
if !errors.As(err, &recovery) || recovery.Kind != "record_write" {
t.Fatalf("SaveEvidence() error=%v", err)
@@ -198,14 +198,14 @@ func TestSaveEvidenceChecksEachActualFilename(t *testing.T) {
next := func(name string) func(string) (string, error) {
return func(directory string) (string, error) { return filepath.Join(directory, name), nil }
}
path, err := saveEvidenceNamed(repo, repo, dir, dir, Evidence{Tag: "v1.0.0"}, next("attempt-0.json"))
path, err := saveEvidenceNamed(t.Context(), repo, repo, dir, dir, Evidence{Tag: "v1.0.0"}, next("attempt-0.json"))
if err != nil {
t.Fatal(err)
}
recordGit(t, repo, "check-ignore", "--", path)
// A collision must validate the replacement name as well.
calls := 0
_, err = saveEvidenceNamed(repo, repo, dir, dir, Evidence{Tag: "v2.0.0"}, func(directory string) (string, error) {
_, err = saveEvidenceNamed(t.Context(), repo, repo, dir, dir, Evidence{Tag: "v2.0.0"}, func(directory string) (string, error) {
calls++
name := "attempt-0.json"
if calls > 1 {
@@ -234,8 +234,8 @@ func TestListAttemptsReturnsReadableRecordsAndDiagnosticsInPathOrder(t *testing.
if err := os.Mkdir(directory, 0700); err != nil {
t.Fatal(err)
}
second, _ := SaveEvidence(repo, directory, Evidence{Tag: "v2.0.0"})
first, _ := SaveEvidence(repo, directory, Evidence{Tag: "v1.0.0"})
second, _ := SaveEvidence(t.Context(), repo, directory, Evidence{Tag: "v2.0.0"})
first, _ := SaveEvidence(t.Context(), repo, directory, Evidence{Tag: "v1.0.0"})
if err := os.WriteFile(filepath.Join(directory, "broken.json"), []byte("{"), 0600); err != nil {
t.Fatal(err)
}