diff --git a/cli/src/cli_util.rs b/cli/src/cli_util.rs index 4798c8f26..fb338523d 100644 --- a/cli/src/cli_util.rs +++ b/cli/src/cli_util.rs @@ -54,7 +54,6 @@ use jj_lib::backend::CommitId; use jj_lib::backend::MergedTreeId; use jj_lib::backend::TreeValue; use jj_lib::commit::Commit; -use jj_lib::dag_walk; use jj_lib::file_util; use jj_lib::fileset; use jj_lib::fileset::FilesetDiagnostics; @@ -111,10 +110,10 @@ use jj_lib::str_util::StringPattern; use jj_lib::transaction::Transaction; use jj_lib::view::View; use jj_lib::working_copy::CheckoutStats; -use jj_lib::working_copy::LockedWorkingCopy; use jj_lib::working_copy::SnapshotOptions; use jj_lib::working_copy::WorkingCopy; use jj_lib::working_copy::WorkingCopyFactory; +use jj_lib::working_copy::WorkingCopyFreshness; use jj_lib::workspace::default_working_copy_factories; use jj_lib::workspace::get_working_copy_factory; use jj_lib::workspace::DefaultWorkspaceLoaderFactory; @@ -1558,7 +1557,7 @@ impl WorkspaceCommandHelper { let mut locked_ws = self.workspace.start_working_copy_mutation()?; let old_op_id = locked_ws.locked_wc().old_operation_id().clone(); let (repo, wc_commit) = - match check_stale_working_copy(locked_ws.locked_wc(), &wc_commit, &repo) { + match WorkingCopyFreshness::check_stale(locked_ws.locked_wc(), &wc_commit, &repo) { Ok(WorkingCopyFreshness::Fresh) => (repo, wc_commit), Ok(WorkingCopyFreshness::Updated(wc_operation)) => { let repo = repo.reload_at(&wc_operation)?; @@ -2214,55 +2213,6 @@ pub fn start_repo_transaction( tx } -/// Whether the working copy is stale or not. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum WorkingCopyFreshness { - /// The working copy isn't stale, and no need to reload the repo. - Fresh, - /// The working copy was updated since we loaded the repo. The repo must be - /// reloaded at the working copy's operation. - Updated(Box), - /// The working copy is behind the latest operation. - WorkingCopyStale, - /// The working copy is a sibling of the latest operation. - SiblingOperation, -} - -#[instrument(skip_all)] -pub fn check_stale_working_copy( - locked_wc: &dyn LockedWorkingCopy, - wc_commit: &Commit, - repo: &ReadonlyRepo, -) -> Result { - // Check if the working copy's tree matches the repo's view - let wc_tree_id = locked_wc.old_tree_id(); - if wc_commit.tree_id() == wc_tree_id { - // The working copy isn't stale, and no need to reload the repo. - Ok(WorkingCopyFreshness::Fresh) - } else { - let wc_operation = repo.loader().load_operation(locked_wc.old_operation_id())?; - let repo_operation = repo.operation(); - let ancestor_op = dag_walk::closest_common_node_ok( - [Ok(wc_operation.clone())], - [Ok(repo_operation.clone())], - |op: &Operation| op.id().clone(), - |op: &Operation| op.parents().collect_vec(), - )? - .expect("unrelated operations"); - if ancestor_op.id() == repo_operation.id() { - // The working copy was updated since we loaded the repo. The repo must be - // reloaded at the working copy's operation. - Ok(WorkingCopyFreshness::Updated(Box::new(wc_operation))) - } else if ancestor_op.id() == wc_operation.id() { - // The working copy was not updated when some repo operation committed, - // meaning that it's stale compared to the repo view. - Ok(WorkingCopyFreshness::WorkingCopyStale) - } else { - Ok(WorkingCopyFreshness::SiblingOperation) - } - } -} - #[instrument(skip_all)] pub fn print_conflicted_paths( conflicts: &[(RepoPathBuf, MergedTreeValue)], diff --git a/cli/src/commands/workspace/update_stale.rs b/cli/src/commands/workspace/update_stale.rs index cb38bba1b..a3e53b511 100644 --- a/cli/src/commands/workspace/update_stale.rs +++ b/cli/src/commands/workspace/update_stale.rs @@ -18,13 +18,12 @@ use jj_lib::object_id::ObjectId; use jj_lib::op_store::OpStoreError; use jj_lib::repo::ReadonlyRepo; use jj_lib::repo::Repo; +use jj_lib::working_copy::WorkingCopyFreshness; use tracing::instrument; -use crate::cli_util::check_stale_working_copy; use crate::cli_util::print_checkout_stats; use crate::cli_util::short_commit_hash; use crate::cli_util::CommandHelper; -use crate::cli_util::WorkingCopyFreshness; use crate::cli_util::WorkspaceCommandHelper; use crate::command_error::internal_error_with_message; use crate::command_error::user_error; @@ -67,7 +66,7 @@ pub fn cmd_workspace_update_stale( let repo = workspace_command.repo().clone(); let (mut locked_ws, desired_wc_commit) = workspace_command.unchecked_start_working_copy_mutation()?; - match check_stale_working_copy(locked_ws.locked_wc(), &desired_wc_commit, &repo)? { + match WorkingCopyFreshness::check_stale(locked_ws.locked_wc(), &desired_wc_commit, &repo)? { WorkingCopyFreshness::Fresh | WorkingCopyFreshness::Updated(_) => { writeln!( ui.status(), diff --git a/lib/src/working_copy.rs b/lib/src/working_copy.rs index 09f71e555..12fe5cf9f 100644 --- a/lib/src/working_copy.rs +++ b/lib/src/working_copy.rs @@ -20,18 +20,24 @@ use std::ffi::OsString; use std::path::PathBuf; use std::sync::Arc; +use itertools::Itertools; use thiserror::Error; +use tracing::instrument; use crate::backend::BackendError; use crate::backend::MergedTreeId; use crate::commit::Commit; +use crate::dag_walk; use crate::fsmonitor::FsmonitorSettings; use crate::gitignore::GitIgnoreError; use crate::gitignore::GitIgnoreFile; use crate::matchers::EverythingMatcher; use crate::matchers::Matcher; +use crate::op_store::OpStoreError; use crate::op_store::OperationId; use crate::op_store::WorkspaceId; +use crate::operation::Operation; +use crate::repo::ReadonlyRepo; use crate::repo_path::InvalidRepoPathError; use crate::repo_path::RepoPath; use crate::repo_path::RepoPathBuf; @@ -310,6 +316,59 @@ pub enum ResetError { }, } +/// Whether the working copy is stale or not. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum WorkingCopyFreshness { + /// The working copy isn't stale, and no need to reload the repo. + Fresh, + /// The working copy was updated since we loaded the repo. The repo must be + /// reloaded at the working copy's operation. + Updated(Box), + /// The working copy is behind the latest operation. + WorkingCopyStale, + /// The working copy is a sibling of the latest operation. + SiblingOperation, +} + +impl WorkingCopyFreshness { + /// Determine the freshness of the provided working copy relative to the + /// target commit. + #[instrument(skip_all)] + pub fn check_stale( + locked_wc: &dyn LockedWorkingCopy, + wc_commit: &Commit, + repo: &ReadonlyRepo, + ) -> Result { + // Check if the working copy's tree matches the repo's view + let wc_tree_id = locked_wc.old_tree_id(); + if wc_commit.tree_id() == wc_tree_id { + // The working copy isn't stale, and no need to reload the repo. + Ok(Self::Fresh) + } else { + let wc_operation = repo.loader().load_operation(locked_wc.old_operation_id())?; + let repo_operation = repo.operation(); + let ancestor_op = dag_walk::closest_common_node_ok( + [Ok(wc_operation.clone())], + [Ok(repo_operation.clone())], + |op: &Operation| op.id().clone(), + |op: &Operation| op.parents().collect_vec(), + )? + .expect("unrelated operations"); + if ancestor_op.id() == repo_operation.id() { + // The working copy was updated since we loaded the repo. The repo must be + // reloaded at the working copy's operation. + Ok(Self::Updated(Box::new(wc_operation))) + } else if ancestor_op.id() == wc_operation.id() { + // The working copy was not updated when some repo operation committed, + // meaning that it's stale compared to the repo view. + Ok(Self::WorkingCopyStale) + } else { + Ok(Self::SiblingOperation) + } + } + } +} + /// An error while reading the working copy state. #[derive(Debug, Error)] #[error("{message}")]