RunAuditDiff compares behavior between a base workflow run and one or more comparison runs. The base run is the reference point; each comparison run is diffed against it independently.
(ctx context.Context, baseRunID int64, compareRunIDs []int64, opts AuditOptions)
| 112 | // RunAuditDiff compares behavior between a base workflow run and one or more comparison runs. |
| 113 | // The base run is the reference point; each comparison run is diffed against it independently. |
| 114 | func RunAuditDiff(ctx context.Context, baseRunID int64, compareRunIDs []int64, opts AuditOptions) error { |
| 115 | owner := opts.Owner |
| 116 | repo := opts.Repo |
| 117 | hostname := opts.Hostname |
| 118 | outputDir := opts.OutputDir |
| 119 | verbose := opts.Verbose |
| 120 | format := opts.Format |
| 121 | artifactSets := opts.ArtifactSets |
| 122 | |
| 123 | auditDiffLog.Printf("Starting audit diff: base=%d, compare=%v", baseRunID, compareRunIDs) |
| 124 | |
| 125 | // Validate and resolve artifact sets into a concrete filter. |
| 126 | if err := ValidateArtifactSets(artifactSets); err != nil { |
| 127 | return err |
| 128 | } |
| 129 | artifactFilter := ResolveArtifactFilter(artifactSets) |
| 130 | if len(artifactFilter) > 0 { |
| 131 | auditDiffLog.Printf("Artifact filter active: %v", artifactFilter) |
| 132 | if verbose { |
| 133 | fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Artifact filter: downloading only "+strings.Join(artifactFilter, ", "))) |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | // Auto-detect GHES host from git remote if hostname is not provided |
| 138 | if hostname == "" { |
| 139 | hostname = getHostFromOriginRemote() |
| 140 | if hostname != "github.com" { |
| 141 | auditDiffLog.Printf("Auto-detected GHES host from git remote: %s", hostname) |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | // Check context cancellation |
| 146 | select { |
| 147 | case <-ctx.Done(): |
| 148 | fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Operation cancelled")) |
| 149 | return ctx.Err() |
| 150 | default: |
| 151 | } |
| 152 | |
| 153 | if len(compareRunIDs) == 1 { |
| 154 | fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Comparing workflow runs: Run #%d → Run #%d", baseRunID, compareRunIDs[0]))) |
| 155 | } else { |
| 156 | fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Comparing workflow runs: Run #%d (base) vs %d comparison runs", baseRunID, len(compareRunIDs)))) |
| 157 | } |
| 158 | |
| 159 | // Load base run summary once (shared across all comparisons) |
| 160 | fmt.Fprintln(os.Stderr, console.FormatProgressMessage(fmt.Sprintf("Loading data for base run %d...", baseRunID))) |
| 161 | baseSummary, err := loadRunSummaryForDiff(ctx, baseRunID, outputDir, owner, repo, hostname, verbose, artifactFilter) |
| 162 | if err != nil { |
| 163 | return fmt.Errorf("failed to load data for base run %d: %w", baseRunID, err) |
| 164 | } |
| 165 | |
| 166 | diffs := make([]*AuditDiff, 0, len(compareRunIDs)) |
| 167 | |
| 168 | for _, compareRunID := range compareRunIDs { |
| 169 | // Check context cancellation between downloads |
| 170 | select { |
| 171 | case <-ctx.Done(): |
no test coverage detected