diff --git a/CHANGELOG.md b/CHANGELOG.md index e5310caf3b..52fdf22383 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ All notable changes to `src-cli` are documented in this file. ### Changed +- `src campaign [apply|preview]` now prints more detailed information about the diffs produced in each repository when run in verbose mode with `-v`. [#390](https://github.com/sourcegraph/src-cli/pull/390) + ### Fixed - If `src campaign [validate|apply|preview]` was aborted while it was downloading repository archives it could leave behind partial ZIP files that would produce an error on the next run. This is now fixed by deleting partial files on abort. [#388](https://github.com/sourcegraph/src-cli/pull/388) diff --git a/cmd/src/campaign_progress_printer.go b/cmd/src/campaign_progress_printer.go index 171659a377..a8b7936755 100644 --- a/cmd/src/campaign_progress_printer.go +++ b/cmd/src/campaign_progress_printer.go @@ -9,10 +9,12 @@ import ( "github.com/sourcegraph/src-cli/internal/output" ) -func newCampaignProgressPrinter(out *output.Output, numParallelism int) *campaignProgressPrinter { +func newCampaignProgressPrinter(out *output.Output, verbose bool, numParallelism int) *campaignProgressPrinter { return &campaignProgressPrinter{ out: out, + verbose: verbose, + numParallelism: numParallelism, completedTasks: map[string]bool{}, @@ -26,6 +28,8 @@ func newCampaignProgressPrinter(out *output.Output, numParallelism int) *campaig type campaignProgressPrinter struct { out *output.Output + verbose bool + progress output.ProgressWithStatusBars numStatusBars int @@ -134,18 +138,47 @@ func (p *campaignProgressPrinter) PrintStatuses(statuses []*campaigns.TaskStatus } for _, ts := range newlyCompleted { - statusText, err := taskStatusText(ts) - if err != nil { - p.progress.Verbosef("%-*s failed to display status: %s", p.maxRepoName, ts.RepoName, err) - continue + var fileDiffs []*diff.FileDiff + + if ts.ChangesetSpec != nil { + var err error + fileDiffs, err = diff.ParseMultiFileDiff([]byte(ts.ChangesetSpec.Commits[0].Diff)) + if err != nil { + p.progress.Verbosef("%-*s failed to display status: %s", p.maxRepoName, ts.RepoName, err) + continue + } } - p.progress.Verbosef("%-*s %s", p.maxRepoName, ts.RepoName, statusText) + if p.verbose { + p.progress.WriteLine(output.Linef("", output.StylePending, "%s", ts.RepoName)) + + if ts.ChangesetSpec == nil { + p.progress.Verbosef(" No changes") + } else { + lines, err := verboseDiffSummary(fileDiffs) + if err != nil { + p.progress.Verbosef("%-*s failed to display status: %s", p.maxRepoName, ts.RepoName, err) + continue + } + + for _, line := range lines { + p.progress.Verbose(line) + } + } + + p.progress.Verbose("") + } if idx, ok := p.repoStatusBar[ts.RepoName]; ok { // Log that this task completed, but only if there is no // currently executing one in this bar, to avoid flicker. if _, ok := p.statusBarRepo[idx]; !ok { + statusText, err := taskStatusBarText(ts) + if err != nil { + p.progress.Verbosef("%-*s failed to display status: %s", p.maxRepoName, ts.RepoName, err) + continue + } + if ts.Err != nil { p.progress.StatusBarFailf(idx, statusText) } else { @@ -163,7 +196,7 @@ func (p *campaignProgressPrinter) PrintStatuses(statuses []*campaigns.TaskStatus continue } - statusText, err := taskStatusText(ts) + statusText, err := taskStatusBarText(ts) if err != nil { p.progress.Verbosef("%-*s failed to display status: %s", p.maxRepoName, ts.RepoName, err) continue @@ -181,7 +214,7 @@ type statusTexter interface { StatusText() string } -func taskStatusText(ts *campaigns.TaskStatus) (string, error) { +func taskStatusBarText(ts *campaigns.TaskStatus) (string, error) { var statusText string if ts.IsCompleted() { @@ -223,3 +256,57 @@ func taskStatusText(ts *campaigns.TaskStatus) (string, error) { return statusText, nil } + +func verboseDiffSummary(fileDiffs []*diff.FileDiff) ([]string, error) { + var ( + lines []string + + maxFilenameLen int + sumInsertions int + sumDeletions int + ) + + fileStats := make(map[string]string, len(fileDiffs)) + + for _, f := range fileDiffs { + name := f.NewName + if name == "/dev/null" { + name = f.OrigName + } + + if len(name) > maxFilenameLen { + maxFilenameLen = len(name) + } + + stat := f.Stat() + + sumInsertions += int(stat.Added) + int(stat.Changed) + sumDeletions += int(stat.Deleted) + int(stat.Changed) + + num := stat.Added + 2*stat.Changed + stat.Deleted + + fileStats[name] = fmt.Sprintf("%d %s", num, diffStatDiagram(stat)) + } + + for file, stats := range fileStats { + lines = append(lines, fmt.Sprintf("\t%-*s | %s", maxFilenameLen, file, stats)) + } + + var insertionsPlural string + if sumInsertions != 0 { + insertionsPlural = "s" + } + + var deletionsPlural string + if sumDeletions != 1 { + deletionsPlural = "s" + } + + lines = append(lines, fmt.Sprintf(" %s, %s, %s", + diffStatDescription(fileDiffs), + fmt.Sprintf("%d insertion%s", sumInsertions, insertionsPlural), + fmt.Sprintf("%d deletion%s", sumDeletions, deletionsPlural), + )) + + return lines, nil +} diff --git a/cmd/src/campaigns_common.go b/cmd/src/campaigns_common.go index c34545065d..6927d74788 100644 --- a/cmd/src/campaigns_common.go +++ b/cmd/src/campaigns_common.go @@ -232,7 +232,7 @@ func campaignsExecute(ctx context.Context, out *output.Output, svc *campaigns.Se campaignsCompletePending(pending, "Resolved repositories") } - p := newCampaignProgressPrinter(out, opts.Parallelism) + p := newCampaignProgressPrinter(out, *verbose, opts.Parallelism) specs, err := svc.ExecuteCampaignSpec(ctx, repos, executor, campaignSpec, p.PrintStatuses) if err != nil { return "", "", err diff --git a/internal/campaigns/service.go b/internal/campaigns/service.go index 6e15dae6df..5b285da17b 100644 --- a/internal/campaigns/service.go +++ b/internal/campaigns/service.go @@ -188,11 +188,10 @@ type ExecutorOpts struct { Parallelism int Timeout time.Duration - ClearCache bool - KeepLogs bool - VerboseLogger bool - TempDir string - CacheDir string + ClearCache bool + KeepLogs bool + TempDir string + CacheDir string } func (svc *Service) NewExecutor(opts ExecutorOpts) Executor {