1#![allow(missing_docs)]
16
17use std::collections::BTreeMap;
18use std::collections::HashMap;
19use std::collections::HashSet;
20
21use itertools::Itertools;
22use thiserror::Error;
23
24use crate::backend::CommitId;
25use crate::op_store;
26use crate::op_store::BookmarkTarget;
27use crate::op_store::RefTarget;
28use crate::op_store::RefTargetOptionExt as _;
29use crate::op_store::RemoteRef;
30use crate::op_store::WorkspaceId;
31use crate::refs;
32use crate::refs::LocalAndRemoteRef;
33use crate::refs::RemoteRefSymbol;
34use crate::str_util::StringPattern;
35
36#[derive(PartialEq, Eq, Debug, Clone)]
38pub struct View {
39 data: op_store::View,
40}
41
42impl View {
43 pub fn new(op_store_view: op_store::View) -> Self {
44 View {
45 data: op_store_view,
46 }
47 }
48
49 pub fn wc_commit_ids(&self) -> &HashMap<WorkspaceId, CommitId> {
50 &self.data.wc_commit_ids
51 }
52
53 pub fn get_wc_commit_id(&self, workspace_id: &WorkspaceId) -> Option<&CommitId> {
54 self.data.wc_commit_ids.get(workspace_id)
55 }
56
57 pub fn workspaces_for_wc_commit_id(&self, commit_id: &CommitId) -> Vec<WorkspaceId> {
58 let mut workspaces_ids = vec![];
59 for (workspace_id, wc_commit_id) in &self.data.wc_commit_ids {
60 if wc_commit_id == commit_id {
61 workspaces_ids.push(workspace_id.clone());
62 }
63 }
64 workspaces_ids
65 }
66
67 pub fn is_wc_commit_id(&self, commit_id: &CommitId) -> bool {
68 self.data.wc_commit_ids.values().contains(commit_id)
69 }
70
71 pub fn heads(&self) -> &HashSet<CommitId> {
72 &self.data.head_ids
73 }
74
75 pub fn bookmarks(&self) -> impl Iterator<Item = (&str, BookmarkTarget<'_>)> {
77 op_store::merge_join_bookmark_views(&self.data.local_bookmarks, &self.data.remote_views)
78 }
79
80 pub fn tags(&self) -> &BTreeMap<String, RefTarget> {
81 &self.data.tags
82 }
83
84 pub fn git_refs(&self) -> &BTreeMap<String, RefTarget> {
85 &self.data.git_refs
86 }
87
88 pub fn git_head(&self) -> &RefTarget {
89 &self.data.git_head
90 }
91
92 pub fn set_wc_commit(&mut self, workspace_id: WorkspaceId, commit_id: CommitId) {
93 self.data.wc_commit_ids.insert(workspace_id, commit_id);
94 }
95
96 pub fn remove_wc_commit(&mut self, workspace_id: &WorkspaceId) {
97 self.data.wc_commit_ids.remove(workspace_id);
98 }
99
100 pub fn rename_workspace(
101 &mut self,
102 old_workspace_id: &WorkspaceId,
103 new_workspace_id: WorkspaceId,
104 ) -> Result<(), RenameWorkspaceError> {
105 if self.data.wc_commit_ids.contains_key(&new_workspace_id) {
106 return Err(RenameWorkspaceError::WorkspaceAlreadyExists {
107 workspace_id: new_workspace_id.as_str().to_owned(),
108 });
109 }
110 let wc_commit_id = self
111 .data
112 .wc_commit_ids
113 .remove(old_workspace_id)
114 .ok_or_else(|| RenameWorkspaceError::WorkspaceDoesNotExist {
115 workspace_id: old_workspace_id.as_str().to_owned(),
116 })?;
117 self.data
118 .wc_commit_ids
119 .insert(new_workspace_id, wc_commit_id);
120 Ok(())
121 }
122
123 pub fn add_head(&mut self, head_id: &CommitId) {
124 self.data.head_ids.insert(head_id.clone());
125 }
126
127 pub fn remove_head(&mut self, head_id: &CommitId) {
128 self.data.head_ids.remove(head_id);
129 }
130
131 pub fn local_bookmarks(&self) -> impl Iterator<Item = (&str, &RefTarget)> {
133 self.data
134 .local_bookmarks
135 .iter()
136 .map(|(name, target)| (name.as_ref(), target))
137 }
138
139 pub fn local_bookmarks_for_commit<'a: 'b, 'b>(
142 &'a self,
143 commit_id: &'b CommitId,
144 ) -> impl Iterator<Item = (&'a str, &'a RefTarget)> + 'b {
145 self.local_bookmarks()
146 .filter(|(_, target)| target.added_ids().contains(commit_id))
147 }
148
149 pub fn local_bookmarks_matching<'a: 'b, 'b>(
152 &'a self,
153 pattern: &'b StringPattern,
154 ) -> impl Iterator<Item = (&'a str, &'a RefTarget)> + 'b {
155 pattern
156 .filter_btree_map(&self.data.local_bookmarks)
157 .map(|(name, target)| (name.as_ref(), target))
158 }
159
160 pub fn get_local_bookmark(&self, name: &str) -> &RefTarget {
161 self.data.local_bookmarks.get(name).flatten()
162 }
163
164 pub fn set_local_bookmark_target(&mut self, name: &str, target: RefTarget) {
168 if target.is_present() {
169 self.data.local_bookmarks.insert(name.to_owned(), target);
170 } else {
171 self.data.local_bookmarks.remove(name);
172 }
173 }
174
175 pub fn all_remote_bookmarks(&self) -> impl Iterator<Item = (RemoteRefSymbol<'_>, &RemoteRef)> {
178 op_store::flatten_remote_bookmarks(&self.data.remote_views)
179 .map(|((name, remote), remote_ref)| (RemoteRefSymbol { name, remote }, remote_ref))
180 }
181
182 pub fn remote_bookmarks(&self, remote_name: &str) -> impl Iterator<Item = (&str, &RemoteRef)> {
185 let maybe_remote_view = self.data.remote_views.get(remote_name);
186 maybe_remote_view
187 .map(|remote_view| {
188 remote_view
189 .bookmarks
190 .iter()
191 .map(|(name, remote_ref)| (name.as_ref(), remote_ref))
192 })
193 .into_iter()
194 .flatten()
195 }
196
197 pub fn remote_bookmarks_matching<'a: 'b, 'b>(
202 &'a self,
203 bookmark_pattern: &'b StringPattern,
204 remote_pattern: &'b StringPattern,
205 ) -> impl Iterator<Item = (RemoteRefSymbol<'a>, &'a RemoteRef)> + 'b {
206 remote_pattern
208 .filter_btree_map(&self.data.remote_views)
209 .map(|(remote, remote_view)| {
210 bookmark_pattern
211 .filter_btree_map(&remote_view.bookmarks)
212 .map(|(name, remote_ref)| {
213 let symbol = RemoteRefSymbol { name, remote };
214 (symbol, remote_ref)
215 })
216 })
217 .kmerge_by(|(symbol1, _), (symbol2, _)| symbol1 < symbol2)
218 }
219
220 pub fn get_remote_bookmark(&self, symbol: RemoteRefSymbol<'_>) -> &RemoteRef {
221 if let Some(remote_view) = self.data.remote_views.get(symbol.remote) {
222 remote_view.bookmarks.get(symbol.name).flatten()
223 } else {
224 RemoteRef::absent_ref()
225 }
226 }
227
228 pub fn set_remote_bookmark(&mut self, symbol: RemoteRefSymbol<'_>, remote_ref: RemoteRef) {
231 if remote_ref.is_present() {
232 let remote_view = self
233 .data
234 .remote_views
235 .entry(symbol.remote.to_owned())
236 .or_default();
237 remote_view
238 .bookmarks
239 .insert(symbol.name.to_owned(), remote_ref);
240 } else if let Some(remote_view) = self.data.remote_views.get_mut(symbol.remote) {
241 remote_view.bookmarks.remove(symbol.name);
242 }
243 }
244
245 pub fn local_remote_bookmarks<'a>(
253 &'a self,
254 remote_name: &str,
255 ) -> impl Iterator<Item = (&'a str, LocalAndRemoteRef<'a>)> + 'a {
256 refs::iter_named_local_remote_refs(
257 self.local_bookmarks(),
258 self.remote_bookmarks(remote_name),
259 )
260 .map(|(name, (local_target, remote_ref))| {
261 let targets = LocalAndRemoteRef {
262 local_target,
263 remote_ref,
264 };
265 (name, targets)
266 })
267 }
268
269 pub fn local_remote_bookmarks_matching<'a: 'b, 'b>(
279 &'a self,
280 bookmark_pattern: &'b StringPattern,
281 remote_name: &str,
282 ) -> impl Iterator<Item = (&'a str, LocalAndRemoteRef<'a>)> + 'b {
283 let maybe_remote_view = self.data.remote_views.get(remote_name);
286 refs::iter_named_local_remote_refs(
287 bookmark_pattern.filter_btree_map(&self.data.local_bookmarks),
288 maybe_remote_view
289 .map(|remote_view| bookmark_pattern.filter_btree_map(&remote_view.bookmarks))
290 .into_iter()
291 .flatten(),
292 )
293 .map(|(name, (local_target, remote_ref))| {
294 let targets = LocalAndRemoteRef {
295 local_target,
296 remote_ref,
297 };
298 (name.as_ref(), targets)
299 })
300 }
301
302 pub fn remove_remote(&mut self, remote_name: &str) {
303 self.data.remote_views.remove(remote_name);
304 }
305
306 pub fn rename_remote(&mut self, old: &str, new: &str) {
307 if let Some(remote_view) = self.data.remote_views.remove(old) {
308 self.data.remote_views.insert(new.to_owned(), remote_view);
309 }
310 }
311
312 pub fn get_tag(&self, name: &str) -> &RefTarget {
313 self.data.tags.get(name).flatten()
314 }
315
316 pub fn tags_matching<'a: 'b, 'b>(
319 &'a self,
320 pattern: &'b StringPattern,
321 ) -> impl Iterator<Item = (&'a str, &'a RefTarget)> + 'b {
322 pattern
323 .filter_btree_map(&self.data.tags)
324 .map(|(name, target)| (name.as_ref(), target))
325 }
326
327 pub fn set_tag_target(&mut self, name: &str, target: RefTarget) {
330 if target.is_present() {
331 self.data.tags.insert(name.to_owned(), target);
332 } else {
333 self.data.tags.remove(name);
334 }
335 }
336
337 pub fn get_git_ref(&self, name: &str) -> &RefTarget {
338 self.data.git_refs.get(name).flatten()
339 }
340
341 pub fn set_git_ref_target(&mut self, name: &str, target: RefTarget) {
344 if target.is_present() {
345 self.data.git_refs.insert(name.to_owned(), target);
346 } else {
347 self.data.git_refs.remove(name);
348 }
349 }
350
351 pub fn set_git_head_target(&mut self, target: RefTarget) {
354 self.data.git_head = target;
355 }
356
357 pub fn all_referenced_commit_ids(&self) -> impl Iterator<Item = &CommitId> {
366 fn ref_target_ids(target: &RefTarget) -> impl Iterator<Item = &CommitId> {
369 target.as_merge().iter().flatten()
370 }
371
372 let op_store::View {
375 head_ids,
376 local_bookmarks,
377 tags,
378 remote_views,
379 git_refs,
380 git_head,
381 wc_commit_ids,
382 } = &self.data;
383 itertools::chain!(
384 head_ids,
385 local_bookmarks.values().flat_map(ref_target_ids),
386 tags.values().flat_map(ref_target_ids),
387 remote_views.values().flat_map(|remote_view| {
388 let op_store::RemoteView { bookmarks } = remote_view;
389 bookmarks
390 .values()
391 .flat_map(|remote_ref| ref_target_ids(&remote_ref.target))
392 }),
393 git_refs.values().flat_map(ref_target_ids),
394 ref_target_ids(git_head),
395 wc_commit_ids.values()
396 )
397 }
398
399 pub fn set_view(&mut self, data: op_store::View) {
400 self.data = data;
401 }
402
403 pub fn store_view(&self) -> &op_store::View {
404 &self.data
405 }
406
407 pub fn store_view_mut(&mut self) -> &mut op_store::View {
408 &mut self.data
409 }
410}
411
412#[derive(Debug, Error)]
414pub enum RenameWorkspaceError {
415 #[error("Workspace {workspace_id} not found")]
416 WorkspaceDoesNotExist { workspace_id: String },
417
418 #[error("Workspace {workspace_id} already exists")]
419 WorkspaceAlreadyExists { workspace_id: String },
420}