Skip to content

Commit

Permalink
working_copy: plumbing to propagate untracked paths to caller
Browse files Browse the repository at this point in the history
  • Loading branch information
yuja committed Dec 11, 2024
1 parent e1936a2 commit f4fdc19
Show file tree
Hide file tree
Showing 9 changed files with 70 additions and 21 deletions.
6 changes: 5 additions & 1 deletion cli/examples/custom-working-copy/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ use jj_lib::working_copy::LockedWorkingCopy;
use jj_lib::working_copy::ResetError;
use jj_lib::working_copy::SnapshotError;
use jj_lib::working_copy::SnapshotOptions;
use jj_lib::working_copy::SnapshotStats;
use jj_lib::working_copy::WorkingCopy;
use jj_lib::working_copy::WorkingCopyFactory;
use jj_lib::working_copy::WorkingCopyStateError;
Expand Down Expand Up @@ -233,7 +234,10 @@ impl LockedWorkingCopy for LockedConflictsWorkingCopy {
self.inner.old_tree_id()
}

fn snapshot(&mut self, options: &SnapshotOptions) -> Result<MergedTreeId, SnapshotError> {
fn snapshot(
&mut self,
options: &SnapshotOptions,
) -> Result<(MergedTreeId, SnapshotStats), SnapshotError> {
let options = SnapshotOptions {
base_ignores: options.base_ignores.chain("", "/.conflicts".as_bytes())?,
..options.clone()
Expand Down
3 changes: 2 additions & 1 deletion cli/src/cli_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1806,7 +1806,8 @@ See https://martinvonz.github.io/jj/latest/working-copy/#stale-working-copy \
};
self.user_repo = ReadonlyUserRepo::new(repo);
let progress = crate::progress::snapshot_progress(ui);
let new_tree_id = locked_ws
// TODO: print stats
let (new_tree_id, _stats) = locked_ws
.locked_wc()
.snapshot(&SnapshotOptions {
base_ignores,
Expand Down
3 changes: 2 additions & 1 deletion cli/src/commands/file/track.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ pub(crate) fn cmd_file_track(
let mut tx = workspace_command.start_transaction().into_inner();
let base_ignores = workspace_command.base_ignores()?;
let (mut locked_ws, _wc_commit) = workspace_command.start_working_copy_mutation()?;
locked_ws.locked_wc().snapshot(&SnapshotOptions {
// TODO: print stats
let (_tree_id, _stats) = locked_ws.locked_wc().snapshot(&SnapshotOptions {
base_ignores,
fsmonitor_settings: command.settings().fsmonitor_settings()?,
progress: None,
Expand Down
3 changes: 2 additions & 1 deletion cli/src/commands/file/untrack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ pub(crate) fn cmd_file_untrack(
locked_ws.locked_wc().reset(&new_commit)?;
// 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_ws.locked_wc().snapshot(&SnapshotOptions {
// TODO: print stats
let (wc_tree_id, _stats) = locked_ws.locked_wc().snapshot(&SnapshotOptions {
base_ignores,
fsmonitor_settings: command.settings().fsmonitor_settings()?,
progress: None,
Expand Down
28 changes: 22 additions & 6 deletions lib/src/local_working_copy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ use crate::working_copy::ResetError;
use crate::working_copy::SnapshotError;
use crate::working_copy::SnapshotOptions;
use crate::working_copy::SnapshotProgress;
use crate::working_copy::SnapshotStats;
use crate::working_copy::UntrackedReason;
use crate::working_copy::WorkingCopy;
use crate::working_copy::WorkingCopyFactory;
use crate::working_copy::WorkingCopyStateError;
Expand Down Expand Up @@ -908,7 +910,10 @@ impl TreeState {
/// Look for changes to the working copy. If there are any changes, create
/// a new tree from it.
#[instrument(skip_all)]
pub fn snapshot(&mut self, options: &SnapshotOptions) -> Result<bool, SnapshotError> {
pub fn snapshot(
&mut self,
options: &SnapshotOptions,
) -> Result<(bool, SnapshotStats), SnapshotError> {
let &SnapshotOptions {
ref base_ignores,
ref fsmonitor_settings,
Expand All @@ -935,11 +940,12 @@ impl TreeState {
if matcher.visit(RepoPath::root()).is_nothing() {
// No need to load the current tree, set up channels, etc.
self.watchman_clock = watchman_clock;
return Ok(is_dirty);
return Ok((is_dirty, SnapshotStats::default()));
}

let (tree_entries_tx, tree_entries_rx) = channel();
let (file_states_tx, file_states_rx) = channel();
let (untracked_paths_tx, untracked_paths_rx) = channel();
let (deleted_files_tx, deleted_files_rx) = channel();

trace_span!("traverse filesystem").in_scope(|| -> Result<(), SnapshotError> {
Expand All @@ -951,6 +957,7 @@ impl TreeState {
// Move tx sides so they'll be dropped at the end of the scope.
tree_entries_tx,
file_states_tx,
untracked_paths_tx,
deleted_files_tx,
error: OnceLock::new(),
progress,
Expand All @@ -972,6 +979,9 @@ impl TreeState {
snapshotter.into_result()
})?;

let stats = SnapshotStats {
untracked_paths: untracked_paths_rx.into_iter().collect(),
};
let mut tree_builder = MergedTreeBuilder::new(self.tree_id.clone());
trace_span!("process tree entries").in_scope(|| {
for (path, tree_values) in &tree_entries_rx {
Expand Down Expand Up @@ -1011,7 +1021,7 @@ impl TreeState {
assert_eq!(state_paths, tree_paths);
}
self.watchman_clock = watchman_clock;
Ok(is_dirty)
Ok((is_dirty, stats))
}

#[instrument(skip_all)]
Expand Down Expand Up @@ -1087,6 +1097,8 @@ struct FileSnapshotter<'a> {
start_tracking_matcher: &'a dyn Matcher,
tree_entries_tx: Sender<(RepoPathBuf, MergedTreeValue)>,
file_states_tx: Sender<(RepoPathBuf, FileState)>,
#[allow(unused)] // TODO
untracked_paths_tx: Sender<(RepoPathBuf, UntrackedReason)>,
deleted_files_tx: Sender<RepoPathBuf>,
error: OnceLock<SnapshotError>,
progress: Option<&'a SnapshotProgress<'a>>,
Expand Down Expand Up @@ -2150,16 +2162,20 @@ impl LockedWorkingCopy for LockedLocalWorkingCopy {
&self.old_tree_id
}

fn snapshot(&mut self, options: &SnapshotOptions) -> Result<MergedTreeId, SnapshotError> {
fn snapshot(
&mut self,
options: &SnapshotOptions,
) -> Result<(MergedTreeId, SnapshotStats), SnapshotError> {
let tree_state = self
.wc
.tree_state_mut()
.map_err(|err| SnapshotError::Other {
message: "Failed to read the working copy state".to_string(),
err: err.into(),
})?;
self.tree_state_dirty |= tree_state.snapshot(options)?;
Ok(tree_state.current_tree_id().clone())
let (is_dirty, stats) = tree_state.snapshot(options)?;
self.tree_state_dirty |= is_dirty;
Ok((tree_state.current_tree_id().clone(), stats))
}

fn check_out(
Expand Down
27 changes: 25 additions & 2 deletions lib/src/working_copy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
//! default local-disk implementation.
use std::any::Any;
use std::collections::BTreeMap;
use std::ffi::OsString;
use std::path::PathBuf;
use std::sync::Arc;
Expand Down Expand Up @@ -113,8 +114,11 @@ pub trait LockedWorkingCopy {
/// The tree at the time the lock was taken
fn old_tree_id(&self) -> &MergedTreeId;

/// Snapshot the working copy and return the tree id.
fn snapshot(&mut self, options: &SnapshotOptions) -> Result<MergedTreeId, SnapshotError>;
/// Snapshot the working copy. Returns the tree id and stats.
fn snapshot(
&mut self,
options: &SnapshotOptions,
) -> Result<(MergedTreeId, SnapshotStats), SnapshotError>;

/// Check out the specified commit in the working copy.
fn check_out(
Expand Down Expand Up @@ -249,6 +253,25 @@ impl SnapshotOptions<'_> {
/// A callback for getting progress updates.
pub type SnapshotProgress<'a> = dyn Fn(&RepoPath) + 'a + Sync;

/// Stats about a snapshot operation on a working copy.
#[derive(Clone, Debug, Default)]
pub struct SnapshotStats {
/// List of new (previously untracked) files which are still untracked.
pub untracked_paths: BTreeMap<RepoPathBuf, UntrackedReason>,
}

/// Reason why the new path isn't tracked.
#[derive(Clone, Debug)]
pub enum UntrackedReason {
/// File was larger than the specified maximum file size.
FileTooLarge {
/// Actual size of the large file.
size: u64,
/// Maximum allowed size.
max_size: u64,
},
}

/// Options used when checking out a tree in the working copy.
#[derive(Clone)]
pub struct CheckoutOptions {
Expand Down
9 changes: 5 additions & 4 deletions lib/tests/test_local_working_copy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -939,7 +939,7 @@ fn test_snapshot_racy_timestamps() {
.workspace
.start_working_copy_mutation()
.unwrap();
let new_tree_id = locked_ws
let (new_tree_id, _stats) = locked_ws
.locked_wc()
.snapshot(&SnapshotOptions::empty_for_test())
.unwrap();
Expand Down Expand Up @@ -973,7 +973,7 @@ fn test_snapshot_special_file() {

// Snapshot the working copy with the socket file
let mut locked_ws = ws.start_working_copy_mutation().unwrap();
let tree_id = locked_ws
let (tree_id, _stats) = locked_ws
.locked_wc()
.snapshot(&SnapshotOptions::empty_for_test())
.unwrap();
Expand Down Expand Up @@ -2052,15 +2052,16 @@ fn test_fsmonitor() {
.iter()
.map(|p| p.to_fs_path_unchecked(Path::new("")))
.collect();
locked_ws
let (tree_id, _stats) = locked_ws
.locked_wc()
.snapshot(&SnapshotOptions {
fsmonitor_settings: FsmonitorSettings::Test {
changed_files: fs_paths,
},
..SnapshotOptions::empty_for_test()
})
.unwrap()
.unwrap();
tree_id
};

{
Expand Down
2 changes: 1 addition & 1 deletion lib/tests/test_local_working_copy_concurrent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ fn test_checkout_parallel() {
// &CheckoutOptions::empty_for_test()) should never produce a
// different tree.
let mut locked_ws = workspace.start_working_copy_mutation().unwrap();
let new_tree_id = locked_ws
let (new_tree_id, _stats) = locked_ws
.locked_wc()
.snapshot(&SnapshotOptions::empty_for_test())
.unwrap();
Expand Down
10 changes: 6 additions & 4 deletions lib/testutils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ use jj_lib::tree::Tree;
use jj_lib::tree_builder::TreeBuilder;
use jj_lib::working_copy::SnapshotError;
use jj_lib::working_copy::SnapshotOptions;
use jj_lib::working_copy::SnapshotStats;
use jj_lib::workspace::Workspace;
use pollster::FutureExt;
use tempfile::TempDir;
Expand Down Expand Up @@ -302,17 +303,18 @@ impl TestWorkspace {
pub fn snapshot_with_options(
&mut self,
options: &SnapshotOptions,
) -> Result<MergedTree, SnapshotError> {
) -> Result<(MergedTree, SnapshotStats), SnapshotError> {
let mut locked_ws = self.workspace.start_working_copy_mutation().unwrap();
let tree_id = locked_ws.locked_wc().snapshot(options)?;
let (tree_id, stats) = locked_ws.locked_wc().snapshot(options)?;
// arbitrary operation id
locked_ws.finish(self.repo.op_id().clone()).unwrap();
Ok(self.repo.store().get_root_tree(&tree_id).unwrap())
Ok((self.repo.store().get_root_tree(&tree_id).unwrap(), stats))
}

/// Like `snapshot_with_option()` but with default options
pub fn snapshot(&mut self) -> Result<MergedTree, SnapshotError> {
self.snapshot_with_options(&SnapshotOptions::empty_for_test())
let (tree_id, _stats) = self.snapshot_with_options(&SnapshotOptions::empty_for_test())?;
Ok(tree_id)
}
}

Expand Down

0 comments on commit f4fdc19

Please sign in to comment.