jj/lib/tests/test_working_copy_sparse.rs
2023-05-06 11:07:46 -07:00

240 lines
9.7 KiB
Rust

// Copyright 2022 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use itertools::Itertools;
use jujutsu_lib::gitignore::GitIgnoreFile;
use jujutsu_lib::matchers::EverythingMatcher;
use jujutsu_lib::repo::Repo;
use jujutsu_lib::repo_path::RepoPath;
use jujutsu_lib::working_copy::{CheckoutStats, WorkingCopy};
use testutils::TestWorkspace;
#[test]
fn test_sparse_checkout() {
let settings = testutils::user_settings();
let mut test_workspace = TestWorkspace::init(&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 root_file2_path = RepoPath::from_internal_string("file2");
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 dir1_subdir1_path = RepoPath::from_internal_string("dir1/subdir1");
let dir1_subdir1_file1_path = RepoPath::from_internal_string("dir1/subdir1/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"),
(&root_file2_path, "contents"),
(&dir1_file1_path, "contents"),
(&dir1_file2_path, "contents"),
(&dir1_subdir1_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];
let stats = locked_wc
.set_sparse_patterns(sparse_patterns.clone())
.unwrap();
assert_eq!(
stats,
CheckoutStats {
updated_files: 0,
added_files: 0,
removed_files: 3
}
);
assert_eq!(locked_wc.sparse_patterns(), sparse_patterns);
assert!(!root_file1_path.to_fs_path(&working_copy_path).exists());
assert!(!root_file2_path.to_fs_path(&working_copy_path).exists());
assert!(dir1_file1_path.to_fs_path(&working_copy_path).exists());
assert!(dir1_file2_path.to_fs_path(&working_copy_path).exists());
assert!(dir1_subdir1_file1_path
.to_fs_path(&working_copy_path)
.exists());
assert!(!dir2_file1_path.to_fs_path(&working_copy_path).exists());
// Write the new state to disk
locked_wc.finish(repo.op_id().clone());
assert_eq!(
wc.file_states().keys().collect_vec(),
vec![&dir1_file1_path, &dir1_file2_path, &dir1_subdir1_file1_path]
);
assert_eq!(wc.sparse_patterns(), sparse_patterns);
// Reload the state to check that it was persisted
let mut wc = WorkingCopy::load(
repo.store().clone(),
wc.working_copy_path().to_path_buf(),
wc.state_path().to_path_buf(),
);
assert_eq!(
wc.file_states().keys().collect_vec(),
vec![&dir1_file1_path, &dir1_file2_path, &dir1_subdir1_file1_path]
);
assert_eq!(wc.sparse_patterns(), sparse_patterns);
// Set sparse patterns to file2, dir1/subdir1/ and dir2/
let mut locked_wc = wc.start_mutation();
let sparse_patterns = vec![root_file1_path.clone(), dir1_subdir1_path, dir2_path];
let stats = locked_wc
.set_sparse_patterns(sparse_patterns.clone())
.unwrap();
assert_eq!(
stats,
CheckoutStats {
updated_files: 0,
added_files: 2,
removed_files: 2
}
);
assert_eq!(locked_wc.sparse_patterns(), sparse_patterns);
assert!(root_file1_path.to_fs_path(&working_copy_path).exists());
assert!(!root_file2_path.to_fs_path(&working_copy_path).exists());
assert!(!dir1_file1_path.to_fs_path(&working_copy_path).exists());
assert!(!dir1_file2_path.to_fs_path(&working_copy_path).exists());
assert!(dir1_subdir1_file1_path
.to_fs_path(&working_copy_path)
.exists());
assert!(dir2_file1_path.to_fs_path(&working_copy_path).exists());
locked_wc.finish(repo.op_id().clone());
assert_eq!(
wc.file_states().keys().collect_vec(),
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 = TestWorkspace::init(&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.snapshot(GitIgnoreFile::empty(), None).unwrap();
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());
// 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.snapshot(GitIgnoreFile::empty(), None).unwrap();
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 = TestWorkspace::init(&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.snapshot(GitIgnoreFile::empty(), None).unwrap();
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);
}