working_copy: move freshness calculation into lib for sharing

This is to facilitate automatic update-stale in extensions and in the CommandHelper layer.
This commit is contained in:
dploch 2024-11-08 11:23:17 -05:00 committed by Daniel Ploch
parent c656fd30af
commit afe25464fe
3 changed files with 63 additions and 55 deletions

View file

@ -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<Operation>),
/// 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<WorkingCopyFreshness, OpStoreError> {
// 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)],

View file

@ -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(),

View file

@ -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<Operation>),
/// 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<Self, OpStoreError> {
// 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}")]