mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-12 07:14:38 +00:00
739bf8decf
I'm going to add "checked" version of to_fs_path(), but all callers can't be migrated to it. For example, an error message should be produced even if the path is malformed. This patch also adds error variants to propagate InvalidRepoPathError. They don't use ::Other { .. } so the errors can be distinguished in tests.
310 lines
11 KiB
Rust
310 lines
11 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 futures::StreamExt as _;
|
|
use itertools::Itertools;
|
|
use jj_lib::local_working_copy::LocalWorkingCopy;
|
|
use jj_lib::matchers::EverythingMatcher;
|
|
use jj_lib::repo::Repo;
|
|
use jj_lib::repo_path::RepoPath;
|
|
use jj_lib::repo_path::RepoPathBuf;
|
|
use jj_lib::working_copy::CheckoutStats;
|
|
use jj_lib::working_copy::WorkingCopy;
|
|
use pollster::FutureExt as _;
|
|
use testutils::commit_with_tree;
|
|
use testutils::create_tree;
|
|
use testutils::TestWorkspace;
|
|
|
|
fn to_owned_path_vec(paths: &[&RepoPath]) -> Vec<RepoPathBuf> {
|
|
paths.iter().map(|&path| path.to_owned()).collect()
|
|
}
|
|
|
|
#[test]
|
|
fn test_sparse_checkout() {
|
|
let settings = testutils::user_settings();
|
|
let mut test_workspace = TestWorkspace::init(&settings);
|
|
let repo = &test_workspace.repo;
|
|
let working_copy_path = test_workspace.workspace.workspace_root().to_owned();
|
|
|
|
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 = 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.id());
|
|
|
|
test_workspace
|
|
.workspace
|
|
.check_out(repo.op_id().clone(), None, &commit)
|
|
.unwrap();
|
|
let ws = &mut test_workspace.workspace;
|
|
|
|
// Set sparse patterns to only dir1/
|
|
let mut locked_ws = ws.start_working_copy_mutation().unwrap();
|
|
let sparse_patterns = to_owned_path_vec(&[dir1_path]);
|
|
let stats = locked_ws
|
|
.locked_wc()
|
|
.set_sparse_patterns(sparse_patterns.clone())
|
|
.unwrap();
|
|
assert_eq!(
|
|
stats,
|
|
CheckoutStats {
|
|
updated_files: 0,
|
|
added_files: 0,
|
|
removed_files: 3,
|
|
skipped_files: 0,
|
|
}
|
|
);
|
|
assert_eq!(
|
|
locked_ws.locked_wc().sparse_patterns().unwrap(),
|
|
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());
|
|
|
|
// Write the new state to disk
|
|
locked_ws.finish(repo.op_id().clone()).unwrap();
|
|
let wc: &LocalWorkingCopy = ws.working_copy().as_any().downcast_ref().unwrap();
|
|
assert_eq!(
|
|
wc.file_states().unwrap().paths().collect_vec(),
|
|
vec![dir1_file1_path, dir1_file2_path, dir1_subdir1_file1_path]
|
|
);
|
|
assert_eq!(wc.sparse_patterns().unwrap(), sparse_patterns);
|
|
|
|
// Reload the state to check that it was persisted
|
|
let wc = LocalWorkingCopy::load(
|
|
repo.store().clone(),
|
|
ws.workspace_root().to_path_buf(),
|
|
wc.state_path().to_path_buf(),
|
|
);
|
|
assert_eq!(
|
|
wc.file_states().unwrap().paths().collect_vec(),
|
|
vec![dir1_file1_path, dir1_file2_path, dir1_subdir1_file1_path]
|
|
);
|
|
assert_eq!(wc.sparse_patterns().unwrap(), sparse_patterns);
|
|
|
|
// Set sparse patterns to file2, dir1/subdir1/ and dir2/
|
|
let mut locked_wc = wc.start_mutation().unwrap();
|
|
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())
|
|
.unwrap();
|
|
assert_eq!(
|
|
stats,
|
|
CheckoutStats {
|
|
updated_files: 0,
|
|
added_files: 2,
|
|
removed_files: 2,
|
|
skipped_files: 0,
|
|
}
|
|
);
|
|
assert_eq!(locked_wc.sparse_patterns().unwrap(), 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()).unwrap();
|
|
let wc: &LocalWorkingCopy = wc.as_any().downcast_ref().unwrap();
|
|
assert_eq!(
|
|
wc.file_states().unwrap().paths().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);
|
|
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 = 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 = create_tree(
|
|
repo,
|
|
&[
|
|
(root_file1_path, "contents"),
|
|
(dir1_file1_path, "contents"),
|
|
(dir2_file1_path, "contents"),
|
|
],
|
|
);
|
|
|
|
let commit = commit_with_tree(repo.store(), tree.id());
|
|
test_workspace
|
|
.workspace
|
|
.check_out(repo.op_id().clone(), None, &commit)
|
|
.unwrap();
|
|
|
|
// Set sparse patterns to only dir1/
|
|
let mut locked_ws = test_workspace
|
|
.workspace
|
|
.start_working_copy_mutation()
|
|
.unwrap();
|
|
let sparse_patterns = to_owned_path_vec(&[dir1_path]);
|
|
locked_ws
|
|
.locked_wc()
|
|
.set_sparse_patterns(sparse_patterns)
|
|
.unwrap();
|
|
locked_ws.finish(repo.op_id().clone()).unwrap();
|
|
|
|
// Write modified version of all files, including files that are not in the
|
|
// sparse patterns.
|
|
std::fs::write(
|
|
root_file1_path.to_fs_path_unchecked(&working_copy_path),
|
|
"modified",
|
|
)
|
|
.unwrap();
|
|
std::fs::write(
|
|
dir1_file1_path.to_fs_path_unchecked(&working_copy_path),
|
|
"modified",
|
|
)
|
|
.unwrap();
|
|
std::fs::create_dir(dir2_path.to_fs_path_unchecked(&working_copy_path)).unwrap();
|
|
std::fs::write(
|
|
dir2_file1_path.to_fs_path_unchecked(&working_copy_path),
|
|
"modified",
|
|
)
|
|
.unwrap();
|
|
|
|
// Create a tree from the working copy. Only dir1/file1 should be updated in the
|
|
// tree.
|
|
let modified_tree = test_workspace.snapshot().unwrap();
|
|
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);
|
|
|
|
// Set sparse patterns to also include dir2/
|
|
let mut locked_ws = test_workspace
|
|
.workspace
|
|
.start_working_copy_mutation()
|
|
.unwrap();
|
|
let sparse_patterns = to_owned_path_vec(&[dir1_path, dir2_path]);
|
|
locked_ws
|
|
.locked_wc()
|
|
.set_sparse_patterns(sparse_patterns)
|
|
.unwrap();
|
|
locked_ws.finish(op_id).unwrap();
|
|
|
|
// Create a tree from the working copy. Only dir1/file1 and dir2/file1 should be
|
|
// updated in the tree.
|
|
let modified_tree = test_workspace.snapshot().unwrap();
|
|
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);
|
|
}
|
|
|
|
#[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);
|
|
let repo = &test_workspace.repo;
|
|
let working_copy_path = test_workspace.workspace.workspace_root().to_owned();
|
|
|
|
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");
|
|
|
|
// Set sparse patterns to only dir1/
|
|
let mut locked_ws = test_workspace
|
|
.workspace
|
|
.start_working_copy_mutation()
|
|
.unwrap();
|
|
let sparse_patterns = to_owned_path_vec(&[dir1_path]);
|
|
locked_ws
|
|
.locked_wc()
|
|
.set_sparse_patterns(sparse_patterns)
|
|
.unwrap();
|
|
locked_ws.finish(repo.op_id().clone()).unwrap();
|
|
|
|
// 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_unchecked(&working_copy_path)).unwrap();
|
|
std::fs::write(
|
|
dir1_file1_path.to_fs_path_unchecked(&working_copy_path),
|
|
"contents",
|
|
)
|
|
.unwrap();
|
|
std::fs::write(
|
|
dir1_file2_path.to_fs_path_unchecked(&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 modified_tree = test_workspace.snapshot().unwrap();
|
|
let entries = modified_tree.entries().collect_vec();
|
|
assert_eq!(entries.len(), 1);
|
|
assert_eq!(entries[0].0.as_ref(), dir1_file2_path);
|
|
}
|