just playing with tangled
at globpattern 422 lines 15 kB view raw
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::op_store::WorkspaceId; 30use crate::refs; 31use crate::refs::LocalAndRemoteRef; 32use crate::refs::RemoteRefSymbol; 33use crate::str_util::StringPattern; 34 35/// A wrapper around [`op_store::View`] that defines additional methods. 36#[derive(PartialEq, Eq, Debug, Clone)] 37pub struct View { 38 data: op_store::View, 39} 40 41impl View { 42 pub fn new(op_store_view: op_store::View) -> Self { 43 View { 44 data: op_store_view, 45 } 46 } 47 48 pub fn wc_commit_ids(&self) -> &BTreeMap<WorkspaceId, CommitId> { 49 &self.data.wc_commit_ids 50 } 51 52 pub fn get_wc_commit_id(&self, workspace_id: &WorkspaceId) -> Option<&CommitId> { 53 self.data.wc_commit_ids.get(workspace_id) 54 } 55 56 pub fn workspaces_for_wc_commit_id(&self, commit_id: &CommitId) -> Vec<WorkspaceId> { 57 let mut workspaces_ids = vec![]; 58 for (workspace_id, wc_commit_id) in &self.data.wc_commit_ids { 59 if wc_commit_id == commit_id { 60 workspaces_ids.push(workspace_id.clone()); 61 } 62 } 63 workspaces_ids 64 } 65 66 pub fn is_wc_commit_id(&self, commit_id: &CommitId) -> bool { 67 self.data.wc_commit_ids.values().contains(commit_id) 68 } 69 70 pub fn heads(&self) -> &HashSet<CommitId> { 71 &self.data.head_ids 72 } 73 74 /// Iterates pair of local and remote bookmarks by bookmark name. 75 pub fn bookmarks(&self) -> impl Iterator<Item = (&str, BookmarkTarget<'_>)> { 76 op_store::merge_join_bookmark_views(&self.data.local_bookmarks, &self.data.remote_views) 77 } 78 79 pub fn tags(&self) -> &BTreeMap<String, RefTarget> { 80 &self.data.tags 81 } 82 83 pub fn git_refs(&self) -> &BTreeMap<String, RefTarget> { 84 &self.data.git_refs 85 } 86 87 pub fn git_head(&self) -> &RefTarget { 88 &self.data.git_head 89 } 90 91 pub fn set_wc_commit(&mut self, workspace_id: WorkspaceId, commit_id: CommitId) { 92 self.data.wc_commit_ids.insert(workspace_id, commit_id); 93 } 94 95 pub fn remove_wc_commit(&mut self, workspace_id: &WorkspaceId) { 96 self.data.wc_commit_ids.remove(workspace_id); 97 } 98 99 pub fn rename_workspace( 100 &mut self, 101 old_workspace_id: &WorkspaceId, 102 new_workspace_id: WorkspaceId, 103 ) -> Result<(), RenameWorkspaceError> { 104 if self.data.wc_commit_ids.contains_key(&new_workspace_id) { 105 return Err(RenameWorkspaceError::WorkspaceAlreadyExists { 106 workspace_id: new_workspace_id.as_str().to_owned(), 107 }); 108 } 109 let wc_commit_id = self 110 .data 111 .wc_commit_ids 112 .remove(old_workspace_id) 113 .ok_or_else(|| RenameWorkspaceError::WorkspaceDoesNotExist { 114 workspace_id: old_workspace_id.as_str().to_owned(), 115 })?; 116 self.data 117 .wc_commit_ids 118 .insert(new_workspace_id, wc_commit_id); 119 Ok(()) 120 } 121 122 pub fn add_head(&mut self, head_id: &CommitId) { 123 self.data.head_ids.insert(head_id.clone()); 124 } 125 126 pub fn remove_head(&mut self, head_id: &CommitId) { 127 self.data.head_ids.remove(head_id); 128 } 129 130 /// Iterates local bookmark `(name, target)`s in lexicographical order. 131 pub fn local_bookmarks(&self) -> impl Iterator<Item = (&str, &RefTarget)> { 132 self.data 133 .local_bookmarks 134 .iter() 135 .map(|(name, target)| (name.as_ref(), target)) 136 } 137 138 /// Iterates local bookmarks `(name, target)` in lexicographical order where 139 /// the target adds `commit_id`. 140 pub fn local_bookmarks_for_commit<'a, 'b>( 141 &'a self, 142 commit_id: &'b CommitId, 143 ) -> impl Iterator<Item = (&'a str, &'a RefTarget)> + use<'a, 'b> { 144 self.local_bookmarks() 145 .filter(|(_, target)| target.added_ids().contains(commit_id)) 146 } 147 148 /// Iterates local bookmark `(name, target)`s matching the given pattern. 149 /// Entries are sorted by `name`. 150 pub fn local_bookmarks_matching<'a, 'b>( 151 &'a self, 152 pattern: &'b StringPattern, 153 ) -> impl Iterator<Item = (&'a str, &'a RefTarget)> + use<'a, 'b> { 154 pattern 155 .filter_btree_map(&self.data.local_bookmarks) 156 .map(|(name, target)| (name.as_ref(), target)) 157 } 158 159 pub fn get_local_bookmark(&self, name: &str) -> &RefTarget { 160 self.data.local_bookmarks.get(name).flatten() 161 } 162 163 /// Sets local bookmark to point to the given target. If the target is 164 /// absent, and if no associated remote bookmarks exist, the bookmark 165 /// will be removed. 166 pub fn set_local_bookmark_target(&mut self, name: &str, target: RefTarget) { 167 if target.is_present() { 168 self.data.local_bookmarks.insert(name.to_owned(), target); 169 } else { 170 self.data.local_bookmarks.remove(name); 171 } 172 } 173 174 /// Iterates over `(symbol, remote_ref)` for all remote bookmarks in 175 /// lexicographical order. 176 pub fn all_remote_bookmarks(&self) -> impl Iterator<Item = (RemoteRefSymbol<'_>, &RemoteRef)> { 177 op_store::flatten_remote_bookmarks(&self.data.remote_views) 178 .map(|((name, remote), remote_ref)| (RemoteRefSymbol { name, remote }, remote_ref)) 179 } 180 181 /// Iterates over `(name, remote_ref)`s for all remote bookmarks of the 182 /// specified remote in lexicographical order. 183 pub fn remote_bookmarks( 184 &self, 185 remote_name: &str, 186 ) -> impl Iterator<Item = (&str, &RemoteRef)> + use<'_> { 187 let maybe_remote_view = self.data.remote_views.get(remote_name); 188 maybe_remote_view 189 .map(|remote_view| { 190 remote_view 191 .bookmarks 192 .iter() 193 .map(|(name, remote_ref)| (name.as_ref(), remote_ref)) 194 }) 195 .into_iter() 196 .flatten() 197 } 198 199 /// Iterates over `(symbol, remote_ref)`s for all remote bookmarks of the 200 /// specified remote that match the given pattern. 201 /// 202 /// Entries are sorted by `symbol`, which is `(name, remote)`. 203 pub fn remote_bookmarks_matching<'a, 'b>( 204 &'a self, 205 bookmark_pattern: &'b StringPattern, 206 remote_pattern: &'b StringPattern, 207 ) -> impl Iterator<Item = (RemoteRefSymbol<'a>, &'a RemoteRef)> + use<'a, 'b> { 208 // Use kmerge instead of flat_map for consistency with all_remote_bookmarks(). 209 remote_pattern 210 .filter_btree_map(&self.data.remote_views) 211 .map(|(remote, remote_view)| { 212 bookmark_pattern 213 .filter_btree_map(&remote_view.bookmarks) 214 .map(|(name, remote_ref)| { 215 let symbol = RemoteRefSymbol { name, remote }; 216 (symbol, remote_ref) 217 }) 218 }) 219 .kmerge_by(|(symbol1, _), (symbol2, _)| symbol1 < symbol2) 220 } 221 222 pub fn get_remote_bookmark(&self, symbol: RemoteRefSymbol<'_>) -> &RemoteRef { 223 if let Some(remote_view) = self.data.remote_views.get(symbol.remote) { 224 remote_view.bookmarks.get(symbol.name).flatten() 225 } else { 226 RemoteRef::absent_ref() 227 } 228 } 229 230 /// Sets remote-tracking bookmark to the given target and state. If the 231 /// target is absent, the bookmark will be removed. 232 pub fn set_remote_bookmark(&mut self, symbol: RemoteRefSymbol<'_>, remote_ref: RemoteRef) { 233 if remote_ref.is_present() { 234 let remote_view = self 235 .data 236 .remote_views 237 .entry(symbol.remote.to_owned()) 238 .or_default(); 239 remote_view 240 .bookmarks 241 .insert(symbol.name.to_owned(), remote_ref); 242 } else if let Some(remote_view) = self.data.remote_views.get_mut(symbol.remote) { 243 remote_view.bookmarks.remove(symbol.name); 244 } 245 } 246 247 /// Iterates over `(name, {local_ref, remote_ref})`s for every bookmark 248 /// present locally and/or on the specified remote, in lexicographical 249 /// order. 250 /// 251 /// Note that this does *not* take into account whether the local bookmark 252 /// tracks the remote bookmark or not. Missing values are represented as 253 /// RefTarget::absent_ref() or RemoteRef::absent_ref(). 254 pub fn local_remote_bookmarks( 255 &self, 256 remote_name: &str, 257 ) -> impl Iterator<Item = (&str, LocalAndRemoteRef<'_>)> + use<'_> { 258 refs::iter_named_local_remote_refs( 259 self.local_bookmarks(), 260 self.remote_bookmarks(remote_name), 261 ) 262 .map(|(name, (local_target, remote_ref))| { 263 let targets = LocalAndRemoteRef { 264 local_target, 265 remote_ref, 266 }; 267 (name, targets) 268 }) 269 } 270 271 /// Iterates over `(name, TrackingRefPair {local_ref, remote_ref})`s for 272 /// every bookmark with a name that matches the given pattern, and that is 273 /// present locally and/or on the specified remote. 274 /// 275 /// Entries are sorted by `name`. 276 /// 277 /// Note that this does *not* take into account whether the local bookmark 278 /// tracks the remote bookmark or not. Missing values are represented as 279 /// RefTarget::absent_ref() or RemoteRef::absent_ref(). 280 pub fn local_remote_bookmarks_matching<'a, 'b>( 281 &'a self, 282 bookmark_pattern: &'b StringPattern, 283 remote_name: &str, 284 ) -> impl Iterator<Item = (&'a str, LocalAndRemoteRef<'a>)> + use<'a, 'b> { 285 // Change remote_name to StringPattern if needed, but merge-join adapter won't 286 // be usable. 287 let maybe_remote_view = self.data.remote_views.get(remote_name); 288 refs::iter_named_local_remote_refs( 289 bookmark_pattern.filter_btree_map(&self.data.local_bookmarks), 290 maybe_remote_view 291 .map(|remote_view| bookmark_pattern.filter_btree_map(&remote_view.bookmarks)) 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: &str) { 305 self.data.remote_views.remove(remote_name); 306 } 307 308 pub fn rename_remote(&mut self, old: &str, new: &str) { 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: &str) -> &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 str, &'a RefTarget)> + use<'a, 'b> { 324 pattern 325 .filter_btree_map(&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: &str, 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: &str) -> &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: &str, 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 {workspace_id} not found")] 418 WorkspaceDoesNotExist { workspace_id: String }, 419 420 #[error("Workspace {workspace_id} already exists")] 421 WorkspaceAlreadyExists { workspace_id: String }, 422}