1#![expect(missing_docs)]
16
17use std::any::Any;
18use std::collections::HashMap;
19use std::collections::hash_map;
20use std::convert::Infallible;
21use std::fmt;
22use std::ops::ControlFlow;
23use std::ops::Range;
24use std::sync::Arc;
25use std::sync::LazyLock;
26
27use futures::Stream;
28use futures::StreamExt as _;
29use futures::future::LocalBoxFuture;
30use futures::stream::LocalBoxStream;
31use itertools::Itertools as _;
32use pollster::FutureExt as _;
33use thiserror::Error;
34
35use crate::backend::BackendError;
36use crate::backend::ChangeId;
37use crate::backend::CommitId;
38use crate::commit::Commit;
39use crate::dsl_util;
40use crate::dsl_util::collect_similar;
41use crate::fileset;
42use crate::fileset::FilesetAliasesMap;
43use crate::fileset::FilesetDiagnostics;
44use crate::fileset::FilesetExpression;
45use crate::fileset::FilesetParseContext;
46use crate::graph::GraphNode;
47use crate::id_prefix::IdPrefixContext;
48use crate::id_prefix::IdPrefixIndex;
49use crate::index::ResolvedChangeTargets;
50use crate::object_id::HexPrefix;
51use crate::object_id::PrefixResolution;
52use crate::op_store::LocalRemoteRefTarget;
53use crate::op_store::RefTarget;
54use crate::op_store::RemoteRefState;
55use crate::op_walk;
56use crate::ref_name::RefName;
57use crate::ref_name::RemoteName;
58use crate::ref_name::RemoteRefSymbol;
59use crate::ref_name::RemoteRefSymbolBuf;
60use crate::ref_name::WorkspaceName;
61use crate::ref_name::WorkspaceNameBuf;
62use crate::repo::ReadonlyRepo;
63use crate::repo::Repo;
64use crate::repo::RepoLoaderError;
65use crate::revset_parser;
66pub use crate::revset_parser::BinaryOp;
67pub use crate::revset_parser::ExpressionKind;
68pub use crate::revset_parser::ExpressionNode;
69pub use crate::revset_parser::FunctionCallNode;
70pub use crate::revset_parser::RevsetAliasesMap;
71pub use crate::revset_parser::RevsetDiagnostics;
72pub use crate::revset_parser::RevsetParseError;
73pub use crate::revset_parser::RevsetParseErrorKind;
74pub use crate::revset_parser::UnaryOp;
75pub use crate::revset_parser::expect_literal;
76pub use crate::revset_parser::parse_program;
77pub use crate::revset_parser::parse_symbol;
78use crate::store::Store;
79use crate::str_util::StringExpression;
80use crate::str_util::StringPattern;
81use crate::symbol_util::format_string;
82use crate::time_util::DatePattern;
83use crate::time_util::DatePatternContext;
84use crate::ui_path::RepoPathUiConverter;
85
86#[derive(Debug, Error)]
88pub enum RevsetResolutionError {
89 #[error("Revision `{name}` doesn't exist")]
90 NoSuchRevision {
91 name: String,
92 candidates: Vec<String>,
93 },
94 #[error("Workspace `{}` doesn't have a working-copy commit", name.as_symbol())]
95 WorkspaceMissingWorkingCopy { name: WorkspaceNameBuf },
96 #[error("An empty string is not a valid revision")]
97 EmptyString,
98 #[error("Commit ID prefix `{0}` is ambiguous")]
99 AmbiguousCommitIdPrefix(String),
100 #[error("Change ID prefix `{0}` is ambiguous")]
101 AmbiguousChangeIdPrefix(String),
102 #[error("Change ID `{symbol}` is divergent")]
103 DivergentChangeId {
104 symbol: String,
105 visible_targets: Vec<(usize, CommitId)>,
106 },
107 #[error("Name `{symbol}` is conflicted")]
108 ConflictedRef {
109 kind: &'static str,
110 symbol: String,
111 targets: Vec<CommitId>,
112 },
113 #[error("Unexpected error from commit backend")]
114 Backend(#[source] BackendError),
115 #[error(transparent)]
116 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
117}
118
119#[derive(Debug, Error)]
121pub enum RevsetEvaluationError {
122 #[error("Unexpected error from commit backend")]
123 Backend(#[from] BackendError),
124 #[error(transparent)]
125 Other(Box<dyn std::error::Error + Send + Sync>),
126}
127
128impl RevsetEvaluationError {
129 pub fn into_backend_error(self) -> BackendError {
132 match self {
133 Self::Backend(err) => err,
134 Self::Other(err) => BackendError::Other(err),
135 }
136 }
137}
138
139pub const GENERATION_RANGE_FULL: Range<u64> = 0..u64::MAX;
141pub const GENERATION_RANGE_EMPTY: Range<u64> = 0..0;
142
143pub const PARENTS_RANGE_FULL: Range<u32> = 0..u32::MAX;
144
145#[derive(Clone, Debug)]
147pub enum RevsetCommitRef {
148 WorkingCopy(WorkspaceNameBuf),
149 WorkingCopies,
150 Symbol(String),
151 RemoteSymbol(RemoteRefSymbolBuf),
152 ChangeId(HexPrefix),
153 CommitId(HexPrefix),
154 Bookmarks(StringExpression),
155 RemoteBookmarks {
156 symbol: RemoteRefSymbolExpression,
157 remote_ref_state: Option<RemoteRefState>,
158 },
159 Tags(StringExpression),
160 RemoteTags {
161 symbol: RemoteRefSymbolExpression,
162 remote_ref_state: Option<RemoteRefState>,
163 },
164}
165
166#[derive(Clone, Debug)]
168pub struct RemoteRefSymbolExpression {
169 pub name: StringExpression,
171 pub remote: StringExpression,
173}
174
175pub trait RevsetFilterExtension: std::fmt::Debug + Any + Send + Sync {
177 fn matches_commit(&self, commit: &Commit) -> bool;
179}
180
181impl dyn RevsetFilterExtension {
182 pub fn downcast_ref<T: RevsetFilterExtension>(&self) -> Option<&T> {
184 (self as &dyn Any).downcast_ref()
185 }
186}
187
188#[derive(Eq, Copy, Clone, Debug, PartialEq)]
189pub enum DiffMatchSide {
190 Either,
191 Left,
192 Right,
193}
194
195#[derive(Clone, Debug)]
196pub enum RevsetFilterPredicate {
197 ParentCount(Range<u32>),
199 Description(StringExpression),
201 Subject(StringExpression),
203 AuthorName(StringExpression),
205 AuthorEmail(StringExpression),
207 AuthorDate(DatePattern),
209 CommitterName(StringExpression),
211 CommitterEmail(StringExpression),
213 CommitterDate(DatePattern),
215 File(FilesetExpression),
217 DiffLines {
219 text: StringExpression,
220 files: FilesetExpression,
221 side: DiffMatchSide,
222 },
223 HasConflict,
225 Signed,
227 Extension(Arc<dyn RevsetFilterExtension>),
229}
230
231mod private {
232 pub trait ExpressionState {
234 type CommitRef: Clone;
235 type Operation: Clone;
236 }
237
238 #[derive(Debug)]
240 pub enum UserExpressionState {}
241 #[derive(Debug)]
242 pub enum ResolvedExpressionState {}
243}
244
245use private::ExpressionState;
246use private::ResolvedExpressionState;
247use private::UserExpressionState;
248
249impl ExpressionState for UserExpressionState {
250 type CommitRef = RevsetCommitRef;
251 type Operation = String;
252}
253
254impl ExpressionState for ResolvedExpressionState {
255 type CommitRef = Infallible;
256 type Operation = Infallible;
257}
258
259pub type UserRevsetExpression = RevsetExpression<UserExpressionState>;
261pub type ResolvedRevsetExpression = RevsetExpression<ResolvedExpressionState>;
263
264#[derive(Clone, Debug)]
269pub enum RevsetExpression<St: ExpressionState> {
270 None,
271 All,
272 VisibleHeads,
273 VisibleHeadsOrReferenced,
276 Root,
277 Commits(Vec<CommitId>),
278 CommitRef(St::CommitRef),
279 Ancestors {
280 heads: Arc<Self>,
281 generation: Range<u64>,
282 parents_range: Range<u32>,
283 },
284 Descendants {
285 roots: Arc<Self>,
286 generation: Range<u64>,
287 },
288 Range {
290 roots: Arc<Self>,
291 heads: Arc<Self>,
292 generation: Range<u64>,
293 parents_range: Range<u32>,
295 },
296 DagRange {
298 roots: Arc<Self>,
299 heads: Arc<Self>,
300 },
302 Reachable {
304 sources: Arc<Self>,
305 domain: Arc<Self>,
306 },
307 Heads(Arc<Self>),
308 HeadsRange {
311 roots: Arc<Self>,
312 heads: Arc<Self>,
313 parents_range: Range<u32>,
314 filter: Arc<Self>,
315 },
316 Roots(Arc<Self>),
317 Forks,
318 ForkPoint(Arc<Self>),
319 MergePoint(Arc<Self>),
320 Bisect(Arc<Self>),
321 HasSize {
322 candidates: Arc<Self>,
323 count: usize,
324 },
325 Latest {
326 candidates: Arc<Self>,
327 count: usize,
328 },
329 Filter(RevsetFilterPredicate),
330 AsFilter(Arc<Self>),
332 Divergent,
333 AtOperation {
335 operation: St::Operation,
336 candidates: Arc<Self>,
337 },
338 WithinReference {
341 candidates: Arc<Self>,
342 commits: Vec<CommitId>,
344 },
345 WithinVisibility {
349 candidates: Arc<Self>,
350 visible_heads: Vec<CommitId>,
352 },
353 Coalesce(Arc<Self>, Arc<Self>),
354 Present(Arc<Self>),
355 NotIn(Arc<Self>),
356 Union(Arc<Self>, Arc<Self>),
357 Intersection(Arc<Self>, Arc<Self>),
358 Difference(Arc<Self>, Arc<Self>),
359}
360
361impl<St: ExpressionState> RevsetExpression<St> {
364 pub fn none() -> Arc<Self> {
365 Arc::new(Self::None)
366 }
367
368 pub fn all() -> Arc<Self> {
371 Arc::new(Self::All)
372 }
373
374 pub fn visible_heads() -> Arc<Self> {
375 Arc::new(Self::VisibleHeads)
376 }
377
378 fn visible_heads_or_referenced() -> Arc<Self> {
379 Arc::new(Self::VisibleHeadsOrReferenced)
380 }
381
382 pub fn root() -> Arc<Self> {
383 Arc::new(Self::Root)
384 }
385
386 pub fn forks() -> Arc<Self> {
387 Arc::new(Self::Forks)
388 }
389
390 pub fn commit(commit_id: CommitId) -> Arc<Self> {
391 Self::commits(vec![commit_id])
392 }
393
394 pub fn commits(commit_ids: Vec<CommitId>) -> Arc<Self> {
395 Arc::new(Self::Commits(commit_ids))
396 }
397
398 pub fn filter(predicate: RevsetFilterPredicate) -> Arc<Self> {
399 Arc::new(Self::Filter(predicate))
400 }
401
402 pub fn divergent() -> Arc<Self> {
403 Arc::new(Self::AsFilter(Arc::new(Self::Divergent)))
404 }
405
406 pub fn is_empty() -> Arc<Self> {
408 Self::filter(RevsetFilterPredicate::File(FilesetExpression::all())).negated()
409 }
410}
411
412impl<St: ExpressionState<CommitRef = RevsetCommitRef>> RevsetExpression<St> {
414 pub fn working_copy(name: WorkspaceNameBuf) -> Arc<Self> {
415 Arc::new(Self::CommitRef(RevsetCommitRef::WorkingCopy(name)))
416 }
417
418 pub fn working_copies() -> Arc<Self> {
419 Arc::new(Self::CommitRef(RevsetCommitRef::WorkingCopies))
420 }
421
422 pub fn symbol(value: String) -> Arc<Self> {
423 Arc::new(Self::CommitRef(RevsetCommitRef::Symbol(value)))
424 }
425
426 pub fn remote_symbol(value: RemoteRefSymbolBuf) -> Arc<Self> {
427 let commit_ref = RevsetCommitRef::RemoteSymbol(value);
428 Arc::new(Self::CommitRef(commit_ref))
429 }
430
431 pub fn change_id_prefix(prefix: HexPrefix) -> Arc<Self> {
432 let commit_ref = RevsetCommitRef::ChangeId(prefix);
433 Arc::new(Self::CommitRef(commit_ref))
434 }
435
436 pub fn commit_id_prefix(prefix: HexPrefix) -> Arc<Self> {
437 let commit_ref = RevsetCommitRef::CommitId(prefix);
438 Arc::new(Self::CommitRef(commit_ref))
439 }
440
441 pub fn bookmarks(expression: StringExpression) -> Arc<Self> {
442 Arc::new(Self::CommitRef(RevsetCommitRef::Bookmarks(expression)))
443 }
444
445 pub fn remote_bookmarks(
446 symbol: RemoteRefSymbolExpression,
447 remote_ref_state: Option<RemoteRefState>,
448 ) -> Arc<Self> {
449 Arc::new(Self::CommitRef(RevsetCommitRef::RemoteBookmarks {
450 symbol,
451 remote_ref_state,
452 }))
453 }
454
455 pub fn tags(expression: StringExpression) -> Arc<Self> {
456 Arc::new(Self::CommitRef(RevsetCommitRef::Tags(expression)))
457 }
458
459 pub fn remote_tags(
460 symbol: RemoteRefSymbolExpression,
461 remote_ref_state: Option<RemoteRefState>,
462 ) -> Arc<Self> {
463 Arc::new(Self::CommitRef(RevsetCommitRef::RemoteTags {
464 symbol,
465 remote_ref_state,
466 }))
467 }
468}
469
470impl<St: ExpressionState> RevsetExpression<St> {
472 pub fn latest(self: &Arc<Self>, count: usize) -> Arc<Self> {
473 Arc::new(Self::Latest {
474 candidates: self.clone(),
475 count,
476 })
477 }
478
479 pub fn heads(self: &Arc<Self>) -> Arc<Self> {
481 Arc::new(Self::Heads(self.clone()))
482 }
483
484 pub fn roots(self: &Arc<Self>) -> Arc<Self> {
486 Arc::new(Self::Roots(self.clone()))
487 }
488
489 pub fn parents(self: &Arc<Self>) -> Arc<Self> {
491 self.ancestors_at(1)
492 }
493
494 pub fn ancestors(self: &Arc<Self>) -> Arc<Self> {
496 self.ancestors_range(GENERATION_RANGE_FULL)
497 }
498
499 pub fn ancestors_at(self: &Arc<Self>, generation: u64) -> Arc<Self> {
502 self.ancestors_range(generation..generation.saturating_add(1))
503 }
504
505 pub fn ancestors_range(self: &Arc<Self>, generation_range: Range<u64>) -> Arc<Self> {
507 Arc::new(Self::Ancestors {
508 heads: self.clone(),
509 generation: generation_range,
510 parents_range: PARENTS_RANGE_FULL,
511 })
512 }
513
514 pub fn first_ancestors(self: &Arc<Self>) -> Arc<Self> {
516 self.first_ancestors_range(GENERATION_RANGE_FULL)
517 }
518
519 pub fn first_ancestors_at(self: &Arc<Self>, generation: u64) -> Arc<Self> {
522 self.first_ancestors_range(generation..generation.saturating_add(1))
523 }
524
525 pub fn first_ancestors_range(self: &Arc<Self>, generation_range: Range<u64>) -> Arc<Self> {
527 Arc::new(Self::Ancestors {
528 heads: self.clone(),
529 generation: generation_range,
530 parents_range: 0..1,
531 })
532 }
533
534 pub fn children(self: &Arc<Self>) -> Arc<Self> {
536 self.descendants_at(1)
537 }
538
539 pub fn descendants(self: &Arc<Self>) -> Arc<Self> {
541 self.descendants_range(GENERATION_RANGE_FULL)
542 }
543
544 pub fn descendants_at(self: &Arc<Self>, generation: u64) -> Arc<Self> {
547 self.descendants_range(generation..generation.saturating_add(1))
548 }
549
550 pub fn descendants_range(self: &Arc<Self>, generation_range: Range<u64>) -> Arc<Self> {
552 Arc::new(Self::Descendants {
553 roots: self.clone(),
554 generation: generation_range,
555 })
556 }
557
558 pub fn fork_point(self: &Arc<Self>) -> Arc<Self> {
560 Arc::new(Self::ForkPoint(self.clone()))
561 }
562
563 pub fn merge_point(self: &Arc<Self>) -> Arc<Self> {
565 Arc::new(Self::MergePoint(self.clone()))
566 }
567
568 pub fn bisect(self: &Arc<Self>) -> Arc<Self> {
570 Arc::new(Self::Bisect(self.clone()))
571 }
572
573 pub fn has_size(self: &Arc<Self>, count: usize) -> Arc<Self> {
575 Arc::new(Self::HasSize {
576 candidates: self.clone(),
577 count,
578 })
579 }
580
581 pub fn filtered(self: &Arc<Self>, predicate: RevsetFilterPredicate) -> Arc<Self> {
583 self.intersection(&Self::filter(predicate))
584 }
585
586 pub fn dag_range_to(self: &Arc<Self>, heads: &Arc<Self>) -> Arc<Self> {
589 Arc::new(Self::DagRange {
590 roots: self.clone(),
591 heads: heads.clone(),
592 })
593 }
594
595 pub fn connected(self: &Arc<Self>) -> Arc<Self> {
598 self.dag_range_to(self)
599 }
600
601 pub fn reachable(self: &Arc<Self>, domain: &Arc<Self>) -> Arc<Self> {
604 Arc::new(Self::Reachable {
605 sources: self.clone(),
606 domain: domain.clone(),
607 })
608 }
609
610 pub fn range(self: &Arc<Self>, heads: &Arc<Self>) -> Arc<Self> {
612 Arc::new(Self::Range {
613 roots: self.clone(),
614 heads: heads.clone(),
615 generation: GENERATION_RANGE_FULL,
616 parents_range: PARENTS_RANGE_FULL,
617 })
618 }
619
620 pub fn within_visibility(self: &Arc<Self>, repo: &dyn Repo) -> Arc<Self> {
626 Arc::new(Self::WithinVisibility {
627 candidates: self.clone(),
628 visible_heads: repo.view().heads().iter().cloned().collect(),
629 })
630 }
631
632 pub fn present(self: &Arc<Self>) -> Arc<Self> {
634 Arc::new(Self::Present(self.clone()))
635 }
636
637 pub fn negated(self: &Arc<Self>) -> Arc<Self> {
639 Arc::new(Self::NotIn(self.clone()))
640 }
641
642 pub fn union(self: &Arc<Self>, other: &Arc<Self>) -> Arc<Self> {
644 Arc::new(Self::Union(self.clone(), other.clone()))
645 }
646
647 pub fn union_all(expressions: &[Arc<Self>]) -> Arc<Self> {
649 to_binary_expression(expressions, &Self::none, &Self::union)
650 }
651
652 pub fn intersection(self: &Arc<Self>, other: &Arc<Self>) -> Arc<Self> {
654 Arc::new(Self::Intersection(self.clone(), other.clone()))
655 }
656
657 pub fn minus(self: &Arc<Self>, other: &Arc<Self>) -> Arc<Self> {
659 Arc::new(Self::Difference(self.clone(), other.clone()))
660 }
661
662 pub fn coalesce(expressions: &[Arc<Self>]) -> Arc<Self> {
665 to_binary_expression(expressions, &Self::none, &Self::coalesce2)
666 }
667
668 fn coalesce2(self: &Arc<Self>, other: &Arc<Self>) -> Arc<Self> {
669 Arc::new(Self::Coalesce(self.clone(), other.clone()))
670 }
671}
672
673impl<St: ExpressionState<CommitRef = RevsetCommitRef>> RevsetExpression<St> {
674 pub fn as_symbol(&self) -> Option<&str> {
676 match self {
677 Self::CommitRef(RevsetCommitRef::Symbol(name)) => Some(name),
678 _ => None,
679 }
680 }
681}
682
683impl UserRevsetExpression {
684 pub fn resolve_user_expression(
687 &self,
688 repo: &dyn Repo,
689 symbol_resolver: &SymbolResolver,
690 ) -> Result<Arc<ResolvedRevsetExpression>, RevsetResolutionError> {
691 resolve_symbols(repo, self, symbol_resolver)
692 }
693}
694
695impl ResolvedRevsetExpression {
696 pub fn evaluate<'index>(
698 self: Arc<Self>,
699 repo: &'index dyn Repo,
700 ) -> Result<Box<dyn Revset + 'index>, RevsetEvaluationError> {
701 let expr = optimize(self).to_backend_expression(repo);
702 repo.index().evaluate_revset(&expr, repo.store())
703 }
704
705 pub fn evaluate_unoptimized<'index>(
710 self: &Arc<Self>,
711 repo: &'index dyn Repo,
712 ) -> Result<Box<dyn Revset + 'index>, RevsetEvaluationError> {
713 let expr = resolve_referenced_commits(self)
716 .as_ref()
717 .unwrap_or(self)
718 .to_backend_expression(repo);
719 repo.index().evaluate_revset(&expr, repo.store())
720 }
721
722 pub fn to_backend_expression(&self, repo: &dyn Repo) -> ResolvedExpression {
725 resolve_visibility(repo, self)
726 }
727}
728
729#[derive(Clone, Debug)]
730pub enum ResolvedPredicateExpression {
731 Filter(RevsetFilterPredicate),
733 Divergent {
734 visible_heads: Vec<CommitId>,
735 },
736 Set(Box<ResolvedExpression>),
739 NotIn(Box<Self>),
740 Union(Box<Self>, Box<Self>),
741 Intersection(Box<Self>, Box<Self>),
742}
743
744#[derive(Clone, Debug)]
752pub enum ResolvedExpression {
753 Commits(Vec<CommitId>),
754 Ancestors {
755 heads: Box<Self>,
756 generation: Range<u64>,
757 parents_range: Range<u32>,
758 },
759 Range {
761 roots: Box<Self>,
762 heads: Box<Self>,
763 generation: Range<u64>,
764 parents_range: Range<u32>,
766 },
767 DagRange {
769 roots: Box<Self>,
770 heads: Box<Self>,
771 generation_from_roots: Range<u64>,
772 },
773 Reachable {
775 sources: Box<Self>,
776 domain: Box<Self>,
777 },
778 Heads(Box<Self>),
779 HeadsRange {
782 roots: Box<Self>,
783 heads: Box<Self>,
784 parents_range: Range<u32>,
785 filter: Option<ResolvedPredicateExpression>,
786 },
787 Roots(Box<Self>),
788 Forks {
789 heads: Box<Self>,
790 },
791 ForkPoint(Box<Self>),
792 MergePoint {
793 roots: Box<Self>,
794 visible_heads: Box<Self>,
795 },
796 Bisect(Box<Self>),
797 HasSize {
798 candidates: Box<Self>,
799 count: usize,
800 },
801 Latest {
802 candidates: Box<Self>,
803 count: usize,
804 },
805 Coalesce(Box<Self>, Box<Self>),
806 Union(Box<Self>, Box<Self>),
807 FilterWithin {
809 candidates: Box<Self>,
810 predicate: ResolvedPredicateExpression,
811 },
812 Intersection(Box<Self>, Box<Self>),
814 Difference(Box<Self>, Box<Self>),
815}
816
817pub type RevsetFunction = fn(
818 &mut RevsetDiagnostics,
819 &FunctionCallNode,
820 &LoweringContext,
821) -> Result<Arc<UserRevsetExpression>, RevsetParseError>;
822
823static BUILTIN_FUNCTION_MAP: LazyLock<HashMap<&str, RevsetFunction>> = LazyLock::new(|| {
824 let mut map: HashMap<&str, RevsetFunction> = HashMap::new();
827 map.insert("parents", |diagnostics, function, context| {
828 let ([arg], [depth_opt_arg]) = function.expect_arguments()?;
829 let expression = lower_expression(diagnostics, arg, context)?;
830 if let Some(depth_arg) = depth_opt_arg {
831 let depth = expect_literal("integer", depth_arg)?;
832 Ok(expression.ancestors_at(depth))
833 } else {
834 Ok(expression.parents())
835 }
836 });
837 map.insert("children", |diagnostics, function, context| {
838 let ([arg], [depth_opt_arg]) = function.expect_arguments()?;
839 let expression = lower_expression(diagnostics, arg, context)?;
840 if let Some(depth_arg) = depth_opt_arg {
841 let depth = expect_literal("integer", depth_arg)?;
842 Ok(expression.descendants_at(depth))
843 } else {
844 Ok(expression.children())
845 }
846 });
847 map.insert("ancestors", |diagnostics, function, context| {
848 let ([heads_arg], [depth_opt_arg]) = function.expect_arguments()?;
849 let heads = lower_expression(diagnostics, heads_arg, context)?;
850 let generation = if let Some(depth_arg) = depth_opt_arg {
851 let depth = expect_literal("integer", depth_arg)?;
852 0..depth
853 } else {
854 GENERATION_RANGE_FULL
855 };
856 Ok(heads.ancestors_range(generation))
857 });
858 map.insert("descendants", |diagnostics, function, context| {
859 let ([roots_arg], [depth_opt_arg]) = function.expect_arguments()?;
860 let roots = lower_expression(diagnostics, roots_arg, context)?;
861 let generation = if let Some(depth_arg) = depth_opt_arg {
862 let depth = expect_literal("integer", depth_arg)?;
863 0..depth
864 } else {
865 GENERATION_RANGE_FULL
866 };
867 Ok(roots.descendants_range(generation))
868 });
869 map.insert("first_parent", |diagnostics, function, context| {
870 let ([arg], [depth_opt_arg]) = function.expect_arguments()?;
871 let expression = lower_expression(diagnostics, arg, context)?;
872 let depth = if let Some(depth_arg) = depth_opt_arg {
873 expect_literal("integer", depth_arg)?
874 } else {
875 1
876 };
877 Ok(expression.first_ancestors_at(depth))
878 });
879 map.insert("first_ancestors", |diagnostics, function, context| {
880 let ([heads_arg], [depth_opt_arg]) = function.expect_arguments()?;
881 let heads = lower_expression(diagnostics, heads_arg, context)?;
882 let generation = if let Some(depth_arg) = depth_opt_arg {
883 let depth = expect_literal("integer", depth_arg)?;
884 0..depth
885 } else {
886 GENERATION_RANGE_FULL
887 };
888 Ok(heads.first_ancestors_range(generation))
889 });
890 map.insert("connected", |diagnostics, function, context| {
891 let [arg] = function.expect_exact_arguments()?;
892 let candidates = lower_expression(diagnostics, arg, context)?;
893 Ok(candidates.connected())
894 });
895 map.insert("reachable", |diagnostics, function, context| {
896 let [source_arg, domain_arg] = function.expect_exact_arguments()?;
897 let sources = lower_expression(diagnostics, source_arg, context)?;
898 let domain = lower_expression(diagnostics, domain_arg, context)?;
899 Ok(sources.reachable(&domain))
900 });
901 map.insert("none", |_diagnostics, function, _context| {
902 function.expect_no_arguments()?;
903 Ok(RevsetExpression::none())
904 });
905 map.insert("all", |_diagnostics, function, _context| {
906 function.expect_no_arguments()?;
907 Ok(RevsetExpression::all())
908 });
909 map.insert("working_copies", |_diagnostics, function, _context| {
910 function.expect_no_arguments()?;
911 Ok(RevsetExpression::working_copies())
912 });
913 map.insert("heads", |diagnostics, function, context| {
914 let [arg] = function.expect_exact_arguments()?;
915 let candidates = lower_expression(diagnostics, arg, context)?;
916 Ok(candidates.heads())
917 });
918 map.insert("roots", |diagnostics, function, context| {
919 let [arg] = function.expect_exact_arguments()?;
920 let candidates = lower_expression(diagnostics, arg, context)?;
921 Ok(candidates.roots())
922 });
923 map.insert("visible_heads", |_diagnostics, function, _context| {
924 function.expect_no_arguments()?;
925 Ok(RevsetExpression::visible_heads())
926 });
927 map.insert("root", |_diagnostics, function, _context| {
928 function.expect_no_arguments()?;
929 Ok(RevsetExpression::root())
930 });
931 map.insert("change_id", |diagnostics, function, _context| {
932 let [arg] = function.expect_exact_arguments()?;
933 let prefix = revset_parser::catch_aliases(diagnostics, arg, |_diagnostics, arg| {
934 let value = revset_parser::expect_string_literal("change ID prefix", arg)?;
935 HexPrefix::try_from_reverse_hex(value)
936 .ok_or_else(|| RevsetParseError::expression("Invalid change ID prefix", arg.span))
937 })?;
938 Ok(RevsetExpression::change_id_prefix(prefix))
939 });
940 map.insert("commit_id", |diagnostics, function, _context| {
941 let [arg] = function.expect_exact_arguments()?;
942 let prefix = revset_parser::catch_aliases(diagnostics, arg, |_diagnostics, arg| {
943 let value = revset_parser::expect_string_literal("commit ID prefix", arg)?;
944 HexPrefix::try_from_hex(value)
945 .ok_or_else(|| RevsetParseError::expression("Invalid commit ID prefix", arg.span))
946 })?;
947 Ok(RevsetExpression::commit_id_prefix(prefix))
948 });
949 map.insert("bookmarks", |diagnostics, function, _context| {
950 let ([], [opt_arg]) = function.expect_arguments()?;
951 let expr = if let Some(arg) = opt_arg {
952 expect_string_expression(diagnostics, arg)?
953 } else {
954 StringExpression::all()
955 };
956 Ok(RevsetExpression::bookmarks(expr))
957 });
958 map.insert("remote_bookmarks", |diagnostics, function, context| {
959 let symbol = parse_remote_refs_arguments(diagnostics, function, context)?;
960 let state = None;
961 Ok(RevsetExpression::remote_bookmarks(symbol, state))
962 });
963 map.insert(
964 "tracked_remote_bookmarks",
965 |diagnostics, function, context| {
966 let symbol = parse_remote_refs_arguments(diagnostics, function, context)?;
967 let state = Some(RemoteRefState::Tracked);
968 Ok(RevsetExpression::remote_bookmarks(symbol, state))
969 },
970 );
971 map.insert(
972 "untracked_remote_bookmarks",
973 |diagnostics, function, context| {
974 let symbol = parse_remote_refs_arguments(diagnostics, function, context)?;
975 let state = Some(RemoteRefState::New);
976 Ok(RevsetExpression::remote_bookmarks(symbol, state))
977 },
978 );
979 map.insert("tags", |diagnostics, function, _context| {
980 let ([], [opt_arg]) = function.expect_arguments()?;
981 let expr = if let Some(arg) = opt_arg {
982 expect_string_expression(diagnostics, arg)?
983 } else {
984 StringExpression::all()
985 };
986 Ok(RevsetExpression::tags(expr))
987 });
988 map.insert("remote_tags", |diagnostics, function, context| {
989 let symbol = parse_remote_refs_arguments(diagnostics, function, context)?;
990 let state = None;
991 Ok(RevsetExpression::remote_tags(symbol, state))
992 });
993 map.insert("tracked_remote_tags", |diagnostics, function, context| {
994 let symbol = parse_remote_refs_arguments(diagnostics, function, context)?;
995 let state = Some(RemoteRefState::Tracked);
996 Ok(RevsetExpression::remote_tags(symbol, state))
997 });
998 map.insert("untracked_remote_tags", |diagnostics, function, context| {
999 let symbol = parse_remote_refs_arguments(diagnostics, function, context)?;
1000 let state = Some(RemoteRefState::New);
1001 Ok(RevsetExpression::remote_tags(symbol, state))
1002 });
1003 map.insert("latest", |diagnostics, function, context| {
1004 let ([candidates_arg], [count_opt_arg]) = function.expect_arguments()?;
1005 let candidates = lower_expression(diagnostics, candidates_arg, context)?;
1006 let count = if let Some(count_arg) = count_opt_arg {
1007 expect_literal("integer", count_arg)?
1008 } else {
1009 1
1010 };
1011 Ok(candidates.latest(count))
1012 });
1013 map.insert("fork_point", |diagnostics, function, context| {
1014 let [expression_arg] = function.expect_exact_arguments()?;
1015 let expression = lower_expression(diagnostics, expression_arg, context)?;
1016 Ok(RevsetExpression::fork_point(&expression))
1017 });
1018 map.insert("merge_point", |diagnostics, function, context| {
1019 let [expression_arg] = function.expect_exact_arguments()?;
1020 let expression = lower_expression(diagnostics, expression_arg, context)?;
1021 Ok(RevsetExpression::merge_point(&expression))
1022 });
1023 map.insert("bisect", |diagnostics, function, context| {
1024 let [expression_arg] = function.expect_exact_arguments()?;
1025 let expression = lower_expression(diagnostics, expression_arg, context)?;
1026 Ok(RevsetExpression::bisect(&expression))
1027 });
1028 map.insert("exactly", |diagnostics, function, context| {
1029 let ([candidates_arg, count_arg], []) = function.expect_arguments()?;
1030 let candidates = lower_expression(diagnostics, candidates_arg, context)?;
1031 let count = expect_literal("integer", count_arg)?;
1032 Ok(candidates.has_size(count))
1033 });
1034 map.insert("merges", |_diagnostics, function, _context| {
1035 function.expect_no_arguments()?;
1036 Ok(RevsetExpression::filter(
1037 RevsetFilterPredicate::ParentCount(2..u32::MAX),
1038 ))
1039 });
1040 map.insert("forks", |_diagnostics, function, _context| {
1041 function.expect_no_arguments()?;
1042 Ok(RevsetExpression::forks())
1043 });
1044 map.insert("description", |diagnostics, function, _context| {
1045 let [arg] = function.expect_exact_arguments()?;
1046 let expr = expect_string_expression(diagnostics, arg)?;
1047 let predicate = RevsetFilterPredicate::Description(expr);
1048 Ok(RevsetExpression::filter(predicate))
1049 });
1050 map.insert("subject", |diagnostics, function, _context| {
1051 let [arg] = function.expect_exact_arguments()?;
1052 let expr = expect_string_expression(diagnostics, arg)?;
1053 let predicate = RevsetFilterPredicate::Subject(expr);
1054 Ok(RevsetExpression::filter(predicate))
1055 });
1056 map.insert("author", |diagnostics, function, _context| {
1057 let [arg] = function.expect_exact_arguments()?;
1058 let expr = expect_string_expression(diagnostics, arg)?;
1059 let name_predicate = RevsetFilterPredicate::AuthorName(expr.clone());
1060 let email_predicate = RevsetFilterPredicate::AuthorEmail(expr);
1061 Ok(RevsetExpression::filter(name_predicate)
1062 .union(&RevsetExpression::filter(email_predicate)))
1063 });
1064 map.insert("author_name", |diagnostics, function, _context| {
1065 let [arg] = function.expect_exact_arguments()?;
1066 let expr = expect_string_expression(diagnostics, arg)?;
1067 let predicate = RevsetFilterPredicate::AuthorName(expr);
1068 Ok(RevsetExpression::filter(predicate))
1069 });
1070 map.insert("author_email", |diagnostics, function, _context| {
1071 let [arg] = function.expect_exact_arguments()?;
1072 let expr = expect_string_expression(diagnostics, arg)?;
1073 let predicate = RevsetFilterPredicate::AuthorEmail(expr);
1074 Ok(RevsetExpression::filter(predicate))
1075 });
1076 map.insert("author_date", |diagnostics, function, context| {
1077 let [arg] = function.expect_exact_arguments()?;
1078 let pattern = expect_date_pattern(diagnostics, arg, context.date_pattern_context())?;
1079 Ok(RevsetExpression::filter(RevsetFilterPredicate::AuthorDate(
1080 pattern,
1081 )))
1082 });
1083 map.insert("signed", |_diagnostics, function, _context| {
1084 function.expect_no_arguments()?;
1085 let predicate = RevsetFilterPredicate::Signed;
1086 Ok(RevsetExpression::filter(predicate))
1087 });
1088 map.insert("mine", |_diagnostics, function, context| {
1089 function.expect_no_arguments()?;
1090 let pattern = StringPattern::exact_i(context.user_email);
1094 let predicate = RevsetFilterPredicate::AuthorEmail(StringExpression::pattern(pattern));
1095 Ok(RevsetExpression::filter(predicate))
1096 });
1097 map.insert("committer", |diagnostics, function, _context| {
1098 let [arg] = function.expect_exact_arguments()?;
1099 let expr = expect_string_expression(diagnostics, arg)?;
1100 let name_predicate = RevsetFilterPredicate::CommitterName(expr.clone());
1101 let email_predicate = RevsetFilterPredicate::CommitterEmail(expr);
1102 Ok(RevsetExpression::filter(name_predicate)
1103 .union(&RevsetExpression::filter(email_predicate)))
1104 });
1105 map.insert("committer_name", |diagnostics, function, _context| {
1106 let [arg] = function.expect_exact_arguments()?;
1107 let expr = expect_string_expression(diagnostics, arg)?;
1108 let predicate = RevsetFilterPredicate::CommitterName(expr);
1109 Ok(RevsetExpression::filter(predicate))
1110 });
1111 map.insert("committer_email", |diagnostics, function, _context| {
1112 let [arg] = function.expect_exact_arguments()?;
1113 let expr = expect_string_expression(diagnostics, arg)?;
1114 let predicate = RevsetFilterPredicate::CommitterEmail(expr);
1115 Ok(RevsetExpression::filter(predicate))
1116 });
1117 map.insert("committer_date", |diagnostics, function, context| {
1118 let [arg] = function.expect_exact_arguments()?;
1119 let pattern = expect_date_pattern(diagnostics, arg, context.date_pattern_context())?;
1120 Ok(RevsetExpression::filter(
1121 RevsetFilterPredicate::CommitterDate(pattern),
1122 ))
1123 });
1124 map.insert("empty", |_diagnostics, function, _context| {
1125 function.expect_no_arguments()?;
1126 Ok(RevsetExpression::is_empty())
1127 });
1128 map.insert("files", |diagnostics, function, context| {
1129 let fileset_context = context.fileset_parse_context().ok_or_else(|| {
1130 RevsetParseError::with_span(
1131 RevsetParseErrorKind::FsPathWithoutWorkspace,
1132 function.args_span, )
1134 })?;
1135 let [arg] = function.expect_exact_arguments()?;
1136 let expr = expect_fileset_expression(diagnostics, arg, &fileset_context)?;
1137 Ok(RevsetExpression::filter(RevsetFilterPredicate::File(expr)))
1138 });
1139 map.insert("diff_lines", |diagnostics, function, context| {
1140 if function.name != "diff_lines" {
1141 diagnostics.add_warning(RevsetParseError::expression(
1143 "diff_contains() is deprecated; use diff_lines() instead",
1144 function.name_span,
1145 ));
1146 }
1147 let ([text_arg], [files_opt_arg]) = function.expect_arguments()?;
1148 let text = expect_string_expression(diagnostics, text_arg)?;
1149 let files = expand_optional_files_arg(files_opt_arg, diagnostics, context)?;
1150 let predicate = RevsetFilterPredicate::DiffLines {
1151 text,
1152 files,
1153 side: DiffMatchSide::Either,
1154 };
1155 Ok(RevsetExpression::filter(predicate))
1156 });
1157 map.insert("diff_lines_added", |diagnostics, function, context| {
1158 let ([text_arg], [files_opt_arg]) = function.expect_arguments()?;
1159 let text = expect_string_expression(diagnostics, text_arg)?;
1160 let files = expand_optional_files_arg(files_opt_arg, diagnostics, context)?;
1161 let predicate = RevsetFilterPredicate::DiffLines {
1162 text,
1163 files,
1164 side: DiffMatchSide::Right,
1165 };
1166 Ok(RevsetExpression::filter(predicate))
1167 });
1168 map.insert("diff_lines_removed", |diagnostics, function, context| {
1169 let ([text_arg], [files_opt_arg]) = function.expect_arguments()?;
1170 let text = expect_string_expression(diagnostics, text_arg)?;
1171 let files = expand_optional_files_arg(files_opt_arg, diagnostics, context)?;
1172 let predicate = RevsetFilterPredicate::DiffLines {
1173 text,
1174 files,
1175 side: DiffMatchSide::Left,
1176 };
1177 Ok(RevsetExpression::filter(predicate))
1178 });
1179 map.insert("diff_contains", map["diff_lines"]);
1181 map.insert("conflicts", |_diagnostics, function, _context| {
1182 function.expect_no_arguments()?;
1183 Ok(RevsetExpression::filter(RevsetFilterPredicate::HasConflict))
1184 });
1185 map.insert("divergent", |_diagnostics, function, _context| {
1186 function.expect_no_arguments()?;
1187 Ok(RevsetExpression::divergent())
1188 });
1189 map.insert("present", |diagnostics, function, context| {
1190 let [arg] = function.expect_exact_arguments()?;
1191 let expression = lower_expression(diagnostics, arg, context)?;
1192 Ok(expression.present())
1193 });
1194 map.insert("at_operation", |diagnostics, function, context| {
1195 let [op_arg, cand_arg] = function.expect_exact_arguments()?;
1196 let operation = revset_parser::catch_aliases(diagnostics, op_arg, |_diagnostics, node| {
1198 Ok(node.span.as_str().to_owned())
1199 })?;
1200 let candidates = lower_expression(diagnostics, cand_arg, context)?;
1201 Ok(Arc::new(RevsetExpression::AtOperation {
1202 operation,
1203 candidates,
1204 }))
1205 });
1206 map.insert("coalesce", |diagnostics, function, context| {
1207 let ([], args) = function.expect_some_arguments()?;
1208 let expressions: Vec<_> = args
1209 .iter()
1210 .map(|arg| lower_expression(diagnostics, arg, context))
1211 .try_collect()?;
1212 Ok(RevsetExpression::coalesce(&expressions))
1213 });
1214 map
1215});
1216
1217fn expand_optional_files_arg(
1218 files_opt_arg: Option<&ExpressionNode>,
1219 diagnostics: &mut RevsetDiagnostics,
1220 context: &LoweringContext,
1221) -> Result<FilesetExpression, RevsetParseError> {
1222 if let Some(files_arg) = files_opt_arg {
1223 let fileset_context = context.fileset_parse_context().ok_or_else(|| {
1224 RevsetParseError::with_span(
1225 RevsetParseErrorKind::FsPathWithoutWorkspace,
1226 files_arg.span,
1227 )
1228 })?;
1229 expect_fileset_expression(diagnostics, files_arg, &fileset_context)
1230 } else {
1231 Ok(FilesetExpression::all())
1234 }
1235}
1236
1237pub fn expect_fileset_expression(
1239 diagnostics: &mut RevsetDiagnostics,
1240 node: &ExpressionNode,
1241 context: &FilesetParseContext,
1242) -> Result<FilesetExpression, RevsetParseError> {
1243 revset_parser::catch_aliases(diagnostics, node, |diagnostics, node| {
1247 let mut inner_diagnostics = FilesetDiagnostics::new();
1248 let expression = fileset::parse(&mut inner_diagnostics, node.span.as_str(), context)
1249 .map_err(|err| {
1250 RevsetParseError::expression("In fileset expression", node.span).with_source(err)
1251 })?;
1252 diagnostics.extend_with(inner_diagnostics, |diag| {
1253 RevsetParseError::expression("In fileset expression", node.span).with_source(diag)
1254 });
1255 Ok(expression)
1256 })
1257}
1258
1259pub fn expect_string_expression(
1261 diagnostics: &mut RevsetDiagnostics,
1262 node: &ExpressionNode,
1263) -> Result<StringExpression, RevsetParseError> {
1264 revset_parser::catch_aliases(diagnostics, node, |diagnostics, node| {
1265 let expr_error = || RevsetParseError::expression("Invalid string expression", node.span);
1266 let pattern_error = || RevsetParseError::expression("Invalid string pattern", node.span);
1267 let default_pattern = |value: &str| {
1268 let pattern =
1269 StringPattern::glob(value).map_err(|err| pattern_error().with_source(err))?;
1270 Ok(StringExpression::pattern(pattern))
1271 };
1272 match &node.kind {
1273 ExpressionKind::Identifier(value) => default_pattern(value),
1274 ExpressionKind::String(value) => default_pattern(value),
1275 ExpressionKind::Pattern(pattern) => {
1276 let value = revset_parser::expect_string_literal("string", &pattern.value)?;
1277 let pattern = StringPattern::from_str_kind(value, pattern.name)
1278 .map_err(|err| pattern_error().with_source(err))?;
1279 Ok(StringExpression::pattern(pattern))
1280 }
1281 ExpressionKind::RemoteSymbol(_)
1282 | ExpressionKind::AtWorkspace(_)
1283 | ExpressionKind::AtCurrentWorkspace
1284 | ExpressionKind::DagRangeAll
1285 | ExpressionKind::RangeAll => Err(expr_error()),
1286 ExpressionKind::Unary(op, arg_node) => {
1287 let arg = expect_string_expression(diagnostics, arg_node)?;
1288 match op {
1289 UnaryOp::Negate => Ok(arg.negated()),
1290 UnaryOp::DagRangePre
1291 | UnaryOp::DagRangePost
1292 | UnaryOp::RangePre
1293 | UnaryOp::RangePost
1294 | UnaryOp::Parents
1295 | UnaryOp::Children => Err(expr_error()),
1296 }
1297 }
1298 ExpressionKind::Binary(op, lhs_node, rhs_node) => {
1299 let lhs = expect_string_expression(diagnostics, lhs_node)?;
1300 let rhs = expect_string_expression(diagnostics, rhs_node)?;
1301 match op {
1302 BinaryOp::Intersection => Ok(lhs.intersection(rhs)),
1303 BinaryOp::Difference => Ok(lhs.intersection(rhs.negated())),
1304 BinaryOp::DagRange | BinaryOp::Range => Err(expr_error()),
1305 }
1306 }
1307 ExpressionKind::UnionAll(nodes) => {
1308 let expressions = nodes
1309 .iter()
1310 .map(|node| expect_string_expression(diagnostics, node))
1311 .try_collect()?;
1312 Ok(StringExpression::union_all(expressions))
1313 }
1314 ExpressionKind::FunctionCall(_) => Err(expr_error()),
1315 ExpressionKind::AliasExpanded(..) => unreachable!(),
1316 }
1317 })
1318}
1319
1320pub fn expect_date_pattern(
1321 diagnostics: &mut RevsetDiagnostics,
1322 node: &ExpressionNode,
1323 context: &DatePatternContext,
1324) -> Result<DatePattern, RevsetParseError> {
1325 revset_parser::catch_aliases(diagnostics, node, |_diagnostics, node| {
1326 let (value, kind) = revset_parser::expect_string_pattern("date pattern", node)?;
1327 let kind = kind.ok_or_else(|| {
1328 RevsetParseError::expression("Date pattern must specify 'after' or 'before'", node.span)
1329 })?;
1330 context.parse_relative(value, kind).map_err(|err| {
1331 RevsetParseError::expression("Invalid date pattern", node.span).with_source(err)
1332 })
1333 })
1334}
1335
1336fn parse_remote_refs_arguments(
1337 diagnostics: &mut RevsetDiagnostics,
1338 function: &FunctionCallNode,
1339 context: &LoweringContext,
1340) -> Result<RemoteRefSymbolExpression, RevsetParseError> {
1341 let ([], [name_opt_arg, remote_opt_arg]) = function.expect_named_arguments(&["", "remote"])?;
1342 let name = if let Some(name_arg) = name_opt_arg {
1343 expect_string_expression(diagnostics, name_arg)?
1344 } else {
1345 StringExpression::all()
1346 };
1347 let remote = if let Some(remote_arg) = remote_opt_arg {
1348 expect_string_expression(diagnostics, remote_arg)?
1349 } else if let Some(remote) = context.default_ignored_remote {
1350 StringExpression::exact(remote).negated()
1351 } else {
1352 StringExpression::all()
1353 };
1354 Ok(RemoteRefSymbolExpression { name, remote })
1355}
1356
1357fn lower_function_call(
1359 diagnostics: &mut RevsetDiagnostics,
1360 function: &FunctionCallNode,
1361 context: &LoweringContext,
1362) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
1363 let function_map = &context.extensions.function_map;
1364 if let Some(func) = function_map.get(function.name) {
1365 func(diagnostics, function, context)
1366 } else {
1367 Err(RevsetParseError::with_span(
1368 RevsetParseErrorKind::NoSuchFunction {
1369 name: function.name.to_owned(),
1370 candidates: collect_similar(function.name, function_map.keys()),
1371 },
1372 function.name_span,
1373 ))
1374 }
1375}
1376
1377pub fn lower_expression(
1380 diagnostics: &mut RevsetDiagnostics,
1381 node: &ExpressionNode,
1382 context: &LoweringContext,
1383) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
1384 revset_parser::catch_aliases(diagnostics, node, |diagnostics, node| match &node.kind {
1385 ExpressionKind::Identifier(name) => Ok(RevsetExpression::symbol((*name).to_owned())),
1386 ExpressionKind::String(name) => Ok(RevsetExpression::symbol(name.to_owned())),
1387 ExpressionKind::Pattern(_) => Err(RevsetParseError::with_span(
1388 RevsetParseErrorKind::NotInfixOperator {
1389 op: ":".to_owned(),
1390 similar_op: "::".to_owned(),
1391 description: "DAG range".to_owned(),
1392 },
1393 node.span,
1394 )),
1395 ExpressionKind::RemoteSymbol(symbol) => Ok(RevsetExpression::remote_symbol(symbol.clone())),
1396 ExpressionKind::AtWorkspace(name) => Ok(RevsetExpression::working_copy(name.into())),
1397 ExpressionKind::AtCurrentWorkspace => {
1398 let ctx = context.workspace.as_ref().ok_or_else(|| {
1399 RevsetParseError::with_span(
1400 RevsetParseErrorKind::WorkingCopyWithoutWorkspace,
1401 node.span,
1402 )
1403 })?;
1404 Ok(RevsetExpression::working_copy(
1405 ctx.workspace_name.to_owned(),
1406 ))
1407 }
1408 ExpressionKind::DagRangeAll => Ok(RevsetExpression::all()),
1409 ExpressionKind::RangeAll => Ok(RevsetExpression::root().negated()),
1410 ExpressionKind::Unary(op, arg_node) => {
1411 let arg = lower_expression(diagnostics, arg_node, context)?;
1412 match op {
1413 UnaryOp::Negate => Ok(arg.negated()),
1414 UnaryOp::DagRangePre => Ok(arg.ancestors()),
1415 UnaryOp::DagRangePost => Ok(arg.descendants()),
1416 UnaryOp::RangePre => Ok(RevsetExpression::root().range(&arg)),
1417 UnaryOp::RangePost => Ok(arg.ancestors().negated()),
1418 UnaryOp::Parents => Ok(arg.parents()),
1419 UnaryOp::Children => Ok(arg.children()),
1420 }
1421 }
1422 ExpressionKind::Binary(op, lhs_node, rhs_node) => {
1423 let lhs = lower_expression(diagnostics, lhs_node, context)?;
1424 let rhs = lower_expression(diagnostics, rhs_node, context)?;
1425 match op {
1426 BinaryOp::Intersection => Ok(lhs.intersection(&rhs)),
1427 BinaryOp::Difference => Ok(lhs.minus(&rhs)),
1428 BinaryOp::DagRange => Ok(lhs.dag_range_to(&rhs)),
1429 BinaryOp::Range => Ok(lhs.range(&rhs)),
1430 }
1431 }
1432 ExpressionKind::UnionAll(nodes) => {
1433 let expressions: Vec<_> = nodes
1434 .iter()
1435 .map(|node| lower_expression(diagnostics, node, context))
1436 .try_collect()?;
1437 Ok(RevsetExpression::union_all(&expressions))
1438 }
1439 ExpressionKind::FunctionCall(function) => {
1440 lower_function_call(diagnostics, function, context)
1441 }
1442 ExpressionKind::AliasExpanded(..) => unreachable!(),
1443 })
1444}
1445
1446pub fn parse(
1447 diagnostics: &mut RevsetDiagnostics,
1448 revset_str: &str,
1449 context: &RevsetParseContext,
1450) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
1451 let node = parse_program(revset_str)?;
1452 let node =
1453 dsl_util::expand_aliases_with_locals(node, context.aliases_map, &context.local_variables)?;
1454 lower_expression(diagnostics, &node, &context.to_lowering_context())
1455 .map_err(|err| err.extend_function_candidates(context.aliases_map.function_names()))
1456}
1457
1458pub fn parse_string_expression(
1460 diagnostics: &mut RevsetDiagnostics,
1461 text: &str,
1462) -> Result<StringExpression, RevsetParseError> {
1463 let node = parse_program(text)?;
1464 expect_string_expression(diagnostics, &node)
1465}
1466
1467fn to_binary_expression<T: Clone>(
1470 expressions: &[T],
1471 unit: &impl Fn() -> T,
1472 binary: &impl Fn(&T, &T) -> T,
1473) -> T {
1474 match expressions {
1475 [] => unit(),
1476 [expression] => expression.clone(),
1477 _ => {
1478 let (left, right) = expressions.split_at(expressions.len() / 2);
1480 binary(
1481 &to_binary_expression(left, unit, binary),
1482 &to_binary_expression(right, unit, binary),
1483 )
1484 }
1485 }
1486}
1487
1488type TransformedExpression<St> = Option<Arc<RevsetExpression<St>>>;
1490type PreTransformedExpression<St> = ControlFlow<TransformedExpression<St>, ()>;
1493
1494fn transform_expression<St: ExpressionState>(
1496 expression: &Arc<RevsetExpression<St>>,
1497 mut pre: impl FnMut(&Arc<RevsetExpression<St>>) -> PreTransformedExpression<St>,
1498 mut post: impl FnMut(&Arc<RevsetExpression<St>>) -> TransformedExpression<St>,
1499) -> TransformedExpression<St> {
1500 let Ok(transformed) =
1501 try_transform_expression::<St, Infallible>(expression, |x| Ok(pre(x)), |x| Ok(post(x)));
1502 transformed
1503}
1504
1505fn transform_expression_bottom_up<St: ExpressionState>(
1507 expression: &Arc<RevsetExpression<St>>,
1508 post: impl FnMut(&Arc<RevsetExpression<St>>) -> TransformedExpression<St>,
1509) -> TransformedExpression<St> {
1510 transform_expression(expression, |_| ControlFlow::Continue(()), post)
1511}
1512
1513fn try_transform_expression<St: ExpressionState, E>(
1526 expression: &Arc<RevsetExpression<St>>,
1527 mut pre: impl FnMut(&Arc<RevsetExpression<St>>) -> Result<PreTransformedExpression<St>, E>,
1528 mut post: impl FnMut(&Arc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1529) -> Result<TransformedExpression<St>, E> {
1530 fn transform_child_rec<St: ExpressionState, E>(
1531 expression: &Arc<RevsetExpression<St>>,
1532 pre: &mut impl FnMut(&Arc<RevsetExpression<St>>) -> Result<PreTransformedExpression<St>, E>,
1533 post: &mut impl FnMut(&Arc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1534 ) -> Result<TransformedExpression<St>, E> {
1535 Ok(match expression.as_ref() {
1536 RevsetExpression::None => None,
1537 RevsetExpression::All => None,
1538 RevsetExpression::VisibleHeads => None,
1539 RevsetExpression::VisibleHeadsOrReferenced => None,
1540 RevsetExpression::Root => None,
1541 RevsetExpression::Commits(_) => None,
1542 RevsetExpression::CommitRef(_) => None,
1543 RevsetExpression::Ancestors {
1544 heads,
1545 generation,
1546 parents_range,
1547 } => transform_rec(heads, pre, post)?.map(|heads| RevsetExpression::Ancestors {
1548 heads,
1549 generation: generation.clone(),
1550 parents_range: parents_range.clone(),
1551 }),
1552 RevsetExpression::Descendants { roots, generation } => transform_rec(roots, pre, post)?
1553 .map(|roots| RevsetExpression::Descendants {
1554 roots,
1555 generation: generation.clone(),
1556 }),
1557 RevsetExpression::Range {
1558 roots,
1559 heads,
1560 generation,
1561 parents_range,
1562 } => transform_rec_pair((roots, heads), pre, post)?.map(|(roots, heads)| {
1563 RevsetExpression::Range {
1564 roots,
1565 heads,
1566 generation: generation.clone(),
1567 parents_range: parents_range.clone(),
1568 }
1569 }),
1570 RevsetExpression::DagRange { roots, heads } => {
1571 transform_rec_pair((roots, heads), pre, post)?
1572 .map(|(roots, heads)| RevsetExpression::DagRange { roots, heads })
1573 }
1574 RevsetExpression::Reachable { sources, domain } => {
1575 transform_rec_pair((sources, domain), pre, post)?
1576 .map(|(sources, domain)| RevsetExpression::Reachable { sources, domain })
1577 }
1578 RevsetExpression::Heads(candidates) => {
1579 transform_rec(candidates, pre, post)?.map(RevsetExpression::Heads)
1580 }
1581 RevsetExpression::HeadsRange {
1582 roots,
1583 heads,
1584 parents_range,
1585 filter,
1586 } => {
1587 let transformed_roots = transform_rec(roots, pre, post)?;
1588 let transformed_heads = transform_rec(heads, pre, post)?;
1589 let transformed_filter = transform_rec(filter, pre, post)?;
1590 (transformed_roots.is_some()
1591 || transformed_heads.is_some()
1592 || transformed_filter.is_some())
1593 .then(|| RevsetExpression::HeadsRange {
1594 roots: transformed_roots.unwrap_or_else(|| roots.clone()),
1595 heads: transformed_heads.unwrap_or_else(|| heads.clone()),
1596 parents_range: parents_range.clone(),
1597 filter: transformed_filter.unwrap_or_else(|| filter.clone()),
1598 })
1599 }
1600 RevsetExpression::Roots(candidates) => {
1601 transform_rec(candidates, pre, post)?.map(RevsetExpression::Roots)
1602 }
1603 RevsetExpression::Forks => None,
1604 RevsetExpression::ForkPoint(expression) => {
1605 transform_rec(expression, pre, post)?.map(RevsetExpression::ForkPoint)
1606 }
1607 RevsetExpression::MergePoint(expression) => {
1608 transform_rec(expression, pre, post)?.map(RevsetExpression::MergePoint)
1609 }
1610 RevsetExpression::Bisect(expression) => {
1611 transform_rec(expression, pre, post)?.map(RevsetExpression::Bisect)
1612 }
1613 RevsetExpression::HasSize { candidates, count } => {
1614 transform_rec(candidates, pre, post)?.map(|candidates| RevsetExpression::HasSize {
1615 candidates,
1616 count: *count,
1617 })
1618 }
1619 RevsetExpression::Latest { candidates, count } => transform_rec(candidates, pre, post)?
1620 .map(|candidates| RevsetExpression::Latest {
1621 candidates,
1622 count: *count,
1623 }),
1624 RevsetExpression::Filter(_) => None,
1625 RevsetExpression::AsFilter(candidates) => {
1626 transform_rec(candidates, pre, post)?.map(RevsetExpression::AsFilter)
1627 }
1628 RevsetExpression::Divergent => None,
1629 RevsetExpression::AtOperation {
1630 operation,
1631 candidates,
1632 } => transform_rec(candidates, pre, post)?.map(|candidates| {
1633 RevsetExpression::AtOperation {
1634 operation: operation.clone(),
1635 candidates,
1636 }
1637 }),
1638 RevsetExpression::WithinReference {
1639 candidates,
1640 commits,
1641 } => transform_rec(candidates, pre, post)?.map(|candidates| {
1642 RevsetExpression::WithinReference {
1643 candidates,
1644 commits: commits.clone(),
1645 }
1646 }),
1647 RevsetExpression::WithinVisibility {
1648 candidates,
1649 visible_heads,
1650 } => transform_rec(candidates, pre, post)?.map(|candidates| {
1651 RevsetExpression::WithinVisibility {
1652 candidates,
1653 visible_heads: visible_heads.clone(),
1654 }
1655 }),
1656 RevsetExpression::Coalesce(expression1, expression2) => transform_rec_pair(
1657 (expression1, expression2),
1658 pre,
1659 post,
1660 )?
1661 .map(|(expression1, expression2)| RevsetExpression::Coalesce(expression1, expression2)),
1662 RevsetExpression::Present(candidates) => {
1663 transform_rec(candidates, pre, post)?.map(RevsetExpression::Present)
1664 }
1665 RevsetExpression::NotIn(complement) => {
1666 transform_rec(complement, pre, post)?.map(RevsetExpression::NotIn)
1667 }
1668 RevsetExpression::Union(expression1, expression2) => {
1669 transform_rec_pair((expression1, expression2), pre, post)?.map(
1670 |(expression1, expression2)| RevsetExpression::Union(expression1, expression2),
1671 )
1672 }
1673 RevsetExpression::Intersection(expression1, expression2) => {
1674 transform_rec_pair((expression1, expression2), pre, post)?.map(
1675 |(expression1, expression2)| {
1676 RevsetExpression::Intersection(expression1, expression2)
1677 },
1678 )
1679 }
1680 RevsetExpression::Difference(expression1, expression2) => {
1681 transform_rec_pair((expression1, expression2), pre, post)?.map(
1682 |(expression1, expression2)| {
1683 RevsetExpression::Difference(expression1, expression2)
1684 },
1685 )
1686 }
1687 }
1688 .map(Arc::new))
1689 }
1690
1691 #[expect(clippy::type_complexity)]
1692 fn transform_rec_pair<St: ExpressionState, E>(
1693 (expression1, expression2): (&Arc<RevsetExpression<St>>, &Arc<RevsetExpression<St>>),
1694 pre: &mut impl FnMut(&Arc<RevsetExpression<St>>) -> Result<PreTransformedExpression<St>, E>,
1695 post: &mut impl FnMut(&Arc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1696 ) -> Result<Option<(Arc<RevsetExpression<St>>, Arc<RevsetExpression<St>>)>, E> {
1697 match (
1698 transform_rec(expression1, pre, post)?,
1699 transform_rec(expression2, pre, post)?,
1700 ) {
1701 (Some(new_expression1), Some(new_expression2)) => {
1702 Ok(Some((new_expression1, new_expression2)))
1703 }
1704 (Some(new_expression1), None) => Ok(Some((new_expression1, expression2.clone()))),
1705 (None, Some(new_expression2)) => Ok(Some((expression1.clone(), new_expression2))),
1706 (None, None) => Ok(None),
1707 }
1708 }
1709
1710 fn transform_rec<St: ExpressionState, E>(
1711 expression: &Arc<RevsetExpression<St>>,
1712 pre: &mut impl FnMut(&Arc<RevsetExpression<St>>) -> Result<PreTransformedExpression<St>, E>,
1713 post: &mut impl FnMut(&Arc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1714 ) -> Result<TransformedExpression<St>, E> {
1715 if let ControlFlow::Break(transformed) = pre(expression)? {
1716 return Ok(transformed);
1717 }
1718 if let Some(new_expression) = transform_child_rec(expression, pre, post)? {
1719 Ok(Some(post(&new_expression)?.unwrap_or(new_expression)))
1721 } else {
1722 post(expression)
1723 }
1724 }
1725
1726 transform_rec(expression, &mut pre, &mut post)
1727}
1728
1729trait ExpressionStateFolder<InSt: ExpressionState, OutSt: ExpressionState> {
1734 type Error;
1735
1736 fn fold_expression(
1739 &mut self,
1740 expression: &RevsetExpression<InSt>,
1741 ) -> Result<Arc<RevsetExpression<OutSt>>, Self::Error> {
1742 fold_child_expression_state(self, expression)
1743 }
1744
1745 fn fold_commit_ref(
1747 &mut self,
1748 commit_ref: &InSt::CommitRef,
1749 ) -> Result<Arc<RevsetExpression<OutSt>>, Self::Error>;
1750
1751 fn fold_at_operation(
1753 &mut self,
1754 operation: &InSt::Operation,
1755 candidates: &RevsetExpression<InSt>,
1756 ) -> Result<Arc<RevsetExpression<OutSt>>, Self::Error>;
1757}
1758
1759fn fold_child_expression_state<InSt, OutSt, F>(
1761 folder: &mut F,
1762 expression: &RevsetExpression<InSt>,
1763) -> Result<Arc<RevsetExpression<OutSt>>, F::Error>
1764where
1765 InSt: ExpressionState,
1766 OutSt: ExpressionState,
1767 F: ExpressionStateFolder<InSt, OutSt> + ?Sized,
1768{
1769 let expression: Arc<_> = match expression {
1770 RevsetExpression::None => RevsetExpression::None.into(),
1771 RevsetExpression::All => RevsetExpression::All.into(),
1772 RevsetExpression::VisibleHeads => RevsetExpression::VisibleHeads.into(),
1773 RevsetExpression::VisibleHeadsOrReferenced => {
1774 RevsetExpression::VisibleHeadsOrReferenced.into()
1775 }
1776 RevsetExpression::Root => RevsetExpression::Root.into(),
1777 RevsetExpression::Commits(ids) => RevsetExpression::Commits(ids.clone()).into(),
1778 RevsetExpression::CommitRef(commit_ref) => folder.fold_commit_ref(commit_ref)?,
1779 RevsetExpression::Ancestors {
1780 heads,
1781 generation,
1782 parents_range,
1783 } => {
1784 let heads = folder.fold_expression(heads)?;
1785 let generation = generation.clone();
1786 let parents_range = parents_range.clone();
1787 RevsetExpression::Ancestors {
1788 heads,
1789 generation,
1790 parents_range,
1791 }
1792 .into()
1793 }
1794 RevsetExpression::Descendants { roots, generation } => {
1795 let roots = folder.fold_expression(roots)?;
1796 let generation = generation.clone();
1797 RevsetExpression::Descendants { roots, generation }.into()
1798 }
1799 RevsetExpression::Range {
1800 roots,
1801 heads,
1802 generation,
1803 parents_range,
1804 } => {
1805 let roots = folder.fold_expression(roots)?;
1806 let heads = folder.fold_expression(heads)?;
1807 let generation = generation.clone();
1808 let parents_range = parents_range.clone();
1809 RevsetExpression::Range {
1810 roots,
1811 heads,
1812 generation,
1813 parents_range,
1814 }
1815 .into()
1816 }
1817 RevsetExpression::DagRange { roots, heads } => {
1818 let roots = folder.fold_expression(roots)?;
1819 let heads = folder.fold_expression(heads)?;
1820 RevsetExpression::DagRange { roots, heads }.into()
1821 }
1822 RevsetExpression::Reachable { sources, domain } => {
1823 let sources = folder.fold_expression(sources)?;
1824 let domain = folder.fold_expression(domain)?;
1825 RevsetExpression::Reachable { sources, domain }.into()
1826 }
1827 RevsetExpression::Heads(heads) => {
1828 let heads = folder.fold_expression(heads)?;
1829 RevsetExpression::Heads(heads).into()
1830 }
1831 RevsetExpression::HeadsRange {
1832 roots,
1833 heads,
1834 parents_range,
1835 filter,
1836 } => {
1837 let roots = folder.fold_expression(roots)?;
1838 let heads = folder.fold_expression(heads)?;
1839 let parents_range = parents_range.clone();
1840 let filter = folder.fold_expression(filter)?;
1841 RevsetExpression::HeadsRange {
1842 roots,
1843 heads,
1844 parents_range,
1845 filter,
1846 }
1847 .into()
1848 }
1849 RevsetExpression::Roots(roots) => {
1850 let roots = folder.fold_expression(roots)?;
1851 RevsetExpression::Roots(roots).into()
1852 }
1853 RevsetExpression::Forks => RevsetExpression::Forks.into(),
1854 RevsetExpression::ForkPoint(expression) => {
1855 let expression = folder.fold_expression(expression)?;
1856 RevsetExpression::ForkPoint(expression).into()
1857 }
1858 RevsetExpression::MergePoint(expression) => {
1859 let expression = folder.fold_expression(expression)?;
1860 RevsetExpression::MergePoint(expression).into()
1861 }
1862 RevsetExpression::Bisect(expression) => {
1863 let expression = folder.fold_expression(expression)?;
1864 RevsetExpression::Bisect(expression).into()
1865 }
1866 RevsetExpression::HasSize { candidates, count } => {
1867 let candidates = folder.fold_expression(candidates)?;
1868 let count = *count;
1869 RevsetExpression::HasSize { candidates, count }.into()
1870 }
1871 RevsetExpression::Latest { candidates, count } => {
1872 let candidates = folder.fold_expression(candidates)?;
1873 let count = *count;
1874 RevsetExpression::Latest { candidates, count }.into()
1875 }
1876 RevsetExpression::Filter(predicate) => RevsetExpression::Filter(predicate.clone()).into(),
1877 RevsetExpression::AsFilter(candidates) => {
1878 let candidates = folder.fold_expression(candidates)?;
1879 RevsetExpression::AsFilter(candidates).into()
1880 }
1881 RevsetExpression::Divergent => RevsetExpression::Divergent.into(),
1882 RevsetExpression::AtOperation {
1883 operation,
1884 candidates,
1885 } => folder.fold_at_operation(operation, candidates)?,
1886 RevsetExpression::WithinReference {
1887 candidates,
1888 commits,
1889 } => {
1890 let candidates = folder.fold_expression(candidates)?;
1891 let commits = commits.clone();
1892 RevsetExpression::WithinReference {
1893 candidates,
1894 commits,
1895 }
1896 .into()
1897 }
1898 RevsetExpression::WithinVisibility {
1899 candidates,
1900 visible_heads,
1901 } => {
1902 let candidates = folder.fold_expression(candidates)?;
1903 let visible_heads = visible_heads.clone();
1904 RevsetExpression::WithinVisibility {
1905 candidates,
1906 visible_heads,
1907 }
1908 .into()
1909 }
1910 RevsetExpression::Coalesce(expression1, expression2) => {
1911 let expression1 = folder.fold_expression(expression1)?;
1912 let expression2 = folder.fold_expression(expression2)?;
1913 RevsetExpression::Coalesce(expression1, expression2).into()
1914 }
1915 RevsetExpression::Present(candidates) => {
1916 let candidates = folder.fold_expression(candidates)?;
1917 RevsetExpression::Present(candidates).into()
1918 }
1919 RevsetExpression::NotIn(complement) => {
1920 let complement = folder.fold_expression(complement)?;
1921 RevsetExpression::NotIn(complement).into()
1922 }
1923 RevsetExpression::Union(expression1, expression2) => {
1924 let expression1 = folder.fold_expression(expression1)?;
1925 let expression2 = folder.fold_expression(expression2)?;
1926 RevsetExpression::Union(expression1, expression2).into()
1927 }
1928 RevsetExpression::Intersection(expression1, expression2) => {
1929 let expression1 = folder.fold_expression(expression1)?;
1930 let expression2 = folder.fold_expression(expression2)?;
1931 RevsetExpression::Intersection(expression1, expression2).into()
1932 }
1933 RevsetExpression::Difference(expression1, expression2) => {
1934 let expression1 = folder.fold_expression(expression1)?;
1935 let expression2 = folder.fold_expression(expression2)?;
1936 RevsetExpression::Difference(expression1, expression2).into()
1937 }
1938 };
1939 Ok(expression)
1940}
1941
1942fn resolve_referenced_commits<St: ExpressionState>(
1946 expression: &Arc<RevsetExpression<St>>,
1947) -> TransformedExpression<St> {
1948 let omit_empty = true;
1950 resolve_referenced_commits_sub(expression, omit_empty)
1951}
1952
1953fn resolve_referenced_commits_sub<St: ExpressionState>(
1954 expression: &Arc<RevsetExpression<St>>,
1955 omit_empty: bool,
1956) -> TransformedExpression<St> {
1957 if matches!(
1959 expression.as_ref(),
1960 RevsetExpression::WithinReference { .. }
1961 ) {
1962 return None;
1963 }
1964
1965 let mut inner_commits = Vec::new();
1967 let mut outer_commits = Vec::new();
1968 let transformed = transform_expression(
1969 expression,
1970 |expression| match expression.as_ref() {
1971 RevsetExpression::WithinReference { commits, .. } => {
1973 inner_commits.extend_from_slice(commits);
1974 ControlFlow::Break(None)
1975 }
1976 RevsetExpression::WithinVisibility {
1978 candidates,
1979 visible_heads,
1980 } => {
1981 inner_commits.extend_from_slice(visible_heads);
1983 let transformed = resolve_referenced_commits_sub(candidates, false);
1986 match transformed.as_deref().unwrap_or(candidates) {
1988 RevsetExpression::WithinReference { commits, .. } => {
1989 inner_commits.extend_from_slice(commits);
1990 }
1991 _ => unreachable!("WithinReference should never be omitted"),
1992 }
1993 ControlFlow::Break(transformed.map(|candidates| {
1994 Arc::new(RevsetExpression::WithinVisibility {
1995 candidates,
1996 visible_heads: visible_heads.clone(),
1997 })
1998 }))
1999 }
2000 _ => ControlFlow::Continue(()),
2001 },
2002 |expression| {
2003 if let RevsetExpression::Commits(commits) = expression.as_ref() {
2004 outer_commits.extend_from_slice(commits);
2005 }
2006 None
2007 },
2008 );
2009
2010 outer_commits.extend(inner_commits);
2013 if omit_empty && outer_commits.is_empty() {
2014 return transformed;
2015 }
2016 Some(Arc::new(RevsetExpression::WithinReference {
2017 candidates: transformed.unwrap_or_else(|| expression.clone()),
2018 commits: outer_commits,
2019 }))
2020}
2021
2022fn flatten_intersections<St: ExpressionState>(
2025 expression: &Arc<RevsetExpression<St>>,
2026) -> TransformedExpression<St> {
2027 fn flatten<St: ExpressionState>(
2028 expression1: &Arc<RevsetExpression<St>>,
2029 expression2: &Arc<RevsetExpression<St>>,
2030 ) -> TransformedExpression<St> {
2031 let recurse = |a, b| flatten(a, b).unwrap_or_else(|| a.intersection(b));
2032
2033 match expression2.as_ref() {
2034 RevsetExpression::Intersection(inner1, inner2) => {
2036 Some(recurse(expression1, inner1).intersection(inner2))
2037 }
2038 _ => None,
2039 }
2040 }
2041
2042 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2043 RevsetExpression::Intersection(expression1, expression2) => {
2044 flatten(expression1, expression2)
2045 }
2046 _ => None,
2047 })
2048}
2049
2050fn sort_intersection_by_key<St: ExpressionState, T: Ord>(
2054 base: &Arc<RevsetExpression<St>>,
2055 expression: &Arc<RevsetExpression<St>>,
2056 mut get_key: impl FnMut(&RevsetExpression<St>) -> T,
2057) -> TransformedExpression<St> {
2058 fn sort_intersection_helper<St: ExpressionState, T: Ord>(
2061 base: &Arc<RevsetExpression<St>>,
2062 expression: &Arc<RevsetExpression<St>>,
2063 expression_key: T,
2064 mut get_key: impl FnMut(&RevsetExpression<St>) -> T,
2065 ) -> TransformedExpression<St> {
2066 if let RevsetExpression::Intersection(inner1, inner2) = base.as_ref() {
2067 (expression_key < get_key(inner2)).then(|| {
2069 sort_intersection_helper(inner1, expression, expression_key, get_key)
2070 .unwrap_or_else(|| inner1.intersection(expression))
2071 .intersection(inner2)
2072 })
2073 } else {
2074 (expression_key < get_key(base)).then(|| expression.intersection(base))
2076 }
2077 }
2078
2079 sort_intersection_helper(base, expression, get_key(expression), get_key)
2080}
2081
2082fn sort_negations_and_ancestors<St: ExpressionState>(
2087 expression: &Arc<RevsetExpression<St>>,
2088) -> TransformedExpression<St> {
2089 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
2090 enum AncestorsOrder {
2091 NegatedAncestors,
2092 Ancestors,
2093 Other,
2094 NegatedOther,
2095 }
2096
2097 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2098 RevsetExpression::Intersection(expression1, expression2) => {
2099 sort_intersection_by_key(expression1, expression2, |expression| match expression {
2100 RevsetExpression::Ancestors {
2101 heads: _,
2102 generation: Range { end: u64::MAX, .. },
2103 parents_range: _,
2104 } => AncestorsOrder::Ancestors,
2105 RevsetExpression::NotIn(complement) => match complement.as_ref() {
2106 RevsetExpression::Ancestors {
2107 heads: _,
2108 generation: Range { end: u64::MAX, .. },
2109 parents_range: PARENTS_RANGE_FULL,
2112 } => AncestorsOrder::NegatedAncestors,
2113 _ => AncestorsOrder::NegatedOther,
2114 },
2115 _ => AncestorsOrder::Other,
2116 })
2117 }
2118 _ => None,
2119 })
2120}
2121
2122fn internalize_filter<St: ExpressionState>(
2131 expression: &Arc<RevsetExpression<St>>,
2132) -> TransformedExpression<St> {
2133 fn get_filter<St: ExpressionState>(
2134 expression: &Arc<RevsetExpression<St>>,
2135 ) -> Option<&Arc<RevsetExpression<St>>> {
2136 match expression.as_ref() {
2137 RevsetExpression::Filter(_) => Some(expression),
2138 RevsetExpression::AsFilter(candidates) => Some(candidates),
2139 _ => None,
2140 }
2141 }
2142
2143 fn mark_filter<St: ExpressionState>(
2144 expression: Arc<RevsetExpression<St>>,
2145 ) -> Arc<RevsetExpression<St>> {
2146 Arc::new(RevsetExpression::AsFilter(expression))
2147 }
2148
2149 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2150 RevsetExpression::Present(e) => get_filter(e).map(|f| mark_filter(f.present())),
2152 RevsetExpression::NotIn(e) => get_filter(e).map(|f| mark_filter(f.negated())),
2153 RevsetExpression::Union(e1, e2) => {
2154 let f1 = get_filter(e1);
2155 let f2 = get_filter(e2);
2156 (f1.is_some() || f2.is_some())
2157 .then(|| mark_filter(f1.unwrap_or(e1).union(f2.unwrap_or(e2))))
2158 }
2159 RevsetExpression::Intersection(e1, e2) => match (get_filter(e1), get_filter(e2)) {
2165 (Some(f1), Some(f2)) => Some(mark_filter(f1.intersection(f2))),
2167 (Some(_), None) => Some(e2.intersection(e1)),
2169 (None, Some(f2)) => match e1.as_ref() {
2171 RevsetExpression::Intersection(e1a, e1b) => {
2172 get_filter(e1b).map(|f1b| e1a.intersection(&mark_filter(f1b.intersection(f2))))
2173 }
2174 _ => None,
2175 },
2176 (None, None) => match e1.as_ref() {
2178 RevsetExpression::Intersection(e1a, e1b) => {
2179 get_filter(e1b).map(|_| e1a.intersection(e2).intersection(e1b))
2180 }
2181 _ => None,
2182 },
2183 },
2184 _ => None,
2186 })
2187}
2188
2189fn fold_redundant_expression<St: ExpressionState>(
2194 expression: &Arc<RevsetExpression<St>>,
2195) -> TransformedExpression<St> {
2196 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2197 RevsetExpression::Commits(commits) if commits.is_empty() => Some(RevsetExpression::none()),
2198 RevsetExpression::NotIn(outer) => match outer.as_ref() {
2199 RevsetExpression::NotIn(inner) => Some(inner.clone()),
2200 RevsetExpression::None => Some(RevsetExpression::all()),
2201 RevsetExpression::All => Some(RevsetExpression::none()),
2202 _ => None,
2203 },
2204 RevsetExpression::Union(expression1, expression2) => {
2205 match (expression1.as_ref(), expression2.as_ref()) {
2206 (_, RevsetExpression::None) => Some(expression1.clone()),
2207 (RevsetExpression::None, _) => Some(expression2.clone()),
2208 (RevsetExpression::All, _) => Some(RevsetExpression::all()),
2209 (_, RevsetExpression::All) => Some(RevsetExpression::all()),
2210 _ => None,
2211 }
2212 }
2213 RevsetExpression::Intersection(expression1, expression2) => {
2214 match (expression1.as_ref(), expression2.as_ref()) {
2215 (RevsetExpression::None, _) => Some(RevsetExpression::none()),
2216 (_, RevsetExpression::None) => Some(RevsetExpression::none()),
2217 (_, RevsetExpression::All) => Some(expression1.clone()),
2218 (RevsetExpression::All, _) => Some(expression2.clone()),
2219 _ => None,
2220 }
2221 }
2222 _ => None,
2223 })
2224}
2225
2226fn ancestors_to_heads<St: ExpressionState>(
2230 expression: &RevsetExpression<St>,
2231) -> Result<Arc<RevsetExpression<St>>, ()> {
2232 match ancestors_to_heads_and_parents_range(expression) {
2233 Ok((heads, PARENTS_RANGE_FULL)) => Ok(heads),
2234 _ => Err(()),
2235 }
2236}
2237
2238fn ancestors_to_heads_and_parents_range<St: ExpressionState>(
2239 expression: &RevsetExpression<St>,
2240) -> Result<(Arc<RevsetExpression<St>>, Range<u32>), ()> {
2241 match expression {
2242 RevsetExpression::Ancestors {
2243 heads,
2244 generation: GENERATION_RANGE_FULL,
2245 parents_range,
2246 } => Ok((heads.clone(), parents_range.clone())),
2247 RevsetExpression::Ancestors {
2248 heads,
2249 generation: Range {
2250 start,
2251 end: u64::MAX,
2252 },
2253 parents_range,
2254 } => Ok((
2255 Arc::new(RevsetExpression::Ancestors {
2256 heads: heads.clone(),
2257 generation: (*start)..start.saturating_add(1),
2258 parents_range: parents_range.clone(),
2259 }),
2260 parents_range.clone(),
2261 )),
2262 _ => Err(()),
2263 }
2264}
2265
2266fn fold_ancestors_union<St: ExpressionState>(
2271 expression: &Arc<RevsetExpression<St>>,
2272) -> TransformedExpression<St> {
2273 fn union_ancestors<St: ExpressionState>(
2274 expression1: &Arc<RevsetExpression<St>>,
2275 expression2: &Arc<RevsetExpression<St>>,
2276 ) -> TransformedExpression<St> {
2277 let heads1 = ancestors_to_heads(expression1).ok()?;
2278 let heads2 = ancestors_to_heads(expression2).ok()?;
2279 Some(heads1.union(&heads2).ancestors())
2280 }
2281
2282 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2283 RevsetExpression::Union(expression1, expression2) => {
2284 union_ancestors(expression1, expression2)
2286 }
2287 RevsetExpression::Intersection(expression1, expression2) => {
2288 match (expression1.as_ref(), expression2.as_ref()) {
2289 (RevsetExpression::NotIn(complement1), RevsetExpression::NotIn(complement2)) => {
2291 union_ancestors(complement1, complement2).map(|expression| expression.negated())
2292 }
2293 _ => None,
2294 }
2295 }
2296 _ => None,
2297 })
2298}
2299
2300fn fold_heads_range<St: ExpressionState>(
2307 expression: &Arc<RevsetExpression<St>>,
2308) -> TransformedExpression<St> {
2309 struct FilteredRange<St: ExpressionState> {
2311 roots: Arc<RevsetExpression<St>>,
2312 heads_and_parents_range: Option<(Arc<RevsetExpression<St>>, Range<u32>)>,
2313 filter: Arc<RevsetExpression<St>>,
2314 }
2315
2316 impl<St: ExpressionState> FilteredRange<St> {
2317 fn new(roots: Arc<RevsetExpression<St>>) -> Self {
2318 Self {
2320 roots,
2321 heads_and_parents_range: None,
2322 filter: RevsetExpression::all(),
2323 }
2324 }
2325
2326 fn add(mut self, expression: &Arc<RevsetExpression<St>>) -> Self {
2327 if self.heads_and_parents_range.is_none() {
2328 if let Ok(heads_and_parents_range) =
2330 ancestors_to_heads_and_parents_range(expression)
2331 {
2332 self.heads_and_parents_range = Some(heads_and_parents_range);
2333 return self;
2334 }
2335 }
2336 self.add_filter(expression)
2337 }
2338
2339 fn add_filter(mut self, expression: &Arc<RevsetExpression<St>>) -> Self {
2340 self.filter = if let RevsetExpression::All = self.filter.as_ref() {
2341 expression.clone()
2343 } else {
2344 self.filter.intersection(expression)
2345 };
2346 self
2347 }
2348 }
2349
2350 fn to_filtered_range<St: ExpressionState>(
2351 expression: &Arc<RevsetExpression<St>>,
2352 ) -> Option<FilteredRange<St>> {
2353 if let Ok(heads_and_parents_range) = ancestors_to_heads_and_parents_range(expression) {
2357 return Some(FilteredRange {
2358 roots: RevsetExpression::none(),
2359 heads_and_parents_range: Some(heads_and_parents_range),
2360 filter: RevsetExpression::all(),
2361 });
2362 }
2363 match expression.as_ref() {
2364 RevsetExpression::NotIn(complement) => {
2367 if let Ok(roots) = ancestors_to_heads(complement) {
2368 Some(FilteredRange::new(roots))
2369 } else {
2370 Some(FilteredRange::new(RevsetExpression::none()).add_filter(expression))
2373 }
2374 }
2375 RevsetExpression::All | RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_) => {
2377 Some(FilteredRange::new(RevsetExpression::none()).add_filter(expression))
2378 }
2379 RevsetExpression::Intersection(expression1, expression2) => {
2382 to_filtered_range(expression1).map(|filtered_range| filtered_range.add(expression2))
2383 }
2384 _ => None,
2385 }
2386 }
2387
2388 fn to_heads_range<St: ExpressionState>(
2389 candidates: &Arc<RevsetExpression<St>>,
2390 ) -> Option<Arc<RevsetExpression<St>>> {
2391 to_filtered_range(candidates).map(|filtered_range| {
2392 let (heads, parents_range) =
2393 filtered_range.heads_and_parents_range.unwrap_or_else(|| {
2394 (
2395 RevsetExpression::visible_heads_or_referenced(),
2396 PARENTS_RANGE_FULL,
2397 )
2398 });
2399 RevsetExpression::HeadsRange {
2400 roots: filtered_range.roots,
2401 heads,
2402 parents_range,
2403 filter: filtered_range.filter,
2404 }
2405 .into()
2406 })
2407 }
2408
2409 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2410 RevsetExpression::Ancestors {
2413 heads,
2414 generation: GENERATION_RANGE_FULL,
2417 parents_range: PARENTS_RANGE_FULL,
2418 } => to_heads_range(heads).map(|heads| heads.ancestors()),
2419 RevsetExpression::Heads(candidates) => to_heads_range(candidates),
2422 _ => None,
2423 })
2424}
2425
2426fn to_difference_range<St: ExpressionState>(
2427 expression: &Arc<RevsetExpression<St>>,
2428 complement: &Arc<RevsetExpression<St>>,
2429) -> TransformedExpression<St> {
2430 let RevsetExpression::Ancestors {
2431 heads,
2432 generation,
2433 parents_range,
2434 } = expression.as_ref()
2435 else {
2436 return None;
2437 };
2438 let roots = ancestors_to_heads(complement).ok()?;
2439 Some(Arc::new(RevsetExpression::Range {
2442 roots,
2443 heads: heads.clone(),
2444 generation: generation.clone(),
2445 parents_range: parents_range.clone(),
2446 }))
2447}
2448
2449fn fold_difference<St: ExpressionState>(
2452 expression: &Arc<RevsetExpression<St>>,
2453) -> TransformedExpression<St> {
2454 fn to_difference<St: ExpressionState>(
2455 expression: &Arc<RevsetExpression<St>>,
2456 complement: &Arc<RevsetExpression<St>>,
2457 ) -> Arc<RevsetExpression<St>> {
2458 to_difference_range(expression, complement).unwrap_or_else(|| expression.minus(complement))
2459 }
2460
2461 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2462 RevsetExpression::Intersection(expression1, expression2) => {
2463 match (expression1.as_ref(), expression2.as_ref()) {
2464 (_, RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_)) => None,
2466 (_, RevsetExpression::NotIn(complement)) => {
2467 Some(to_difference(expression1, complement))
2468 }
2469 (RevsetExpression::NotIn(complement), _) => {
2470 Some(to_difference(expression2, complement))
2471 }
2472 _ => None,
2473 }
2474 }
2475 _ => None,
2476 })
2477}
2478
2479fn fold_not_in_ancestors<St: ExpressionState>(
2484 expression: &Arc<RevsetExpression<St>>,
2485) -> TransformedExpression<St> {
2486 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2487 RevsetExpression::NotIn(complement)
2488 if matches!(complement.as_ref(), RevsetExpression::Ancestors { .. }) =>
2489 {
2490 to_difference_range(
2493 &RevsetExpression::visible_heads_or_referenced().ancestors(),
2494 complement,
2495 )
2496 }
2497 _ => None,
2498 })
2499}
2500
2501fn unfold_difference<St: ExpressionState>(
2506 expression: &Arc<RevsetExpression<St>>,
2507) -> TransformedExpression<St> {
2508 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2509 RevsetExpression::Range {
2511 roots,
2512 heads,
2513 parents_range,
2514 generation,
2515 } => {
2516 let heads_ancestors = Arc::new(RevsetExpression::Ancestors {
2517 heads: heads.clone(),
2518 generation: generation.clone(),
2519 parents_range: parents_range.clone(),
2520 });
2521 Some(heads_ancestors.intersection(&roots.ancestors().negated()))
2522 }
2523 RevsetExpression::Difference(expression1, expression2) => {
2524 Some(expression1.intersection(&expression2.negated()))
2525 }
2526 _ => None,
2527 })
2528}
2529
2530fn fold_generation<St: ExpressionState>(
2533 expression: &Arc<RevsetExpression<St>>,
2534) -> TransformedExpression<St> {
2535 fn add_generation(generation1: &Range<u64>, generation2: &Range<u64>) -> Range<u64> {
2536 if generation1.is_empty() || generation2.is_empty() {
2538 GENERATION_RANGE_EMPTY
2539 } else {
2540 let start = u64::saturating_add(generation1.start, generation2.start);
2541 let end = u64::saturating_add(generation1.end, generation2.end - 1);
2542 start..end
2543 }
2544 }
2545
2546 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2547 RevsetExpression::Ancestors {
2548 heads,
2549 generation: generation1,
2550 parents_range: parents1,
2551 } => {
2552 match heads.as_ref() {
2553 RevsetExpression::Ancestors {
2557 heads,
2558 generation: generation2,
2559 parents_range: parents2,
2560 } if parents2 == parents1 => Some(Arc::new(RevsetExpression::Ancestors {
2561 heads: heads.clone(),
2562 generation: add_generation(generation1, generation2),
2563 parents_range: parents1.clone(),
2564 })),
2565 _ => None,
2566 }
2567 }
2568 RevsetExpression::Descendants {
2569 roots,
2570 generation: generation1,
2571 } => {
2572 match roots.as_ref() {
2573 RevsetExpression::Descendants {
2577 roots,
2578 generation: generation2,
2579 } => Some(Arc::new(RevsetExpression::Descendants {
2580 roots: roots.clone(),
2581 generation: add_generation(generation1, generation2),
2582 })),
2583 _ => None,
2584 }
2585 }
2586 _ => None,
2588 })
2589}
2590
2591pub fn optimize<St: ExpressionState>(
2594 expression: Arc<RevsetExpression<St>>,
2595) -> Arc<RevsetExpression<St>> {
2596 let expression = resolve_referenced_commits(&expression).unwrap_or(expression);
2599 let expression = unfold_difference(&expression).unwrap_or(expression);
2600 let expression = fold_redundant_expression(&expression).unwrap_or(expression);
2601 let expression = fold_generation(&expression).unwrap_or(expression);
2602 let expression = flatten_intersections(&expression).unwrap_or(expression);
2603 let expression = sort_negations_and_ancestors(&expression).unwrap_or(expression);
2604 let expression = fold_ancestors_union(&expression).unwrap_or(expression);
2605 let expression = internalize_filter(&expression).unwrap_or(expression);
2606 let expression = fold_heads_range(&expression).unwrap_or(expression);
2607 let expression = fold_difference(&expression).unwrap_or(expression);
2608 fold_not_in_ancestors(&expression).unwrap_or(expression)
2609}
2610
2611pub fn walk_revs<'index>(
2615 repo: &'index dyn Repo,
2616 wanted: &[CommitId],
2617 unwanted: &[CommitId],
2618) -> Result<Box<dyn Revset + 'index>, RevsetEvaluationError> {
2619 RevsetExpression::commits(unwanted.to_vec())
2620 .range(&RevsetExpression::commits(wanted.to_vec()))
2621 .evaluate(repo)
2622}
2623
2624fn reload_repo_at_operation(
2625 repo: &dyn Repo,
2626 op_str: &str,
2627) -> Result<Arc<ReadonlyRepo>, RevsetResolutionError> {
2628 let base_repo = repo.base_repo();
2632 let operation = op_walk::resolve_op_with_repo(base_repo, op_str)
2633 .block_on()
2634 .map_err(|err| RevsetResolutionError::Other(err.into()))?;
2635 base_repo
2636 .reload_at(&operation)
2637 .block_on()
2638 .map_err(|err| match err {
2639 RepoLoaderError::Backend(err) => RevsetResolutionError::Backend(err),
2640 RepoLoaderError::Index(_)
2641 | RepoLoaderError::IndexStore(_)
2642 | RepoLoaderError::OpHeadsStoreError(_)
2643 | RepoLoaderError::OpStore(_)
2644 | RepoLoaderError::TransactionCommit(_) => RevsetResolutionError::Other(err.into()),
2645 })
2646}
2647
2648fn resolve_remote_symbol(
2649 repo: &dyn Repo,
2650 symbol: RemoteRefSymbol<'_>,
2651) -> Result<CommitId, RevsetResolutionError> {
2652 let remote_ref = repo.view().get_remote_tag(symbol);
2653 if let Some(id) = to_resolved_ref("remote_tag", symbol, &remote_ref.target)? {
2654 return Ok(id);
2655 }
2656 let remote_ref = repo.view().get_remote_bookmark(symbol);
2657 if let Some(id) = to_resolved_ref("remote_bookmark", symbol, &remote_ref.target)? {
2658 return Ok(id);
2659 }
2660 Err(make_no_such_symbol_error(repo, symbol.to_string()))
2661}
2662
2663fn to_resolved_ref(
2664 kind: &'static str,
2665 symbol: impl ToString,
2666 target: &RefTarget,
2667) -> Result<Option<CommitId>, RevsetResolutionError> {
2668 match target.as_resolved() {
2669 Some(Some(id)) => Ok(Some(id.clone())),
2670 Some(None) => Ok(None),
2671 None => Err(RevsetResolutionError::ConflictedRef {
2672 kind,
2673 symbol: symbol.to_string(),
2674 targets: target.added_ids().cloned().collect(),
2675 }),
2676 }
2677}
2678
2679fn all_formatted_ref_symbols<'a>(
2680 all_refs: impl Iterator<Item = (&'a RefName, LocalRemoteRefTarget<'a>)>,
2681 include_synced_remotes: bool,
2682) -> impl Iterator<Item = String> {
2683 all_refs.flat_map(move |(name, targets)| {
2684 let local_target = targets.local_target;
2685 let local_symbol = local_target
2686 .is_present()
2687 .then(|| format_symbol(name.as_str()));
2688 let remote_symbols = targets
2689 .remote_refs
2690 .into_iter()
2691 .filter(move |&(_, remote_ref)| {
2692 include_synced_remotes
2693 || !remote_ref.is_tracked()
2694 || remote_ref.target != *local_target
2695 })
2696 .map(move |(remote, _)| format_remote_symbol(name.as_str(), remote.as_str()));
2697 local_symbol.into_iter().chain(remote_symbols)
2698 })
2699}
2700
2701fn make_no_such_symbol_error(repo: &dyn Repo, name: String) -> RevsetResolutionError {
2702 let include_synced_remotes = name.contains('@');
2703 let tag_names = all_formatted_ref_symbols(repo.view().tags(), include_synced_remotes);
2704 let bookmark_names = all_formatted_ref_symbols(repo.view().bookmarks(), include_synced_remotes);
2705 let mut candidates = collect_similar(&name, itertools::chain(tag_names, bookmark_names));
2706 candidates.dedup(); RevsetResolutionError::NoSuchRevision { name, candidates }
2708}
2709
2710pub trait PartialSymbolResolver {
2714 fn resolve_symbol(
2715 &self,
2716 repo: &dyn Repo,
2717 symbol: &str,
2718 ) -> Result<Option<CommitId>, RevsetResolutionError>;
2719}
2720
2721struct TagResolver;
2722
2723impl PartialSymbolResolver for TagResolver {
2724 fn resolve_symbol(
2725 &self,
2726 repo: &dyn Repo,
2727 symbol: &str,
2728 ) -> Result<Option<CommitId>, RevsetResolutionError> {
2729 let target = repo.view().get_local_tag(symbol.as_ref());
2730 to_resolved_ref("tag", symbol, target)
2731 }
2732}
2733
2734struct BookmarkResolver;
2735
2736impl PartialSymbolResolver for BookmarkResolver {
2737 fn resolve_symbol(
2738 &self,
2739 repo: &dyn Repo,
2740 symbol: &str,
2741 ) -> Result<Option<CommitId>, RevsetResolutionError> {
2742 let target = repo.view().get_local_bookmark(symbol.as_ref());
2743 to_resolved_ref("bookmark", symbol, target)
2744 }
2745}
2746
2747const DEFAULT_RESOLVERS: &[&dyn PartialSymbolResolver] = &[&TagResolver, &BookmarkResolver];
2748
2749struct CommitPrefixResolver<'a> {
2750 context_repo: &'a dyn Repo,
2751 context: Option<&'a IdPrefixContext>,
2752}
2753
2754impl CommitPrefixResolver<'_> {
2755 fn try_resolve(
2756 &self,
2757 repo: &dyn Repo,
2758 prefix: &HexPrefix,
2759 ) -> Result<Option<CommitId>, RevsetResolutionError> {
2760 let index = self
2761 .context
2762 .map(|ctx| ctx.populate(self.context_repo))
2763 .transpose()
2764 .map_err(|err| RevsetResolutionError::Other(err.into()))?
2765 .unwrap_or(IdPrefixIndex::empty());
2766 match index
2767 .resolve_commit_prefix(repo, prefix)
2768 .map_err(|err| RevsetResolutionError::Other(err.into()))?
2769 {
2770 PrefixResolution::AmbiguousMatch => {
2771 Err(RevsetResolutionError::AmbiguousCommitIdPrefix(prefix.hex()))
2772 }
2773 PrefixResolution::SingleMatch(id) => Ok(Some(id)),
2774 PrefixResolution::NoMatch => Ok(None),
2775 }
2776 }
2777}
2778
2779impl PartialSymbolResolver for CommitPrefixResolver<'_> {
2780 fn resolve_symbol(
2781 &self,
2782 repo: &dyn Repo,
2783 symbol: &str,
2784 ) -> Result<Option<CommitId>, RevsetResolutionError> {
2785 if let Some(prefix) = HexPrefix::try_from_hex(symbol) {
2786 self.try_resolve(repo, &prefix)
2787 } else {
2788 Ok(None)
2789 }
2790 }
2791}
2792
2793struct ChangePrefixResolver<'a> {
2794 context_repo: &'a dyn Repo,
2795 context: Option<&'a IdPrefixContext>,
2796}
2797
2798impl ChangePrefixResolver<'_> {
2799 fn try_resolve(
2800 &self,
2801 repo: &dyn Repo,
2802 prefix: &HexPrefix,
2803 ) -> Result<Option<ResolvedChangeTargets>, RevsetResolutionError> {
2804 let index = self
2805 .context
2806 .map(|ctx| ctx.populate(self.context_repo))
2807 .transpose()
2808 .map_err(|err| RevsetResolutionError::Other(err.into()))?
2809 .unwrap_or(IdPrefixIndex::empty());
2810 match index
2811 .resolve_change_prefix(repo, prefix)
2812 .block_on()
2813 .map_err(|err| RevsetResolutionError::Other(err.into()))?
2814 {
2815 PrefixResolution::AmbiguousMatch => Err(
2816 RevsetResolutionError::AmbiguousChangeIdPrefix(prefix.reverse_hex()),
2817 ),
2818 PrefixResolution::SingleMatch(ids) => Ok(Some(ids)),
2819 PrefixResolution::NoMatch => Ok(None),
2820 }
2821 }
2822}
2823
2824impl PartialSymbolResolver for ChangePrefixResolver<'_> {
2825 fn resolve_symbol(
2826 &self,
2827 repo: &dyn Repo,
2828 symbol: &str,
2829 ) -> Result<Option<CommitId>, RevsetResolutionError> {
2830 let (change_id, offset) = if let Some((prefix, suffix)) = symbol.split_once('/') {
2831 if prefix.is_empty() || suffix.is_empty() {
2832 return Ok(None);
2833 }
2834 let Ok(offset) = suffix.parse() else {
2835 return Ok(None);
2836 };
2837 (prefix, Some(offset))
2838 } else {
2839 (symbol, None)
2840 };
2841 let Some(prefix) = HexPrefix::try_from_reverse_hex(change_id) else {
2842 return Ok(None);
2843 };
2844 let Some(targets) = self.try_resolve(repo, &prefix)? else {
2845 return Ok(None);
2846 };
2847 if let Some(offset) = offset {
2848 return Ok(targets.at_offset(offset).cloned());
2849 }
2850 match targets.visible_with_offsets().at_most_one() {
2851 Ok(maybe_resolved) => Ok(maybe_resolved.map(|(_, target)| target.clone())),
2852 Err(visible_targets) => Err(RevsetResolutionError::DivergentChangeId {
2853 symbol: change_id.to_owned(),
2854 visible_targets: visible_targets
2855 .map(|(i, target)| (i, target.clone()))
2856 .collect_vec(),
2857 }),
2858 }
2859 }
2860}
2861
2862pub trait SymbolResolverExtension: Send + Sync {
2869 fn new_resolvers<'a>(
2874 &self,
2875 context_repo: &'a dyn Repo,
2876 ) -> Vec<Box<dyn PartialSymbolResolver + 'a>>;
2877}
2878
2879pub struct SymbolResolver<'a> {
2882 commit_id_resolver: CommitPrefixResolver<'a>,
2883 change_id_resolver: ChangePrefixResolver<'a>,
2884 extensions: Vec<Box<dyn PartialSymbolResolver + 'a>>,
2885}
2886
2887impl<'a> SymbolResolver<'a> {
2888 pub fn new(
2891 context_repo: &'a dyn Repo,
2892 extensions: &[impl AsRef<dyn SymbolResolverExtension>],
2893 ) -> Self {
2894 SymbolResolver {
2895 commit_id_resolver: CommitPrefixResolver {
2896 context_repo,
2897 context: None,
2898 },
2899 change_id_resolver: ChangePrefixResolver {
2900 context_repo,
2901 context: None,
2902 },
2903 extensions: extensions
2904 .iter()
2905 .flat_map(|ext| ext.as_ref().new_resolvers(context_repo))
2906 .collect(),
2907 }
2908 }
2909
2910 pub fn with_id_prefix_context(mut self, id_prefix_context: &'a IdPrefixContext) -> Self {
2911 self.commit_id_resolver.context = Some(id_prefix_context);
2912 self.change_id_resolver.context = Some(id_prefix_context);
2913 self
2914 }
2915
2916 fn partial_resolvers(&self) -> impl Iterator<Item = &(dyn PartialSymbolResolver + 'a)> {
2917 let prefix_resolvers: [&dyn PartialSymbolResolver; 2] =
2918 [&self.commit_id_resolver, &self.change_id_resolver];
2919 itertools::chain!(
2920 DEFAULT_RESOLVERS.iter().copied(),
2921 prefix_resolvers,
2922 self.extensions.iter().map(|e| e.as_ref())
2923 )
2924 }
2925
2926 pub fn resolve_symbol(
2928 &self,
2929 repo: &dyn Repo,
2930 symbol: &str,
2931 ) -> Result<CommitId, RevsetResolutionError> {
2932 if symbol.is_empty() {
2933 return Err(RevsetResolutionError::EmptyString);
2934 }
2935
2936 for partial_resolver in self.partial_resolvers() {
2937 if let Some(id) = partial_resolver.resolve_symbol(repo, symbol)? {
2938 return Ok(id);
2939 }
2940 }
2941
2942 Err(make_no_such_symbol_error(repo, format_symbol(symbol)))
2943 }
2944}
2945
2946fn resolve_commit_ref(
2947 repo: &dyn Repo,
2948 commit_ref: &RevsetCommitRef,
2949 symbol_resolver: &SymbolResolver,
2950) -> Result<Vec<CommitId>, RevsetResolutionError> {
2951 match commit_ref {
2952 RevsetCommitRef::Symbol(symbol) => {
2953 let commit_id = symbol_resolver.resolve_symbol(repo, symbol)?;
2954 Ok(vec![commit_id])
2955 }
2956 RevsetCommitRef::RemoteSymbol(symbol) => {
2957 let commit_id = resolve_remote_symbol(repo, symbol.as_ref())?;
2958 Ok(vec![commit_id])
2959 }
2960 RevsetCommitRef::WorkingCopy(name) => {
2961 if let Some(commit_id) = repo.view().get_wc_commit_id(name) {
2962 Ok(vec![commit_id.clone()])
2963 } else {
2964 Err(RevsetResolutionError::WorkspaceMissingWorkingCopy { name: name.clone() })
2965 }
2966 }
2967 RevsetCommitRef::WorkingCopies => {
2968 let wc_commits = repo.view().wc_commit_ids().values().cloned().collect_vec();
2969 Ok(wc_commits)
2970 }
2971 RevsetCommitRef::ChangeId(prefix) => {
2972 let resolver = &symbol_resolver.change_id_resolver;
2973 Ok(resolver
2974 .try_resolve(repo, prefix)?
2975 .and_then(ResolvedChangeTargets::into_visible)
2976 .unwrap_or_else(Vec::new))
2977 }
2978 RevsetCommitRef::CommitId(prefix) => {
2979 let resolver = &symbol_resolver.commit_id_resolver;
2980 Ok(resolver.try_resolve(repo, prefix)?.into_iter().collect())
2981 }
2982 RevsetCommitRef::Bookmarks(expression) => {
2983 let commit_ids = repo
2984 .view()
2985 .local_bookmarks_matching(&expression.to_matcher())
2986 .flat_map(|(_, target)| target.added_ids())
2987 .cloned()
2988 .collect();
2989 Ok(commit_ids)
2990 }
2991 RevsetCommitRef::RemoteBookmarks {
2992 symbol,
2993 remote_ref_state,
2994 } => {
2995 let name_matcher = symbol.name.to_matcher();
2996 let remote_matcher = symbol.remote.to_matcher();
2997 let commit_ids = repo
2998 .view()
2999 .remote_bookmarks_matching(&name_matcher, &remote_matcher)
3000 .filter(|(_, remote_ref)| {
3001 remote_ref_state.is_none_or(|state| remote_ref.state == state)
3002 })
3003 .flat_map(|(_, remote_ref)| remote_ref.target.added_ids())
3004 .cloned()
3005 .collect();
3006 Ok(commit_ids)
3007 }
3008 RevsetCommitRef::Tags(expression) => {
3009 let commit_ids = repo
3010 .view()
3011 .local_tags_matching(&expression.to_matcher())
3012 .flat_map(|(_, target)| target.added_ids())
3013 .cloned()
3014 .collect();
3015 Ok(commit_ids)
3016 }
3017 RevsetCommitRef::RemoteTags {
3018 symbol,
3019 remote_ref_state,
3020 } => {
3021 let name_matcher = symbol.name.to_matcher();
3022 let remote_matcher = symbol.remote.to_matcher();
3023 let commit_ids = repo
3024 .view()
3025 .remote_tags_matching(&name_matcher, &remote_matcher)
3026 .filter(|(_, remote_ref)| {
3027 remote_ref_state.is_none_or(|state| remote_ref.state == state)
3028 })
3029 .flat_map(|(_, remote_ref)| remote_ref.target.added_ids())
3030 .cloned()
3031 .collect();
3032 Ok(commit_ids)
3033 }
3034 }
3035}
3036
3037struct ExpressionSymbolResolver<'a, 'b> {
3039 base_repo: &'a dyn Repo,
3040 repo_stack: Vec<Arc<ReadonlyRepo>>,
3041 symbol_resolver: &'a SymbolResolver<'b>,
3042}
3043
3044impl<'a, 'b> ExpressionSymbolResolver<'a, 'b> {
3045 fn new(base_repo: &'a dyn Repo, symbol_resolver: &'a SymbolResolver<'b>) -> Self {
3046 Self {
3047 base_repo,
3048 repo_stack: vec![],
3049 symbol_resolver,
3050 }
3051 }
3052
3053 fn repo(&self) -> &dyn Repo {
3054 self.repo_stack
3055 .last()
3056 .map_or(self.base_repo, |repo| repo.as_ref())
3057 }
3058}
3059
3060impl ExpressionStateFolder<UserExpressionState, ResolvedExpressionState>
3061 for ExpressionSymbolResolver<'_, '_>
3062{
3063 type Error = RevsetResolutionError;
3064
3065 fn fold_expression(
3066 &mut self,
3067 expression: &UserRevsetExpression,
3068 ) -> Result<Arc<ResolvedRevsetExpression>, Self::Error> {
3069 match expression {
3070 RevsetExpression::Present(candidates) => {
3072 self.fold_expression(candidates).or_else(|err| match err {
3073 RevsetResolutionError::NoSuchRevision { .. }
3074 | RevsetResolutionError::WorkspaceMissingWorkingCopy { .. } => {
3075 Ok(RevsetExpression::none())
3076 }
3077 RevsetResolutionError::EmptyString
3078 | RevsetResolutionError::AmbiguousCommitIdPrefix(_)
3079 | RevsetResolutionError::AmbiguousChangeIdPrefix(_)
3080 | RevsetResolutionError::DivergentChangeId { .. }
3081 | RevsetResolutionError::ConflictedRef { .. }
3082 | RevsetResolutionError::Backend(_)
3083 | RevsetResolutionError::Other(_) => Err(err),
3084 })
3085 }
3086 _ => fold_child_expression_state(self, expression),
3087 }
3088 }
3089
3090 fn fold_commit_ref(
3091 &mut self,
3092 commit_ref: &RevsetCommitRef,
3093 ) -> Result<Arc<ResolvedRevsetExpression>, Self::Error> {
3094 let commit_ids = resolve_commit_ref(self.repo(), commit_ref, self.symbol_resolver)?;
3095 Ok(RevsetExpression::commits(commit_ids))
3096 }
3097
3098 fn fold_at_operation(
3099 &mut self,
3100 operation: &String,
3101 candidates: &UserRevsetExpression,
3102 ) -> Result<Arc<ResolvedRevsetExpression>, Self::Error> {
3103 let repo = reload_repo_at_operation(self.repo(), operation)?;
3104 self.repo_stack.push(repo);
3105 let candidates = self.fold_expression(candidates)?;
3106 let expression = candidates.within_visibility(self.repo());
3107 self.repo_stack.pop();
3108 Ok(expression)
3109 }
3110}
3111
3112fn resolve_symbols(
3113 repo: &dyn Repo,
3114 expression: &UserRevsetExpression,
3115 symbol_resolver: &SymbolResolver,
3116) -> Result<Arc<ResolvedRevsetExpression>, RevsetResolutionError> {
3117 let mut resolver = ExpressionSymbolResolver::new(repo, symbol_resolver);
3118 resolver.fold_expression(expression)
3119}
3120
3121fn resolve_visibility(
3131 repo: &dyn Repo,
3132 expression: &ResolvedRevsetExpression,
3133) -> ResolvedExpression {
3134 let context = VisibilityResolutionContext {
3135 referenced_commits: &[],
3136 visible_heads: &repo.view().heads().iter().cloned().collect_vec(),
3137 root: repo.store().root_commit_id(),
3138 is_heads_normalized: repo.view().is_heads_normalized(),
3139 };
3140 context.resolve(expression)
3141}
3142
3143#[derive(Clone, Debug)]
3144struct VisibilityResolutionContext<'a> {
3145 referenced_commits: &'a [CommitId],
3146 visible_heads: &'a [CommitId],
3147 root: &'a CommitId,
3148 is_heads_normalized: bool,
3149}
3150
3151impl VisibilityResolutionContext<'_> {
3152 fn resolve(&self, expression: &ResolvedRevsetExpression) -> ResolvedExpression {
3154 match expression {
3155 RevsetExpression::None => ResolvedExpression::Commits(vec![]),
3156 RevsetExpression::All => self.resolve_all(),
3157 RevsetExpression::VisibleHeads => self.resolve_visible_heads(),
3158 RevsetExpression::VisibleHeadsOrReferenced => {
3159 self.resolve_visible_heads_or_referenced()
3160 }
3161 RevsetExpression::Root => self.resolve_root(),
3162 RevsetExpression::Commits(commit_ids) => {
3163 ResolvedExpression::Commits(commit_ids.clone())
3164 }
3165 RevsetExpression::CommitRef(commit_ref) => match *commit_ref {},
3166 RevsetExpression::Ancestors {
3167 heads,
3168 generation,
3169 parents_range,
3170 } => ResolvedExpression::Ancestors {
3171 heads: self.resolve(heads).into(),
3172 generation: generation.clone(),
3173 parents_range: parents_range.clone(),
3174 },
3175 RevsetExpression::Descendants { roots, generation } => ResolvedExpression::DagRange {
3176 roots: self.resolve(roots).into(),
3177 heads: self.resolve_visible_heads_or_referenced().into(),
3178 generation_from_roots: generation.clone(),
3179 },
3180 RevsetExpression::Range {
3181 roots,
3182 heads,
3183 generation,
3184 parents_range,
3185 } => ResolvedExpression::Range {
3186 roots: self.resolve(roots).into(),
3187 heads: self.resolve(heads).into(),
3188 generation: generation.clone(),
3189 parents_range: parents_range.clone(),
3190 },
3191 RevsetExpression::DagRange { roots, heads } => ResolvedExpression::DagRange {
3192 roots: self.resolve(roots).into(),
3193 heads: self.resolve(heads).into(),
3194 generation_from_roots: GENERATION_RANGE_FULL,
3195 },
3196 RevsetExpression::Reachable { sources, domain } => ResolvedExpression::Reachable {
3197 sources: self.resolve(sources).into(),
3198 domain: self.resolve(domain).into(),
3199 },
3200 RevsetExpression::Heads(candidates) => {
3201 ResolvedExpression::Heads(self.resolve(candidates).into())
3202 }
3203 RevsetExpression::HeadsRange {
3204 roots,
3205 heads,
3206 parents_range,
3207 filter,
3208 } => ResolvedExpression::HeadsRange {
3209 roots: self.resolve(roots).into(),
3210 heads: self.resolve(heads).into(),
3211 parents_range: parents_range.clone(),
3212 filter: (!matches!(filter.as_ref(), RevsetExpression::All))
3213 .then(|| self.resolve_predicate(filter)),
3214 },
3215 RevsetExpression::Roots(candidates) => {
3216 ResolvedExpression::Roots(self.resolve(candidates).into())
3217 }
3218 RevsetExpression::Forks => ResolvedExpression::Forks {
3219 heads: self.resolve_visible_heads_or_referenced().into(),
3220 },
3221 RevsetExpression::ForkPoint(expression) => {
3222 ResolvedExpression::ForkPoint(self.resolve(expression).into())
3223 }
3224 RevsetExpression::MergePoint(expression) => ResolvedExpression::MergePoint {
3225 roots: self.resolve(expression).into(),
3226 visible_heads: self.resolve_visible_heads_or_referenced().into(),
3227 },
3228 RevsetExpression::Bisect(expression) => {
3229 ResolvedExpression::Bisect(self.resolve(expression).into())
3230 }
3231 RevsetExpression::Latest { candidates, count } => ResolvedExpression::Latest {
3232 candidates: self.resolve(candidates).into(),
3233 count: *count,
3234 },
3235 RevsetExpression::HasSize { candidates, count } => ResolvedExpression::HasSize {
3236 candidates: self.resolve(candidates).into(),
3237 count: *count,
3238 },
3239 RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_) => {
3240 ResolvedExpression::FilterWithin {
3243 candidates: self.resolve_all().into(),
3244 predicate: self.resolve_predicate(expression),
3245 }
3246 }
3247 RevsetExpression::Divergent => ResolvedExpression::FilterWithin {
3248 candidates: self.resolve_all().into(),
3249 predicate: ResolvedPredicateExpression::Divergent {
3250 visible_heads: self.visible_heads.to_owned(),
3251 },
3252 },
3253 RevsetExpression::AtOperation { operation, .. } => match *operation {},
3254 RevsetExpression::WithinReference {
3255 candidates,
3256 commits,
3257 } => {
3258 let context = VisibilityResolutionContext {
3259 referenced_commits: commits,
3260 visible_heads: self.visible_heads,
3261 root: self.root,
3262 is_heads_normalized: self.is_heads_normalized,
3263 };
3264 context.resolve(candidates)
3265 }
3266 RevsetExpression::WithinVisibility {
3267 candidates,
3268 visible_heads,
3269 } => {
3270 let context = VisibilityResolutionContext {
3271 referenced_commits: self.referenced_commits,
3272 visible_heads,
3273 root: self.root,
3274 is_heads_normalized: self.is_heads_normalized,
3275 };
3276 context.resolve(candidates)
3277 }
3278 RevsetExpression::Coalesce(expression1, expression2) => ResolvedExpression::Coalesce(
3279 self.resolve(expression1).into(),
3280 self.resolve(expression2).into(),
3281 ),
3282 RevsetExpression::Present(candidates) => self.resolve(candidates),
3284 RevsetExpression::NotIn(complement) => ResolvedExpression::Difference(
3285 self.resolve_all().into(),
3286 self.resolve(complement).into(),
3287 ),
3288 RevsetExpression::Union(expression1, expression2) => ResolvedExpression::Union(
3289 self.resolve(expression1).into(),
3290 self.resolve(expression2).into(),
3291 ),
3292 RevsetExpression::Intersection(expression1, expression2) => {
3293 match expression2.as_ref() {
3294 RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_) => {
3295 ResolvedExpression::FilterWithin {
3296 candidates: self.resolve(expression1).into(),
3297 predicate: self.resolve_predicate(expression2),
3298 }
3299 }
3300 _ => ResolvedExpression::Intersection(
3301 self.resolve(expression1).into(),
3302 self.resolve(expression2).into(),
3303 ),
3304 }
3305 }
3306 RevsetExpression::Difference(expression1, expression2) => {
3307 ResolvedExpression::Difference(
3308 self.resolve(expression1).into(),
3309 self.resolve(expression2).into(),
3310 )
3311 }
3312 }
3313 }
3314
3315 fn resolve_all(&self) -> ResolvedExpression {
3316 ResolvedExpression::Ancestors {
3317 heads: self.resolve_visible_heads_or_referenced().into(),
3318 generation: GENERATION_RANGE_FULL,
3319 parents_range: PARENTS_RANGE_FULL,
3320 }
3321 }
3322
3323 fn resolve_visible_heads(&self) -> ResolvedExpression {
3324 let visible_heads = ResolvedExpression::Commits(self.visible_heads.to_owned());
3325 if self.is_heads_normalized {
3326 visible_heads
3327 } else {
3328 ResolvedExpression::Heads(visible_heads.into())
3329 }
3330 }
3331
3332 fn resolve_visible_heads_or_referenced(&self) -> ResolvedExpression {
3333 let commits = itertools::chain(self.referenced_commits, self.visible_heads)
3338 .cloned()
3339 .collect();
3340 ResolvedExpression::Commits(commits)
3341 }
3342
3343 fn resolve_root(&self) -> ResolvedExpression {
3344 ResolvedExpression::Commits(vec![self.root.to_owned()])
3345 }
3346
3347 fn resolve_predicate(
3352 &self,
3353 expression: &ResolvedRevsetExpression,
3354 ) -> ResolvedPredicateExpression {
3355 match expression {
3356 RevsetExpression::None
3357 | RevsetExpression::All
3358 | RevsetExpression::VisibleHeads
3359 | RevsetExpression::VisibleHeadsOrReferenced
3360 | RevsetExpression::Root
3361 | RevsetExpression::Commits(_)
3362 | RevsetExpression::CommitRef(_)
3363 | RevsetExpression::Ancestors { .. }
3364 | RevsetExpression::Descendants { .. }
3365 | RevsetExpression::Range { .. }
3366 | RevsetExpression::DagRange { .. }
3367 | RevsetExpression::Reachable { .. }
3368 | RevsetExpression::Heads(_)
3369 | RevsetExpression::HeadsRange { .. }
3370 | RevsetExpression::Roots(_)
3371 | RevsetExpression::Forks
3372 | RevsetExpression::ForkPoint(_)
3373 | RevsetExpression::MergePoint(_)
3374 | RevsetExpression::Bisect(_)
3375 | RevsetExpression::HasSize { .. }
3376 | RevsetExpression::Latest { .. } => {
3377 ResolvedPredicateExpression::Set(self.resolve(expression).into())
3378 }
3379 RevsetExpression::Filter(predicate) => {
3380 ResolvedPredicateExpression::Filter(predicate.clone())
3381 }
3382 RevsetExpression::AsFilter(candidates) => self.resolve_predicate(candidates),
3383 RevsetExpression::Divergent => ResolvedPredicateExpression::Divergent {
3384 visible_heads: self.visible_heads.to_owned(),
3385 },
3386 RevsetExpression::AtOperation { operation, .. } => match *operation {},
3387 RevsetExpression::WithinReference { .. }
3389 | RevsetExpression::WithinVisibility { .. } => {
3390 ResolvedPredicateExpression::Set(self.resolve(expression).into())
3391 }
3392 RevsetExpression::Coalesce(_, _) => {
3393 ResolvedPredicateExpression::Set(self.resolve(expression).into())
3394 }
3395 RevsetExpression::Present(candidates) => self.resolve_predicate(candidates),
3397 RevsetExpression::NotIn(complement) => {
3398 ResolvedPredicateExpression::NotIn(self.resolve_predicate(complement).into())
3399 }
3400 RevsetExpression::Union(expression1, expression2) => {
3401 let predicate1 = self.resolve_predicate(expression1);
3402 let predicate2 = self.resolve_predicate(expression2);
3403 ResolvedPredicateExpression::Union(predicate1.into(), predicate2.into())
3404 }
3405 RevsetExpression::Intersection(expression1, expression2) => {
3406 let predicate1 = self.resolve_predicate(expression1);
3407 let predicate2 = self.resolve_predicate(expression2);
3408 ResolvedPredicateExpression::Intersection(predicate1.into(), predicate2.into())
3409 }
3410 RevsetExpression::Difference(expression1, expression2) => {
3411 let predicate1 = self.resolve_predicate(expression1);
3412 let predicate2 = self.resolve_predicate(expression2);
3413 let predicate2 = ResolvedPredicateExpression::NotIn(predicate2.into());
3414 ResolvedPredicateExpression::Intersection(predicate1.into(), predicate2.into())
3415 }
3416 }
3417 }
3418}
3419
3420pub trait Revset: fmt::Debug {
3421 fn stream<'a>(&self) -> LocalBoxStream<'a, Result<CommitId, RevsetEvaluationError>>
3424 where
3425 Self: 'a;
3426
3427 fn commit_change_ids<'a>(
3429 &self,
3430 ) -> LocalBoxStream<'a, Result<(CommitId, ChangeId), RevsetEvaluationError>>
3431 where
3432 Self: 'a;
3433
3434 fn stream_graph<'a>(
3437 &self,
3438 ) -> LocalBoxStream<'a, Result<GraphNode<CommitId>, RevsetEvaluationError>>
3439 where
3440 Self: 'a;
3441
3442 fn is_empty(&self) -> Result<bool, RevsetEvaluationError>;
3444
3445 fn count_estimate(&self) -> Result<(usize, Option<usize>), RevsetEvaluationError>;
3450
3451 fn containing_fn<'a>(&self) -> Box<RevsetContainingFn<'a>>
3457 where
3458 Self: 'a;
3459}
3460
3461pub type RevsetContainingFn<'a> =
3463 dyn Fn(&CommitId) -> LocalBoxFuture<'a, Result<bool, RevsetEvaluationError>> + 'a;
3464
3465pub trait RevsetStreamExt {
3466 fn commits(
3467 self,
3468 store: &Arc<Store>,
3469 ) -> impl Stream<Item = Result<Commit, RevsetEvaluationError>> + use<'_, Self>;
3470}
3471
3472impl<S: Stream<Item = Result<CommitId, RevsetEvaluationError>>> RevsetStreamExt for S {
3473 fn commits(
3474 self,
3475 store: &Arc<Store>,
3476 ) -> impl Stream<Item = Result<Commit, RevsetEvaluationError>> + use<'_, S> {
3477 self.map(async move |result| {
3478 let commit_id = result?;
3479 let commit = store
3480 .get_commit_async(&commit_id)
3481 .await
3482 .map_err(RevsetEvaluationError::Backend)?;
3483 Ok(commit)
3484 })
3485 .buffered(store.concurrency())
3486 }
3487}
3488
3489pub struct RevsetExtensions {
3491 symbol_resolvers: Vec<Box<dyn SymbolResolverExtension>>,
3492 function_map: HashMap<&'static str, RevsetFunction>,
3493}
3494
3495impl Default for RevsetExtensions {
3496 fn default() -> Self {
3497 Self::new()
3498 }
3499}
3500
3501impl RevsetExtensions {
3502 pub fn new() -> Self {
3503 Self {
3504 symbol_resolvers: vec![],
3505 function_map: BUILTIN_FUNCTION_MAP.clone(),
3506 }
3507 }
3508
3509 pub fn symbol_resolvers(&self) -> &[Box<dyn SymbolResolverExtension>] {
3510 &self.symbol_resolvers
3511 }
3512
3513 pub fn add_symbol_resolver(&mut self, symbol_resolver: Box<dyn SymbolResolverExtension>) {
3514 self.symbol_resolvers.push(symbol_resolver);
3515 }
3516
3517 pub fn add_custom_function(&mut self, name: &'static str, func: RevsetFunction) {
3518 match self.function_map.entry(name) {
3519 hash_map::Entry::Occupied(_) => {
3520 panic!("Conflict registering revset function '{name}'")
3521 }
3522 hash_map::Entry::Vacant(v) => v.insert(func),
3523 };
3524 }
3525}
3526
3527#[derive(Clone)]
3529pub struct RevsetParseContext<'a> {
3530 pub aliases_map: &'a RevsetAliasesMap,
3531 pub local_variables: HashMap<&'a str, ExpressionNode<'a>>,
3532 pub user_email: &'a str,
3533 pub date_pattern_context: DatePatternContext,
3534 pub default_ignored_remote: Option<&'a RemoteName>,
3536 pub fileset_aliases_map: &'a FilesetAliasesMap,
3537 pub extensions: &'a RevsetExtensions,
3538 pub workspace: Option<RevsetWorkspaceContext<'a>>,
3539}
3540
3541impl<'a> RevsetParseContext<'a> {
3542 fn to_lowering_context(&self) -> LoweringContext<'a> {
3543 let RevsetParseContext {
3544 aliases_map: _,
3545 local_variables: _,
3546 user_email,
3547 date_pattern_context,
3548 default_ignored_remote,
3549 fileset_aliases_map,
3550 extensions,
3551 workspace,
3552 } = *self;
3553 LoweringContext {
3554 user_email,
3555 date_pattern_context,
3556 default_ignored_remote,
3557 fileset_aliases_map,
3558 extensions,
3559 workspace,
3560 }
3561 }
3562}
3563
3564#[derive(Clone)]
3566pub struct LoweringContext<'a> {
3567 user_email: &'a str,
3568 date_pattern_context: DatePatternContext,
3569 default_ignored_remote: Option<&'a RemoteName>,
3570 fileset_aliases_map: &'a FilesetAliasesMap,
3571 extensions: &'a RevsetExtensions,
3572 workspace: Option<RevsetWorkspaceContext<'a>>,
3573}
3574
3575impl<'a> LoweringContext<'a> {
3576 pub fn user_email(&self) -> &'a str {
3577 self.user_email
3578 }
3579
3580 pub fn date_pattern_context(&self) -> &DatePatternContext {
3581 &self.date_pattern_context
3582 }
3583
3584 pub fn fileset_parse_context(&self) -> Option<FilesetParseContext<'_>> {
3585 Some(FilesetParseContext {
3586 aliases_map: self.fileset_aliases_map,
3587 path_converter: self.workspace?.path_converter,
3588 })
3589 }
3590
3591 pub fn symbol_resolvers(&self) -> &'a [impl AsRef<dyn SymbolResolverExtension> + use<>] {
3592 self.extensions.symbol_resolvers()
3593 }
3594}
3595
3596#[derive(Clone, Copy, Debug)]
3598pub struct RevsetWorkspaceContext<'a> {
3599 pub path_converter: &'a RepoPathUiConverter,
3600 pub workspace_name: &'a WorkspaceName,
3601}
3602
3603pub fn format_symbol(literal: &str) -> String {
3609 if revset_parser::is_identifier(literal) {
3610 literal.to_string()
3611 } else {
3612 format_string(literal)
3613 }
3614}
3615
3616pub fn format_remote_symbol(name: &str, remote: &str) -> String {
3618 let name = format_symbol(name);
3619 let remote = format_symbol(remote);
3620 format!("{name}@{remote}")
3621}
3622
3623#[cfg(test)]
3624#[rustversion::attr(
3625 since(1.89),
3626 expect(clippy::cloned_ref_to_slice_refs, reason = "makes tests more readable")
3627)]
3628mod tests {
3629 use std::path::PathBuf;
3630
3631 use assert_matches::assert_matches;
3632
3633 use super::*;
3634 use crate::tests::TestResult;
3635
3636 fn parse(revset_str: &str) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
3637 parse_with_aliases(revset_str, [] as [(&str, &str); 0])
3638 }
3639
3640 fn parse_with_workspace(
3641 revset_str: &str,
3642 workspace_name: &WorkspaceName,
3643 ) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
3644 parse_with_aliases_and_workspace(revset_str, [] as [(&str, &str); 0], workspace_name)
3645 }
3646
3647 fn parse_with_aliases(
3648 revset_str: &str,
3649 aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
3650 ) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
3651 let mut aliases_map = RevsetAliasesMap::new();
3652 for (decl, defn) in aliases {
3653 aliases_map.insert(decl, defn, None)?;
3654 }
3655 let context = RevsetParseContext {
3656 aliases_map: &aliases_map,
3657 local_variables: HashMap::new(),
3658 user_email: "test.user@example.com",
3659 date_pattern_context: chrono::Utc::now().fixed_offset().into(),
3660 default_ignored_remote: Some("ignored".as_ref()),
3661 fileset_aliases_map: &FilesetAliasesMap::new(),
3662 extensions: &RevsetExtensions::default(),
3663 workspace: None,
3664 };
3665 super::parse(&mut RevsetDiagnostics::new(), revset_str, &context)
3666 }
3667
3668 fn parse_with_aliases_and_workspace(
3669 revset_str: &str,
3670 aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
3671 workspace_name: &WorkspaceName,
3672 ) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
3673 let path_converter = RepoPathUiConverter::Fs {
3675 cwd: PathBuf::from("/"),
3676 base: PathBuf::from("/"),
3677 };
3678 let workspace_ctx = RevsetWorkspaceContext {
3679 path_converter: &path_converter,
3680 workspace_name,
3681 };
3682 let mut aliases_map = RevsetAliasesMap::new();
3683 for (decl, defn) in aliases {
3684 aliases_map.insert(decl, defn, None)?;
3685 }
3686 let context = RevsetParseContext {
3687 aliases_map: &aliases_map,
3688 local_variables: HashMap::new(),
3689 user_email: "test.user@example.com",
3690 date_pattern_context: chrono::Utc::now().fixed_offset().into(),
3691 default_ignored_remote: Some("ignored".as_ref()),
3692 fileset_aliases_map: &FilesetAliasesMap::new(),
3693 extensions: &RevsetExtensions::default(),
3694 workspace: Some(workspace_ctx),
3695 };
3696 super::parse(&mut RevsetDiagnostics::new(), revset_str, &context)
3697 }
3698
3699 fn insta_settings() -> insta::Settings {
3700 let mut settings = insta::Settings::clone_current();
3701 for _ in 0..4 {
3704 settings.add_filter(
3705 r"(?x)
3706 \b([A-Z]\w*)\(\n
3707 \s*(.{1,60}),\n
3708 \s*\)",
3709 "$1($2)",
3710 );
3711 }
3712 settings
3713 }
3714
3715 #[test]
3716 #[expect(clippy::redundant_clone)] fn test_revset_expression_building() {
3718 let settings = insta_settings();
3719 let _guard = settings.bind_to_scope();
3720 let current_wc = UserRevsetExpression::working_copy(WorkspaceName::DEFAULT.to_owned());
3721 let foo_symbol = UserRevsetExpression::symbol("foo".to_string());
3722 let bar_symbol = UserRevsetExpression::symbol("bar".to_string());
3723 let baz_symbol = UserRevsetExpression::symbol("baz".to_string());
3724
3725 insta::assert_debug_snapshot!(
3726 current_wc,
3727 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
3728 insta::assert_debug_snapshot!(
3729 current_wc.heads(),
3730 @r#"Heads(CommitRef(WorkingCopy(WorkspaceNameBuf("default"))))"#);
3731 insta::assert_debug_snapshot!(
3732 current_wc.roots(),
3733 @r#"Roots(CommitRef(WorkingCopy(WorkspaceNameBuf("default"))))"#);
3734 insta::assert_debug_snapshot!(
3735 current_wc.parents(), @r#"
3736 Ancestors {
3737 heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3738 generation: 1..2,
3739 parents_range: 0..4294967295,
3740 }
3741 "#);
3742 insta::assert_debug_snapshot!(
3743 current_wc.ancestors(), @r#"
3744 Ancestors {
3745 heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3746 generation: 0..18446744073709551615,
3747 parents_range: 0..4294967295,
3748 }
3749 "#);
3750 insta::assert_debug_snapshot!(
3751 foo_symbol.children(), @r#"
3752 Descendants {
3753 roots: CommitRef(Symbol("foo")),
3754 generation: 1..2,
3755 }
3756 "#);
3757 insta::assert_debug_snapshot!(
3758 foo_symbol.descendants(), @r#"
3759 Descendants {
3760 roots: CommitRef(Symbol("foo")),
3761 generation: 0..18446744073709551615,
3762 }
3763 "#);
3764 insta::assert_debug_snapshot!(
3765 foo_symbol.dag_range_to(¤t_wc), @r#"
3766 DagRange {
3767 roots: CommitRef(Symbol("foo")),
3768 heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3769 }
3770 "#);
3771 insta::assert_debug_snapshot!(
3772 foo_symbol.connected(), @r#"
3773 DagRange {
3774 roots: CommitRef(Symbol("foo")),
3775 heads: CommitRef(Symbol("foo")),
3776 }
3777 "#);
3778 insta::assert_debug_snapshot!(
3779 foo_symbol.range(¤t_wc), @r#"
3780 Range {
3781 roots: CommitRef(Symbol("foo")),
3782 heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3783 generation: 0..18446744073709551615,
3784 parents_range: 0..4294967295,
3785 }
3786 "#);
3787 insta::assert_debug_snapshot!(
3788 foo_symbol.negated(),
3789 @r#"NotIn(CommitRef(Symbol("foo")))"#);
3790 insta::assert_debug_snapshot!(
3791 foo_symbol.union(¤t_wc), @r#"
3792 Union(
3793 CommitRef(Symbol("foo")),
3794 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3795 )
3796 "#);
3797 insta::assert_debug_snapshot!(
3798 UserRevsetExpression::union_all(&[]),
3799 @"None");
3800 insta::assert_debug_snapshot!(
3801 RevsetExpression::union_all(&[current_wc.clone()]),
3802 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
3803 insta::assert_debug_snapshot!(
3804 RevsetExpression::union_all(&[current_wc.clone(), foo_symbol.clone()]),
3805 @r#"
3806 Union(
3807 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3808 CommitRef(Symbol("foo")),
3809 )
3810 "#);
3811 insta::assert_debug_snapshot!(
3812 RevsetExpression::union_all(&[
3813 current_wc.clone(),
3814 foo_symbol.clone(),
3815 bar_symbol.clone(),
3816 ]),
3817 @r#"
3818 Union(
3819 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3820 Union(
3821 CommitRef(Symbol("foo")),
3822 CommitRef(Symbol("bar")),
3823 ),
3824 )
3825 "#);
3826 insta::assert_debug_snapshot!(
3827 RevsetExpression::union_all(&[
3828 current_wc.clone(),
3829 foo_symbol.clone(),
3830 bar_symbol.clone(),
3831 baz_symbol.clone(),
3832 ]),
3833 @r#"
3834 Union(
3835 Union(
3836 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3837 CommitRef(Symbol("foo")),
3838 ),
3839 Union(
3840 CommitRef(Symbol("bar")),
3841 CommitRef(Symbol("baz")),
3842 ),
3843 )
3844 "#);
3845 insta::assert_debug_snapshot!(
3846 foo_symbol.intersection(¤t_wc), @r#"
3847 Intersection(
3848 CommitRef(Symbol("foo")),
3849 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3850 )
3851 "#);
3852 insta::assert_debug_snapshot!(
3853 foo_symbol.minus(¤t_wc), @r#"
3854 Difference(
3855 CommitRef(Symbol("foo")),
3856 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3857 )
3858 "#);
3859 insta::assert_debug_snapshot!(
3860 UserRevsetExpression::coalesce(&[]),
3861 @"None");
3862 insta::assert_debug_snapshot!(
3863 RevsetExpression::coalesce(&[current_wc.clone()]),
3864 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
3865 insta::assert_debug_snapshot!(
3866 RevsetExpression::coalesce(&[current_wc.clone(), foo_symbol.clone()]),
3867 @r#"
3868 Coalesce(
3869 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3870 CommitRef(Symbol("foo")),
3871 )
3872 "#);
3873 insta::assert_debug_snapshot!(
3874 RevsetExpression::coalesce(&[
3875 current_wc.clone(),
3876 foo_symbol.clone(),
3877 bar_symbol.clone(),
3878 ]),
3879 @r#"
3880 Coalesce(
3881 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3882 Coalesce(
3883 CommitRef(Symbol("foo")),
3884 CommitRef(Symbol("bar")),
3885 ),
3886 )
3887 "#);
3888 }
3889
3890 #[test]
3891 fn test_parse_revset() -> TestResult {
3892 let settings = insta_settings();
3893 let _guard = settings.bind_to_scope();
3894 let main_workspace_name = WorkspaceNameBuf::from("main");
3895 let other_workspace_name = WorkspaceNameBuf::from("other");
3896
3897 insta::assert_debug_snapshot!(
3899 parse("@").unwrap_err().kind(),
3900 @"WorkingCopyWithoutWorkspace");
3901 insta::assert_debug_snapshot!(
3902 parse("main@")?,
3903 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
3904 insta::assert_debug_snapshot!(
3905 parse_with_workspace("@", &main_workspace_name)?,
3906 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
3907 insta::assert_debug_snapshot!(
3908 parse_with_workspace("main@", &other_workspace_name)?,
3909 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
3910 insta::assert_debug_snapshot!(
3912 parse("author_name(foo@)").unwrap_err().kind(),
3913 @r#"Expression("Invalid string expression")"#);
3914 insta::assert_debug_snapshot!(
3915 parse(r#"author_name("foo@")"#)?,
3916 @r#"Filter(AuthorName(Pattern(Exact("foo@"))))"#);
3917 insta::assert_debug_snapshot!(
3919 parse("foo")?,
3920 @r#"CommitRef(Symbol("foo"))"#);
3921 insta::assert_debug_snapshot!(
3923 parse("bookmarks()")?,
3924 @r#"CommitRef(Bookmarks(Pattern(Substring(""))))"#);
3925 insta::assert_debug_snapshot!(
3927 parse("tags()")?,
3928 @r#"CommitRef(Tags(Pattern(Substring(""))))"#);
3929 insta::assert_debug_snapshot!(parse("remote_bookmarks()")?, @r#"
3930 CommitRef(
3931 RemoteBookmarks {
3932 symbol: RemoteRefSymbolExpression {
3933 name: Pattern(Substring("")),
3934 remote: NotIn(Pattern(Exact("ignored"))),
3935 },
3936 remote_ref_state: None,
3937 },
3938 )
3939 "#);
3940 insta::assert_debug_snapshot!(parse("tracked_remote_bookmarks()")?, @r#"
3941 CommitRef(
3942 RemoteBookmarks {
3943 symbol: RemoteRefSymbolExpression {
3944 name: Pattern(Substring("")),
3945 remote: NotIn(Pattern(Exact("ignored"))),
3946 },
3947 remote_ref_state: Some(Tracked),
3948 },
3949 )
3950 "#);
3951 insta::assert_debug_snapshot!(parse("untracked_remote_bookmarks()")?, @r#"
3952 CommitRef(
3953 RemoteBookmarks {
3954 symbol: RemoteRefSymbolExpression {
3955 name: Pattern(Substring("")),
3956 remote: NotIn(Pattern(Exact("ignored"))),
3957 },
3958 remote_ref_state: Some(New),
3959 },
3960 )
3961 "#);
3962 insta::assert_debug_snapshot!(parse("remote_tags()")?, @r#"
3963 CommitRef(
3964 RemoteTags {
3965 symbol: RemoteRefSymbolExpression {
3966 name: Pattern(Substring("")),
3967 remote: NotIn(Pattern(Exact("ignored"))),
3968 },
3969 remote_ref_state: None,
3970 },
3971 )
3972 "#);
3973 insta::assert_debug_snapshot!(parse("tracked_remote_tags()")?, @r#"
3974 CommitRef(
3975 RemoteTags {
3976 symbol: RemoteRefSymbolExpression {
3977 name: Pattern(Substring("")),
3978 remote: NotIn(Pattern(Exact("ignored"))),
3979 },
3980 remote_ref_state: Some(Tracked),
3981 },
3982 )
3983 "#);
3984 insta::assert_debug_snapshot!(parse("untracked_remote_tags()")?, @r#"
3985 CommitRef(
3986 RemoteTags {
3987 symbol: RemoteRefSymbolExpression {
3988 name: Pattern(Substring("")),
3989 remote: NotIn(Pattern(Exact("ignored"))),
3990 },
3991 remote_ref_state: Some(New),
3992 },
3993 )
3994 "#);
3995 insta::assert_debug_snapshot!(
3997 parse("'foo'")?,
3998 @r#"CommitRef(Symbol("foo"))"#);
3999 insta::assert_debug_snapshot!(parse("foo-")?, @r#"
4001 Ancestors {
4002 heads: CommitRef(Symbol("foo")),
4003 generation: 1..2,
4004 parents_range: 0..4294967295,
4005 }
4006 "#);
4007 insta::assert_debug_snapshot!(parse("foo+")?, @r#"
4009 Descendants {
4010 roots: CommitRef(Symbol("foo")),
4011 generation: 1..2,
4012 }
4013 "#);
4014 insta::assert_debug_snapshot!(parse("::foo")?, @r#"
4016 Ancestors {
4017 heads: CommitRef(Symbol("foo")),
4018 generation: 0..18446744073709551615,
4019 parents_range: 0..4294967295,
4020 }
4021 "#);
4022 insta::assert_debug_snapshot!(parse("foo::")?, @r#"
4024 Descendants {
4025 roots: CommitRef(Symbol("foo")),
4026 generation: 0..18446744073709551615,
4027 }
4028 "#);
4029 insta::assert_debug_snapshot!(parse("foo::bar")?, @r#"
4031 DagRange {
4032 roots: CommitRef(Symbol("foo")),
4033 heads: CommitRef(Symbol("bar")),
4034 }
4035 "#);
4036 insta::assert_debug_snapshot!(parse("::")?, @"All");
4038 insta::assert_debug_snapshot!(parse("..foo")?, @r#"
4040 Range {
4041 roots: Root,
4042 heads: CommitRef(Symbol("foo")),
4043 generation: 0..18446744073709551615,
4044 parents_range: 0..4294967295,
4045 }
4046 "#);
4047 insta::assert_debug_snapshot!(parse("foo..")?, @r#"
4048 NotIn(
4049 Ancestors {
4050 heads: CommitRef(Symbol("foo")),
4051 generation: 0..18446744073709551615,
4052 parents_range: 0..4294967295,
4053 },
4054 )
4055 "#);
4056 insta::assert_debug_snapshot!(parse("foo..bar")?, @r#"
4057 Range {
4058 roots: CommitRef(Symbol("foo")),
4059 heads: CommitRef(Symbol("bar")),
4060 generation: 0..18446744073709551615,
4061 parents_range: 0..4294967295,
4062 }
4063 "#);
4064 insta::assert_debug_snapshot!(parse("..")?, @"NotIn(Root)");
4066 insta::assert_debug_snapshot!(
4068 parse("~ foo")?,
4069 @r#"NotIn(CommitRef(Symbol("foo")))"#);
4070 insta::assert_debug_snapshot!(parse("foo & bar")?, @r#"
4072 Intersection(
4073 CommitRef(Symbol("foo")),
4074 CommitRef(Symbol("bar")),
4075 )
4076 "#);
4077 insta::assert_debug_snapshot!(parse("foo | bar")?, @r#"
4079 Union(
4080 CommitRef(Symbol("foo")),
4081 CommitRef(Symbol("bar")),
4082 )
4083 "#);
4084 insta::assert_debug_snapshot!(parse("foo ~ bar")?, @r#"
4086 Difference(
4087 CommitRef(Symbol("foo")),
4088 CommitRef(Symbol("bar")),
4089 )
4090 "#);
4091 Ok(())
4092 }
4093
4094 #[test]
4095 fn test_parse_string_pattern() -> TestResult {
4096 let settings = insta_settings();
4097 let _guard = settings.bind_to_scope();
4098
4099 insta::assert_debug_snapshot!(
4100 parse(r#"bookmarks("foo")"#)?,
4101 @r#"CommitRef(Bookmarks(Pattern(Exact("foo"))))"#);
4102 insta::assert_debug_snapshot!(
4103 parse(r#"bookmarks(exact:"foo")"#)?,
4104 @r#"CommitRef(Bookmarks(Pattern(Exact("foo"))))"#);
4105 insta::assert_debug_snapshot!(
4106 parse(r#"bookmarks(substring:"foo")"#)?,
4107 @r#"CommitRef(Bookmarks(Pattern(Substring("foo"))))"#);
4108 insta::assert_debug_snapshot!(
4109 parse(r#"bookmarks(bad:"foo")"#).unwrap_err().kind(),
4110 @r#"Expression("Invalid string pattern")"#);
4111 insta::assert_debug_snapshot!(
4112 parse(r#"bookmarks(exact::"foo")"#).unwrap_err().kind(),
4113 @r#"Expression("Invalid string expression")"#);
4114 insta::assert_debug_snapshot!(
4115 parse(r#"bookmarks(exact:"foo"+)"#).unwrap_err().kind(),
4116 @r#"Expression("Expected string")"#);
4117
4118 insta::assert_debug_snapshot!(
4119 parse(r#"tags("foo")"#)?,
4120 @r#"CommitRef(Tags(Pattern(Exact("foo"))))"#);
4121 insta::assert_debug_snapshot!(
4122 parse(r#"tags(exact:"foo")"#)?,
4123 @r#"CommitRef(Tags(Pattern(Exact("foo"))))"#);
4124 insta::assert_debug_snapshot!(
4125 parse(r#"tags(substring:"foo")"#)?,
4126 @r#"CommitRef(Tags(Pattern(Substring("foo"))))"#);
4127 insta::assert_debug_snapshot!(
4128 parse(r#"tags(bad:"foo")"#).unwrap_err().kind(),
4129 @r#"Expression("Invalid string pattern")"#);
4130 insta::assert_debug_snapshot!(
4131 parse(r#"tags(exact::"foo")"#).unwrap_err().kind(),
4132 @r#"Expression("Invalid string expression")"#);
4133 insta::assert_debug_snapshot!(
4134 parse(r#"tags(exact:"foo"+)"#).unwrap_err().kind(),
4135 @r#"Expression("Expected string")"#);
4136
4137 assert_matches!(
4139 parse(r#"(exact:"foo")"#).unwrap_err().kind(),
4140 RevsetParseErrorKind::NotInfixOperator { .. }
4141 );
4142 Ok(())
4143 }
4144
4145 #[test]
4146 fn test_parse_compound_string_expression() -> TestResult {
4147 let settings = insta_settings();
4148 let _guard = settings.bind_to_scope();
4149
4150 insta::assert_debug_snapshot!(
4151 parse(r#"tags(~a)"#)?,
4152 @r#"
4153 CommitRef(
4154 Tags(NotIn(Pattern(Exact("a")))),
4155 )
4156 "#);
4157 insta::assert_debug_snapshot!(
4158 parse(r#"tags(a|b&c)"#)?,
4159 @r#"
4160 CommitRef(
4161 Tags(
4162 Union(
4163 Pattern(Exact("a")),
4164 Intersection(
4165 Pattern(Exact("b")),
4166 Pattern(Exact("c")),
4167 ),
4168 ),
4169 ),
4170 )
4171 "#);
4172 insta::assert_debug_snapshot!(
4173 parse(r#"tags(a|b|c)"#)?,
4174 @r#"
4175 CommitRef(
4176 Tags(
4177 Union(
4178 Pattern(Exact("a")),
4179 Union(
4180 Pattern(Exact("b")),
4181 Pattern(Exact("c")),
4182 ),
4183 ),
4184 ),
4185 )
4186 "#);
4187 insta::assert_debug_snapshot!(
4188 parse(r#"tags(a~(b|c))"#)?,
4189 @r#"
4190 CommitRef(
4191 Tags(
4192 Intersection(
4193 Pattern(Exact("a")),
4194 NotIn(
4195 Union(
4196 Pattern(Exact("b")),
4197 Pattern(Exact("c")),
4198 ),
4199 ),
4200 ),
4201 ),
4202 )
4203 "#);
4204 Ok(())
4205 }
4206
4207 #[test]
4208 fn test_parse_revset_function() -> TestResult {
4209 let settings = insta_settings();
4210 let _guard = settings.bind_to_scope();
4211
4212 insta::assert_debug_snapshot!(
4213 parse("parents(foo)")?, @r#"
4214 Ancestors {
4215 heads: CommitRef(Symbol("foo")),
4216 generation: 1..2,
4217 parents_range: 0..4294967295,
4218 }
4219 "#);
4220 insta::assert_debug_snapshot!(
4221 parse("parents(\"foo\")")?, @r#"
4222 Ancestors {
4223 heads: CommitRef(Symbol("foo")),
4224 generation: 1..2,
4225 parents_range: 0..4294967295,
4226 }
4227 "#);
4228 insta::assert_debug_snapshot!(
4229 parse("ancestors(parents(foo))")?, @r#"
4230 Ancestors {
4231 heads: Ancestors {
4232 heads: CommitRef(Symbol("foo")),
4233 generation: 1..2,
4234 parents_range: 0..4294967295,
4235 },
4236 generation: 0..18446744073709551615,
4237 parents_range: 0..4294967295,
4238 }
4239 "#);
4240 insta::assert_debug_snapshot!(
4241 parse("parents(foo, bar, baz)").unwrap_err().kind(), @r#"
4242 InvalidFunctionArguments {
4243 name: "parents",
4244 message: "Expected 1 to 2 arguments",
4245 }
4246 "#);
4247 insta::assert_debug_snapshot!(
4248 parse("parents(foo, 2)")?, @r#"
4249 Ancestors {
4250 heads: CommitRef(Symbol("foo")),
4251 generation: 2..3,
4252 parents_range: 0..4294967295,
4253 }
4254 "#);
4255 insta::assert_debug_snapshot!(
4256 parse("root()")?,
4257 @"Root");
4258 assert!(parse("root(a)").is_err());
4259 insta::assert_debug_snapshot!(
4260 parse(r#"description("")"#)?,
4261 @r#"Filter(Description(Pattern(Exact(""))))"#);
4262 insta::assert_debug_snapshot!(
4263 parse("description(foo)")?,
4264 @r#"Filter(Description(Pattern(Exact("foo"))))"#);
4265 insta::assert_debug_snapshot!(
4266 parse("description(visible_heads())").unwrap_err().kind(),
4267 @r#"Expression("Invalid string expression")"#);
4268 insta::assert_debug_snapshot!(
4269 parse("description(\"(foo)\")")?,
4270 @r#"Filter(Description(Pattern(Exact("(foo)"))))"#);
4271 assert!(parse("mine(foo)").is_err());
4272 insta::assert_debug_snapshot!(
4273 parse_with_workspace("empty()", WorkspaceName::DEFAULT)?,
4274 @"NotIn(Filter(File(All)))");
4275 assert!(parse_with_workspace("empty(foo)", WorkspaceName::DEFAULT).is_err());
4276 assert!(parse_with_workspace("file()", WorkspaceName::DEFAULT).is_err());
4277 insta::assert_debug_snapshot!(
4278 parse_with_workspace("files(foo)", WorkspaceName::DEFAULT)?,
4279 @r#"Filter(File(Pattern(PrefixPath("foo"))))"#);
4280 insta::assert_debug_snapshot!(
4281 parse_with_workspace("files(all())", WorkspaceName::DEFAULT)?,
4282 @"Filter(File(All))");
4283 insta::assert_debug_snapshot!(
4284 parse_with_workspace(r#"files(file:"foo")"#, WorkspaceName::DEFAULT)?,
4285 @r#"Filter(File(Pattern(FilePath("foo"))))"#);
4286 insta::assert_debug_snapshot!(
4287 parse_with_workspace("files(foo|bar&baz)", WorkspaceName::DEFAULT)?, @r#"
4288 Filter(
4289 File(
4290 UnionAll(
4291 [
4292 Pattern(PrefixPath("foo")),
4293 Intersection(
4294 Pattern(PrefixPath("bar")),
4295 Pattern(PrefixPath("baz")),
4296 ),
4297 ],
4298 ),
4299 ),
4300 )
4301 "#);
4302 insta::assert_debug_snapshot!(
4303 parse_with_workspace(r#"files(~(foo))"#, WorkspaceName::DEFAULT)?,
4304 @r#"
4305 Filter(
4306 File(
4307 Difference(
4308 All,
4309 Pattern(PrefixPath("foo")),
4310 ),
4311 ),
4312 )
4313 "#);
4314 insta::assert_debug_snapshot!(parse("signed()")?, @"Filter(Signed)");
4315 Ok(())
4316 }
4317
4318 #[test]
4319 fn test_parse_revset_change_commit_id_functions() -> TestResult {
4320 let settings = insta_settings();
4321 let _guard = settings.bind_to_scope();
4322
4323 insta::assert_debug_snapshot!(
4324 parse("change_id(z)")?,
4325 @r#"CommitRef(ChangeId(HexPrefix("0")))"#);
4326 insta::assert_debug_snapshot!(
4327 parse("change_id('zk')")?,
4328 @r#"CommitRef(ChangeId(HexPrefix("0f")))"#);
4329 insta::assert_debug_snapshot!(
4330 parse("change_id(01234)").unwrap_err().kind(),
4331 @r#"Expression("Invalid change ID prefix")"#);
4332
4333 insta::assert_debug_snapshot!(
4334 parse("commit_id(0)")?,
4335 @r#"CommitRef(CommitId(HexPrefix("0")))"#);
4336 insta::assert_debug_snapshot!(
4337 parse("commit_id('0f')")?,
4338 @r#"CommitRef(CommitId(HexPrefix("0f")))"#);
4339 insta::assert_debug_snapshot!(
4340 parse("commit_id(xyzzy)").unwrap_err().kind(),
4341 @r#"Expression("Invalid commit ID prefix")"#);
4342 Ok(())
4343 }
4344
4345 #[test]
4346 fn test_parse_revset_author_committer_functions() -> TestResult {
4347 let settings = insta_settings();
4348 let _guard = settings.bind_to_scope();
4349
4350 insta::assert_debug_snapshot!(
4351 parse("author(foo)")?, @r#"
4352 Union(
4353 Filter(AuthorName(Pattern(Exact("foo")))),
4354 Filter(AuthorEmail(Pattern(Exact("foo")))),
4355 )
4356 "#);
4357 insta::assert_debug_snapshot!(
4358 parse("author_name(foo)")?,
4359 @r#"Filter(AuthorName(Pattern(Exact("foo"))))"#);
4360 insta::assert_debug_snapshot!(
4361 parse("author_email(foo)")?,
4362 @r#"Filter(AuthorEmail(Pattern(Exact("foo"))))"#);
4363
4364 insta::assert_debug_snapshot!(
4365 parse("committer(foo)")?, @r#"
4366 Union(
4367 Filter(CommitterName(Pattern(Exact("foo")))),
4368 Filter(CommitterEmail(Pattern(Exact("foo")))),
4369 )
4370 "#);
4371 insta::assert_debug_snapshot!(
4372 parse("committer_name(foo)")?,
4373 @r#"Filter(CommitterName(Pattern(Exact("foo"))))"#);
4374 insta::assert_debug_snapshot!(
4375 parse("committer_email(foo)")?,
4376 @r#"Filter(CommitterEmail(Pattern(Exact("foo"))))"#);
4377
4378 insta::assert_debug_snapshot!(
4379 parse("mine()")?,
4380 @r#"Filter(AuthorEmail(Pattern(ExactI("test.user@example.com"))))"#);
4381 Ok(())
4382 }
4383
4384 #[test]
4385 fn test_parse_revset_keyword_arguments() -> TestResult {
4386 let settings = insta_settings();
4387 let _guard = settings.bind_to_scope();
4388
4389 insta::assert_debug_snapshot!(
4390 parse("remote_bookmarks(remote=foo)")?, @r#"
4391 CommitRef(
4392 RemoteBookmarks {
4393 symbol: RemoteRefSymbolExpression {
4394 name: Pattern(Substring("")),
4395 remote: Pattern(Exact("foo")),
4396 },
4397 remote_ref_state: None,
4398 },
4399 )
4400 "#);
4401 insta::assert_debug_snapshot!(
4402 parse("remote_bookmarks(foo, remote=bar)")?, @r#"
4403 CommitRef(
4404 RemoteBookmarks {
4405 symbol: RemoteRefSymbolExpression {
4406 name: Pattern(Exact("foo")),
4407 remote: Pattern(Exact("bar")),
4408 },
4409 remote_ref_state: None,
4410 },
4411 )
4412 "#);
4413 insta::assert_debug_snapshot!(
4414 parse("tracked_remote_bookmarks(foo, remote=bar)")?, @r#"
4415 CommitRef(
4416 RemoteBookmarks {
4417 symbol: RemoteRefSymbolExpression {
4418 name: Pattern(Exact("foo")),
4419 remote: Pattern(Exact("bar")),
4420 },
4421 remote_ref_state: Some(Tracked),
4422 },
4423 )
4424 "#);
4425 insta::assert_debug_snapshot!(
4426 parse("untracked_remote_bookmarks(foo, remote=bar)")?, @r#"
4427 CommitRef(
4428 RemoteBookmarks {
4429 symbol: RemoteRefSymbolExpression {
4430 name: Pattern(Exact("foo")),
4431 remote: Pattern(Exact("bar")),
4432 },
4433 remote_ref_state: Some(New),
4434 },
4435 )
4436 "#);
4437 insta::assert_debug_snapshot!(
4438 parse(r#"remote_bookmarks(remote=foo, bar)"#).unwrap_err().kind(),
4439 @r#"
4440 InvalidFunctionArguments {
4441 name: "remote_bookmarks",
4442 message: "Positional argument follows keyword argument",
4443 }
4444 "#);
4445 insta::assert_debug_snapshot!(
4446 parse(r#"remote_bookmarks("", foo, remote=bar)"#).unwrap_err().kind(),
4447 @r#"
4448 InvalidFunctionArguments {
4449 name: "remote_bookmarks",
4450 message: "Got multiple values for keyword \"remote\"",
4451 }
4452 "#);
4453 insta::assert_debug_snapshot!(
4454 parse(r#"remote_bookmarks(remote=bar, remote=bar)"#).unwrap_err().kind(),
4455 @r#"
4456 InvalidFunctionArguments {
4457 name: "remote_bookmarks",
4458 message: "Got multiple values for keyword \"remote\"",
4459 }
4460 "#);
4461 insta::assert_debug_snapshot!(
4462 parse(r#"remote_bookmarks(unknown=bar)"#).unwrap_err().kind(),
4463 @r#"
4464 InvalidFunctionArguments {
4465 name: "remote_bookmarks",
4466 message: "Unexpected keyword argument \"unknown\"",
4467 }
4468 "#);
4469 Ok(())
4470 }
4471
4472 #[test]
4473 fn test_expand_symbol_alias() -> TestResult {
4474 let settings = insta_settings();
4475 let _guard = settings.bind_to_scope();
4476
4477 insta::assert_debug_snapshot!(
4478 parse_with_aliases("AB|c", [("AB", "a|b")])?, @r#"
4479 Union(
4480 Union(
4481 CommitRef(Symbol("a")),
4482 CommitRef(Symbol("b")),
4483 ),
4484 CommitRef(Symbol("c")),
4485 )
4486 "#);
4487
4488 insta::assert_debug_snapshot!(
4490 parse_with_aliases_and_workspace("files(A)", [("A", "a")], WorkspaceName::DEFAULT)
4491 ?,
4492 @r#"Filter(File(Pattern(PrefixPath("a"))))"#);
4493
4494 insta::assert_debug_snapshot!(
4496 parse_with_aliases("author_name(A)", [("A", "a")])?,
4497 @r#"Filter(AuthorName(Pattern(Exact("a"))))"#);
4498 insta::assert_debug_snapshot!(
4499 parse_with_aliases("author_name(A)", [("A", "exact:a")])?,
4500 @r#"Filter(AuthorName(Pattern(Exact("a"))))"#);
4501 Ok(())
4502 }
4503
4504 #[test]
4505 fn test_expand_function_alias() -> TestResult {
4506 let settings = insta_settings();
4507 let _guard = settings.bind_to_scope();
4508
4509 insta::assert_debug_snapshot!(
4511 parse_with_aliases("F(a)", [("F(x)", "author_name(x)|committer_name(x)")])?,
4512 @r#"
4513 Union(
4514 Filter(AuthorName(Pattern(Exact("a")))),
4515 Filter(CommitterName(Pattern(Exact("a")))),
4516 )
4517 "#);
4518 Ok(())
4519 }
4520
4521 #[test]
4522 fn test_transform_expression() {
4523 let settings = insta_settings();
4524 let _guard = settings.bind_to_scope();
4525
4526 insta::assert_debug_snapshot!(
4528 transform_expression(
4529 &ResolvedRevsetExpression::root(),
4530 |_| ControlFlow::Break(None),
4531 |_| Some(RevsetExpression::none()),
4532 ), @"None");
4533
4534 insta::assert_debug_snapshot!(
4536 transform_expression(
4537 &ResolvedRevsetExpression::root(),
4538 |_| ControlFlow::Break(Some(RevsetExpression::all())),
4539 |_| Some(RevsetExpression::none()),
4540 ), @"Some(All)");
4541
4542 insta::assert_debug_snapshot!(
4544 transform_expression(
4545 &ResolvedRevsetExpression::root().heads(),
4546 |_| ControlFlow::Continue(()),
4547 |x| match x.as_ref() {
4548 RevsetExpression::Root => Some(RevsetExpression::none()),
4549 _ => None,
4550 },
4551 ), @"Some(Heads(None))");
4552
4553 insta::assert_debug_snapshot!(
4555 transform_expression(
4556 &ResolvedRevsetExpression::root().heads(),
4557 |_| ControlFlow::Continue(()),
4558 |x| match x.as_ref() {
4559 RevsetExpression::Heads(y) => Some(y.clone()),
4560 _ => None,
4561 },
4562 ), @"Some(Root)");
4563 }
4564
4565 #[test]
4566 fn test_resolve_referenced_commits() {
4567 let settings = insta_settings();
4568 let _guard = settings.bind_to_scope();
4569
4570 let visibility1 = Arc::new(ResolvedRevsetExpression::WithinVisibility {
4571 candidates: RevsetExpression::commit(CommitId::from_hex("100001")),
4572 visible_heads: vec![CommitId::from_hex("100000")],
4573 });
4574 let visibility2 = Arc::new(ResolvedRevsetExpression::WithinVisibility {
4575 candidates: RevsetExpression::filter(RevsetFilterPredicate::HasConflict),
4576 visible_heads: vec![CommitId::from_hex("200000")],
4577 });
4578 let commit3 = RevsetExpression::commit(CommitId::from_hex("000003"));
4579
4580 insta::assert_debug_snapshot!(
4583 resolve_referenced_commits(&visibility1.intersection(&RevsetExpression::all())), @r#"
4584 Some(
4585 WithinReference {
4586 candidates: Intersection(
4587 WithinVisibility {
4588 candidates: WithinReference {
4589 candidates: Commits(
4590 [
4591 CommitId("100001"),
4592 ],
4593 ),
4594 commits: [
4595 CommitId("100001"),
4596 ],
4597 },
4598 visible_heads: [
4599 CommitId("100000"),
4600 ],
4601 },
4602 All,
4603 ),
4604 commits: [
4605 CommitId("100000"),
4606 CommitId("100001"),
4607 ],
4608 },
4609 )
4610 "#);
4611
4612 insta::assert_debug_snapshot!(
4616 resolve_referenced_commits(
4617 &visibility2
4618 .intersection(&RevsetExpression::all())
4619 .union(&commit3),
4620 ), @r#"
4621 Some(
4622 WithinReference {
4623 candidates: Union(
4624 Intersection(
4625 WithinVisibility {
4626 candidates: WithinReference {
4627 candidates: Filter(HasConflict),
4628 commits: [],
4629 },
4630 visible_heads: [
4631 CommitId("200000"),
4632 ],
4633 },
4634 All,
4635 ),
4636 Commits(
4637 [
4638 CommitId("000003"),
4639 ],
4640 ),
4641 ),
4642 commits: [
4643 CommitId("000003"),
4644 CommitId("200000"),
4645 ],
4646 },
4647 )
4648 "#);
4649
4650 insta::assert_debug_snapshot!(
4652 resolve_referenced_commits(
4653 &visibility1
4654 .union(&visibility2)
4655 .union(&commit3)
4656 .intersection(&RevsetExpression::all())
4657 ), @r#"
4658 Some(
4659 WithinReference {
4660 candidates: Intersection(
4661 Union(
4662 Union(
4663 WithinVisibility {
4664 candidates: WithinReference {
4665 candidates: Commits(
4666 [
4667 CommitId("100001"),
4668 ],
4669 ),
4670 commits: [
4671 CommitId("100001"),
4672 ],
4673 },
4674 visible_heads: [
4675 CommitId("100000"),
4676 ],
4677 },
4678 WithinVisibility {
4679 candidates: WithinReference {
4680 candidates: Filter(HasConflict),
4681 commits: [],
4682 },
4683 visible_heads: [
4684 CommitId("200000"),
4685 ],
4686 },
4687 ),
4688 Commits(
4689 [
4690 CommitId("000003"),
4691 ],
4692 ),
4693 ),
4694 All,
4695 ),
4696 commits: [
4697 CommitId("000003"),
4698 CommitId("100000"),
4699 CommitId("100001"),
4700 CommitId("200000"),
4701 ],
4702 },
4703 )
4704 "#);
4705
4706 insta::assert_debug_snapshot!(
4708 resolve_referenced_commits(&Arc::new(ResolvedRevsetExpression::WithinVisibility {
4709 candidates: visibility1.clone(),
4710 visible_heads: vec![CommitId::from_hex("400000")],
4711 })), @r#"
4712 Some(
4713 WithinReference {
4714 candidates: WithinVisibility {
4715 candidates: WithinReference {
4716 candidates: WithinVisibility {
4717 candidates: WithinReference {
4718 candidates: Commits(
4719 [
4720 CommitId("100001"),
4721 ],
4722 ),
4723 commits: [
4724 CommitId("100001"),
4725 ],
4726 },
4727 visible_heads: [
4728 CommitId("100000"),
4729 ],
4730 },
4731 commits: [
4732 CommitId("100000"),
4733 CommitId("100001"),
4734 ],
4735 },
4736 visible_heads: [
4737 CommitId("400000"),
4738 ],
4739 },
4740 commits: [
4741 CommitId("400000"),
4742 CommitId("100000"),
4743 CommitId("100001"),
4744 ],
4745 },
4746 )
4747 "#);
4748
4749 let resolved = Arc::new(ResolvedRevsetExpression::WithinReference {
4751 candidates: RevsetExpression::none(),
4754 commits: vec![CommitId::from_hex("100000")],
4755 });
4756 insta::assert_debug_snapshot!(
4757 resolve_referenced_commits(&resolved), @"None");
4758 insta::assert_debug_snapshot!(
4759 resolve_referenced_commits(&resolved.intersection(&RevsetExpression::all())), @r#"
4760 Some(
4761 WithinReference {
4762 candidates: Intersection(
4763 WithinReference {
4764 candidates: None,
4765 commits: [
4766 CommitId("100000"),
4767 ],
4768 },
4769 All,
4770 ),
4771 commits: [
4772 CommitId("100000"),
4773 ],
4774 },
4775 )
4776 "#);
4777 insta::assert_debug_snapshot!(
4778 resolve_referenced_commits(&Arc::new(ResolvedRevsetExpression::WithinVisibility {
4779 candidates: resolved.clone(),
4780 visible_heads: vec![CommitId::from_hex("400000")],
4781 })), @r#"
4782 Some(
4783 WithinReference {
4784 candidates: WithinVisibility {
4785 candidates: WithinReference {
4786 candidates: None,
4787 commits: [
4788 CommitId("100000"),
4789 ],
4790 },
4791 visible_heads: [
4792 CommitId("400000"),
4793 ],
4794 },
4795 commits: [
4796 CommitId("400000"),
4797 CommitId("100000"),
4798 ],
4799 },
4800 )
4801 "#);
4802 }
4803
4804 #[test]
4805 fn test_optimize_subtree() -> TestResult {
4806 let settings = insta_settings();
4807 let _guard = settings.bind_to_scope();
4808
4809 insta::assert_debug_snapshot!(
4813 optimize(parse("parents(bookmarks() & all())")?), @r#"
4814 Ancestors {
4815 heads: CommitRef(Bookmarks(Pattern(Substring("")))),
4816 generation: 1..2,
4817 parents_range: 0..4294967295,
4818 }
4819 "#);
4820 insta::assert_debug_snapshot!(
4821 optimize(parse("children(bookmarks() & all())")?), @r#"
4822 Descendants {
4823 roots: CommitRef(Bookmarks(Pattern(Substring("")))),
4824 generation: 1..2,
4825 }
4826 "#);
4827 insta::assert_debug_snapshot!(
4828 optimize(parse("ancestors(bookmarks() & all())")?), @r#"
4829 Ancestors {
4830 heads: CommitRef(Bookmarks(Pattern(Substring("")))),
4831 generation: 0..18446744073709551615,
4832 parents_range: 0..4294967295,
4833 }
4834 "#);
4835 insta::assert_debug_snapshot!(
4836 optimize(parse("descendants(bookmarks() & all())")?), @r#"
4837 Descendants {
4838 roots: CommitRef(Bookmarks(Pattern(Substring("")))),
4839 generation: 0..18446744073709551615,
4840 }
4841 "#);
4842
4843 insta::assert_debug_snapshot!(
4844 optimize(parse("(bookmarks() & all())..(all() & tags())")?), @r#"
4845 Range {
4846 roots: CommitRef(Bookmarks(Pattern(Substring("")))),
4847 heads: CommitRef(Tags(Pattern(Substring("")))),
4848 generation: 0..18446744073709551615,
4849 parents_range: 0..4294967295,
4850 }
4851 "#);
4852 insta::assert_debug_snapshot!(
4853 optimize(parse("(bookmarks() & all())::(all() & tags())")?), @r#"
4854 DagRange {
4855 roots: CommitRef(Bookmarks(Pattern(Substring("")))),
4856 heads: CommitRef(Tags(Pattern(Substring("")))),
4857 }
4858 "#);
4859
4860 insta::assert_debug_snapshot!(
4861 optimize(parse("heads(bookmarks() & all())")?),
4862 @r#"
4863 Heads(
4864 CommitRef(Bookmarks(Pattern(Substring("")))),
4865 )
4866 "#);
4867 insta::assert_debug_snapshot!(
4868 optimize(parse("roots(bookmarks() & all())")?),
4869 @r#"
4870 Roots(
4871 CommitRef(Bookmarks(Pattern(Substring("")))),
4872 )
4873 "#);
4874
4875 insta::assert_debug_snapshot!(
4876 optimize(parse("latest(bookmarks() & all(), 2)")?), @r#"
4877 Latest {
4878 candidates: CommitRef(Bookmarks(Pattern(Substring("")))),
4879 count: 2,
4880 }
4881 "#);
4882
4883 insta::assert_debug_snapshot!(
4884 optimize(parse("present(foo ~ bar)")?), @r#"
4885 Present(
4886 Difference(
4887 CommitRef(Symbol("foo")),
4888 CommitRef(Symbol("bar")),
4889 ),
4890 )
4891 "#);
4892 insta::assert_debug_snapshot!(
4893 optimize(parse("present(bookmarks() & all())")?),
4894 @r#"
4895 Present(
4896 CommitRef(Bookmarks(Pattern(Substring("")))),
4897 )
4898 "#);
4899
4900 insta::assert_debug_snapshot!(
4901 optimize(parse("at_operation(@-, bookmarks() & all())")?), @r#"
4902 AtOperation {
4903 operation: "@-",
4904 candidates: CommitRef(Bookmarks(Pattern(Substring("")))),
4905 }
4906 "#);
4907 insta::assert_debug_snapshot!(
4908 optimize(Arc::new(RevsetExpression::WithinReference {
4909 candidates: parse("bookmarks() & all()")?,
4910 commits: vec![CommitId::from_hex("012345")],
4911 })), @r#"
4912 WithinReference {
4913 candidates: CommitRef(Bookmarks(Pattern(Substring("")))),
4914 commits: [
4915 CommitId("012345"),
4916 ],
4917 }
4918 "#);
4919 insta::assert_debug_snapshot!(
4920 optimize(Arc::new(RevsetExpression::WithinVisibility {
4921 candidates: parse("bookmarks() & all()")?,
4922 visible_heads: vec![CommitId::from_hex("012345")],
4923 })), @r#"
4924 WithinReference {
4925 candidates: WithinVisibility {
4926 candidates: WithinReference {
4927 candidates: CommitRef(Bookmarks(Pattern(Substring("")))),
4928 commits: [],
4929 },
4930 visible_heads: [
4931 CommitId("012345"),
4932 ],
4933 },
4934 commits: [
4935 CommitId("012345"),
4936 ],
4937 }
4938 "#);
4939
4940 insta::assert_debug_snapshot!(
4941 optimize(parse("~bookmarks() & all()")?),
4942 @r#"
4943 NotIn(
4944 CommitRef(Bookmarks(Pattern(Substring("")))),
4945 )
4946 "#);
4947 insta::assert_debug_snapshot!(
4948 optimize(parse("(bookmarks() & all()) | (all() & tags())")?), @r#"
4949 Union(
4950 CommitRef(Bookmarks(Pattern(Substring("")))),
4951 CommitRef(Tags(Pattern(Substring("")))),
4952 )
4953 "#);
4954 insta::assert_debug_snapshot!(
4955 optimize(parse("(bookmarks() & all()) & (all() & tags())")?), @r#"
4956 Intersection(
4957 CommitRef(Bookmarks(Pattern(Substring("")))),
4958 CommitRef(Tags(Pattern(Substring("")))),
4959 )
4960 "#);
4961 insta::assert_debug_snapshot!(
4962 optimize(parse("(bookmarks() & all()) ~ (all() & tags())")?), @r#"
4963 Difference(
4964 CommitRef(Bookmarks(Pattern(Substring("")))),
4965 CommitRef(Tags(Pattern(Substring("")))),
4966 )
4967 "#);
4968 Ok(())
4969 }
4970
4971 #[test]
4972 fn test_optimize_unchanged_subtree() -> TestResult {
4973 fn unwrap_union(
4974 expression: &UserRevsetExpression,
4975 ) -> (&Arc<UserRevsetExpression>, &Arc<UserRevsetExpression>) {
4976 match expression {
4977 RevsetExpression::Union(left, right) => (left, right),
4978 _ => panic!("unexpected expression: {expression:?}"),
4979 }
4980 }
4981
4982 let parsed = parse("foo-")?;
4984 let optimized = optimize(parsed.clone());
4985 assert!(Arc::ptr_eq(&parsed, &optimized));
4986
4987 let parsed = parse("bookmarks() | tags()")?;
4988 let optimized = optimize(parsed.clone());
4989 assert!(Arc::ptr_eq(&parsed, &optimized));
4990
4991 let parsed = parse("bookmarks() & tags()")?;
4992 let optimized = optimize(parsed.clone());
4993 assert!(Arc::ptr_eq(&parsed, &optimized));
4994
4995 let parsed = parse("(bookmarks() & all()) | tags()")?;
4997 let optimized = optimize(parsed.clone());
4998 assert_matches!(
4999 unwrap_union(&optimized).0.as_ref(),
5000 RevsetExpression::CommitRef(RevsetCommitRef::Bookmarks(_))
5001 );
5002 assert!(Arc::ptr_eq(
5003 unwrap_union(&parsed).1,
5004 unwrap_union(&optimized).1
5005 ));
5006
5007 let parsed = parse("bookmarks() | (all() & tags())")?;
5009 let optimized = optimize(parsed.clone());
5010 assert!(Arc::ptr_eq(
5011 unwrap_union(&parsed).0,
5012 unwrap_union(&optimized).0
5013 ));
5014 assert_matches!(
5015 unwrap_union(&optimized).1.as_ref(),
5016 RevsetExpression::CommitRef(RevsetCommitRef::Tags(_))
5017 );
5018 Ok(())
5019 }
5020
5021 #[test]
5022 fn test_optimize_basic() -> TestResult {
5023 let settings = insta_settings();
5024 let _guard = settings.bind_to_scope();
5025
5026 insta::assert_debug_snapshot!(optimize(parse("all() | none()")?), @"All");
5027 insta::assert_debug_snapshot!(optimize(parse("all() & none()")?), @"None");
5028 insta::assert_debug_snapshot!(optimize(parse("root() | all()")?), @"All");
5029 insta::assert_debug_snapshot!(optimize(parse("root() & all()")?), @"Root");
5030 insta::assert_debug_snapshot!(optimize(parse("none() | root()")?), @"Root");
5031 insta::assert_debug_snapshot!(optimize(parse("none() & root()")?), @"None");
5032 insta::assert_debug_snapshot!(optimize(parse("~none()")?), @"All");
5033 insta::assert_debug_snapshot!(optimize(parse("~~none()")?), @"None");
5034 insta::assert_debug_snapshot!(optimize(parse("~all()")?), @"None");
5035 insta::assert_debug_snapshot!(optimize(parse("~~all()")?), @"All");
5036 insta::assert_debug_snapshot!(optimize(parse("~~foo")?), @r#"CommitRef(Symbol("foo"))"#);
5037 insta::assert_debug_snapshot!(
5038 optimize(parse("(root() | none()) & (visible_heads() | ~~all())")?), @"Root");
5039 insta::assert_debug_snapshot!(
5040 optimize(UserRevsetExpression::commits(vec![])), @"None");
5041 insta::assert_debug_snapshot!(
5042 optimize(UserRevsetExpression::commits(vec![]).negated()), @"All");
5043 Ok(())
5044 }
5045
5046 #[test]
5047 fn test_optimize_difference() -> TestResult {
5048 let settings = insta_settings();
5049 let _guard = settings.bind_to_scope();
5050
5051 insta::assert_debug_snapshot!(optimize(parse("foo & ~bar")?), @r#"
5052 Difference(
5053 CommitRef(Symbol("foo")),
5054 CommitRef(Symbol("bar")),
5055 )
5056 "#);
5057 insta::assert_debug_snapshot!(optimize(parse("~foo & bar")?), @r#"
5058 Difference(
5059 CommitRef(Symbol("bar")),
5060 CommitRef(Symbol("foo")),
5061 )
5062 "#);
5063 insta::assert_debug_snapshot!(optimize(parse("~foo & bar & ~baz")?), @r#"
5064 Difference(
5065 Difference(
5066 CommitRef(Symbol("bar")),
5067 CommitRef(Symbol("foo")),
5068 ),
5069 CommitRef(Symbol("baz")),
5070 )
5071 "#);
5072 insta::assert_debug_snapshot!(optimize(parse("(all() & ~foo) & bar")?), @r#"
5073 Difference(
5074 CommitRef(Symbol("bar")),
5075 CommitRef(Symbol("foo")),
5076 )
5077 "#);
5078
5079 insta::assert_debug_snapshot!(
5081 optimize(parse("all() ~ foo")?),
5082 @r#"NotIn(CommitRef(Symbol("foo")))"#);
5083 insta::assert_debug_snapshot!(optimize(parse("foo ~ bar")?), @r#"
5084 Difference(
5085 CommitRef(Symbol("foo")),
5086 CommitRef(Symbol("bar")),
5087 )
5088 "#);
5089 insta::assert_debug_snapshot!(optimize(parse("(all() ~ foo) & bar")?), @r#"
5090 Difference(
5091 CommitRef(Symbol("bar")),
5092 CommitRef(Symbol("foo")),
5093 )
5094 "#);
5095
5096 insta::assert_debug_snapshot!(optimize(parse("::foo & ~::bar")?), @r#"
5098 Range {
5099 roots: CommitRef(Symbol("bar")),
5100 heads: CommitRef(Symbol("foo")),
5101 generation: 0..18446744073709551615,
5102 parents_range: 0..4294967295,
5103 }
5104 "#);
5105 insta::assert_debug_snapshot!(optimize(parse("~::foo & ::bar")?), @r#"
5106 Range {
5107 roots: CommitRef(Symbol("foo")),
5108 heads: CommitRef(Symbol("bar")),
5109 generation: 0..18446744073709551615,
5110 parents_range: 0..4294967295,
5111 }
5112 "#);
5113 insta::assert_debug_snapshot!(optimize(parse("foo..")?), @r#"
5114 Range {
5115 roots: CommitRef(Symbol("foo")),
5116 heads: VisibleHeadsOrReferenced,
5117 generation: 0..18446744073709551615,
5118 parents_range: 0..4294967295,
5119 }
5120 "#);
5121 insta::assert_debug_snapshot!(optimize(parse("foo..bar")?), @r#"
5122 Range {
5123 roots: CommitRef(Symbol("foo")),
5124 heads: CommitRef(Symbol("bar")),
5125 generation: 0..18446744073709551615,
5126 parents_range: 0..4294967295,
5127 }
5128 "#);
5129 insta::assert_debug_snapshot!(optimize(parse("foo.. & ::bar")?), @r#"
5130 Range {
5131 roots: CommitRef(Symbol("foo")),
5132 heads: CommitRef(Symbol("bar")),
5133 generation: 0..18446744073709551615,
5134 parents_range: 0..4294967295,
5135 }
5136 "#);
5137 insta::assert_debug_snapshot!(optimize(parse("foo.. & first_ancestors(bar)")?), @r#"
5138 Range {
5139 roots: CommitRef(Symbol("foo")),
5140 heads: CommitRef(Symbol("bar")),
5141 generation: 0..18446744073709551615,
5142 parents_range: 0..1,
5143 }
5144 "#);
5145
5146 insta::assert_debug_snapshot!(optimize(parse("foo & ~~bar")?), @r#"
5148 Intersection(
5149 CommitRef(Symbol("foo")),
5150 CommitRef(Symbol("bar")),
5151 )
5152 "#);
5153 insta::assert_debug_snapshot!(optimize(parse("foo & ~~~bar")?), @r#"
5154 Difference(
5155 CommitRef(Symbol("foo")),
5156 CommitRef(Symbol("bar")),
5157 )
5158 "#);
5159 insta::assert_debug_snapshot!(optimize(parse("~(all() & ~foo) & bar")?), @r#"
5160 Intersection(
5161 CommitRef(Symbol("foo")),
5162 CommitRef(Symbol("bar")),
5163 )
5164 "#);
5165
5166 insta::assert_debug_snapshot!(optimize(parse("~foo & ~bar")?), @r#"
5168 Difference(
5169 NotIn(CommitRef(Symbol("foo"))),
5170 CommitRef(Symbol("bar")),
5171 )
5172 "#);
5173
5174 insta::assert_debug_snapshot!(optimize(parse("a..b & c..d")?), @r#"
5176 Intersection(
5177 Range {
5178 roots: Union(
5179 CommitRef(Symbol("a")),
5180 CommitRef(Symbol("c")),
5181 ),
5182 heads: CommitRef(Symbol("b")),
5183 generation: 0..18446744073709551615,
5184 parents_range: 0..4294967295,
5185 },
5186 Ancestors {
5187 heads: CommitRef(Symbol("d")),
5188 generation: 0..18446744073709551615,
5189 parents_range: 0..4294967295,
5190 },
5191 )
5192 "#);
5193
5194 insta::assert_debug_snapshot!(optimize(parse("foo..bar ~ first_ancestors(baz)")?), @r#"
5196 Difference(
5197 Range {
5198 roots: CommitRef(Symbol("foo")),
5199 heads: CommitRef(Symbol("bar")),
5200 generation: 0..18446744073709551615,
5201 parents_range: 0..4294967295,
5202 },
5203 Ancestors {
5204 heads: CommitRef(Symbol("baz")),
5205 generation: 0..18446744073709551615,
5206 parents_range: 0..1,
5207 },
5208 )
5209 "#);
5210
5211 insta::assert_debug_snapshot!(optimize(parse("foo ~ ::a & (::b & bar & ::c) & (baz ~ ::d)")?), @r#"
5214 Intersection(
5215 Intersection(
5216 Intersection(
5217 Intersection(
5218 Range {
5219 roots: Union(
5220 CommitRef(Symbol("a")),
5221 CommitRef(Symbol("d")),
5222 ),
5223 heads: CommitRef(Symbol("b")),
5224 generation: 0..18446744073709551615,
5225 parents_range: 0..4294967295,
5226 },
5227 Ancestors {
5228 heads: CommitRef(Symbol("c")),
5229 generation: 0..18446744073709551615,
5230 parents_range: 0..4294967295,
5231 },
5232 ),
5233 CommitRef(Symbol("foo")),
5234 ),
5235 CommitRef(Symbol("bar")),
5236 ),
5237 CommitRef(Symbol("baz")),
5238 )
5239 "#);
5240 Ok(())
5241 }
5242
5243 #[test]
5244 fn test_optimize_not_in_ancestors() -> TestResult {
5245 let settings = insta_settings();
5246 let _guard = settings.bind_to_scope();
5247
5248 insta::assert_debug_snapshot!(optimize(parse("~(::foo)")?), @r#"
5250 Range {
5251 roots: CommitRef(Symbol("foo")),
5252 heads: VisibleHeadsOrReferenced,
5253 generation: 0..18446744073709551615,
5254 parents_range: 0..4294967295,
5255 }
5256 "#);
5257
5258 insta::assert_debug_snapshot!(optimize(parse("~(::foo-)")?), @r#"
5260 Range {
5261 roots: Ancestors {
5262 heads: CommitRef(Symbol("foo")),
5263 generation: 1..2,
5264 parents_range: 0..4294967295,
5265 },
5266 heads: VisibleHeadsOrReferenced,
5267 generation: 0..18446744073709551615,
5268 parents_range: 0..4294967295,
5269 }
5270 "#);
5271 insta::assert_debug_snapshot!(optimize(parse("~(::foo--)")?), @r#"
5272 Range {
5273 roots: Ancestors {
5274 heads: CommitRef(Symbol("foo")),
5275 generation: 2..3,
5276 parents_range: 0..4294967295,
5277 },
5278 heads: VisibleHeadsOrReferenced,
5279 generation: 0..18446744073709551615,
5280 parents_range: 0..4294967295,
5281 }
5282 "#);
5283
5284 insta::assert_debug_snapshot!(optimize(parse("~ancestors(foo, 1)")?), @r#"
5286 NotIn(
5287 Ancestors {
5288 heads: CommitRef(Symbol("foo")),
5289 generation: 0..1,
5290 parents_range: 0..4294967295,
5291 },
5292 )
5293 "#);
5294 insta::assert_debug_snapshot!(optimize(parse("~ancestors(foo-, 1)")?), @r#"
5295 NotIn(
5296 Ancestors {
5297 heads: CommitRef(Symbol("foo")),
5298 generation: 1..2,
5299 parents_range: 0..4294967295,
5300 },
5301 )
5302 "#);
5303 Ok(())
5304 }
5305
5306 #[test]
5307 fn test_optimize_filter_difference() -> TestResult {
5308 let settings = insta_settings();
5309 let _guard = settings.bind_to_scope();
5310
5311 insta::assert_debug_snapshot!(optimize(parse("~empty()")?), @"Filter(File(All))");
5313
5314 insta::assert_debug_snapshot!(
5316 optimize(parse("(author_name(foo) & ~bar) & baz")?), @r#"
5317 Intersection(
5318 Difference(
5319 CommitRef(Symbol("baz")),
5320 CommitRef(Symbol("bar")),
5321 ),
5322 Filter(AuthorName(Pattern(Exact("foo")))),
5323 )
5324 "#);
5325
5326 insta::assert_debug_snapshot!(
5328 optimize(parse("~foo & author_name(bar)")?), @r#"
5329 Intersection(
5330 NotIn(CommitRef(Symbol("foo"))),
5331 Filter(AuthorName(Pattern(Exact("bar")))),
5332 )
5333 "#);
5334 insta::assert_debug_snapshot!(
5335 optimize(parse("~foo & (author_name(bar) | baz)")?), @r#"
5336 Intersection(
5337 NotIn(CommitRef(Symbol("foo"))),
5338 AsFilter(
5339 Union(
5340 Filter(AuthorName(Pattern(Exact("bar")))),
5341 CommitRef(Symbol("baz")),
5342 ),
5343 ),
5344 )
5345 "#);
5346
5347 insta::assert_debug_snapshot!(
5349 optimize(parse("author_name(foo) ~ bar")?), @r#"
5350 Intersection(
5351 NotIn(CommitRef(Symbol("bar"))),
5352 Filter(AuthorName(Pattern(Exact("foo")))),
5353 )
5354 "#);
5355 Ok(())
5356 }
5357
5358 #[test]
5359 fn test_optimize_filter_intersection() -> TestResult {
5360 let settings = insta_settings();
5361 let _guard = settings.bind_to_scope();
5362
5363 insta::assert_debug_snapshot!(
5364 optimize(parse("author_name(foo)")?),
5365 @r#"Filter(AuthorName(Pattern(Exact("foo"))))"#);
5366
5367 insta::assert_debug_snapshot!(optimize(parse("foo & description(bar)")?), @r#"
5368 Intersection(
5369 CommitRef(Symbol("foo")),
5370 Filter(Description(Pattern(Exact("bar")))),
5371 )
5372 "#);
5373 insta::assert_debug_snapshot!(optimize(parse("author_name(foo) & bar")?), @r#"
5374 Intersection(
5375 CommitRef(Symbol("bar")),
5376 Filter(AuthorName(Pattern(Exact("foo")))),
5377 )
5378 "#);
5379 insta::assert_debug_snapshot!(
5380 optimize(parse("author_name(foo) & committer_name(bar)")?), @r#"
5381 AsFilter(
5382 Intersection(
5383 Filter(AuthorName(Pattern(Exact("foo")))),
5384 Filter(CommitterName(Pattern(Exact("bar")))),
5385 ),
5386 )
5387 "#);
5388 insta::assert_debug_snapshot!(optimize(parse("divergent() & foo")?), @r#"
5389 Intersection(
5390 CommitRef(Symbol("foo")),
5391 AsFilter(Divergent),
5392 )
5393 "#);
5394
5395 insta::assert_debug_snapshot!(
5396 optimize(parse("foo & description(bar) & author_name(baz)")?), @r#"
5397 Intersection(
5398 CommitRef(Symbol("foo")),
5399 AsFilter(
5400 Intersection(
5401 Filter(Description(Pattern(Exact("bar")))),
5402 Filter(AuthorName(Pattern(Exact("baz")))),
5403 ),
5404 ),
5405 )
5406 "#);
5407 insta::assert_debug_snapshot!(
5408 optimize(parse("committer_name(foo) & bar & author_name(baz)")?), @r#"
5409 Intersection(
5410 CommitRef(Symbol("bar")),
5411 AsFilter(
5412 Intersection(
5413 Filter(CommitterName(Pattern(Exact("foo")))),
5414 Filter(AuthorName(Pattern(Exact("baz")))),
5415 ),
5416 ),
5417 )
5418 "#);
5419 insta::assert_debug_snapshot!(
5420 optimize(parse_with_workspace(
5421 "committer_name(foo) & files(bar) & baz",
5422 WorkspaceName::DEFAULT)?,
5423 ), @r#"
5424 Intersection(
5425 CommitRef(Symbol("baz")),
5426 AsFilter(
5427 Intersection(
5428 Filter(CommitterName(Pattern(Exact("foo")))),
5429 Filter(File(Pattern(PrefixPath("bar")))),
5430 ),
5431 ),
5432 )
5433 "#);
5434 insta::assert_debug_snapshot!(
5435 optimize(parse_with_workspace(
5436 "committer_name(foo) & files(bar) & author_name(baz)",
5437 WorkspaceName::DEFAULT)?,
5438 ), @r#"
5439 AsFilter(
5440 Intersection(
5441 Intersection(
5442 Filter(CommitterName(Pattern(Exact("foo")))),
5443 Filter(File(Pattern(PrefixPath("bar")))),
5444 ),
5445 Filter(AuthorName(Pattern(Exact("baz")))),
5446 ),
5447 )
5448 "#);
5449 insta::assert_debug_snapshot!(
5450 optimize(parse_with_workspace(
5451 "foo & files(bar) & baz",
5452 WorkspaceName::DEFAULT)?,
5453 ), @r#"
5454 Intersection(
5455 Intersection(
5456 CommitRef(Symbol("foo")),
5457 CommitRef(Symbol("baz")),
5458 ),
5459 Filter(File(Pattern(PrefixPath("bar")))),
5460 )
5461 "#);
5462
5463 insta::assert_debug_snapshot!(
5464 optimize(parse("foo & description(bar) & author_name(baz) & qux")?), @r#"
5465 Intersection(
5466 Intersection(
5467 CommitRef(Symbol("foo")),
5468 CommitRef(Symbol("qux")),
5469 ),
5470 AsFilter(
5471 Intersection(
5472 Filter(Description(Pattern(Exact("bar")))),
5473 Filter(AuthorName(Pattern(Exact("baz")))),
5474 ),
5475 ),
5476 )
5477 "#);
5478 insta::assert_debug_snapshot!(
5479 optimize(parse("foo & description(bar) & parents(author_name(baz)) & qux")?),
5480 @r#"
5481 Intersection(
5482 Intersection(
5483 Intersection(
5484 CommitRef(Symbol("foo")),
5485 Ancestors {
5486 heads: Filter(AuthorName(Pattern(Exact("baz")))),
5487 generation: 1..2,
5488 parents_range: 0..4294967295,
5489 },
5490 ),
5491 CommitRef(Symbol("qux")),
5492 ),
5493 Filter(Description(Pattern(Exact("bar")))),
5494 )
5495 "#);
5496 insta::assert_debug_snapshot!(
5497 optimize(parse("foo & description(bar) & parents(author_name(baz) & qux)")?),
5498 @r#"
5499 Intersection(
5500 Intersection(
5501 CommitRef(Symbol("foo")),
5502 Ancestors {
5503 heads: Intersection(
5504 CommitRef(Symbol("qux")),
5505 Filter(AuthorName(Pattern(Exact("baz")))),
5506 ),
5507 generation: 1..2,
5508 parents_range: 0..4294967295,
5509 },
5510 ),
5511 Filter(Description(Pattern(Exact("bar")))),
5512 )
5513 "#);
5514
5515 insta::assert_debug_snapshot!(
5517 optimize(parse("(a & author_name(A)) & (b & author_name(B)) & (c & author_name(C))")?),
5518 @r#"
5519 Intersection(
5520 Intersection(
5521 Intersection(
5522 CommitRef(Symbol("a")),
5523 CommitRef(Symbol("b")),
5524 ),
5525 CommitRef(Symbol("c")),
5526 ),
5527 AsFilter(
5528 Intersection(
5529 Intersection(
5530 Filter(AuthorName(Pattern(Exact("A")))),
5531 Filter(AuthorName(Pattern(Exact("B")))),
5532 ),
5533 Filter(AuthorName(Pattern(Exact("C")))),
5534 ),
5535 ),
5536 )
5537 "#);
5538 insta::assert_debug_snapshot!(
5539 optimize(parse("(a & author_name(A)) & ((b & author_name(B)) & (c & author_name(C))) & d")?),
5540 @r#"
5541 Intersection(
5542 Intersection(
5543 Intersection(
5544 Intersection(
5545 CommitRef(Symbol("a")),
5546 CommitRef(Symbol("b")),
5547 ),
5548 CommitRef(Symbol("c")),
5549 ),
5550 CommitRef(Symbol("d")),
5551 ),
5552 AsFilter(
5553 Intersection(
5554 Intersection(
5555 Filter(AuthorName(Pattern(Exact("A")))),
5556 Filter(AuthorName(Pattern(Exact("B")))),
5557 ),
5558 Filter(AuthorName(Pattern(Exact("C")))),
5559 ),
5560 ),
5561 )
5562 "#);
5563
5564 insta::assert_debug_snapshot!(
5566 optimize(parse("foo & (all() & description(bar)) & (author_name(baz) & all())")?),
5567 @r#"
5568 Intersection(
5569 CommitRef(Symbol("foo")),
5570 AsFilter(
5571 Intersection(
5572 Filter(Description(Pattern(Exact("bar")))),
5573 Filter(AuthorName(Pattern(Exact("baz")))),
5574 ),
5575 ),
5576 )
5577 "#);
5578
5579 insta::assert_debug_snapshot!(
5581 optimize(parse("author_name(foo) & bar & at_operation(@-, committer_name(baz))")?),
5582 @r#"
5583 Intersection(
5584 Intersection(
5585 CommitRef(Symbol("bar")),
5586 AtOperation {
5587 operation: "@-",
5588 candidates: Filter(CommitterName(Pattern(Exact("baz")))),
5589 },
5590 ),
5591 Filter(AuthorName(Pattern(Exact("foo")))),
5592 )
5593 "#);
5594 Ok(())
5595 }
5596
5597 #[test]
5598 fn test_optimize_filter_subtree() -> TestResult {
5599 let settings = insta_settings();
5600 let _guard = settings.bind_to_scope();
5601
5602 insta::assert_debug_snapshot!(
5603 optimize(parse("(author_name(foo) | bar) & baz")?), @r#"
5604 Intersection(
5605 CommitRef(Symbol("baz")),
5606 AsFilter(
5607 Union(
5608 Filter(AuthorName(Pattern(Exact("foo")))),
5609 CommitRef(Symbol("bar")),
5610 ),
5611 ),
5612 )
5613 "#);
5614
5615 insta::assert_debug_snapshot!(
5617 optimize(parse("merges() & foo | bar")?), @r#"
5618 Union(
5619 Intersection(
5620 CommitRef(Symbol("foo")),
5621 Filter(ParentCount(2..4294967295)),
5622 ),
5623 CommitRef(Symbol("bar")),
5624 )
5625 "#);
5626
5627 insta::assert_debug_snapshot!(
5630 optimize(parse("merges() & foo | conflicts()")?), @r#"
5631 AsFilter(
5632 Union(
5633 Intersection(
5634 CommitRef(Symbol("foo")),
5635 Filter(ParentCount(2..4294967295)),
5636 ),
5637 Filter(HasConflict),
5638 ),
5639 )
5640 "#);
5641
5642 insta::assert_debug_snapshot!(
5644 optimize(parse("foo | conflicts() & merges() & signed()")?), @r#"
5645 AsFilter(
5646 Union(
5647 CommitRef(Symbol("foo")),
5648 Intersection(
5649 Intersection(
5650 Filter(HasConflict),
5651 Filter(ParentCount(2..4294967295)),
5652 ),
5653 Filter(Signed),
5654 ),
5655 ),
5656 )
5657 "#);
5658
5659 insta::assert_debug_snapshot!(
5660 optimize(parse("(foo | committer_name(bar)) & description(baz) & qux")?), @r#"
5661 Intersection(
5662 CommitRef(Symbol("qux")),
5663 AsFilter(
5664 Intersection(
5665 Union(
5666 CommitRef(Symbol("foo")),
5667 Filter(CommitterName(Pattern(Exact("bar")))),
5668 ),
5669 Filter(Description(Pattern(Exact("baz")))),
5670 ),
5671 ),
5672 )
5673 "#);
5674
5675 insta::assert_debug_snapshot!(
5676 optimize(parse(
5677 "(~present(author_name(foo) & description(bar)) | baz) & qux")?), @r#"
5678 Intersection(
5679 CommitRef(Symbol("qux")),
5680 AsFilter(
5681 Union(
5682 NotIn(
5683 Present(
5684 Intersection(
5685 Filter(AuthorName(Pattern(Exact("foo")))),
5686 Filter(Description(Pattern(Exact("bar")))),
5687 ),
5688 ),
5689 ),
5690 CommitRef(Symbol("baz")),
5691 ),
5692 ),
5693 )
5694 "#);
5695
5696 insta::assert_debug_snapshot!(
5698 optimize(parse(
5699 "(a & (author_name(A) | 0)) & (b & (author_name(B) | 1)) & (c & (author_name(C) | 2))")?),
5700 @r#"
5701 Intersection(
5702 Intersection(
5703 Intersection(
5704 CommitRef(Symbol("a")),
5705 CommitRef(Symbol("b")),
5706 ),
5707 CommitRef(Symbol("c")),
5708 ),
5709 AsFilter(
5710 Intersection(
5711 Intersection(
5712 Union(
5713 Filter(AuthorName(Pattern(Exact("A")))),
5714 CommitRef(Symbol("0")),
5715 ),
5716 Union(
5717 Filter(AuthorName(Pattern(Exact("B")))),
5718 CommitRef(Symbol("1")),
5719 ),
5720 ),
5721 Union(
5722 Filter(AuthorName(Pattern(Exact("C")))),
5723 CommitRef(Symbol("2")),
5724 ),
5725 ),
5726 ),
5727 )
5728 "#);
5729
5730 insta::assert_debug_snapshot!(optimize(parse("::foo | ::author_name(bar)")?), @r#"
5732 Ancestors {
5733 heads: HeadsRange {
5734 roots: None,
5735 heads: VisibleHeadsOrReferenced,
5736 parents_range: 0..4294967295,
5737 filter: AsFilter(
5738 Union(
5739 CommitRef(Symbol("foo")),
5740 Filter(AuthorName(Pattern(Exact("bar")))),
5741 ),
5742 ),
5743 },
5744 generation: 0..18446744073709551615,
5745 parents_range: 0..4294967295,
5746 }
5747 "#);
5748 Ok(())
5749 }
5750
5751 #[test]
5752 fn test_optimize_ancestors() -> TestResult {
5753 let settings = insta_settings();
5754 let _guard = settings.bind_to_scope();
5755
5756 insta::assert_debug_snapshot!(optimize(parse("foo--")?), @r#"
5758 Ancestors {
5759 heads: CommitRef(Symbol("foo")),
5760 generation: 2..3,
5761 parents_range: 0..4294967295,
5762 }
5763 "#);
5764 insta::assert_debug_snapshot!(optimize(parse("::(foo---)")?), @r#"
5765 Ancestors {
5766 heads: CommitRef(Symbol("foo")),
5767 generation: 3..18446744073709551615,
5768 parents_range: 0..4294967295,
5769 }
5770 "#);
5771 insta::assert_debug_snapshot!(optimize(parse("(::foo)---")?), @r#"
5772 Ancestors {
5773 heads: CommitRef(Symbol("foo")),
5774 generation: 3..18446744073709551615,
5775 parents_range: 0..4294967295,
5776 }
5777 "#);
5778
5779 insta::assert_debug_snapshot!(optimize(parse("foo---+")?), @r#"
5781 Descendants {
5782 roots: Ancestors {
5783 heads: CommitRef(Symbol("foo")),
5784 generation: 3..4,
5785 parents_range: 0..4294967295,
5786 },
5787 generation: 1..2,
5788 }
5789 "#);
5790
5791 insta::assert_debug_snapshot!(optimize(parse("foo..(bar--)")?), @r#"
5793 Range {
5794 roots: CommitRef(Symbol("foo")),
5795 heads: CommitRef(Symbol("bar")),
5796 generation: 2..18446744073709551615,
5797 parents_range: 0..4294967295,
5798 }
5799 "#);
5800 insta::assert_debug_snapshot!(optimize(parse("(foo--)..(bar---)")?), @r#"
5802 Range {
5803 roots: Ancestors {
5804 heads: CommitRef(Symbol("foo")),
5805 generation: 2..3,
5806 parents_range: 0..4294967295,
5807 },
5808 heads: CommitRef(Symbol("bar")),
5809 generation: 3..18446744073709551615,
5810 parents_range: 0..4294967295,
5811 }
5812 "#);
5813 insta::assert_debug_snapshot!(
5815 optimize(parse("~ancestors(foo, 2) & ::bar")?), @r#"
5816 Difference(
5817 Ancestors {
5818 heads: CommitRef(Symbol("bar")),
5819 generation: 0..18446744073709551615,
5820 parents_range: 0..4294967295,
5821 },
5822 Ancestors {
5823 heads: CommitRef(Symbol("foo")),
5824 generation: 0..2,
5825 parents_range: 0..4294967295,
5826 },
5827 )
5828 "#);
5829
5830 insta::assert_debug_snapshot!(optimize(parse("(foo..bar)--")?), @r#"
5833 Ancestors {
5834 heads: Range {
5835 roots: CommitRef(Symbol("foo")),
5836 heads: CommitRef(Symbol("bar")),
5837 generation: 0..18446744073709551615,
5838 parents_range: 0..4294967295,
5839 },
5840 generation: 2..3,
5841 parents_range: 0..4294967295,
5842 }
5843 "#);
5844 insta::assert_debug_snapshot!(optimize(parse("foo..(bar..baz)")?), @r#"
5845 Range {
5846 roots: CommitRef(Symbol("foo")),
5847 heads: HeadsRange {
5848 roots: CommitRef(Symbol("bar")),
5849 heads: CommitRef(Symbol("baz")),
5850 parents_range: 0..4294967295,
5851 filter: All,
5852 },
5853 generation: 0..18446744073709551615,
5854 parents_range: 0..4294967295,
5855 }
5856 "#);
5857
5858 insta::assert_debug_snapshot!(
5860 optimize(parse("ancestors(ancestors(foo), 0)")?), @r#"
5861 Ancestors {
5862 heads: CommitRef(Symbol("foo")),
5863 generation: 0..0,
5864 parents_range: 0..4294967295,
5865 }
5866 "#
5867 );
5868 insta::assert_debug_snapshot!(
5869 optimize(parse("ancestors(ancestors(foo, 0))")?), @r#"
5870 Ancestors {
5871 heads: CommitRef(Symbol("foo")),
5872 generation: 0..0,
5873 parents_range: 0..4294967295,
5874 }
5875 "#
5876 );
5877
5878 insta::assert_debug_snapshot!(
5880 optimize(parse("first_ancestors(first_ancestors(foo, 5), 5)")?), @r#"
5881 Ancestors {
5882 heads: CommitRef(Symbol("foo")),
5883 generation: 0..9,
5884 parents_range: 0..1,
5885 }
5886 "#
5887 );
5888 insta::assert_debug_snapshot!(
5889 optimize(parse("first_ancestors(first_parent(foo), 5)")?), @r#"
5890 Ancestors {
5891 heads: CommitRef(Symbol("foo")),
5892 generation: 1..6,
5893 parents_range: 0..1,
5894 }
5895 "#
5896 );
5897 insta::assert_debug_snapshot!(
5898 optimize(parse("first_ancestors(ancestors(foo, 5), 5)")?), @r#"
5899 Ancestors {
5900 heads: Ancestors {
5901 heads: CommitRef(Symbol("foo")),
5902 generation: 0..5,
5903 parents_range: 0..4294967295,
5904 },
5905 generation: 0..5,
5906 parents_range: 0..1,
5907 }
5908 "#
5909 );
5910 insta::assert_debug_snapshot!(
5911 optimize(parse("ancestors(first_ancestors(foo, 5), 5)")?), @r#"
5912 Ancestors {
5913 heads: Ancestors {
5914 heads: CommitRef(Symbol("foo")),
5915 generation: 0..5,
5916 parents_range: 0..1,
5917 },
5918 generation: 0..5,
5919 parents_range: 0..4294967295,
5920 }
5921 "#
5922 );
5923 Ok(())
5924 }
5925
5926 #[test]
5927 fn test_optimize_descendants() -> TestResult {
5928 let settings = insta_settings();
5929 let _guard = settings.bind_to_scope();
5930
5931 insta::assert_debug_snapshot!(optimize(parse("foo++")?), @r#"
5933 Descendants {
5934 roots: CommitRef(Symbol("foo")),
5935 generation: 2..3,
5936 }
5937 "#);
5938 insta::assert_debug_snapshot!(optimize(parse("(foo+++)::")?), @r#"
5939 Descendants {
5940 roots: CommitRef(Symbol("foo")),
5941 generation: 3..18446744073709551615,
5942 }
5943 "#);
5944 insta::assert_debug_snapshot!(optimize(parse("(foo::)+++")?), @r#"
5945 Descendants {
5946 roots: CommitRef(Symbol("foo")),
5947 generation: 3..18446744073709551615,
5948 }
5949 "#);
5950
5951 insta::assert_debug_snapshot!(optimize(parse("foo+++-")?), @r#"
5953 Ancestors {
5954 heads: Descendants {
5955 roots: CommitRef(Symbol("foo")),
5956 generation: 3..4,
5957 },
5958 generation: 1..2,
5959 parents_range: 0..4294967295,
5960 }
5961 "#);
5962
5963 insta::assert_debug_snapshot!(optimize(parse("(foo++)::bar")?), @r#"
5967 DagRange {
5968 roots: Descendants {
5969 roots: CommitRef(Symbol("foo")),
5970 generation: 2..3,
5971 },
5972 heads: CommitRef(Symbol("bar")),
5973 }
5974 "#);
5975 Ok(())
5976 }
5977
5978 #[test]
5979 fn test_optimize_flatten_intersection() -> TestResult {
5980 let settings = insta_settings();
5981 let _guard = settings.bind_to_scope();
5982
5983 insta::assert_debug_snapshot!(optimize(parse("a & ((b & c) & (d & e))")?), @r#"
5985 Intersection(
5986 Intersection(
5987 Intersection(
5988 Intersection(
5989 CommitRef(Symbol("a")),
5990 CommitRef(Symbol("b")),
5991 ),
5992 CommitRef(Symbol("c")),
5993 ),
5994 CommitRef(Symbol("d")),
5995 ),
5996 CommitRef(Symbol("e")),
5997 )
5998 "#);
5999 Ok(())
6000 }
6001
6002 #[test]
6003 fn test_optimize_ancestors_union() -> TestResult {
6004 let settings = insta_settings();
6005 let _guard = settings.bind_to_scope();
6006
6007 insta::assert_debug_snapshot!(optimize(parse("::a | ::b | ::c | ::d")?), @r#"
6009 Ancestors {
6010 heads: Union(
6011 Union(
6012 CommitRef(Symbol("a")),
6013 CommitRef(Symbol("b")),
6014 ),
6015 Union(
6016 CommitRef(Symbol("c")),
6017 CommitRef(Symbol("d")),
6018 ),
6019 ),
6020 generation: 0..18446744073709551615,
6021 parents_range: 0..4294967295,
6022 }
6023 "#);
6024 insta::assert_debug_snapshot!(optimize(parse("ancestors(a-) | ancestors(b)")?), @r#"
6025 Ancestors {
6026 heads: Union(
6027 Ancestors {
6028 heads: CommitRef(Symbol("a")),
6029 generation: 1..2,
6030 parents_range: 0..4294967295,
6031 },
6032 CommitRef(Symbol("b")),
6033 ),
6034 generation: 0..18446744073709551615,
6035 parents_range: 0..4294967295,
6036 }
6037 "#);
6038
6039 insta::assert_debug_snapshot!(optimize(parse("~::a- & ~::b & ~::c & ::d")?), @r#"
6041 Range {
6042 roots: Union(
6043 Union(
6044 Ancestors {
6045 heads: CommitRef(Symbol("a")),
6046 generation: 1..2,
6047 parents_range: 0..4294967295,
6048 },
6049 CommitRef(Symbol("b")),
6050 ),
6051 CommitRef(Symbol("c")),
6052 ),
6053 heads: CommitRef(Symbol("d")),
6054 generation: 0..18446744073709551615,
6055 parents_range: 0..4294967295,
6056 }
6057 "#);
6058 insta::assert_debug_snapshot!(optimize(parse("a..b ~ ::c- ~ ::d")?), @r#"
6059 Range {
6060 roots: Union(
6061 Union(
6062 CommitRef(Symbol("a")),
6063 Ancestors {
6064 heads: CommitRef(Symbol("c")),
6065 generation: 1..2,
6066 parents_range: 0..4294967295,
6067 },
6068 ),
6069 CommitRef(Symbol("d")),
6070 ),
6071 heads: CommitRef(Symbol("b")),
6072 generation: 0..18446744073709551615,
6073 parents_range: 0..4294967295,
6074 }
6075 "#);
6076
6077 insta::assert_debug_snapshot!(optimize(parse("ancestors(a, 2) | ancestors(b)")?), @r#"
6079 Union(
6080 Ancestors {
6081 heads: CommitRef(Symbol("a")),
6082 generation: 0..2,
6083 parents_range: 0..4294967295,
6084 },
6085 Ancestors {
6086 heads: CommitRef(Symbol("b")),
6087 generation: 0..18446744073709551615,
6088 parents_range: 0..4294967295,
6089 },
6090 )
6091 "#);
6092 Ok(())
6093 }
6094
6095 #[test]
6096 fn test_optimize_sort_negations_and_ancestors() -> TestResult {
6097 let settings = insta_settings();
6098 let _guard = settings.bind_to_scope();
6099
6100 insta::assert_debug_snapshot!(optimize(parse("~a & ::b & ~::c & d ~ e & f & ::g & ~::h")?), @r#"
6103 Difference(
6104 Difference(
6105 Intersection(
6106 Intersection(
6107 Intersection(
6108 Range {
6109 roots: Union(
6110 CommitRef(Symbol("c")),
6111 CommitRef(Symbol("h")),
6112 ),
6113 heads: CommitRef(Symbol("b")),
6114 generation: 0..18446744073709551615,
6115 parents_range: 0..4294967295,
6116 },
6117 Ancestors {
6118 heads: CommitRef(Symbol("g")),
6119 generation: 0..18446744073709551615,
6120 parents_range: 0..4294967295,
6121 },
6122 ),
6123 CommitRef(Symbol("d")),
6124 ),
6125 CommitRef(Symbol("f")),
6126 ),
6127 CommitRef(Symbol("a")),
6128 ),
6129 CommitRef(Symbol("e")),
6130 )
6131 "#);
6132 Ok(())
6133 }
6134
6135 #[test]
6136 fn test_optimize_heads_range() -> TestResult {
6137 let settings = insta_settings();
6138 let _guard = settings.bind_to_scope();
6139
6140 insta::assert_debug_snapshot!(optimize(parse("heads(::)")?), @"
6142 HeadsRange {
6143 roots: None,
6144 heads: VisibleHeadsOrReferenced,
6145 parents_range: 0..4294967295,
6146 filter: All,
6147 }
6148 ");
6149 insta::assert_debug_snapshot!(optimize(parse("heads(::foo)")?), @r#"
6150 HeadsRange {
6151 roots: None,
6152 heads: CommitRef(Symbol("foo")),
6153 parents_range: 0..4294967295,
6154 filter: All,
6155 }
6156 "#);
6157 insta::assert_debug_snapshot!(optimize(parse("heads(..)")?), @"
6160 HeadsRange {
6161 roots: None,
6162 heads: VisibleHeadsOrReferenced,
6163 parents_range: 0..4294967295,
6164 filter: NotIn(Root),
6165 }
6166 ");
6167 insta::assert_debug_snapshot!(optimize(parse("heads(foo..)")?), @r#"
6168 HeadsRange {
6169 roots: CommitRef(Symbol("foo")),
6170 heads: VisibleHeadsOrReferenced,
6171 parents_range: 0..4294967295,
6172 filter: All,
6173 }
6174 "#);
6175 insta::assert_debug_snapshot!(optimize(parse("heads(..bar)")?), @r#"
6176 HeadsRange {
6177 roots: Root,
6178 heads: CommitRef(Symbol("bar")),
6179 parents_range: 0..4294967295,
6180 filter: All,
6181 }
6182 "#);
6183 insta::assert_debug_snapshot!(optimize(parse("heads(foo..bar)")?), @r#"
6184 HeadsRange {
6185 roots: CommitRef(Symbol("foo")),
6186 heads: CommitRef(Symbol("bar")),
6187 parents_range: 0..4294967295,
6188 filter: All,
6189 }
6190 "#);
6191 insta::assert_debug_snapshot!(optimize(parse("heads(~::foo & ::bar)")?), @r#"
6192 HeadsRange {
6193 roots: CommitRef(Symbol("foo")),
6194 heads: CommitRef(Symbol("bar")),
6195 parents_range: 0..4294967295,
6196 filter: All,
6197 }
6198 "#);
6199 insta::assert_debug_snapshot!(optimize(parse("heads(~::foo)")?), @r#"
6200 HeadsRange {
6201 roots: CommitRef(Symbol("foo")),
6202 heads: VisibleHeadsOrReferenced,
6203 parents_range: 0..4294967295,
6204 filter: All,
6205 }
6206 "#);
6207 insta::assert_debug_snapshot!(optimize(parse("heads(a..b & c..d)")?), @r#"
6208 HeadsRange {
6209 roots: Union(
6210 CommitRef(Symbol("a")),
6211 CommitRef(Symbol("c")),
6212 ),
6213 heads: CommitRef(Symbol("b")),
6214 parents_range: 0..4294967295,
6215 filter: Ancestors {
6216 heads: CommitRef(Symbol("d")),
6217 generation: 0..18446744073709551615,
6218 parents_range: 0..4294967295,
6219 },
6220 }
6221 "#);
6222
6223 insta::assert_debug_snapshot!(optimize(parse("heads(first_ancestors(foo))")?), @r#"
6225 HeadsRange {
6226 roots: None,
6227 heads: CommitRef(Symbol("foo")),
6228 parents_range: 0..1,
6229 filter: All,
6230 }
6231 "#);
6232 insta::assert_debug_snapshot!(optimize(parse("heads(first_ancestors(foo) & bar..)")?), @r#"
6233 HeadsRange {
6234 roots: CommitRef(Symbol("bar")),
6235 heads: CommitRef(Symbol("foo")),
6236 parents_range: 0..1,
6237 filter: All,
6238 }
6239 "#);
6240 insta::assert_debug_snapshot!(optimize(parse("heads(foo.. & first_ancestors(bar) & ::baz)")?), @r#"
6241 HeadsRange {
6242 roots: CommitRef(Symbol("foo")),
6243 heads: CommitRef(Symbol("bar")),
6244 parents_range: 0..1,
6245 filter: Ancestors {
6246 heads: CommitRef(Symbol("baz")),
6247 generation: 0..18446744073709551615,
6248 parents_range: 0..4294967295,
6249 },
6250 }
6251 "#);
6252
6253 insta::assert_debug_snapshot!(optimize(parse("heads(ancestors(foo, 2))")?), @r#"
6255 Heads(
6256 Ancestors {
6257 heads: CommitRef(Symbol("foo")),
6258 generation: 0..2,
6259 parents_range: 0..4294967295,
6260 },
6261 )
6262 "#);
6263 insta::assert_debug_snapshot!(optimize(parse("heads(first_ancestors(foo, 2))")?), @r#"
6264 Heads(
6265 Ancestors {
6266 heads: CommitRef(Symbol("foo")),
6267 generation: 0..2,
6268 parents_range: 0..1,
6269 },
6270 )
6271 "#);
6272
6273 insta::assert_debug_snapshot!(optimize(parse("heads(ancestors(foo--))")?), @r#"
6275 HeadsRange {
6276 roots: None,
6277 heads: Ancestors {
6278 heads: CommitRef(Symbol("foo")),
6279 generation: 2..3,
6280 parents_range: 0..4294967295,
6281 },
6282 parents_range: 0..4294967295,
6283 filter: All,
6284 }
6285 "#);
6286 insta::assert_debug_snapshot!(optimize(parse("heads(first_ancestors(first_parent(foo, 2)))")?), @r#"
6287 HeadsRange {
6288 roots: None,
6289 heads: Ancestors {
6290 heads: CommitRef(Symbol("foo")),
6291 generation: 2..3,
6292 parents_range: 0..1,
6293 },
6294 parents_range: 0..1,
6295 filter: All,
6296 }
6297 "#);
6298
6299 insta::assert_debug_snapshot!(optimize(parse("heads(author_name(A) | author_name(B))")?), @r#"
6301 HeadsRange {
6302 roots: None,
6303 heads: VisibleHeadsOrReferenced,
6304 parents_range: 0..4294967295,
6305 filter: AsFilter(
6306 Union(
6307 Filter(AuthorName(Pattern(Exact("A")))),
6308 Filter(AuthorName(Pattern(Exact("B")))),
6309 ),
6310 ),
6311 }
6312 "#);
6313 insta::assert_debug_snapshot!(optimize(parse("heads(~author_name(A))")?), @r#"
6314 HeadsRange {
6315 roots: None,
6316 heads: VisibleHeadsOrReferenced,
6317 parents_range: 0..4294967295,
6318 filter: AsFilter(
6319 NotIn(
6320 Filter(AuthorName(Pattern(Exact("A")))),
6321 ),
6322 ),
6323 }
6324 "#);
6325 insta::assert_debug_snapshot!(optimize(parse("heads(~foo)")?), @r#"
6326 HeadsRange {
6327 roots: None,
6328 heads: VisibleHeadsOrReferenced,
6329 parents_range: 0..4294967295,
6330 filter: NotIn(CommitRef(Symbol("foo"))),
6331 }
6332 "#);
6333
6334 insta::assert_debug_snapshot!(optimize(parse("heads(author_name(A) & ::foo ~ author_name(B))")?), @r#"
6336 HeadsRange {
6337 roots: None,
6338 heads: CommitRef(Symbol("foo")),
6339 parents_range: 0..4294967295,
6340 filter: AsFilter(
6341 Difference(
6342 Filter(AuthorName(Pattern(Exact("A")))),
6343 Filter(AuthorName(Pattern(Exact("B")))),
6344 ),
6345 ),
6346 }
6347 "#);
6348
6349 insta::assert_debug_snapshot!(optimize(parse("heads(~foo & ~roots(bar) & ::baz)")?), @r#"
6351 HeadsRange {
6352 roots: None,
6353 heads: CommitRef(Symbol("baz")),
6354 parents_range: 0..4294967295,
6355 filter: Difference(
6356 NotIn(CommitRef(Symbol("foo"))),
6357 Roots(CommitRef(Symbol("bar"))),
6358 ),
6359 }
6360 "#);
6361 Ok(())
6362 }
6363
6364 #[test]
6365 fn test_optimize_ancestors_heads_range() -> TestResult {
6366 insta::assert_debug_snapshot!(optimize(parse("::description(bar)")?), @r#"
6368 Ancestors {
6369 heads: HeadsRange {
6370 roots: None,
6371 heads: VisibleHeadsOrReferenced,
6372 parents_range: 0..4294967295,
6373 filter: Filter(
6374 Description(
6375 Pattern(
6376 Exact(
6377 "bar",
6378 ),
6379 ),
6380 ),
6381 ),
6382 },
6383 generation: 0..18446744073709551615,
6384 parents_range: 0..4294967295,
6385 }
6386 "#);
6387 insta::assert_debug_snapshot!(optimize(parse("author_name(foo)..")?), @r#"
6388 Range {
6389 roots: HeadsRange {
6390 roots: None,
6391 heads: VisibleHeadsOrReferenced,
6392 parents_range: 0..4294967295,
6393 filter: Filter(
6394 AuthorName(
6395 Pattern(
6396 Exact(
6397 "foo",
6398 ),
6399 ),
6400 ),
6401 ),
6402 },
6403 heads: VisibleHeadsOrReferenced,
6404 generation: 0..18446744073709551615,
6405 parents_range: 0..4294967295,
6406 }
6407 "#);
6408
6409 insta::assert_debug_snapshot!(optimize(parse("::(foo..bar)")?), @r#"
6411 Ancestors {
6412 heads: HeadsRange {
6413 roots: CommitRef(
6414 Symbol(
6415 "foo",
6416 ),
6417 ),
6418 heads: CommitRef(
6419 Symbol(
6420 "bar",
6421 ),
6422 ),
6423 parents_range: 0..4294967295,
6424 filter: All,
6425 },
6426 generation: 0..18446744073709551615,
6427 parents_range: 0..4294967295,
6428 }
6429 "#);
6430
6431 insta::assert_debug_snapshot!(optimize(parse("ancestors(author_name(foo), 5)")?), @r#"
6433 Ancestors {
6434 heads: Filter(
6435 AuthorName(
6436 Pattern(
6437 Exact(
6438 "foo",
6439 ),
6440 ),
6441 ),
6442 ),
6443 generation: 0..5,
6444 parents_range: 0..4294967295,
6445 }
6446 "#);
6447 insta::assert_debug_snapshot!(optimize(parse("first_ancestors(author_name(foo))")?), @r#"
6448 Ancestors {
6449 heads: Filter(
6450 AuthorName(
6451 Pattern(
6452 Exact(
6453 "foo",
6454 ),
6455 ),
6456 ),
6457 ),
6458 generation: 0..18446744073709551615,
6459 parents_range: 0..1,
6460 }
6461 "#);
6462 Ok(())
6463 }
6464
6465 #[test]
6466 fn test_escape_string_literal() {
6467 assert_eq!(format_symbol("foo"), "foo");
6469 assert_eq!(format_symbol("foo.bar"), "foo.bar");
6470
6471 assert_eq!(format_symbol("foo@bar"), r#""foo@bar""#);
6473 assert_eq!(format_symbol("foo bar"), r#""foo bar""#);
6474 assert_eq!(format_symbol(" foo "), r#"" foo ""#);
6475 assert_eq!(format_symbol("(foo)"), r#""(foo)""#);
6476 assert_eq!(format_symbol("all:foo"), r#""all:foo""#);
6477
6478 assert_eq!(format_symbol("foo\"bar"), r#""foo\"bar""#);
6480 assert_eq!(format_symbol("foo\\bar"), r#""foo\\bar""#);
6481 assert_eq!(format_symbol("foo\\\"bar"), r#""foo\\\"bar""#);
6482 assert_eq!(format_symbol("foo\nbar"), r#""foo\nbar""#);
6483
6484 assert_eq!(format_symbol("foo\"bar"), r#""foo\"bar""#);
6487 assert_eq!(format_symbol("foo\\bar"), r#""foo\\bar""#);
6488 assert_eq!(format_symbol("foo\\\"bar"), r#""foo\\\"bar""#);
6489 assert_eq!(format_symbol("foo \x01 bar"), r#""foo \x01 bar""#);
6490 }
6491
6492 #[test]
6493 fn test_escape_remote_symbol() {
6494 assert_eq!(format_remote_symbol("foo", "bar"), "foo@bar");
6495 assert_eq!(
6496 format_remote_symbol(" foo ", "bar:baz"),
6497 r#"" foo "@"bar:baz""#
6498 );
6499 }
6500}