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