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