jj_lib/
view.rs

1// Copyright 2020 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![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/// A wrapper around [`op_store::View`] that defines additional methods.
42#[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    /// Iterates pair of local and remote bookmarks by bookmark name.
81    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    /// Iterates local bookmark `(name, target)`s in lexicographical order.
133    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    /// Iterates local bookmarks `(name, target)` in lexicographical order where
141    /// the target adds `commit_id`.
142    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    /// Iterates local bookmark `(name, target)`s matching the given pattern.
151    /// Entries are sorted by `name`.
152    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    /// Sets local bookmark to point to the given target. If the target is
166    /// absent, and if no associated remote bookmarks exist, the bookmark
167    /// will be removed.
168    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    /// Iterates over `(symbol, remote_ref)` for all remote bookmarks in
177    /// lexicographical order.
178    pub fn all_remote_bookmarks(&self) -> impl Iterator<Item = (RemoteRefSymbol<'_>, &RemoteRef)> {
179        op_store::flatten_remote_bookmarks(&self.data.remote_views)
180    }
181
182    /// Iterates over `(name, remote_ref)`s for all remote bookmarks of the
183    /// specified remote in lexicographical order.
184    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    /// Iterates over `(symbol, remote_ref)`s for all remote bookmarks of the
201    /// specified remote that match the given pattern.
202    ///
203    /// Entries are sorted by `symbol`, which is `(name, remote)`.
204    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        // Use kmerge instead of flat_map for consistency with all_remote_bookmarks().
210        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    /// Sets remote-tracking bookmark to the given target and state. If the
229    /// target is absent, the bookmark will be removed.
230    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    /// Iterates over `(name, {local_ref, remote_ref})`s for every bookmark
246    /// present locally and/or on the specified remote, in lexicographical
247    /// order.
248    ///
249    /// Note that this does *not* take into account whether the local bookmark
250    /// tracks the remote bookmark or not. Missing values are represented as
251    /// RefTarget::absent_ref() or RemoteRef::absent_ref().
252    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    /// Iterates over `(name, TrackingRefPair {local_ref, remote_ref})`s for
270    /// every bookmark with a name that matches the given pattern, and that is
271    /// present locally and/or on the specified remote.
272    ///
273    /// Entries are sorted by `name`.
274    ///
275    /// Note that this does *not* take into account whether the local bookmark
276    /// tracks the remote bookmark or not. Missing values are represented as
277    /// RefTarget::absent_ref() or RemoteRef::absent_ref().
278    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        // Change remote_name to StringPattern if needed, but merge-join adapter won't
284        // be usable.
285        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    /// Iterates tags `(name, target)`s matching the given pattern. Entries
319    /// are sorted by `name`.
320    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    /// Sets tag to point to the given target. If the target is absent, the tag
330    /// will be removed.
331    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    /// Sets the last imported Git ref to point to the given target. If the
344    /// target is absent, the reference will be removed.
345    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    /// Sets Git HEAD to point to the given target. If the target is absent, the
354    /// reference will be cleared.
355    pub fn set_git_head_target(&mut self, target: RefTarget) {
356        self.data.git_head = target;
357    }
358
359    /// Iterates all commit ids referenced by this view.
360    ///
361    /// This can include hidden commits referenced by remote bookmarks, previous
362    /// positions of conflicted bookmarks, etc. The ancestors and predecessors
363    /// of the returned commits should be considered reachable from the
364    /// view. Use this to build commit index from scratch.
365    ///
366    /// The iteration order is unspecified, and may include duplicated entries.
367    pub fn all_referenced_commit_ids(&self) -> impl Iterator<Item = &CommitId> {
368        // Include both added/removed ids since ancestry information of old
369        // references will be needed while merging views.
370        fn ref_target_ids(target: &RefTarget) -> impl Iterator<Item = &CommitId> {
371            target.as_merge().iter().flatten()
372        }
373
374        // Some of the fields (e.g. wc_commit_ids) would be redundant, but let's
375        // not be smart here. Callers will build a larger set of commits anyway.
376        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/// Error from attempts to rename a workspace
415#[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}