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