ok/jj
1
0
Fork 0
forked from mirrors/jj

working copy: add check_out() function to the backend trait

This includes documenting the new function and the other types moved
to the `working_copy` module.
This commit is contained in:
Martin von Zweigbergk 2023-10-12 06:10:31 -07:00 committed by Martin von Zweigbergk
parent 781859cb51
commit 0d2247b0df
8 changed files with 91 additions and 73 deletions

View file

@ -42,7 +42,7 @@ use jj_lib::gitignore::GitIgnoreFile;
use jj_lib::hex_util::to_reverse_hex;
use jj_lib::id_prefix::IdPrefixContext;
use jj_lib::local_working_copy::{
CheckoutStats, LocalWorkingCopy, LockedLocalWorkingCopy, ResetError, WorkingCopyStateError,
LocalWorkingCopy, LockedLocalWorkingCopy, ResetError, WorkingCopyStateError,
};
use jj_lib::matchers::{EverythingMatcher, Matcher, PrefixMatcher, Visit};
use jj_lib::merged_tree::{MergedTree, MergedTreeBuilder};
@ -62,7 +62,9 @@ use jj_lib::revset::{
use jj_lib::settings::{ConfigResultExt as _, UserSettings};
use jj_lib::transaction::Transaction;
use jj_lib::tree::TreeMergeError;
use jj_lib::working_copy::{LockedWorkingCopy, SnapshotError, SnapshotOptions, WorkingCopy};
use jj_lib::working_copy::{
CheckoutStats, LockedWorkingCopy, SnapshotError, SnapshotOptions, WorkingCopy,
};
use jj_lib::workspace::{
LockedWorkspace, Workspace, WorkspaceInitError, WorkspaceLoadError, WorkspaceLoader,
};

View file

@ -10,14 +10,14 @@ use itertools::Itertools;
use jj_lib::backend::{FileId, MergedTreeId, TreeValue};
use jj_lib::conflicts::{self, materialize_merge_result};
use jj_lib::gitignore::GitIgnoreFile;
use jj_lib::local_working_copy::{CheckoutError, TreeState, TreeStateError};
use jj_lib::local_working_copy::{TreeState, TreeStateError};
use jj_lib::matchers::Matcher;
use jj_lib::merge::Merge;
use jj_lib::merged_tree::{MergedTree, MergedTreeBuilder};
use jj_lib::repo_path::RepoPath;
use jj_lib::settings::UserSettings;
use jj_lib::store::Store;
use jj_lib::working_copy::SnapshotOptions;
use jj_lib::working_copy::{CheckoutError, SnapshotOptions};
use regex::{Captures, Regex};
use tempfile::TempDir;
use thiserror::Error;

View file

@ -59,7 +59,8 @@ use crate::settings::HumanByteSize;
use crate::store::Store;
use crate::tree::Tree;
use crate::working_copy::{
LockedWorkingCopy, SnapshotError, SnapshotOptions, SnapshotProgress, WorkingCopy,
CheckoutError, CheckoutStats, LockedWorkingCopy, SnapshotError, SnapshotOptions,
SnapshotProgress, WorkingCopy,
};
#[cfg(unix)]
@ -302,45 +303,6 @@ fn file_state(metadata: &Metadata) -> Option<FileState> {
})
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct CheckoutStats {
pub updated_files: u32,
pub added_files: u32,
pub removed_files: u32,
pub skipped_files: u32,
}
#[derive(Debug, Error)]
pub enum CheckoutError {
// The current working-copy commit was deleted, maybe by an overly aggressive GC that happened
// while the current process was running.
#[error("Current working-copy commit not found: {source}")]
SourceNotFound {
source: Box<dyn std::error::Error + Send + Sync>,
},
// Another process checked out a commit while the current process was running (after the
// working copy was read by the current process).
#[error("Concurrent checkout")]
ConcurrentCheckout,
#[error("Internal backend error: {0}")]
InternalBackendError(#[from] BackendError),
#[error("{message}: {err:?}")]
Other {
message: String,
#[source]
err: Box<dyn std::error::Error + Send + Sync>,
},
}
impl CheckoutError {
fn for_stat_error(err: std::io::Error, path: &Path) -> Self {
CheckoutError::Other {
message: format!("Failed to stat file {}", path.display()),
err: err.into(),
}
}
}
struct FsmonitorMatcher {
matcher: Option<Box<dyn Matcher>>,
watchman_clock: Option<crate::protos::working_copy::WatchmanClock>,
@ -1068,7 +1030,7 @@ impl TreeState {
// mtime is set at write time and won't change when we close the file.)
let metadata = file
.metadata()
.map_err(|err| CheckoutError::for_stat_error(err, disk_path))?;
.map_err(|err| checkout_error_for_stat_error(err, disk_path))?;
Ok(FileState::for_file(executable, size, &metadata))
}
@ -1098,7 +1060,7 @@ impl TreeState {
}
let metadata = disk_path
.symlink_metadata()
.map_err(|err| CheckoutError::for_stat_error(err, disk_path))?;
.map_err(|err| checkout_error_for_stat_error(err, disk_path))?;
Ok(FileState::for_symlink(&metadata))
}
@ -1129,7 +1091,7 @@ impl TreeState {
// Windows like we do with the executable bit for regular files.
let metadata = file
.metadata()
.map_err(|err| CheckoutError::for_stat_error(err, disk_path))?;
.map_err(|err| checkout_error_for_stat_error(err, disk_path))?;
Ok(FileState::for_file(false, size, &metadata))
}
@ -1139,7 +1101,7 @@ impl TreeState {
{
let mode = if executable { 0o755 } else { 0o644 };
fs::set_permissions(disk_path, fs::Permissions::from_mode(mode))
.map_err(|err| CheckoutError::for_stat_error(err, disk_path))?;
.map_err(|err| checkout_error_for_stat_error(err, disk_path))?;
}
Ok(())
}
@ -1317,6 +1279,13 @@ impl TreeState {
}
}
fn checkout_error_for_stat_error(err: std::io::Error, path: &Path) -> CheckoutError {
CheckoutError::Other {
message: format!("Failed to stat file {}", path.display()),
err: err.into(),
}
}
#[derive(Debug, Error)]
#[error("{message}: {err:?}")]
pub struct WorkingCopyStateError {
@ -1565,6 +1534,21 @@ impl LockedWorkingCopy for LockedLocalWorkingCopy {
self.tree_state_dirty |= tree_state.snapshot(options)?;
Ok(tree_state.current_tree_id().clone())
}
fn check_out(&mut self, new_tree: &MergedTree) -> Result<CheckoutStats, CheckoutError> {
// TODO: Write a "pending_checkout" file with the new TreeId so we can
// continue an interrupted update if we find such a file.
let stats = self
.wc
.tree_state_mut()
.map_err(|err| CheckoutError::Other {
message: "Failed to load the working copy state".to_string(),
err: err.into(),
})?
.check_out(new_tree)?;
self.tree_state_dirty = true;
Ok(stats)
}
}
impl LockedLocalWorkingCopy {
@ -1580,21 +1564,6 @@ impl LockedLocalWorkingCopy {
Ok(())
}
pub fn check_out(&mut self, new_tree: &MergedTree) -> Result<CheckoutStats, CheckoutError> {
// TODO: Write a "pending_checkout" file with the new TreeId so we can
// continue an interrupted update if we find such a file.
let stats = self
.wc
.tree_state_mut()
.map_err(|err| CheckoutError::Other {
message: "Failed to load the working copy state".to_string(),
err: err.into(),
})?
.check_out(new_tree)?;
self.tree_state_dirty = true;
Ok(stats)
}
pub fn reset(&mut self, new_tree: &MergedTree) -> Result<(), ResetError> {
self.wc
.tree_state_mut()

View file

@ -25,6 +25,7 @@ use thiserror::Error;
use crate::backend::{BackendError, MergedTreeId};
use crate::fsmonitor::FsmonitorKind;
use crate::gitignore::GitIgnoreFile;
use crate::merged_tree::MergedTree;
use crate::op_store::{OperationId, WorkspaceId};
use crate::repo_path::RepoPath;
use crate::settings::HumanByteSize;
@ -61,6 +62,10 @@ pub trait LockedWorkingCopy {
/// Snapshot the working copy and return the tree id.
fn snapshot(&mut self, options: SnapshotOptions) -> Result<MergedTreeId, SnapshotError>;
/// Check out the specified tree in the working copy.
// TODO: Pass a Commit object here because some implementations need that.
fn check_out(&mut self, new_tree: &MergedTree) -> Result<CheckoutStats, CheckoutError>;
}
/// An error while snapshotting the working copy.
@ -143,3 +148,48 @@ impl SnapshotOptions<'_> {
/// A callback for getting progress updates.
pub type SnapshotProgress<'a> = dyn Fn(&RepoPath) + 'a + Sync;
/// Stats about a checkout operation on a working copy. All "files" mentioned
/// below may also be symlinks or materialized conflicts.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct CheckoutStats {
/// The number of files that were updated in the working copy.
/// These files existed before and after the checkout.
pub updated_files: u32,
/// The number of files added in the working copy.
pub added_files: u32,
/// The number of files removed in the working copy.
pub removed_files: u32,
/// The number of files that were supposed to be updated or added in the
/// working copy but were skipped because there was an untracked (probably
/// ignored) file in its place.
pub skipped_files: u32,
}
/// The working-copy checkout failed.
#[derive(Debug, Error)]
pub enum CheckoutError {
/// The current working-copy commit was deleted, maybe by an overly
/// aggressive GC that happened while the current process was running.
#[error("Current working-copy commit not found: {source}")]
SourceNotFound {
/// The underlying error.
source: Box<dyn std::error::Error + Send + Sync>,
},
/// Another process checked out a commit while the current process was
/// running (after the working copy was read by the current process).
#[error("Concurrent checkout")]
ConcurrentCheckout,
/// Reading or writing from the commit backend failed.
#[error("Internal backend error: {0}")]
InternalBackendError(#[from] BackendError),
/// Some other error happened while checking out the working copy.
#[error("{message}: {err:?}")]
Other {
/// Error message.
message: String,
/// The underlying error.
#[source]
err: Box<dyn std::error::Error + Send + Sync>,
},
}

View file

@ -27,9 +27,7 @@ use crate::file_util::{self, IoResultExt as _, PathError};
use crate::git_backend::GitBackend;
use crate::index::IndexStore;
use crate::local_backend::LocalBackend;
use crate::local_working_copy::{
CheckoutError, CheckoutStats, LocalWorkingCopy, LockedLocalWorkingCopy, WorkingCopyStateError,
};
use crate::local_working_copy::{LocalWorkingCopy, LockedLocalWorkingCopy, WorkingCopyStateError};
use crate::merged_tree::MergedTree;
use crate::op_heads_store::OpHeadsStore;
use crate::op_store::{OpStore, OperationId, WorkspaceId};
@ -39,7 +37,7 @@ use crate::repo::{
};
use crate::settings::UserSettings;
use crate::submodule_store::SubmoduleStore;
use crate::working_copy::{LockedWorkingCopy, WorkingCopy};
use crate::working_copy::{CheckoutError, CheckoutStats, LockedWorkingCopy, WorkingCopy};
#[derive(Error, Debug)]
pub enum WorkspaceInitError {

View file

@ -27,14 +27,14 @@ use std::sync::Arc;
use itertools::Itertools;
use jj_lib::backend::{MergedTreeId, ObjectId, TreeId, TreeValue};
use jj_lib::fsmonitor::FsmonitorKind;
use jj_lib::local_working_copy::{CheckoutStats, LocalWorkingCopy};
use jj_lib::local_working_copy::LocalWorkingCopy;
use jj_lib::merge::Merge;
use jj_lib::merged_tree::{MergedTree, MergedTreeBuilder};
use jj_lib::op_store::{OperationId, WorkspaceId};
use jj_lib::repo::{ReadonlyRepo, Repo};
use jj_lib::repo_path::{RepoPath, RepoPathComponent, RepoPathJoin};
use jj_lib::settings::UserSettings;
use jj_lib::working_copy::{LockedWorkingCopy, SnapshotError, SnapshotOptions};
use jj_lib::working_copy::{CheckoutStats, LockedWorkingCopy, SnapshotError, SnapshotOptions};
use jj_lib::workspace::LockedWorkspace;
use test_case::test_case;
use testutils::{create_tree, write_random_commit, TestRepoBackend, TestWorkspace};

View file

@ -16,10 +16,9 @@ use std::cmp::max;
use std::thread;
use assert_matches::assert_matches;
use jj_lib::local_working_copy::CheckoutError;
use jj_lib::repo::Repo;
use jj_lib::repo_path::RepoPath;
use jj_lib::working_copy::{LockedWorkingCopy, SnapshotOptions};
use jj_lib::working_copy::{CheckoutError, LockedWorkingCopy, SnapshotOptions};
use jj_lib::workspace::Workspace;
use testutils::{create_tree, write_working_copy_file, TestRepo, TestWorkspace};

View file

@ -13,11 +13,11 @@
// limitations under the License.
use itertools::Itertools;
use jj_lib::local_working_copy::{CheckoutStats, LocalWorkingCopy};
use jj_lib::local_working_copy::LocalWorkingCopy;
use jj_lib::matchers::EverythingMatcher;
use jj_lib::repo::Repo;
use jj_lib::repo_path::RepoPath;
use jj_lib::working_copy::WorkingCopy;
use jj_lib::working_copy::{CheckoutStats, WorkingCopy};
use testutils::{create_tree, TestWorkspace};
#[test]