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:
parent
781859cb51
commit
0d2247b0df
8 changed files with 91 additions and 73 deletions
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
Loading…
Reference in a new issue