From 7df574741850606106cc40d76ed5baa5d9f06ce7 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Sun, 25 Oct 2026 08:40:47 +0800 Subject: [PATCH] fix(vcs): Label restored local changes as yours after Smart Checkout; name the branch checked out Conflicts from restoring stashed changes show the user's uncommitted changes on the left like other operations, and the checkout notification names the new branch instead of the previous one. --- crates/ide-vcs/src/ops.rs | 6 ++++++ crates/ide-vcs/tests/repo.rs | 5 +++++ src/features/vcs/__tests__/conflicts.test.ts | 4 ++++ src/features/vcs/branchOps.ts | 2 ++ src/features/vcs/conflicts.ts | 5 +++++ 5 files changed, 22 insertions(+) diff --git a/crates/ide-vcs/src/ops.rs b/crates/ide-vcs/src/ops.rs index 892a2ef66..e36f77c92 100644 --- a/crates/ide-vcs/src/ops.rs +++ b/crates/ide-vcs/src/ops.rs @@ -56,6 +56,12 @@ pub(crate) fn merge_sides(r: &Repo) -> IdeResult { } Some("cherry-pick") => sides.commit = read("CHERRY_PICK_HEAD").map(short), Some("revert") => sides.commit = read("REVERT_HEAD").map(short), + // Conflicts outside an operation come from restoring stashed changes (Smart + // Checkout, update with autostash): stage 2 is the new HEAD, stage 3 the user's. + None if !crate::write::unmerged_paths(r)?.is_empty() => { + sides.operation = Some("unstash".into()); + sides.reversed = true; + } _ => {} } Ok(sides) diff --git a/crates/ide-vcs/tests/repo.rs b/crates/ide-vcs/tests/repo.rs index f06f05d8f..40f6a95bc 100644 --- a/crates/ide-vcs/tests/repo.rs +++ b/crates/ide-vcs/tests/repo.rs @@ -868,6 +868,11 @@ fn smart_and_force_checkout_with_local_changes_in_the_way() { assert_eq!(outcome, CheckoutOutcome::Conflicts { files: vec!["f.txt".into()] }); assert_eq!(repo.branch_info().unwrap().head.as_deref(), Some("other")); assert_eq!(repo.stashes().unwrap().len(), 1, "git keeps the stash after a conflicting pop"); + // The stashed edit is the user's side of the merge, the checked-out branch the other. + let v = repo.merge_versions(&fx.path("f.txt")).unwrap(); + assert_eq!(v.sides.operation.as_deref(), Some("unstash")); + assert!(v.sides.reversed); + assert_eq!(v.ours.as_deref(), Some("1\nmine\n3\n4\n5\n")); // Force discards the change in the way. fx.git(&["reset", "-q", "--hard"]); diff --git a/src/features/vcs/__tests__/conflicts.test.ts b/src/features/vcs/__tests__/conflicts.test.ts index fc32ac029..28a1d8c82 100644 --- a/src/features/vcs/__tests__/conflicts.test.ts +++ b/src/features/vcs/__tests__/conflicts.test.ts @@ -71,6 +71,10 @@ describe("mergeTitles", () => { yours: "Local Changes", theirs: "Changes from cherry-pick abc12345", }); + expect(mergeTitles(sides("unstash"))).toEqual({ + yours: "Your uncommitted changes", + theirs: "Changes from feature", + }); expect(mergeTitles(null)).toEqual({ yours: "Your version", theirs: "Theirs (incoming)" }); }); }); diff --git a/src/features/vcs/branchOps.ts b/src/features/vcs/branchOps.ts index 36392ea9f..3f4802ef2 100644 --- a/src/features/vcs/branchOps.ts +++ b/src/features/vcs/branchOps.ts @@ -75,6 +75,8 @@ async function performCheckout(rev: string, success: () => string): Promise call(commands.vcsCheckout(currentRepo(), rev, false, opts))); if (!outcome) return false; + // `success` may name the branch now checked out. + if (outcome.kind === "done" || outcome.kind === "conflicts") await refreshNow(); switch (outcome.kind) { case "done": notifications().notify({ group: "vcs", level: "info", title: success() }); diff --git a/src/features/vcs/conflicts.ts b/src/features/vcs/conflicts.ts index 6591743ad..0ebab5529 100644 --- a/src/features/vcs/conflicts.ts +++ b/src/features/vcs/conflicts.ts @@ -133,6 +133,11 @@ export function mergeTitles(sides: MergeSides | null | undefined): { yours: stri yours: branch ? `Changes from ${branch}` : "Your version", theirs: other ? `Changes from ${other}` : commit ? `Changes from ${commit}` : "Changes from diverging branches", }; + case "unstash": + return { + yours: "Your uncommitted changes", + theirs: branch ? `Changes from ${branch}` : "Changes from remote", + }; case "cherry-pick": return { yours: "Local Changes",