working_copy: respect sparse patterns when writing tree (#52)

This commit is contained in:
Martin von Zweigbergk 2022-04-17 00:02:09 -07:00 committed by Martin von Zweigbergk
parent 0d881de56c
commit 18a64c365a
2 changed files with 132 additions and 6 deletions

View file

@ -327,6 +327,7 @@ impl TreeState {
// Look for changes to the working copy. If there are any changes, create
// a new tree from it and return it, and also update the dirstate on disk.
pub fn write_tree(&mut self, base_ignores: Arc<GitIgnoreFile>) -> TreeId {
let sparse_matcher = self.sparse_matcher();
let mut work = vec![(
RepoPath::root(),
self.working_copy_path.clone(),
@ -336,6 +337,9 @@ impl TreeState {
let mut deleted_files: HashSet<_> = self.file_states.keys().cloned().collect();
while !work.is_empty() {
let (dir, disk_dir, git_ignore) = work.pop().unwrap();
if sparse_matcher.visit(&dir).is_nothing() {
continue;
}
let git_ignore = git_ignore
.chain_with_file(&dir.to_internal_dir_string(), disk_dir.join(".gitignore"));
for maybe_entry in disk_dir.read_dir().unwrap() {
@ -367,12 +371,14 @@ impl TreeState {
work.push((sub_path, entry.path(), git_ignore.clone()));
} else {
deleted_files.remove(&sub_path);
self.update_file_state(
sub_path,
entry.path(),
git_ignore.as_ref(),
&mut tree_builder,
);
if sparse_matcher.matches(&sub_path) {
self.update_file_state(
sub_path,
entry.path(),
git_ignore.as_ref(),
&mut tree_builder,
);
}
}
}
}

View file

@ -13,6 +13,8 @@
// limitations under the License.
use itertools::Itertools;
use jujutsu_lib::gitignore::GitIgnoreFile;
use jujutsu_lib::matchers::EverythingMatcher;
use jujutsu_lib::repo_path::RepoPath;
use jujutsu_lib::testutils;
use jujutsu_lib::working_copy::{CheckoutStats, WorkingCopy};
@ -122,3 +124,121 @@ fn test_sparse_checkout() {
vec![&dir1_subdir1_file1_path, &dir2_file1_path, &root_file1_path]
);
}
/// Test that sparse patterns are respected on commit
#[test]
fn test_sparse_commit() {
let settings = testutils::user_settings();
let mut test_workspace = testutils::init_workspace(&settings, false);
let repo = &test_workspace.repo;
let working_copy_path = test_workspace.workspace.workspace_root().clone();
let root_file1_path = RepoPath::from_internal_string("file1");
let dir1_path = RepoPath::from_internal_string("dir1");
let dir1_file1_path = RepoPath::from_internal_string("dir1/file1");
let dir2_path = RepoPath::from_internal_string("dir2");
let dir2_file1_path = RepoPath::from_internal_string("dir2/file1");
let tree = testutils::create_tree(
repo,
&[
(&root_file1_path, "contents"),
(&dir1_file1_path, "contents"),
(&dir2_file1_path, "contents"),
],
);
let wc = test_workspace.workspace.working_copy_mut();
wc.check_out(repo.op_id().clone(), None, &tree).unwrap();
// Set sparse patterns to only dir1/
let mut locked_wc = wc.start_mutation();
let sparse_patterns = vec![dir1_path.clone()];
locked_wc.set_sparse_patterns(sparse_patterns).unwrap();
locked_wc.finish(repo.op_id().clone());
// Write modified version of all files, including files that are not in the
// sparse patterns.
std::fs::write(root_file1_path.to_fs_path(&working_copy_path), "modified").unwrap();
std::fs::write(dir1_file1_path.to_fs_path(&working_copy_path), "modified").unwrap();
std::fs::create_dir(dir2_path.to_fs_path(&working_copy_path)).unwrap();
std::fs::write(dir2_file1_path.to_fs_path(&working_copy_path), "modified").unwrap();
// Create a tree from the working copy. Only dir1/file1 should be updated in the
// tree.
let mut locked_wc = wc.start_mutation();
let modified_tree_id = locked_wc.write_tree(GitIgnoreFile::empty());
locked_wc.finish(repo.op_id().clone());
let modified_tree = repo
.store()
.get_tree(&RepoPath::root(), &modified_tree_id)
.unwrap();
let diff = tree.diff(&modified_tree, &EverythingMatcher).collect_vec();
assert_eq!(diff.len(), 1);
assert_eq!(diff[0].0, dir1_file1_path);
// Set sparse patterns to also include dir2/
let mut locked_wc = wc.start_mutation();
let sparse_patterns = vec![dir1_path, dir2_path];
locked_wc.set_sparse_patterns(sparse_patterns).unwrap();
locked_wc.finish(repo.op_id().clone());
// Write out a modified version of dir2/file1 again because it was overwritten
// when we added dir2/ to the sparse patterns.
// TODO: We shouldn't overwrite files when updating (there's already a TODO
// about that in `TreeState::write_file()`).
std::fs::write(dir2_file1_path.to_fs_path(&working_copy_path), "modified").unwrap();
// Create a tree from the working copy. Only dir1/file1 and dir2/file1 should be
// updated in the tree.
let mut locked_wc = wc.start_mutation();
let modified_tree_id = locked_wc.write_tree(GitIgnoreFile::empty());
locked_wc.finish(repo.op_id().clone());
let modified_tree = repo
.store()
.get_tree(&RepoPath::root(), &modified_tree_id)
.unwrap();
let diff = tree.diff(&modified_tree, &EverythingMatcher).collect_vec();
assert_eq!(diff.len(), 2);
assert_eq!(diff[0].0, dir1_file1_path);
assert_eq!(diff[1].0, dir2_file1_path);
}
#[test]
fn test_sparse_commit_gitignore() {
// Test that (untracked) .gitignore files in parent directories are respected
let settings = testutils::user_settings();
let mut test_workspace = testutils::init_workspace(&settings, false);
let repo = &test_workspace.repo;
let working_copy_path = test_workspace.workspace.workspace_root().clone();
let dir1_path = RepoPath::from_internal_string("dir1");
let dir1_file1_path = RepoPath::from_internal_string("dir1/file1");
let dir1_file2_path = RepoPath::from_internal_string("dir1/file2");
let wc = test_workspace.workspace.working_copy_mut();
// Set sparse patterns to only dir1/
let mut locked_wc = wc.start_mutation();
let sparse_patterns = vec![dir1_path.clone()];
locked_wc.set_sparse_patterns(sparse_patterns).unwrap();
locked_wc.finish(repo.op_id().clone());
// Write dir1/file1 and dir1/file2 and a .gitignore saying to ignore dir1/file1
std::fs::write(working_copy_path.join(".gitignore"), "dir1/file1").unwrap();
std::fs::create_dir(dir1_path.to_fs_path(&working_copy_path)).unwrap();
std::fs::write(dir1_file1_path.to_fs_path(&working_copy_path), "contents").unwrap();
std::fs::write(dir1_file2_path.to_fs_path(&working_copy_path), "contents").unwrap();
// Create a tree from the working copy. Only dir1/file2 should be updated in the
// tree because dir1/file1 is ignored.
let mut locked_wc = wc.start_mutation();
let modified_tree_id = locked_wc.write_tree(GitIgnoreFile::empty());
locked_wc.finish(repo.op_id().clone());
let modified_tree = repo
.store()
.get_tree(&RepoPath::root(), &modified_tree_id)
.unwrap();
let entries = modified_tree.entries().collect_vec();
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].0, dir1_file2_path);
}