git: Add CHERRY_PICK_HEAD to the list of merge heads (#26145)

Attempt to fix an issue where conflicts from a cherry-pick don't get
cleared out of the git panel after being resolved.

Release Notes:

- Git Beta: Fixed resolution of conflicts from cherry-picks not being
reflected in the git panel
This commit is contained in:
Cole Miller 2025-03-05 17:31:45 -05:00 committed by GitHub
parent 431727fdd7
commit 5daadc0d30
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 112 additions and 0 deletions

View file

@ -427,6 +427,15 @@ impl GitRepository for RealGitRepository {
true
})
.ok();
if let Some(oid) = self
.repository
.lock()
.find_reference("CHERRY_PICK_HEAD")
.ok()
.and_then(|reference| reference.target())
{
shas.push(oid.to_string())
}
shas
}

View file

@ -5,6 +5,7 @@ use crate::{
use anyhow::Result;
use fs::{FakeFs, Fs, RealFs, RemoveOptions};
use git::{
repository::RepoPath,
status::{
FileStatus, GitSummary, StatusCode, TrackedStatus, TrackedSummary, UnmergedStatus,
UnmergedStatusCode,
@ -3307,6 +3308,87 @@ async fn test_propagate_statuses_for_nested_repos(cx: &mut TestAppContext) {
);
}
#[gpui::test]
async fn test_conflicted_cherry_pick(cx: &mut TestAppContext) {
init_test(cx);
cx.executor().allow_parking();
let root = TempTree::new(json!({
"project": {
"a.txt": "a",
},
}));
let root_path = root.path();
let tree = Worktree::local(
root_path,
true,
Arc::new(RealFs::default()),
Default::default(),
&mut cx.to_async(),
)
.await
.unwrap();
let repo = git_init(&root_path.join("project"));
git_add("a.txt", &repo);
git_commit("init", &repo);
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
.await;
tree.flush_fs_events(cx).await;
git_branch("other-branch", &repo);
git_checkout("refs/heads/other-branch", &repo);
std::fs::write(root_path.join("project/a.txt"), "A").unwrap();
git_add("a.txt", &repo);
git_commit("capitalize", &repo);
let commit = repo
.head()
.expect("Failed to get HEAD")
.peel_to_commit()
.expect("HEAD is not a commit");
git_checkout("refs/heads/master", &repo);
std::fs::write(root_path.join("project/a.txt"), "b").unwrap();
git_add("a.txt", &repo);
git_commit("improve letter", &repo);
git_cherry_pick(&commit, &repo);
std::fs::read_to_string(root_path.join("project/.git/CHERRY_PICK_HEAD"))
.expect("No CHERRY_PICK_HEAD");
pretty_assertions::assert_eq!(
git_status(&repo),
collections::HashMap::from_iter([("a.txt".to_owned(), git2::Status::CONFLICTED)])
);
tree.flush_fs_events(cx).await;
let conflicts = tree.update(cx, |tree, _| {
let entry = tree.git_entries().nth(0).expect("No git entry").clone();
entry
.current_merge_conflicts
.iter()
.cloned()
.collect::<Vec<_>>()
});
pretty_assertions::assert_eq!(conflicts, [RepoPath::from("a.txt")]);
git_add("a.txt", &repo);
// Attempt to manually simulate what `git cherry-pick --continue` would do.
git_commit("whatevs", &repo);
std::fs::remove_file(root.path().join("project/.git/CHERRY_PICK_HEAD"))
.expect("Failed to remove CHERRY_PICK_HEAD");
pretty_assertions::assert_eq!(git_status(&repo), collections::HashMap::default());
tree.flush_fs_events(cx).await;
let conflicts = tree.update(cx, |tree, _| {
let entry = tree.git_entries().nth(0).expect("No git entry").clone();
entry
.current_merge_conflicts
.iter()
.cloned()
.collect::<Vec<_>>()
});
pretty_assertions::assert_eq!(conflicts, []);
}
#[gpui::test]
async fn test_private_single_file_worktree(cx: &mut TestAppContext) {
init_test(cx);
@ -3405,6 +3487,11 @@ fn git_commit(msg: &'static str, repo: &git2::Repository) {
}
}
#[track_caller]
fn git_cherry_pick(commit: &git2::Commit<'_>, repo: &git2::Repository) {
repo.cherrypick(commit, None).expect("Failed to cherrypick");
}
#[track_caller]
fn git_stash(repo: &mut git2::Repository) {
use git2::Signature;
@ -3430,6 +3517,22 @@ fn git_reset(offset: usize, repo: &git2::Repository) {
.expect("Could not reset");
}
#[track_caller]
fn git_branch(name: &str, repo: &git2::Repository) {
let head = repo
.head()
.expect("Couldn't get repo head")
.peel_to_commit()
.expect("HEAD is not a commit");
repo.branch(name, &head, false).expect("Failed to commit");
}
#[track_caller]
fn git_checkout(name: &str, repo: &git2::Repository) {
repo.set_head(name).expect("Failed to set head");
repo.checkout_head(None).expect("Failed to check out head");
}
#[allow(dead_code)]
#[track_caller]
fn git_status(repo: &git2::Repository) -> collections::HashMap<String, git2::Status> {