Make FakeGitRepository behave more like a real git repository (#26961)
This PR reworks the `FakeGitRepository` type that we use for testing git interactions, to make it more realistic. In particular, the `status` method now derives the Git status from the differences between HEAD, the index, and the working copy. This way, if you modify a file in the `FakeFs`, the Git repository's `status` method will reflect that modification. Release Notes: - N/A --------- Co-authored-by: Junkui Zhang <364772080@qq.com>
This commit is contained in:
parent
5f398071b2
commit
74a39c7263
15 changed files with 790 additions and 679 deletions
|
@ -103,7 +103,6 @@ async fn test_project_diff(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext)
|
|||
}),
|
||||
)
|
||||
.await;
|
||||
client_a.fs().recalculate_git_status(Path::new("/a/.git"));
|
||||
cx_b.run_until_parked();
|
||||
|
||||
project_b.update(cx_b, |project, cx| {
|
||||
|
|
|
@ -2958,15 +2958,38 @@ async fn test_git_status_sync(
|
|||
.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
".git": {},
|
||||
"a.txt": "a",
|
||||
"b.txt": "b",
|
||||
".git": {},
|
||||
"a.txt": "a",
|
||||
"b.txt": "b",
|
||||
"c.txt": "c",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
const A_TXT: &str = "a.txt";
|
||||
const B_TXT: &str = "b.txt";
|
||||
// Initially, a.txt is uncommitted, but present in the index,
|
||||
// and b.txt is unmerged.
|
||||
client_a.fs().set_head_for_repo(
|
||||
"/dir/.git".as_ref(),
|
||||
&[("b.txt".into(), "B".into()), ("c.txt".into(), "c".into())],
|
||||
);
|
||||
client_a.fs().set_index_for_repo(
|
||||
"/dir/.git".as_ref(),
|
||||
&[
|
||||
("a.txt".into(), "".into()),
|
||||
("b.txt".into(), "B".into()),
|
||||
("c.txt".into(), "c".into()),
|
||||
],
|
||||
);
|
||||
client_a.fs().set_unmerged_paths_for_repo(
|
||||
"/dir/.git".as_ref(),
|
||||
&[(
|
||||
"b.txt".into(),
|
||||
UnmergedStatus {
|
||||
first_head: UnmergedStatusCode::Updated,
|
||||
second_head: UnmergedStatusCode::Deleted,
|
||||
},
|
||||
)],
|
||||
);
|
||||
|
||||
const A_STATUS_START: FileStatus = FileStatus::Tracked(TrackedStatus {
|
||||
index_status: StatusCode::Added,
|
||||
|
@ -2977,14 +3000,6 @@ async fn test_git_status_sync(
|
|||
second_head: UnmergedStatusCode::Deleted,
|
||||
});
|
||||
|
||||
client_a.fs().set_status_for_repo_via_git_operation(
|
||||
Path::new("/dir/.git"),
|
||||
&[
|
||||
(Path::new(A_TXT), A_STATUS_START),
|
||||
(Path::new(B_TXT), B_STATUS_START),
|
||||
],
|
||||
);
|
||||
|
||||
let (project_local, _worktree_id) = client_a.build_local_project("/dir", cx_a).await;
|
||||
let project_id = active_call_a
|
||||
.update(cx_a, |call, cx| {
|
||||
|
@ -3000,7 +3015,7 @@ async fn test_git_status_sync(
|
|||
|
||||
#[track_caller]
|
||||
fn assert_status(
|
||||
file: &impl AsRef<Path>,
|
||||
file: impl AsRef<Path>,
|
||||
status: Option<FileStatus>,
|
||||
project: &Project,
|
||||
cx: &App,
|
||||
|
@ -3014,13 +3029,15 @@ async fn test_git_status_sync(
|
|||
}
|
||||
|
||||
project_local.read_with(cx_a, |project, cx| {
|
||||
assert_status(&Path::new(A_TXT), Some(A_STATUS_START), project, cx);
|
||||
assert_status(&Path::new(B_TXT), Some(B_STATUS_START), project, cx);
|
||||
assert_status("a.txt", Some(A_STATUS_START), project, cx);
|
||||
assert_status("b.txt", Some(B_STATUS_START), project, cx);
|
||||
assert_status("c.txt", None, project, cx);
|
||||
});
|
||||
|
||||
project_remote.read_with(cx_b, |project, cx| {
|
||||
assert_status(&Path::new(A_TXT), Some(A_STATUS_START), project, cx);
|
||||
assert_status(&Path::new(B_TXT), Some(B_STATUS_START), project, cx);
|
||||
assert_status("a.txt", Some(A_STATUS_START), project, cx);
|
||||
assert_status("b.txt", Some(B_STATUS_START), project, cx);
|
||||
assert_status("c.txt", None, project, cx);
|
||||
});
|
||||
|
||||
const A_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
|
||||
|
@ -3029,30 +3046,42 @@ async fn test_git_status_sync(
|
|||
});
|
||||
const B_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
|
||||
index_status: StatusCode::Deleted,
|
||||
worktree_status: StatusCode::Unmodified,
|
||||
worktree_status: StatusCode::Added,
|
||||
});
|
||||
const C_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
|
||||
index_status: StatusCode::Unmodified,
|
||||
worktree_status: StatusCode::Modified,
|
||||
});
|
||||
|
||||
client_a.fs().set_status_for_repo_via_working_copy_change(
|
||||
Path::new("/dir/.git"),
|
||||
&[
|
||||
(Path::new(A_TXT), A_STATUS_END),
|
||||
(Path::new(B_TXT), B_STATUS_END),
|
||||
],
|
||||
// Delete b.txt from the index, mark conflict as resolved,
|
||||
// and modify c.txt in the working copy.
|
||||
client_a.fs().set_index_for_repo(
|
||||
"/dir/.git".as_ref(),
|
||||
&[("a.txt".into(), "a".into()), ("c.txt".into(), "c".into())],
|
||||
);
|
||||
client_a
|
||||
.fs()
|
||||
.set_unmerged_paths_for_repo("/dir/.git".as_ref(), &[]);
|
||||
client_a
|
||||
.fs()
|
||||
.atomic_write("/dir/c.txt".into(), "CC".into())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Wait for buffer_local_a to receive it
|
||||
executor.run_until_parked();
|
||||
|
||||
// Smoke test status reading
|
||||
|
||||
project_local.read_with(cx_a, |project, cx| {
|
||||
assert_status(&Path::new(A_TXT), Some(A_STATUS_END), project, cx);
|
||||
assert_status(&Path::new(B_TXT), Some(B_STATUS_END), project, cx);
|
||||
assert_status("a.txt", Some(A_STATUS_END), project, cx);
|
||||
assert_status("b.txt", Some(B_STATUS_END), project, cx);
|
||||
assert_status("c.txt", Some(C_STATUS_END), project, cx);
|
||||
});
|
||||
|
||||
project_remote.read_with(cx_b, |project, cx| {
|
||||
assert_status(&Path::new(A_TXT), Some(A_STATUS_END), project, cx);
|
||||
assert_status(&Path::new(B_TXT), Some(B_STATUS_END), project, cx);
|
||||
assert_status("a.txt", Some(A_STATUS_END), project, cx);
|
||||
assert_status("b.txt", Some(B_STATUS_END), project, cx);
|
||||
assert_status("c.txt", Some(C_STATUS_END), project, cx);
|
||||
});
|
||||
|
||||
// And synchronization while joining
|
||||
|
@ -3060,8 +3089,9 @@ async fn test_git_status_sync(
|
|||
executor.run_until_parked();
|
||||
|
||||
project_remote_c.read_with(cx_c, |project, cx| {
|
||||
assert_status(&Path::new(A_TXT), Some(A_STATUS_END), project, cx);
|
||||
assert_status(&Path::new(B_TXT), Some(B_STATUS_END), project, cx);
|
||||
assert_status("a.txt", Some(A_STATUS_END), project, cx);
|
||||
assert_status("b.txt", Some(B_STATUS_END), project, cx);
|
||||
assert_status("c.txt", Some(C_STATUS_END), project, cx);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -128,7 +128,6 @@ enum GitOperation {
|
|||
WriteGitStatuses {
|
||||
repo_path: PathBuf,
|
||||
statuses: Vec<(PathBuf, FileStatus)>,
|
||||
git_operation: bool,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -987,7 +986,6 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||
GitOperation::WriteGitStatuses {
|
||||
repo_path,
|
||||
statuses,
|
||||
git_operation,
|
||||
} => {
|
||||
if !client.fs().directories(false).contains(&repo_path) {
|
||||
return Err(TestError::Inapplicable);
|
||||
|
@ -1016,17 +1014,9 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||
client.fs().create_dir(&dot_git_dir).await?;
|
||||
}
|
||||
|
||||
if git_operation {
|
||||
client.fs().set_status_for_repo_via_git_operation(
|
||||
&dot_git_dir,
|
||||
statuses.as_slice(),
|
||||
);
|
||||
} else {
|
||||
client.fs().set_status_for_repo_via_working_copy_change(
|
||||
&dot_git_dir,
|
||||
statuses.as_slice(),
|
||||
);
|
||||
}
|
||||
client
|
||||
.fs()
|
||||
.set_status_for_repo(&dot_git_dir, statuses.as_slice());
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -1455,18 +1445,13 @@ fn generate_git_operation(rng: &mut StdRng, client: &TestClient) -> GitOperation
|
|||
}
|
||||
64..=100 => {
|
||||
let file_paths = generate_file_paths(&repo_path, rng, client);
|
||||
|
||||
let statuses = file_paths
|
||||
.into_iter()
|
||||
.map(|path| (path, gen_status(rng)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let git_operation = rng.gen::<bool>();
|
||||
|
||||
GitOperation::WriteGitStatuses {
|
||||
repo_path,
|
||||
statuses,
|
||||
git_operation,
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
|
@ -1605,15 +1590,24 @@ fn gen_file_name(rng: &mut StdRng) -> String {
|
|||
}
|
||||
|
||||
fn gen_status(rng: &mut StdRng) -> FileStatus {
|
||||
fn gen_status_code(rng: &mut StdRng) -> StatusCode {
|
||||
match rng.gen_range(0..7) {
|
||||
0 => StatusCode::Modified,
|
||||
1 => StatusCode::TypeChanged,
|
||||
2 => StatusCode::Added,
|
||||
3 => StatusCode::Deleted,
|
||||
4 => StatusCode::Renamed,
|
||||
5 => StatusCode::Copied,
|
||||
6 => StatusCode::Unmodified,
|
||||
fn gen_tracked_status(rng: &mut StdRng) -> TrackedStatus {
|
||||
match rng.gen_range(0..3) {
|
||||
0 => TrackedStatus {
|
||||
index_status: StatusCode::Unmodified,
|
||||
worktree_status: StatusCode::Unmodified,
|
||||
},
|
||||
1 => TrackedStatus {
|
||||
index_status: StatusCode::Modified,
|
||||
worktree_status: StatusCode::Modified,
|
||||
},
|
||||
2 => TrackedStatus {
|
||||
index_status: StatusCode::Added,
|
||||
worktree_status: StatusCode::Modified,
|
||||
},
|
||||
3 => TrackedStatus {
|
||||
index_status: StatusCode::Added,
|
||||
worktree_status: StatusCode::Unmodified,
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
@ -1627,17 +1621,12 @@ fn gen_status(rng: &mut StdRng) -> FileStatus {
|
|||
}
|
||||
}
|
||||
|
||||
match rng.gen_range(0..4) {
|
||||
0 => FileStatus::Untracked,
|
||||
1 => FileStatus::Ignored,
|
||||
2 => FileStatus::Unmerged(UnmergedStatus {
|
||||
match rng.gen_range(0..2) {
|
||||
0 => FileStatus::Unmerged(UnmergedStatus {
|
||||
first_head: gen_unmerged_status_code(rng),
|
||||
second_head: gen_unmerged_status_code(rng),
|
||||
}),
|
||||
3 => FileStatus::Tracked(TrackedStatus {
|
||||
index_status: gen_status_code(rng),
|
||||
worktree_status: gen_status_code(rng),
|
||||
}),
|
||||
1 => FileStatus::Tracked(gen_tracked_status(rng)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue