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",