@@ -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> {