working_copy: return Result from WorkingCopy::tree_state/WorkingCopy::tree_state_mut

This commit is contained in:
Waleed Khan 2023-07-14 23:33:59 +03:00
parent 60f1d7e307
commit 6d7998f8c5
7 changed files with 124 additions and 104 deletions

View File

@ -15,6 +15,7 @@
#![allow(missing_docs)] #![allow(missing_docs)]
use std::collections::{BTreeMap, HashSet}; use std::collections::{BTreeMap, HashSet};
use std::error::Error;
use std::ffi::OsString; use std::ffi::OsString;
use std::fs; use std::fs;
use std::fs::{DirEntry, File, Metadata, OpenOptions}; use std::fs::{DirEntry, File, Metadata, OpenOptions};
@ -299,6 +300,8 @@ pub enum SnapshotError {
InvalidUtf8SymlinkTarget { path: PathBuf, target: PathBuf }, InvalidUtf8SymlinkTarget { path: PathBuf, target: PathBuf },
#[error("Internal backend error: {0}")] #[error("Internal backend error: {0}")]
InternalBackendError(#[from] BackendError), InternalBackendError(#[from] BackendError),
#[error(transparent)]
TreeStateError(#[from] TreeStateError),
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -374,6 +377,8 @@ pub enum ResetError {
}, },
#[error("Internal error: {0}")] #[error("Internal error: {0}")]
InternalBackendError(#[from] BackendError), InternalBackendError(#[from] BackendError),
#[error(transparent)]
TreeStateError(#[from] TreeStateError),
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -398,6 +403,8 @@ pub enum TreeStateError {
path: PathBuf, path: PathBuf,
source: tempfile::PersistError, source: tempfile::PersistError,
}, },
#[error("Filesystem monitor error: {0}")]
Fsmonitor(Box<dyn Error + Send + Sync>),
} }
impl TreeState { impl TreeState {
@ -580,10 +587,16 @@ impl TreeState {
#[tokio::main] #[tokio::main]
pub async fn query_watchman( pub async fn query_watchman(
&self, &self,
) -> Result<(watchman::Clock, Option<Vec<PathBuf>>), watchman::Error> { ) -> Result<(watchman::Clock, Option<Vec<PathBuf>>), TreeStateError> {
let fsmonitor = watchman::Fsmonitor::init(&self.working_copy_path).await?; let fsmonitor = watchman::Fsmonitor::init(&self.working_copy_path)
.await
.map_err(|err| TreeStateError::Fsmonitor(Box::new(err)))?;
let previous_clock = self.watchman_clock.clone().map(watchman::Clock::from); let previous_clock = self.watchman_clock.clone().map(watchman::Clock::from);
fsmonitor.query_changed_files(previous_clock).await let changed_files = fsmonitor
.query_changed_files(previous_clock)
.await
.map_err(|err| TreeStateError::Fsmonitor(Box::new(err)))?;
Ok(changed_files)
} }
/// Look for changes to the working copy. If there are any changes, create /// Look for changes to the working copy. If there are any changes, create
@ -1306,33 +1319,31 @@ impl WorkingCopy {
&self.checkout_state().workspace_id &self.checkout_state().workspace_id
} }
fn tree_state(&self) -> &TreeState { fn tree_state(&self) -> Result<&TreeState, TreeStateError> {
self.tree_state self.tree_state.get_or_try_init(|| {
.get_or_try_init(|| { TreeState::load(
TreeState::load( self.store.clone(),
self.store.clone(), self.working_copy_path.clone(),
self.working_copy_path.clone(), self.state_path.clone(),
self.state_path.clone(), )
) })
})
.unwrap() // FIXME: propagate error
} }
fn tree_state_mut(&mut self) -> &mut TreeState { fn tree_state_mut(&mut self) -> Result<&mut TreeState, TreeStateError> {
self.tree_state(); // ensure loaded self.tree_state()?; // ensure loaded
self.tree_state.get_mut().unwrap() Ok(self.tree_state.get_mut().unwrap())
} }
pub fn current_tree_id(&self) -> &TreeId { pub fn current_tree_id(&self) -> Result<&TreeId, TreeStateError> {
self.tree_state().current_tree_id() Ok(self.tree_state()?.current_tree_id())
} }
pub fn file_states(&self) -> &BTreeMap<RepoPath, FileState> { pub fn file_states(&self) -> Result<&BTreeMap<RepoPath, FileState>, TreeStateError> {
self.tree_state().file_states() Ok(self.tree_state()?.file_states())
} }
pub fn sparse_patterns(&self) -> &[RepoPath] { pub fn sparse_patterns(&self) -> Result<&[RepoPath], TreeStateError> {
self.tree_state().sparse_patterns() Ok(self.tree_state()?.sparse_patterns())
} }
fn save(&mut self) { fn save(&mut self) {
@ -1343,7 +1354,7 @@ impl WorkingCopy {
}); });
} }
pub fn start_mutation(&mut self) -> LockedWorkingCopy { pub fn start_mutation(&mut self) -> Result<LockedWorkingCopy, TreeStateError> {
let lock_path = self.state_path.join("working_copy.lock"); let lock_path = self.state_path.join("working_copy.lock");
let lock = FileLock::lock(lock_path); let lock = FileLock::lock(lock_path);
@ -1353,16 +1364,16 @@ impl WorkingCopy {
// has changed. // has changed.
self.tree_state.take(); self.tree_state.take();
let old_operation_id = self.operation_id().clone(); let old_operation_id = self.operation_id().clone();
let old_tree_id = self.current_tree_id().clone(); let old_tree_id = self.current_tree_id()?.clone();
LockedWorkingCopy { Ok(LockedWorkingCopy {
wc: self, wc: self,
lock, lock,
old_operation_id, old_operation_id,
old_tree_id, old_tree_id,
tree_state_dirty: false, tree_state_dirty: false,
closed: false, closed: false,
} })
} }
pub fn check_out( pub fn check_out(
@ -1371,7 +1382,7 @@ impl WorkingCopy {
old_tree_id: Option<&TreeId>, old_tree_id: Option<&TreeId>,
new_tree: &Tree, new_tree: &Tree,
) -> Result<CheckoutStats, CheckoutError> { ) -> Result<CheckoutStats, CheckoutError> {
let mut locked_wc = self.start_mutation(); let mut locked_wc = self.start_mutation()?;
// Check if the current working-copy commit has changed on disk compared to what // Check if the current working-copy commit has changed on disk compared to what
// the caller expected. It's safe to check out another commit // the caller expected. It's safe to check out another commit
// regardless, but it's probably not what the caller wanted, so we let // regardless, but it's probably not what the caller wanted, so we let
@ -1390,8 +1401,8 @@ impl WorkingCopy {
#[cfg(feature = "watchman")] #[cfg(feature = "watchman")]
pub fn query_watchman( pub fn query_watchman(
&self, &self,
) -> Result<(watchman::Clock, Option<Vec<PathBuf>>), watchman::Error> { ) -> Result<(watchman::Clock, Option<Vec<PathBuf>>), TreeStateError> {
self.tree_state().query_watchman() self.tree_state()?.query_watchman()
} }
} }
@ -1419,7 +1430,7 @@ impl LockedWorkingCopy<'_> {
} }
pub fn reset_watchman(&mut self) -> Result<(), SnapshotError> { pub fn reset_watchman(&mut self) -> Result<(), SnapshotError> {
self.wc.tree_state_mut().reset_watchman(); self.wc.tree_state_mut()?.reset_watchman();
self.tree_state_dirty = true; self.tree_state_dirty = true;
Ok(()) Ok(())
} }
@ -1428,7 +1439,7 @@ impl LockedWorkingCopy<'_> {
// because the TreeState may be long-lived if the library is used in a // because the TreeState may be long-lived if the library is used in a
// long-lived process. // long-lived process.
pub fn snapshot(&mut self, options: SnapshotOptions) -> Result<TreeId, SnapshotError> { pub fn snapshot(&mut self, options: SnapshotOptions) -> Result<TreeId, SnapshotError> {
let tree_state = self.wc.tree_state_mut(); let tree_state = self.wc.tree_state_mut()?;
self.tree_state_dirty |= tree_state.snapshot(options)?; self.tree_state_dirty |= tree_state.snapshot(options)?;
Ok(tree_state.current_tree_id().clone()) Ok(tree_state.current_tree_id().clone())
} }
@ -1436,18 +1447,18 @@ 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 new TreeId so we can // TODO: Write a "pending_checkout" file with the new TreeId so we can
// continue an interrupted update if we find such a file. // continue an interrupted update if we find such a file.
let stats = self.wc.tree_state_mut().check_out(new_tree)?; let stats = self.wc.tree_state_mut()?.check_out(new_tree)?;
self.tree_state_dirty = true; self.tree_state_dirty = true;
Ok(stats) Ok(stats)
} }
pub fn reset(&mut self, new_tree: &Tree) -> Result<(), ResetError> { pub fn reset(&mut self, new_tree: &Tree) -> Result<(), ResetError> {
self.wc.tree_state_mut().reset(new_tree)?; self.wc.tree_state_mut()?.reset(new_tree)?;
self.tree_state_dirty = true; self.tree_state_dirty = true;
Ok(()) Ok(())
} }
pub fn sparse_patterns(&self) -> &[RepoPath] { pub fn sparse_patterns(&self) -> Result<&[RepoPath], TreeStateError> {
self.wc.sparse_patterns() self.wc.sparse_patterns()
} }
@ -1459,16 +1470,16 @@ impl LockedWorkingCopy<'_> {
// continue an interrupted update if we find such a file. // continue an interrupted update if we find such a file.
let stats = self let stats = self
.wc .wc
.tree_state_mut() .tree_state_mut()?
.set_sparse_patterns(new_sparse_patterns)?; .set_sparse_patterns(new_sparse_patterns)?;
self.tree_state_dirty = true; self.tree_state_dirty = true;
Ok(stats) Ok(stats)
} }
pub fn finish(mut self, operation_id: OperationId) -> Result<(), TreeStateError> { pub fn finish(mut self, operation_id: OperationId) -> Result<(), TreeStateError> {
assert!(self.tree_state_dirty || &self.old_tree_id == self.wc.current_tree_id()); assert!(self.tree_state_dirty || &self.old_tree_id == self.wc.current_tree_id()?);
if self.tree_state_dirty { if self.tree_state_dirty {
self.wc.tree_state_mut().save()?; self.wc.tree_state_mut()?.save()?;
} }
if self.old_operation_id != operation_id { if self.old_operation_id != operation_id {
self.wc.checkout_state_mut().operation_id = operation_id; self.wc.checkout_state_mut().operation_id = operation_id;

View File

@ -44,8 +44,8 @@ fn test_root(use_git: bool) {
let repo = &test_workspace.repo; let repo = &test_workspace.repo;
let wc = test_workspace.workspace.working_copy_mut(); let wc = test_workspace.workspace.working_copy_mut();
assert_eq!(wc.sparse_patterns(), vec![RepoPath::root()]); assert_eq!(wc.sparse_patterns().unwrap(), vec![RepoPath::root()]);
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let new_tree_id = locked_wc let new_tree_id = locked_wc
.snapshot(SnapshotOptions::empty_for_test()) .snapshot(SnapshotOptions::empty_for_test())
.unwrap(); .unwrap();
@ -205,7 +205,7 @@ fn test_checkout_file_transitions(use_git: bool) {
.unwrap(); .unwrap();
// Check that the working copy is clean. // Check that the working copy is clean.
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let new_tree_id = locked_wc let new_tree_id = locked_wc
.snapshot(SnapshotOptions::empty_for_test()) .snapshot(SnapshotOptions::empty_for_test())
.unwrap(); .unwrap();
@ -340,17 +340,17 @@ fn test_reset() {
// Test the setup: the file should exist on disk and in the tree state. // Test the setup: the file should exist on disk and in the tree state.
assert!(ignored_path.to_fs_path(&workspace_root).is_file()); assert!(ignored_path.to_fs_path(&workspace_root).is_file());
assert!(wc.file_states().contains_key(&ignored_path)); assert!(wc.file_states().unwrap().contains_key(&ignored_path));
// After we reset to the commit without the file, it should still exist on disk, // After we reset to the commit without the file, it should still exist on disk,
// but it should not be in the tree state, and it should not get added when we // but it should not be in the tree state, and it should not get added when we
// commit the working copy (because it's ignored). // commit the working copy (because it's ignored).
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
locked_wc.reset(&tree_without_file).unwrap(); locked_wc.reset(&tree_without_file).unwrap();
locked_wc.finish(repo.op_id().clone()).unwrap(); locked_wc.finish(repo.op_id().clone()).unwrap();
assert!(ignored_path.to_fs_path(&workspace_root).is_file()); assert!(ignored_path.to_fs_path(&workspace_root).is_file());
assert!(!wc.file_states().contains_key(&ignored_path)); assert!(!wc.file_states().unwrap().contains_key(&ignored_path));
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let new_tree_id = locked_wc let new_tree_id = locked_wc
.snapshot(SnapshotOptions::empty_for_test()) .snapshot(SnapshotOptions::empty_for_test())
.unwrap(); .unwrap();
@ -360,12 +360,12 @@ fn test_reset() {
// After we reset to the commit without the file, it should still exist on disk, // After we reset to the commit without the file, it should still exist on disk,
// but it should not be in the tree state, and it should not get added when we // but it should not be in the tree state, and it should not get added when we
// commit the working copy (because it's ignored). // commit the working copy (because it's ignored).
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
locked_wc.reset(&tree_without_file).unwrap(); locked_wc.reset(&tree_without_file).unwrap();
locked_wc.finish(repo.op_id().clone()).unwrap(); locked_wc.finish(repo.op_id().clone()).unwrap();
assert!(ignored_path.to_fs_path(&workspace_root).is_file()); assert!(ignored_path.to_fs_path(&workspace_root).is_file());
assert!(!wc.file_states().contains_key(&ignored_path)); assert!(!wc.file_states().unwrap().contains_key(&ignored_path));
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let new_tree_id = locked_wc let new_tree_id = locked_wc
.snapshot(SnapshotOptions::empty_for_test()) .snapshot(SnapshotOptions::empty_for_test())
.unwrap(); .unwrap();
@ -374,12 +374,12 @@ fn test_reset() {
// Now test the opposite direction: resetting to a commit where the file is // Now test the opposite direction: resetting to a commit where the file is
// tracked. The file should become tracked (even though it's ignored). // tracked. The file should become tracked (even though it's ignored).
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
locked_wc.reset(&tree_with_file).unwrap(); locked_wc.reset(&tree_with_file).unwrap();
locked_wc.finish(repo.op_id().clone()).unwrap(); locked_wc.finish(repo.op_id().clone()).unwrap();
assert!(ignored_path.to_fs_path(&workspace_root).is_file()); assert!(ignored_path.to_fs_path(&workspace_root).is_file());
assert!(wc.file_states().contains_key(&ignored_path)); assert!(wc.file_states().unwrap().contains_key(&ignored_path));
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let new_tree_id = locked_wc let new_tree_id = locked_wc
.snapshot(SnapshotOptions::empty_for_test()) .snapshot(SnapshotOptions::empty_for_test())
.unwrap(); .unwrap();
@ -410,27 +410,27 @@ fn test_checkout_discard() {
// Test the setup: the file should exist on disk and in the tree state. // Test the setup: the file should exist on disk and in the tree state.
assert!(file1_path.to_fs_path(&workspace_root).is_file()); assert!(file1_path.to_fs_path(&workspace_root).is_file());
assert!(wc.file_states().contains_key(&file1_path)); assert!(wc.file_states().unwrap().contains_key(&file1_path));
// Start a checkout // Start a checkout
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
locked_wc.check_out(&tree2).unwrap(); locked_wc.check_out(&tree2).unwrap();
// The change should be reflected in the working copy but not saved // The change should be reflected in the working copy but not saved
assert!(!file1_path.to_fs_path(&workspace_root).is_file()); assert!(!file1_path.to_fs_path(&workspace_root).is_file());
assert!(file2_path.to_fs_path(&workspace_root).is_file()); assert!(file2_path.to_fs_path(&workspace_root).is_file());
let reloaded_wc = WorkingCopy::load(store.clone(), workspace_root.clone(), state_path.clone()); let reloaded_wc = WorkingCopy::load(store.clone(), workspace_root.clone(), state_path.clone());
assert!(reloaded_wc.file_states().contains_key(&file1_path)); assert!(reloaded_wc.file_states().unwrap().contains_key(&file1_path));
assert!(!reloaded_wc.file_states().contains_key(&file2_path)); assert!(!reloaded_wc.file_states().unwrap().contains_key(&file2_path));
locked_wc.discard(); locked_wc.discard();
// The change should remain in the working copy, but not in memory and not saved // The change should remain in the working copy, but not in memory and not saved
assert!(wc.file_states().contains_key(&file1_path)); assert!(wc.file_states().unwrap().contains_key(&file1_path));
assert!(!wc.file_states().contains_key(&file2_path)); assert!(!wc.file_states().unwrap().contains_key(&file2_path));
assert!(!file1_path.to_fs_path(&workspace_root).is_file()); assert!(!file1_path.to_fs_path(&workspace_root).is_file());
assert!(file2_path.to_fs_path(&workspace_root).is_file()); assert!(file2_path.to_fs_path(&workspace_root).is_file());
let reloaded_wc = WorkingCopy::load(store.clone(), workspace_root, state_path); let reloaded_wc = WorkingCopy::load(store.clone(), workspace_root, state_path);
assert!(reloaded_wc.file_states().contains_key(&file1_path)); assert!(reloaded_wc.file_states().unwrap().contains_key(&file1_path));
assert!(!reloaded_wc.file_states().contains_key(&file2_path)); assert!(!reloaded_wc.file_states().unwrap().contains_key(&file2_path));
} }
#[test_case(false ; "local backend")] #[test_case(false ; "local backend")]
@ -457,7 +457,7 @@ fn test_snapshot_racy_timestamps(use_git: bool) {
.unwrap(); .unwrap();
file.write_all(format!("contents {i}").as_bytes()).unwrap(); file.write_all(format!("contents {i}").as_bytes()).unwrap();
} }
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let new_tree_id = locked_wc let new_tree_id = locked_wc
.snapshot(SnapshotOptions::empty_for_test()) .snapshot(SnapshotOptions::empty_for_test())
.unwrap(); .unwrap();
@ -491,7 +491,7 @@ fn test_snapshot_special_file() {
// Snapshot the working copy with the socket file // Snapshot the working copy with the socket file
let wc = test_workspace.workspace.working_copy_mut(); let wc = test_workspace.workspace.working_copy_mut();
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let tree_id = locked_wc let tree_id = locked_wc
.snapshot(SnapshotOptions::empty_for_test()) .snapshot(SnapshotOptions::empty_for_test())
.unwrap(); .unwrap();
@ -503,14 +503,14 @@ fn test_snapshot_special_file() {
vec![file1_path.clone(), file2_path.clone()] vec![file1_path.clone(), file2_path.clone()]
); );
assert_eq!( assert_eq!(
wc.file_states().keys().cloned().collect_vec(), wc.file_states().unwrap().keys().cloned().collect_vec(),
vec![file1_path, file2_path.clone()] vec![file1_path, file2_path.clone()]
); );
// Replace a regular file by a socket and snapshot the working copy again // Replace a regular file by a socket and snapshot the working copy again
std::fs::remove_file(&file1_disk_path).unwrap(); std::fs::remove_file(&file1_disk_path).unwrap();
UnixListener::bind(&file1_disk_path).unwrap(); UnixListener::bind(&file1_disk_path).unwrap();
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let tree_id = locked_wc let tree_id = locked_wc
.snapshot(SnapshotOptions::empty_for_test()) .snapshot(SnapshotOptions::empty_for_test())
.unwrap(); .unwrap();
@ -522,7 +522,7 @@ fn test_snapshot_special_file() {
vec![file2_path.clone()] vec![file2_path.clone()]
); );
assert_eq!( assert_eq!(
wc.file_states().keys().cloned().collect_vec(), wc.file_states().unwrap().keys().cloned().collect_vec(),
vec![file2_path] vec![file2_path]
); );
} }
@ -552,7 +552,7 @@ fn test_gitignores(use_git: bool) {
testutils::write_working_copy_file(&workspace_root, &subdir_modified_path, "1"); testutils::write_working_copy_file(&workspace_root, &subdir_modified_path, "1");
let wc = test_workspace.workspace.working_copy_mut(); let wc = test_workspace.workspace.working_copy_mut();
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let new_tree_id1 = locked_wc let new_tree_id1 = locked_wc
.snapshot(SnapshotOptions::empty_for_test()) .snapshot(SnapshotOptions::empty_for_test())
.unwrap(); .unwrap();
@ -584,7 +584,7 @@ fn test_gitignores(use_git: bool) {
testutils::write_working_copy_file(&workspace_root, &subdir_modified_path, "2"); testutils::write_working_copy_file(&workspace_root, &subdir_modified_path, "2");
testutils::write_working_copy_file(&workspace_root, &subdir_ignored_path, "2"); testutils::write_working_copy_file(&workspace_root, &subdir_ignored_path, "2");
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let new_tree_id2 = locked_wc let new_tree_id2 = locked_wc
.snapshot(SnapshotOptions::empty_for_test()) .snapshot(SnapshotOptions::empty_for_test())
.unwrap(); .unwrap();
@ -675,7 +675,7 @@ fn test_gitignores_ignored_directory_already_tracked(use_git: bool) {
// Check that the file is still in the tree created by snapshotting the working // Check that the file is still in the tree created by snapshotting the working
// copy (that it didn't get removed because the directory is ignored) // copy (that it didn't get removed because the directory is ignored)
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let new_tree_id = locked_wc let new_tree_id = locked_wc
.snapshot(SnapshotOptions::empty_for_test()) .snapshot(SnapshotOptions::empty_for_test())
.unwrap(); .unwrap();
@ -707,7 +707,11 @@ fn test_dotgit_ignored(use_git: bool) {
&RepoPath::from_internal_string(".git/file"), &RepoPath::from_internal_string(".git/file"),
"contents", "contents",
); );
let mut locked_wc = test_workspace.workspace.working_copy_mut().start_mutation(); let mut locked_wc = test_workspace
.workspace
.working_copy_mut()
.start_mutation()
.unwrap();
let new_tree_id = locked_wc let new_tree_id = locked_wc
.snapshot(SnapshotOptions::empty_for_test()) .snapshot(SnapshotOptions::empty_for_test())
.unwrap(); .unwrap();
@ -721,7 +725,11 @@ fn test_dotgit_ignored(use_git: bool) {
&RepoPath::from_internal_string(".git"), &RepoPath::from_internal_string(".git"),
"contents", "contents",
); );
let mut locked_wc = test_workspace.workspace.working_copy_mut().start_mutation(); let mut locked_wc = test_workspace
.workspace
.working_copy_mut()
.start_mutation()
.unwrap();
let new_tree_id = locked_wc let new_tree_id = locked_wc
.snapshot(SnapshotOptions::empty_for_test()) .snapshot(SnapshotOptions::empty_for_test())
.unwrap(); .unwrap();
@ -777,7 +785,7 @@ fn test_gitsubmodule() {
// Check that the files present in the submodule are not tracked // Check that the files present in the submodule are not tracked
// when we snapshot // when we snapshot
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let new_tree_id = locked_wc let new_tree_id = locked_wc
.snapshot(SnapshotOptions::empty_for_test()) .snapshot(SnapshotOptions::empty_for_test())
.unwrap(); .unwrap();
@ -831,7 +839,7 @@ fn test_fsmonitor() {
let workspace_root = test_workspace.workspace.workspace_root().clone(); let workspace_root = test_workspace.workspace.workspace_root().clone();
let wc = test_workspace.workspace.working_copy_mut(); let wc = test_workspace.workspace.working_copy_mut();
assert_eq!(wc.sparse_patterns(), vec![RepoPath::root()]); assert_eq!(wc.sparse_patterns().unwrap(), vec![RepoPath::root()]);
let foo_path = RepoPath::from_internal_string("foo"); let foo_path = RepoPath::from_internal_string("foo");
let bar_path = RepoPath::from_internal_string("bar"); let bar_path = RepoPath::from_internal_string("bar");
@ -861,14 +869,14 @@ fn test_fsmonitor() {
}; };
{ {
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let tree_id = snapshot(&mut locked_wc, &[]); let tree_id = snapshot(&mut locked_wc, &[]);
assert_eq!(tree_id, *repo.store().empty_tree_id()); assert_eq!(tree_id, *repo.store().empty_tree_id());
locked_wc.discard(); locked_wc.discard();
} }
{ {
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let tree_id = snapshot(&mut locked_wc, &[&foo_path]); let tree_id = snapshot(&mut locked_wc, &[&foo_path]);
insta::assert_snapshot!(testutils::dump_tree(repo.store(), &tree_id), @r###" insta::assert_snapshot!(testutils::dump_tree(repo.store(), &tree_id), @r###"
tree 205f6b799e7d5c2524468ca006a0131aa57ecce7 tree 205f6b799e7d5c2524468ca006a0131aa57ecce7
@ -878,7 +886,7 @@ fn test_fsmonitor() {
} }
{ {
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let tree_id = snapshot( let tree_id = snapshot(
&mut locked_wc, &mut locked_wc,
&[&foo_path, &bar_path, &nested_path, &ignored_path], &[&foo_path, &bar_path, &nested_path, &ignored_path],
@ -895,7 +903,7 @@ fn test_fsmonitor() {
{ {
testutils::write_working_copy_file(&workspace_root, &foo_path, "updated foo\n"); testutils::write_working_copy_file(&workspace_root, &foo_path, "updated foo\n");
testutils::write_working_copy_file(&workspace_root, &bar_path, "updated bar\n"); testutils::write_working_copy_file(&workspace_root, &bar_path, "updated bar\n");
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let tree_id = snapshot(&mut locked_wc, &[&foo_path]); let tree_id = snapshot(&mut locked_wc, &[&foo_path]);
insta::assert_snapshot!(testutils::dump_tree(repo.store(), &tree_id), @r###" insta::assert_snapshot!(testutils::dump_tree(repo.store(), &tree_id), @r###"
tree 2f57ab8f48ae62e3137079f2add9878dfa1d1bcc tree 2f57ab8f48ae62e3137079f2add9878dfa1d1bcc
@ -908,7 +916,7 @@ fn test_fsmonitor() {
{ {
std::fs::remove_file(foo_path.to_fs_path(&workspace_root)).unwrap(); std::fs::remove_file(foo_path.to_fs_path(&workspace_root)).unwrap();
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let tree_id = snapshot(&mut locked_wc, &[&foo_path]); let tree_id = snapshot(&mut locked_wc, &[&foo_path]);
insta::assert_snapshot!(testutils::dump_tree(repo.store(), &tree_id), @r###" insta::assert_snapshot!(testutils::dump_tree(repo.store(), &tree_id), @r###"
tree 34b83765131477e1a7d72160079daec12c6144e3 tree 34b83765131477e1a7d72160079daec12c6144e3

View File

@ -72,7 +72,10 @@ fn test_concurrent_checkout(use_git: bool) {
// Check that the tree2 is still checked out on disk. // Check that the tree2 is still checked out on disk.
let workspace3 = let workspace3 =
Workspace::load(&settings, &workspace1_root, &StoreFactories::default()).unwrap(); Workspace::load(&settings, &workspace1_root, &StoreFactories::default()).unwrap();
assert_eq!(workspace3.working_copy().current_tree_id(), &tree_id2); assert_eq!(
workspace3.working_copy().current_tree_id().unwrap(),
&tree_id2
);
} }
#[test_case(false ; "local backend")] #[test_case(false ; "local backend")]
@ -133,7 +136,7 @@ fn test_checkout_parallel(use_git: bool) {
// different tree than the one we just checked out, but since // different tree than the one we just checked out, but since
// write_tree() should take the same lock as check_out(), write_tree() // write_tree() should take the same lock as check_out(), write_tree()
// should never produce a different tree. // should never produce a different tree.
let mut locked_wc = workspace.working_copy_mut().start_mutation(); let mut locked_wc = workspace.working_copy_mut().start_mutation().unwrap();
let new_tree_id = locked_wc let new_tree_id = locked_wc
.snapshot(SnapshotOptions::empty_for_test()) .snapshot(SnapshotOptions::empty_for_test())
.unwrap(); .unwrap();

View File

@ -52,7 +52,7 @@ fn test_sparse_checkout() {
wc.check_out(repo.op_id().clone(), None, &tree).unwrap(); wc.check_out(repo.op_id().clone(), None, &tree).unwrap();
// Set sparse patterns to only dir1/ // Set sparse patterns to only dir1/
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let sparse_patterns = vec![dir1_path]; let sparse_patterns = vec![dir1_path];
let stats = locked_wc let stats = locked_wc
.set_sparse_patterns(sparse_patterns.clone()) .set_sparse_patterns(sparse_patterns.clone())
@ -65,7 +65,7 @@ fn test_sparse_checkout() {
removed_files: 3 removed_files: 3
} }
); );
assert_eq!(locked_wc.sparse_patterns(), sparse_patterns); assert_eq!(locked_wc.sparse_patterns().unwrap(), sparse_patterns);
assert!(!root_file1_path.to_fs_path(&working_copy_path).exists()); assert!(!root_file1_path.to_fs_path(&working_copy_path).exists());
assert!(!root_file2_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_file1_path.to_fs_path(&working_copy_path).exists());
@ -78,10 +78,10 @@ fn test_sparse_checkout() {
// Write the new state to disk // Write the new state to disk
locked_wc.finish(repo.op_id().clone()).unwrap(); locked_wc.finish(repo.op_id().clone()).unwrap();
assert_eq!( assert_eq!(
wc.file_states().keys().collect_vec(), wc.file_states().unwrap().keys().collect_vec(),
vec![&dir1_file1_path, &dir1_file2_path, &dir1_subdir1_file1_path] vec![&dir1_file1_path, &dir1_file2_path, &dir1_subdir1_file1_path]
); );
assert_eq!(wc.sparse_patterns(), sparse_patterns); assert_eq!(wc.sparse_patterns().unwrap(), sparse_patterns);
// Reload the state to check that it was persisted // Reload the state to check that it was persisted
let mut wc = WorkingCopy::load( let mut wc = WorkingCopy::load(
@ -90,13 +90,13 @@ fn test_sparse_checkout() {
wc.state_path().to_path_buf(), wc.state_path().to_path_buf(),
); );
assert_eq!( assert_eq!(
wc.file_states().keys().collect_vec(), wc.file_states().unwrap().keys().collect_vec(),
vec![&dir1_file1_path, &dir1_file2_path, &dir1_subdir1_file1_path] vec![&dir1_file1_path, &dir1_file2_path, &dir1_subdir1_file1_path]
); );
assert_eq!(wc.sparse_patterns(), sparse_patterns); assert_eq!(wc.sparse_patterns().unwrap(), sparse_patterns);
// Set sparse patterns to file2, dir1/subdir1/ and dir2/ // Set sparse patterns to file2, dir1/subdir1/ and dir2/
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let sparse_patterns = vec![root_file1_path.clone(), dir1_subdir1_path, dir2_path]; let sparse_patterns = vec![root_file1_path.clone(), dir1_subdir1_path, dir2_path];
let stats = locked_wc let stats = locked_wc
.set_sparse_patterns(sparse_patterns.clone()) .set_sparse_patterns(sparse_patterns.clone())
@ -109,7 +109,7 @@ fn test_sparse_checkout() {
removed_files: 2 removed_files: 2
} }
); );
assert_eq!(locked_wc.sparse_patterns(), sparse_patterns); assert_eq!(locked_wc.sparse_patterns().unwrap(), sparse_patterns);
assert!(root_file1_path.to_fs_path(&working_copy_path).exists()); assert!(root_file1_path.to_fs_path(&working_copy_path).exists());
assert!(!root_file2_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_file1_path.to_fs_path(&working_copy_path).exists());
@ -120,7 +120,7 @@ fn test_sparse_checkout() {
assert!(dir2_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()).unwrap(); locked_wc.finish(repo.op_id().clone()).unwrap();
assert_eq!( assert_eq!(
wc.file_states().keys().collect_vec(), wc.file_states().unwrap().keys().collect_vec(),
vec![&dir1_subdir1_file1_path, &dir2_file1_path, &root_file1_path] vec![&dir1_subdir1_file1_path, &dir2_file1_path, &root_file1_path]
); );
} }
@ -152,7 +152,7 @@ fn test_sparse_commit() {
wc.check_out(repo.op_id().clone(), None, &tree).unwrap(); wc.check_out(repo.op_id().clone(), None, &tree).unwrap();
// Set sparse patterns to only dir1/ // Set sparse patterns to only dir1/
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let sparse_patterns = vec![dir1_path.clone()]; let sparse_patterns = vec![dir1_path.clone()];
locked_wc.set_sparse_patterns(sparse_patterns).unwrap(); locked_wc.set_sparse_patterns(sparse_patterns).unwrap();
locked_wc.finish(repo.op_id().clone()).unwrap(); locked_wc.finish(repo.op_id().clone()).unwrap();
@ -166,7 +166,7 @@ fn test_sparse_commit() {
// Create a tree from the working copy. Only dir1/file1 should be updated in the // Create a tree from the working copy. Only dir1/file1 should be updated in the
// tree. // tree.
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let modified_tree_id = locked_wc let modified_tree_id = locked_wc
.snapshot(SnapshotOptions::empty_for_test()) .snapshot(SnapshotOptions::empty_for_test())
.unwrap(); .unwrap();
@ -180,14 +180,14 @@ fn test_sparse_commit() {
assert_eq!(diff[0].0, dir1_file1_path); assert_eq!(diff[0].0, dir1_file1_path);
// Set sparse patterns to also include dir2/ // Set sparse patterns to also include dir2/
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let sparse_patterns = vec![dir1_path, dir2_path]; let sparse_patterns = vec![dir1_path, dir2_path];
locked_wc.set_sparse_patterns(sparse_patterns).unwrap(); locked_wc.set_sparse_patterns(sparse_patterns).unwrap();
locked_wc.finish(repo.op_id().clone()).unwrap(); locked_wc.finish(repo.op_id().clone()).unwrap();
// Create a tree from the working copy. Only dir1/file1 and dir2/file1 should be // Create a tree from the working copy. Only dir1/file1 and dir2/file1 should be
// updated in the tree. // updated in the tree.
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let modified_tree_id = locked_wc let modified_tree_id = locked_wc
.snapshot(SnapshotOptions::empty_for_test()) .snapshot(SnapshotOptions::empty_for_test())
.unwrap(); .unwrap();
@ -217,7 +217,7 @@ fn test_sparse_commit_gitignore() {
let wc = test_workspace.workspace.working_copy_mut(); let wc = test_workspace.workspace.working_copy_mut();
// Set sparse patterns to only dir1/ // Set sparse patterns to only dir1/
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let sparse_patterns = vec![dir1_path.clone()]; let sparse_patterns = vec![dir1_path.clone()];
locked_wc.set_sparse_patterns(sparse_patterns).unwrap(); locked_wc.set_sparse_patterns(sparse_patterns).unwrap();
locked_wc.finish(repo.op_id().clone()).unwrap(); locked_wc.finish(repo.op_id().clone()).unwrap();
@ -230,7 +230,7 @@ fn test_sparse_commit_gitignore() {
// Create a tree from the working copy. Only dir1/file2 should be updated in the // Create a tree from the working copy. Only dir1/file2 should be updated in the
// tree because dir1/file1 is ignored. // tree because dir1/file1 is ignored.
let mut locked_wc = wc.start_mutation(); let mut locked_wc = wc.start_mutation().unwrap();
let modified_tree_id = locked_wc let modified_tree_id = locked_wc
.snapshot(SnapshotOptions::empty_for_test()) .snapshot(SnapshotOptions::empty_for_test())
.unwrap(); .unwrap();

View File

@ -749,7 +749,7 @@ impl WorkspaceCommandHelper {
tx.mut_repo() tx.mut_repo()
.check_out(workspace_id, &self.settings, &new_git_head_commit)?; .check_out(workspace_id, &self.settings, &new_git_head_commit)?;
let mut locked_working_copy = let mut locked_working_copy =
self.workspace.working_copy_mut().start_mutation(); self.workspace.working_copy_mut().start_mutation()?;
// The working copy was presumably updated by the git command that updated // The working copy was presumably updated by the git command that updated
// HEAD, so we just need to reset our working copy // HEAD, so we just need to reset our working copy
// state to it without updating working copy files. // state to it without updating working copy files.
@ -825,7 +825,7 @@ impl WorkspaceCommandHelper {
return Err(user_error("Nothing checked out in this workspace")); return Err(user_error("Nothing checked out in this workspace"));
}; };
let locked_working_copy = self.workspace.working_copy_mut().start_mutation(); let locked_working_copy = self.workspace.working_copy_mut().start_mutation()?;
Ok((locked_working_copy, wc_commit)) Ok((locked_working_copy, wc_commit))
} }
@ -1104,7 +1104,7 @@ impl WorkspaceCommandHelper {
let base_ignores = self.base_ignores(); let base_ignores = self.base_ignores();
// Compare working-copy tree and operation with repo's, and reload as needed. // Compare working-copy tree and operation with repo's, and reload as needed.
let mut locked_wc = self.workspace.working_copy_mut().start_mutation(); let mut locked_wc = self.workspace.working_copy_mut().start_mutation()?;
let old_op_id = locked_wc.old_operation_id().clone(); let old_op_id = locked_wc.old_operation_id().clone();
let (repo, wc_commit) = match check_stale_working_copy(&locked_wc, &wc_commit, &repo) { let (repo, wc_commit) = match check_stale_working_copy(&locked_wc, &wc_commit, &repo) {
Ok(None) => (repo, wc_commit), Ok(None) => (repo, wc_commit),
@ -1816,7 +1816,7 @@ pub fn update_working_copy(
Some(stats) Some(stats)
} else { } else {
// Record new operation id which represents the latest working-copy state // Record new operation id which represents the latest working-copy state
let locked_wc = wc.start_mutation(); let locked_wc = wc.start_mutation()?;
locked_wc.finish(repo.op_id().clone())?; locked_wc.finish(repo.op_id().clone())?;
None None
}; };

View File

@ -103,8 +103,8 @@ pub fn cmd_debug(
let workspace_command = command.workspace_helper(ui)?; let workspace_command = command.workspace_helper(ui)?;
let wc = workspace_command.working_copy(); let wc = workspace_command.working_copy();
writeln!(ui, "Current operation: {:?}", wc.operation_id())?; writeln!(ui, "Current operation: {:?}", wc.operation_id())?;
writeln!(ui, "Current tree: {:?}", wc.current_tree_id())?; writeln!(ui, "Current tree: {:?}", wc.current_tree_id()?)?;
for (file, state) in wc.file_states() { for (file, state) in wc.file_states()? {
writeln!( writeln!(
ui, ui,
"{:?} {:13?} {:10?} {:?}", "{:?} {:13?} {:10?} {:?}",
@ -238,13 +238,11 @@ fn cmd_debug_watchman(
let repo = workspace_command.repo().clone(); let repo = workspace_command.repo().clone();
match subcommand { match subcommand {
DebugWatchmanSubcommand::QueryClock => { DebugWatchmanSubcommand::QueryClock => {
let (clock, _changed_files) = let (clock, _changed_files) = workspace_command.working_copy().query_watchman()?;
workspace_command.working_copy().query_watchman().unwrap();
ui.write(&format!("Clock: {clock:?}"))?; ui.write(&format!("Clock: {clock:?}"))?;
} }
DebugWatchmanSubcommand::QueryChangedFiles => { DebugWatchmanSubcommand::QueryChangedFiles => {
let (_clock, changed_files) = let (_clock, changed_files) = workspace_command.working_copy().query_watchman()?;
workspace_command.working_copy().query_watchman().unwrap();
ui.write(&format!("Changed files: {changed_files:?}"))?; ui.write(&format!("Changed files: {changed_files:?}"))?;
} }
DebugWatchmanSubcommand::ResetClock => { DebugWatchmanSubcommand::ResetClock => {

View File

@ -3559,7 +3559,7 @@ fn cmd_sparse_list(
_args: &SparseListArgs, _args: &SparseListArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let workspace_command = command.workspace_helper(ui)?; let workspace_command = command.workspace_helper(ui)?;
for path in workspace_command.working_copy().sparse_patterns() { for path in workspace_command.working_copy().sparse_patterns()? {
let ui_path = workspace_command.format_file_path(path); let ui_path = workspace_command.format_file_path(path);
writeln!(ui, "{ui_path}")?; writeln!(ui, "{ui_path}")?;
} }
@ -3596,7 +3596,7 @@ fn cmd_sparse_set(
new_patterns.insert(RepoPath::root()); new_patterns.insert(RepoPath::root());
} else { } else {
if !args.clear { if !args.clear {
new_patterns.extend(locked_wc.sparse_patterns().iter().cloned()); new_patterns.extend(locked_wc.sparse_patterns()?.iter().cloned());
for path in paths_to_remove { for path in paths_to_remove {
new_patterns.remove(&path); new_patterns.remove(&path);
} }