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

workspace: load working copy implementation dynamically

This makes `Workspace::load()` look a new `.jj/working_copy/type` file
in order to load the right working copy implementation, just like
`Repo::load()` picks the right backends based on `.jj/store/type`,
`.jj/op_store/type`, etc. We don't write the file yet, and we don't
have a way of adding alternative working copy implementations, so it
will always be `LocalWorkingCopy` for now.
This commit is contained in:
Martin von Zweigbergk 2023-10-14 05:52:50 -07:00 committed by Martin von Zweigbergk
parent e1f00d9426
commit 6bfd618275
7 changed files with 103 additions and 26 deletions

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::collections::{HashSet, VecDeque};
use std::collections::{HashMap, HashSet, VecDeque};
use std::env::{self, ArgsOs, VarError};
use std::ffi::{OsStr, OsString};
use std::fmt::Debug;
@ -64,7 +64,8 @@ use jj_lib::working_copy::{
WorkingCopyStateError,
};
use jj_lib::workspace::{
LockedWorkspace, Workspace, WorkspaceInitError, WorkspaceLoadError, WorkspaceLoader,
default_working_copy_factories, LockedWorkspace, WorkingCopyFactory, Workspace,
WorkspaceInitError, WorkspaceLoadError, WorkspaceLoader,
};
use jj_lib::{dag_walk, file_util, git, revset};
use once_cell::unsync::OnceCell;
@ -501,6 +502,7 @@ pub struct CommandHelper {
layered_configs: LayeredConfigs,
maybe_workspace_loader: Result<WorkspaceLoader, CommandError>,
store_factories: StoreFactories,
working_copy_factories: HashMap<String, WorkingCopyFactory>,
}
impl CommandHelper {
@ -515,6 +517,7 @@ impl CommandHelper {
layered_configs: LayeredConfigs,
maybe_workspace_loader: Result<WorkspaceLoader, CommandError>,
store_factories: StoreFactories,
working_copy_factories: HashMap<String, WorkingCopyFactory>,
) -> Self {
// `cwd` is canonicalized for consistency with `Workspace::workspace_root()` and
// to easily compute relative paths between them.
@ -530,6 +533,7 @@ impl CommandHelper {
layered_configs,
maybe_workspace_loader,
store_factories,
working_copy_factories,
}
}
@ -599,7 +603,11 @@ impl CommandHelper {
pub fn load_workspace(&self) -> Result<Workspace, CommandError> {
let loader = self.workspace_loader()?;
loader
.load(&self.settings, &self.store_factories)
.load(
&self.settings,
&self.store_factories,
&self.working_copy_factories,
)
.map_err(|err| map_workspace_load_error(err, &self.global_args))
}
@ -2789,6 +2797,7 @@ impl CliRunner {
let config = layered_configs.merge();
ui.reset(&config)?;
let settings = UserSettings::from_config(config);
let working_copy_factories = default_working_copy_factories();
let command_helper = CommandHelper::new(
self.app,
cwd,
@ -2799,6 +2808,7 @@ impl CliRunner {
layered_configs,
maybe_workspace_loader,
self.store_factories.unwrap_or_default(),
working_copy_factories,
);
(self.dispatch_fn)(ui, &command_helper)
}

View file

@ -1290,7 +1290,7 @@ impl WorkingCopy for LocalWorkingCopy {
}
fn name(&self) -> &str {
"local"
Self::name()
}
fn path(&self) -> &Path {
@ -1340,6 +1340,10 @@ impl WorkingCopy for LocalWorkingCopy {
}
impl LocalWorkingCopy {
pub fn name() -> &'static str {
"local"
}
/// Initializes a new working copy at `working_copy_path`. The working
/// copy's state will be stored in the `state_path` directory. The working
/// copy will have the empty tree checked out.

View file

@ -548,7 +548,7 @@ impl StoreFactories {
}
}
fn read_store_type_compat(
pub fn read_store_type_compat(
store: &'static str,
path: impl AsRef<Path>,
default: impl FnOnce() -> &'static str,

View file

@ -14,6 +14,7 @@
#![allow(missing_docs)]
use std::collections::HashMap;
use std::fs;
use std::fs::File;
use std::io::{self, Write};
@ -30,11 +31,12 @@ use crate::local_backend::LocalBackend;
use crate::local_working_copy::LocalWorkingCopy;
use crate::op_store::{OperationId, WorkspaceId};
use crate::repo::{
BackendInitializer, CheckOutCommitError, IndexStoreInitializer, OpHeadsStoreInitializer,
OpStoreInitializer, ReadonlyRepo, Repo, RepoInitError, RepoLoader, StoreFactories,
StoreLoadError, SubmoduleStoreInitializer,
read_store_type_compat, BackendInitializer, CheckOutCommitError, IndexStoreInitializer,
OpHeadsStoreInitializer, OpStoreInitializer, ReadonlyRepo, Repo, RepoInitError, RepoLoader,
StoreFactories, StoreLoadError, SubmoduleStoreInitializer,
};
use crate::settings::UserSettings;
use crate::store::Store;
use crate::working_copy::{
CheckoutError, CheckoutStats, LockedWorkingCopy, WorkingCopy, WorkingCopyStateError,
};
@ -271,9 +273,10 @@ impl Workspace {
user_settings: &UserSettings,
workspace_path: &Path,
store_factories: &StoreFactories,
working_copy_factories: &HashMap<String, WorkingCopyFactory>,
) -> Result<Self, WorkspaceLoadError> {
let loader = WorkspaceLoader::init(workspace_path)?;
let workspace = loader.load(user_settings, store_factories)?;
let workspace = loader.load(user_settings, store_factories, working_copy_factories)?;
Ok(workspace)
}
@ -406,14 +409,51 @@ impl WorkspaceLoader {
&self,
user_settings: &UserSettings,
store_factories: &StoreFactories,
working_copy_factories: &HashMap<String, WorkingCopyFactory>,
) -> Result<Workspace, WorkspaceLoadError> {
let repo_loader = RepoLoader::init(user_settings, &self.repo_dir, store_factories)?;
let working_copy = LocalWorkingCopy::load(
repo_loader.store().clone(),
self.workspace_root.clone(),
self.working_copy_state_path.clone(),
);
let workspace = Workspace::new(&self.workspace_root, Box::new(working_copy), repo_loader)?;
let working_copy = self.load_working_copy(repo_loader.store(), working_copy_factories)?;
let workspace = Workspace::new(&self.workspace_root, working_copy, repo_loader)?;
Ok(workspace)
}
fn load_working_copy(
&self,
store: &Arc<Store>,
working_copy_factories: &HashMap<String, WorkingCopyFactory>,
) -> Result<Box<dyn WorkingCopy>, StoreLoadError> {
// For compatibility with existing repos. TODO: Delete default in 0.17+
let working_copy_type = read_store_type_compat(
"working copy",
self.working_copy_state_path.join("type"),
LocalWorkingCopy::name,
)?;
let working_copy_factory =
working_copy_factories
.get(&working_copy_type)
.ok_or_else(|| StoreLoadError::UnsupportedType {
store: "working copy",
store_type: working_copy_type.to_string(),
})?;
let working_copy =
working_copy_factory(store, &self.workspace_root, &self.working_copy_state_path);
Ok(working_copy)
}
}
pub fn default_working_copy_factories() -> HashMap<String, WorkingCopyFactory> {
let mut factories: HashMap<String, WorkingCopyFactory> = HashMap::new();
factories.insert(
LocalWorkingCopy::name().to_owned(),
Box::new(|store, working_copy_path, store_path| {
Box::new(LocalWorkingCopy::load(
store.clone(),
working_copy_path.to_owned(),
store_path.to_owned(),
))
}),
);
factories
}
pub type WorkingCopyFactory = Box<dyn Fn(&Arc<Store>, &Path, &Path) -> Box<dyn WorkingCopy>>;

View file

@ -16,7 +16,7 @@ use std::path::Path;
use itertools::Itertools;
use jj_lib::repo::{Repo, StoreFactories};
use jj_lib::workspace::Workspace;
use jj_lib::workspace::{default_working_copy_factories, Workspace};
use test_case::test_case;
use testutils::{create_random_commit, load_repo_at_head, TestRepoBackend, TestWorkspace};
@ -115,8 +115,13 @@ fn test_bad_locking_children(backend: TestRepoBackend) {
// Simulate a write of a commit that happens on one machine
let machine1_root = test_workspace.root_dir().join("machine1");
copy_directory(workspace_root, &machine1_root);
let machine1_workspace =
Workspace::load(&settings, &machine1_root, &StoreFactories::default()).unwrap();
let machine1_workspace = Workspace::load(
&settings,
&machine1_root,
&StoreFactories::default(),
&default_working_copy_factories(),
)
.unwrap();
let machine1_repo = machine1_workspace
.repo_loader()
.load_at_head(&settings)
@ -131,8 +136,13 @@ fn test_bad_locking_children(backend: TestRepoBackend) {
// Simulate a write of a commit that happens on another machine
let machine2_root = test_workspace.root_dir().join("machine2");
copy_directory(workspace_root, &machine2_root);
let machine2_workspace =
Workspace::load(&settings, &machine2_root, &StoreFactories::default()).unwrap();
let machine2_workspace = Workspace::load(
&settings,
&machine2_root,
&StoreFactories::default(),
&default_working_copy_factories(),
)
.unwrap();
let machine2_repo = machine2_workspace
.repo_loader()
.load_at_head(&settings)
@ -148,8 +158,13 @@ fn test_bad_locking_children(backend: TestRepoBackend) {
// both machines
let merged_path = test_workspace.root_dir().join("merged");
merge_directories(&machine1_root, workspace_root, &machine2_root, &merged_path);
let merged_workspace =
Workspace::load(&settings, &merged_path, &StoreFactories::default()).unwrap();
let merged_workspace = Workspace::load(
&settings,
&merged_path,
&StoreFactories::default(),
&default_working_copy_factories(),
)
.unwrap();
let merged_repo = merged_workspace
.repo_loader()
.load_at_head(&settings)

View file

@ -19,7 +19,7 @@ use assert_matches::assert_matches;
use jj_lib::repo::Repo;
use jj_lib::repo_path::RepoPath;
use jj_lib::working_copy::{CheckoutError, SnapshotOptions};
use jj_lib::workspace::Workspace;
use jj_lib::workspace::{default_working_copy_factories, Workspace};
use testutils::{commit_with_tree, create_tree, write_working_copy_file, TestRepo, TestWorkspace};
#[test]
@ -52,6 +52,7 @@ fn test_concurrent_checkout() {
&settings,
&workspace1_root,
&TestRepo::default_store_factories(),
&default_working_copy_factories(),
)
.unwrap();
ws2.check_out(repo.op_id().clone(), Some(&tree_id1), &commit2)
@ -68,6 +69,7 @@ fn test_concurrent_checkout() {
&settings,
&workspace1_root,
&TestRepo::default_store_factories(),
&default_working_copy_factories(),
)
.unwrap();
assert_eq!(*ws3.working_copy().tree_id().unwrap(), tree_id2);
@ -114,6 +116,7 @@ fn test_checkout_parallel() {
&settings,
&workspace_root,
&TestRepo::default_store_factories(),
&default_working_copy_factories(),
)
.unwrap();
// The operation ID is not correct, but that doesn't matter for this test

View file

@ -15,7 +15,7 @@
use assert_matches::assert_matches;
use jj_lib::op_store::WorkspaceId;
use jj_lib::repo::Repo;
use jj_lib::workspace::{Workspace, WorkspaceLoadError};
use jj_lib::workspace::{default_working_copy_factories, Workspace, WorkspaceLoadError};
use testutils::{TestRepo, TestWorkspace};
#[test]
@ -28,6 +28,7 @@ fn test_load_bad_path() {
&settings,
&workspace_root,
&TestRepo::default_store_factories(),
&default_working_copy_factories(),
);
assert_matches!(
result.err(),
@ -65,8 +66,12 @@ fn test_init_additional_workspace() {
workspace.repo_path().canonicalize().unwrap()
);
assert_eq!(*ws2.workspace_root(), ws2_root.canonicalize().unwrap());
let same_workspace =
Workspace::load(&settings, &ws2_root, &TestRepo::default_store_factories());
let same_workspace = Workspace::load(
&settings,
&ws2_root,
&TestRepo::default_store_factories(),
&default_working_copy_factories(),
);
assert!(same_workspace.is_ok());
let same_workspace = same_workspace.unwrap();
assert_eq!(same_workspace.workspace_id(), &ws2_id);