Migrate most callers of git-related worktree APIs to use the GitStore (#27225)
This is a pure refactoring PR that goes through all the git-related APIs exposed by the worktree crate and minimizes their use outside that crate, migrating callers of those APIs to read from the GitStore instead. This is to prepare for evacuating git repository state from worktrees and making the GitStore the new source of truth. Other drive-by changes: - `project::git` is now `project::git_store`, for consistency with the other project stores - the project panel's test module has been split into its own file Release Notes: - N/A --------- Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
parent
9134630841
commit
cf7d639fbc
26 changed files with 6480 additions and 6429 deletions
|
@ -1,15 +1,12 @@
|
|||
use crate::{
|
||||
worktree_settings::WorktreeSettings, Entry, EntryKind, Event, PathChange, Snapshot,
|
||||
WorkDirectory, Worktree, WorktreeModelHandle,
|
||||
worktree_settings::WorktreeSettings, Entry, EntryKind, Event, PathChange, WorkDirectory,
|
||||
Worktree, WorktreeModelHandle,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use fs::{FakeFs, Fs, RealFs, RemoveOptions};
|
||||
use git::{
|
||||
repository::RepoPath,
|
||||
status::{
|
||||
FileStatus, GitSummary, StatusCode, TrackedStatus, TrackedSummary, UnmergedStatus,
|
||||
UnmergedStatusCode,
|
||||
},
|
||||
status::{FileStatus, StatusCode, TrackedStatus},
|
||||
GITIGNORE,
|
||||
};
|
||||
use git2::RepositoryInitOptions;
|
||||
|
@ -27,7 +24,6 @@ use std::{
|
|||
mem,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use util::{path, test::TempTree, ResultExt};
|
||||
|
||||
|
@ -1472,86 +1468,6 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_bump_mtime_of_git_repo_workdir(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
// Create a worktree with a git directory.
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/root"),
|
||||
json!({
|
||||
".git": {},
|
||||
"a.txt": "",
|
||||
"b": {
|
||||
"c.txt": "",
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
fs.set_head_and_index_for_repo(
|
||||
path!("/root/.git").as_ref(),
|
||||
&[("a.txt".into(), "".into()), ("b/c.txt".into(), "".into())],
|
||||
);
|
||||
cx.run_until_parked();
|
||||
|
||||
let tree = Worktree::local(
|
||||
path!("/root").as_ref(),
|
||||
true,
|
||||
fs.clone(),
|
||||
Default::default(),
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let (old_entry_ids, old_mtimes) = tree.read_with(cx, |tree, _| {
|
||||
(
|
||||
tree.entries(true, 0).map(|e| e.id).collect::<Vec<_>>(),
|
||||
tree.entries(true, 0).map(|e| e.mtime).collect::<Vec<_>>(),
|
||||
)
|
||||
});
|
||||
|
||||
// Regression test: after the directory is scanned, touch the git repo's
|
||||
// working directory, bumping its mtime. That directory keeps its project
|
||||
// entry id after the directories are re-scanned.
|
||||
fs.touch_path(path!("/root")).await;
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let (new_entry_ids, new_mtimes) = tree.read_with(cx, |tree, _| {
|
||||
(
|
||||
tree.entries(true, 0).map(|e| e.id).collect::<Vec<_>>(),
|
||||
tree.entries(true, 0).map(|e| e.mtime).collect::<Vec<_>>(),
|
||||
)
|
||||
});
|
||||
assert_eq!(new_entry_ids, old_entry_ids);
|
||||
assert_ne!(new_mtimes, old_mtimes);
|
||||
|
||||
// Regression test: changes to the git repository should still be
|
||||
// detected.
|
||||
fs.set_head_for_repo(
|
||||
path!("/root/.git").as_ref(),
|
||||
&[
|
||||
("a.txt".into(), "".into()),
|
||||
("b/c.txt".into(), "something-else".into()),
|
||||
],
|
||||
);
|
||||
cx.executor().run_until_parked();
|
||||
cx.executor().advance_clock(Duration::from_secs(1));
|
||||
|
||||
let snapshot = tree.read_with(cx, |tree, _| tree.snapshot());
|
||||
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new(""), MODIFIED),
|
||||
(Path::new("a.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("b/c.txt"), MODIFIED),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
@ -2196,11 +2112,6 @@ fn random_filename(rng: &mut impl Rng) -> String {
|
|||
.collect()
|
||||
}
|
||||
|
||||
const CONFLICT: FileStatus = FileStatus::Unmerged(UnmergedStatus {
|
||||
first_head: UnmergedStatusCode::Updated,
|
||||
second_head: UnmergedStatusCode::Updated,
|
||||
});
|
||||
|
||||
// NOTE:
|
||||
// This test always fails on Windows, because on Windows, unlike on Unix, you can't rename
|
||||
// a directory which some program has already open.
|
||||
|
@ -2244,7 +2155,7 @@ async fn test_rename_work_directory(cx: &mut TestAppContext) {
|
|||
|
||||
cx.read(|cx| {
|
||||
let tree = tree.read(cx);
|
||||
let repo = tree.repositories().iter().next().unwrap();
|
||||
let repo = tree.repositories.iter().next().unwrap();
|
||||
assert_eq!(
|
||||
repo.work_directory,
|
||||
WorkDirectory::in_project("projects/project1")
|
||||
|
@ -2268,7 +2179,7 @@ async fn test_rename_work_directory(cx: &mut TestAppContext) {
|
|||
|
||||
cx.read(|cx| {
|
||||
let tree = tree.read(cx);
|
||||
let repo = tree.repositories().iter().next().unwrap();
|
||||
let repo = tree.repositories.iter().next().unwrap();
|
||||
assert_eq!(
|
||||
repo.work_directory,
|
||||
WorkDirectory::in_project("projects/project2")
|
||||
|
@ -2529,8 +2440,8 @@ async fn test_file_status(cx: &mut TestAppContext) {
|
|||
// Check that the right git state is observed on startup
|
||||
tree.read_with(cx, |tree, _cx| {
|
||||
let snapshot = tree.snapshot();
|
||||
assert_eq!(snapshot.repositories().iter().count(), 1);
|
||||
let repo_entry = snapshot.repositories().iter().next().unwrap();
|
||||
assert_eq!(snapshot.repositories.iter().count(), 1);
|
||||
let repo_entry = snapshot.repositories.iter().next().unwrap();
|
||||
assert_eq!(
|
||||
repo_entry.work_directory,
|
||||
WorkDirectory::in_project("project")
|
||||
|
@ -2705,7 +2616,7 @@ async fn test_git_repository_status(cx: &mut TestAppContext) {
|
|||
// Check that the right git state is observed on startup
|
||||
tree.read_with(cx, |tree, _cx| {
|
||||
let snapshot = tree.snapshot();
|
||||
let repo = snapshot.repositories().iter().next().unwrap();
|
||||
let repo = snapshot.repositories.iter().next().unwrap();
|
||||
let entries = repo.status().collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(entries.len(), 3);
|
||||
|
@ -2727,7 +2638,7 @@ async fn test_git_repository_status(cx: &mut TestAppContext) {
|
|||
|
||||
tree.read_with(cx, |tree, _cx| {
|
||||
let snapshot = tree.snapshot();
|
||||
let repository = snapshot.repositories().iter().next().unwrap();
|
||||
let repository = snapshot.repositories.iter().next().unwrap();
|
||||
let entries = repository.status().collect::<Vec<_>>();
|
||||
|
||||
std::assert_eq!(entries.len(), 4, "entries: {entries:?}");
|
||||
|
@ -2760,7 +2671,7 @@ async fn test_git_repository_status(cx: &mut TestAppContext) {
|
|||
|
||||
tree.read_with(cx, |tree, _cx| {
|
||||
let snapshot = tree.snapshot();
|
||||
let repo = snapshot.repositories().iter().next().unwrap();
|
||||
let repo = snapshot.repositories.iter().next().unwrap();
|
||||
let entries = repo.status().collect::<Vec<_>>();
|
||||
|
||||
// Deleting an untracked entry, b.txt, should leave no status
|
||||
|
@ -2814,7 +2725,7 @@ async fn test_git_status_postprocessing(cx: &mut TestAppContext) {
|
|||
|
||||
tree.read_with(cx, |tree, _cx| {
|
||||
let snapshot = tree.snapshot();
|
||||
let repo = snapshot.repositories().iter().next().unwrap();
|
||||
let repo = snapshot.repositories.iter().next().unwrap();
|
||||
let entries = repo.status().collect::<Vec<_>>();
|
||||
|
||||
// `sub` doesn't appear in our computed statuses.
|
||||
|
@ -2883,8 +2794,8 @@ async fn test_repository_subfolder_git_status(cx: &mut TestAppContext) {
|
|||
// Ensure that the git status is loaded correctly
|
||||
tree.read_with(cx, |tree, _cx| {
|
||||
let snapshot = tree.snapshot();
|
||||
assert_eq!(snapshot.repositories().iter().count(), 1);
|
||||
let repo = snapshot.repositories().iter().next().unwrap();
|
||||
assert_eq!(snapshot.repositories.iter().count(), 1);
|
||||
let repo = snapshot.repositories.iter().next().unwrap();
|
||||
assert_eq!(
|
||||
repo.work_directory.canonicalize(),
|
||||
WorkDirectory::AboveProject {
|
||||
|
@ -2913,442 +2824,13 @@ async fn test_repository_subfolder_git_status(cx: &mut TestAppContext) {
|
|||
tree.read_with(cx, |tree, _cx| {
|
||||
let snapshot = tree.snapshot();
|
||||
|
||||
assert!(snapshot.repositories().iter().next().is_some());
|
||||
assert!(snapshot.repositories.iter().next().is_some());
|
||||
|
||||
assert_eq!(snapshot.status_for_file("c.txt"), None);
|
||||
assert_eq!(snapshot.status_for_file("d/e.txt"), None);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_traverse_with_git_status(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/root"),
|
||||
json!({
|
||||
"x": {
|
||||
".git": {},
|
||||
"x1.txt": "foo",
|
||||
"x2.txt": "bar",
|
||||
"y": {
|
||||
".git": {},
|
||||
"y1.txt": "baz",
|
||||
"y2.txt": "qux"
|
||||
},
|
||||
"z.txt": "sneaky..."
|
||||
},
|
||||
"z": {
|
||||
".git": {},
|
||||
"z1.txt": "quux",
|
||||
"z2.txt": "quuux"
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
fs.set_status_for_repo(
|
||||
Path::new(path!("/root/x/.git")),
|
||||
&[
|
||||
(Path::new("x2.txt"), StatusCode::Modified.index()),
|
||||
(Path::new("z.txt"), StatusCode::Added.index()),
|
||||
],
|
||||
);
|
||||
fs.set_status_for_repo(
|
||||
Path::new(path!("/root/x/y/.git")),
|
||||
&[(Path::new("y1.txt"), CONFLICT)],
|
||||
);
|
||||
fs.set_status_for_repo(
|
||||
Path::new(path!("/root/z/.git")),
|
||||
&[(Path::new("z2.txt"), StatusCode::Added.index())],
|
||||
);
|
||||
|
||||
let tree = Worktree::local(
|
||||
Path::new(path!("/root")),
|
||||
true,
|
||||
fs.clone(),
|
||||
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();
|
||||
|
||||
let snapshot = tree.read_with(cx, |tree, _| tree.snapshot());
|
||||
|
||||
let mut traversal = snapshot
|
||||
.traverse_from_path(true, false, true, Path::new("x"))
|
||||
.with_git_statuses();
|
||||
|
||||
let entry = traversal.next().unwrap();
|
||||
assert_eq!(entry.path.as_ref(), Path::new("x/x1.txt"));
|
||||
assert_eq!(entry.git_summary, GitSummary::UNCHANGED);
|
||||
let entry = traversal.next().unwrap();
|
||||
assert_eq!(entry.path.as_ref(), Path::new("x/x2.txt"));
|
||||
assert_eq!(entry.git_summary, MODIFIED);
|
||||
let entry = traversal.next().unwrap();
|
||||
assert_eq!(entry.path.as_ref(), Path::new("x/y/y1.txt"));
|
||||
assert_eq!(entry.git_summary, GitSummary::CONFLICT);
|
||||
let entry = traversal.next().unwrap();
|
||||
assert_eq!(entry.path.as_ref(), Path::new("x/y/y2.txt"));
|
||||
assert_eq!(entry.git_summary, GitSummary::UNCHANGED);
|
||||
let entry = traversal.next().unwrap();
|
||||
assert_eq!(entry.path.as_ref(), Path::new("x/z.txt"));
|
||||
assert_eq!(entry.git_summary, ADDED);
|
||||
let entry = traversal.next().unwrap();
|
||||
assert_eq!(entry.path.as_ref(), Path::new("z/z1.txt"));
|
||||
assert_eq!(entry.git_summary, GitSummary::UNCHANGED);
|
||||
let entry = traversal.next().unwrap();
|
||||
assert_eq!(entry.path.as_ref(), Path::new("z/z2.txt"));
|
||||
assert_eq!(entry.git_summary, ADDED);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/root"),
|
||||
json!({
|
||||
".git": {},
|
||||
"a": {
|
||||
"b": {
|
||||
"c1.txt": "",
|
||||
"c2.txt": "",
|
||||
},
|
||||
"d": {
|
||||
"e1.txt": "",
|
||||
"e2.txt": "",
|
||||
"e3.txt": "",
|
||||
}
|
||||
},
|
||||
"f": {
|
||||
"no-status.txt": ""
|
||||
},
|
||||
"g": {
|
||||
"h1.txt": "",
|
||||
"h2.txt": ""
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
fs.set_status_for_repo(
|
||||
Path::new(path!("/root/.git")),
|
||||
&[
|
||||
(Path::new("a/b/c1.txt"), StatusCode::Added.index()),
|
||||
(Path::new("a/d/e2.txt"), StatusCode::Modified.index()),
|
||||
(Path::new("g/h2.txt"), CONFLICT),
|
||||
],
|
||||
);
|
||||
|
||||
let tree = Worktree::local(
|
||||
Path::new(path!("/root")),
|
||||
true,
|
||||
fs.clone(),
|
||||
Default::default(),
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
let snapshot = tree.read_with(cx, |tree, _| tree.snapshot());
|
||||
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new(""), GitSummary::CONFLICT + MODIFIED + ADDED),
|
||||
(Path::new("g"), GitSummary::CONFLICT),
|
||||
(Path::new("g/h2.txt"), GitSummary::CONFLICT),
|
||||
],
|
||||
);
|
||||
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new(""), GitSummary::CONFLICT + ADDED + MODIFIED),
|
||||
(Path::new("a"), ADDED + MODIFIED),
|
||||
(Path::new("a/b"), ADDED),
|
||||
(Path::new("a/b/c1.txt"), ADDED),
|
||||
(Path::new("a/b/c2.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("a/d"), MODIFIED),
|
||||
(Path::new("a/d/e2.txt"), MODIFIED),
|
||||
(Path::new("f"), GitSummary::UNCHANGED),
|
||||
(Path::new("f/no-status.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("g"), GitSummary::CONFLICT),
|
||||
(Path::new("g/h2.txt"), GitSummary::CONFLICT),
|
||||
],
|
||||
);
|
||||
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new("a/b"), ADDED),
|
||||
(Path::new("a/b/c1.txt"), ADDED),
|
||||
(Path::new("a/b/c2.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("a/d"), MODIFIED),
|
||||
(Path::new("a/d/e1.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("a/d/e2.txt"), MODIFIED),
|
||||
(Path::new("f"), GitSummary::UNCHANGED),
|
||||
(Path::new("f/no-status.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("g"), GitSummary::CONFLICT),
|
||||
],
|
||||
);
|
||||
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new("a/b/c1.txt"), ADDED),
|
||||
(Path::new("a/b/c2.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("a/d/e1.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("a/d/e2.txt"), MODIFIED),
|
||||
(Path::new("f/no-status.txt"), GitSummary::UNCHANGED),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_propagate_statuses_for_repos_under_project(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/root"),
|
||||
json!({
|
||||
"x": {
|
||||
".git": {},
|
||||
"x1.txt": "foo",
|
||||
"x2.txt": "bar"
|
||||
},
|
||||
"y": {
|
||||
".git": {},
|
||||
"y1.txt": "baz",
|
||||
"y2.txt": "qux"
|
||||
},
|
||||
"z": {
|
||||
".git": {},
|
||||
"z1.txt": "quux",
|
||||
"z2.txt": "quuux"
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
fs.set_status_for_repo(
|
||||
Path::new(path!("/root/x/.git")),
|
||||
&[(Path::new("x1.txt"), StatusCode::Added.index())],
|
||||
);
|
||||
fs.set_status_for_repo(
|
||||
Path::new(path!("/root/y/.git")),
|
||||
&[
|
||||
(Path::new("y1.txt"), CONFLICT),
|
||||
(Path::new("y2.txt"), StatusCode::Modified.index()),
|
||||
],
|
||||
);
|
||||
fs.set_status_for_repo(
|
||||
Path::new(path!("/root/z/.git")),
|
||||
&[(Path::new("z2.txt"), StatusCode::Modified.index())],
|
||||
);
|
||||
|
||||
let tree = Worktree::local(
|
||||
Path::new(path!("/root")),
|
||||
true,
|
||||
fs.clone(),
|
||||
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();
|
||||
|
||||
let snapshot = tree.read_with(cx, |tree, _| tree.snapshot());
|
||||
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[(Path::new("x"), ADDED), (Path::new("x/x1.txt"), ADDED)],
|
||||
);
|
||||
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new("y"), GitSummary::CONFLICT + MODIFIED),
|
||||
(Path::new("y/y1.txt"), GitSummary::CONFLICT),
|
||||
(Path::new("y/y2.txt"), MODIFIED),
|
||||
],
|
||||
);
|
||||
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new("z"), MODIFIED),
|
||||
(Path::new("z/z2.txt"), MODIFIED),
|
||||
],
|
||||
);
|
||||
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[(Path::new("x"), ADDED), (Path::new("x/x1.txt"), ADDED)],
|
||||
);
|
||||
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new("x"), ADDED),
|
||||
(Path::new("x/x1.txt"), ADDED),
|
||||
(Path::new("x/x2.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("y"), GitSummary::CONFLICT + MODIFIED),
|
||||
(Path::new("y/y1.txt"), GitSummary::CONFLICT),
|
||||
(Path::new("y/y2.txt"), MODIFIED),
|
||||
(Path::new("z"), MODIFIED),
|
||||
(Path::new("z/z1.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("z/z2.txt"), MODIFIED),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_propagate_statuses_for_nested_repos(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/root"),
|
||||
json!({
|
||||
"x": {
|
||||
".git": {},
|
||||
"x1.txt": "foo",
|
||||
"x2.txt": "bar",
|
||||
"y": {
|
||||
".git": {},
|
||||
"y1.txt": "baz",
|
||||
"y2.txt": "qux"
|
||||
},
|
||||
"z.txt": "sneaky..."
|
||||
},
|
||||
"z": {
|
||||
".git": {},
|
||||
"z1.txt": "quux",
|
||||
"z2.txt": "quuux"
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
fs.set_status_for_repo(
|
||||
Path::new(path!("/root/x/.git")),
|
||||
&[
|
||||
(Path::new("x2.txt"), StatusCode::Modified.index()),
|
||||
(Path::new("z.txt"), StatusCode::Added.index()),
|
||||
],
|
||||
);
|
||||
fs.set_status_for_repo(
|
||||
Path::new(path!("/root/x/y/.git")),
|
||||
&[(Path::new("y1.txt"), CONFLICT)],
|
||||
);
|
||||
|
||||
fs.set_status_for_repo(
|
||||
Path::new(path!("/root/z/.git")),
|
||||
&[(Path::new("z2.txt"), StatusCode::Added.index())],
|
||||
);
|
||||
|
||||
let tree = Worktree::local(
|
||||
Path::new(path!("/root")),
|
||||
true,
|
||||
fs.clone(),
|
||||
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();
|
||||
|
||||
let snapshot = tree.read_with(cx, |tree, _| tree.snapshot());
|
||||
|
||||
// Sanity check the propagation for x/y and z
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new("x/y"), GitSummary::CONFLICT),
|
||||
(Path::new("x/y/y1.txt"), GitSummary::CONFLICT),
|
||||
(Path::new("x/y/y2.txt"), GitSummary::UNCHANGED),
|
||||
],
|
||||
);
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new("z"), ADDED),
|
||||
(Path::new("z/z1.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("z/z2.txt"), ADDED),
|
||||
],
|
||||
);
|
||||
|
||||
// Test one of the fundamental cases of propagation blocking, the transition from one git repository to another
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new("x"), MODIFIED + ADDED),
|
||||
(Path::new("x/y"), GitSummary::CONFLICT),
|
||||
(Path::new("x/y/y1.txt"), GitSummary::CONFLICT),
|
||||
],
|
||||
);
|
||||
|
||||
// Sanity check everything around it
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new("x"), MODIFIED + ADDED),
|
||||
(Path::new("x/x1.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("x/x2.txt"), MODIFIED),
|
||||
(Path::new("x/y"), GitSummary::CONFLICT),
|
||||
(Path::new("x/y/y1.txt"), GitSummary::CONFLICT),
|
||||
(Path::new("x/y/y2.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("x/z.txt"), ADDED),
|
||||
],
|
||||
);
|
||||
|
||||
// Test the other fundamental case, transitioning from git repository to non-git repository
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new(""), GitSummary::UNCHANGED),
|
||||
(Path::new("x"), MODIFIED + ADDED),
|
||||
(Path::new("x/x1.txt"), GitSummary::UNCHANGED),
|
||||
],
|
||||
);
|
||||
|
||||
// And all together now
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new(""), GitSummary::UNCHANGED),
|
||||
(Path::new("x"), MODIFIED + ADDED),
|
||||
(Path::new("x/x1.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("x/x2.txt"), MODIFIED),
|
||||
(Path::new("x/y"), GitSummary::CONFLICT),
|
||||
(Path::new("x/y/y1.txt"), GitSummary::CONFLICT),
|
||||
(Path::new("x/y/y2.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("x/z.txt"), ADDED),
|
||||
(Path::new("z"), ADDED),
|
||||
(Path::new("z/z1.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("z/z2.txt"), ADDED),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_conflicted_cherry_pick(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
@ -3403,7 +2885,7 @@ async fn test_conflicted_cherry_pick(cx: &mut TestAppContext) {
|
|||
);
|
||||
tree.flush_fs_events(cx).await;
|
||||
let conflicts = tree.update(cx, |tree, _| {
|
||||
let entry = tree.git_entries().nth(0).expect("No git entry").clone();
|
||||
let entry = tree.repositories.first().expect("No git entry").clone();
|
||||
entry
|
||||
.current_merge_conflicts
|
||||
.iter()
|
||||
|
@ -3420,7 +2902,7 @@ async fn test_conflicted_cherry_pick(cx: &mut TestAppContext) {
|
|||
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();
|
||||
let entry = tree.repositories.first().expect("No git entry").clone();
|
||||
entry
|
||||
.current_merge_conflicts
|
||||
.iter()
|
||||
|
@ -3490,34 +2972,6 @@ fn test_unrelativize() {
|
|||
);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check_git_statuses(snapshot: &Snapshot, expected_statuses: &[(&Path, GitSummary)]) {
|
||||
let mut traversal = snapshot
|
||||
.traverse_from_path(true, true, false, "".as_ref())
|
||||
.with_git_statuses();
|
||||
let found_statuses = expected_statuses
|
||||
.iter()
|
||||
.map(|&(path, _)| {
|
||||
let git_entry = traversal
|
||||
.find(|git_entry| &*git_entry.path == path)
|
||||
.unwrap_or_else(|| panic!("Traversal has no entry for {path:?}"));
|
||||
(path, git_entry.git_summary)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(found_statuses, expected_statuses);
|
||||
}
|
||||
|
||||
const ADDED: GitSummary = GitSummary {
|
||||
index: TrackedSummary::ADDED,
|
||||
count: 1,
|
||||
..GitSummary::UNCHANGED
|
||||
};
|
||||
const MODIFIED: GitSummary = GitSummary {
|
||||
index: TrackedSummary::MODIFIED,
|
||||
count: 1,
|
||||
..GitSummary::UNCHANGED
|
||||
};
|
||||
|
||||
#[track_caller]
|
||||
fn git_init(path: &Path) -> git2::Repository {
|
||||
let mut init_opts = RepositoryInitOptions::new();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue