Skip to content

Commit 981373d

Browse files
henrikjeclaude
andcommitted
fix(undo): use clearer language in undo plan display
Replace scary "reset to" with "restore to" and show the target commit subject so users can confirm where they're going back to. Drop confusing commit count and file count stats that didn't match any mental model. Stash restore now shows file count for the actual uncommitted files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 597708b commit 981373d

File tree

4 files changed

+33
-19
lines changed

4 files changed

+33
-19
lines changed

src/lib/sync/undo/assess.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,21 +224,28 @@ export async function assessSyncUndo(record: OperationRecord, wsDir: string): Pr
224224
const currentHead = headResult.stdout.trim();
225225

226226
if (state.postHead && currentHead === state.postHead) {
227-
const commitCountResult = await gitLocal(repoDir, "rev-list", "--count", `${state.preHead}..${state.postHead}`);
227+
const [commitCountResult, diffStats, subjectResult, stashStats] = await Promise.all([
228+
gitLocal(repoDir, "rev-list", "--count", `${state.preHead}..${state.postHead}`),
229+
getDiffShortstat(repoDir, state.preHead, state.postHead),
230+
gitLocal(repoDir, "log", "--format=%s", "-1", state.preHead),
231+
state.stashSha ? getDiffShortstat(repoDir, state.preHead, state.stashSha) : Promise.resolve(null),
232+
]);
228233
const commitCount = Number.parseInt(commitCountResult.stdout.trim(), 10) || 0;
229-
const diffStats = await getDiffShortstat(repoDir, state.preHead, state.postHead);
234+
const targetSubject = subjectResult.exitCode === 0 ? subjectResult.stdout.trim() : undefined;
230235

231236
assessments.push({
232237
repo: repoName,
233238
repoDir,
234239
action: "needs-undo",
235-
detail: `reset to ${state.preHead.slice(0, 7)}`,
240+
detail: `restore to ${state.preHead.slice(0, 7)}`,
241+
targetSubject,
236242
stats: {
237243
commitCount,
238244
filesChanged: diffStats?.files ?? 0,
239245
insertions: diffStats?.insertions ?? 0,
240246
deletions: diffStats?.deletions ?? 0,
241247
hasStash: state.stashSha != null,
248+
stashFilesChanged: stashStats?.files,
242249
},
243250
});
244251
} else if (currentHead === state.preHead) {

src/lib/sync/undo/execute.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ export async function executeSyncUndo(record: OperationRecord, assessments: Repo
143143
const state = record.repos[a.repo];
144144
if (!state) continue;
145145

146-
inlineStart(a.repo, "resetting");
146+
inlineStart(a.repo, "restoring");
147147
const result = await gitLocal(a.repoDir, "reset", "--hard", state.preHead);
148148
if (result.exitCode === 0) {
149149
if (state.stashSha) {
@@ -153,19 +153,19 @@ export async function executeSyncUndo(record: OperationRecord, assessments: Repo
153153
if (fallbackResult.exitCode !== 0) {
154154
inlineResult(
155155
a.repo,
156-
`reset to ${state.preHead.slice(0, 7)} (stash restore failed — run 'git stash apply ${state.stashSha}' manually)`,
156+
`restored to ${state.preHead.slice(0, 7)} (stash restore failed — run 'git stash apply ${state.stashSha}' manually)`,
157157
);
158158
undone++;
159159
undoneRepos.push(a.repo);
160160
continue;
161161
}
162162
}
163163
}
164-
inlineResult(a.repo, `reset to ${state.preHead.slice(0, 7)}`);
164+
inlineResult(a.repo, `restored to ${state.preHead.slice(0, 7)}`);
165165
undone++;
166166
undoneRepos.push(a.repo);
167167
} else {
168-
inlineResult(a.repo, "failed to reset");
168+
inlineResult(a.repo, "failed to restore");
169169
failures.push(a.repo);
170170
}
171171
}

src/lib/sync/undo/plan.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { type RenderContext, render } from "../../render/render";
55
import { verboseCommitsToNodes } from "../../render/status-verbose";
66
import { plural } from "../../terminal/output";
77
import { shouldColor } from "../../terminal/tty";
8-
import type { RepoUndoAssessment } from "./types";
8+
import type { RepoUndoAssessment, UndoStats } from "./types";
99

1010
export function formatUndoPlan(
1111
record: OperationRecord,
@@ -30,21 +30,16 @@ export function formatUndoPlan(
3030
switch (a.action) {
3131
case "needs-undo": {
3232
actionCell = a.forced ? cell(a.detail ?? "undo", "attention") : cell(a.detail ?? "undo");
33+
if (a.targetSubject) {
34+
actionCell = suffix(actionCell, ` "${truncate(a.targetSubject, 50)}"`, "muted");
35+
}
36+
if (a.stats?.hasStash) {
37+
actionCell = suffix(actionCell, ` — + restore stash${stashFilesSuffix(a.stats)}`, "muted");
38+
}
3339
if (verbose && a.verbose?.commits && a.verbose.commits.length > 0) {
34-
if (a.stats?.hasStash) {
35-
actionCell = suffix(actionCell, " — + restore stash", "muted");
36-
}
3740
afterRow = verboseCommitsToNodes(a.verbose.commits, a.verbose.totalCommits, "Rolling back:", {
3841
diffStats: a.verbose.diffStats,
3942
});
40-
} else if (a.stats) {
41-
const parts: string[] = [];
42-
if (a.stats.commitCount > 0) parts.push(plural(a.stats.commitCount, "commit"));
43-
if (a.stats.filesChanged > 0) parts.push(`${a.stats.filesChanged} files changed`);
44-
if (a.stats.hasStash) parts.push("+ restore stash");
45-
if (parts.length > 0) {
46-
actionCell = suffix(actionCell, ` — ${parts.join(", ")}`, "muted");
47-
}
4843
}
4944
break;
5045
}
@@ -92,3 +87,13 @@ function formatTime(iso: string): string {
9287
return iso;
9388
}
9489
}
90+
91+
function truncate(text: string, max: number): string {
92+
return text.length > max ? `${text.slice(0, max - 1)}…` : text;
93+
}
94+
95+
function stashFilesSuffix(stats: UndoStats): string {
96+
return stats.stashFilesChanged != null && stats.stashFilesChanged > 0
97+
? ` (${plural(stats.stashFilesChanged, "file")})`
98+
: "";
99+
}

src/lib/sync/undo/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface UndoStats {
1515
insertions: number;
1616
deletions: number;
1717
hasStash: boolean;
18+
stashFilesChanged?: number;
1819
}
1920

2021
export interface UndoVerboseInfo {
@@ -28,6 +29,7 @@ export interface RepoUndoAssessment {
2829
repoDir: string;
2930
action: UndoAction;
3031
detail?: string;
32+
targetSubject?: string;
3133
stats?: UndoStats;
3234
verbose?: UndoVerboseInfo;
3335
/** True when a drifted repo was reclassified to needs-undo by --force */

0 commit comments

Comments
 (0)