// Copyright 2020 The Jujutsu Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #![allow(missing_docs)] use std::collections::{BTreeMap, HashMap, HashSet}; use itertools::Itertools; use crate::backend::CommitId; use crate::op_store::{BranchTarget, RefTarget, RefTargetOptionExt as _, RemoteRef, WorkspaceId}; use crate::refs::LocalAndRemoteRef; use crate::str_util::StringPattern; use crate::{op_store, refs}; /// A wrapper around [`op_store::View`] that defines additional methods. #[derive(PartialEq, Eq, Debug, Clone)] pub struct View { data: op_store::View, } impl View { pub fn new(op_store_view: op_store::View) -> Self { View { data: op_store_view, } } pub fn wc_commit_ids(&self) -> &HashMap { &self.data.wc_commit_ids } pub fn get_wc_commit_id(&self, workspace_id: &WorkspaceId) -> Option<&CommitId> { self.data.wc_commit_ids.get(workspace_id) } pub fn workspaces_for_wc_commit_id(&self, commit_id: &CommitId) -> Vec { let mut workspaces_ids = vec![]; for (workspace_id, wc_commit_id) in &self.data.wc_commit_ids { if wc_commit_id == commit_id { workspaces_ids.push(workspace_id.clone()); } } workspaces_ids } pub fn is_wc_commit_id(&self, commit_id: &CommitId) -> bool { self.data.wc_commit_ids.values().contains(commit_id) } pub fn heads(&self) -> &HashSet { &self.data.head_ids } /// Iterates pair of local and remote branches by branch name. pub fn branches(&self) -> impl Iterator)> { op_store::merge_join_branch_views(&self.data.local_branches, &self.data.remote_views) } pub fn tags(&self) -> &BTreeMap { &self.data.tags } pub fn git_refs(&self) -> &BTreeMap { &self.data.git_refs } pub fn git_head(&self) -> &RefTarget { &self.data.git_head } pub fn set_wc_commit(&mut self, workspace_id: WorkspaceId, commit_id: CommitId) { self.data.wc_commit_ids.insert(workspace_id, commit_id); } pub fn remove_wc_commit(&mut self, workspace_id: &WorkspaceId) { self.data.wc_commit_ids.remove(workspace_id); } pub fn add_head(&mut self, head_id: &CommitId) { self.data.head_ids.insert(head_id.clone()); } pub fn remove_head(&mut self, head_id: &CommitId) { self.data.head_ids.remove(head_id); } /// Returns true if any local or remote branch of the given `name` exists. #[must_use] pub fn has_branch(&self, name: &str) -> bool { self.data.local_branches.contains_key(name) || self .data .remote_views .values() .any(|remote_view| remote_view.branches.contains_key(name)) } // TODO: maybe rename to forget_branch() because this seems unusual operation? pub fn remove_branch(&mut self, name: &str) { self.data.local_branches.remove(name); for remote_view in self.data.remote_views.values_mut() { remote_view.branches.remove(name); } } /// Iterates local branch `(name, target)`s in lexicographical order. pub fn local_branches(&self) -> impl Iterator { self.data .local_branches .iter() .map(|(name, target)| (name.as_ref(), target)) } /// Iterates local branch `(name, target)`s matching the given pattern. /// Entries are sorted by `name`. pub fn local_branches_matching<'a: 'b, 'b>( &'a self, pattern: &'b StringPattern, ) -> impl Iterator + 'b { pattern .filter_btree_map(&self.data.local_branches) .map(|(name, target)| (name.as_ref(), target)) } pub fn get_local_branch(&self, name: &str) -> &RefTarget { self.data.local_branches.get(name).flatten() } /// Sets local branch to point to the given target. If the target is absent, /// and if no associated remote branches exist, the branch will be removed. pub fn set_local_branch_target(&mut self, name: &str, target: RefTarget) { if target.is_present() { self.data.local_branches.insert(name.to_owned(), target); } else { self.data.local_branches.remove(name); } } /// Iterates over `((name, remote_name), remote_ref)` for all remote /// branches in lexicographical order. pub fn all_remote_branches(&self) -> impl Iterator { op_store::flatten_remote_branches(&self.data.remote_views) } /// Iterates over `(name, remote_ref)`s for all remote branches of the /// specified remote in lexicographical order. pub fn remote_branches(&self, remote_name: &str) -> impl Iterator { let maybe_remote_view = self.data.remote_views.get(remote_name); maybe_remote_view .map(|remote_view| { remote_view .branches .iter() .map(|(name, remote_ref)| (name.as_ref(), remote_ref)) }) .into_iter() .flatten() } /// Iterates over `(name, remote_ref)`s for all remote branches of the /// specified remote that match the given pattern. /// /// Entries are sorted by `(name, remote_name)`. pub fn remote_branches_matching<'a: 'b, 'b>( &'a self, branch_pattern: &'b StringPattern, remote_pattern: &'b StringPattern, ) -> impl Iterator + 'b { // Use kmerge instead of flat_map for consistency with all_remote_branches(). remote_pattern .filter_btree_map(&self.data.remote_views) .map(|(remote_name, remote_view)| { branch_pattern.filter_btree_map(&remote_view.branches).map( |(branch_name, remote_ref)| { let full_name = (branch_name.as_ref(), remote_name.as_ref()); (full_name, remote_ref) }, ) }) .kmerge_by(|(full_name1, _), (full_name2, _)| full_name1 < full_name2) } pub fn get_remote_branch(&self, name: &str, remote_name: &str) -> &RemoteRef { if let Some(remote_view) = self.data.remote_views.get(remote_name) { remote_view.branches.get(name).flatten() } else { RemoteRef::absent_ref() } } /// Sets remote-tracking branch to the given target and state. If the target /// is absent, the branch will be removed. pub fn set_remote_branch(&mut self, name: &str, remote_name: &str, remote_ref: RemoteRef) { if remote_ref.is_present() { let remote_view = self .data .remote_views .entry(remote_name.to_owned()) .or_default(); remote_view.branches.insert(name.to_owned(), remote_ref); } else if let Some(remote_view) = self.data.remote_views.get_mut(remote_name) { remote_view.branches.remove(name); } } /// Iterates over `(name, {local_ref, remote_ref})`s for every branch /// present locally and/or on the specified remote, in lexicographical /// order. /// /// Note that this does *not* take into account whether the local branch /// tracks the remote branch or not. Missing values are represented as /// RefTarget::absent_ref() or RemoteRef::absent_ref(). pub fn local_remote_branches<'a>( &'a self, remote_name: &str, ) -> impl Iterator)> + 'a { refs::iter_named_local_remote_refs(self.local_branches(), self.remote_branches(remote_name)) .map(|(name, (local_target, remote_ref))| { let targets = LocalAndRemoteRef { local_target, remote_ref, }; (name, targets) }) } /// Iterates over `(name, TrackingRefPair {local_ref, remote_ref})`s for /// every branch with a name that matches the given pattern, and that is /// present locally and/or on the specified remote. /// /// Entries are sorted by `name`. /// /// Note that this does *not* take into account whether the local branch /// tracks the remote branch or not. Missing values are represented as /// RefTarget::absent_ref() or RemoteRef::absent_ref(). pub fn local_remote_branches_matching<'a: 'b, 'b>( &'a self, branch_pattern: &'b StringPattern, remote_name: &str, ) -> impl Iterator)> + 'b { // Change remote_name to StringPattern if needed, but merge-join adapter won't // be usable. let maybe_remote_view = self.data.remote_views.get(remote_name); refs::iter_named_local_remote_refs( branch_pattern.filter_btree_map(&self.data.local_branches), maybe_remote_view .map(|remote_view| branch_pattern.filter_btree_map(&remote_view.branches)) .into_iter() .flatten(), ) .map(|(name, (local_target, remote_ref))| { let targets = LocalAndRemoteRef { local_target, remote_ref, }; (name.as_ref(), targets) }) } pub fn remove_remote(&mut self, remote_name: &str) { self.data.remote_views.remove(remote_name); } pub fn rename_remote(&mut self, old: &str, new: &str) { if let Some(remote_view) = self.data.remote_views.remove(old) { self.data.remote_views.insert(new.to_owned(), remote_view); } } pub fn get_tag(&self, name: &str) -> &RefTarget { self.data.tags.get(name).flatten() } /// Sets tag to point to the given target. If the target is absent, the tag /// will be removed. pub fn set_tag_target(&mut self, name: &str, target: RefTarget) { if target.is_present() { self.data.tags.insert(name.to_owned(), target); } else { self.data.tags.remove(name); } } pub fn get_git_ref(&self, name: &str) -> &RefTarget { self.data.git_refs.get(name).flatten() } /// Sets the last imported Git ref to point to the given target. If the /// target is absent, the reference will be removed. pub fn set_git_ref_target(&mut self, name: &str, target: RefTarget) { if target.is_present() { self.data.git_refs.insert(name.to_owned(), target); } else { self.data.git_refs.remove(name); } } /// Sets `HEAD@git` to point to the given target. If the target is absent, /// the reference will be cleared. pub fn set_git_head_target(&mut self, target: RefTarget) { self.data.git_head = target; } /// Iterates all commit ids referenced by this view. /// /// This can include hidden commits referenced by remote branches, previous /// positions of conflicted branches, etc. The ancestors and predecessors of /// the returned commits should be considered reachable from the view. Use /// this to build commit index from scratch. /// /// The iteration order is unspecified, and may include duplicated entries. pub fn all_referenced_commit_ids(&self) -> impl Iterator { // Include both added/removed ids since ancestry information of old // references will be needed while merging views. fn ref_target_ids(target: &RefTarget) -> impl Iterator { target.as_merge().iter().flatten() } // Some of the fields (e.g. wc_commit_ids) would be redundant, but let's // not be smart here. Callers will build a larger set of commits anyway. let op_store::View { head_ids, local_branches, tags, remote_views, git_refs, git_head, wc_commit_ids, } = &self.data; itertools::chain!( head_ids, local_branches.values().flat_map(ref_target_ids), tags.values().flat_map(ref_target_ids), remote_views.values().flat_map(|remote_view| { let op_store::RemoteView { branches } = remote_view; branches .values() .flat_map(|remote_ref| ref_target_ids(&remote_ref.target)) }), git_refs.values().flat_map(ref_target_ids), ref_target_ids(git_head), wc_commit_ids.values() ) } pub fn set_view(&mut self, data: op_store::View) { self.data = data; } pub fn store_view(&self) -> &op_store::View { &self.data } pub fn store_view_mut(&mut self) -> &mut op_store::View { &mut self.data } }