package publish import ( "context" "errors" "fmt" "os" "os/exec" "path/filepath" "sort" "strings" ) type Attempt struct { RecordPath string Evidence Evidence } type AttemptsResult struct { RecordDir string Attempts []Attempt Diagnostics []*RecoveryError } func ListAttempts(ctx context.Context, repositoryDir, override string) (AttemptsResult, error) { if repositoryDir == "" { repositoryDir = "." } result := AttemptsResult{Attempts: []Attempt{}, Diagnostics: []*RecoveryError{}} var directory string { resolved, err := recordDirectoryFor(ctx, repositoryDir, override) if err != nil { return result, &RecoveryError{Kind: "storage_enumeration", cause: err} } directory = resolved } result.RecordDir = directory if err := ctx.Err(); err != nil { return result, &RecoveryError{Kind: "canceled", cause: err} } entries, err := os.ReadDir(directory) if err != nil { return result, &RecoveryError{Kind: "storage_enumeration", cause: fmt.Errorf("enumerate records in %q: %w", directory, err)} } paths := make([]string, 0, len(entries)) for _, entry := range entries { if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".json") { paths = append(paths, filepath.Join(directory, entry.Name())) } } sort.Strings(paths) for _, path := range paths { if err := ctx.Err(); err != nil { return result, &RecoveryError{Kind: "canceled", cause: err} } absolute, err := filepath.Abs(path) if err != nil { result.Diagnostics = append(result.Diagnostics, &RecoveryError{Kind: "record_read", RecordPath: path, cause: err}) continue } evidence, err := LoadEvidence(absolute) if err != nil { var recordError *RecoveryError if errors.As(err, &recordError) { result.Diagnostics = append(result.Diagnostics, recordError) } continue } result.Attempts = append(result.Attempts, Attempt{RecordPath: absolute, Evidence: evidence}) } return result, nil } func recordDirectoryFor(ctx context.Context, repositoryDir, override string) (string, error) { 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 }