git: Handle git status output for deleted-in-index state (#23483)
When a file exists in HEAD, is deleted in the index, and exists again in the working copy, git produces two lines for it, one reading `D ` (deleted in index, unmodified in working copy), and the other reading `??` (untracked). Merge these two into the equivalent of `DA`. Release Notes: - Improved handling of files that are deleted in the git index but exist in HEAD and the working copy
This commit is contained in:
parent
08b3c03241
commit
55d99e0259
2 changed files with 83 additions and 0 deletions
|
@ -62,6 +62,13 @@ impl FileStatus {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn index(index_status: StatusCode) -> Self {
|
||||||
|
FileStatus::Tracked(TrackedStatus {
|
||||||
|
worktree_status: StatusCode::Unmodified,
|
||||||
|
index_status,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Generate a FileStatus Code from a byte pair, as described in
|
/// Generate a FileStatus Code from a byte pair, as described in
|
||||||
/// https://git-scm.com/docs/git-status#_output
|
/// https://git-scm.com/docs/git-status#_output
|
||||||
///
|
///
|
||||||
|
@ -454,6 +461,26 @@ impl GitStatus {
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
entries.sort_unstable_by(|(a, _), (b, _)| a.cmp(&b));
|
entries.sort_unstable_by(|(a, _), (b, _)| a.cmp(&b));
|
||||||
|
// When a file exists in HEAD, is deleted in the index, and exists again in the working copy,
|
||||||
|
// git produces two lines for it, one reading `D ` (deleted in index, unmodified in working copy)
|
||||||
|
// and the other reading `??` (untracked). Merge these two into the equivalent of `DA`.
|
||||||
|
entries.dedup_by(|(a, a_status), (b, b_status)| {
|
||||||
|
const INDEX_DELETED: FileStatus = FileStatus::index(StatusCode::Deleted);
|
||||||
|
if a.ne(&b) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
match (*a_status, *b_status) {
|
||||||
|
(INDEX_DELETED, FileStatus::Untracked) | (FileStatus::Untracked, INDEX_DELETED) => {
|
||||||
|
*b_status = TrackedStatus {
|
||||||
|
index_status: StatusCode::Deleted,
|
||||||
|
worktree_status: StatusCode::Added,
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
_ => panic!("Unexpected duplicated status entries: {a_status:?} and {b_status:?}"),
|
||||||
|
}
|
||||||
|
true
|
||||||
|
});
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
entries: entries.into(),
|
entries: entries.into(),
|
||||||
})
|
})
|
||||||
|
|
|
@ -2639,6 +2639,62 @@ async fn test_git_repository_status(cx: &mut TestAppContext) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_git_status_postprocessing(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
cx.executor().allow_parking();
|
||||||
|
|
||||||
|
let root = temp_tree(json!({
|
||||||
|
"project": {
|
||||||
|
"sub": {},
|
||||||
|
"a.txt": "",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
let work_dir = root.path().join("project");
|
||||||
|
let repo = git_init(work_dir.as_path());
|
||||||
|
// a.txt exists in HEAD and the working copy but is deleted in the index.
|
||||||
|
git_add("a.txt", &repo);
|
||||||
|
git_commit("Initial commit", &repo);
|
||||||
|
git_remove_index("a.txt".as_ref(), &repo);
|
||||||
|
// `sub` is a nested git repository.
|
||||||
|
let _sub = git_init(&work_dir.join("sub"));
|
||||||
|
|
||||||
|
let tree = Worktree::local(
|
||||||
|
root.path(),
|
||||||
|
true,
|
||||||
|
Arc::new(RealFs::default()),
|
||||||
|
Default::default(),
|
||||||
|
&mut cx.to_async(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
tree.flush_fs_events(cx).await;
|
||||||
|
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
||||||
|
.await;
|
||||||
|
cx.executor().run_until_parked();
|
||||||
|
|
||||||
|
tree.read_with(cx, |tree, _cx| {
|
||||||
|
let snapshot = tree.snapshot();
|
||||||
|
let repo = snapshot.repositories().iter().next().unwrap();
|
||||||
|
let entries = repo.status().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// `sub` doesn't appear in our computed statuses.
|
||||||
|
assert_eq!(entries.len(), 1);
|
||||||
|
assert_eq!(entries[0].repo_path.as_ref(), Path::new("a.txt"));
|
||||||
|
// a.txt appears with a combined `DA` status.
|
||||||
|
assert_eq!(
|
||||||
|
entries[0].status,
|
||||||
|
TrackedStatus {
|
||||||
|
index_status: StatusCode::Deleted,
|
||||||
|
worktree_status: StatusCode::Added
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_repository_subfolder_git_status(cx: &mut TestAppContext) {
|
async fn test_repository_subfolder_git_status(cx: &mut TestAppContext) {
|
||||||
init_test(cx);
|
init_test(cx);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue