forked from mirrors/jj
working_copy: allow updating sparse patterns (#52)
With this patch, we add support for setting the sparse patterns, and we respect it when updating the working copy.
This commit is contained in:
parent
ceb6c152a1
commit
0d881de56c
2 changed files with 170 additions and 7 deletions
|
@ -37,7 +37,7 @@ use crate::backend::{
|
||||||
use crate::conflicts::{materialize_conflict, update_conflict_from_content};
|
use crate::conflicts::{materialize_conflict, update_conflict_from_content};
|
||||||
use crate::gitignore::GitIgnoreFile;
|
use crate::gitignore::GitIgnoreFile;
|
||||||
use crate::lock::FileLock;
|
use crate::lock::FileLock;
|
||||||
use crate::matchers::{EverythingMatcher, Matcher};
|
use crate::matchers::{DifferenceMatcher, Matcher, PrefixMatcher};
|
||||||
use crate::op_store::{OperationId, WorkspaceId};
|
use crate::op_store::{OperationId, WorkspaceId};
|
||||||
use crate::repo_path::{RepoPath, RepoPathComponent, RepoPathJoin};
|
use crate::repo_path::{RepoPath, RepoPathComponent, RepoPathJoin};
|
||||||
use crate::store::Store;
|
use crate::store::Store;
|
||||||
|
@ -226,6 +226,10 @@ impl TreeState {
|
||||||
&self.sparse_patterns
|
&self.sparse_patterns
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sparse_matcher(&self) -> Box<dyn Matcher> {
|
||||||
|
Box::new(PrefixMatcher::new(&self.sparse_patterns))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn init(store: Arc<Store>, working_copy_path: PathBuf, state_path: PathBuf) -> TreeState {
|
pub fn init(store: Arc<Store>, working_copy_path: PathBuf, state_path: PathBuf) -> TreeState {
|
||||||
let mut wc = TreeState::empty(store, working_copy_path, state_path);
|
let mut wc = TreeState::empty(store, working_copy_path, state_path);
|
||||||
wc.save();
|
wc.save();
|
||||||
|
@ -579,13 +583,39 @@ impl TreeState {
|
||||||
BackendError::NotFound => CheckoutError::SourceNotFound,
|
BackendError::NotFound => CheckoutError::SourceNotFound,
|
||||||
other => CheckoutError::InternalBackendError(other),
|
other => CheckoutError::InternalBackendError(other),
|
||||||
})?;
|
})?;
|
||||||
let stats = self.update(&old_tree, new_tree, &EverythingMatcher)?;
|
let stats = self.update(&old_tree, new_tree, self.sparse_matcher().as_ref())?;
|
||||||
self.tree_id = new_tree.id().clone();
|
self.tree_id = new_tree.id().clone();
|
||||||
Ok(stats)
|
Ok(stats)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_sparse_patterns(&mut self, sparse_patterns: Vec<RepoPath>) {
|
pub fn set_sparse_patterns(
|
||||||
|
&mut self,
|
||||||
|
sparse_patterns: Vec<RepoPath>,
|
||||||
|
) -> Result<CheckoutStats, CheckoutError> {
|
||||||
|
let tree = self
|
||||||
|
.store
|
||||||
|
.get_tree(&RepoPath::root(), &self.tree_id)
|
||||||
|
.map_err(|err| match err {
|
||||||
|
BackendError::NotFound => CheckoutError::SourceNotFound,
|
||||||
|
other => CheckoutError::InternalBackendError(other),
|
||||||
|
})?;
|
||||||
|
let old_matcher = PrefixMatcher::new(&self.sparse_patterns);
|
||||||
|
let new_matcher = PrefixMatcher::new(&sparse_patterns);
|
||||||
|
let added_matcher = DifferenceMatcher::new(&new_matcher, &old_matcher);
|
||||||
|
let removed_matcher = DifferenceMatcher::new(&old_matcher, &new_matcher);
|
||||||
|
let empty_tree = Tree::null(self.store.clone(), RepoPath::root());
|
||||||
|
let added_stats = self.update(&empty_tree, &tree, &added_matcher)?;
|
||||||
|
let removed_stats = self.update(&tree, &empty_tree, &removed_matcher)?;
|
||||||
self.sparse_patterns = sparse_patterns;
|
self.sparse_patterns = sparse_patterns;
|
||||||
|
assert_eq!(added_stats.updated_files, 0);
|
||||||
|
assert_eq!(added_stats.removed_files, 0);
|
||||||
|
assert_eq!(removed_stats.updated_files, 0);
|
||||||
|
assert_eq!(removed_stats.added_files, 0);
|
||||||
|
Ok(CheckoutStats {
|
||||||
|
updated_files: 0,
|
||||||
|
added_files: added_stats.added_files,
|
||||||
|
removed_files: removed_stats.removed_files,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
|
@ -684,7 +714,7 @@ impl TreeState {
|
||||||
other => ResetError::InternalBackendError(other),
|
other => ResetError::InternalBackendError(other),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
for (path, diff) in old_tree.diff(new_tree, &EverythingMatcher) {
|
for (path, diff) in old_tree.diff(new_tree, self.sparse_matcher().as_ref()) {
|
||||||
match diff {
|
match diff {
|
||||||
Diff::Removed(_before) => {
|
Diff::Removed(_before) => {
|
||||||
self.file_states.remove(&path);
|
self.file_states.remove(&path);
|
||||||
|
@ -766,6 +796,10 @@ impl WorkingCopy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn working_copy_path(&self) -> &Path {
|
||||||
|
&self.working_copy_path
|
||||||
|
}
|
||||||
|
|
||||||
pub fn state_path(&self) -> &Path {
|
pub fn state_path(&self) -> &Path {
|
||||||
&self.state_path
|
&self.state_path
|
||||||
}
|
}
|
||||||
|
@ -925,8 +959,8 @@ impl LockedWorkingCopy<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_out(&mut self, new_tree: &Tree) -> Result<CheckoutStats, CheckoutError> {
|
pub fn check_out(&mut self, new_tree: &Tree) -> Result<CheckoutStats, CheckoutError> {
|
||||||
// TODO: Write a "pending_checkout" file with the old and new TreeIds so we can
|
// TODO: Write a "pending_checkout" file with the new TreeId so we can
|
||||||
// continue an interrupted checkout if we find such a file.
|
// continue an interrupted update if we find such a file.
|
||||||
let stats = self.wc.tree_state().as_mut().unwrap().check_out(new_tree)?;
|
let stats = self.wc.tree_state().as_mut().unwrap().check_out(new_tree)?;
|
||||||
Ok(stats)
|
Ok(stats)
|
||||||
}
|
}
|
||||||
|
@ -939,7 +973,12 @@ impl LockedWorkingCopy<'_> {
|
||||||
self.wc.sparse_patterns()
|
self.wc.sparse_patterns()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_sparse_patterns(&mut self, new_sparse_patterns: Vec<RepoPath>) {
|
pub fn set_sparse_patterns(
|
||||||
|
&mut self,
|
||||||
|
new_sparse_patterns: Vec<RepoPath>,
|
||||||
|
) -> Result<CheckoutStats, CheckoutError> {
|
||||||
|
// TODO: Write a "pending_checkout" file with new sparse patterns so we can
|
||||||
|
// continue an interrupted update if we find such a file.
|
||||||
self.wc
|
self.wc
|
||||||
.tree_state()
|
.tree_state()
|
||||||
.as_mut()
|
.as_mut()
|
||||||
|
|
124
lib/tests/test_working_copy_sparse.rs
Normal file
124
lib/tests/test_working_copy_sparse.rs
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
// Copyright 2022 Google LLC
|
||||||
|
//
|
||||||
|
// 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::repo_path::RepoPath;
|
||||||
|
use jujutsu_lib::testutils;
|
||||||
|
use jujutsu_lib::working_copy::{CheckoutStats, WorkingCopy};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sparse_checkout() {
|
||||||
|
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 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]
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in a new issue