jj_lib/
simple_workspace_store.rs1use std::fs;
18use std::io::Write as _;
19use std::path::Path;
20use std::path::PathBuf;
21
22use jj_core::backend::BackendInitError;
23use jj_core::backend::BackendLoadError;
24use prost::Message as _;
25use tempfile::NamedTempFile;
26use thiserror::Error;
27
28use crate::file_util::BadPathEncoding;
29use crate::file_util::IoResultExt as _;
30use crate::file_util::PathError;
31use crate::file_util::normalize_path;
32use crate::file_util::path_from_bytes;
33use crate::file_util::path_to_bytes;
34use crate::file_util::persist_temp_file;
35use crate::file_util::relative_path;
36use crate::file_util::slash_path;
37use crate::lock::FileLock;
38use crate::lock::FileLockError;
39use crate::protos::simple_workspace_store;
40use crate::ref_name::WorkspaceName;
41use crate::workspace_store::WorkspaceStore;
42use crate::workspace_store::WorkspaceStoreError;
43
44#[derive(Error, Debug)]
46pub enum SimpleWorkspaceStoreError {
47 #[error(transparent)]
49 Path(#[from] PathError),
50 #[error("Failed to lock workspace store")]
52 Lock(#[from] FileLockError),
53 #[error(transparent)]
55 ProstDecode(#[from] prost::DecodeError),
56 #[error(transparent)]
58 BadPathEncoding(#[from] BadPathEncoding),
59}
60
61impl From<SimpleWorkspaceStoreError> for WorkspaceStoreError {
62 fn from(err: SimpleWorkspaceStoreError) -> Self {
63 Self::Other(Box::new(err))
64 }
65}
66
67#[derive(Debug)]
69pub struct SimpleWorkspaceStore {
70 store_dir: PathBuf,
71 store_file: PathBuf,
72 lock_file: PathBuf,
73}
74
75impl SimpleWorkspaceStore {
76 pub const NAME: &str = "simple_workspace_store";
78
79 fn new(store_dir: &Path) -> Self {
80 let store_dir = store_dir.to_path_buf();
81 let store_file = store_dir.join("index");
82 let lock_file = store_file.with_extension("lock");
83 Self {
84 store_dir,
85 store_file,
86 lock_file,
87 }
88 }
89
90 pub fn load(store_dir: &Path) -> Result<Self, BackendLoadError> {
92 let store = Self::new(store_dir);
93 if !store.store_file.exists() {
94 store
96 .initialize()
97 .map_err(|err| BackendLoadError(err.into()))?;
98 }
99 Ok(store)
100 }
101
102 pub fn init(store_dir: &Path) -> Result<Self, BackendInitError> {
104 let store = Self::new(store_dir);
105 store.initialize()?;
106 Ok(store)
107 }
108
109 fn initialize(&self) -> Result<(), BackendInitError> {
110 let _lock = self.lock().map_err(|err| BackendInitError(err.into()))?;
111 self.write_store(simple_workspace_store::Workspaces::default())
112 .map_err(|err| BackendInitError(err.into()))?;
113 Ok(())
114 }
115
116 fn repo_path(&self) -> &Path {
117 self.store_dir
118 .parent()
119 .expect("store_dir must be under the repo_path")
120 }
121
122 fn lock(&self) -> Result<FileLock, FileLockError> {
123 FileLock::lock(self.lock_file.clone())
124 }
125
126 fn read_store(&self) -> Result<simple_workspace_store::Workspaces, SimpleWorkspaceStoreError> {
127 let workspace_data = fs::read(&self.store_file).context(&self.store_file)?;
128
129 let workspaces_proto = simple_workspace_store::Workspaces::decode(&*workspace_data)?;
130
131 Ok(workspaces_proto)
132 }
133
134 fn write_store(
135 &self,
136 workspaces_proto: simple_workspace_store::Workspaces,
137 ) -> Result<(), SimpleWorkspaceStoreError> {
138 let store_file_parent = self.store_file.parent().unwrap();
140 let temp_file = NamedTempFile::new_in(store_file_parent).context(store_file_parent)?;
141
142 temp_file
143 .as_file()
144 .write_all(&workspaces_proto.encode_to_vec())
145 .context(temp_file.path())?;
146
147 persist_temp_file(temp_file, &self.store_file).context(&self.store_file)?;
148
149 Ok(())
150 }
151}
152
153impl WorkspaceStore for SimpleWorkspaceStore {
154 fn name(&self) -> &str {
155 Self::NAME
156 }
157
158 fn add(&self, workspace_name: &WorkspaceName, path: &Path) -> Result<(), WorkspaceStoreError> {
159 let _lock = self.lock().map_err(SimpleWorkspaceStoreError::Lock)?;
160
161 let mut workspaces_proto = self.read_store()?;
162
163 workspaces_proto
165 .workspaces
166 .retain(|w| w.name.as_str() != workspace_name.as_str());
167
168 let path_to_store = relative_path(self.repo_path(), path);
169 let path_to_store = if path_to_store.is_relative() {
170 slash_path(&path_to_store).into_owned()
171 } else {
172 path_to_store
173 };
174 workspaces_proto
175 .workspaces
176 .push(simple_workspace_store::Workspace {
177 name: workspace_name.as_str().to_string(),
178 path: path_to_bytes(&path_to_store)
179 .map_err(SimpleWorkspaceStoreError::BadPathEncoding)?
180 .to_owned(),
181 });
182
183 self.write_store(workspaces_proto)?;
184
185 Ok(())
186 }
187
188 fn forget(&self, workspace_names: &[&WorkspaceName]) -> Result<(), WorkspaceStoreError> {
189 let _lock = self.lock().map_err(SimpleWorkspaceStoreError::Lock)?;
190
191 let mut workspaces_proto = self.read_store()?;
192
193 workspaces_proto.workspaces.retain(|w| {
194 !workspace_names
195 .iter()
196 .any(|name| w.name.as_str() == name.as_str())
197 });
198
199 self.write_store(workspaces_proto)?;
200
201 Ok(())
202 }
203
204 fn rename(
205 &self,
206 old_name: &WorkspaceName,
207 new_name: &WorkspaceName,
208 ) -> Result<(), WorkspaceStoreError> {
209 let _lock = self.lock().map_err(SimpleWorkspaceStoreError::Lock)?;
210
211 let mut workspaces_proto = self.read_store()?;
212
213 for workspace in &mut workspaces_proto.workspaces {
214 if workspace.name.as_str() == old_name.as_str() {
215 workspace.name = new_name.as_str().to_string();
216 }
217 }
218
219 self.write_store(workspaces_proto)?;
220
221 Ok(())
222 }
223
224 fn get_workspace_path(
225 &self,
226 workspace_name: &WorkspaceName,
227 ) -> Result<Option<PathBuf>, WorkspaceStoreError> {
228 let workspaces = self.read_store()?.workspaces;
229 let Some(w) = workspaces
230 .iter()
231 .find(|w| w.name.as_str() == workspace_name.as_str())
232 else {
233 return Ok(None);
234 };
235
236 let path = path_from_bytes(&w.path).map_err(SimpleWorkspaceStoreError::BadPathEncoding)?;
237 Ok(Some(normalize_path(&self.repo_path().join(path))))
239 }
240}