1#![allow(missing_docs)]
16
17use std::collections::BTreeMap;
18use std::collections::HashSet;
19
20use itertools::Itertools as _;
21use thiserror::Error;
22
23use crate::backend::CommitId;
24use crate::op_store;
25use crate::op_store::BookmarkTarget;
26use crate::op_store::RefTarget;
27use crate::op_store::RefTargetOptionExt as _;
28use crate::op_store::RemoteRef;
29use crate::ref_name::GitRefName;
30use crate::ref_name::GitRefNameBuf;
31use crate::ref_name::RefName;
32use crate::ref_name::RefNameBuf;
33use crate::ref_name::RemoteName;
34use crate::ref_name::RemoteRefSymbol;
35use crate::ref_name::WorkspaceName;
36use crate::ref_name::WorkspaceNameBuf;
37use crate::refs;
38use crate::refs::LocalAndRemoteRef;
39use crate::str_util::StringPattern;
40
41#[derive(PartialEq, Eq, Debug, Clone)]
43pub struct View {
44 data: op_store::View,
45}
46
47impl View {
48 pub fn new(op_store_view: op_store::View) -> Self {
49 View {
50 data: op_store_view,
51 }
52 }
53
54 pub fn wc_commit_ids(&self) -> &BTreeMap<WorkspaceNameBuf, CommitId> {
55 &self.data.wc_commit_ids
56 }
57
58 pub fn get_wc_commit_id(&self, name: &WorkspaceName) -> Option<&CommitId> {
59 self.data.wc_commit_ids.get(name)
60 }
61
62 pub fn workspaces_for_wc_commit_id(&self, commit_id: &CommitId) -> Vec<WorkspaceNameBuf> {
63 let mut workspace_names = vec![];
64 for (name, wc_commit_id) in &self.data.wc_commit_ids {
65 if wc_commit_id == commit_id {
66 workspace_names.push(name.clone());
67 }
68 }
69 workspace_names
70 }
71
72 pub fn is_wc_commit_id(&self, commit_id: &CommitId) -> bool {
73 self.data.wc_commit_ids.values().contains(commit_id)
74 }
75
76 pub fn heads(&self) -> &HashSet<CommitId> {
77 &self.data.head_ids
78 }
79
80 pub fn bookmarks(&self) -> impl Iterator<Item = (&RefName, BookmarkTarget<'_>)> {
82 op_store::merge_join_bookmark_views(&self.data.local_bookmarks, &self.data.remote_views)
83 }
84
85 pub fn tags(&self) -> &BTreeMap<RefNameBuf, RefTarget> {
86 &self.data.tags
87 }
88
89 pub fn git_refs(&self) -> &BTreeMap<GitRefNameBuf, RefTarget> {
90 &self.data.git_refs
91 }
92
93 pub fn git_head(&self) -> &RefTarget {
94 &self.data.git_head
95 }
96
97 pub fn set_wc_commit(&mut self, name: WorkspaceNameBuf, commit_id: CommitId) {
98 self.data.wc_commit_ids.insert(name, commit_id);
99 }
100
101 pub fn remove_wc_commit(&mut self, name: &WorkspaceName) {
102 self.data.wc_commit_ids.remove(name);
103 }
104
105 pub fn rename_workspace(
106 &mut self,
107 old_name: &WorkspaceName,
108 new_name: WorkspaceNameBuf,
109 ) -> Result<(), RenameWorkspaceError> {
110 if self.data.wc_commit_ids.contains_key(&new_name) {
111 return Err(RenameWorkspaceError::WorkspaceAlreadyExists {
112 name: new_name.clone(),
113 });
114 }
115 let wc_commit_id = self.data.wc_commit_ids.remove(old_name).ok_or_else(|| {
116 RenameWorkspaceError::WorkspaceDoesNotExist {
117 name: old_name.to_owned(),
118 }
119 })?;
120 self.data.wc_commit_ids.insert(new_name, wc_commit_id);
121 Ok(())
122 }
123
124 pub fn add_head(&mut self, head_id: &CommitId) {
125 self.data.head_ids.insert(head_id.clone());
126 }
127
128 pub fn remove_head(&mut self, head_id: &CommitId) {
129 self.data.head_ids.remove(head_id);
130 }
131
132 pub fn local_bookmarks(&self) -> impl Iterator<Item = (&RefName, &RefTarget)> {
134 self.data
135 .local_bookmarks
136 .iter()
137 .map(|(name, target)| (name.as_ref(), target))
138 }
139
140 pub fn local_bookmarks_for_commit<'a, 'b>(
143 &'a self,
144 commit_id: &'b CommitId,
145 ) -> impl Iterator<Item = (&'a RefName, &'a RefTarget)> + use<'a, 'b> {
146 self.local_bookmarks()
147 .filter(|(_, target)| target.added_ids().contains(commit_id))
148 }
149
150 pub fn local_bookmarks_matching<'a, 'b>(
153 &'a self,
154 pattern: &'b StringPattern,
155 ) -> impl Iterator<Item = (&'a RefName, &'a RefTarget)> + use<'a, 'b> {
156 pattern
157 .filter_btree_map_as_deref(&self.data.local_bookmarks)
158 .map(|(name, target)| (name.as_ref(), target))
159 }
160
161 pub fn get_local_bookmark(&self, name: &RefName) -> &RefTarget {
162 self.data.local_bookmarks.get(name).flatten()
163 }
164
165 pub fn set_local_bookmark_target(&mut self, name: &RefName, target: RefTarget) {
169 if target.is_present() {
170 self.data.local_bookmarks.insert(name.to_owned(), target);
171 } else {
172 self.data.local_bookmarks.remove(name);
173 }
174 }
175
176 pub fn all_remote_bookmarks(&self) -> impl Iterator<Item = (RemoteRefSymbol<'_>, &RemoteRef)> {
179 op_store::flatten_remote_bookmarks(&self.data.remote_views)
180 }
181
182 pub fn remote_bookmarks(
185 &self,
186 remote_name: &RemoteName,
187 ) -> impl Iterator<Item = (&RefName, &RemoteRef)> + use<'_> {
188 let maybe_remote_view = self.data.remote_views.get(remote_name);
189 maybe_remote_view
190 .map(|remote_view| {
191 remote_view
192 .bookmarks
193 .iter()
194 .map(|(name, remote_ref)| (name.as_ref(), remote_ref))
195 })
196 .into_iter()
197 .flatten()
198 }
199
200 pub fn remote_bookmarks_matching<'a, 'b>(
205 &'a self,
206 bookmark_pattern: &'b StringPattern,
207 remote_pattern: &'b StringPattern,
208 ) -> impl Iterator<Item = (RemoteRefSymbol<'a>, &'a RemoteRef)> + use<'a, 'b> {
209 remote_pattern
211 .filter_btree_map_as_deref(&self.data.remote_views)
212 .map(|(remote, remote_view)| {
213 bookmark_pattern
214 .filter_btree_map_as_deref(&remote_view.bookmarks)
215 .map(|(name, remote_ref)| (name.to_remote_symbol(remote), remote_ref))
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(
253 &self,
254 remote_name: &RemoteName,
255 ) -> impl Iterator<Item = (&RefName, LocalAndRemoteRef<'_>)> + use<'_> {
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>(
279 &'a self,
280 bookmark_pattern: &'b StringPattern,
281 remote_name: &RemoteName,
282 ) -> impl Iterator<Item = (&'a RefName, LocalAndRemoteRef<'a>)> + use<'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_as_deref(&self.data.local_bookmarks),
288 maybe_remote_view
289 .map(|remote_view| {
290 bookmark_pattern.filter_btree_map_as_deref(&remote_view.bookmarks)
291 })
292 .into_iter()
293 .flatten(),
294 )
295 .map(|(name, (local_target, remote_ref))| {
296 let targets = LocalAndRemoteRef {
297 local_target,
298 remote_ref,
299 };
300 (name.as_ref(), targets)
301 })
302 }
303
304 pub fn remove_remote(&mut self, remote_name: &RemoteName) {
305 self.data.remote_views.remove(remote_name);
306 }
307
308 pub fn rename_remote(&mut self, old: &RemoteName, new: &RemoteName) {
309 if let Some(remote_view) = self.data.remote_views.remove(old) {
310 self.data.remote_views.insert(new.to_owned(), remote_view);
311 }
312 }
313
314 pub fn get_tag(&self, name: &RefName) -> &RefTarget {
315 self.data.tags.get(name).flatten()
316 }
317
318 pub fn tags_matching<'a, 'b>(
321 &'a self,
322 pattern: &'b StringPattern,
323 ) -> impl Iterator<Item = (&'a RefName, &'a RefTarget)> + use<'a, 'b> {
324 pattern
325 .filter_btree_map_as_deref(&self.data.tags)
326 .map(|(name, target)| (name.as_ref(), target))
327 }
328
329 pub fn set_tag_target(&mut self, name: &RefName, target: RefTarget) {
332 if target.is_present() {
333 self.data.tags.insert(name.to_owned(), target);
334 } else {
335 self.data.tags.remove(name);
336 }
337 }
338
339 pub fn get_git_ref(&self, name: &GitRefName) -> &RefTarget {
340 self.data.git_refs.get(name).flatten()
341 }
342
343 pub fn set_git_ref_target(&mut self, name: &GitRefName, target: RefTarget) {
346 if target.is_present() {
347 self.data.git_refs.insert(name.to_owned(), target);
348 } else {
349 self.data.git_refs.remove(name);
350 }
351 }
352
353 pub fn set_git_head_target(&mut self, target: RefTarget) {
356 self.data.git_head = target;
357 }
358
359 pub fn all_referenced_commit_ids(&self) -> impl Iterator<Item = &CommitId> {
368 fn ref_target_ids(target: &RefTarget) -> impl Iterator<Item = &CommitId> {
371 target.as_merge().iter().flatten()
372 }
373
374 let op_store::View {
377 head_ids,
378 local_bookmarks,
379 tags,
380 remote_views,
381 git_refs,
382 git_head,
383 wc_commit_ids,
384 } = &self.data;
385 itertools::chain!(
386 head_ids,
387 local_bookmarks.values().flat_map(ref_target_ids),
388 tags.values().flat_map(ref_target_ids),
389 remote_views.values().flat_map(|remote_view| {
390 let op_store::RemoteView { bookmarks } = remote_view;
391 bookmarks
392 .values()
393 .flat_map(|remote_ref| ref_target_ids(&remote_ref.target))
394 }),
395 git_refs.values().flat_map(ref_target_ids),
396 ref_target_ids(git_head),
397 wc_commit_ids.values()
398 )
399 }
400
401 pub fn set_view(&mut self, data: op_store::View) {
402 self.data = data;
403 }
404
405 pub fn store_view(&self) -> &op_store::View {
406 &self.data
407 }
408
409 pub fn store_view_mut(&mut self) -> &mut op_store::View {
410 &mut self.data
411 }
412}
413
414#[derive(Debug, Error)]
416pub enum RenameWorkspaceError {
417 #[error("Workspace {} not found", name.as_symbol())]
418 WorkspaceDoesNotExist { name: WorkspaceNameBuf },
419
420 #[error("Workspace {} already exists", name.as_symbol())]
421 WorkspaceAlreadyExists { name: WorkspaceNameBuf },
422}