mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-18 10:07:28 +00:00
working_copy: respect sparse patterns when writing tree (#52)
This commit is contained in:
parent
0d881de56c
commit
18a64c365a
2 changed files with 132 additions and 6 deletions
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue