1#![allow(missing_docs)]
16
17use std::any::Any;
18use std::collections::hash_map;
19use std::collections::HashMap;
20use std::convert::Infallible;
21use std::fmt;
22use std::ops::ControlFlow;
23use std::ops::Range;
24use std::rc::Rc;
25use std::sync::Arc;
26
27use itertools::Itertools as _;
28use once_cell::sync::Lazy;
29use thiserror::Error;
30
31use crate::backend::BackendError;
32use crate::backend::ChangeId;
33use crate::backend::CommitId;
34use crate::commit::Commit;
35use crate::dsl_util;
36use crate::dsl_util::collect_similar;
37use crate::fileset;
38use crate::fileset::FilesetDiagnostics;
39use crate::fileset::FilesetExpression;
40use crate::graph::GraphNode;
41use crate::id_prefix::IdPrefixContext;
42use crate::id_prefix::IdPrefixIndex;
43use crate::object_id::HexPrefix;
44use crate::object_id::PrefixResolution;
45use crate::op_store::RemoteRefState;
46use crate::op_walk;
47use crate::ref_name::RemoteRefSymbol;
48use crate::ref_name::RemoteRefSymbolBuf;
49use crate::ref_name::WorkspaceName;
50use crate::ref_name::WorkspaceNameBuf;
51use crate::repo::ReadonlyRepo;
52use crate::repo::Repo;
53use crate::repo::RepoLoaderError;
54use crate::repo_path::RepoPathUiConverter;
55use crate::revset_parser;
56pub use crate::revset_parser::expect_literal;
57pub use crate::revset_parser::parse_program;
58pub use crate::revset_parser::parse_symbol;
59pub use crate::revset_parser::BinaryOp;
60pub use crate::revset_parser::ExpressionKind;
61pub use crate::revset_parser::ExpressionNode;
62pub use crate::revset_parser::FunctionCallNode;
63pub use crate::revset_parser::RevsetAliasesMap;
64pub use crate::revset_parser::RevsetDiagnostics;
65pub use crate::revset_parser::RevsetParseError;
66pub use crate::revset_parser::RevsetParseErrorKind;
67pub use crate::revset_parser::UnaryOp;
68use crate::store::Store;
69use crate::str_util::StringPattern;
70use crate::time_util::DatePattern;
71use crate::time_util::DatePatternContext;
72
73#[derive(Debug, Error)]
75pub enum RevsetResolutionError {
76 #[error("Revision `{name}` doesn't exist")]
77 NoSuchRevision {
78 name: String,
79 candidates: Vec<String>,
80 },
81 #[error("Workspace `{}` doesn't have a working-copy commit", name.as_symbol())]
82 WorkspaceMissingWorkingCopy { name: WorkspaceNameBuf },
83 #[error("An empty string is not a valid revision")]
84 EmptyString,
85 #[error("Commit ID prefix `{0}` is ambiguous")]
86 AmbiguousCommitIdPrefix(String),
87 #[error("Change ID prefix `{0}` is ambiguous")]
88 AmbiguousChangeIdPrefix(String),
89 #[error("Unexpected error from commit backend")]
90 Backend(#[source] BackendError),
91 #[error(transparent)]
92 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
93}
94
95#[derive(Debug, Error)]
97pub enum RevsetEvaluationError {
98 #[error("Unexpected error from commit backend")]
99 Backend(#[from] BackendError),
100 #[error(transparent)]
101 Other(Box<dyn std::error::Error + Send + Sync>),
102}
103
104impl RevsetEvaluationError {
105 pub fn into_backend_error(self) -> BackendError {
108 match self {
109 Self::Backend(err) => err,
110 Self::Other(err) => BackendError::Other(err),
111 }
112 }
113}
114
115pub const GENERATION_RANGE_FULL: Range<u64> = 0..u64::MAX;
117pub const GENERATION_RANGE_EMPTY: Range<u64> = 0..0;
118
119#[derive(Clone, Copy, Debug, Eq, PartialEq)]
124pub enum RevsetModifier {
125 All,
128}
129
130#[derive(Clone, Debug)]
132pub enum RevsetCommitRef {
133 WorkingCopy(WorkspaceNameBuf),
134 WorkingCopies,
135 Symbol(String),
136 RemoteSymbol(RemoteRefSymbolBuf),
137 ChangeId(HexPrefix),
138 CommitId(HexPrefix),
139 Bookmarks(StringPattern),
140 RemoteBookmarks {
141 bookmark_pattern: StringPattern,
142 remote_pattern: StringPattern,
143 remote_ref_state: Option<RemoteRefState>,
144 },
145 Tags(StringPattern),
146 GitRefs,
147 GitHead,
148}
149
150pub trait RevsetFilterExtension: std::fmt::Debug + Any {
152 fn as_any(&self) -> &dyn Any;
153
154 fn matches_commit(&self, commit: &Commit) -> bool;
156}
157
158#[derive(Clone, Debug)]
159pub enum RevsetFilterPredicate {
160 ParentCount(Range<u32>),
162 Description(StringPattern),
164 Subject(StringPattern),
166 AuthorName(StringPattern),
168 AuthorEmail(StringPattern),
170 AuthorDate(DatePattern),
172 CommitterName(StringPattern),
174 CommitterEmail(StringPattern),
176 CommitterDate(DatePattern),
178 File(FilesetExpression),
180 DiffContains {
182 text: StringPattern,
183 files: FilesetExpression,
184 },
185 HasConflict,
187 Signed,
189 Extension(Rc<dyn RevsetFilterExtension>),
191}
192
193mod private {
194 pub trait ExpressionState {
196 type CommitRef: Clone;
197 type Operation: Clone;
198 }
199
200 #[derive(Debug)]
202 pub enum UserExpressionState {}
203 #[derive(Debug)]
204 pub enum ResolvedExpressionState {}
205}
206
207use private::ExpressionState;
208use private::ResolvedExpressionState;
209use private::UserExpressionState;
210
211impl ExpressionState for UserExpressionState {
212 type CommitRef = RevsetCommitRef;
213 type Operation = String;
214}
215
216impl ExpressionState for ResolvedExpressionState {
217 type CommitRef = Infallible;
218 type Operation = Infallible;
219}
220
221pub type UserRevsetExpression = RevsetExpression<UserExpressionState>;
223pub type ResolvedRevsetExpression = RevsetExpression<ResolvedExpressionState>;
225
226#[derive(Clone, Debug)]
231pub enum RevsetExpression<St: ExpressionState> {
232 None,
233 All,
234 VisibleHeads,
235 VisibleHeadsOrReferenced,
238 Root,
239 Commits(Vec<CommitId>),
240 CommitRef(St::CommitRef),
241 Ancestors {
242 heads: Rc<Self>,
243 generation: Range<u64>,
244 },
245 Descendants {
246 roots: Rc<Self>,
247 generation: Range<u64>,
248 },
249 Range {
251 roots: Rc<Self>,
252 heads: Rc<Self>,
253 generation: Range<u64>,
254 },
255 DagRange {
257 roots: Rc<Self>,
258 heads: Rc<Self>,
259 },
261 Reachable {
263 sources: Rc<Self>,
264 domain: Rc<Self>,
265 },
266 Heads(Rc<Self>),
267 HeadsRange {
270 roots: Rc<Self>,
271 heads: Rc<Self>,
272 filter: Rc<Self>,
273 },
274 Roots(Rc<Self>),
275 ForkPoint(Rc<Self>),
276 Latest {
277 candidates: Rc<Self>,
278 count: usize,
279 },
280 Filter(RevsetFilterPredicate),
281 AsFilter(Rc<Self>),
283 AtOperation {
285 operation: St::Operation,
286 candidates: Rc<Self>,
287 },
288 WithinReference {
291 candidates: Rc<Self>,
292 commits: Vec<CommitId>,
294 },
295 WithinVisibility {
297 candidates: Rc<Self>,
298 visible_heads: Vec<CommitId>,
300 },
301 Coalesce(Rc<Self>, Rc<Self>),
302 Present(Rc<Self>),
303 NotIn(Rc<Self>),
304 Union(Rc<Self>, Rc<Self>),
305 Intersection(Rc<Self>, Rc<Self>),
306 Difference(Rc<Self>, Rc<Self>),
307}
308
309impl<St: ExpressionState> RevsetExpression<St> {
312 pub fn none() -> Rc<Self> {
313 Rc::new(Self::None)
314 }
315
316 pub fn all() -> Rc<Self> {
319 Rc::new(Self::All)
320 }
321
322 pub fn visible_heads() -> Rc<Self> {
323 Rc::new(Self::VisibleHeads)
324 }
325
326 fn visible_heads_or_referenced() -> Rc<Self> {
327 Rc::new(Self::VisibleHeadsOrReferenced)
328 }
329
330 pub fn root() -> Rc<Self> {
331 Rc::new(Self::Root)
332 }
333
334 pub fn commit(commit_id: CommitId) -> Rc<Self> {
335 Self::commits(vec![commit_id])
336 }
337
338 pub fn commits(commit_ids: Vec<CommitId>) -> Rc<Self> {
339 Rc::new(Self::Commits(commit_ids))
340 }
341
342 pub fn filter(predicate: RevsetFilterPredicate) -> Rc<Self> {
343 Rc::new(Self::Filter(predicate))
344 }
345
346 pub fn is_empty() -> Rc<Self> {
348 Self::filter(RevsetFilterPredicate::File(FilesetExpression::all())).negated()
349 }
350}
351
352impl<St: ExpressionState<CommitRef = RevsetCommitRef>> RevsetExpression<St> {
354 pub fn working_copy(name: WorkspaceNameBuf) -> Rc<Self> {
355 Rc::new(Self::CommitRef(RevsetCommitRef::WorkingCopy(name)))
356 }
357
358 pub fn working_copies() -> Rc<Self> {
359 Rc::new(Self::CommitRef(RevsetCommitRef::WorkingCopies))
360 }
361
362 pub fn symbol(value: String) -> Rc<Self> {
363 Rc::new(Self::CommitRef(RevsetCommitRef::Symbol(value)))
364 }
365
366 pub fn remote_symbol(value: RemoteRefSymbolBuf) -> Rc<Self> {
367 let commit_ref = RevsetCommitRef::RemoteSymbol(value);
368 Rc::new(Self::CommitRef(commit_ref))
369 }
370
371 pub fn change_id_prefix(prefix: HexPrefix) -> Rc<Self> {
372 let commit_ref = RevsetCommitRef::ChangeId(prefix);
373 Rc::new(Self::CommitRef(commit_ref))
374 }
375
376 pub fn commit_id_prefix(prefix: HexPrefix) -> Rc<Self> {
377 let commit_ref = RevsetCommitRef::CommitId(prefix);
378 Rc::new(Self::CommitRef(commit_ref))
379 }
380
381 pub fn bookmarks(pattern: StringPattern) -> Rc<Self> {
382 Rc::new(Self::CommitRef(RevsetCommitRef::Bookmarks(pattern)))
383 }
384
385 pub fn remote_bookmarks(
386 bookmark_pattern: StringPattern,
387 remote_pattern: StringPattern,
388 remote_ref_state: Option<RemoteRefState>,
389 ) -> Rc<Self> {
390 Rc::new(Self::CommitRef(RevsetCommitRef::RemoteBookmarks {
391 bookmark_pattern,
392 remote_pattern,
393 remote_ref_state,
394 }))
395 }
396
397 pub fn tags(pattern: StringPattern) -> Rc<Self> {
398 Rc::new(Self::CommitRef(RevsetCommitRef::Tags(pattern)))
399 }
400
401 pub fn git_refs() -> Rc<Self> {
402 Rc::new(Self::CommitRef(RevsetCommitRef::GitRefs))
403 }
404
405 pub fn git_head() -> Rc<Self> {
406 Rc::new(Self::CommitRef(RevsetCommitRef::GitHead))
407 }
408}
409
410impl<St: ExpressionState> RevsetExpression<St> {
412 pub fn latest(self: &Rc<Self>, count: usize) -> Rc<Self> {
413 Rc::new(Self::Latest {
414 candidates: self.clone(),
415 count,
416 })
417 }
418
419 pub fn heads(self: &Rc<Self>) -> Rc<Self> {
421 Rc::new(Self::Heads(self.clone()))
422 }
423
424 pub fn roots(self: &Rc<Self>) -> Rc<Self> {
426 Rc::new(Self::Roots(self.clone()))
427 }
428
429 pub fn parents(self: &Rc<Self>) -> Rc<Self> {
431 self.ancestors_at(1)
432 }
433
434 pub fn ancestors(self: &Rc<Self>) -> Rc<Self> {
436 self.ancestors_range(GENERATION_RANGE_FULL)
437 }
438
439 pub fn ancestors_at(self: &Rc<Self>, generation: u64) -> Rc<Self> {
442 self.ancestors_range(generation..generation.saturating_add(1))
443 }
444
445 pub fn ancestors_range(self: &Rc<Self>, generation_range: Range<u64>) -> Rc<Self> {
447 Rc::new(Self::Ancestors {
448 heads: self.clone(),
449 generation: generation_range,
450 })
451 }
452
453 pub fn children(self: &Rc<Self>) -> Rc<Self> {
455 self.descendants_at(1)
456 }
457
458 pub fn descendants(self: &Rc<Self>) -> Rc<Self> {
460 self.descendants_range(GENERATION_RANGE_FULL)
461 }
462
463 pub fn descendants_at(self: &Rc<Self>, generation: u64) -> Rc<Self> {
466 self.descendants_range(generation..generation.saturating_add(1))
467 }
468
469 pub fn descendants_range(self: &Rc<Self>, generation_range: Range<u64>) -> Rc<Self> {
471 Rc::new(Self::Descendants {
472 roots: self.clone(),
473 generation: generation_range,
474 })
475 }
476
477 pub fn fork_point(self: &Rc<Self>) -> Rc<Self> {
479 Rc::new(Self::ForkPoint(self.clone()))
480 }
481
482 pub fn filtered(self: &Rc<Self>, predicate: RevsetFilterPredicate) -> Rc<Self> {
484 self.intersection(&Self::filter(predicate))
485 }
486
487 pub fn dag_range_to(self: &Rc<Self>, heads: &Rc<Self>) -> Rc<Self> {
490 Rc::new(Self::DagRange {
491 roots: self.clone(),
492 heads: heads.clone(),
493 })
494 }
495
496 pub fn connected(self: &Rc<Self>) -> Rc<Self> {
499 self.dag_range_to(self)
500 }
501
502 pub fn reachable(self: &Rc<Self>, domain: &Rc<Self>) -> Rc<Self> {
505 Rc::new(Self::Reachable {
506 sources: self.clone(),
507 domain: domain.clone(),
508 })
509 }
510
511 pub fn range(self: &Rc<Self>, heads: &Rc<Self>) -> Rc<Self> {
513 Rc::new(Self::Range {
514 roots: self.clone(),
515 heads: heads.clone(),
516 generation: GENERATION_RANGE_FULL,
517 })
518 }
519
520 pub fn present(self: &Rc<Self>) -> Rc<Self> {
522 Rc::new(Self::Present(self.clone()))
523 }
524
525 pub fn negated(self: &Rc<Self>) -> Rc<Self> {
527 Rc::new(Self::NotIn(self.clone()))
528 }
529
530 pub fn union(self: &Rc<Self>, other: &Rc<Self>) -> Rc<Self> {
532 Rc::new(Self::Union(self.clone(), other.clone()))
533 }
534
535 pub fn union_all(expressions: &[Rc<Self>]) -> Rc<Self> {
537 to_binary_expression(expressions, &Self::none, &Self::union)
538 }
539
540 pub fn intersection(self: &Rc<Self>, other: &Rc<Self>) -> Rc<Self> {
542 Rc::new(Self::Intersection(self.clone(), other.clone()))
543 }
544
545 pub fn minus(self: &Rc<Self>, other: &Rc<Self>) -> Rc<Self> {
547 Rc::new(Self::Difference(self.clone(), other.clone()))
548 }
549
550 pub fn coalesce(expressions: &[Rc<Self>]) -> Rc<Self> {
553 to_binary_expression(expressions, &Self::none, &Self::coalesce2)
554 }
555
556 fn coalesce2(self: &Rc<Self>, other: &Rc<Self>) -> Rc<Self> {
557 Rc::new(Self::Coalesce(self.clone(), other.clone()))
558 }
559}
560
561impl<St: ExpressionState<CommitRef = RevsetCommitRef>> RevsetExpression<St> {
562 pub fn as_symbol(&self) -> Option<&str> {
564 match self {
565 RevsetExpression::CommitRef(RevsetCommitRef::Symbol(name)) => Some(name),
566 _ => None,
567 }
568 }
569}
570
571impl UserRevsetExpression {
572 pub fn resolve_user_expression(
575 &self,
576 repo: &dyn Repo,
577 symbol_resolver: &SymbolResolver,
578 ) -> Result<Rc<ResolvedRevsetExpression>, RevsetResolutionError> {
579 resolve_symbols(repo, self, symbol_resolver)
580 }
581}
582
583impl ResolvedRevsetExpression {
584 pub fn evaluate<'index>(
586 self: Rc<Self>,
587 repo: &'index dyn Repo,
588 ) -> Result<Box<dyn Revset + 'index>, RevsetEvaluationError> {
589 let expr = optimize(self).to_backend_expression(repo);
590 repo.index().evaluate_revset(&expr, repo.store())
591 }
592
593 pub fn evaluate_unoptimized<'index>(
598 self: &Rc<Self>,
599 repo: &'index dyn Repo,
600 ) -> Result<Box<dyn Revset + 'index>, RevsetEvaluationError> {
601 let expr = resolve_referenced_commits(self)
604 .as_ref()
605 .unwrap_or(self)
606 .to_backend_expression(repo);
607 repo.index().evaluate_revset(&expr, repo.store())
608 }
609
610 pub fn to_backend_expression(&self, repo: &dyn Repo) -> ResolvedExpression {
613 resolve_visibility(repo, self)
614 }
615}
616
617#[derive(Clone, Debug)]
618pub enum ResolvedPredicateExpression {
619 Filter(RevsetFilterPredicate),
621 Set(Box<ResolvedExpression>),
624 NotIn(Box<Self>),
625 Union(Box<Self>, Box<Self>),
626 Intersection(Box<Self>, Box<Self>),
627}
628
629#[derive(Clone, Debug)]
637pub enum ResolvedExpression {
638 Commits(Vec<CommitId>),
639 Ancestors {
640 heads: Box<Self>,
641 generation: Range<u64>,
642 },
643 Range {
645 roots: Box<Self>,
646 heads: Box<Self>,
647 generation: Range<u64>,
648 },
649 DagRange {
651 roots: Box<Self>,
652 heads: Box<Self>,
653 generation_from_roots: Range<u64>,
654 },
655 Reachable {
657 sources: Box<Self>,
658 domain: Box<Self>,
659 },
660 Heads(Box<Self>),
661 HeadsRange {
664 roots: Box<Self>,
665 heads: Box<Self>,
666 filter: Option<ResolvedPredicateExpression>,
667 },
668 Roots(Box<Self>),
669 ForkPoint(Box<Self>),
670 Latest {
671 candidates: Box<Self>,
672 count: usize,
673 },
674 Coalesce(Box<Self>, Box<Self>),
675 Union(Box<Self>, Box<Self>),
676 FilterWithin {
678 candidates: Box<Self>,
679 predicate: ResolvedPredicateExpression,
680 },
681 Intersection(Box<Self>, Box<Self>),
683 Difference(Box<Self>, Box<Self>),
684}
685
686pub type RevsetFunction = fn(
687 &mut RevsetDiagnostics,
688 &FunctionCallNode,
689 &LoweringContext,
690) -> Result<Rc<UserRevsetExpression>, RevsetParseError>;
691
692static BUILTIN_FUNCTION_MAP: Lazy<HashMap<&'static str, RevsetFunction>> = Lazy::new(|| {
693 let mut map: HashMap<&'static str, RevsetFunction> = HashMap::new();
696 map.insert("parents", |diagnostics, function, context| {
697 let ([arg], [depth_opt_arg]) = function.expect_arguments()?;
698 let expression = lower_expression(diagnostics, arg, context)?;
699 if let Some(depth_arg) = depth_opt_arg {
700 let depth = expect_literal("integer", depth_arg)?;
701 Ok(expression.ancestors_at(depth))
702 } else {
703 Ok(expression.parents())
704 }
705 });
706 map.insert("children", |diagnostics, function, context| {
707 let ([arg], [depth_opt_arg]) = function.expect_arguments()?;
708 let expression = lower_expression(diagnostics, arg, context)?;
709 if let Some(depth_arg) = depth_opt_arg {
710 let depth = expect_literal("integer", depth_arg)?;
711 Ok(expression.descendants_at(depth))
712 } else {
713 Ok(expression.children())
714 }
715 });
716 map.insert("ancestors", |diagnostics, function, context| {
717 let ([heads_arg], [depth_opt_arg]) = function.expect_arguments()?;
718 let heads = lower_expression(diagnostics, heads_arg, context)?;
719 let generation = if let Some(depth_arg) = depth_opt_arg {
720 let depth = expect_literal("integer", depth_arg)?;
721 0..depth
722 } else {
723 GENERATION_RANGE_FULL
724 };
725 Ok(heads.ancestors_range(generation))
726 });
727 map.insert("descendants", |diagnostics, function, context| {
728 let ([roots_arg], [depth_opt_arg]) = function.expect_arguments()?;
729 let roots = lower_expression(diagnostics, roots_arg, context)?;
730 let generation = if let Some(depth_arg) = depth_opt_arg {
731 let depth = expect_literal("integer", depth_arg)?;
732 0..depth
733 } else {
734 GENERATION_RANGE_FULL
735 };
736 Ok(roots.descendants_range(generation))
737 });
738 map.insert("connected", |diagnostics, function, context| {
739 let [arg] = function.expect_exact_arguments()?;
740 let candidates = lower_expression(diagnostics, arg, context)?;
741 Ok(candidates.connected())
742 });
743 map.insert("reachable", |diagnostics, function, context| {
744 let [source_arg, domain_arg] = function.expect_exact_arguments()?;
745 let sources = lower_expression(diagnostics, source_arg, context)?;
746 let domain = lower_expression(diagnostics, domain_arg, context)?;
747 Ok(sources.reachable(&domain))
748 });
749 map.insert("none", |_diagnostics, function, _context| {
750 function.expect_no_arguments()?;
751 Ok(RevsetExpression::none())
752 });
753 map.insert("all", |_diagnostics, function, _context| {
754 function.expect_no_arguments()?;
755 Ok(RevsetExpression::all())
756 });
757 map.insert("working_copies", |_diagnostics, function, _context| {
758 function.expect_no_arguments()?;
759 Ok(RevsetExpression::working_copies())
760 });
761 map.insert("heads", |diagnostics, function, context| {
762 let [arg] = function.expect_exact_arguments()?;
763 let candidates = lower_expression(diagnostics, arg, context)?;
764 Ok(candidates.heads())
765 });
766 map.insert("roots", |diagnostics, function, context| {
767 let [arg] = function.expect_exact_arguments()?;
768 let candidates = lower_expression(diagnostics, arg, context)?;
769 Ok(candidates.roots())
770 });
771 map.insert("visible_heads", |_diagnostics, function, _context| {
772 function.expect_no_arguments()?;
773 Ok(RevsetExpression::visible_heads())
774 });
775 map.insert("root", |_diagnostics, function, _context| {
776 function.expect_no_arguments()?;
777 Ok(RevsetExpression::root())
778 });
779 map.insert("change_id", |diagnostics, function, _context| {
780 let [arg] = function.expect_exact_arguments()?;
781 let prefix = revset_parser::catch_aliases(diagnostics, arg, |_diagnostics, arg| {
782 let value = revset_parser::expect_string_literal("change ID prefix", arg)?;
783 HexPrefix::try_from_reverse_hex(value)
784 .ok_or_else(|| RevsetParseError::expression("Invalid change ID prefix", arg.span))
785 })?;
786 Ok(RevsetExpression::change_id_prefix(prefix))
787 });
788 map.insert("commit_id", |diagnostics, function, _context| {
789 let [arg] = function.expect_exact_arguments()?;
790 let prefix = revset_parser::catch_aliases(diagnostics, arg, |_diagnostics, arg| {
791 let value = revset_parser::expect_string_literal("commit ID prefix", arg)?;
792 HexPrefix::try_from_hex(value)
793 .ok_or_else(|| RevsetParseError::expression("Invalid commit ID prefix", arg.span))
794 })?;
795 Ok(RevsetExpression::commit_id_prefix(prefix))
796 });
797 map.insert("bookmarks", |diagnostics, function, _context| {
798 let ([], [opt_arg]) = function.expect_arguments()?;
799 let pattern = if let Some(arg) = opt_arg {
800 expect_string_pattern(diagnostics, arg)?
801 } else {
802 StringPattern::everything()
803 };
804 Ok(RevsetExpression::bookmarks(pattern))
805 });
806 map.insert("remote_bookmarks", |diagnostics, function, _context| {
807 parse_remote_bookmarks_arguments(diagnostics, function, None)
808 });
809 map.insert(
810 "tracked_remote_bookmarks",
811 |diagnostics, function, _context| {
812 parse_remote_bookmarks_arguments(diagnostics, function, Some(RemoteRefState::Tracked))
813 },
814 );
815 map.insert(
816 "untracked_remote_bookmarks",
817 |diagnostics, function, _context| {
818 parse_remote_bookmarks_arguments(diagnostics, function, Some(RemoteRefState::New))
819 },
820 );
821 map.insert("tags", |diagnostics, function, _context| {
822 let ([], [opt_arg]) = function.expect_arguments()?;
823 let pattern = if let Some(arg) = opt_arg {
824 expect_string_pattern(diagnostics, arg)?
825 } else {
826 StringPattern::everything()
827 };
828 Ok(RevsetExpression::tags(pattern))
829 });
830 map.insert("git_refs", |_diagnostics, function, _context| {
831 function.expect_no_arguments()?;
832 Ok(RevsetExpression::git_refs())
833 });
834 map.insert("git_head", |_diagnostics, function, _context| {
835 function.expect_no_arguments()?;
836 Ok(RevsetExpression::git_head())
837 });
838 map.insert("latest", |diagnostics, function, context| {
839 let ([candidates_arg], [count_opt_arg]) = function.expect_arguments()?;
840 let candidates = lower_expression(diagnostics, candidates_arg, context)?;
841 let count = if let Some(count_arg) = count_opt_arg {
842 expect_literal("integer", count_arg)?
843 } else {
844 1
845 };
846 Ok(candidates.latest(count))
847 });
848 map.insert("fork_point", |diagnostics, function, context| {
849 let [expression_arg] = function.expect_exact_arguments()?;
850 let expression = lower_expression(diagnostics, expression_arg, context)?;
851 Ok(RevsetExpression::fork_point(&expression))
852 });
853 map.insert("merges", |_diagnostics, function, _context| {
854 function.expect_no_arguments()?;
855 Ok(RevsetExpression::filter(
856 RevsetFilterPredicate::ParentCount(2..u32::MAX),
857 ))
858 });
859 map.insert("description", |diagnostics, function, _context| {
860 let [arg] = function.expect_exact_arguments()?;
861 let pattern = expect_string_pattern(diagnostics, arg)?;
862 Ok(RevsetExpression::filter(
863 RevsetFilterPredicate::Description(pattern),
864 ))
865 });
866 map.insert("subject", |diagnostics, function, _context| {
867 let [arg] = function.expect_exact_arguments()?;
868 let pattern = expect_string_pattern(diagnostics, arg)?;
869 let predicate = RevsetFilterPredicate::Subject(pattern);
870 Ok(RevsetExpression::filter(predicate))
871 });
872 map.insert("author", |diagnostics, function, _context| {
873 let [arg] = function.expect_exact_arguments()?;
874 let pattern = expect_string_pattern(diagnostics, arg)?;
875 let name_predicate = RevsetFilterPredicate::AuthorName(pattern.clone());
876 let email_predicate = RevsetFilterPredicate::AuthorEmail(pattern);
877 Ok(RevsetExpression::filter(name_predicate)
878 .union(&RevsetExpression::filter(email_predicate)))
879 });
880 map.insert("author_name", |diagnostics, function, _context| {
881 let [arg] = function.expect_exact_arguments()?;
882 let pattern = expect_string_pattern(diagnostics, arg)?;
883 let predicate = RevsetFilterPredicate::AuthorName(pattern);
884 Ok(RevsetExpression::filter(predicate))
885 });
886 map.insert("author_email", |diagnostics, function, _context| {
887 let [arg] = function.expect_exact_arguments()?;
888 let pattern = expect_string_pattern(diagnostics, arg)?;
889 let predicate = RevsetFilterPredicate::AuthorEmail(pattern);
890 Ok(RevsetExpression::filter(predicate))
891 });
892 map.insert("author_date", |diagnostics, function, context| {
893 let [arg] = function.expect_exact_arguments()?;
894 let pattern = expect_date_pattern(diagnostics, arg, context.date_pattern_context())?;
895 Ok(RevsetExpression::filter(RevsetFilterPredicate::AuthorDate(
896 pattern,
897 )))
898 });
899 map.insert("signed", |_diagnostics, function, _context| {
900 function.expect_no_arguments()?;
901 let predicate = RevsetFilterPredicate::Signed;
902 Ok(RevsetExpression::filter(predicate))
903 });
904 map.insert("mine", |_diagnostics, function, context| {
905 function.expect_no_arguments()?;
906 let predicate =
910 RevsetFilterPredicate::AuthorEmail(StringPattern::exact_i(context.user_email));
911 Ok(RevsetExpression::filter(predicate))
912 });
913 map.insert("committer", |diagnostics, function, _context| {
914 let [arg] = function.expect_exact_arguments()?;
915 let pattern = expect_string_pattern(diagnostics, arg)?;
916 let name_predicate = RevsetFilterPredicate::CommitterName(pattern.clone());
917 let email_predicate = RevsetFilterPredicate::CommitterEmail(pattern);
918 Ok(RevsetExpression::filter(name_predicate)
919 .union(&RevsetExpression::filter(email_predicate)))
920 });
921 map.insert("committer_name", |diagnostics, function, _context| {
922 let [arg] = function.expect_exact_arguments()?;
923 let pattern = expect_string_pattern(diagnostics, arg)?;
924 let predicate = RevsetFilterPredicate::CommitterName(pattern);
925 Ok(RevsetExpression::filter(predicate))
926 });
927 map.insert("committer_email", |diagnostics, function, _context| {
928 let [arg] = function.expect_exact_arguments()?;
929 let pattern = expect_string_pattern(diagnostics, arg)?;
930 let predicate = RevsetFilterPredicate::CommitterEmail(pattern);
931 Ok(RevsetExpression::filter(predicate))
932 });
933 map.insert("committer_date", |diagnostics, function, context| {
934 let [arg] = function.expect_exact_arguments()?;
935 let pattern = expect_date_pattern(diagnostics, arg, context.date_pattern_context())?;
936 Ok(RevsetExpression::filter(
937 RevsetFilterPredicate::CommitterDate(pattern),
938 ))
939 });
940 map.insert("empty", |_diagnostics, function, _context| {
941 function.expect_no_arguments()?;
942 Ok(RevsetExpression::is_empty())
943 });
944 map.insert("files", |diagnostics, function, context| {
945 let ctx = context.workspace.as_ref().ok_or_else(|| {
946 RevsetParseError::with_span(
947 RevsetParseErrorKind::FsPathWithoutWorkspace,
948 function.args_span, )
950 })?;
951 let [arg] = function.expect_exact_arguments()?;
952 let expr = expect_fileset_expression(diagnostics, arg, ctx.path_converter)?;
953 Ok(RevsetExpression::filter(RevsetFilterPredicate::File(expr)))
954 });
955 map.insert("diff_contains", |diagnostics, function, context| {
956 let ([text_arg], [files_opt_arg]) = function.expect_arguments()?;
957 let text = expect_string_pattern(diagnostics, text_arg)?;
958 let files = if let Some(files_arg) = files_opt_arg {
959 let ctx = context.workspace.as_ref().ok_or_else(|| {
960 RevsetParseError::with_span(
961 RevsetParseErrorKind::FsPathWithoutWorkspace,
962 files_arg.span,
963 )
964 })?;
965 expect_fileset_expression(diagnostics, files_arg, ctx.path_converter)?
966 } else {
967 FilesetExpression::all()
970 };
971 Ok(RevsetExpression::filter(
972 RevsetFilterPredicate::DiffContains { text, files },
973 ))
974 });
975 map.insert("conflicts", |_diagnostics, function, _context| {
976 function.expect_no_arguments()?;
977 Ok(RevsetExpression::filter(RevsetFilterPredicate::HasConflict))
978 });
979 map.insert("present", |diagnostics, function, context| {
980 let [arg] = function.expect_exact_arguments()?;
981 let expression = lower_expression(diagnostics, arg, context)?;
982 Ok(expression.present())
983 });
984 map.insert("at_operation", |diagnostics, function, context| {
985 let [op_arg, cand_arg] = function.expect_exact_arguments()?;
986 let operation = revset_parser::catch_aliases(diagnostics, op_arg, |_diagnostics, node| {
988 Ok(node.span.as_str().to_owned())
989 })?;
990 let candidates = lower_expression(diagnostics, cand_arg, context)?;
991 Ok(Rc::new(RevsetExpression::AtOperation {
992 operation,
993 candidates,
994 }))
995 });
996 map.insert("coalesce", |diagnostics, function, context| {
997 let ([], args) = function.expect_some_arguments()?;
998 let expressions: Vec<_> = args
999 .iter()
1000 .map(|arg| lower_expression(diagnostics, arg, context))
1001 .try_collect()?;
1002 Ok(RevsetExpression::coalesce(&expressions))
1003 });
1004 map
1005});
1006
1007pub fn expect_fileset_expression(
1009 diagnostics: &mut RevsetDiagnostics,
1010 node: &ExpressionNode,
1011 path_converter: &RepoPathUiConverter,
1012) -> Result<FilesetExpression, RevsetParseError> {
1013 revset_parser::catch_aliases(diagnostics, node, |diagnostics, node| {
1017 let mut inner_diagnostics = FilesetDiagnostics::new();
1018 let expression = fileset::parse(&mut inner_diagnostics, node.span.as_str(), path_converter)
1019 .map_err(|err| {
1020 RevsetParseError::expression("In fileset expression", node.span).with_source(err)
1021 })?;
1022 diagnostics.extend_with(inner_diagnostics, |diag| {
1023 RevsetParseError::expression("In fileset expression", node.span).with_source(diag)
1024 });
1025 Ok(expression)
1026 })
1027}
1028
1029pub fn expect_string_pattern(
1030 diagnostics: &mut RevsetDiagnostics,
1031 node: &ExpressionNode,
1032) -> Result<StringPattern, RevsetParseError> {
1033 revset_parser::catch_aliases(diagnostics, node, |_diagnostics, node| {
1034 let (value, kind) = revset_parser::expect_string_pattern("string pattern", node)?;
1035 if let Some(kind) = kind {
1036 StringPattern::from_str_kind(value, kind).map_err(|err| {
1037 RevsetParseError::expression("Invalid string pattern", node.span).with_source(err)
1038 })
1039 } else {
1040 Ok(StringPattern::Substring(value.to_owned()))
1041 }
1042 })
1043}
1044
1045pub fn expect_date_pattern(
1046 diagnostics: &mut RevsetDiagnostics,
1047 node: &ExpressionNode,
1048 context: &DatePatternContext,
1049) -> Result<DatePattern, RevsetParseError> {
1050 revset_parser::catch_aliases(diagnostics, node, |_diagnostics, node| {
1051 let (value, kind) = revset_parser::expect_string_pattern("date pattern", node)?;
1052 let kind = kind.ok_or_else(|| {
1053 RevsetParseError::expression("Date pattern must specify 'after' or 'before'", node.span)
1054 })?;
1055 context.parse_relative(value, kind).map_err(|err| {
1056 RevsetParseError::expression("Invalid date pattern", node.span).with_source(err)
1057 })
1058 })
1059}
1060
1061fn parse_remote_bookmarks_arguments(
1062 diagnostics: &mut RevsetDiagnostics,
1063 function: &FunctionCallNode,
1064 remote_ref_state: Option<RemoteRefState>,
1065) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
1066 let ([], [bookmark_opt_arg, remote_opt_arg]) =
1067 function.expect_named_arguments(&["", "remote"])?;
1068 let bookmark_pattern = if let Some(bookmark_arg) = bookmark_opt_arg {
1069 expect_string_pattern(diagnostics, bookmark_arg)?
1070 } else {
1071 StringPattern::everything()
1072 };
1073 let remote_pattern = if let Some(remote_arg) = remote_opt_arg {
1074 expect_string_pattern(diagnostics, remote_arg)?
1075 } else {
1076 StringPattern::everything()
1077 };
1078 Ok(RevsetExpression::remote_bookmarks(
1079 bookmark_pattern,
1080 remote_pattern,
1081 remote_ref_state,
1082 ))
1083}
1084
1085fn lower_function_call(
1087 diagnostics: &mut RevsetDiagnostics,
1088 function: &FunctionCallNode,
1089 context: &LoweringContext,
1090) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
1091 let function_map = &context.extensions.function_map;
1092 if let Some(func) = function_map.get(function.name) {
1093 func(diagnostics, function, context)
1094 } else {
1095 Err(RevsetParseError::with_span(
1096 RevsetParseErrorKind::NoSuchFunction {
1097 name: function.name.to_owned(),
1098 candidates: collect_similar(function.name, function_map.keys()),
1099 },
1100 function.name_span,
1101 ))
1102 }
1103}
1104
1105pub fn lower_expression(
1108 diagnostics: &mut RevsetDiagnostics,
1109 node: &ExpressionNode,
1110 context: &LoweringContext,
1111) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
1112 revset_parser::catch_aliases(diagnostics, node, |diagnostics, node| match &node.kind {
1113 ExpressionKind::Identifier(name) => Ok(RevsetExpression::symbol((*name).to_owned())),
1114 ExpressionKind::String(name) => Ok(RevsetExpression::symbol(name.to_owned())),
1115 ExpressionKind::StringPattern { .. } => Err(RevsetParseError::with_span(
1116 RevsetParseErrorKind::NotInfixOperator {
1117 op: ":".to_owned(),
1118 similar_op: "::".to_owned(),
1119 description: "DAG range".to_owned(),
1120 },
1121 node.span,
1122 )),
1123 ExpressionKind::RemoteSymbol(symbol) => Ok(RevsetExpression::remote_symbol(symbol.clone())),
1124 ExpressionKind::AtWorkspace(name) => Ok(RevsetExpression::working_copy(name.into())),
1125 ExpressionKind::AtCurrentWorkspace => {
1126 let ctx = context.workspace.as_ref().ok_or_else(|| {
1127 RevsetParseError::with_span(
1128 RevsetParseErrorKind::WorkingCopyWithoutWorkspace,
1129 node.span,
1130 )
1131 })?;
1132 Ok(RevsetExpression::working_copy(
1133 ctx.workspace_name.to_owned(),
1134 ))
1135 }
1136 ExpressionKind::DagRangeAll => Ok(RevsetExpression::all()),
1137 ExpressionKind::RangeAll => Ok(RevsetExpression::root().negated()),
1138 ExpressionKind::Unary(op, arg_node) => {
1139 let arg = lower_expression(diagnostics, arg_node, context)?;
1140 match op {
1141 UnaryOp::Negate => Ok(arg.negated()),
1142 UnaryOp::DagRangePre => Ok(arg.ancestors()),
1143 UnaryOp::DagRangePost => Ok(arg.descendants()),
1144 UnaryOp::RangePre => Ok(RevsetExpression::root().range(&arg)),
1145 UnaryOp::RangePost => Ok(arg.ancestors().negated()),
1146 UnaryOp::Parents => Ok(arg.parents()),
1147 UnaryOp::Children => Ok(arg.children()),
1148 }
1149 }
1150 ExpressionKind::Binary(op, lhs_node, rhs_node) => {
1151 let lhs = lower_expression(diagnostics, lhs_node, context)?;
1152 let rhs = lower_expression(diagnostics, rhs_node, context)?;
1153 match op {
1154 BinaryOp::Intersection => Ok(lhs.intersection(&rhs)),
1155 BinaryOp::Difference => Ok(lhs.minus(&rhs)),
1156 BinaryOp::DagRange => Ok(lhs.dag_range_to(&rhs)),
1157 BinaryOp::Range => Ok(lhs.range(&rhs)),
1158 }
1159 }
1160 ExpressionKind::UnionAll(nodes) => {
1161 let expressions: Vec<_> = nodes
1162 .iter()
1163 .map(|node| lower_expression(diagnostics, node, context))
1164 .try_collect()?;
1165 Ok(RevsetExpression::union_all(&expressions))
1166 }
1167 ExpressionKind::FunctionCall(function) => {
1168 lower_function_call(diagnostics, function, context)
1169 }
1170 ExpressionKind::Modifier(modifier) => {
1171 let name = modifier.name;
1172 Err(RevsetParseError::expression(
1173 format!("Modifier `{name}:` is not allowed in sub expression"),
1174 modifier.name_span,
1175 ))
1176 }
1177 ExpressionKind::AliasExpanded(..) => unreachable!(),
1178 })
1179}
1180
1181pub fn parse(
1182 diagnostics: &mut RevsetDiagnostics,
1183 revset_str: &str,
1184 context: &RevsetParseContext,
1185) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
1186 let node = parse_program(revset_str)?;
1187 let node =
1188 dsl_util::expand_aliases_with_locals(node, context.aliases_map, &context.local_variables)?;
1189 lower_expression(diagnostics, &node, &context.to_lowering_context())
1190 .map_err(|err| err.extend_function_candidates(context.aliases_map.function_names()))
1191}
1192
1193pub fn parse_with_modifier(
1194 diagnostics: &mut RevsetDiagnostics,
1195 revset_str: &str,
1196 context: &RevsetParseContext,
1197) -> Result<(Rc<UserRevsetExpression>, Option<RevsetModifier>), RevsetParseError> {
1198 let node = parse_program(revset_str)?;
1199 let node =
1200 dsl_util::expand_aliases_with_locals(node, context.aliases_map, &context.local_variables)?;
1201 revset_parser::catch_aliases(diagnostics, &node, |diagnostics, node| match &node.kind {
1202 ExpressionKind::Modifier(modifier) => {
1203 let parsed_modifier = match modifier.name {
1204 "all" => RevsetModifier::All,
1205 _ => {
1206 return Err(RevsetParseError::with_span(
1207 RevsetParseErrorKind::NoSuchModifier(modifier.name.to_owned()),
1208 modifier.name_span,
1209 ));
1210 }
1211 };
1212 let parsed_body =
1213 lower_expression(diagnostics, &modifier.body, &context.to_lowering_context())?;
1214 Ok((parsed_body, Some(parsed_modifier)))
1215 }
1216 _ => {
1217 let parsed_body = lower_expression(diagnostics, node, &context.to_lowering_context())?;
1218 Ok((parsed_body, None))
1219 }
1220 })
1221 .map_err(|err| err.extend_function_candidates(context.aliases_map.function_names()))
1222}
1223
1224fn to_binary_expression<T: Clone>(
1227 expressions: &[T],
1228 unit: &impl Fn() -> T,
1229 binary: &impl Fn(&T, &T) -> T,
1230) -> T {
1231 match expressions {
1232 [] => unit(),
1233 [expression] => expression.clone(),
1234 _ => {
1235 let (left, right) = expressions.split_at(expressions.len() / 2);
1237 binary(
1238 &to_binary_expression(left, unit, binary),
1239 &to_binary_expression(right, unit, binary),
1240 )
1241 }
1242 }
1243}
1244
1245type TransformedExpression<St> = Option<Rc<RevsetExpression<St>>>;
1247type PreTransformedExpression<St> = ControlFlow<TransformedExpression<St>, ()>;
1250
1251fn transform_expression<St: ExpressionState>(
1253 expression: &Rc<RevsetExpression<St>>,
1254 mut pre: impl FnMut(&Rc<RevsetExpression<St>>) -> PreTransformedExpression<St>,
1255 mut post: impl FnMut(&Rc<RevsetExpression<St>>) -> TransformedExpression<St>,
1256) -> TransformedExpression<St> {
1257 let Ok(transformed) =
1258 try_transform_expression::<St, Infallible>(expression, |x| Ok(pre(x)), |x| Ok(post(x)));
1259 transformed
1260}
1261
1262fn transform_expression_bottom_up<St: ExpressionState>(
1264 expression: &Rc<RevsetExpression<St>>,
1265 post: impl FnMut(&Rc<RevsetExpression<St>>) -> TransformedExpression<St>,
1266) -> TransformedExpression<St> {
1267 transform_expression(expression, |_| ControlFlow::Continue(()), post)
1268}
1269
1270fn try_transform_expression<St: ExpressionState, E>(
1283 expression: &Rc<RevsetExpression<St>>,
1284 mut pre: impl FnMut(&Rc<RevsetExpression<St>>) -> Result<PreTransformedExpression<St>, E>,
1285 mut post: impl FnMut(&Rc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1286) -> Result<TransformedExpression<St>, E> {
1287 fn transform_child_rec<St: ExpressionState, E>(
1288 expression: &Rc<RevsetExpression<St>>,
1289 pre: &mut impl FnMut(&Rc<RevsetExpression<St>>) -> Result<PreTransformedExpression<St>, E>,
1290 post: &mut impl FnMut(&Rc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1291 ) -> Result<TransformedExpression<St>, E> {
1292 Ok(match expression.as_ref() {
1293 RevsetExpression::None => None,
1294 RevsetExpression::All => None,
1295 RevsetExpression::VisibleHeads => None,
1296 RevsetExpression::VisibleHeadsOrReferenced => None,
1297 RevsetExpression::Root => None,
1298 RevsetExpression::Commits(_) => None,
1299 RevsetExpression::CommitRef(_) => None,
1300 RevsetExpression::Ancestors { heads, generation } => transform_rec(heads, pre, post)?
1301 .map(|heads| RevsetExpression::Ancestors {
1302 heads,
1303 generation: generation.clone(),
1304 }),
1305 RevsetExpression::Descendants { roots, generation } => transform_rec(roots, pre, post)?
1306 .map(|roots| RevsetExpression::Descendants {
1307 roots,
1308 generation: generation.clone(),
1309 }),
1310 RevsetExpression::Range {
1311 roots,
1312 heads,
1313 generation,
1314 } => transform_rec_pair((roots, heads), pre, post)?.map(|(roots, heads)| {
1315 RevsetExpression::Range {
1316 roots,
1317 heads,
1318 generation: generation.clone(),
1319 }
1320 }),
1321 RevsetExpression::DagRange { roots, heads } => {
1322 transform_rec_pair((roots, heads), pre, post)?
1323 .map(|(roots, heads)| RevsetExpression::DagRange { roots, heads })
1324 }
1325 RevsetExpression::Reachable { sources, domain } => {
1326 transform_rec_pair((sources, domain), pre, post)?
1327 .map(|(sources, domain)| RevsetExpression::Reachable { sources, domain })
1328 }
1329 RevsetExpression::Heads(candidates) => {
1330 transform_rec(candidates, pre, post)?.map(RevsetExpression::Heads)
1331 }
1332 RevsetExpression::HeadsRange {
1333 roots,
1334 heads,
1335 filter,
1336 } => {
1337 let transformed_roots = transform_rec(roots, pre, post)?;
1338 let transformed_heads = transform_rec(heads, pre, post)?;
1339 let transformed_filter = transform_rec(filter, pre, post)?;
1340 (transformed_roots.is_some()
1341 || transformed_heads.is_some()
1342 || transformed_filter.is_some())
1343 .then(|| RevsetExpression::HeadsRange {
1344 roots: transformed_roots.unwrap_or_else(|| roots.clone()),
1345 heads: transformed_heads.unwrap_or_else(|| heads.clone()),
1346 filter: transformed_filter.unwrap_or_else(|| filter.clone()),
1347 })
1348 }
1349 RevsetExpression::Roots(candidates) => {
1350 transform_rec(candidates, pre, post)?.map(RevsetExpression::Roots)
1351 }
1352 RevsetExpression::ForkPoint(expression) => {
1353 transform_rec(expression, pre, post)?.map(RevsetExpression::ForkPoint)
1354 }
1355 RevsetExpression::Latest { candidates, count } => transform_rec(candidates, pre, post)?
1356 .map(|candidates| RevsetExpression::Latest {
1357 candidates,
1358 count: *count,
1359 }),
1360 RevsetExpression::Filter(_) => None,
1361 RevsetExpression::AsFilter(candidates) => {
1362 transform_rec(candidates, pre, post)?.map(RevsetExpression::AsFilter)
1363 }
1364 RevsetExpression::AtOperation {
1365 operation,
1366 candidates,
1367 } => transform_rec(candidates, pre, post)?.map(|candidates| {
1368 RevsetExpression::AtOperation {
1369 operation: operation.clone(),
1370 candidates,
1371 }
1372 }),
1373 RevsetExpression::WithinReference {
1374 candidates,
1375 commits,
1376 } => transform_rec(candidates, pre, post)?.map(|candidates| {
1377 RevsetExpression::WithinReference {
1378 candidates,
1379 commits: commits.clone(),
1380 }
1381 }),
1382 RevsetExpression::WithinVisibility {
1383 candidates,
1384 visible_heads,
1385 } => transform_rec(candidates, pre, post)?.map(|candidates| {
1386 RevsetExpression::WithinVisibility {
1387 candidates,
1388 visible_heads: visible_heads.clone(),
1389 }
1390 }),
1391 RevsetExpression::Coalesce(expression1, expression2) => transform_rec_pair(
1392 (expression1, expression2),
1393 pre,
1394 post,
1395 )?
1396 .map(|(expression1, expression2)| RevsetExpression::Coalesce(expression1, expression2)),
1397 RevsetExpression::Present(candidates) => {
1398 transform_rec(candidates, pre, post)?.map(RevsetExpression::Present)
1399 }
1400 RevsetExpression::NotIn(complement) => {
1401 transform_rec(complement, pre, post)?.map(RevsetExpression::NotIn)
1402 }
1403 RevsetExpression::Union(expression1, expression2) => {
1404 transform_rec_pair((expression1, expression2), pre, post)?.map(
1405 |(expression1, expression2)| RevsetExpression::Union(expression1, expression2),
1406 )
1407 }
1408 RevsetExpression::Intersection(expression1, expression2) => {
1409 transform_rec_pair((expression1, expression2), pre, post)?.map(
1410 |(expression1, expression2)| {
1411 RevsetExpression::Intersection(expression1, expression2)
1412 },
1413 )
1414 }
1415 RevsetExpression::Difference(expression1, expression2) => {
1416 transform_rec_pair((expression1, expression2), pre, post)?.map(
1417 |(expression1, expression2)| {
1418 RevsetExpression::Difference(expression1, expression2)
1419 },
1420 )
1421 }
1422 }
1423 .map(Rc::new))
1424 }
1425
1426 #[expect(clippy::type_complexity)]
1427 fn transform_rec_pair<St: ExpressionState, E>(
1428 (expression1, expression2): (&Rc<RevsetExpression<St>>, &Rc<RevsetExpression<St>>),
1429 pre: &mut impl FnMut(&Rc<RevsetExpression<St>>) -> Result<PreTransformedExpression<St>, E>,
1430 post: &mut impl FnMut(&Rc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1431 ) -> Result<Option<(Rc<RevsetExpression<St>>, Rc<RevsetExpression<St>>)>, E> {
1432 match (
1433 transform_rec(expression1, pre, post)?,
1434 transform_rec(expression2, pre, post)?,
1435 ) {
1436 (Some(new_expression1), Some(new_expression2)) => {
1437 Ok(Some((new_expression1, new_expression2)))
1438 }
1439 (Some(new_expression1), None) => Ok(Some((new_expression1, expression2.clone()))),
1440 (None, Some(new_expression2)) => Ok(Some((expression1.clone(), new_expression2))),
1441 (None, None) => Ok(None),
1442 }
1443 }
1444
1445 fn transform_rec<St: ExpressionState, E>(
1446 expression: &Rc<RevsetExpression<St>>,
1447 pre: &mut impl FnMut(&Rc<RevsetExpression<St>>) -> Result<PreTransformedExpression<St>, E>,
1448 post: &mut impl FnMut(&Rc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1449 ) -> Result<TransformedExpression<St>, E> {
1450 if let ControlFlow::Break(transformed) = pre(expression)? {
1451 return Ok(transformed);
1452 }
1453 if let Some(new_expression) = transform_child_rec(expression, pre, post)? {
1454 Ok(Some(post(&new_expression)?.unwrap_or(new_expression)))
1456 } else {
1457 post(expression)
1458 }
1459 }
1460
1461 transform_rec(expression, &mut pre, &mut post)
1462}
1463
1464trait ExpressionStateFolder<InSt: ExpressionState, OutSt: ExpressionState> {
1469 type Error;
1470
1471 fn fold_expression(
1474 &mut self,
1475 expression: &RevsetExpression<InSt>,
1476 ) -> Result<Rc<RevsetExpression<OutSt>>, Self::Error> {
1477 fold_child_expression_state(self, expression)
1478 }
1479
1480 fn fold_commit_ref(
1482 &mut self,
1483 commit_ref: &InSt::CommitRef,
1484 ) -> Result<Rc<RevsetExpression<OutSt>>, Self::Error>;
1485
1486 fn fold_at_operation(
1488 &mut self,
1489 operation: &InSt::Operation,
1490 candidates: &RevsetExpression<InSt>,
1491 ) -> Result<Rc<RevsetExpression<OutSt>>, Self::Error>;
1492}
1493
1494fn fold_child_expression_state<InSt, OutSt, F>(
1496 folder: &mut F,
1497 expression: &RevsetExpression<InSt>,
1498) -> Result<Rc<RevsetExpression<OutSt>>, F::Error>
1499where
1500 InSt: ExpressionState,
1501 OutSt: ExpressionState,
1502 F: ExpressionStateFolder<InSt, OutSt> + ?Sized,
1503{
1504 let expression: Rc<_> = match expression {
1505 RevsetExpression::None => RevsetExpression::None.into(),
1506 RevsetExpression::All => RevsetExpression::All.into(),
1507 RevsetExpression::VisibleHeads => RevsetExpression::VisibleHeads.into(),
1508 RevsetExpression::VisibleHeadsOrReferenced => {
1509 RevsetExpression::VisibleHeadsOrReferenced.into()
1510 }
1511 RevsetExpression::Root => RevsetExpression::Root.into(),
1512 RevsetExpression::Commits(ids) => RevsetExpression::Commits(ids.clone()).into(),
1513 RevsetExpression::CommitRef(commit_ref) => folder.fold_commit_ref(commit_ref)?,
1514 RevsetExpression::Ancestors { heads, generation } => {
1515 let heads = folder.fold_expression(heads)?;
1516 let generation = generation.clone();
1517 RevsetExpression::Ancestors { heads, generation }.into()
1518 }
1519 RevsetExpression::Descendants { roots, generation } => {
1520 let roots = folder.fold_expression(roots)?;
1521 let generation = generation.clone();
1522 RevsetExpression::Descendants { roots, generation }.into()
1523 }
1524 RevsetExpression::Range {
1525 roots,
1526 heads,
1527 generation,
1528 } => {
1529 let roots = folder.fold_expression(roots)?;
1530 let heads = folder.fold_expression(heads)?;
1531 let generation = generation.clone();
1532 RevsetExpression::Range {
1533 roots,
1534 heads,
1535 generation,
1536 }
1537 .into()
1538 }
1539 RevsetExpression::DagRange { roots, heads } => {
1540 let roots = folder.fold_expression(roots)?;
1541 let heads = folder.fold_expression(heads)?;
1542 RevsetExpression::DagRange { roots, heads }.into()
1543 }
1544 RevsetExpression::Reachable { sources, domain } => {
1545 let sources = folder.fold_expression(sources)?;
1546 let domain = folder.fold_expression(domain)?;
1547 RevsetExpression::Reachable { sources, domain }.into()
1548 }
1549 RevsetExpression::Heads(heads) => {
1550 let heads = folder.fold_expression(heads)?;
1551 RevsetExpression::Heads(heads).into()
1552 }
1553 RevsetExpression::HeadsRange {
1554 roots,
1555 heads,
1556 filter,
1557 } => {
1558 let roots = folder.fold_expression(roots)?;
1559 let heads = folder.fold_expression(heads)?;
1560 let filter = folder.fold_expression(filter)?;
1561 RevsetExpression::HeadsRange {
1562 roots,
1563 heads,
1564 filter,
1565 }
1566 .into()
1567 }
1568 RevsetExpression::Roots(roots) => {
1569 let roots = folder.fold_expression(roots)?;
1570 RevsetExpression::Roots(roots).into()
1571 }
1572 RevsetExpression::ForkPoint(expression) => {
1573 let expression = folder.fold_expression(expression)?;
1574 RevsetExpression::ForkPoint(expression).into()
1575 }
1576 RevsetExpression::Latest { candidates, count } => {
1577 let candidates = folder.fold_expression(candidates)?;
1578 let count = *count;
1579 RevsetExpression::Latest { candidates, count }.into()
1580 }
1581 RevsetExpression::Filter(predicate) => RevsetExpression::Filter(predicate.clone()).into(),
1582 RevsetExpression::AsFilter(candidates) => {
1583 let candidates = folder.fold_expression(candidates)?;
1584 RevsetExpression::AsFilter(candidates).into()
1585 }
1586 RevsetExpression::AtOperation {
1587 operation,
1588 candidates,
1589 } => folder.fold_at_operation(operation, candidates)?,
1590 RevsetExpression::WithinReference {
1591 candidates,
1592 commits,
1593 } => {
1594 let candidates = folder.fold_expression(candidates)?;
1595 let commits = commits.clone();
1596 RevsetExpression::WithinReference {
1597 candidates,
1598 commits,
1599 }
1600 .into()
1601 }
1602 RevsetExpression::WithinVisibility {
1603 candidates,
1604 visible_heads,
1605 } => {
1606 let candidates = folder.fold_expression(candidates)?;
1607 let visible_heads = visible_heads.clone();
1608 RevsetExpression::WithinVisibility {
1609 candidates,
1610 visible_heads,
1611 }
1612 .into()
1613 }
1614 RevsetExpression::Coalesce(expression1, expression2) => {
1615 let expression1 = folder.fold_expression(expression1)?;
1616 let expression2 = folder.fold_expression(expression2)?;
1617 RevsetExpression::Coalesce(expression1, expression2).into()
1618 }
1619 RevsetExpression::Present(candidates) => {
1620 let candidates = folder.fold_expression(candidates)?;
1621 RevsetExpression::Present(candidates).into()
1622 }
1623 RevsetExpression::NotIn(complement) => {
1624 let complement = folder.fold_expression(complement)?;
1625 RevsetExpression::NotIn(complement).into()
1626 }
1627 RevsetExpression::Union(expression1, expression2) => {
1628 let expression1 = folder.fold_expression(expression1)?;
1629 let expression2 = folder.fold_expression(expression2)?;
1630 RevsetExpression::Union(expression1, expression2).into()
1631 }
1632 RevsetExpression::Intersection(expression1, expression2) => {
1633 let expression1 = folder.fold_expression(expression1)?;
1634 let expression2 = folder.fold_expression(expression2)?;
1635 RevsetExpression::Intersection(expression1, expression2).into()
1636 }
1637 RevsetExpression::Difference(expression1, expression2) => {
1638 let expression1 = folder.fold_expression(expression1)?;
1639 let expression2 = folder.fold_expression(expression2)?;
1640 RevsetExpression::Difference(expression1, expression2).into()
1641 }
1642 };
1643 Ok(expression)
1644}
1645
1646fn resolve_referenced_commits<St: ExpressionState>(
1650 expression: &Rc<RevsetExpression<St>>,
1651) -> TransformedExpression<St> {
1652 if matches!(
1654 expression.as_ref(),
1655 RevsetExpression::WithinReference { .. }
1656 ) {
1657 return None;
1658 }
1659
1660 let mut inner_commits = Vec::new();
1662 let mut outer_commits = Vec::new();
1663 let transformed = transform_expression(
1664 expression,
1665 |expression| match expression.as_ref() {
1666 RevsetExpression::WithinReference { commits, .. } => {
1668 inner_commits.extend_from_slice(commits);
1669 ControlFlow::Break(None)
1670 }
1671 RevsetExpression::WithinVisibility {
1673 candidates,
1674 visible_heads,
1675 } => {
1676 inner_commits.extend_from_slice(visible_heads);
1678 let transformed = resolve_referenced_commits(candidates);
1679 if let RevsetExpression::WithinReference { commits, .. } =
1681 transformed.as_deref().unwrap_or(candidates)
1682 {
1683 inner_commits.extend_from_slice(commits);
1684 }
1685 ControlFlow::Break(transformed.map(|candidates| {
1686 Rc::new(RevsetExpression::WithinVisibility {
1687 candidates,
1688 visible_heads: visible_heads.clone(),
1689 })
1690 }))
1691 }
1692 _ => ControlFlow::Continue(()),
1693 },
1694 |expression| {
1695 if let RevsetExpression::Commits(commits) = expression.as_ref() {
1696 outer_commits.extend_from_slice(commits);
1697 }
1698 None
1699 },
1700 );
1701
1702 outer_commits.extend(inner_commits);
1705 if outer_commits.is_empty() {
1706 return transformed;
1708 }
1709 Some(Rc::new(RevsetExpression::WithinReference {
1710 candidates: transformed.unwrap_or_else(|| expression.clone()),
1711 commits: outer_commits,
1712 }))
1713}
1714
1715fn flatten_intersections<St: ExpressionState>(
1718 expression: &Rc<RevsetExpression<St>>,
1719) -> TransformedExpression<St> {
1720 fn flatten<St: ExpressionState>(
1721 expression1: &Rc<RevsetExpression<St>>,
1722 expression2: &Rc<RevsetExpression<St>>,
1723 ) -> TransformedExpression<St> {
1724 let recurse = |a, b| flatten(a, b).unwrap_or_else(|| a.intersection(b));
1725
1726 match expression2.as_ref() {
1727 RevsetExpression::Intersection(inner1, inner2) => {
1729 Some(recurse(expression1, inner1).intersection(inner2))
1730 }
1731 _ => None,
1732 }
1733 }
1734
1735 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
1736 RevsetExpression::Intersection(expression1, expression2) => {
1737 flatten(expression1, expression2)
1738 }
1739 _ => None,
1740 })
1741}
1742
1743fn sort_intersection_by_key<St: ExpressionState, T: Ord>(
1747 base: &Rc<RevsetExpression<St>>,
1748 expression: &Rc<RevsetExpression<St>>,
1749 mut get_key: impl FnMut(&RevsetExpression<St>) -> T,
1750) -> TransformedExpression<St> {
1751 fn sort_intersection_helper<St: ExpressionState, T: Ord>(
1754 base: &Rc<RevsetExpression<St>>,
1755 expression: &Rc<RevsetExpression<St>>,
1756 expression_key: T,
1757 mut get_key: impl FnMut(&RevsetExpression<St>) -> T,
1758 ) -> TransformedExpression<St> {
1759 if let RevsetExpression::Intersection(inner1, inner2) = base.as_ref() {
1760 (expression_key < get_key(inner2)).then(|| {
1762 sort_intersection_helper(inner1, expression, expression_key, get_key)
1763 .unwrap_or_else(|| inner1.intersection(expression))
1764 .intersection(inner2)
1765 })
1766 } else {
1767 (expression_key < get_key(base)).then(|| expression.intersection(base))
1769 }
1770 }
1771
1772 sort_intersection_helper(base, expression, get_key(expression), get_key)
1773}
1774
1775fn sort_negations_and_ancestors<St: ExpressionState>(
1780 expression: &Rc<RevsetExpression<St>>,
1781) -> TransformedExpression<St> {
1782 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
1783 enum AncestorsOrder {
1784 NegatedAncestors,
1785 Ancestors,
1786 Other,
1787 NegatedOther,
1788 }
1789
1790 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
1791 RevsetExpression::Intersection(expression1, expression2) => {
1792 sort_intersection_by_key(expression1, expression2, |expression| match expression {
1793 RevsetExpression::Ancestors {
1794 generation: Range { end: u64::MAX, .. },
1795 ..
1796 } => AncestorsOrder::Ancestors,
1797 RevsetExpression::NotIn(complement) => match complement.as_ref() {
1798 RevsetExpression::Ancestors {
1799 generation: Range { end: u64::MAX, .. },
1800 ..
1801 } => AncestorsOrder::NegatedAncestors,
1802 _ => AncestorsOrder::NegatedOther,
1803 },
1804 _ => AncestorsOrder::Other,
1805 })
1806 }
1807 _ => None,
1808 })
1809}
1810
1811fn internalize_filter<St: ExpressionState>(
1820 expression: &Rc<RevsetExpression<St>>,
1821) -> TransformedExpression<St> {
1822 fn get_filter<St: ExpressionState>(
1823 expression: &Rc<RevsetExpression<St>>,
1824 ) -> Option<&Rc<RevsetExpression<St>>> {
1825 match expression.as_ref() {
1826 RevsetExpression::Filter(_) => Some(expression),
1827 RevsetExpression::AsFilter(candidates) => Some(candidates),
1828 _ => None,
1829 }
1830 }
1831
1832 fn mark_filter<St: ExpressionState>(
1833 expression: Rc<RevsetExpression<St>>,
1834 ) -> Rc<RevsetExpression<St>> {
1835 Rc::new(RevsetExpression::AsFilter(expression))
1836 }
1837
1838 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
1839 RevsetExpression::Present(e) => get_filter(e).map(|f| mark_filter(f.present())),
1841 RevsetExpression::NotIn(e) => get_filter(e).map(|f| mark_filter(f.negated())),
1842 RevsetExpression::Union(e1, e2) => {
1843 let f1 = get_filter(e1);
1844 let f2 = get_filter(e2);
1845 (f1.is_some() || f2.is_some())
1846 .then(|| mark_filter(f1.unwrap_or(e1).union(f2.unwrap_or(e2))))
1847 }
1848 RevsetExpression::Intersection(e1, e2) => match (get_filter(e1), get_filter(e2)) {
1854 (Some(f1), Some(f2)) => Some(mark_filter(f1.intersection(f2))),
1856 (Some(_), None) => Some(e2.intersection(e1)),
1858 (None, Some(f2)) => match e1.as_ref() {
1860 RevsetExpression::Intersection(e1a, e1b) => {
1861 get_filter(e1b).map(|f1b| e1a.intersection(&mark_filter(f1b.intersection(f2))))
1862 }
1863 _ => None,
1864 },
1865 (None, None) => match e1.as_ref() {
1867 RevsetExpression::Intersection(e1a, e1b) => {
1868 get_filter(e1b).map(|_| e1a.intersection(e2).intersection(e1b))
1869 }
1870 _ => None,
1871 },
1872 },
1873 _ => None,
1875 })
1876}
1877
1878fn fold_redundant_expression<St: ExpressionState>(
1883 expression: &Rc<RevsetExpression<St>>,
1884) -> TransformedExpression<St> {
1885 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
1886 RevsetExpression::Commits(commits) if commits.is_empty() => Some(RevsetExpression::none()),
1887 RevsetExpression::NotIn(outer) => match outer.as_ref() {
1888 RevsetExpression::NotIn(inner) => Some(inner.clone()),
1889 RevsetExpression::None => Some(RevsetExpression::all()),
1890 RevsetExpression::All => Some(RevsetExpression::none()),
1891 _ => None,
1892 },
1893 RevsetExpression::Union(expression1, expression2) => {
1894 match (expression1.as_ref(), expression2.as_ref()) {
1895 (_, RevsetExpression::None) => Some(expression1.clone()),
1896 (RevsetExpression::None, _) => Some(expression2.clone()),
1897 (RevsetExpression::All, _) => Some(RevsetExpression::all()),
1898 (_, RevsetExpression::All) => Some(RevsetExpression::all()),
1899 _ => None,
1900 }
1901 }
1902 RevsetExpression::Intersection(expression1, expression2) => {
1903 match (expression1.as_ref(), expression2.as_ref()) {
1904 (RevsetExpression::None, _) => Some(RevsetExpression::none()),
1905 (_, RevsetExpression::None) => Some(RevsetExpression::none()),
1906 (_, RevsetExpression::All) => Some(expression1.clone()),
1907 (RevsetExpression::All, _) => Some(expression2.clone()),
1908 _ => None,
1909 }
1910 }
1911 _ => None,
1912 })
1913}
1914
1915fn ancestors_to_heads<St: ExpressionState>(
1919 expression: &RevsetExpression<St>,
1920) -> Result<Rc<RevsetExpression<St>>, ()> {
1921 match expression {
1922 RevsetExpression::Ancestors {
1923 heads,
1924 generation: GENERATION_RANGE_FULL,
1925 } => Ok(heads.clone()),
1926 RevsetExpression::Ancestors {
1927 heads,
1928 generation: Range {
1929 start,
1930 end: u64::MAX,
1931 },
1932 } => Ok(heads.ancestors_at(*start)),
1933 _ => Err(()),
1934 }
1935}
1936
1937fn fold_ancestors_union<St: ExpressionState>(
1942 expression: &Rc<RevsetExpression<St>>,
1943) -> TransformedExpression<St> {
1944 fn union_ancestors<St: ExpressionState>(
1945 expression1: &Rc<RevsetExpression<St>>,
1946 expression2: &Rc<RevsetExpression<St>>,
1947 ) -> TransformedExpression<St> {
1948 let heads1 = ancestors_to_heads(expression1).ok()?;
1949 let heads2 = ancestors_to_heads(expression2).ok()?;
1950 Some(heads1.union(&heads2).ancestors())
1951 }
1952
1953 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
1954 RevsetExpression::Union(expression1, expression2) => {
1955 union_ancestors(expression1, expression2)
1957 }
1958 RevsetExpression::Intersection(expression1, expression2) => {
1959 match (expression1.as_ref(), expression2.as_ref()) {
1960 (RevsetExpression::NotIn(complement1), RevsetExpression::NotIn(complement2)) => {
1962 union_ancestors(complement1, complement2).map(|expression| expression.negated())
1963 }
1964 _ => None,
1965 }
1966 }
1967 _ => None,
1968 })
1969}
1970
1971fn fold_heads_range<St: ExpressionState>(
1976 expression: &Rc<RevsetExpression<St>>,
1977) -> TransformedExpression<St> {
1978 struct FilteredRange<St: ExpressionState> {
1980 roots: Rc<RevsetExpression<St>>,
1981 heads: Option<Rc<RevsetExpression<St>>>,
1982 filter: Rc<RevsetExpression<St>>,
1983 }
1984
1985 impl<St: ExpressionState> FilteredRange<St> {
1986 fn new(roots: Rc<RevsetExpression<St>>) -> Self {
1987 Self {
1989 roots,
1990 heads: None,
1991 filter: RevsetExpression::all(),
1992 }
1993 }
1994
1995 fn add(mut self, expression: &Rc<RevsetExpression<St>>) -> Self {
1996 if self.heads.is_none() {
1997 if let Ok(heads) = ancestors_to_heads(expression) {
1999 self.heads = Some(heads);
2000 return self;
2001 }
2002 }
2003 self.add_filter(expression)
2004 }
2005
2006 fn add_filter(mut self, expression: &Rc<RevsetExpression<St>>) -> Self {
2007 if let RevsetExpression::All = self.filter.as_ref() {
2008 self.filter = expression.clone();
2010 } else {
2011 self.filter = self.filter.intersection(expression);
2012 }
2013 self
2014 }
2015 }
2016
2017 fn to_filtered_range<St: ExpressionState>(
2018 expression: &Rc<RevsetExpression<St>>,
2019 ) -> Option<FilteredRange<St>> {
2020 if let Ok(heads) = ancestors_to_heads(expression) {
2024 return Some(FilteredRange {
2025 roots: RevsetExpression::none(),
2026 heads: Some(heads),
2027 filter: RevsetExpression::all(),
2028 });
2029 }
2030 match expression.as_ref() {
2031 RevsetExpression::NotIn(complement) => {
2034 if let Ok(roots) = ancestors_to_heads(complement) {
2035 Some(FilteredRange::new(roots))
2036 } else {
2037 Some(FilteredRange::new(RevsetExpression::none()).add_filter(expression))
2040 }
2041 }
2042 RevsetExpression::All | RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_) => {
2044 Some(FilteredRange::new(RevsetExpression::none()).add_filter(expression))
2045 }
2046 RevsetExpression::Intersection(expression1, expression2) => {
2049 to_filtered_range(expression1).map(|filtered_range| filtered_range.add(expression2))
2050 }
2051 _ => None,
2052 }
2053 }
2054
2055 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2056 RevsetExpression::Heads(candidates) => {
2057 to_filtered_range(candidates).map(|filtered_range| {
2058 RevsetExpression::HeadsRange {
2059 roots: filtered_range.roots,
2060 heads: filtered_range
2061 .heads
2062 .unwrap_or_else(RevsetExpression::visible_heads_or_referenced),
2063 filter: filtered_range.filter,
2064 }
2065 .into()
2066 })
2067 }
2068 _ => None,
2069 })
2070}
2071
2072fn to_difference_range<St: ExpressionState>(
2073 expression: &Rc<RevsetExpression<St>>,
2074 complement: &Rc<RevsetExpression<St>>,
2075) -> TransformedExpression<St> {
2076 let RevsetExpression::Ancestors { heads, generation } = expression.as_ref() else {
2077 return None;
2078 };
2079 let roots = ancestors_to_heads(complement).ok()?;
2080 Some(Rc::new(RevsetExpression::Range {
2083 roots,
2084 heads: heads.clone(),
2085 generation: generation.clone(),
2086 }))
2087}
2088
2089fn fold_difference<St: ExpressionState>(
2092 expression: &Rc<RevsetExpression<St>>,
2093) -> TransformedExpression<St> {
2094 fn to_difference<St: ExpressionState>(
2095 expression: &Rc<RevsetExpression<St>>,
2096 complement: &Rc<RevsetExpression<St>>,
2097 ) -> Rc<RevsetExpression<St>> {
2098 to_difference_range(expression, complement).unwrap_or_else(|| expression.minus(complement))
2099 }
2100
2101 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2102 RevsetExpression::Intersection(expression1, expression2) => {
2103 match (expression1.as_ref(), expression2.as_ref()) {
2104 (_, RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_)) => None,
2106 (_, RevsetExpression::NotIn(complement)) => {
2107 Some(to_difference(expression1, complement))
2108 }
2109 (RevsetExpression::NotIn(complement), _) => {
2110 Some(to_difference(expression2, complement))
2111 }
2112 _ => None,
2113 }
2114 }
2115 _ => None,
2116 })
2117}
2118
2119fn fold_not_in_ancestors<St: ExpressionState>(
2124 expression: &Rc<RevsetExpression<St>>,
2125) -> TransformedExpression<St> {
2126 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2127 RevsetExpression::NotIn(complement)
2128 if matches!(complement.as_ref(), RevsetExpression::Ancestors { .. }) =>
2129 {
2130 to_difference_range(
2133 &RevsetExpression::visible_heads_or_referenced().ancestors(),
2134 complement,
2135 )
2136 }
2137 _ => None,
2138 })
2139}
2140
2141fn unfold_difference<St: ExpressionState>(
2146 expression: &Rc<RevsetExpression<St>>,
2147) -> TransformedExpression<St> {
2148 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2149 RevsetExpression::Range {
2151 roots,
2152 heads,
2153 generation,
2154 } => {
2155 let heads_ancestors = Rc::new(RevsetExpression::Ancestors {
2156 heads: heads.clone(),
2157 generation: generation.clone(),
2158 });
2159 Some(heads_ancestors.intersection(&roots.ancestors().negated()))
2160 }
2161 RevsetExpression::Difference(expression1, expression2) => {
2162 Some(expression1.intersection(&expression2.negated()))
2163 }
2164 _ => None,
2165 })
2166}
2167
2168fn fold_generation<St: ExpressionState>(
2171 expression: &Rc<RevsetExpression<St>>,
2172) -> TransformedExpression<St> {
2173 fn add_generation(generation1: &Range<u64>, generation2: &Range<u64>) -> Range<u64> {
2174 if generation1.is_empty() || generation2.is_empty() {
2176 GENERATION_RANGE_EMPTY
2177 } else {
2178 let start = u64::saturating_add(generation1.start, generation2.start);
2179 let end = u64::saturating_add(generation1.end, generation2.end - 1);
2180 start..end
2181 }
2182 }
2183
2184 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2185 RevsetExpression::Ancestors {
2186 heads,
2187 generation: generation1,
2188 } => {
2189 match heads.as_ref() {
2190 RevsetExpression::Ancestors {
2194 heads,
2195 generation: generation2,
2196 } => Some(Rc::new(RevsetExpression::Ancestors {
2197 heads: heads.clone(),
2198 generation: add_generation(generation1, generation2),
2199 })),
2200 _ => None,
2201 }
2202 }
2203 RevsetExpression::Descendants {
2204 roots,
2205 generation: generation1,
2206 } => {
2207 match roots.as_ref() {
2208 RevsetExpression::Descendants {
2212 roots,
2213 generation: generation2,
2214 } => Some(Rc::new(RevsetExpression::Descendants {
2215 roots: roots.clone(),
2216 generation: add_generation(generation1, generation2),
2217 })),
2218 _ => None,
2219 }
2220 }
2221 _ => None,
2223 })
2224}
2225
2226pub fn optimize<St: ExpressionState>(
2229 expression: Rc<RevsetExpression<St>>,
2230) -> Rc<RevsetExpression<St>> {
2231 let expression = resolve_referenced_commits(&expression).unwrap_or(expression);
2234 let expression = unfold_difference(&expression).unwrap_or(expression);
2235 let expression = fold_redundant_expression(&expression).unwrap_or(expression);
2236 let expression = fold_generation(&expression).unwrap_or(expression);
2237 let expression = flatten_intersections(&expression).unwrap_or(expression);
2238 let expression = sort_negations_and_ancestors(&expression).unwrap_or(expression);
2239 let expression = internalize_filter(&expression).unwrap_or(expression);
2240 let expression = fold_ancestors_union(&expression).unwrap_or(expression);
2241 let expression = fold_heads_range(&expression).unwrap_or(expression);
2242 let expression = fold_difference(&expression).unwrap_or(expression);
2243 fold_not_in_ancestors(&expression).unwrap_or(expression)
2244}
2245
2246pub fn walk_revs<'index>(
2250 repo: &'index dyn Repo,
2251 wanted: &[CommitId],
2252 unwanted: &[CommitId],
2253) -> Result<Box<dyn Revset + 'index>, RevsetEvaluationError> {
2254 RevsetExpression::commits(unwanted.to_vec())
2255 .range(&RevsetExpression::commits(wanted.to_vec()))
2256 .evaluate(repo)
2257}
2258
2259fn reload_repo_at_operation(
2260 repo: &dyn Repo,
2261 op_str: &str,
2262) -> Result<Arc<ReadonlyRepo>, RevsetResolutionError> {
2263 let base_repo = repo.base_repo();
2267 let operation = op_walk::resolve_op_with_repo(base_repo, op_str)
2268 .map_err(|err| RevsetResolutionError::Other(err.into()))?;
2269 base_repo.reload_at(&operation).map_err(|err| match err {
2270 RepoLoaderError::Backend(err) => RevsetResolutionError::Backend(err),
2271 RepoLoaderError::IndexRead(_)
2272 | RepoLoaderError::OpHeadResolution(_)
2273 | RepoLoaderError::OpHeadsStoreError(_)
2274 | RepoLoaderError::OpStore(_)
2275 | RepoLoaderError::TransactionCommit(_) => RevsetResolutionError::Other(err.into()),
2276 })
2277}
2278
2279fn resolve_remote_bookmark(repo: &dyn Repo, symbol: RemoteRefSymbol<'_>) -> Option<Vec<CommitId>> {
2280 let target = &repo.view().get_remote_bookmark(symbol).target;
2281 target
2282 .is_present()
2283 .then(|| target.added_ids().cloned().collect())
2284}
2285
2286fn all_formatted_bookmark_symbols(
2287 repo: &dyn Repo,
2288 include_synced_remotes: bool,
2289) -> impl Iterator<Item = String> + use<'_> {
2290 let view = repo.view();
2291 view.bookmarks().flat_map(move |(name, bookmark_target)| {
2292 let local_target = bookmark_target.local_target;
2293 let local_symbol = local_target
2294 .is_present()
2295 .then(|| format_symbol(name.as_str()));
2296 let remote_symbols = bookmark_target
2297 .remote_refs
2298 .into_iter()
2299 .filter(move |&(_, remote_ref)| {
2300 include_synced_remotes
2301 || !remote_ref.is_tracked()
2302 || remote_ref.target != *local_target
2303 })
2304 .map(move |(remote, _)| format_remote_symbol(name.as_str(), remote.as_str()));
2305 local_symbol.into_iter().chain(remote_symbols)
2306 })
2307}
2308
2309fn make_no_such_symbol_error(repo: &dyn Repo, name: String) -> RevsetResolutionError {
2310 let bookmark_names = all_formatted_bookmark_symbols(repo, name.contains('@'));
2312 let candidates = collect_similar(&name, bookmark_names);
2313 RevsetResolutionError::NoSuchRevision { name, candidates }
2314}
2315
2316pub trait PartialSymbolResolver {
2320 fn resolve_symbol(
2321 &self,
2322 repo: &dyn Repo,
2323 symbol: &str,
2324 ) -> Result<Option<Vec<CommitId>>, RevsetResolutionError>;
2325}
2326
2327struct TagResolver;
2328
2329impl PartialSymbolResolver for TagResolver {
2330 fn resolve_symbol(
2331 &self,
2332 repo: &dyn Repo,
2333 symbol: &str,
2334 ) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
2335 let target = repo.view().get_tag(symbol.as_ref());
2336 Ok(target
2337 .is_present()
2338 .then(|| target.added_ids().cloned().collect()))
2339 }
2340}
2341
2342struct BookmarkResolver;
2343
2344impl PartialSymbolResolver for BookmarkResolver {
2345 fn resolve_symbol(
2346 &self,
2347 repo: &dyn Repo,
2348 symbol: &str,
2349 ) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
2350 let target = repo.view().get_local_bookmark(symbol.as_ref());
2351 Ok(target
2352 .is_present()
2353 .then(|| target.added_ids().cloned().collect()))
2354 }
2355}
2356
2357struct GitRefResolver;
2358
2359impl PartialSymbolResolver for GitRefResolver {
2360 fn resolve_symbol(
2361 &self,
2362 repo: &dyn Repo,
2363 symbol: &str,
2364 ) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
2365 let view = repo.view();
2366 for git_ref_prefix in &["", "refs/"] {
2367 let target = view.get_git_ref([git_ref_prefix, symbol].concat().as_ref());
2368 if target.is_present() {
2369 return Ok(Some(target.added_ids().cloned().collect()));
2370 }
2371 }
2372
2373 Ok(None)
2374 }
2375}
2376
2377const DEFAULT_RESOLVERS: &[&'static dyn PartialSymbolResolver] =
2378 &[&TagResolver, &BookmarkResolver, &GitRefResolver];
2379
2380struct CommitPrefixResolver<'a> {
2381 context_repo: &'a dyn Repo,
2382 context: Option<&'a IdPrefixContext>,
2383}
2384
2385impl CommitPrefixResolver<'_> {
2386 fn try_resolve(
2387 &self,
2388 repo: &dyn Repo,
2389 prefix: &HexPrefix,
2390 ) -> Result<Option<CommitId>, RevsetResolutionError> {
2391 let index = self
2392 .context
2393 .map(|ctx| ctx.populate(self.context_repo))
2394 .transpose()
2395 .map_err(|err| RevsetResolutionError::Other(err.into()))?
2396 .unwrap_or(IdPrefixIndex::empty());
2397 match index.resolve_commit_prefix(repo, prefix) {
2398 PrefixResolution::AmbiguousMatch => {
2399 Err(RevsetResolutionError::AmbiguousCommitIdPrefix(prefix.hex()))
2400 }
2401 PrefixResolution::SingleMatch(id) => Ok(Some(id)),
2402 PrefixResolution::NoMatch => Ok(None),
2403 }
2404 }
2405}
2406
2407impl PartialSymbolResolver for CommitPrefixResolver<'_> {
2408 fn resolve_symbol(
2409 &self,
2410 repo: &dyn Repo,
2411 symbol: &str,
2412 ) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
2413 if let Some(prefix) = HexPrefix::try_from_hex(symbol) {
2414 Ok(self.try_resolve(repo, &prefix)?.map(|id| vec![id]))
2415 } else {
2416 Ok(None)
2417 }
2418 }
2419}
2420
2421struct ChangePrefixResolver<'a> {
2422 context_repo: &'a dyn Repo,
2423 context: Option<&'a IdPrefixContext>,
2424}
2425
2426impl ChangePrefixResolver<'_> {
2427 fn try_resolve(
2428 &self,
2429 repo: &dyn Repo,
2430 prefix: &HexPrefix,
2431 ) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
2432 let index = self
2433 .context
2434 .map(|ctx| ctx.populate(self.context_repo))
2435 .transpose()
2436 .map_err(|err| RevsetResolutionError::Other(err.into()))?
2437 .unwrap_or(IdPrefixIndex::empty());
2438 match index.resolve_change_prefix(repo, prefix) {
2439 PrefixResolution::AmbiguousMatch => Err(
2440 RevsetResolutionError::AmbiguousChangeIdPrefix(prefix.reverse_hex()),
2441 ),
2442 PrefixResolution::SingleMatch(ids) => Ok(Some(ids)),
2443 PrefixResolution::NoMatch => Ok(None),
2444 }
2445 }
2446}
2447
2448impl PartialSymbolResolver for ChangePrefixResolver<'_> {
2449 fn resolve_symbol(
2450 &self,
2451 repo: &dyn Repo,
2452 symbol: &str,
2453 ) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
2454 if let Some(prefix) = HexPrefix::try_from_reverse_hex(symbol) {
2455 self.try_resolve(repo, &prefix)
2456 } else {
2457 Ok(None)
2458 }
2459 }
2460}
2461
2462pub trait SymbolResolverExtension {
2469 fn new_resolvers<'a>(
2474 &self,
2475 context_repo: &'a dyn Repo,
2476 ) -> Vec<Box<dyn PartialSymbolResolver + 'a>>;
2477}
2478
2479pub struct SymbolResolver<'a> {
2482 commit_id_resolver: CommitPrefixResolver<'a>,
2483 change_id_resolver: ChangePrefixResolver<'a>,
2484 extensions: Vec<Box<dyn PartialSymbolResolver + 'a>>,
2485}
2486
2487impl<'a> SymbolResolver<'a> {
2488 pub fn new(
2491 context_repo: &'a dyn Repo,
2492 extensions: &[impl AsRef<dyn SymbolResolverExtension>],
2493 ) -> Self {
2494 SymbolResolver {
2495 commit_id_resolver: CommitPrefixResolver {
2496 context_repo,
2497 context: None,
2498 },
2499 change_id_resolver: ChangePrefixResolver {
2500 context_repo,
2501 context: None,
2502 },
2503 extensions: extensions
2504 .iter()
2505 .flat_map(|ext| ext.as_ref().new_resolvers(context_repo))
2506 .collect(),
2507 }
2508 }
2509
2510 pub fn with_id_prefix_context(mut self, id_prefix_context: &'a IdPrefixContext) -> Self {
2511 self.commit_id_resolver.context = Some(id_prefix_context);
2512 self.change_id_resolver.context = Some(id_prefix_context);
2513 self
2514 }
2515
2516 fn partial_resolvers(&self) -> impl Iterator<Item = &(dyn PartialSymbolResolver + 'a)> {
2517 let prefix_resolvers: [&dyn PartialSymbolResolver; 2] =
2518 [&self.commit_id_resolver, &self.change_id_resolver];
2519 itertools::chain!(
2520 DEFAULT_RESOLVERS.iter().copied(),
2521 prefix_resolvers,
2522 self.extensions.iter().map(|e| e.as_ref())
2523 )
2524 }
2525
2526 pub fn resolve_symbol(
2528 &self,
2529 repo: &dyn Repo,
2530 symbol: &str,
2531 ) -> Result<Vec<CommitId>, RevsetResolutionError> {
2532 if symbol.is_empty() {
2533 return Err(RevsetResolutionError::EmptyString);
2534 }
2535
2536 for partial_resolver in self.partial_resolvers() {
2537 if let Some(ids) = partial_resolver.resolve_symbol(repo, symbol)? {
2538 return Ok(ids);
2539 }
2540 }
2541
2542 Err(make_no_such_symbol_error(repo, format_symbol(symbol)))
2543 }
2544}
2545
2546fn resolve_commit_ref(
2547 repo: &dyn Repo,
2548 commit_ref: &RevsetCommitRef,
2549 symbol_resolver: &SymbolResolver,
2550) -> Result<Vec<CommitId>, RevsetResolutionError> {
2551 match commit_ref {
2552 RevsetCommitRef::Symbol(symbol) => symbol_resolver.resolve_symbol(repo, symbol),
2553 RevsetCommitRef::RemoteSymbol(symbol) => resolve_remote_bookmark(repo, symbol.as_ref())
2554 .ok_or_else(|| make_no_such_symbol_error(repo, symbol.to_string())),
2555 RevsetCommitRef::WorkingCopy(name) => {
2556 if let Some(commit_id) = repo.view().get_wc_commit_id(name) {
2557 Ok(vec![commit_id.clone()])
2558 } else {
2559 Err(RevsetResolutionError::WorkspaceMissingWorkingCopy { name: name.clone() })
2560 }
2561 }
2562 RevsetCommitRef::WorkingCopies => {
2563 let wc_commits = repo.view().wc_commit_ids().values().cloned().collect_vec();
2564 Ok(wc_commits)
2565 }
2566 RevsetCommitRef::ChangeId(prefix) => {
2567 let resolver = &symbol_resolver.change_id_resolver;
2568 Ok(resolver.try_resolve(repo, prefix)?.unwrap_or_else(Vec::new))
2569 }
2570 RevsetCommitRef::CommitId(prefix) => {
2571 let resolver = &symbol_resolver.commit_id_resolver;
2572 Ok(resolver.try_resolve(repo, prefix)?.into_iter().collect())
2573 }
2574 RevsetCommitRef::Bookmarks(pattern) => {
2575 let commit_ids = repo
2576 .view()
2577 .local_bookmarks_matching(pattern)
2578 .flat_map(|(_, target)| target.added_ids())
2579 .cloned()
2580 .collect();
2581 Ok(commit_ids)
2582 }
2583 RevsetCommitRef::RemoteBookmarks {
2584 bookmark_pattern,
2585 remote_pattern,
2586 remote_ref_state,
2587 } => {
2588 let commit_ids = repo
2590 .view()
2591 .remote_bookmarks_matching(bookmark_pattern, remote_pattern)
2592 .filter(|(_, remote_ref)| {
2593 remote_ref_state.is_none_or(|state| remote_ref.state == state)
2594 })
2595 .filter(|&(symbol, _)| !crate::git::is_special_git_remote(symbol.remote))
2596 .flat_map(|(_, remote_ref)| remote_ref.target.added_ids())
2597 .cloned()
2598 .collect();
2599 Ok(commit_ids)
2600 }
2601 RevsetCommitRef::Tags(pattern) => {
2602 let commit_ids = repo
2603 .view()
2604 .tags_matching(pattern)
2605 .flat_map(|(_, target)| target.added_ids())
2606 .cloned()
2607 .collect();
2608 Ok(commit_ids)
2609 }
2610 RevsetCommitRef::GitRefs => {
2611 let mut commit_ids = vec![];
2612 for ref_target in repo.view().git_refs().values() {
2613 commit_ids.extend(ref_target.added_ids().cloned());
2614 }
2615 Ok(commit_ids)
2616 }
2617 RevsetCommitRef::GitHead => Ok(repo.view().git_head().added_ids().cloned().collect()),
2618 }
2619}
2620
2621struct ExpressionSymbolResolver<'a, 'b> {
2623 base_repo: &'a dyn Repo,
2624 repo_stack: Vec<Arc<ReadonlyRepo>>,
2625 symbol_resolver: &'a SymbolResolver<'b>,
2626}
2627
2628impl<'a, 'b> ExpressionSymbolResolver<'a, 'b> {
2629 fn new(base_repo: &'a dyn Repo, symbol_resolver: &'a SymbolResolver<'b>) -> Self {
2630 ExpressionSymbolResolver {
2631 base_repo,
2632 repo_stack: vec![],
2633 symbol_resolver,
2634 }
2635 }
2636
2637 fn repo(&self) -> &dyn Repo {
2638 self.repo_stack
2639 .last()
2640 .map_or(self.base_repo, |repo| repo.as_ref())
2641 }
2642}
2643
2644impl ExpressionStateFolder<UserExpressionState, ResolvedExpressionState>
2645 for ExpressionSymbolResolver<'_, '_>
2646{
2647 type Error = RevsetResolutionError;
2648
2649 fn fold_expression(
2650 &mut self,
2651 expression: &UserRevsetExpression,
2652 ) -> Result<Rc<ResolvedRevsetExpression>, Self::Error> {
2653 match expression {
2654 RevsetExpression::Present(candidates) => {
2656 self.fold_expression(candidates).or_else(|err| match err {
2657 RevsetResolutionError::NoSuchRevision { .. }
2658 | RevsetResolutionError::WorkspaceMissingWorkingCopy { .. } => {
2659 Ok(RevsetExpression::none())
2660 }
2661 RevsetResolutionError::EmptyString
2662 | RevsetResolutionError::AmbiguousCommitIdPrefix(_)
2663 | RevsetResolutionError::AmbiguousChangeIdPrefix(_)
2664 | RevsetResolutionError::Backend(_)
2665 | RevsetResolutionError::Other(_) => Err(err),
2666 })
2667 }
2668 _ => fold_child_expression_state(self, expression),
2669 }
2670 }
2671
2672 fn fold_commit_ref(
2673 &mut self,
2674 commit_ref: &RevsetCommitRef,
2675 ) -> Result<Rc<ResolvedRevsetExpression>, Self::Error> {
2676 let commit_ids = resolve_commit_ref(self.repo(), commit_ref, self.symbol_resolver)?;
2677 Ok(RevsetExpression::commits(commit_ids))
2678 }
2679
2680 fn fold_at_operation(
2681 &mut self,
2682 operation: &String,
2683 candidates: &UserRevsetExpression,
2684 ) -> Result<Rc<ResolvedRevsetExpression>, Self::Error> {
2685 let repo = reload_repo_at_operation(self.repo(), operation)?;
2686 self.repo_stack.push(repo);
2687 let candidates = self.fold_expression(candidates)?;
2688 let visible_heads = self.repo().view().heads().iter().cloned().collect();
2689 self.repo_stack.pop();
2690 Ok(Rc::new(RevsetExpression::WithinVisibility {
2691 candidates,
2692 visible_heads,
2693 }))
2694 }
2695}
2696
2697fn resolve_symbols(
2698 repo: &dyn Repo,
2699 expression: &UserRevsetExpression,
2700 symbol_resolver: &SymbolResolver,
2701) -> Result<Rc<ResolvedRevsetExpression>, RevsetResolutionError> {
2702 let mut resolver = ExpressionSymbolResolver::new(repo, symbol_resolver);
2703 resolver.fold_expression(expression)
2704}
2705
2706fn resolve_visibility(
2716 repo: &dyn Repo,
2717 expression: &ResolvedRevsetExpression,
2718) -> ResolvedExpression {
2719 let context = VisibilityResolutionContext {
2720 referenced_commits: &[],
2721 visible_heads: &repo.view().heads().iter().cloned().collect_vec(),
2722 root: repo.store().root_commit_id(),
2723 };
2724 context.resolve(expression)
2725}
2726
2727#[derive(Clone, Debug)]
2728struct VisibilityResolutionContext<'a> {
2729 referenced_commits: &'a [CommitId],
2730 visible_heads: &'a [CommitId],
2731 root: &'a CommitId,
2732}
2733
2734impl VisibilityResolutionContext<'_> {
2735 fn resolve(&self, expression: &ResolvedRevsetExpression) -> ResolvedExpression {
2737 match expression {
2738 RevsetExpression::None => ResolvedExpression::Commits(vec![]),
2739 RevsetExpression::All => self.resolve_all(),
2740 RevsetExpression::VisibleHeads => self.resolve_visible_heads(),
2741 RevsetExpression::VisibleHeadsOrReferenced => {
2742 self.resolve_visible_heads_or_referenced()
2743 }
2744 RevsetExpression::Root => self.resolve_root(),
2745 RevsetExpression::Commits(commit_ids) => {
2746 ResolvedExpression::Commits(commit_ids.clone())
2747 }
2748 RevsetExpression::CommitRef(commit_ref) => match *commit_ref {},
2749 RevsetExpression::Ancestors { heads, generation } => ResolvedExpression::Ancestors {
2750 heads: self.resolve(heads).into(),
2751 generation: generation.clone(),
2752 },
2753 RevsetExpression::Descendants { roots, generation } => ResolvedExpression::DagRange {
2754 roots: self.resolve(roots).into(),
2755 heads: self.resolve_visible_heads_or_referenced().into(),
2756 generation_from_roots: generation.clone(),
2757 },
2758 RevsetExpression::Range {
2759 roots,
2760 heads,
2761 generation,
2762 } => ResolvedExpression::Range {
2763 roots: self.resolve(roots).into(),
2764 heads: self.resolve(heads).into(),
2765 generation: generation.clone(),
2766 },
2767 RevsetExpression::DagRange { roots, heads } => ResolvedExpression::DagRange {
2768 roots: self.resolve(roots).into(),
2769 heads: self.resolve(heads).into(),
2770 generation_from_roots: GENERATION_RANGE_FULL,
2771 },
2772 RevsetExpression::Reachable { sources, domain } => ResolvedExpression::Reachable {
2773 sources: self.resolve(sources).into(),
2774 domain: self.resolve(domain).into(),
2775 },
2776 RevsetExpression::Heads(candidates) => {
2777 ResolvedExpression::Heads(self.resolve(candidates).into())
2778 }
2779 RevsetExpression::HeadsRange {
2780 roots,
2781 heads,
2782 filter,
2783 } => ResolvedExpression::HeadsRange {
2784 roots: self.resolve(roots).into(),
2785 heads: self.resolve(heads).into(),
2786 filter: (!matches!(filter.as_ref(), RevsetExpression::All))
2787 .then(|| self.resolve_predicate(filter)),
2788 },
2789 RevsetExpression::Roots(candidates) => {
2790 ResolvedExpression::Roots(self.resolve(candidates).into())
2791 }
2792 RevsetExpression::ForkPoint(expression) => {
2793 ResolvedExpression::ForkPoint(self.resolve(expression).into())
2794 }
2795 RevsetExpression::Latest { candidates, count } => ResolvedExpression::Latest {
2796 candidates: self.resolve(candidates).into(),
2797 count: *count,
2798 },
2799 RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_) => {
2800 ResolvedExpression::FilterWithin {
2803 candidates: self.resolve_all().into(),
2804 predicate: self.resolve_predicate(expression),
2805 }
2806 }
2807 RevsetExpression::AtOperation { operation, .. } => match *operation {},
2808 RevsetExpression::WithinReference {
2809 candidates,
2810 commits,
2811 } => {
2812 let context = VisibilityResolutionContext {
2813 referenced_commits: commits,
2814 visible_heads: self.visible_heads,
2815 root: self.root,
2816 };
2817 context.resolve(candidates)
2818 }
2819 RevsetExpression::WithinVisibility {
2820 candidates,
2821 visible_heads,
2822 } => {
2823 let context = VisibilityResolutionContext {
2824 referenced_commits: self.referenced_commits,
2825 visible_heads,
2826 root: self.root,
2827 };
2828 context.resolve(candidates)
2829 }
2830 RevsetExpression::Coalesce(expression1, expression2) => ResolvedExpression::Coalesce(
2831 self.resolve(expression1).into(),
2832 self.resolve(expression2).into(),
2833 ),
2834 RevsetExpression::Present(candidates) => self.resolve(candidates),
2836 RevsetExpression::NotIn(complement) => ResolvedExpression::Difference(
2837 self.resolve_all().into(),
2838 self.resolve(complement).into(),
2839 ),
2840 RevsetExpression::Union(expression1, expression2) => ResolvedExpression::Union(
2841 self.resolve(expression1).into(),
2842 self.resolve(expression2).into(),
2843 ),
2844 RevsetExpression::Intersection(expression1, expression2) => {
2845 match expression2.as_ref() {
2846 RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_) => {
2847 ResolvedExpression::FilterWithin {
2848 candidates: self.resolve(expression1).into(),
2849 predicate: self.resolve_predicate(expression2),
2850 }
2851 }
2852 _ => ResolvedExpression::Intersection(
2853 self.resolve(expression1).into(),
2854 self.resolve(expression2).into(),
2855 ),
2856 }
2857 }
2858 RevsetExpression::Difference(expression1, expression2) => {
2859 ResolvedExpression::Difference(
2860 self.resolve(expression1).into(),
2861 self.resolve(expression2).into(),
2862 )
2863 }
2864 }
2865 }
2866
2867 fn resolve_all(&self) -> ResolvedExpression {
2868 ResolvedExpression::Ancestors {
2869 heads: self.resolve_visible_heads_or_referenced().into(),
2870 generation: GENERATION_RANGE_FULL,
2871 }
2872 }
2873
2874 fn resolve_visible_heads(&self) -> ResolvedExpression {
2875 ResolvedExpression::Commits(self.visible_heads.to_owned())
2876 }
2877
2878 fn resolve_visible_heads_or_referenced(&self) -> ResolvedExpression {
2879 let commits = itertools::chain(self.referenced_commits, self.visible_heads)
2884 .cloned()
2885 .collect();
2886 ResolvedExpression::Commits(commits)
2887 }
2888
2889 fn resolve_root(&self) -> ResolvedExpression {
2890 ResolvedExpression::Commits(vec![self.root.to_owned()])
2891 }
2892
2893 fn resolve_predicate(
2898 &self,
2899 expression: &ResolvedRevsetExpression,
2900 ) -> ResolvedPredicateExpression {
2901 match expression {
2902 RevsetExpression::None
2903 | RevsetExpression::All
2904 | RevsetExpression::VisibleHeads
2905 | RevsetExpression::VisibleHeadsOrReferenced
2906 | RevsetExpression::Root
2907 | RevsetExpression::Commits(_)
2908 | RevsetExpression::CommitRef(_)
2909 | RevsetExpression::Ancestors { .. }
2910 | RevsetExpression::Descendants { .. }
2911 | RevsetExpression::Range { .. }
2912 | RevsetExpression::DagRange { .. }
2913 | RevsetExpression::Reachable { .. }
2914 | RevsetExpression::Heads(_)
2915 | RevsetExpression::HeadsRange { .. }
2916 | RevsetExpression::Roots(_)
2917 | RevsetExpression::ForkPoint(_)
2918 | RevsetExpression::Latest { .. } => {
2919 ResolvedPredicateExpression::Set(self.resolve(expression).into())
2920 }
2921 RevsetExpression::Filter(predicate) => {
2922 ResolvedPredicateExpression::Filter(predicate.clone())
2923 }
2924 RevsetExpression::AsFilter(candidates) => self.resolve_predicate(candidates),
2925 RevsetExpression::AtOperation { operation, .. } => match *operation {},
2926 RevsetExpression::WithinReference { .. }
2928 | RevsetExpression::WithinVisibility { .. } => {
2929 ResolvedPredicateExpression::Set(self.resolve(expression).into())
2930 }
2931 RevsetExpression::Coalesce(_, _) => {
2932 ResolvedPredicateExpression::Set(self.resolve(expression).into())
2933 }
2934 RevsetExpression::Present(candidates) => self.resolve_predicate(candidates),
2936 RevsetExpression::NotIn(complement) => {
2937 ResolvedPredicateExpression::NotIn(self.resolve_predicate(complement).into())
2938 }
2939 RevsetExpression::Union(expression1, expression2) => {
2940 let predicate1 = self.resolve_predicate(expression1);
2941 let predicate2 = self.resolve_predicate(expression2);
2942 ResolvedPredicateExpression::Union(predicate1.into(), predicate2.into())
2943 }
2944 RevsetExpression::Intersection(expression1, expression2) => {
2945 let predicate1 = self.resolve_predicate(expression1);
2946 let predicate2 = self.resolve_predicate(expression2);
2947 ResolvedPredicateExpression::Intersection(predicate1.into(), predicate2.into())
2948 }
2949 RevsetExpression::Difference(expression1, expression2) => {
2950 let predicate1 = self.resolve_predicate(expression1);
2951 let predicate2 = self.resolve_predicate(expression2);
2952 let predicate2 = ResolvedPredicateExpression::NotIn(predicate2.into());
2953 ResolvedPredicateExpression::Intersection(predicate1.into(), predicate2.into())
2954 }
2955 }
2956 }
2957}
2958
2959pub trait Revset: fmt::Debug {
2960 fn iter<'a>(&self) -> Box<dyn Iterator<Item = Result<CommitId, RevsetEvaluationError>> + 'a>
2962 where
2963 Self: 'a;
2964
2965 fn commit_change_ids<'a>(
2967 &self,
2968 ) -> Box<dyn Iterator<Item = Result<(CommitId, ChangeId), RevsetEvaluationError>> + 'a>
2969 where
2970 Self: 'a;
2971
2972 fn iter_graph<'a>(
2973 &self,
2974 ) -> Box<dyn Iterator<Item = Result<GraphNode<CommitId>, RevsetEvaluationError>> + 'a>
2975 where
2976 Self: 'a;
2977
2978 fn is_empty(&self) -> bool;
2980
2981 fn count_estimate(&self) -> Result<(usize, Option<usize>), RevsetEvaluationError>;
2986
2987 fn containing_fn<'a>(&self) -> Box<RevsetContainingFn<'a>>
2993 where
2994 Self: 'a;
2995}
2996
2997pub type RevsetContainingFn<'a> = dyn Fn(&CommitId) -> Result<bool, RevsetEvaluationError> + 'a;
2999
3000pub trait RevsetIteratorExt<I> {
3001 fn commits(self, store: &Arc<Store>) -> RevsetCommitIterator<I>;
3002}
3003
3004impl<I: Iterator<Item = Result<CommitId, RevsetEvaluationError>>> RevsetIteratorExt<I> for I {
3005 fn commits(self, store: &Arc<Store>) -> RevsetCommitIterator<I> {
3006 RevsetCommitIterator {
3007 iter: self,
3008 store: store.clone(),
3009 }
3010 }
3011}
3012
3013pub struct RevsetCommitIterator<I> {
3014 store: Arc<Store>,
3015 iter: I,
3016}
3017
3018impl<I: Iterator<Item = Result<CommitId, RevsetEvaluationError>>> Iterator
3019 for RevsetCommitIterator<I>
3020{
3021 type Item = Result<Commit, RevsetEvaluationError>;
3022
3023 fn next(&mut self) -> Option<Self::Item> {
3024 self.iter.next().map(|commit_id| {
3025 let commit_id = commit_id?;
3026 self.store
3027 .get_commit(&commit_id)
3028 .map_err(RevsetEvaluationError::Backend)
3029 })
3030 }
3031}
3032
3033pub struct RevsetExtensions {
3035 symbol_resolvers: Vec<Box<dyn SymbolResolverExtension>>,
3036 function_map: HashMap<&'static str, RevsetFunction>,
3037}
3038
3039impl Default for RevsetExtensions {
3040 fn default() -> Self {
3041 Self::new()
3042 }
3043}
3044
3045impl RevsetExtensions {
3046 pub fn new() -> Self {
3047 Self {
3048 symbol_resolvers: vec![],
3049 function_map: BUILTIN_FUNCTION_MAP.clone(),
3050 }
3051 }
3052
3053 pub fn symbol_resolvers(&self) -> &[impl AsRef<dyn SymbolResolverExtension> + use<>] {
3054 &self.symbol_resolvers
3055 }
3056
3057 pub fn add_symbol_resolver(&mut self, symbol_resolver: Box<dyn SymbolResolverExtension>) {
3058 self.symbol_resolvers.push(symbol_resolver);
3059 }
3060
3061 pub fn add_custom_function(&mut self, name: &'static str, func: RevsetFunction) {
3062 match self.function_map.entry(name) {
3063 hash_map::Entry::Occupied(_) => {
3064 panic!("Conflict registering revset function '{name}'")
3065 }
3066 hash_map::Entry::Vacant(v) => v.insert(func),
3067 };
3068 }
3069}
3070
3071#[derive(Clone)]
3073pub struct RevsetParseContext<'a> {
3074 pub aliases_map: &'a RevsetAliasesMap,
3075 pub local_variables: HashMap<&'a str, ExpressionNode<'a>>,
3076 pub user_email: &'a str,
3077 pub date_pattern_context: DatePatternContext,
3078 pub extensions: &'a RevsetExtensions,
3079 pub workspace: Option<RevsetWorkspaceContext<'a>>,
3080}
3081
3082impl<'a> RevsetParseContext<'a> {
3083 fn to_lowering_context(&self) -> LoweringContext<'a> {
3084 let RevsetParseContext {
3085 aliases_map: _,
3086 local_variables: _,
3087 user_email,
3088 date_pattern_context,
3089 extensions,
3090 workspace,
3091 } = *self;
3092 LoweringContext {
3093 user_email,
3094 date_pattern_context,
3095 extensions,
3096 workspace,
3097 }
3098 }
3099}
3100
3101#[derive(Clone)]
3103pub struct LoweringContext<'a> {
3104 user_email: &'a str,
3105 date_pattern_context: DatePatternContext,
3106 extensions: &'a RevsetExtensions,
3107 workspace: Option<RevsetWorkspaceContext<'a>>,
3108}
3109
3110impl<'a> LoweringContext<'a> {
3111 pub fn user_email(&self) -> &'a str {
3112 self.user_email
3113 }
3114
3115 pub fn date_pattern_context(&self) -> &DatePatternContext {
3116 &self.date_pattern_context
3117 }
3118
3119 pub fn symbol_resolvers(&self) -> &'a [impl AsRef<dyn SymbolResolverExtension> + use<>] {
3120 self.extensions.symbol_resolvers()
3121 }
3122}
3123
3124#[derive(Clone, Copy, Debug)]
3126pub struct RevsetWorkspaceContext<'a> {
3127 pub path_converter: &'a RepoPathUiConverter,
3128 pub workspace_name: &'a WorkspaceName,
3129}
3130
3131pub fn format_symbol(literal: &str) -> String {
3137 if revset_parser::is_identifier(literal) {
3138 literal.to_string()
3139 } else {
3140 format_string(literal)
3141 }
3142}
3143
3144pub fn format_string(literal: &str) -> String {
3146 format!(r#""{}""#, dsl_util::escape_string(literal))
3147}
3148
3149pub fn format_remote_symbol(name: &str, remote: &str) -> String {
3151 let name = format_symbol(name);
3152 let remote = format_symbol(remote);
3153 format!("{name}@{remote}")
3154}
3155
3156#[cfg(test)]
3157#[rustversion::attr(
3158 since(1.89),
3159 expect(clippy::cloned_ref_to_slice_refs, reason = "makes tests more readable")
3160)]
3161mod tests {
3162 use std::path::PathBuf;
3163
3164 use assert_matches::assert_matches;
3165
3166 use super::*;
3167
3168 fn parse(revset_str: &str) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
3169 parse_with_aliases(revset_str, [] as [(&str, &str); 0])
3170 }
3171
3172 fn parse_with_workspace(
3173 revset_str: &str,
3174 workspace_name: &WorkspaceName,
3175 ) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
3176 parse_with_aliases_and_workspace(revset_str, [] as [(&str, &str); 0], workspace_name)
3177 }
3178
3179 fn parse_with_aliases(
3180 revset_str: &str,
3181 aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
3182 ) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
3183 let mut aliases_map = RevsetAliasesMap::new();
3184 for (decl, defn) in aliases {
3185 aliases_map.insert(decl, defn).unwrap();
3186 }
3187 let context = RevsetParseContext {
3188 aliases_map: &aliases_map,
3189 local_variables: HashMap::new(),
3190 user_email: "test.user@example.com",
3191 date_pattern_context: chrono::Utc::now().fixed_offset().into(),
3192 extensions: &RevsetExtensions::default(),
3193 workspace: None,
3194 };
3195 super::parse(&mut RevsetDiagnostics::new(), revset_str, &context)
3196 }
3197
3198 fn parse_with_aliases_and_workspace(
3199 revset_str: &str,
3200 aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
3201 workspace_name: &WorkspaceName,
3202 ) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
3203 let path_converter = RepoPathUiConverter::Fs {
3205 cwd: PathBuf::from("/"),
3206 base: PathBuf::from("/"),
3207 };
3208 let workspace_ctx = RevsetWorkspaceContext {
3209 path_converter: &path_converter,
3210 workspace_name,
3211 };
3212 let mut aliases_map = RevsetAliasesMap::new();
3213 for (decl, defn) in aliases {
3214 aliases_map.insert(decl, defn).unwrap();
3215 }
3216 let context = RevsetParseContext {
3217 aliases_map: &aliases_map,
3218 local_variables: HashMap::new(),
3219 user_email: "test.user@example.com",
3220 date_pattern_context: chrono::Utc::now().fixed_offset().into(),
3221 extensions: &RevsetExtensions::default(),
3222 workspace: Some(workspace_ctx),
3223 };
3224 super::parse(&mut RevsetDiagnostics::new(), revset_str, &context)
3225 }
3226
3227 fn parse_with_modifier(
3228 revset_str: &str,
3229 ) -> Result<(Rc<UserRevsetExpression>, Option<RevsetModifier>), RevsetParseError> {
3230 parse_with_aliases_and_modifier(revset_str, [] as [(&str, &str); 0])
3231 }
3232
3233 fn parse_with_aliases_and_modifier(
3234 revset_str: &str,
3235 aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
3236 ) -> Result<(Rc<UserRevsetExpression>, Option<RevsetModifier>), RevsetParseError> {
3237 let mut aliases_map = RevsetAliasesMap::new();
3238 for (decl, defn) in aliases {
3239 aliases_map.insert(decl, defn).unwrap();
3240 }
3241 let context = RevsetParseContext {
3242 aliases_map: &aliases_map,
3243 local_variables: HashMap::new(),
3244 user_email: "test.user@example.com",
3245 date_pattern_context: chrono::Utc::now().fixed_offset().into(),
3246 extensions: &RevsetExtensions::default(),
3247 workspace: None,
3248 };
3249 super::parse_with_modifier(&mut RevsetDiagnostics::new(), revset_str, &context)
3250 }
3251
3252 fn insta_settings() -> insta::Settings {
3253 let mut settings = insta::Settings::clone_current();
3254 for _ in 0..4 {
3257 settings.add_filter(
3258 r"(?x)
3259 \b([A-Z]\w*)\(\n
3260 \s*(.{1,60}),\n
3261 \s*\)",
3262 "$1($2)",
3263 );
3264 }
3265 settings
3266 }
3267
3268 #[test]
3269 #[expect(clippy::redundant_clone)] fn test_revset_expression_building() {
3271 let settings = insta_settings();
3272 let _guard = settings.bind_to_scope();
3273 let current_wc = UserRevsetExpression::working_copy(WorkspaceName::DEFAULT.to_owned());
3274 let foo_symbol = UserRevsetExpression::symbol("foo".to_string());
3275 let bar_symbol = UserRevsetExpression::symbol("bar".to_string());
3276 let baz_symbol = UserRevsetExpression::symbol("baz".to_string());
3277
3278 insta::assert_debug_snapshot!(
3279 current_wc,
3280 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
3281 insta::assert_debug_snapshot!(
3282 current_wc.heads(),
3283 @r#"Heads(CommitRef(WorkingCopy(WorkspaceNameBuf("default"))))"#);
3284 insta::assert_debug_snapshot!(
3285 current_wc.roots(),
3286 @r#"Roots(CommitRef(WorkingCopy(WorkspaceNameBuf("default"))))"#);
3287 insta::assert_debug_snapshot!(
3288 current_wc.parents(), @r#"
3289 Ancestors {
3290 heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3291 generation: 1..2,
3292 }
3293 "#);
3294 insta::assert_debug_snapshot!(
3295 current_wc.ancestors(), @r#"
3296 Ancestors {
3297 heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3298 generation: 0..18446744073709551615,
3299 }
3300 "#);
3301 insta::assert_debug_snapshot!(
3302 foo_symbol.children(), @r#"
3303 Descendants {
3304 roots: CommitRef(Symbol("foo")),
3305 generation: 1..2,
3306 }
3307 "#);
3308 insta::assert_debug_snapshot!(
3309 foo_symbol.descendants(), @r#"
3310 Descendants {
3311 roots: CommitRef(Symbol("foo")),
3312 generation: 0..18446744073709551615,
3313 }
3314 "#);
3315 insta::assert_debug_snapshot!(
3316 foo_symbol.dag_range_to(¤t_wc), @r#"
3317 DagRange {
3318 roots: CommitRef(Symbol("foo")),
3319 heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3320 }
3321 "#);
3322 insta::assert_debug_snapshot!(
3323 foo_symbol.connected(), @r#"
3324 DagRange {
3325 roots: CommitRef(Symbol("foo")),
3326 heads: CommitRef(Symbol("foo")),
3327 }
3328 "#);
3329 insta::assert_debug_snapshot!(
3330 foo_symbol.range(¤t_wc), @r#"
3331 Range {
3332 roots: CommitRef(Symbol("foo")),
3333 heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3334 generation: 0..18446744073709551615,
3335 }
3336 "#);
3337 insta::assert_debug_snapshot!(
3338 foo_symbol.negated(),
3339 @r#"NotIn(CommitRef(Symbol("foo")))"#);
3340 insta::assert_debug_snapshot!(
3341 foo_symbol.union(¤t_wc), @r#"
3342 Union(
3343 CommitRef(Symbol("foo")),
3344 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3345 )
3346 "#);
3347 insta::assert_debug_snapshot!(
3348 UserRevsetExpression::union_all(&[]),
3349 @"None");
3350 insta::assert_debug_snapshot!(
3351 RevsetExpression::union_all(&[current_wc.clone()]),
3352 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
3353 insta::assert_debug_snapshot!(
3354 RevsetExpression::union_all(&[current_wc.clone(), foo_symbol.clone()]),
3355 @r#"
3356 Union(
3357 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3358 CommitRef(Symbol("foo")),
3359 )
3360 "#);
3361 insta::assert_debug_snapshot!(
3362 RevsetExpression::union_all(&[
3363 current_wc.clone(),
3364 foo_symbol.clone(),
3365 bar_symbol.clone(),
3366 ]),
3367 @r#"
3368 Union(
3369 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3370 Union(
3371 CommitRef(Symbol("foo")),
3372 CommitRef(Symbol("bar")),
3373 ),
3374 )
3375 "#);
3376 insta::assert_debug_snapshot!(
3377 RevsetExpression::union_all(&[
3378 current_wc.clone(),
3379 foo_symbol.clone(),
3380 bar_symbol.clone(),
3381 baz_symbol.clone(),
3382 ]),
3383 @r#"
3384 Union(
3385 Union(
3386 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3387 CommitRef(Symbol("foo")),
3388 ),
3389 Union(
3390 CommitRef(Symbol("bar")),
3391 CommitRef(Symbol("baz")),
3392 ),
3393 )
3394 "#);
3395 insta::assert_debug_snapshot!(
3396 foo_symbol.intersection(¤t_wc), @r#"
3397 Intersection(
3398 CommitRef(Symbol("foo")),
3399 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3400 )
3401 "#);
3402 insta::assert_debug_snapshot!(
3403 foo_symbol.minus(¤t_wc), @r#"
3404 Difference(
3405 CommitRef(Symbol("foo")),
3406 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3407 )
3408 "#);
3409 insta::assert_debug_snapshot!(
3410 UserRevsetExpression::coalesce(&[]),
3411 @"None");
3412 insta::assert_debug_snapshot!(
3413 RevsetExpression::coalesce(&[current_wc.clone()]),
3414 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
3415 insta::assert_debug_snapshot!(
3416 RevsetExpression::coalesce(&[current_wc.clone(), foo_symbol.clone()]),
3417 @r#"
3418 Coalesce(
3419 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3420 CommitRef(Symbol("foo")),
3421 )
3422 "#);
3423 insta::assert_debug_snapshot!(
3424 RevsetExpression::coalesce(&[
3425 current_wc.clone(),
3426 foo_symbol.clone(),
3427 bar_symbol.clone(),
3428 ]),
3429 @r#"
3430 Coalesce(
3431 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3432 Coalesce(
3433 CommitRef(Symbol("foo")),
3434 CommitRef(Symbol("bar")),
3435 ),
3436 )
3437 "#);
3438 }
3439
3440 #[test]
3441 fn test_parse_revset() {
3442 let settings = insta_settings();
3443 let _guard = settings.bind_to_scope();
3444 let main_workspace_name = WorkspaceNameBuf::from("main");
3445 let other_workspace_name = WorkspaceNameBuf::from("other");
3446
3447 insta::assert_debug_snapshot!(
3449 parse("@").unwrap_err().kind(),
3450 @"WorkingCopyWithoutWorkspace");
3451 insta::assert_debug_snapshot!(
3452 parse("main@").unwrap(),
3453 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
3454 insta::assert_debug_snapshot!(
3455 parse_with_workspace("@", &main_workspace_name).unwrap(),
3456 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
3457 insta::assert_debug_snapshot!(
3458 parse_with_workspace("main@", &other_workspace_name).unwrap(),
3459 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
3460 insta::assert_debug_snapshot!(
3462 parse("author_name(foo@)").unwrap_err().kind(),
3463 @r#"Expression("Expected string pattern")"#);
3464 insta::assert_debug_snapshot!(
3465 parse(r#"author_name("foo@")"#).unwrap(),
3466 @r#"Filter(AuthorName(Substring("foo@")))"#);
3467 insta::assert_debug_snapshot!(
3469 parse("foo").unwrap(),
3470 @r#"CommitRef(Symbol("foo"))"#);
3471 insta::assert_debug_snapshot!(
3473 parse("bookmarks()").unwrap(),
3474 @r#"CommitRef(Bookmarks(Substring("")))"#);
3475 insta::assert_debug_snapshot!(
3477 parse("tags()").unwrap(),
3478 @r#"CommitRef(Tags(Substring("")))"#);
3479 insta::assert_debug_snapshot!(parse("remote_bookmarks()").unwrap(), @r#"
3480 CommitRef(
3481 RemoteBookmarks {
3482 bookmark_pattern: Substring(""),
3483 remote_pattern: Substring(""),
3484 remote_ref_state: None,
3485 },
3486 )
3487 "#);
3488 insta::assert_debug_snapshot!(parse("tracked_remote_bookmarks()").unwrap(), @r#"
3489 CommitRef(
3490 RemoteBookmarks {
3491 bookmark_pattern: Substring(""),
3492 remote_pattern: Substring(""),
3493 remote_ref_state: Some(Tracked),
3494 },
3495 )
3496 "#);
3497 insta::assert_debug_snapshot!(parse("untracked_remote_bookmarks()").unwrap(), @r#"
3498 CommitRef(
3499 RemoteBookmarks {
3500 bookmark_pattern: Substring(""),
3501 remote_pattern: Substring(""),
3502 remote_ref_state: Some(New),
3503 },
3504 )
3505 "#);
3506 insta::assert_debug_snapshot!(
3508 parse("'foo'").unwrap(),
3509 @r#"CommitRef(Symbol("foo"))"#);
3510 insta::assert_debug_snapshot!(parse("foo-").unwrap(), @r#"
3512 Ancestors {
3513 heads: CommitRef(Symbol("foo")),
3514 generation: 1..2,
3515 }
3516 "#);
3517 insta::assert_debug_snapshot!(parse("foo+").unwrap(), @r#"
3519 Descendants {
3520 roots: CommitRef(Symbol("foo")),
3521 generation: 1..2,
3522 }
3523 "#);
3524 insta::assert_debug_snapshot!(parse("::foo").unwrap(), @r#"
3526 Ancestors {
3527 heads: CommitRef(Symbol("foo")),
3528 generation: 0..18446744073709551615,
3529 }
3530 "#);
3531 insta::assert_debug_snapshot!(parse("foo::").unwrap(), @r#"
3533 Descendants {
3534 roots: CommitRef(Symbol("foo")),
3535 generation: 0..18446744073709551615,
3536 }
3537 "#);
3538 insta::assert_debug_snapshot!(parse("foo::bar").unwrap(), @r#"
3540 DagRange {
3541 roots: CommitRef(Symbol("foo")),
3542 heads: CommitRef(Symbol("bar")),
3543 }
3544 "#);
3545 insta::assert_debug_snapshot!(parse("::").unwrap(), @"All");
3547 insta::assert_debug_snapshot!(parse("..foo").unwrap(), @r#"
3549 Range {
3550 roots: Root,
3551 heads: CommitRef(Symbol("foo")),
3552 generation: 0..18446744073709551615,
3553 }
3554 "#);
3555 insta::assert_debug_snapshot!(parse("foo..").unwrap(), @r#"
3556 NotIn(
3557 Ancestors {
3558 heads: CommitRef(Symbol("foo")),
3559 generation: 0..18446744073709551615,
3560 },
3561 )
3562 "#);
3563 insta::assert_debug_snapshot!(parse("foo..bar").unwrap(), @r#"
3564 Range {
3565 roots: CommitRef(Symbol("foo")),
3566 heads: CommitRef(Symbol("bar")),
3567 generation: 0..18446744073709551615,
3568 }
3569 "#);
3570 insta::assert_debug_snapshot!(parse("..").unwrap(), @r#"NotIn(Root)"#);
3572 insta::assert_debug_snapshot!(
3574 parse("~ foo").unwrap(),
3575 @r#"NotIn(CommitRef(Symbol("foo")))"#);
3576 insta::assert_debug_snapshot!(parse("foo & bar").unwrap(), @r#"
3578 Intersection(
3579 CommitRef(Symbol("foo")),
3580 CommitRef(Symbol("bar")),
3581 )
3582 "#);
3583 insta::assert_debug_snapshot!(parse("foo | bar").unwrap(), @r#"
3585 Union(
3586 CommitRef(Symbol("foo")),
3587 CommitRef(Symbol("bar")),
3588 )
3589 "#);
3590 insta::assert_debug_snapshot!(parse("foo ~ bar").unwrap(), @r#"
3592 Difference(
3593 CommitRef(Symbol("foo")),
3594 CommitRef(Symbol("bar")),
3595 )
3596 "#);
3597 }
3598
3599 #[test]
3600 fn test_parse_revset_with_modifier() {
3601 let settings = insta_settings();
3602 let _guard = settings.bind_to_scope();
3603
3604 insta::assert_debug_snapshot!(
3605 parse_with_modifier("all:foo").unwrap(), @r#"
3606 (
3607 CommitRef(Symbol("foo")),
3608 Some(All),
3609 )
3610 "#);
3611
3612 insta::assert_debug_snapshot!(
3614 parse_with_modifier(r#"exact:"foo""#).unwrap_err().kind(),
3615 @r#"NoSuchModifier("exact")"#);
3616 }
3617
3618 #[test]
3619 fn test_parse_string_pattern() {
3620 let settings = insta_settings();
3621 let _guard = settings.bind_to_scope();
3622
3623 insta::assert_debug_snapshot!(
3624 parse(r#"bookmarks("foo")"#).unwrap(),
3625 @r#"CommitRef(Bookmarks(Substring("foo")))"#);
3626 insta::assert_debug_snapshot!(
3627 parse(r#"bookmarks(exact:"foo")"#).unwrap(),
3628 @r#"CommitRef(Bookmarks(Exact("foo")))"#);
3629 insta::assert_debug_snapshot!(
3630 parse(r#"bookmarks(substring:"foo")"#).unwrap(),
3631 @r#"CommitRef(Bookmarks(Substring("foo")))"#);
3632 insta::assert_debug_snapshot!(
3633 parse(r#"bookmarks(bad:"foo")"#).unwrap_err().kind(),
3634 @r#"Expression("Invalid string pattern")"#);
3635 insta::assert_debug_snapshot!(
3636 parse(r#"bookmarks(exact::"foo")"#).unwrap_err().kind(),
3637 @r#"Expression("Expected string pattern")"#);
3638 insta::assert_debug_snapshot!(
3639 parse(r#"bookmarks(exact:"foo"+)"#).unwrap_err().kind(),
3640 @r#"Expression("Expected string pattern")"#);
3641
3642 insta::assert_debug_snapshot!(
3643 parse(r#"tags("foo")"#).unwrap(),
3644 @r#"CommitRef(Tags(Substring("foo")))"#);
3645 insta::assert_debug_snapshot!(
3646 parse(r#"tags(exact:"foo")"#).unwrap(),
3647 @r#"CommitRef(Tags(Exact("foo")))"#);
3648 insta::assert_debug_snapshot!(
3649 parse(r#"tags(substring:"foo")"#).unwrap(),
3650 @r#"CommitRef(Tags(Substring("foo")))"#);
3651 insta::assert_debug_snapshot!(
3652 parse(r#"tags(bad:"foo")"#).unwrap_err().kind(),
3653 @r#"Expression("Invalid string pattern")"#);
3654 insta::assert_debug_snapshot!(
3655 parse(r#"tags(exact::"foo")"#).unwrap_err().kind(),
3656 @r#"Expression("Expected string pattern")"#);
3657 insta::assert_debug_snapshot!(
3658 parse(r#"tags(exact:"foo"+)"#).unwrap_err().kind(),
3659 @r#"Expression("Expected string pattern")"#);
3660
3661 assert_matches!(
3663 parse(r#"(exact:"foo")"#).unwrap_err().kind(),
3664 RevsetParseErrorKind::NotInfixOperator { .. }
3665 );
3666 }
3667
3668 #[test]
3669 fn test_parse_revset_function() {
3670 let settings = insta_settings();
3671 let _guard = settings.bind_to_scope();
3672
3673 insta::assert_debug_snapshot!(
3674 parse("parents(foo)").unwrap(), @r#"
3675 Ancestors {
3676 heads: CommitRef(Symbol("foo")),
3677 generation: 1..2,
3678 }
3679 "#);
3680 insta::assert_debug_snapshot!(
3681 parse("parents(\"foo\")").unwrap(), @r#"
3682 Ancestors {
3683 heads: CommitRef(Symbol("foo")),
3684 generation: 1..2,
3685 }
3686 "#);
3687 insta::assert_debug_snapshot!(
3688 parse("ancestors(parents(foo))").unwrap(), @r#"
3689 Ancestors {
3690 heads: Ancestors {
3691 heads: CommitRef(Symbol("foo")),
3692 generation: 1..2,
3693 },
3694 generation: 0..18446744073709551615,
3695 }
3696 "#);
3697 insta::assert_debug_snapshot!(
3698 parse("parents(foo, bar, baz)").unwrap_err().kind(), @r#"
3699 InvalidFunctionArguments {
3700 name: "parents",
3701 message: "Expected 1 to 2 arguments",
3702 }
3703 "#);
3704 insta::assert_debug_snapshot!(
3705 parse("parents(foo, 2)").unwrap(), @r#"
3706 Ancestors {
3707 heads: CommitRef(Symbol("foo")),
3708 generation: 2..3,
3709 }
3710 "#);
3711 insta::assert_debug_snapshot!(
3712 parse("root()").unwrap(),
3713 @"Root");
3714 assert!(parse("root(a)").is_err());
3715 insta::assert_debug_snapshot!(
3716 parse(r#"description("")"#).unwrap(),
3717 @r#"Filter(Description(Substring("")))"#);
3718 insta::assert_debug_snapshot!(
3719 parse("description(foo)").unwrap(),
3720 @r#"Filter(Description(Substring("foo")))"#);
3721 insta::assert_debug_snapshot!(
3722 parse("description(visible_heads())").unwrap_err().kind(),
3723 @r#"Expression("Expected string pattern")"#);
3724 insta::assert_debug_snapshot!(
3725 parse("description(\"(foo)\")").unwrap(),
3726 @r#"Filter(Description(Substring("(foo)")))"#);
3727 assert!(parse("mine(foo)").is_err());
3728 insta::assert_debug_snapshot!(
3729 parse_with_workspace("empty()", WorkspaceName::DEFAULT).unwrap(),
3730 @"NotIn(Filter(File(All)))");
3731 assert!(parse_with_workspace("empty(foo)", WorkspaceName::DEFAULT).is_err());
3732 assert!(parse_with_workspace("file()", WorkspaceName::DEFAULT).is_err());
3733 insta::assert_debug_snapshot!(
3734 parse_with_workspace("files(foo)", WorkspaceName::DEFAULT).unwrap(),
3735 @r#"Filter(File(Pattern(PrefixPath("foo"))))"#);
3736 insta::assert_debug_snapshot!(
3737 parse_with_workspace("files(all())", WorkspaceName::DEFAULT).unwrap(),
3738 @"Filter(File(All))");
3739 insta::assert_debug_snapshot!(
3740 parse_with_workspace(r#"files(file:"foo")"#, WorkspaceName::DEFAULT).unwrap(),
3741 @r#"Filter(File(Pattern(FilePath("foo"))))"#);
3742 insta::assert_debug_snapshot!(
3743 parse_with_workspace("files(foo|bar&baz)", WorkspaceName::DEFAULT).unwrap(), @r#"
3744 Filter(
3745 File(
3746 UnionAll(
3747 [
3748 Pattern(PrefixPath("foo")),
3749 Intersection(
3750 Pattern(PrefixPath("bar")),
3751 Pattern(PrefixPath("baz")),
3752 ),
3753 ],
3754 ),
3755 ),
3756 )
3757 "#);
3758 insta::assert_debug_snapshot!(parse("signed()").unwrap(), @"Filter(Signed)");
3759 }
3760
3761 #[test]
3762 fn test_parse_revset_change_commit_id_functions() {
3763 let settings = insta_settings();
3764 let _guard = settings.bind_to_scope();
3765
3766 insta::assert_debug_snapshot!(
3767 parse("change_id(z)").unwrap(),
3768 @r#"CommitRef(ChangeId(HexPrefix("0")))"#);
3769 insta::assert_debug_snapshot!(
3770 parse("change_id('zk')").unwrap(),
3771 @r#"CommitRef(ChangeId(HexPrefix("0f")))"#);
3772 insta::assert_debug_snapshot!(
3773 parse("change_id(01234)").unwrap_err().kind(),
3774 @r#"Expression("Invalid change ID prefix")"#);
3775
3776 insta::assert_debug_snapshot!(
3777 parse("commit_id(0)").unwrap(),
3778 @r#"CommitRef(CommitId(HexPrefix("0")))"#);
3779 insta::assert_debug_snapshot!(
3780 parse("commit_id('0f')").unwrap(),
3781 @r#"CommitRef(CommitId(HexPrefix("0f")))"#);
3782 insta::assert_debug_snapshot!(
3783 parse("commit_id(xyzzy)").unwrap_err().kind(),
3784 @r#"Expression("Invalid commit ID prefix")"#);
3785 }
3786
3787 #[test]
3788 fn test_parse_revset_author_committer_functions() {
3789 let settings = insta_settings();
3790 let _guard = settings.bind_to_scope();
3791
3792 insta::assert_debug_snapshot!(
3793 parse("author(foo)").unwrap(), @r#"
3794 Union(
3795 Filter(AuthorName(Substring("foo"))),
3796 Filter(AuthorEmail(Substring("foo"))),
3797 )
3798 "#);
3799 insta::assert_debug_snapshot!(
3800 parse("author_name(foo)").unwrap(),
3801 @r#"Filter(AuthorName(Substring("foo")))"#);
3802 insta::assert_debug_snapshot!(
3803 parse("author_email(foo)").unwrap(),
3804 @r#"Filter(AuthorEmail(Substring("foo")))"#);
3805
3806 insta::assert_debug_snapshot!(
3807 parse("committer(foo)").unwrap(), @r#"
3808 Union(
3809 Filter(CommitterName(Substring("foo"))),
3810 Filter(CommitterEmail(Substring("foo"))),
3811 )
3812 "#);
3813 insta::assert_debug_snapshot!(
3814 parse("committer_name(foo)").unwrap(),
3815 @r#"Filter(CommitterName(Substring("foo")))"#);
3816 insta::assert_debug_snapshot!(
3817 parse("committer_email(foo)").unwrap(),
3818 @r#"Filter(CommitterEmail(Substring("foo")))"#);
3819
3820 insta::assert_debug_snapshot!(
3821 parse("mine()").unwrap(),
3822 @r#"Filter(AuthorEmail(ExactI("test.user@example.com")))"#);
3823 }
3824
3825 #[test]
3826 fn test_parse_revset_keyword_arguments() {
3827 let settings = insta_settings();
3828 let _guard = settings.bind_to_scope();
3829
3830 insta::assert_debug_snapshot!(
3831 parse("remote_bookmarks(remote=foo)").unwrap(), @r#"
3832 CommitRef(
3833 RemoteBookmarks {
3834 bookmark_pattern: Substring(""),
3835 remote_pattern: Substring("foo"),
3836 remote_ref_state: None,
3837 },
3838 )
3839 "#);
3840 insta::assert_debug_snapshot!(
3841 parse("remote_bookmarks(foo, remote=bar)").unwrap(), @r#"
3842 CommitRef(
3843 RemoteBookmarks {
3844 bookmark_pattern: Substring("foo"),
3845 remote_pattern: Substring("bar"),
3846 remote_ref_state: None,
3847 },
3848 )
3849 "#);
3850 insta::assert_debug_snapshot!(
3851 parse("tracked_remote_bookmarks(foo, remote=bar)").unwrap(), @r#"
3852 CommitRef(
3853 RemoteBookmarks {
3854 bookmark_pattern: Substring("foo"),
3855 remote_pattern: Substring("bar"),
3856 remote_ref_state: Some(Tracked),
3857 },
3858 )
3859 "#);
3860 insta::assert_debug_snapshot!(
3861 parse("untracked_remote_bookmarks(foo, remote=bar)").unwrap(), @r#"
3862 CommitRef(
3863 RemoteBookmarks {
3864 bookmark_pattern: Substring("foo"),
3865 remote_pattern: Substring("bar"),
3866 remote_ref_state: Some(New),
3867 },
3868 )
3869 "#);
3870 insta::assert_debug_snapshot!(
3871 parse(r#"remote_bookmarks(remote=foo, bar)"#).unwrap_err().kind(),
3872 @r#"
3873 InvalidFunctionArguments {
3874 name: "remote_bookmarks",
3875 message: "Positional argument follows keyword argument",
3876 }
3877 "#);
3878 insta::assert_debug_snapshot!(
3879 parse(r#"remote_bookmarks("", foo, remote=bar)"#).unwrap_err().kind(),
3880 @r#"
3881 InvalidFunctionArguments {
3882 name: "remote_bookmarks",
3883 message: "Got multiple values for keyword \"remote\"",
3884 }
3885 "#);
3886 insta::assert_debug_snapshot!(
3887 parse(r#"remote_bookmarks(remote=bar, remote=bar)"#).unwrap_err().kind(),
3888 @r#"
3889 InvalidFunctionArguments {
3890 name: "remote_bookmarks",
3891 message: "Got multiple values for keyword \"remote\"",
3892 }
3893 "#);
3894 insta::assert_debug_snapshot!(
3895 parse(r#"remote_bookmarks(unknown=bar)"#).unwrap_err().kind(),
3896 @r#"
3897 InvalidFunctionArguments {
3898 name: "remote_bookmarks",
3899 message: "Unexpected keyword argument \"unknown\"",
3900 }
3901 "#);
3902 }
3903
3904 #[test]
3905 fn test_expand_symbol_alias() {
3906 let settings = insta_settings();
3907 let _guard = settings.bind_to_scope();
3908
3909 insta::assert_debug_snapshot!(
3910 parse_with_aliases("AB|c", [("AB", "a|b")]).unwrap(), @r#"
3911 Union(
3912 Union(
3913 CommitRef(Symbol("a")),
3914 CommitRef(Symbol("b")),
3915 ),
3916 CommitRef(Symbol("c")),
3917 )
3918 "#);
3919
3920 insta::assert_debug_snapshot!(
3922 parse_with_aliases_and_workspace("files(A)", [("A", "a")], WorkspaceName::DEFAULT)
3923 .unwrap(),
3924 @r#"Filter(File(Pattern(PrefixPath("a"))))"#);
3925
3926 insta::assert_debug_snapshot!(
3928 parse_with_aliases("author_name(A)", [("A", "a")]).unwrap(),
3929 @r#"Filter(AuthorName(Substring("a")))"#);
3930 insta::assert_debug_snapshot!(
3933 parse_with_aliases("author_name(A)", [("A", "(exact:a)")]).unwrap(),
3934 @r#"Filter(AuthorName(Exact("a")))"#);
3935
3936 insta::assert_debug_snapshot!(
3938 parse_with_aliases_and_modifier("A-", [("A", "all:a")]).unwrap_err().kind(),
3939 @r#"InAliasExpansion("A")"#);
3940 }
3941
3942 #[test]
3943 fn test_expand_function_alias() {
3944 let settings = insta_settings();
3945 let _guard = settings.bind_to_scope();
3946
3947 insta::assert_debug_snapshot!(
3949 parse_with_aliases("F(a)", [("F(x)", "author_name(x)|committer_name(x)")]).unwrap(),
3950 @r#"
3951 Union(
3952 Filter(AuthorName(Substring("a"))),
3953 Filter(CommitterName(Substring("a"))),
3954 )
3955 "#);
3956 }
3957
3958 #[test]
3959 fn test_transform_expression() {
3960 let settings = insta_settings();
3961 let _guard = settings.bind_to_scope();
3962
3963 insta::assert_debug_snapshot!(
3965 transform_expression(
3966 &ResolvedRevsetExpression::root(),
3967 |_| ControlFlow::Break(None),
3968 |_| Some(RevsetExpression::none()),
3969 ), @"None");
3970
3971 insta::assert_debug_snapshot!(
3973 transform_expression(
3974 &ResolvedRevsetExpression::root(),
3975 |_| ControlFlow::Break(Some(RevsetExpression::all())),
3976 |_| Some(RevsetExpression::none()),
3977 ), @"Some(All)");
3978
3979 insta::assert_debug_snapshot!(
3981 transform_expression(
3982 &ResolvedRevsetExpression::root().heads(),
3983 |_| ControlFlow::Continue(()),
3984 |x| match x.as_ref() {
3985 RevsetExpression::Root => Some(RevsetExpression::none()),
3986 _ => None,
3987 },
3988 ), @"Some(Heads(None))");
3989
3990 insta::assert_debug_snapshot!(
3992 transform_expression(
3993 &ResolvedRevsetExpression::root().heads(),
3994 |_| ControlFlow::Continue(()),
3995 |x| match x.as_ref() {
3996 RevsetExpression::Heads(y) => Some(y.clone()),
3997 _ => None,
3998 },
3999 ), @"Some(Root)");
4000 }
4001
4002 #[test]
4003 fn test_resolve_referenced_commits() {
4004 let settings = insta_settings();
4005 let _guard = settings.bind_to_scope();
4006
4007 let visibility1 = Rc::new(ResolvedRevsetExpression::WithinVisibility {
4008 candidates: RevsetExpression::commit(CommitId::from_hex("100001")),
4009 visible_heads: vec![CommitId::from_hex("100000")],
4010 });
4011 let visibility2 = Rc::new(ResolvedRevsetExpression::WithinVisibility {
4012 candidates: RevsetExpression::filter(RevsetFilterPredicate::HasConflict),
4013 visible_heads: vec![CommitId::from_hex("200000")],
4014 });
4015 let commit3 = RevsetExpression::commit(CommitId::from_hex("000003"));
4016
4017 insta::assert_debug_snapshot!(
4020 resolve_referenced_commits(&visibility1.intersection(&RevsetExpression::all())), @r#"
4021 Some(
4022 WithinReference {
4023 candidates: Intersection(
4024 WithinVisibility {
4025 candidates: WithinReference {
4026 candidates: Commits(
4027 [
4028 CommitId("100001"),
4029 ],
4030 ),
4031 commits: [
4032 CommitId("100001"),
4033 ],
4034 },
4035 visible_heads: [
4036 CommitId("100000"),
4037 ],
4038 },
4039 All,
4040 ),
4041 commits: [
4042 CommitId("100000"),
4043 CommitId("100001"),
4044 ],
4045 },
4046 )
4047 "#);
4048
4049 insta::assert_debug_snapshot!(
4051 resolve_referenced_commits(
4052 &visibility2
4053 .intersection(&RevsetExpression::all())
4054 .union(&commit3),
4055 ), @r#"
4056 Some(
4057 WithinReference {
4058 candidates: Union(
4059 Intersection(
4060 WithinVisibility {
4061 candidates: Filter(HasConflict),
4062 visible_heads: [
4063 CommitId("200000"),
4064 ],
4065 },
4066 All,
4067 ),
4068 Commits(
4069 [
4070 CommitId("000003"),
4071 ],
4072 ),
4073 ),
4074 commits: [
4075 CommitId("000003"),
4076 CommitId("200000"),
4077 ],
4078 },
4079 )
4080 "#);
4081
4082 insta::assert_debug_snapshot!(
4084 resolve_referenced_commits(
4085 &visibility1
4086 .union(&visibility2)
4087 .union(&commit3)
4088 .intersection(&RevsetExpression::all())
4089 ), @r#"
4090 Some(
4091 WithinReference {
4092 candidates: Intersection(
4093 Union(
4094 Union(
4095 WithinVisibility {
4096 candidates: WithinReference {
4097 candidates: Commits(
4098 [
4099 CommitId("100001"),
4100 ],
4101 ),
4102 commits: [
4103 CommitId("100001"),
4104 ],
4105 },
4106 visible_heads: [
4107 CommitId("100000"),
4108 ],
4109 },
4110 WithinVisibility {
4111 candidates: Filter(HasConflict),
4112 visible_heads: [
4113 CommitId("200000"),
4114 ],
4115 },
4116 ),
4117 Commits(
4118 [
4119 CommitId("000003"),
4120 ],
4121 ),
4122 ),
4123 All,
4124 ),
4125 commits: [
4126 CommitId("000003"),
4127 CommitId("100000"),
4128 CommitId("100001"),
4129 CommitId("200000"),
4130 ],
4131 },
4132 )
4133 "#);
4134
4135 insta::assert_debug_snapshot!(
4137 resolve_referenced_commits(&Rc::new(ResolvedRevsetExpression::WithinVisibility {
4138 candidates: visibility1.clone(),
4139 visible_heads: vec![CommitId::from_hex("400000")],
4140 })), @r#"
4141 Some(
4142 WithinReference {
4143 candidates: WithinVisibility {
4144 candidates: WithinReference {
4145 candidates: WithinVisibility {
4146 candidates: WithinReference {
4147 candidates: Commits(
4148 [
4149 CommitId("100001"),
4150 ],
4151 ),
4152 commits: [
4153 CommitId("100001"),
4154 ],
4155 },
4156 visible_heads: [
4157 CommitId("100000"),
4158 ],
4159 },
4160 commits: [
4161 CommitId("100000"),
4162 CommitId("100001"),
4163 ],
4164 },
4165 visible_heads: [
4166 CommitId("400000"),
4167 ],
4168 },
4169 commits: [
4170 CommitId("400000"),
4171 CommitId("100000"),
4172 CommitId("100001"),
4173 ],
4174 },
4175 )
4176 "#);
4177
4178 let resolved = Rc::new(ResolvedRevsetExpression::WithinReference {
4180 candidates: RevsetExpression::none(),
4183 commits: vec![CommitId::from_hex("100000")],
4184 });
4185 insta::assert_debug_snapshot!(
4186 resolve_referenced_commits(&resolved), @"None");
4187 insta::assert_debug_snapshot!(
4188 resolve_referenced_commits(&resolved.intersection(&RevsetExpression::all())), @r#"
4189 Some(
4190 WithinReference {
4191 candidates: Intersection(
4192 WithinReference {
4193 candidates: None,
4194 commits: [
4195 CommitId("100000"),
4196 ],
4197 },
4198 All,
4199 ),
4200 commits: [
4201 CommitId("100000"),
4202 ],
4203 },
4204 )
4205 "#);
4206 insta::assert_debug_snapshot!(
4207 resolve_referenced_commits(&Rc::new(ResolvedRevsetExpression::WithinVisibility {
4208 candidates: resolved.clone(),
4209 visible_heads: vec![CommitId::from_hex("400000")],
4210 })), @r#"
4211 Some(
4212 WithinReference {
4213 candidates: WithinVisibility {
4214 candidates: WithinReference {
4215 candidates: None,
4216 commits: [
4217 CommitId("100000"),
4218 ],
4219 },
4220 visible_heads: [
4221 CommitId("400000"),
4222 ],
4223 },
4224 commits: [
4225 CommitId("400000"),
4226 CommitId("100000"),
4227 ],
4228 },
4229 )
4230 "#);
4231 }
4232
4233 #[test]
4234 fn test_optimize_subtree() {
4235 let settings = insta_settings();
4236 let _guard = settings.bind_to_scope();
4237
4238 insta::assert_debug_snapshot!(
4242 optimize(parse("parents(bookmarks() & all())").unwrap()), @r#"
4243 Ancestors {
4244 heads: CommitRef(Bookmarks(Substring(""))),
4245 generation: 1..2,
4246 }
4247 "#);
4248 insta::assert_debug_snapshot!(
4249 optimize(parse("children(bookmarks() & all())").unwrap()), @r#"
4250 Descendants {
4251 roots: CommitRef(Bookmarks(Substring(""))),
4252 generation: 1..2,
4253 }
4254 "#);
4255 insta::assert_debug_snapshot!(
4256 optimize(parse("ancestors(bookmarks() & all())").unwrap()), @r#"
4257 Ancestors {
4258 heads: CommitRef(Bookmarks(Substring(""))),
4259 generation: 0..18446744073709551615,
4260 }
4261 "#);
4262 insta::assert_debug_snapshot!(
4263 optimize(parse("descendants(bookmarks() & all())").unwrap()), @r#"
4264 Descendants {
4265 roots: CommitRef(Bookmarks(Substring(""))),
4266 generation: 0..18446744073709551615,
4267 }
4268 "#);
4269
4270 insta::assert_debug_snapshot!(
4271 optimize(parse("(bookmarks() & all())..(all() & tags())").unwrap()), @r#"
4272 Range {
4273 roots: CommitRef(Bookmarks(Substring(""))),
4274 heads: CommitRef(Tags(Substring(""))),
4275 generation: 0..18446744073709551615,
4276 }
4277 "#);
4278 insta::assert_debug_snapshot!(
4279 optimize(parse("(bookmarks() & all())::(all() & tags())").unwrap()), @r#"
4280 DagRange {
4281 roots: CommitRef(Bookmarks(Substring(""))),
4282 heads: CommitRef(Tags(Substring(""))),
4283 }
4284 "#);
4285
4286 insta::assert_debug_snapshot!(
4287 optimize(parse("heads(bookmarks() & all())").unwrap()),
4288 @r#"Heads(CommitRef(Bookmarks(Substring(""))))"#);
4289 insta::assert_debug_snapshot!(
4290 optimize(parse("roots(bookmarks() & all())").unwrap()),
4291 @r#"Roots(CommitRef(Bookmarks(Substring(""))))"#);
4292
4293 insta::assert_debug_snapshot!(
4294 optimize(parse("latest(bookmarks() & all(), 2)").unwrap()), @r#"
4295 Latest {
4296 candidates: CommitRef(Bookmarks(Substring(""))),
4297 count: 2,
4298 }
4299 "#);
4300
4301 insta::assert_debug_snapshot!(
4302 optimize(parse("present(foo ~ bar)").unwrap()), @r#"
4303 Present(
4304 Difference(
4305 CommitRef(Symbol("foo")),
4306 CommitRef(Symbol("bar")),
4307 ),
4308 )
4309 "#);
4310 insta::assert_debug_snapshot!(
4311 optimize(parse("present(bookmarks() & all())").unwrap()),
4312 @r#"Present(CommitRef(Bookmarks(Substring(""))))"#);
4313
4314 insta::assert_debug_snapshot!(
4315 optimize(parse("at_operation(@-, bookmarks() & all())").unwrap()), @r#"
4316 AtOperation {
4317 operation: "@-",
4318 candidates: CommitRef(Bookmarks(Substring(""))),
4319 }
4320 "#);
4321 insta::assert_debug_snapshot!(
4322 optimize(Rc::new(RevsetExpression::WithinReference {
4323 candidates: parse("bookmarks() & all()").unwrap(),
4324 commits: vec![CommitId::from_hex("012345")],
4325 })), @r#"
4326 WithinReference {
4327 candidates: CommitRef(Bookmarks(Substring(""))),
4328 commits: [
4329 CommitId("012345"),
4330 ],
4331 }
4332 "#);
4333 insta::assert_debug_snapshot!(
4334 optimize(Rc::new(RevsetExpression::WithinVisibility {
4335 candidates: parse("bookmarks() & all()").unwrap(),
4336 visible_heads: vec![CommitId::from_hex("012345")],
4337 })), @r#"
4338 WithinReference {
4339 candidates: WithinVisibility {
4340 candidates: CommitRef(Bookmarks(Substring(""))),
4341 visible_heads: [
4342 CommitId("012345"),
4343 ],
4344 },
4345 commits: [
4346 CommitId("012345"),
4347 ],
4348 }
4349 "#);
4350
4351 insta::assert_debug_snapshot!(
4352 optimize(parse("~bookmarks() & all()").unwrap()),
4353 @r#"NotIn(CommitRef(Bookmarks(Substring(""))))"#);
4354 insta::assert_debug_snapshot!(
4355 optimize(parse("(bookmarks() & all()) | (all() & tags())").unwrap()), @r#"
4356 Union(
4357 CommitRef(Bookmarks(Substring(""))),
4358 CommitRef(Tags(Substring(""))),
4359 )
4360 "#);
4361 insta::assert_debug_snapshot!(
4362 optimize(parse("(bookmarks() & all()) & (all() & tags())").unwrap()), @r#"
4363 Intersection(
4364 CommitRef(Bookmarks(Substring(""))),
4365 CommitRef(Tags(Substring(""))),
4366 )
4367 "#);
4368 insta::assert_debug_snapshot!(
4369 optimize(parse("(bookmarks() & all()) ~ (all() & tags())").unwrap()), @r#"
4370 Difference(
4371 CommitRef(Bookmarks(Substring(""))),
4372 CommitRef(Tags(Substring(""))),
4373 )
4374 "#);
4375 }
4376
4377 #[test]
4378 fn test_optimize_unchanged_subtree() {
4379 fn unwrap_union(
4380 expression: &UserRevsetExpression,
4381 ) -> (&Rc<UserRevsetExpression>, &Rc<UserRevsetExpression>) {
4382 match expression {
4383 RevsetExpression::Union(left, right) => (left, right),
4384 _ => panic!("unexpected expression: {expression:?}"),
4385 }
4386 }
4387
4388 let parsed = parse("foo-").unwrap();
4390 let optimized = optimize(parsed.clone());
4391 assert!(Rc::ptr_eq(&parsed, &optimized));
4392
4393 let parsed = parse("bookmarks() | tags()").unwrap();
4394 let optimized = optimize(parsed.clone());
4395 assert!(Rc::ptr_eq(&parsed, &optimized));
4396
4397 let parsed = parse("bookmarks() & tags()").unwrap();
4398 let optimized = optimize(parsed.clone());
4399 assert!(Rc::ptr_eq(&parsed, &optimized));
4400
4401 let parsed = parse("(bookmarks() & all()) | tags()").unwrap();
4403 let optimized = optimize(parsed.clone());
4404 assert_matches!(
4405 unwrap_union(&optimized).0.as_ref(),
4406 RevsetExpression::CommitRef(RevsetCommitRef::Bookmarks(_))
4407 );
4408 assert!(Rc::ptr_eq(
4409 unwrap_union(&parsed).1,
4410 unwrap_union(&optimized).1
4411 ));
4412
4413 let parsed = parse("bookmarks() | (all() & tags())").unwrap();
4415 let optimized = optimize(parsed.clone());
4416 assert!(Rc::ptr_eq(
4417 unwrap_union(&parsed).0,
4418 unwrap_union(&optimized).0
4419 ));
4420 assert_matches!(
4421 unwrap_union(&optimized).1.as_ref(),
4422 RevsetExpression::CommitRef(RevsetCommitRef::Tags(_))
4423 );
4424 }
4425
4426 #[test]
4427 fn test_optimize_basic() {
4428 let settings = insta_settings();
4429 let _guard = settings.bind_to_scope();
4430
4431 insta::assert_debug_snapshot!(optimize(parse("all() | none()").unwrap()), @"All");
4432 insta::assert_debug_snapshot!(optimize(parse("all() & none()").unwrap()), @"None");
4433 insta::assert_debug_snapshot!(optimize(parse("root() | all()").unwrap()), @"All");
4434 insta::assert_debug_snapshot!(optimize(parse("root() & all()").unwrap()), @"Root");
4435 insta::assert_debug_snapshot!(optimize(parse("none() | root()").unwrap()), @"Root");
4436 insta::assert_debug_snapshot!(optimize(parse("none() & root()").unwrap()), @"None");
4437 insta::assert_debug_snapshot!(optimize(parse("~none()").unwrap()), @"All");
4438 insta::assert_debug_snapshot!(optimize(parse("~~none()").unwrap()), @"None");
4439 insta::assert_debug_snapshot!(optimize(parse("~all()").unwrap()), @"None");
4440 insta::assert_debug_snapshot!(optimize(parse("~~all()").unwrap()), @"All");
4441 insta::assert_debug_snapshot!(optimize(parse("~~foo").unwrap()), @r#"CommitRef(Symbol("foo"))"#);
4442 insta::assert_debug_snapshot!(
4443 optimize(parse("(root() | none()) & (visible_heads() | ~~all())").unwrap()), @"Root");
4444 insta::assert_debug_snapshot!(
4445 optimize(UserRevsetExpression::commits(vec![])), @"None");
4446 insta::assert_debug_snapshot!(
4447 optimize(UserRevsetExpression::commits(vec![]).negated()), @"All");
4448 }
4449
4450 #[test]
4451 fn test_optimize_difference() {
4452 let settings = insta_settings();
4453 let _guard = settings.bind_to_scope();
4454
4455 insta::assert_debug_snapshot!(optimize(parse("foo & ~bar").unwrap()), @r#"
4456 Difference(
4457 CommitRef(Symbol("foo")),
4458 CommitRef(Symbol("bar")),
4459 )
4460 "#);
4461 insta::assert_debug_snapshot!(optimize(parse("~foo & bar").unwrap()), @r#"
4462 Difference(
4463 CommitRef(Symbol("bar")),
4464 CommitRef(Symbol("foo")),
4465 )
4466 "#);
4467 insta::assert_debug_snapshot!(optimize(parse("~foo & bar & ~baz").unwrap()), @r#"
4468 Difference(
4469 Difference(
4470 CommitRef(Symbol("bar")),
4471 CommitRef(Symbol("foo")),
4472 ),
4473 CommitRef(Symbol("baz")),
4474 )
4475 "#);
4476 insta::assert_debug_snapshot!(optimize(parse("(all() & ~foo) & bar").unwrap()), @r#"
4477 Difference(
4478 CommitRef(Symbol("bar")),
4479 CommitRef(Symbol("foo")),
4480 )
4481 "#);
4482
4483 insta::assert_debug_snapshot!(
4485 optimize(parse("all() ~ foo").unwrap()),
4486 @r#"NotIn(CommitRef(Symbol("foo")))"#);
4487 insta::assert_debug_snapshot!(optimize(parse("foo ~ bar").unwrap()), @r#"
4488 Difference(
4489 CommitRef(Symbol("foo")),
4490 CommitRef(Symbol("bar")),
4491 )
4492 "#);
4493 insta::assert_debug_snapshot!(optimize(parse("(all() ~ foo) & bar").unwrap()), @r#"
4494 Difference(
4495 CommitRef(Symbol("bar")),
4496 CommitRef(Symbol("foo")),
4497 )
4498 "#);
4499
4500 insta::assert_debug_snapshot!(optimize(parse("::foo & ~::bar").unwrap()), @r#"
4502 Range {
4503 roots: CommitRef(Symbol("bar")),
4504 heads: CommitRef(Symbol("foo")),
4505 generation: 0..18446744073709551615,
4506 }
4507 "#);
4508 insta::assert_debug_snapshot!(optimize(parse("~::foo & ::bar").unwrap()), @r#"
4509 Range {
4510 roots: CommitRef(Symbol("foo")),
4511 heads: CommitRef(Symbol("bar")),
4512 generation: 0..18446744073709551615,
4513 }
4514 "#);
4515 insta::assert_debug_snapshot!(optimize(parse("foo..").unwrap()), @r#"
4516 Range {
4517 roots: CommitRef(Symbol("foo")),
4518 heads: VisibleHeadsOrReferenced,
4519 generation: 0..18446744073709551615,
4520 }
4521 "#);
4522 insta::assert_debug_snapshot!(optimize(parse("foo..bar").unwrap()), @r#"
4523 Range {
4524 roots: CommitRef(Symbol("foo")),
4525 heads: CommitRef(Symbol("bar")),
4526 generation: 0..18446744073709551615,
4527 }
4528 "#);
4529 insta::assert_debug_snapshot!(optimize(parse("foo.. & ::bar").unwrap()), @r#"
4530 Range {
4531 roots: CommitRef(Symbol("foo")),
4532 heads: CommitRef(Symbol("bar")),
4533 generation: 0..18446744073709551615,
4534 }
4535 "#);
4536
4537 insta::assert_debug_snapshot!(optimize(parse("foo & ~~bar").unwrap()), @r#"
4539 Intersection(
4540 CommitRef(Symbol("foo")),
4541 CommitRef(Symbol("bar")),
4542 )
4543 "#);
4544 insta::assert_debug_snapshot!(optimize(parse("foo & ~~~bar").unwrap()), @r#"
4545 Difference(
4546 CommitRef(Symbol("foo")),
4547 CommitRef(Symbol("bar")),
4548 )
4549 "#);
4550 insta::assert_debug_snapshot!(optimize(parse("~(all() & ~foo) & bar").unwrap()), @r#"
4551 Intersection(
4552 CommitRef(Symbol("foo")),
4553 CommitRef(Symbol("bar")),
4554 )
4555 "#);
4556
4557 insta::assert_debug_snapshot!(optimize(parse("~foo & ~bar").unwrap()), @r#"
4559 Difference(
4560 NotIn(CommitRef(Symbol("foo"))),
4561 CommitRef(Symbol("bar")),
4562 )
4563 "#);
4564
4565 insta::assert_debug_snapshot!(optimize(parse("a..b & c..d").unwrap()), @r#"
4567 Intersection(
4568 Range {
4569 roots: Union(
4570 CommitRef(Symbol("a")),
4571 CommitRef(Symbol("c")),
4572 ),
4573 heads: CommitRef(Symbol("b")),
4574 generation: 0..18446744073709551615,
4575 },
4576 Ancestors {
4577 heads: CommitRef(Symbol("d")),
4578 generation: 0..18446744073709551615,
4579 },
4580 )
4581 "#);
4582
4583 insta::assert_debug_snapshot!(optimize(parse("foo ~ ::a & (::b & bar & ::c) & (baz ~ ::d)").unwrap()), @r#"
4586 Intersection(
4587 Intersection(
4588 Intersection(
4589 Intersection(
4590 Range {
4591 roots: Union(
4592 CommitRef(Symbol("a")),
4593 CommitRef(Symbol("d")),
4594 ),
4595 heads: CommitRef(Symbol("b")),
4596 generation: 0..18446744073709551615,
4597 },
4598 Ancestors {
4599 heads: CommitRef(Symbol("c")),
4600 generation: 0..18446744073709551615,
4601 },
4602 ),
4603 CommitRef(Symbol("foo")),
4604 ),
4605 CommitRef(Symbol("bar")),
4606 ),
4607 CommitRef(Symbol("baz")),
4608 )
4609 "#);
4610 }
4611
4612 #[test]
4613 fn test_optimize_not_in_ancestors() {
4614 let settings = insta_settings();
4615 let _guard = settings.bind_to_scope();
4616
4617 insta::assert_debug_snapshot!(optimize(parse("~(::foo)").unwrap()), @r#"
4619 Range {
4620 roots: CommitRef(Symbol("foo")),
4621 heads: VisibleHeadsOrReferenced,
4622 generation: 0..18446744073709551615,
4623 }
4624 "#);
4625
4626 insta::assert_debug_snapshot!(optimize(parse("~(::foo-)").unwrap()), @r#"
4628 Range {
4629 roots: Ancestors {
4630 heads: CommitRef(Symbol("foo")),
4631 generation: 1..2,
4632 },
4633 heads: VisibleHeadsOrReferenced,
4634 generation: 0..18446744073709551615,
4635 }
4636 "#);
4637 insta::assert_debug_snapshot!(optimize(parse("~(::foo--)").unwrap()), @r#"
4638 Range {
4639 roots: Ancestors {
4640 heads: CommitRef(Symbol("foo")),
4641 generation: 2..3,
4642 },
4643 heads: VisibleHeadsOrReferenced,
4644 generation: 0..18446744073709551615,
4645 }
4646 "#);
4647
4648 insta::assert_debug_snapshot!(optimize(parse("~ancestors(foo, 1)").unwrap()), @r#"
4650 NotIn(
4651 Ancestors {
4652 heads: CommitRef(Symbol("foo")),
4653 generation: 0..1,
4654 },
4655 )
4656 "#);
4657 insta::assert_debug_snapshot!(optimize(parse("~ancestors(foo-, 1)").unwrap()), @r#"
4658 NotIn(
4659 Ancestors {
4660 heads: CommitRef(Symbol("foo")),
4661 generation: 1..2,
4662 },
4663 )
4664 "#);
4665 }
4666
4667 #[test]
4668 fn test_optimize_filter_difference() {
4669 let settings = insta_settings();
4670 let _guard = settings.bind_to_scope();
4671
4672 insta::assert_debug_snapshot!(optimize(parse("~empty()").unwrap()), @"Filter(File(All))");
4674
4675 insta::assert_debug_snapshot!(
4677 optimize(parse("(author_name(foo) & ~bar) & baz").unwrap()), @r#"
4678 Intersection(
4679 Difference(
4680 CommitRef(Symbol("baz")),
4681 CommitRef(Symbol("bar")),
4682 ),
4683 Filter(AuthorName(Substring("foo"))),
4684 )
4685 "#);
4686
4687 insta::assert_debug_snapshot!(
4689 optimize(parse("~foo & author_name(bar)").unwrap()), @r#"
4690 Intersection(
4691 NotIn(CommitRef(Symbol("foo"))),
4692 Filter(AuthorName(Substring("bar"))),
4693 )
4694 "#);
4695 insta::assert_debug_snapshot!(
4696 optimize(parse("~foo & (author_name(bar) | baz)").unwrap()), @r#"
4697 Intersection(
4698 NotIn(CommitRef(Symbol("foo"))),
4699 AsFilter(
4700 Union(
4701 Filter(AuthorName(Substring("bar"))),
4702 CommitRef(Symbol("baz")),
4703 ),
4704 ),
4705 )
4706 "#);
4707
4708 insta::assert_debug_snapshot!(
4710 optimize(parse("author_name(foo) ~ bar").unwrap()), @r#"
4711 Intersection(
4712 NotIn(CommitRef(Symbol("bar"))),
4713 Filter(AuthorName(Substring("foo"))),
4714 )
4715 "#);
4716 }
4717
4718 #[test]
4719 fn test_optimize_filter_intersection() {
4720 let settings = insta_settings();
4721 let _guard = settings.bind_to_scope();
4722
4723 insta::assert_debug_snapshot!(
4724 optimize(parse("author_name(foo)").unwrap()),
4725 @r#"Filter(AuthorName(Substring("foo")))"#);
4726
4727 insta::assert_debug_snapshot!(optimize(parse("foo & description(bar)").unwrap()), @r#"
4728 Intersection(
4729 CommitRef(Symbol("foo")),
4730 Filter(Description(Substring("bar"))),
4731 )
4732 "#);
4733 insta::assert_debug_snapshot!(optimize(parse("author_name(foo) & bar").unwrap()), @r#"
4734 Intersection(
4735 CommitRef(Symbol("bar")),
4736 Filter(AuthorName(Substring("foo"))),
4737 )
4738 "#);
4739 insta::assert_debug_snapshot!(
4740 optimize(parse("author_name(foo) & committer_name(bar)").unwrap()), @r#"
4741 AsFilter(
4742 Intersection(
4743 Filter(AuthorName(Substring("foo"))),
4744 Filter(CommitterName(Substring("bar"))),
4745 ),
4746 )
4747 "#);
4748
4749 insta::assert_debug_snapshot!(
4750 optimize(parse("foo & description(bar) & author_name(baz)").unwrap()), @r#"
4751 Intersection(
4752 CommitRef(Symbol("foo")),
4753 AsFilter(
4754 Intersection(
4755 Filter(Description(Substring("bar"))),
4756 Filter(AuthorName(Substring("baz"))),
4757 ),
4758 ),
4759 )
4760 "#);
4761 insta::assert_debug_snapshot!(
4762 optimize(parse("committer_name(foo) & bar & author_name(baz)").unwrap()), @r#"
4763 Intersection(
4764 CommitRef(Symbol("bar")),
4765 AsFilter(
4766 Intersection(
4767 Filter(CommitterName(Substring("foo"))),
4768 Filter(AuthorName(Substring("baz"))),
4769 ),
4770 ),
4771 )
4772 "#);
4773 insta::assert_debug_snapshot!(
4774 optimize(parse_with_workspace(
4775 "committer_name(foo) & files(bar) & baz",
4776 WorkspaceName::DEFAULT).unwrap(),
4777 ), @r#"
4778 Intersection(
4779 CommitRef(Symbol("baz")),
4780 AsFilter(
4781 Intersection(
4782 Filter(CommitterName(Substring("foo"))),
4783 Filter(File(Pattern(PrefixPath("bar")))),
4784 ),
4785 ),
4786 )
4787 "#);
4788 insta::assert_debug_snapshot!(
4789 optimize(parse_with_workspace(
4790 "committer_name(foo) & files(bar) & author_name(baz)",
4791 WorkspaceName::DEFAULT).unwrap(),
4792 ), @r#"
4793 AsFilter(
4794 Intersection(
4795 Intersection(
4796 Filter(CommitterName(Substring("foo"))),
4797 Filter(File(Pattern(PrefixPath("bar")))),
4798 ),
4799 Filter(AuthorName(Substring("baz"))),
4800 ),
4801 )
4802 "#);
4803 insta::assert_debug_snapshot!(
4804 optimize(parse_with_workspace(
4805 "foo & files(bar) & baz",
4806 WorkspaceName::DEFAULT).unwrap(),
4807 ), @r#"
4808 Intersection(
4809 Intersection(
4810 CommitRef(Symbol("foo")),
4811 CommitRef(Symbol("baz")),
4812 ),
4813 Filter(File(Pattern(PrefixPath("bar")))),
4814 )
4815 "#);
4816
4817 insta::assert_debug_snapshot!(
4818 optimize(parse("foo & description(bar) & author_name(baz) & qux").unwrap()), @r#"
4819 Intersection(
4820 Intersection(
4821 CommitRef(Symbol("foo")),
4822 CommitRef(Symbol("qux")),
4823 ),
4824 AsFilter(
4825 Intersection(
4826 Filter(Description(Substring("bar"))),
4827 Filter(AuthorName(Substring("baz"))),
4828 ),
4829 ),
4830 )
4831 "#);
4832 insta::assert_debug_snapshot!(
4833 optimize(parse("foo & description(bar) & parents(author_name(baz)) & qux").unwrap()),
4834 @r#"
4835 Intersection(
4836 Intersection(
4837 Intersection(
4838 CommitRef(Symbol("foo")),
4839 Ancestors {
4840 heads: Filter(AuthorName(Substring("baz"))),
4841 generation: 1..2,
4842 },
4843 ),
4844 CommitRef(Symbol("qux")),
4845 ),
4846 Filter(Description(Substring("bar"))),
4847 )
4848 "#);
4849 insta::assert_debug_snapshot!(
4850 optimize(parse("foo & description(bar) & parents(author_name(baz) & qux)").unwrap()),
4851 @r#"
4852 Intersection(
4853 Intersection(
4854 CommitRef(Symbol("foo")),
4855 Ancestors {
4856 heads: Intersection(
4857 CommitRef(Symbol("qux")),
4858 Filter(AuthorName(Substring("baz"))),
4859 ),
4860 generation: 1..2,
4861 },
4862 ),
4863 Filter(Description(Substring("bar"))),
4864 )
4865 "#);
4866
4867 insta::assert_debug_snapshot!(
4869 optimize(parse("(a & author_name(A)) & (b & author_name(B)) & (c & author_name(C))").unwrap()),
4870 @r#"
4871 Intersection(
4872 Intersection(
4873 Intersection(
4874 CommitRef(Symbol("a")),
4875 CommitRef(Symbol("b")),
4876 ),
4877 CommitRef(Symbol("c")),
4878 ),
4879 AsFilter(
4880 Intersection(
4881 Intersection(
4882 Filter(AuthorName(Substring("A"))),
4883 Filter(AuthorName(Substring("B"))),
4884 ),
4885 Filter(AuthorName(Substring("C"))),
4886 ),
4887 ),
4888 )
4889 "#);
4890 insta::assert_debug_snapshot!(
4891 optimize(parse("(a & author_name(A)) & ((b & author_name(B)) & (c & author_name(C))) & d").unwrap()),
4892 @r#"
4893 Intersection(
4894 Intersection(
4895 Intersection(
4896 Intersection(
4897 CommitRef(Symbol("a")),
4898 CommitRef(Symbol("b")),
4899 ),
4900 CommitRef(Symbol("c")),
4901 ),
4902 CommitRef(Symbol("d")),
4903 ),
4904 AsFilter(
4905 Intersection(
4906 Intersection(
4907 Filter(AuthorName(Substring("A"))),
4908 Filter(AuthorName(Substring("B"))),
4909 ),
4910 Filter(AuthorName(Substring("C"))),
4911 ),
4912 ),
4913 )
4914 "#);
4915
4916 insta::assert_debug_snapshot!(
4918 optimize(parse("foo & (all() & description(bar)) & (author_name(baz) & all())").unwrap()),
4919 @r#"
4920 Intersection(
4921 CommitRef(Symbol("foo")),
4922 AsFilter(
4923 Intersection(
4924 Filter(Description(Substring("bar"))),
4925 Filter(AuthorName(Substring("baz"))),
4926 ),
4927 ),
4928 )
4929 "#);
4930
4931 insta::assert_debug_snapshot!(
4933 optimize(parse("author_name(foo) & bar & at_operation(@-, committer_name(baz))").unwrap()),
4934 @r#"
4935 Intersection(
4936 Intersection(
4937 CommitRef(Symbol("bar")),
4938 AtOperation {
4939 operation: "@-",
4940 candidates: Filter(CommitterName(Substring("baz"))),
4941 },
4942 ),
4943 Filter(AuthorName(Substring("foo"))),
4944 )
4945 "#);
4946 }
4947
4948 #[test]
4949 fn test_optimize_filter_subtree() {
4950 let settings = insta_settings();
4951 let _guard = settings.bind_to_scope();
4952
4953 insta::assert_debug_snapshot!(
4954 optimize(parse("(author_name(foo) | bar) & baz").unwrap()), @r#"
4955 Intersection(
4956 CommitRef(Symbol("baz")),
4957 AsFilter(
4958 Union(
4959 Filter(AuthorName(Substring("foo"))),
4960 CommitRef(Symbol("bar")),
4961 ),
4962 ),
4963 )
4964 "#);
4965
4966 insta::assert_debug_snapshot!(
4968 optimize(parse("merges() & foo | bar").unwrap()), @r#"
4969 Union(
4970 Intersection(
4971 CommitRef(Symbol("foo")),
4972 Filter(ParentCount(2..4294967295)),
4973 ),
4974 CommitRef(Symbol("bar")),
4975 )
4976 "#);
4977
4978 insta::assert_debug_snapshot!(
4981 optimize(parse("merges() & foo | conflicts()").unwrap()), @r#"
4982 AsFilter(
4983 Union(
4984 Intersection(
4985 CommitRef(Symbol("foo")),
4986 Filter(ParentCount(2..4294967295)),
4987 ),
4988 Filter(HasConflict),
4989 ),
4990 )
4991 "#);
4992
4993 insta::assert_debug_snapshot!(
4995 optimize(parse("foo | conflicts() & merges() & signed()").unwrap()), @r#"
4996 AsFilter(
4997 Union(
4998 CommitRef(Symbol("foo")),
4999 Intersection(
5000 Intersection(
5001 Filter(HasConflict),
5002 Filter(ParentCount(2..4294967295)),
5003 ),
5004 Filter(Signed),
5005 ),
5006 ),
5007 )
5008 "#);
5009
5010 insta::assert_debug_snapshot!(
5011 optimize(parse("(foo | committer_name(bar)) & description(baz) & qux").unwrap()), @r#"
5012 Intersection(
5013 CommitRef(Symbol("qux")),
5014 AsFilter(
5015 Intersection(
5016 Union(
5017 CommitRef(Symbol("foo")),
5018 Filter(CommitterName(Substring("bar"))),
5019 ),
5020 Filter(Description(Substring("baz"))),
5021 ),
5022 ),
5023 )
5024 "#);
5025
5026 insta::assert_debug_snapshot!(
5027 optimize(parse(
5028 "(~present(author_name(foo) & description(bar)) | baz) & qux").unwrap()), @r#"
5029 Intersection(
5030 CommitRef(Symbol("qux")),
5031 AsFilter(
5032 Union(
5033 NotIn(
5034 Present(
5035 Intersection(
5036 Filter(AuthorName(Substring("foo"))),
5037 Filter(Description(Substring("bar"))),
5038 ),
5039 ),
5040 ),
5041 CommitRef(Symbol("baz")),
5042 ),
5043 ),
5044 )
5045 "#);
5046
5047 insta::assert_debug_snapshot!(
5049 optimize(parse(
5050 "(a & (author_name(A) | 0)) & (b & (author_name(B) | 1)) & (c & (author_name(C) | 2))").unwrap()),
5051 @r#"
5052 Intersection(
5053 Intersection(
5054 Intersection(
5055 CommitRef(Symbol("a")),
5056 CommitRef(Symbol("b")),
5057 ),
5058 CommitRef(Symbol("c")),
5059 ),
5060 AsFilter(
5061 Intersection(
5062 Intersection(
5063 Union(
5064 Filter(AuthorName(Substring("A"))),
5065 CommitRef(Symbol("0")),
5066 ),
5067 Union(
5068 Filter(AuthorName(Substring("B"))),
5069 CommitRef(Symbol("1")),
5070 ),
5071 ),
5072 Union(
5073 Filter(AuthorName(Substring("C"))),
5074 CommitRef(Symbol("2")),
5075 ),
5076 ),
5077 ),
5078 )
5079 "#);
5080 }
5081
5082 #[test]
5083 fn test_optimize_ancestors() {
5084 let settings = insta_settings();
5085 let _guard = settings.bind_to_scope();
5086
5087 insta::assert_debug_snapshot!(optimize(parse("foo--").unwrap()), @r#"
5089 Ancestors {
5090 heads: CommitRef(Symbol("foo")),
5091 generation: 2..3,
5092 }
5093 "#);
5094 insta::assert_debug_snapshot!(optimize(parse("::(foo---)").unwrap()), @r#"
5095 Ancestors {
5096 heads: CommitRef(Symbol("foo")),
5097 generation: 3..18446744073709551615,
5098 }
5099 "#);
5100 insta::assert_debug_snapshot!(optimize(parse("(::foo)---").unwrap()), @r#"
5101 Ancestors {
5102 heads: CommitRef(Symbol("foo")),
5103 generation: 3..18446744073709551615,
5104 }
5105 "#);
5106
5107 insta::assert_debug_snapshot!(optimize(parse("foo---+").unwrap()), @r#"
5109 Descendants {
5110 roots: Ancestors {
5111 heads: CommitRef(Symbol("foo")),
5112 generation: 3..4,
5113 },
5114 generation: 1..2,
5115 }
5116 "#);
5117
5118 insta::assert_debug_snapshot!(optimize(parse("foo..(bar--)").unwrap()), @r#"
5120 Range {
5121 roots: CommitRef(Symbol("foo")),
5122 heads: CommitRef(Symbol("bar")),
5123 generation: 2..18446744073709551615,
5124 }
5125 "#);
5126 insta::assert_debug_snapshot!(optimize(parse("(foo--)..(bar---)").unwrap()), @r#"
5128 Range {
5129 roots: Ancestors {
5130 heads: CommitRef(Symbol("foo")),
5131 generation: 2..3,
5132 },
5133 heads: CommitRef(Symbol("bar")),
5134 generation: 3..18446744073709551615,
5135 }
5136 "#);
5137 insta::assert_debug_snapshot!(
5139 optimize(parse("~ancestors(foo, 2) & ::bar").unwrap()), @r#"
5140 Difference(
5141 Ancestors {
5142 heads: CommitRef(Symbol("bar")),
5143 generation: 0..18446744073709551615,
5144 },
5145 Ancestors {
5146 heads: CommitRef(Symbol("foo")),
5147 generation: 0..2,
5148 },
5149 )
5150 "#);
5151
5152 insta::assert_debug_snapshot!(optimize(parse("(foo..bar)--").unwrap()), @r#"
5155 Ancestors {
5156 heads: Range {
5157 roots: CommitRef(Symbol("foo")),
5158 heads: CommitRef(Symbol("bar")),
5159 generation: 0..18446744073709551615,
5160 },
5161 generation: 2..3,
5162 }
5163 "#);
5164 insta::assert_debug_snapshot!(optimize(parse("foo..(bar..baz)").unwrap()), @r#"
5165 Range {
5166 roots: CommitRef(Symbol("foo")),
5167 heads: Range {
5168 roots: CommitRef(Symbol("bar")),
5169 heads: CommitRef(Symbol("baz")),
5170 generation: 0..18446744073709551615,
5171 },
5172 generation: 0..18446744073709551615,
5173 }
5174 "#);
5175
5176 insta::assert_debug_snapshot!(
5178 optimize(parse("ancestors(ancestors(foo), 0)").unwrap()), @r#"
5179 Ancestors {
5180 heads: CommitRef(Symbol("foo")),
5181 generation: 0..0,
5182 }
5183 "#
5184 );
5185 insta::assert_debug_snapshot!(
5186 optimize(parse("ancestors(ancestors(foo, 0))").unwrap()), @r#"
5187 Ancestors {
5188 heads: CommitRef(Symbol("foo")),
5189 generation: 0..0,
5190 }
5191 "#
5192 );
5193 }
5194
5195 #[test]
5196 fn test_optimize_descendants() {
5197 let settings = insta_settings();
5198 let _guard = settings.bind_to_scope();
5199
5200 insta::assert_debug_snapshot!(optimize(parse("foo++").unwrap()), @r#"
5202 Descendants {
5203 roots: CommitRef(Symbol("foo")),
5204 generation: 2..3,
5205 }
5206 "#);
5207 insta::assert_debug_snapshot!(optimize(parse("(foo+++)::").unwrap()), @r#"
5208 Descendants {
5209 roots: CommitRef(Symbol("foo")),
5210 generation: 3..18446744073709551615,
5211 }
5212 "#);
5213 insta::assert_debug_snapshot!(optimize(parse("(foo::)+++").unwrap()), @r#"
5214 Descendants {
5215 roots: CommitRef(Symbol("foo")),
5216 generation: 3..18446744073709551615,
5217 }
5218 "#);
5219
5220 insta::assert_debug_snapshot!(optimize(parse("foo+++-").unwrap()), @r#"
5222 Ancestors {
5223 heads: Descendants {
5224 roots: CommitRef(Symbol("foo")),
5225 generation: 3..4,
5226 },
5227 generation: 1..2,
5228 }
5229 "#);
5230
5231 insta::assert_debug_snapshot!(optimize(parse("(foo++)::bar").unwrap()), @r#"
5235 DagRange {
5236 roots: Descendants {
5237 roots: CommitRef(Symbol("foo")),
5238 generation: 2..3,
5239 },
5240 heads: CommitRef(Symbol("bar")),
5241 }
5242 "#);
5243 }
5244
5245 #[test]
5246 fn test_optimize_flatten_intersection() {
5247 let settings = insta_settings();
5248 let _guard = settings.bind_to_scope();
5249
5250 insta::assert_debug_snapshot!(optimize(parse("a & ((b & c) & (d & e))").unwrap()), @r#"
5252 Intersection(
5253 Intersection(
5254 Intersection(
5255 Intersection(
5256 CommitRef(Symbol("a")),
5257 CommitRef(Symbol("b")),
5258 ),
5259 CommitRef(Symbol("c")),
5260 ),
5261 CommitRef(Symbol("d")),
5262 ),
5263 CommitRef(Symbol("e")),
5264 )
5265 "#);
5266 }
5267
5268 #[test]
5269 fn test_optimize_ancestors_union() {
5270 let settings = insta_settings();
5271 let _guard = settings.bind_to_scope();
5272
5273 insta::assert_debug_snapshot!(optimize(parse("::a | ::b | ::c | ::d").unwrap()), @r#"
5275 Ancestors {
5276 heads: Union(
5277 Union(
5278 CommitRef(Symbol("a")),
5279 CommitRef(Symbol("b")),
5280 ),
5281 Union(
5282 CommitRef(Symbol("c")),
5283 CommitRef(Symbol("d")),
5284 ),
5285 ),
5286 generation: 0..18446744073709551615,
5287 }
5288 "#);
5289 insta::assert_debug_snapshot!(optimize(parse("ancestors(a-) | ancestors(b)").unwrap()), @r#"
5290 Ancestors {
5291 heads: Union(
5292 Ancestors {
5293 heads: CommitRef(Symbol("a")),
5294 generation: 1..2,
5295 },
5296 CommitRef(Symbol("b")),
5297 ),
5298 generation: 0..18446744073709551615,
5299 }
5300 "#);
5301
5302 insta::assert_debug_snapshot!(optimize(parse("~::a- & ~::b & ~::c & ::d").unwrap()), @r#"
5304 Range {
5305 roots: Union(
5306 Union(
5307 Ancestors {
5308 heads: CommitRef(Symbol("a")),
5309 generation: 1..2,
5310 },
5311 CommitRef(Symbol("b")),
5312 ),
5313 CommitRef(Symbol("c")),
5314 ),
5315 heads: CommitRef(Symbol("d")),
5316 generation: 0..18446744073709551615,
5317 }
5318 "#);
5319 insta::assert_debug_snapshot!(optimize(parse("a..b ~ ::c- ~ ::d").unwrap()), @r#"
5320 Range {
5321 roots: Union(
5322 Union(
5323 CommitRef(Symbol("a")),
5324 Ancestors {
5325 heads: CommitRef(Symbol("c")),
5326 generation: 1..2,
5327 },
5328 ),
5329 CommitRef(Symbol("d")),
5330 ),
5331 heads: CommitRef(Symbol("b")),
5332 generation: 0..18446744073709551615,
5333 }
5334 "#);
5335
5336 insta::assert_debug_snapshot!(optimize(parse("ancestors(a, 2) | ancestors(b)").unwrap()), @r#"
5338 Union(
5339 Ancestors {
5340 heads: CommitRef(Symbol("a")),
5341 generation: 0..2,
5342 },
5343 Ancestors {
5344 heads: CommitRef(Symbol("b")),
5345 generation: 0..18446744073709551615,
5346 },
5347 )
5348 "#);
5349 }
5350
5351 #[test]
5352 fn test_optimize_sort_negations_and_ancestors() {
5353 let settings = insta_settings();
5354 let _guard = settings.bind_to_scope();
5355
5356 insta::assert_debug_snapshot!(optimize(parse("~a & ::b & ~::c & d ~ e & f & ::g & ~::h").unwrap()), @r#"
5359 Difference(
5360 Difference(
5361 Intersection(
5362 Intersection(
5363 Intersection(
5364 Range {
5365 roots: Union(
5366 CommitRef(Symbol("c")),
5367 CommitRef(Symbol("h")),
5368 ),
5369 heads: CommitRef(Symbol("b")),
5370 generation: 0..18446744073709551615,
5371 },
5372 Ancestors {
5373 heads: CommitRef(Symbol("g")),
5374 generation: 0..18446744073709551615,
5375 },
5376 ),
5377 CommitRef(Symbol("d")),
5378 ),
5379 CommitRef(Symbol("f")),
5380 ),
5381 CommitRef(Symbol("a")),
5382 ),
5383 CommitRef(Symbol("e")),
5384 )
5385 "#);
5386 }
5387
5388 #[test]
5389 fn test_optimize_heads_range() {
5390 let settings = insta_settings();
5391 let _guard = settings.bind_to_scope();
5392
5393 insta::assert_debug_snapshot!(optimize(parse("heads(::)").unwrap()), @r"
5395 HeadsRange {
5396 roots: None,
5397 heads: VisibleHeadsOrReferenced,
5398 filter: All,
5399 }
5400 ");
5401 insta::assert_debug_snapshot!(optimize(parse("heads(::foo)").unwrap()), @r#"
5402 HeadsRange {
5403 roots: None,
5404 heads: CommitRef(Symbol("foo")),
5405 filter: All,
5406 }
5407 "#);
5408 insta::assert_debug_snapshot!(optimize(parse("heads(..)").unwrap()), @r"
5411 HeadsRange {
5412 roots: None,
5413 heads: VisibleHeadsOrReferenced,
5414 filter: NotIn(Root),
5415 }
5416 ");
5417 insta::assert_debug_snapshot!(optimize(parse("heads(foo..)").unwrap()), @r#"
5418 HeadsRange {
5419 roots: CommitRef(Symbol("foo")),
5420 heads: VisibleHeadsOrReferenced,
5421 filter: All,
5422 }
5423 "#);
5424 insta::assert_debug_snapshot!(optimize(parse("heads(..bar)").unwrap()), @r#"
5425 HeadsRange {
5426 roots: Root,
5427 heads: CommitRef(Symbol("bar")),
5428 filter: All,
5429 }
5430 "#);
5431 insta::assert_debug_snapshot!(optimize(parse("heads(foo..bar)").unwrap()), @r#"
5432 HeadsRange {
5433 roots: CommitRef(Symbol("foo")),
5434 heads: CommitRef(Symbol("bar")),
5435 filter: All,
5436 }
5437 "#);
5438 insta::assert_debug_snapshot!(optimize(parse("heads(~::foo & ::bar)").unwrap()), @r#"
5439 HeadsRange {
5440 roots: CommitRef(Symbol("foo")),
5441 heads: CommitRef(Symbol("bar")),
5442 filter: All,
5443 }
5444 "#);
5445 insta::assert_debug_snapshot!(optimize(parse("heads(~::foo)").unwrap()), @r#"
5446 HeadsRange {
5447 roots: CommitRef(Symbol("foo")),
5448 heads: VisibleHeadsOrReferenced,
5449 filter: All,
5450 }
5451 "#);
5452 insta::assert_debug_snapshot!(optimize(parse("heads(a..b & c..d)").unwrap()), @r#"
5453 HeadsRange {
5454 roots: Union(
5455 CommitRef(Symbol("a")),
5456 CommitRef(Symbol("c")),
5457 ),
5458 heads: CommitRef(Symbol("b")),
5459 filter: Ancestors {
5460 heads: CommitRef(Symbol("d")),
5461 generation: 0..18446744073709551615,
5462 },
5463 }
5464 "#);
5465
5466 insta::assert_debug_snapshot!(optimize(parse("heads(ancestors(foo, 2))").unwrap()), @r#"
5468 Heads(
5469 Ancestors {
5470 heads: CommitRef(Symbol("foo")),
5471 generation: 0..2,
5472 },
5473 )
5474 "#);
5475
5476 insta::assert_debug_snapshot!(optimize(parse("heads(ancestors(foo--))").unwrap()), @r#"
5478 HeadsRange {
5479 roots: None,
5480 heads: Ancestors {
5481 heads: CommitRef(Symbol("foo")),
5482 generation: 2..3,
5483 },
5484 filter: All,
5485 }
5486 "#);
5487
5488 insta::assert_debug_snapshot!(optimize(parse("heads(author_name(A) | author_name(B))").unwrap()), @r#"
5490 HeadsRange {
5491 roots: None,
5492 heads: VisibleHeadsOrReferenced,
5493 filter: AsFilter(
5494 Union(
5495 Filter(AuthorName(Substring("A"))),
5496 Filter(AuthorName(Substring("B"))),
5497 ),
5498 ),
5499 }
5500 "#);
5501 insta::assert_debug_snapshot!(optimize(parse("heads(~author_name(A))").unwrap()), @r#"
5502 HeadsRange {
5503 roots: None,
5504 heads: VisibleHeadsOrReferenced,
5505 filter: AsFilter(
5506 NotIn(Filter(AuthorName(Substring("A")))),
5507 ),
5508 }
5509 "#);
5510 insta::assert_debug_snapshot!(optimize(parse("heads(~foo)").unwrap()), @r#"
5511 HeadsRange {
5512 roots: None,
5513 heads: VisibleHeadsOrReferenced,
5514 filter: NotIn(CommitRef(Symbol("foo"))),
5515 }
5516 "#);
5517
5518 insta::assert_debug_snapshot!(optimize(parse("heads(author_name(A) & ::foo ~ author_name(B))").unwrap()), @r#"
5520 HeadsRange {
5521 roots: None,
5522 heads: CommitRef(Symbol("foo")),
5523 filter: AsFilter(
5524 Difference(
5525 Filter(AuthorName(Substring("A"))),
5526 Filter(AuthorName(Substring("B"))),
5527 ),
5528 ),
5529 }
5530 "#);
5531
5532 insta::assert_debug_snapshot!(optimize(parse("heads(~foo & ~roots(bar) & ::baz)").unwrap()), @r#"
5534 HeadsRange {
5535 roots: None,
5536 heads: CommitRef(Symbol("baz")),
5537 filter: Difference(
5538 NotIn(CommitRef(Symbol("foo"))),
5539 Roots(CommitRef(Symbol("bar"))),
5540 ),
5541 }
5542 "#);
5543 }
5544
5545 #[test]
5546 fn test_escape_string_literal() {
5547 assert_eq!(format_symbol("foo"), "foo");
5549 assert_eq!(format_symbol("foo.bar"), "foo.bar");
5550
5551 assert_eq!(format_symbol("foo@bar"), r#""foo@bar""#);
5553 assert_eq!(format_symbol("foo bar"), r#""foo bar""#);
5554 assert_eq!(format_symbol(" foo "), r#"" foo ""#);
5555 assert_eq!(format_symbol("(foo)"), r#""(foo)""#);
5556 assert_eq!(format_symbol("all:foo"), r#""all:foo""#);
5557
5558 assert_eq!(format_symbol("foo\"bar"), r#""foo\"bar""#);
5560 assert_eq!(format_symbol("foo\\bar"), r#""foo\\bar""#);
5561 assert_eq!(format_symbol("foo\\\"bar"), r#""foo\\\"bar""#);
5562 assert_eq!(format_symbol("foo\nbar"), r#""foo\nbar""#);
5563
5564 assert_eq!(format_symbol("foo\"bar"), r#""foo\"bar""#);
5567 assert_eq!(format_symbol("foo\\bar"), r#""foo\\bar""#);
5568 assert_eq!(format_symbol("foo\\\"bar"), r#""foo\\\"bar""#);
5569 assert_eq!(format_symbol("foo \x01 bar"), r#""foo \x01 bar""#);
5570 }
5571
5572 #[test]
5573 fn test_escape_remote_symbol() {
5574 assert_eq!(format_remote_symbol("foo", "bar"), "foo@bar");
5575 assert_eq!(
5576 format_remote_symbol(" foo ", "bar:baz"),
5577 r#"" foo "@"bar:baz""#
5578 );
5579 }
5580}