From ccbb34fddb301f9de1743eabd0486dde0aa77ea9 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Fri, 5 May 2023 16:55:33 -0700 Subject: [PATCH] working_copy: introduce snapshot progress callback --- lib/src/working_copy.rs | 19 ++++++++++++--- lib/tests/test_working_copy.rs | 28 +++++++++++------------ lib/tests/test_working_copy_concurrent.rs | 2 +- lib/tests/test_working_copy_sparse.rs | 6 ++--- src/cli_util.rs | 2 +- src/commands/mod.rs | 2 +- src/merge_tools.rs | 2 +- 7 files changed, 37 insertions(+), 24 deletions(-) diff --git a/lib/src/working_copy.rs b/lib/src/working_copy.rs index 6bac9ee39..7aa63e885 100644 --- a/lib/src/working_copy.rs +++ b/lib/src/working_copy.rs @@ -482,7 +482,11 @@ impl TreeState { /// Look for changes to the working copy. If there are any changes, create /// a new tree from it. - pub fn snapshot(&mut self, base_ignores: Arc) -> Result { + pub fn snapshot( + &mut self, + base_ignores: Arc, + progress: Option<&SnapshotProgress<'_>>, + ) -> Result { let sparse_matcher = self.sparse_matcher(); let mut work = vec![( RepoPath::root(), @@ -535,6 +539,9 @@ impl TreeState { } else { deleted_files.remove(&sub_path); if sparse_matcher.matches(&sub_path) { + if let Some(progress) = progress { + progress(&sub_path); + } self.update_file_state( sub_path, &entry, @@ -1211,9 +1218,13 @@ impl LockedWorkingCopy<'_> { // The base_ignores are passed in here rather than being set on the TreeState // because the TreeState may be long-lived if the library is used in a // long-lived process. - pub fn snapshot(&mut self, base_ignores: Arc) -> Result { + pub fn snapshot( + &mut self, + base_ignores: Arc, + progress: Option<&SnapshotProgress<'_>>, + ) -> Result { let tree_state = self.wc.tree_state_mut(); - self.tree_state_dirty |= tree_state.snapshot(base_ignores)?; + self.tree_state_dirty |= tree_state.snapshot(base_ignores, progress)?; Ok(tree_state.current_tree_id().clone()) } @@ -1278,3 +1289,5 @@ impl Drop for LockedWorkingCopy<'_> { } } } + +pub type SnapshotProgress<'a> = dyn Fn(&RepoPath) + 'a; diff --git a/lib/tests/test_working_copy.rs b/lib/tests/test_working_copy.rs index 609b77e77..830d4302e 100644 --- a/lib/tests/test_working_copy.rs +++ b/lib/tests/test_working_copy.rs @@ -45,7 +45,7 @@ fn test_root(use_git: bool) { let wc = test_workspace.workspace.working_copy_mut(); assert_eq!(wc.sparse_patterns(), vec![RepoPath::root()]); let mut locked_wc = wc.start_mutation(); - let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap(); + let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty(), None).unwrap(); locked_wc.discard(); let wc_commit_id = repo .view() @@ -209,7 +209,7 @@ fn test_checkout_file_transitions(use_git: bool) { // Check that the working copy is clean. let mut locked_wc = wc.start_mutation(); - let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap(); + let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty(), None).unwrap(); locked_wc.discard(); assert_eq!(new_tree_id, right_tree_id); @@ -307,7 +307,7 @@ fn test_reset() { assert!(ignored_path.to_fs_path(&workspace_root).is_file()); assert!(!wc.file_states().contains_key(&ignored_path)); let mut locked_wc = wc.start_mutation(); - let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap(); + let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty(), None).unwrap(); assert_eq!(new_tree_id, *tree_without_file.id()); locked_wc.discard(); @@ -320,7 +320,7 @@ fn test_reset() { assert!(ignored_path.to_fs_path(&workspace_root).is_file()); assert!(!wc.file_states().contains_key(&ignored_path)); let mut locked_wc = wc.start_mutation(); - let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap(); + let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty(), None).unwrap(); assert_eq!(new_tree_id, *tree_without_file.id()); locked_wc.discard(); @@ -332,7 +332,7 @@ fn test_reset() { assert!(ignored_path.to_fs_path(&workspace_root).is_file()); assert!(wc.file_states().contains_key(&ignored_path)); let mut locked_wc = wc.start_mutation(); - let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap(); + let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty(), None).unwrap(); assert_eq!(new_tree_id, *tree_with_file.id()); locked_wc.discard(); } @@ -408,7 +408,7 @@ fn test_snapshot_racy_timestamps(use_git: bool) { file.write_all(format!("contents {i}").as_bytes()).unwrap(); } let mut locked_wc = wc.start_mutation(); - let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap(); + let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty(), None).unwrap(); locked_wc.discard(); assert_ne!(new_tree_id, previous_tree_id); previous_tree_id = new_tree_id; @@ -440,7 +440,7 @@ fn test_snapshot_special_file() { // Snapshot the working copy with the socket file let wc = test_workspace.workspace.working_copy_mut(); let mut locked_wc = wc.start_mutation(); - let tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap(); + let tree_id = locked_wc.snapshot(GitIgnoreFile::empty(), None).unwrap(); locked_wc.finish(OperationId::from_hex("abc123")); let tree = store.get_tree(&RepoPath::root(), &tree_id).unwrap(); // Only the regular files should be in the tree @@ -457,7 +457,7 @@ fn test_snapshot_special_file() { std::fs::remove_file(&file1_disk_path).unwrap(); UnixListener::bind(&file1_disk_path).unwrap(); let mut locked_wc = wc.start_mutation(); - let tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap(); + let tree_id = locked_wc.snapshot(GitIgnoreFile::empty(), None).unwrap(); locked_wc.finish(OperationId::from_hex("abc123")); let tree = store.get_tree(&RepoPath::root(), &tree_id).unwrap(); // Only the regular file should be in the tree @@ -497,7 +497,7 @@ fn test_gitignores(use_git: bool) { let wc = test_workspace.workspace.working_copy_mut(); let mut locked_wc = wc.start_mutation(); - let new_tree_id1 = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap(); + let new_tree_id1 = locked_wc.snapshot(GitIgnoreFile::empty(), None).unwrap(); locked_wc.finish(repo.op_id().clone()); let tree1 = repo .store() @@ -527,7 +527,7 @@ fn test_gitignores(use_git: bool) { testutils::write_working_copy_file(&workspace_root, &subdir_ignored_path, "2"); let mut locked_wc = wc.start_mutation(); - let new_tree_id2 = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap(); + let new_tree_id2 = locked_wc.snapshot(GitIgnoreFile::empty(), None).unwrap(); locked_wc.discard(); let tree2 = repo .store() @@ -616,7 +616,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 // copy (that it didn't get removed because the directory is ignored) let mut locked_wc = wc.start_mutation(); - let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap(); + let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty(), None).unwrap(); locked_wc.discard(); let new_tree = repo .store() @@ -646,7 +646,7 @@ fn test_dotgit_ignored(use_git: bool) { "contents", ); let mut locked_wc = test_workspace.workspace.working_copy_mut().start_mutation(); - let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap(); + let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty(), None).unwrap(); assert_eq!(new_tree_id, *repo.store().empty_tree_id()); locked_wc.discard(); std::fs::remove_dir_all(&dotgit_path).unwrap(); @@ -658,7 +658,7 @@ fn test_dotgit_ignored(use_git: bool) { "contents", ); let mut locked_wc = test_workspace.workspace.working_copy_mut().start_mutation(); - let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap(); + let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty(), None).unwrap(); assert_eq!(new_tree_id, *repo.store().empty_tree_id()); locked_wc.discard(); } @@ -712,7 +712,7 @@ fn test_gitsubmodule() { // Check that the files present in the submodule are not tracked // when we snapshot let mut locked_wc = wc.start_mutation(); - let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap(); + let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty(), None).unwrap(); locked_wc.discard(); assert_eq!(new_tree_id, tree_id); diff --git a/lib/tests/test_working_copy_concurrent.rs b/lib/tests/test_working_copy_concurrent.rs index 2b36864a9..d0cba1b9a 100644 --- a/lib/tests/test_working_copy_concurrent.rs +++ b/lib/tests/test_working_copy_concurrent.rs @@ -134,7 +134,7 @@ fn test_checkout_parallel(use_git: bool) { // write_tree() should take the same lock as check_out(), write_tree() // should never produce a different tree. let mut locked_wc = workspace.working_copy_mut().start_mutation(); - let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap(); + let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty(), None).unwrap(); locked_wc.discard(); assert!(tree_ids.contains(&new_tree_id)); }); diff --git a/lib/tests/test_working_copy_sparse.rs b/lib/tests/test_working_copy_sparse.rs index 43eb3a85f..5a263c1f7 100644 --- a/lib/tests/test_working_copy_sparse.rs +++ b/lib/tests/test_working_copy_sparse.rs @@ -168,7 +168,7 @@ fn test_sparse_commit() { // 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()).unwrap(); + let modified_tree_id = locked_wc.snapshot(GitIgnoreFile::empty(), None).unwrap(); locked_wc.finish(repo.op_id().clone()); let modified_tree = repo .store() @@ -187,7 +187,7 @@ fn test_sparse_commit() { // 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()).unwrap(); + let modified_tree_id = locked_wc.snapshot(GitIgnoreFile::empty(), None).unwrap(); locked_wc.finish(repo.op_id().clone()); let modified_tree = repo .store() @@ -228,7 +228,7 @@ fn test_sparse_commit_gitignore() { // 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()).unwrap(); + let modified_tree_id = locked_wc.snapshot(GitIgnoreFile::empty(), None).unwrap(); locked_wc.finish(repo.op_id().clone()); let modified_tree = repo .store() diff --git a/src/cli_util.rs b/src/cli_util.rs index 0e4bba3a3..ac1941a53 100644 --- a/src/cli_util.rs +++ b/src/cli_util.rs @@ -1000,7 +1000,7 @@ impl WorkspaceCommandHelper { ))); } }; - let new_tree_id = locked_wc.snapshot(base_ignores)?; + let new_tree_id = locked_wc.snapshot(base_ignores, None)?; if new_tree_id != *wc_commit.tree_id() { let mut tx = start_repo_transaction( &self.repo, diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 5b1b9691a..97e604aa9 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1236,7 +1236,7 @@ fn cmd_untrack( locked_working_copy.reset(&new_tree)?; // Commit the working copy again so we can inform the user if paths couldn't be // untracked because they're not ignored. - let wc_tree_id = locked_working_copy.snapshot(base_ignores)?; + let wc_tree_id = locked_working_copy.snapshot(base_ignores, None)?; if wc_tree_id != new_tree_id { let wc_tree = store.get_tree(&RepoPath::root(), &wc_tree_id)?; let added_back = wc_tree.entries_matching(matcher.as_ref()).collect_vec(); diff --git a/src/merge_tools.rs b/src/merge_tools.rs index efc6b9b02..c37fd91d9 100644 --- a/src/merge_tools.rs +++ b/src/merge_tools.rs @@ -379,7 +379,7 @@ pub fn edit_diff( std::fs::remove_file(instructions_path).ok(); } - right_tree_state.snapshot(base_ignores)?; + right_tree_state.snapshot(base_ignores, None)?; Ok(right_tree_state.current_tree_id().clone()) }