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