use futures::StreamExt as _;
use itertools::Itertools as _;
use jj_lib::local_working_copy::LocalWorkingCopy;
use jj_lib::matchers::EverythingMatcher;
use jj_lib::repo::Repo as _;
use jj_lib::repo_path::RepoPath;
use jj_lib::repo_path::RepoPathBuf;
use jj_lib::working_copy::CheckoutStats;
use jj_lib::working_copy::WorkingCopy as _;
use pollster::FutureExt as _;
use testutils::TestResult;
use testutils::TestWorkspace;
use testutils::commit_with_tree;
use testutils::create_tree;
use testutils::repo_path;
fn to_owned_path_vec(paths: &[&RepoPath]) -> Vec<RepoPathBuf> {
paths.iter().map(|&path| path.to_owned()).collect()
}
#[test]
fn test_sparse_checkout() -> TestResult {
let mut test_workspace = TestWorkspace::init();
let repo = &test_workspace.repo;
let working_copy_path = test_workspace.workspace.workspace_root().to_owned();
let root_file1_path = repo_path("file1");
let root_file2_path = repo_path("file2");
let dir1_path = repo_path("dir1");
let dir1_file1_path = repo_path("dir1/file1");
let dir1_file2_path = repo_path("dir1/file2");
let dir1_subdir1_path = repo_path("dir1/subdir1");
let dir1_subdir1_file1_path = repo_path("dir1/subdir1/file1");
let dir2_path = repo_path("dir2");
let dir2_file1_path = repo_path("dir2/file1");
let tree = create_tree(
repo,
&[
(root_file1_path, "contents"),
(root_file2_path, "contents"),
(dir1_file1_path, "contents"),
(dir1_file2_path, "contents"),
(dir1_subdir1_file1_path, "contents"),
(dir2_file1_path, "contents"),
],
);
let commit = commit_with_tree(repo.store(), tree);
test_workspace
.workspace
.check_out(repo.op_id().clone(), None, &commit)
.block_on()?;
let ws = &mut test_workspace.workspace;
let mut locked_ws = ws.start_working_copy_mutation()?;
let sparse_patterns = to_owned_path_vec(&[dir1_path]);
let stats = locked_ws
.locked_wc()
.set_sparse_patterns(sparse_patterns.clone())
.block_on()?;
assert_eq!(
stats,
CheckoutStats {
updated_files: 0,
added_files: 0,
removed_files: 3,
skipped_files: 0,
}
);
assert_eq!(locked_ws.locked_wc().sparse_patterns()?, sparse_patterns);
assert!(
!root_file1_path
.to_fs_path_unchecked(&working_copy_path)
.exists()
);
assert!(
!root_file2_path
.to_fs_path_unchecked(&working_copy_path)
.exists()
);
assert!(
dir1_file1_path
.to_fs_path_unchecked(&working_copy_path)
.exists()
);
assert!(
dir1_file2_path
.to_fs_path_unchecked(&working_copy_path)
.exists()
);
assert!(
dir1_subdir1_file1_path
.to_fs_path_unchecked(&working_copy_path)
.exists()
);
assert!(
!dir2_file1_path
.to_fs_path_unchecked(&working_copy_path)
.exists()
);
locked_ws.finish(repo.op_id().clone()).block_on()?;
let wc: &LocalWorkingCopy = ws.working_copy().downcast_ref().unwrap();
assert_eq!(
wc.file_states()?.paths().collect_vec(),
vec![dir1_file1_path, dir1_file2_path, dir1_subdir1_file1_path]
);
assert_eq!(wc.sparse_patterns()?, sparse_patterns);
let wc = LocalWorkingCopy::load(
repo.store().clone(),
ws.workspace_root().to_path_buf(),
wc.state_path().to_path_buf(),
repo.settings(),
)?;
assert_eq!(
wc.file_states()?.paths().collect_vec(),
vec![dir1_file1_path, dir1_file2_path, dir1_subdir1_file1_path]
);
assert_eq!(wc.sparse_patterns()?, sparse_patterns);
let mut locked_wc = wc.start_mutation()?;
let sparse_patterns = to_owned_path_vec(&[root_file1_path, dir1_subdir1_path, dir2_path]);
let stats = locked_wc
.set_sparse_patterns(sparse_patterns.clone())
.block_on()?;
assert_eq!(
stats,
CheckoutStats {
updated_files: 0,
added_files: 2,
removed_files: 2,
skipped_files: 0,
}
);
assert_eq!(locked_wc.sparse_patterns()?, sparse_patterns);
assert!(
root_file1_path
.to_fs_path_unchecked(&working_copy_path)
.exists()
);
assert!(
!root_file2_path
.to_fs_path_unchecked(&working_copy_path)
.exists()
);
assert!(
!dir1_file1_path
.to_fs_path_unchecked(&working_copy_path)
.exists()
);
assert!(
!dir1_file2_path
.to_fs_path_unchecked(&working_copy_path)
.exists()
);
assert!(
dir1_subdir1_file1_path
.to_fs_path_unchecked(&working_copy_path)
.exists()
);
assert!(
dir2_file1_path
.to_fs_path_unchecked(&working_copy_path)
.exists()
);
let wc = locked_wc.finish(repo.op_id().clone()).block_on()?;
let wc: &LocalWorkingCopy = wc.downcast_ref().unwrap();
assert_eq!(
wc.file_states()?.paths().collect_vec(),
vec![dir1_subdir1_file1_path, dir2_file1_path, root_file1_path]
);
Ok(())
}
#[test]
fn test_sparse_commit() -> TestResult {
let mut test_workspace = TestWorkspace::init();
let repo = &test_workspace.repo;
let op_id = repo.op_id().clone();
let working_copy_path = test_workspace.workspace.workspace_root().to_owned();
let root_file1_path = repo_path("file1");
let dir1_path = repo_path("dir1");
let dir1_file1_path = repo_path("dir1/file1");
let dir2_path = repo_path("dir2");
let dir2_file1_path = repo_path("dir2/file1");
let tree = create_tree(
repo,
&[
(root_file1_path, "contents"),
(dir1_file1_path, "contents"),
(dir2_file1_path, "contents"),
],
);
let commit = commit_with_tree(repo.store(), tree.clone());
test_workspace
.workspace
.check_out(repo.op_id().clone(), None, &commit)
.block_on()?;
let mut locked_ws = test_workspace.workspace.start_working_copy_mutation()?;
let sparse_patterns = to_owned_path_vec(&[dir1_path]);
locked_ws
.locked_wc()
.set_sparse_patterns(sparse_patterns)
.block_on()?;
locked_ws.finish(repo.op_id().clone()).block_on()?;
std::fs::write(
root_file1_path.to_fs_path_unchecked(&working_copy_path),
"modified",
)?;
std::fs::write(
dir1_file1_path.to_fs_path_unchecked(&working_copy_path),
"modified",
)?;
std::fs::create_dir(dir2_path.to_fs_path_unchecked(&working_copy_path))?;
std::fs::write(
dir2_file1_path.to_fs_path_unchecked(&working_copy_path),
"modified",
)?;
let modified_tree = test_workspace.snapshot()?;
let diff: Vec<_> = tree
.diff_stream(&modified_tree, &EverythingMatcher)
.collect()
.block_on();
assert_eq!(diff.len(), 1);
assert_eq!(diff[0].path.as_ref(), dir1_file1_path);
let mut locked_ws = test_workspace.workspace.start_working_copy_mutation()?;
let sparse_patterns = to_owned_path_vec(&[dir1_path, dir2_path]);
locked_ws
.locked_wc()
.set_sparse_patterns(sparse_patterns)
.block_on()?;
locked_ws.finish(op_id).block_on()?;
let modified_tree = test_workspace.snapshot()?;
let diff: Vec<_> = tree
.diff_stream(&modified_tree, &EverythingMatcher)
.collect()
.block_on();
assert_eq!(diff.len(), 2);
assert_eq!(diff[0].path.as_ref(), dir1_file1_path);
assert_eq!(diff[1].path.as_ref(), dir2_file1_path);
Ok(())
}
#[test]
fn test_sparse_commit_gitignore() -> TestResult {
let mut test_workspace = TestWorkspace::init();
let repo = &test_workspace.repo;
let working_copy_path = test_workspace.workspace.workspace_root().to_owned();
let dir1_path = repo_path("dir1");
let dir1_file1_path = repo_path("dir1/file1");
let dir1_file2_path = repo_path("dir1/file2");
let mut locked_ws = test_workspace.workspace.start_working_copy_mutation()?;
let sparse_patterns = to_owned_path_vec(&[dir1_path]);
locked_ws
.locked_wc()
.set_sparse_patterns(sparse_patterns)
.block_on()?;
locked_ws.finish(repo.op_id().clone()).block_on()?;
std::fs::write(working_copy_path.join(".gitignore"), "dir1/file1")?;
std::fs::create_dir(dir1_path.to_fs_path_unchecked(&working_copy_path))?;
std::fs::write(
dir1_file1_path.to_fs_path_unchecked(&working_copy_path),
"contents",
)?;
std::fs::write(
dir1_file2_path.to_fs_path_unchecked(&working_copy_path),
"contents",
)?;
let modified_tree = test_workspace.snapshot()?;
let entries = modified_tree.entries().collect_vec();
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].0.as_ref(), dir1_file2_path);
Ok(())
}