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:
Martin von Zweigbergk 2022-02-09 22:42:50 -08:00 committed by Martin von Zweigbergk
parent ceb6c152a1
commit 0d881de56c
2 changed files with 170 additions and 7 deletions

View file

@ -37,7 +37,7 @@ use crate::backend::{
use crate::conflicts::{materialize_conflict, update_conflict_from_content};
use crate::gitignore::GitIgnoreFile;
use crate::lock::FileLock;
use crate::matchers::{EverythingMatcher, Matcher};
use crate::matchers::{DifferenceMatcher, Matcher, PrefixMatcher};
use crate::op_store::{OperationId, WorkspaceId};
use crate::repo_path::{RepoPath, RepoPathComponent, RepoPathJoin};
use crate::store::Store;
@ -226,6 +226,10 @@ impl TreeState {
&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 {
let mut wc = TreeState::empty(store, working_copy_path, state_path);
wc.save();
@ -579,13 +583,39 @@ impl TreeState {
BackendError::NotFound => CheckoutError::SourceNotFound,
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();
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;
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(
@ -684,7 +714,7 @@ impl TreeState {
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 {
Diff::Removed(_before) => {
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 {
&self.state_path
}
@ -925,8 +959,8 @@ impl LockedWorkingCopy<'_> {
}
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
// continue an interrupted checkout if we find such a file.
// TODO: Write a "pending_checkout" file with the new TreeId so we can
// continue an interrupted update if we find such a file.
let stats = self.wc.tree_state().as_mut().unwrap().check_out(new_tree)?;
Ok(stats)
}
@ -939,7 +973,12 @@ impl LockedWorkingCopy<'_> {
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
.tree_state()
.as_mut()

View 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]
);
}