1use std::fs::File;
16use std::io::{self, Read, Write};
17use std::path::{Path, PathBuf};
18use std::sync::Arc;
19
20use thiserror::Error;
21
22use crate::backend::Backend;
23use crate::git_backend::GitBackend;
24use crate::local_backend::LocalBackend;
25use crate::op_heads_store::OpHeadsStore;
26use crate::op_store::{self, OpStore, OperationMetadata, WorkspaceId};
27use crate::operation::Operation;
28use crate::repo::{
29 CheckOutCommitError, IoResultExt, PathError, ReadonlyRepo, Repo, RepoLoader, StoreFactories,
30};
31use crate::settings::UserSettings;
32use crate::working_copy::WorkingCopy;
33
34#[derive(Error, Debug)]
35pub enum WorkspaceInitError {
36 #[error("The destination repo ({0}) already exists")]
37 DestinationExists(PathBuf),
38 #[error("Repo path could not be interpreted as Unicode text")]
39 NonUnicodePath,
40 #[error(transparent)]
41 CheckOutCommit(#[from] CheckOutCommitError),
42 #[error(transparent)]
43 Path(#[from] PathError),
44}
45
46#[derive(Error, Debug)]
47pub enum WorkspaceLoadError {
48 #[error("The repo appears to no longer be at {0}")]
49 RepoDoesNotExist(PathBuf),
50 #[error("There is no Jujutsu repo in {0}")]
51 NoWorkspaceHere(PathBuf),
52 #[error("Repo path could not be interpreted as Unicode text")]
53 NonUnicodePath,
54 #[error(transparent)]
55 Path(#[from] PathError),
56}
57
58pub struct Workspace {
61 workspace_root: PathBuf,
64 repo_loader: RepoLoader,
65 working_copy: WorkingCopy,
66}
67
68fn create_jj_dir(workspace_root: &Path) -> Result<PathBuf, WorkspaceInitError> {
69 let jj_dir = workspace_root.join(".jj");
70 match std::fs::create_dir(&jj_dir).context(&jj_dir) {
71 Ok(()) => Ok(jj_dir),
72 Err(ref e) if e.error.kind() == io::ErrorKind::AlreadyExists => {
73 Err(WorkspaceInitError::DestinationExists(jj_dir))
74 }
75 Err(e) => Err(e.into()),
76 }
77}
78
79fn init_working_copy(
80 user_settings: &UserSettings,
81 repo: &Arc<ReadonlyRepo>,
82 workspace_root: &Path,
83 jj_dir: &Path,
84 workspace_id: WorkspaceId,
85) -> Result<(WorkingCopy, Arc<ReadonlyRepo>), WorkspaceInitError> {
86 let working_copy_state_path = jj_dir.join("working_copy");
87 std::fs::create_dir(&working_copy_state_path).context(&working_copy_state_path)?;
88
89 let mut tx = repo.start_transaction(
90 user_settings,
91 &format!("add workspace '{}'", workspace_id.as_str()),
92 );
93 tx.mut_repo().check_out(
94 workspace_id.clone(),
95 user_settings,
96 &repo.store().root_commit(),
97 )?;
98 let repo = tx.commit();
99
100 let working_copy = WorkingCopy::init(
101 repo.store().clone(),
102 workspace_root.to_path_buf(),
103 working_copy_state_path,
104 repo.op_id().clone(),
105 workspace_id,
106 );
107 Ok((working_copy, repo))
108}
109
110impl Workspace {
111 fn new(
112 workspace_root: &Path,
113 working_copy: WorkingCopy,
114 repo_loader: RepoLoader,
115 ) -> Result<Workspace, PathError> {
116 let workspace_root = workspace_root.canonicalize().context(workspace_root)?;
117 Ok(Workspace {
118 workspace_root,
119 repo_loader,
120 working_copy,
121 })
122 }
123
124 pub fn init_local(
125 user_settings: &UserSettings,
126 workspace_root: &Path,
127 ) -> Result<(Self, Arc<ReadonlyRepo>), WorkspaceInitError> {
128 Self::init_with_backend(user_settings, workspace_root, |store_path| {
129 Box::new(LocalBackend::init(store_path))
130 })
131 }
132
133 pub fn init_internal_git(
136 user_settings: &UserSettings,
137 workspace_root: &Path,
138 ) -> Result<(Self, Arc<ReadonlyRepo>), WorkspaceInitError> {
139 Self::init_with_backend(user_settings, workspace_root, |store_path| {
140 Box::new(GitBackend::init_internal(store_path))
141 })
142 }
143
144 pub fn init_external_git(
147 user_settings: &UserSettings,
148 workspace_root: &Path,
149 git_repo_path: &Path,
150 ) -> Result<(Self, Arc<ReadonlyRepo>), WorkspaceInitError> {
151 Self::init_with_backend(user_settings, workspace_root, |store_path| {
152 Box::new(GitBackend::init_external(store_path, git_repo_path))
153 })
154 }
155
156 pub fn init_with_factories(
157 user_settings: &UserSettings,
158 workspace_root: &Path,
159 backend_factory: impl FnOnce(&Path) -> Box<dyn Backend>,
160 op_store_factory: impl FnOnce(&Path) -> Box<dyn OpStore>,
161 op_heads_store_factory: impl FnOnce(
162 &Path,
163 &Arc<dyn OpStore>,
164 &op_store::View,
165 OperationMetadata,
166 ) -> (Box<dyn OpHeadsStore>, Operation),
167 ) -> Result<(Self, Arc<ReadonlyRepo>), WorkspaceInitError> {
168 let jj_dir = create_jj_dir(workspace_root)?;
169 let repo_dir = jj_dir.join("repo");
170 std::fs::create_dir(&repo_dir).context(&repo_dir)?;
171 let repo = ReadonlyRepo::init(
172 user_settings,
173 &repo_dir,
174 backend_factory,
175 op_store_factory,
176 op_heads_store_factory,
177 )?;
178 let (working_copy, repo) = init_working_copy(
179 user_settings,
180 &repo,
181 workspace_root,
182 &jj_dir,
183 WorkspaceId::default(),
184 )?;
185 let repo_loader = repo.loader();
186 let workspace = Workspace::new(workspace_root, working_copy, repo_loader)?;
187 Ok((workspace, repo))
188 }
189
190 pub fn init_with_backend(
191 user_settings: &UserSettings,
192 workspace_root: &Path,
193 backend_factory: impl FnOnce(&Path) -> Box<dyn Backend>,
194 ) -> Result<(Self, Arc<ReadonlyRepo>), WorkspaceInitError> {
195 Self::init_with_factories(
196 user_settings,
197 workspace_root,
198 backend_factory,
199 ReadonlyRepo::default_op_store_factory(),
200 ReadonlyRepo::default_op_heads_store_factory(),
201 )
202 }
203
204 pub fn init_workspace_with_existing_repo(
205 user_settings: &UserSettings,
206 workspace_root: &Path,
207 repo: &Arc<ReadonlyRepo>,
208 workspace_id: WorkspaceId,
209 ) -> Result<(Self, Arc<ReadonlyRepo>), WorkspaceInitError> {
210 let jj_dir = create_jj_dir(workspace_root)?;
211
212 let repo_dir = repo.repo_path().canonicalize().context(repo.repo_path())?;
213 let repo_file_path = jj_dir.join("repo");
214 let mut repo_file = File::create(&repo_file_path).context(&repo_file_path)?;
215 repo_file
216 .write_all(
217 repo_dir
218 .to_str()
219 .ok_or(WorkspaceInitError::NonUnicodePath)?
220 .as_bytes(),
221 )
222 .context(&repo_file_path)?;
223
224 let (working_copy, repo) =
225 init_working_copy(user_settings, repo, workspace_root, &jj_dir, workspace_id)?;
226 let workspace = Workspace::new(workspace_root, working_copy, repo.loader())?;
227 Ok((workspace, repo))
228 }
229
230 pub fn load(
231 user_settings: &UserSettings,
232 workspace_path: &Path,
233 store_factories: &StoreFactories,
234 ) -> Result<Self, WorkspaceLoadError> {
235 let loader = WorkspaceLoader::init(workspace_path)?;
236 Ok(loader.load(user_settings, store_factories)?)
237 }
238
239 pub fn workspace_root(&self) -> &PathBuf {
240 &self.workspace_root
241 }
242
243 pub fn workspace_id(&self) -> &WorkspaceId {
244 self.working_copy.workspace_id()
245 }
246
247 pub fn repo_path(&self) -> &PathBuf {
248 self.repo_loader.repo_path()
249 }
250
251 pub fn repo_loader(&self) -> &RepoLoader {
252 &self.repo_loader
253 }
254
255 pub fn working_copy(&self) -> &WorkingCopy {
256 &self.working_copy
257 }
258
259 pub fn working_copy_mut(&mut self) -> &mut WorkingCopy {
260 &mut self.working_copy
261 }
262}
263
264#[derive(Clone)]
265pub struct WorkspaceLoader {
266 workspace_root: PathBuf,
267 repo_dir: PathBuf,
268 working_copy_state_path: PathBuf,
269}
270
271impl WorkspaceLoader {
272 pub fn init(workspace_root: &Path) -> Result<Self, WorkspaceLoadError> {
273 let jj_dir = workspace_root.join(".jj");
274 if !jj_dir.is_dir() {
275 return Err(WorkspaceLoadError::NoWorkspaceHere(
276 workspace_root.to_owned(),
277 ));
278 }
279 let mut repo_dir = jj_dir.join("repo");
280 if repo_dir.is_file() {
283 let mut repo_file = File::open(&repo_dir).context(&repo_dir)?;
284 let mut buf = Vec::new();
285 repo_file.read_to_end(&mut buf).context(&repo_dir)?;
286 let repo_path_str =
287 String::from_utf8(buf).map_err(|_| WorkspaceLoadError::NonUnicodePath)?;
288 repo_dir = jj_dir
289 .join(&repo_path_str)
290 .canonicalize()
291 .context(&repo_path_str)?;
292 if !repo_dir.is_dir() {
293 return Err(WorkspaceLoadError::RepoDoesNotExist(repo_dir));
294 }
295 }
296 let working_copy_state_path = jj_dir.join("working_copy");
297 Ok(WorkspaceLoader {
298 workspace_root: workspace_root.to_owned(),
299 repo_dir,
300 working_copy_state_path,
301 })
302 }
303
304 pub fn workspace_root(&self) -> &Path {
305 &self.workspace_root
306 }
307
308 pub fn repo_path(&self) -> &Path {
309 &self.repo_dir
310 }
311
312 pub fn load(
313 &self,
314 user_settings: &UserSettings,
315 store_factories: &StoreFactories,
316 ) -> Result<Workspace, PathError> {
317 let repo_loader = RepoLoader::init(user_settings, &self.repo_dir, store_factories);
318 let working_copy = WorkingCopy::load(
319 repo_loader.store().clone(),
320 self.workspace_root.clone(),
321 self.working_copy_state_path.clone(),
322 );
323 Workspace::new(&self.workspace_root, working_copy, repo_loader)
324 }
325}