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 RevsetExpression::HasSize {
1869 candidates,
1870 count: *count,
1871 }
1872 .into()
1873 }
1874 RevsetExpression::Latest { candidates, count } => {
1875 let candidates = folder.fold_expression(candidates)?;
1876 let count = *count;
1877 RevsetExpression::Latest { candidates, count }.into()
1878 }
1879 RevsetExpression::Filter(predicate) => RevsetExpression::Filter(predicate.clone()).into(),
1880 RevsetExpression::AsFilter(candidates) => {
1881 let candidates = folder.fold_expression(candidates)?;
1882 RevsetExpression::AsFilter(candidates).into()
1883 }
1884 RevsetExpression::Divergent => RevsetExpression::Divergent.into(),
1885 RevsetExpression::AtOperation {
1886 operation,
1887 candidates,
1888 } => folder.fold_at_operation(operation, candidates)?,
1889 RevsetExpression::WithinReference {
1890 candidates,
1891 commits,
1892 } => {
1893 let candidates = folder.fold_expression(candidates)?;
1894 let commits = commits.clone();
1895 RevsetExpression::WithinReference {
1896 candidates,
1897 commits,
1898 }
1899 .into()
1900 }
1901 RevsetExpression::WithinVisibility {
1902 candidates,
1903 visible_heads,
1904 } => {
1905 let candidates = folder.fold_expression(candidates)?;
1906 let visible_heads = visible_heads.clone();
1907 RevsetExpression::WithinVisibility {
1908 candidates,
1909 visible_heads,
1910 }
1911 .into()
1912 }
1913 RevsetExpression::Coalesce(expression1, expression2) => {
1914 let expression1 = folder.fold_expression(expression1)?;
1915 let expression2 = folder.fold_expression(expression2)?;
1916 RevsetExpression::Coalesce(expression1, expression2).into()
1917 }
1918 RevsetExpression::Present(candidates) => {
1919 let candidates = folder.fold_expression(candidates)?;
1920 RevsetExpression::Present(candidates).into()
1921 }
1922 RevsetExpression::NotIn(complement) => {
1923 let complement = folder.fold_expression(complement)?;
1924 RevsetExpression::NotIn(complement).into()
1925 }
1926 RevsetExpression::Union(expression1, expression2) => {
1927 let expression1 = folder.fold_expression(expression1)?;
1928 let expression2 = folder.fold_expression(expression2)?;
1929 RevsetExpression::Union(expression1, expression2).into()
1930 }
1931 RevsetExpression::Intersection(expression1, expression2) => {
1932 let expression1 = folder.fold_expression(expression1)?;
1933 let expression2 = folder.fold_expression(expression2)?;
1934 RevsetExpression::Intersection(expression1, expression2).into()
1935 }
1936 RevsetExpression::Difference(expression1, expression2) => {
1937 let expression1 = folder.fold_expression(expression1)?;
1938 let expression2 = folder.fold_expression(expression2)?;
1939 RevsetExpression::Difference(expression1, expression2).into()
1940 }
1941 };
1942 Ok(expression)
1943}
1944
1945fn resolve_referenced_commits<St: ExpressionState>(
1949 expression: &Arc<RevsetExpression<St>>,
1950) -> TransformedExpression<St> {
1951 if matches!(
1953 expression.as_ref(),
1954 RevsetExpression::WithinReference { .. }
1955 ) {
1956 return None;
1957 }
1958
1959 let mut inner_commits = Vec::new();
1961 let mut outer_commits = Vec::new();
1962 let transformed = transform_expression(
1963 expression,
1964 |expression| match expression.as_ref() {
1965 RevsetExpression::WithinReference { commits, .. } => {
1967 inner_commits.extend_from_slice(commits);
1968 ControlFlow::Break(None)
1969 }
1970 RevsetExpression::WithinVisibility {
1972 candidates,
1973 visible_heads,
1974 } => {
1975 inner_commits.extend_from_slice(visible_heads);
1977 let transformed = resolve_referenced_commits(candidates);
1978 if let RevsetExpression::WithinReference { commits, .. } =
1980 transformed.as_deref().unwrap_or(candidates)
1981 {
1982 inner_commits.extend_from_slice(commits);
1983 }
1984 ControlFlow::Break(transformed.map(|candidates| {
1985 Arc::new(RevsetExpression::WithinVisibility {
1986 candidates,
1987 visible_heads: visible_heads.clone(),
1988 })
1989 }))
1990 }
1991 _ => ControlFlow::Continue(()),
1992 },
1993 |expression| {
1994 if let RevsetExpression::Commits(commits) = expression.as_ref() {
1995 outer_commits.extend_from_slice(commits);
1996 }
1997 None
1998 },
1999 );
2000
2001 outer_commits.extend(inner_commits);
2004 if outer_commits.is_empty() {
2005 return transformed;
2007 }
2008 Some(Arc::new(RevsetExpression::WithinReference {
2009 candidates: transformed.unwrap_or_else(|| expression.clone()),
2010 commits: outer_commits,
2011 }))
2012}
2013
2014fn flatten_intersections<St: ExpressionState>(
2017 expression: &Arc<RevsetExpression<St>>,
2018) -> TransformedExpression<St> {
2019 fn flatten<St: ExpressionState>(
2020 expression1: &Arc<RevsetExpression<St>>,
2021 expression2: &Arc<RevsetExpression<St>>,
2022 ) -> TransformedExpression<St> {
2023 let recurse = |a, b| flatten(a, b).unwrap_or_else(|| a.intersection(b));
2024
2025 match expression2.as_ref() {
2026 RevsetExpression::Intersection(inner1, inner2) => {
2028 Some(recurse(expression1, inner1).intersection(inner2))
2029 }
2030 _ => None,
2031 }
2032 }
2033
2034 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2035 RevsetExpression::Intersection(expression1, expression2) => {
2036 flatten(expression1, expression2)
2037 }
2038 _ => None,
2039 })
2040}
2041
2042fn sort_intersection_by_key<St: ExpressionState, T: Ord>(
2046 base: &Arc<RevsetExpression<St>>,
2047 expression: &Arc<RevsetExpression<St>>,
2048 mut get_key: impl FnMut(&RevsetExpression<St>) -> T,
2049) -> TransformedExpression<St> {
2050 fn sort_intersection_helper<St: ExpressionState, T: Ord>(
2053 base: &Arc<RevsetExpression<St>>,
2054 expression: &Arc<RevsetExpression<St>>,
2055 expression_key: T,
2056 mut get_key: impl FnMut(&RevsetExpression<St>) -> T,
2057 ) -> TransformedExpression<St> {
2058 if let RevsetExpression::Intersection(inner1, inner2) = base.as_ref() {
2059 (expression_key < get_key(inner2)).then(|| {
2061 sort_intersection_helper(inner1, expression, expression_key, get_key)
2062 .unwrap_or_else(|| inner1.intersection(expression))
2063 .intersection(inner2)
2064 })
2065 } else {
2066 (expression_key < get_key(base)).then(|| expression.intersection(base))
2068 }
2069 }
2070
2071 sort_intersection_helper(base, expression, get_key(expression), get_key)
2072}
2073
2074fn sort_negations_and_ancestors<St: ExpressionState>(
2079 expression: &Arc<RevsetExpression<St>>,
2080) -> TransformedExpression<St> {
2081 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
2082 enum AncestorsOrder {
2083 NegatedAncestors,
2084 Ancestors,
2085 Other,
2086 NegatedOther,
2087 }
2088
2089 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2090 RevsetExpression::Intersection(expression1, expression2) => {
2091 sort_intersection_by_key(expression1, expression2, |expression| match expression {
2092 RevsetExpression::Ancestors {
2093 heads: _,
2094 generation: Range { end: u64::MAX, .. },
2095 parents_range: _,
2096 } => AncestorsOrder::Ancestors,
2097 RevsetExpression::NotIn(complement) => match complement.as_ref() {
2098 RevsetExpression::Ancestors {
2099 heads: _,
2100 generation: Range { end: u64::MAX, .. },
2101 parents_range: PARENTS_RANGE_FULL,
2104 } => AncestorsOrder::NegatedAncestors,
2105 _ => AncestorsOrder::NegatedOther,
2106 },
2107 _ => AncestorsOrder::Other,
2108 })
2109 }
2110 _ => None,
2111 })
2112}
2113
2114fn internalize_filter<St: ExpressionState>(
2123 expression: &Arc<RevsetExpression<St>>,
2124) -> TransformedExpression<St> {
2125 fn get_filter<St: ExpressionState>(
2126 expression: &Arc<RevsetExpression<St>>,
2127 ) -> Option<&Arc<RevsetExpression<St>>> {
2128 match expression.as_ref() {
2129 RevsetExpression::Filter(_) => Some(expression),
2130 RevsetExpression::AsFilter(candidates) => Some(candidates),
2131 _ => None,
2132 }
2133 }
2134
2135 fn mark_filter<St: ExpressionState>(
2136 expression: Arc<RevsetExpression<St>>,
2137 ) -> Arc<RevsetExpression<St>> {
2138 Arc::new(RevsetExpression::AsFilter(expression))
2139 }
2140
2141 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2142 RevsetExpression::Present(e) => get_filter(e).map(|f| mark_filter(f.present())),
2144 RevsetExpression::NotIn(e) => get_filter(e).map(|f| mark_filter(f.negated())),
2145 RevsetExpression::Union(e1, e2) => {
2146 let f1 = get_filter(e1);
2147 let f2 = get_filter(e2);
2148 (f1.is_some() || f2.is_some())
2149 .then(|| mark_filter(f1.unwrap_or(e1).union(f2.unwrap_or(e2))))
2150 }
2151 RevsetExpression::Intersection(e1, e2) => match (get_filter(e1), get_filter(e2)) {
2157 (Some(f1), Some(f2)) => Some(mark_filter(f1.intersection(f2))),
2159 (Some(_), None) => Some(e2.intersection(e1)),
2161 (None, Some(f2)) => match e1.as_ref() {
2163 RevsetExpression::Intersection(e1a, e1b) => {
2164 get_filter(e1b).map(|f1b| e1a.intersection(&mark_filter(f1b.intersection(f2))))
2165 }
2166 _ => None,
2167 },
2168 (None, None) => match e1.as_ref() {
2170 RevsetExpression::Intersection(e1a, e1b) => {
2171 get_filter(e1b).map(|_| e1a.intersection(e2).intersection(e1b))
2172 }
2173 _ => None,
2174 },
2175 },
2176 _ => None,
2178 })
2179}
2180
2181fn fold_redundant_expression<St: ExpressionState>(
2186 expression: &Arc<RevsetExpression<St>>,
2187) -> TransformedExpression<St> {
2188 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2189 RevsetExpression::Commits(commits) if commits.is_empty() => Some(RevsetExpression::none()),
2190 RevsetExpression::NotIn(outer) => match outer.as_ref() {
2191 RevsetExpression::NotIn(inner) => Some(inner.clone()),
2192 RevsetExpression::None => Some(RevsetExpression::all()),
2193 RevsetExpression::All => Some(RevsetExpression::none()),
2194 _ => None,
2195 },
2196 RevsetExpression::Union(expression1, expression2) => {
2197 match (expression1.as_ref(), expression2.as_ref()) {
2198 (_, RevsetExpression::None) => Some(expression1.clone()),
2199 (RevsetExpression::None, _) => Some(expression2.clone()),
2200 (RevsetExpression::All, _) => Some(RevsetExpression::all()),
2201 (_, RevsetExpression::All) => Some(RevsetExpression::all()),
2202 _ => None,
2203 }
2204 }
2205 RevsetExpression::Intersection(expression1, expression2) => {
2206 match (expression1.as_ref(), expression2.as_ref()) {
2207 (RevsetExpression::None, _) => Some(RevsetExpression::none()),
2208 (_, RevsetExpression::None) => Some(RevsetExpression::none()),
2209 (_, RevsetExpression::All) => Some(expression1.clone()),
2210 (RevsetExpression::All, _) => Some(expression2.clone()),
2211 _ => None,
2212 }
2213 }
2214 _ => None,
2215 })
2216}
2217
2218fn ancestors_to_heads<St: ExpressionState>(
2222 expression: &RevsetExpression<St>,
2223) -> Result<Arc<RevsetExpression<St>>, ()> {
2224 match ancestors_to_heads_and_parents_range(expression) {
2225 Ok((heads, PARENTS_RANGE_FULL)) => Ok(heads),
2226 _ => Err(()),
2227 }
2228}
2229
2230fn ancestors_to_heads_and_parents_range<St: ExpressionState>(
2231 expression: &RevsetExpression<St>,
2232) -> Result<(Arc<RevsetExpression<St>>, Range<u32>), ()> {
2233 match expression {
2234 RevsetExpression::Ancestors {
2235 heads,
2236 generation: GENERATION_RANGE_FULL,
2237 parents_range,
2238 } => Ok((heads.clone(), parents_range.clone())),
2239 RevsetExpression::Ancestors {
2240 heads,
2241 generation: Range {
2242 start,
2243 end: u64::MAX,
2244 },
2245 parents_range,
2246 } => Ok((
2247 Arc::new(RevsetExpression::Ancestors {
2248 heads: heads.clone(),
2249 generation: (*start)..start.saturating_add(1),
2250 parents_range: parents_range.clone(),
2251 }),
2252 parents_range.clone(),
2253 )),
2254 _ => Err(()),
2255 }
2256}
2257
2258fn fold_ancestors_union<St: ExpressionState>(
2263 expression: &Arc<RevsetExpression<St>>,
2264) -> TransformedExpression<St> {
2265 fn union_ancestors<St: ExpressionState>(
2266 expression1: &Arc<RevsetExpression<St>>,
2267 expression2: &Arc<RevsetExpression<St>>,
2268 ) -> TransformedExpression<St> {
2269 let heads1 = ancestors_to_heads(expression1).ok()?;
2270 let heads2 = ancestors_to_heads(expression2).ok()?;
2271 Some(heads1.union(&heads2).ancestors())
2272 }
2273
2274 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2275 RevsetExpression::Union(expression1, expression2) => {
2276 union_ancestors(expression1, expression2)
2278 }
2279 RevsetExpression::Intersection(expression1, expression2) => {
2280 match (expression1.as_ref(), expression2.as_ref()) {
2281 (RevsetExpression::NotIn(complement1), RevsetExpression::NotIn(complement2)) => {
2283 union_ancestors(complement1, complement2).map(|expression| expression.negated())
2284 }
2285 _ => None,
2286 }
2287 }
2288 _ => None,
2289 })
2290}
2291
2292fn fold_heads_range<St: ExpressionState>(
2299 expression: &Arc<RevsetExpression<St>>,
2300) -> TransformedExpression<St> {
2301 struct FilteredRange<St: ExpressionState> {
2303 roots: Arc<RevsetExpression<St>>,
2304 heads_and_parents_range: Option<(Arc<RevsetExpression<St>>, Range<u32>)>,
2305 filter: Arc<RevsetExpression<St>>,
2306 }
2307
2308 impl<St: ExpressionState> FilteredRange<St> {
2309 fn new(roots: Arc<RevsetExpression<St>>) -> Self {
2310 Self {
2312 roots,
2313 heads_and_parents_range: None,
2314 filter: RevsetExpression::all(),
2315 }
2316 }
2317
2318 fn add(mut self, expression: &Arc<RevsetExpression<St>>) -> Self {
2319 if self.heads_and_parents_range.is_none() {
2320 if let Ok(heads_and_parents_range) =
2322 ancestors_to_heads_and_parents_range(expression)
2323 {
2324 self.heads_and_parents_range = Some(heads_and_parents_range);
2325 return self;
2326 }
2327 }
2328 self.add_filter(expression)
2329 }
2330
2331 fn add_filter(mut self, expression: &Arc<RevsetExpression<St>>) -> Self {
2332 self.filter = if let RevsetExpression::All = self.filter.as_ref() {
2333 expression.clone()
2335 } else {
2336 self.filter.intersection(expression)
2337 };
2338 self
2339 }
2340 }
2341
2342 fn to_filtered_range<St: ExpressionState>(
2343 expression: &Arc<RevsetExpression<St>>,
2344 ) -> Option<FilteredRange<St>> {
2345 if let Ok(heads_and_parents_range) = ancestors_to_heads_and_parents_range(expression) {
2349 return Some(FilteredRange {
2350 roots: RevsetExpression::none(),
2351 heads_and_parents_range: Some(heads_and_parents_range),
2352 filter: RevsetExpression::all(),
2353 });
2354 }
2355 match expression.as_ref() {
2356 RevsetExpression::NotIn(complement) => {
2359 if let Ok(roots) = ancestors_to_heads(complement) {
2360 Some(FilteredRange::new(roots))
2361 } else {
2362 Some(FilteredRange::new(RevsetExpression::none()).add_filter(expression))
2365 }
2366 }
2367 RevsetExpression::All | RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_) => {
2369 Some(FilteredRange::new(RevsetExpression::none()).add_filter(expression))
2370 }
2371 RevsetExpression::Intersection(expression1, expression2) => {
2374 to_filtered_range(expression1).map(|filtered_range| filtered_range.add(expression2))
2375 }
2376 _ => None,
2377 }
2378 }
2379
2380 fn to_heads_range<St: ExpressionState>(
2381 candidates: &Arc<RevsetExpression<St>>,
2382 ) -> Option<Arc<RevsetExpression<St>>> {
2383 to_filtered_range(candidates).map(|filtered_range| {
2384 let (heads, parents_range) =
2385 filtered_range.heads_and_parents_range.unwrap_or_else(|| {
2386 (
2387 RevsetExpression::visible_heads_or_referenced(),
2388 PARENTS_RANGE_FULL,
2389 )
2390 });
2391 RevsetExpression::HeadsRange {
2392 roots: filtered_range.roots,
2393 heads,
2394 parents_range,
2395 filter: filtered_range.filter,
2396 }
2397 .into()
2398 })
2399 }
2400
2401 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2402 RevsetExpression::Ancestors {
2405 heads,
2406 generation: GENERATION_RANGE_FULL,
2409 parents_range: PARENTS_RANGE_FULL,
2410 } => to_heads_range(heads).map(|heads| heads.ancestors()),
2411 RevsetExpression::Heads(candidates) => to_heads_range(candidates),
2414 _ => None,
2415 })
2416}
2417
2418fn to_difference_range<St: ExpressionState>(
2419 expression: &Arc<RevsetExpression<St>>,
2420 complement: &Arc<RevsetExpression<St>>,
2421) -> TransformedExpression<St> {
2422 let RevsetExpression::Ancestors {
2423 heads,
2424 generation,
2425 parents_range,
2426 } = expression.as_ref()
2427 else {
2428 return None;
2429 };
2430 let roots = ancestors_to_heads(complement).ok()?;
2431 Some(Arc::new(RevsetExpression::Range {
2434 roots,
2435 heads: heads.clone(),
2436 generation: generation.clone(),
2437 parents_range: parents_range.clone(),
2438 }))
2439}
2440
2441fn fold_difference<St: ExpressionState>(
2444 expression: &Arc<RevsetExpression<St>>,
2445) -> TransformedExpression<St> {
2446 fn to_difference<St: ExpressionState>(
2447 expression: &Arc<RevsetExpression<St>>,
2448 complement: &Arc<RevsetExpression<St>>,
2449 ) -> Arc<RevsetExpression<St>> {
2450 to_difference_range(expression, complement).unwrap_or_else(|| expression.minus(complement))
2451 }
2452
2453 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2454 RevsetExpression::Intersection(expression1, expression2) => {
2455 match (expression1.as_ref(), expression2.as_ref()) {
2456 (_, RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_)) => None,
2458 (_, RevsetExpression::NotIn(complement)) => {
2459 Some(to_difference(expression1, complement))
2460 }
2461 (RevsetExpression::NotIn(complement), _) => {
2462 Some(to_difference(expression2, complement))
2463 }
2464 _ => None,
2465 }
2466 }
2467 _ => None,
2468 })
2469}
2470
2471fn fold_not_in_ancestors<St: ExpressionState>(
2476 expression: &Arc<RevsetExpression<St>>,
2477) -> TransformedExpression<St> {
2478 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2479 RevsetExpression::NotIn(complement)
2480 if matches!(complement.as_ref(), RevsetExpression::Ancestors { .. }) =>
2481 {
2482 to_difference_range(
2485 &RevsetExpression::visible_heads_or_referenced().ancestors(),
2486 complement,
2487 )
2488 }
2489 _ => None,
2490 })
2491}
2492
2493fn unfold_difference<St: ExpressionState>(
2498 expression: &Arc<RevsetExpression<St>>,
2499) -> TransformedExpression<St> {
2500 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2501 RevsetExpression::Range {
2503 roots,
2504 heads,
2505 parents_range,
2506 generation,
2507 } => {
2508 let heads_ancestors = Arc::new(RevsetExpression::Ancestors {
2509 heads: heads.clone(),
2510 generation: generation.clone(),
2511 parents_range: parents_range.clone(),
2512 });
2513 Some(heads_ancestors.intersection(&roots.ancestors().negated()))
2514 }
2515 RevsetExpression::Difference(expression1, expression2) => {
2516 Some(expression1.intersection(&expression2.negated()))
2517 }
2518 _ => None,
2519 })
2520}
2521
2522fn fold_generation<St: ExpressionState>(
2525 expression: &Arc<RevsetExpression<St>>,
2526) -> TransformedExpression<St> {
2527 fn add_generation(generation1: &Range<u64>, generation2: &Range<u64>) -> Range<u64> {
2528 if generation1.is_empty() || generation2.is_empty() {
2530 GENERATION_RANGE_EMPTY
2531 } else {
2532 let start = u64::saturating_add(generation1.start, generation2.start);
2533 let end = u64::saturating_add(generation1.end, generation2.end - 1);
2534 start..end
2535 }
2536 }
2537
2538 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2539 RevsetExpression::Ancestors {
2540 heads,
2541 generation: generation1,
2542 parents_range: parents1,
2543 } => {
2544 match heads.as_ref() {
2545 RevsetExpression::Ancestors {
2549 heads,
2550 generation: generation2,
2551 parents_range: parents2,
2552 } if parents2 == parents1 => Some(Arc::new(RevsetExpression::Ancestors {
2553 heads: heads.clone(),
2554 generation: add_generation(generation1, generation2),
2555 parents_range: parents1.clone(),
2556 })),
2557 _ => None,
2558 }
2559 }
2560 RevsetExpression::Descendants {
2561 roots,
2562 generation: generation1,
2563 } => {
2564 match roots.as_ref() {
2565 RevsetExpression::Descendants {
2569 roots,
2570 generation: generation2,
2571 } => Some(Arc::new(RevsetExpression::Descendants {
2572 roots: roots.clone(),
2573 generation: add_generation(generation1, generation2),
2574 })),
2575 _ => None,
2576 }
2577 }
2578 _ => None,
2580 })
2581}
2582
2583pub fn optimize<St: ExpressionState>(
2586 expression: Arc<RevsetExpression<St>>,
2587) -> Arc<RevsetExpression<St>> {
2588 let expression = resolve_referenced_commits(&expression).unwrap_or(expression);
2591 let expression = unfold_difference(&expression).unwrap_or(expression);
2592 let expression = fold_redundant_expression(&expression).unwrap_or(expression);
2593 let expression = fold_generation(&expression).unwrap_or(expression);
2594 let expression = flatten_intersections(&expression).unwrap_or(expression);
2595 let expression = sort_negations_and_ancestors(&expression).unwrap_or(expression);
2596 let expression = fold_ancestors_union(&expression).unwrap_or(expression);
2597 let expression = internalize_filter(&expression).unwrap_or(expression);
2598 let expression = fold_heads_range(&expression).unwrap_or(expression);
2599 let expression = fold_difference(&expression).unwrap_or(expression);
2600 fold_not_in_ancestors(&expression).unwrap_or(expression)
2601}
2602
2603pub fn walk_revs<'index>(
2607 repo: &'index dyn Repo,
2608 wanted: &[CommitId],
2609 unwanted: &[CommitId],
2610) -> Result<Box<dyn Revset + 'index>, RevsetEvaluationError> {
2611 RevsetExpression::commits(unwanted.to_vec())
2612 .range(&RevsetExpression::commits(wanted.to_vec()))
2613 .evaluate(repo)
2614}
2615
2616fn reload_repo_at_operation(
2617 repo: &dyn Repo,
2618 op_str: &str,
2619) -> Result<Arc<ReadonlyRepo>, RevsetResolutionError> {
2620 let base_repo = repo.base_repo();
2624 let operation = op_walk::resolve_op_with_repo(base_repo, op_str)
2625 .block_on()
2626 .map_err(|err| RevsetResolutionError::Other(err.into()))?;
2627 base_repo
2628 .reload_at(&operation)
2629 .block_on()
2630 .map_err(|err| match err {
2631 RepoLoaderError::Backend(err) => RevsetResolutionError::Backend(err),
2632 RepoLoaderError::Index(_)
2633 | RepoLoaderError::IndexStore(_)
2634 | RepoLoaderError::OpHeadResolution(_)
2635 | RepoLoaderError::OpHeadsStoreError(_)
2636 | RepoLoaderError::OpStore(_)
2637 | RepoLoaderError::TransactionCommit(_) => RevsetResolutionError::Other(err.into()),
2638 })
2639}
2640
2641fn resolve_remote_symbol(
2642 repo: &dyn Repo,
2643 symbol: RemoteRefSymbol<'_>,
2644) -> Result<CommitId, RevsetResolutionError> {
2645 let remote_ref = repo.view().get_remote_tag(symbol);
2646 if let Some(id) = to_resolved_ref("remote_tag", symbol, &remote_ref.target)? {
2647 return Ok(id);
2648 }
2649 let remote_ref = repo.view().get_remote_bookmark(symbol);
2650 if let Some(id) = to_resolved_ref("remote_bookmark", symbol, &remote_ref.target)? {
2651 return Ok(id);
2652 }
2653 Err(make_no_such_symbol_error(repo, symbol.to_string()))
2654}
2655
2656fn to_resolved_ref(
2657 kind: &'static str,
2658 symbol: impl ToString,
2659 target: &RefTarget,
2660) -> Result<Option<CommitId>, RevsetResolutionError> {
2661 match target.as_resolved() {
2662 Some(Some(id)) => Ok(Some(id.clone())),
2663 Some(None) => Ok(None),
2664 None => Err(RevsetResolutionError::ConflictedRef {
2665 kind,
2666 symbol: symbol.to_string(),
2667 targets: target.added_ids().cloned().collect(),
2668 }),
2669 }
2670}
2671
2672fn all_formatted_ref_symbols<'a>(
2673 all_refs: impl Iterator<Item = (&'a RefName, LocalRemoteRefTarget<'a>)>,
2674 include_synced_remotes: bool,
2675) -> impl Iterator<Item = String> {
2676 all_refs.flat_map(move |(name, targets)| {
2677 let local_target = targets.local_target;
2678 let local_symbol = local_target
2679 .is_present()
2680 .then(|| format_symbol(name.as_str()));
2681 let remote_symbols = targets
2682 .remote_refs
2683 .into_iter()
2684 .filter(move |&(_, remote_ref)| {
2685 include_synced_remotes
2686 || !remote_ref.is_tracked()
2687 || remote_ref.target != *local_target
2688 })
2689 .map(move |(remote, _)| format_remote_symbol(name.as_str(), remote.as_str()));
2690 local_symbol.into_iter().chain(remote_symbols)
2691 })
2692}
2693
2694fn make_no_such_symbol_error(repo: &dyn Repo, name: String) -> RevsetResolutionError {
2695 let include_synced_remotes = name.contains('@');
2696 let tag_names = all_formatted_ref_symbols(repo.view().tags(), include_synced_remotes);
2697 let bookmark_names = all_formatted_ref_symbols(repo.view().bookmarks(), include_synced_remotes);
2698 let mut candidates = collect_similar(&name, itertools::chain(tag_names, bookmark_names));
2699 candidates.dedup(); RevsetResolutionError::NoSuchRevision { name, candidates }
2701}
2702
2703pub trait PartialSymbolResolver {
2707 fn resolve_symbol(
2708 &self,
2709 repo: &dyn Repo,
2710 symbol: &str,
2711 ) -> Result<Option<CommitId>, RevsetResolutionError>;
2712}
2713
2714struct TagResolver;
2715
2716impl PartialSymbolResolver for TagResolver {
2717 fn resolve_symbol(
2718 &self,
2719 repo: &dyn Repo,
2720 symbol: &str,
2721 ) -> Result<Option<CommitId>, RevsetResolutionError> {
2722 let target = repo.view().get_local_tag(symbol.as_ref());
2723 to_resolved_ref("tag", symbol, target)
2724 }
2725}
2726
2727struct BookmarkResolver;
2728
2729impl PartialSymbolResolver for BookmarkResolver {
2730 fn resolve_symbol(
2731 &self,
2732 repo: &dyn Repo,
2733 symbol: &str,
2734 ) -> Result<Option<CommitId>, RevsetResolutionError> {
2735 let target = repo.view().get_local_bookmark(symbol.as_ref());
2736 to_resolved_ref("bookmark", symbol, target)
2737 }
2738}
2739
2740struct GitRefResolver;
2741
2742impl PartialSymbolResolver for GitRefResolver {
2743 fn resolve_symbol(
2744 &self,
2745 repo: &dyn Repo,
2746 symbol: &str,
2747 ) -> Result<Option<CommitId>, RevsetResolutionError> {
2748 let view = repo.view();
2749 for git_ref_prefix in &["", "refs/"] {
2750 let target = view.get_git_ref([git_ref_prefix, symbol].concat().as_ref());
2751 if let Some(id) = to_resolved_ref("git_ref", symbol, target)? {
2752 return Ok(Some(id));
2753 }
2754 }
2755 Ok(None)
2756 }
2757}
2758
2759const DEFAULT_RESOLVERS: &[&dyn PartialSymbolResolver] =
2760 &[&TagResolver, &BookmarkResolver, &GitRefResolver];
2761
2762struct CommitPrefixResolver<'a> {
2763 context_repo: &'a dyn Repo,
2764 context: Option<&'a IdPrefixContext>,
2765}
2766
2767impl CommitPrefixResolver<'_> {
2768 fn try_resolve(
2769 &self,
2770 repo: &dyn Repo,
2771 prefix: &HexPrefix,
2772 ) -> Result<Option<CommitId>, RevsetResolutionError> {
2773 let index = self
2774 .context
2775 .map(|ctx| ctx.populate(self.context_repo))
2776 .transpose()
2777 .map_err(|err| RevsetResolutionError::Other(err.into()))?
2778 .unwrap_or(IdPrefixIndex::empty());
2779 match index
2780 .resolve_commit_prefix(repo, prefix)
2781 .map_err(|err| RevsetResolutionError::Other(err.into()))?
2782 {
2783 PrefixResolution::AmbiguousMatch => {
2784 Err(RevsetResolutionError::AmbiguousCommitIdPrefix(prefix.hex()))
2785 }
2786 PrefixResolution::SingleMatch(id) => Ok(Some(id)),
2787 PrefixResolution::NoMatch => Ok(None),
2788 }
2789 }
2790}
2791
2792impl PartialSymbolResolver for CommitPrefixResolver<'_> {
2793 fn resolve_symbol(
2794 &self,
2795 repo: &dyn Repo,
2796 symbol: &str,
2797 ) -> Result<Option<CommitId>, RevsetResolutionError> {
2798 if let Some(prefix) = HexPrefix::try_from_hex(symbol) {
2799 self.try_resolve(repo, &prefix)
2800 } else {
2801 Ok(None)
2802 }
2803 }
2804}
2805
2806struct ChangePrefixResolver<'a> {
2807 context_repo: &'a dyn Repo,
2808 context: Option<&'a IdPrefixContext>,
2809}
2810
2811impl ChangePrefixResolver<'_> {
2812 fn try_resolve(
2813 &self,
2814 repo: &dyn Repo,
2815 prefix: &HexPrefix,
2816 ) -> Result<Option<ResolvedChangeTargets>, RevsetResolutionError> {
2817 let index = self
2818 .context
2819 .map(|ctx| ctx.populate(self.context_repo))
2820 .transpose()
2821 .map_err(|err| RevsetResolutionError::Other(err.into()))?
2822 .unwrap_or(IdPrefixIndex::empty());
2823 match index
2824 .resolve_change_prefix(repo, prefix)
2825 .map_err(|err| RevsetResolutionError::Other(err.into()))?
2826 {
2827 PrefixResolution::AmbiguousMatch => Err(
2828 RevsetResolutionError::AmbiguousChangeIdPrefix(prefix.reverse_hex()),
2829 ),
2830 PrefixResolution::SingleMatch(ids) => Ok(Some(ids)),
2831 PrefixResolution::NoMatch => Ok(None),
2832 }
2833 }
2834}
2835
2836impl PartialSymbolResolver for ChangePrefixResolver<'_> {
2837 fn resolve_symbol(
2838 &self,
2839 repo: &dyn Repo,
2840 symbol: &str,
2841 ) -> Result<Option<CommitId>, RevsetResolutionError> {
2842 let (change_id, offset) = if let Some((prefix, suffix)) = symbol.split_once('/') {
2843 if prefix.is_empty() || suffix.is_empty() {
2844 return Ok(None);
2845 }
2846 let Ok(offset) = suffix.parse() else {
2847 return Ok(None);
2848 };
2849 (prefix, Some(offset))
2850 } else {
2851 (symbol, None)
2852 };
2853 let Some(prefix) = HexPrefix::try_from_reverse_hex(change_id) else {
2854 return Ok(None);
2855 };
2856 let Some(targets) = self.try_resolve(repo, &prefix)? else {
2857 return Ok(None);
2858 };
2859 if let Some(offset) = offset {
2860 return Ok(targets.at_offset(offset).cloned());
2861 }
2862 match targets.visible_with_offsets().at_most_one() {
2863 Ok(maybe_resolved) => Ok(maybe_resolved.map(|(_, target)| target.clone())),
2864 Err(visible_targets) => Err(RevsetResolutionError::DivergentChangeId {
2865 symbol: change_id.to_owned(),
2866 visible_targets: visible_targets
2867 .map(|(i, target)| (i, target.clone()))
2868 .collect_vec(),
2869 }),
2870 }
2871 }
2872}
2873
2874pub trait SymbolResolverExtension: Send + Sync {
2881 fn new_resolvers<'a>(
2886 &self,
2887 context_repo: &'a dyn Repo,
2888 ) -> Vec<Box<dyn PartialSymbolResolver + 'a>>;
2889}
2890
2891pub struct SymbolResolver<'a> {
2894 commit_id_resolver: CommitPrefixResolver<'a>,
2895 change_id_resolver: ChangePrefixResolver<'a>,
2896 extensions: Vec<Box<dyn PartialSymbolResolver + 'a>>,
2897}
2898
2899impl<'a> SymbolResolver<'a> {
2900 pub fn new(
2903 context_repo: &'a dyn Repo,
2904 extensions: &[impl AsRef<dyn SymbolResolverExtension>],
2905 ) -> Self {
2906 SymbolResolver {
2907 commit_id_resolver: CommitPrefixResolver {
2908 context_repo,
2909 context: None,
2910 },
2911 change_id_resolver: ChangePrefixResolver {
2912 context_repo,
2913 context: None,
2914 },
2915 extensions: extensions
2916 .iter()
2917 .flat_map(|ext| ext.as_ref().new_resolvers(context_repo))
2918 .collect(),
2919 }
2920 }
2921
2922 pub fn with_id_prefix_context(mut self, id_prefix_context: &'a IdPrefixContext) -> Self {
2923 self.commit_id_resolver.context = Some(id_prefix_context);
2924 self.change_id_resolver.context = Some(id_prefix_context);
2925 self
2926 }
2927
2928 fn partial_resolvers(&self) -> impl Iterator<Item = &(dyn PartialSymbolResolver + 'a)> {
2929 let prefix_resolvers: [&dyn PartialSymbolResolver; 2] =
2930 [&self.commit_id_resolver, &self.change_id_resolver];
2931 itertools::chain!(
2932 DEFAULT_RESOLVERS.iter().copied(),
2933 prefix_resolvers,
2934 self.extensions.iter().map(|e| e.as_ref())
2935 )
2936 }
2937
2938 pub fn resolve_symbol(
2940 &self,
2941 repo: &dyn Repo,
2942 symbol: &str,
2943 ) -> Result<CommitId, RevsetResolutionError> {
2944 if symbol.is_empty() {
2945 return Err(RevsetResolutionError::EmptyString);
2946 }
2947
2948 for partial_resolver in self.partial_resolvers() {
2949 if let Some(id) = partial_resolver.resolve_symbol(repo, symbol)? {
2950 return Ok(id);
2951 }
2952 }
2953
2954 Err(make_no_such_symbol_error(repo, format_symbol(symbol)))
2955 }
2956}
2957
2958fn resolve_commit_ref(
2959 repo: &dyn Repo,
2960 commit_ref: &RevsetCommitRef,
2961 symbol_resolver: &SymbolResolver,
2962) -> Result<Vec<CommitId>, RevsetResolutionError> {
2963 match commit_ref {
2964 RevsetCommitRef::Symbol(symbol) => {
2965 let commit_id = symbol_resolver.resolve_symbol(repo, symbol)?;
2966 Ok(vec![commit_id])
2967 }
2968 RevsetCommitRef::RemoteSymbol(symbol) => {
2969 let commit_id = resolve_remote_symbol(repo, symbol.as_ref())?;
2970 Ok(vec![commit_id])
2971 }
2972 RevsetCommitRef::WorkingCopy(name) => {
2973 if let Some(commit_id) = repo.view().get_wc_commit_id(name) {
2974 Ok(vec![commit_id.clone()])
2975 } else {
2976 Err(RevsetResolutionError::WorkspaceMissingWorkingCopy { name: name.clone() })
2977 }
2978 }
2979 RevsetCommitRef::WorkingCopies => {
2980 let wc_commits = repo.view().wc_commit_ids().values().cloned().collect_vec();
2981 Ok(wc_commits)
2982 }
2983 RevsetCommitRef::ChangeId(prefix) => {
2984 let resolver = &symbol_resolver.change_id_resolver;
2985 Ok(resolver
2986 .try_resolve(repo, prefix)?
2987 .and_then(ResolvedChangeTargets::into_visible)
2988 .unwrap_or_else(Vec::new))
2989 }
2990 RevsetCommitRef::CommitId(prefix) => {
2991 let resolver = &symbol_resolver.commit_id_resolver;
2992 Ok(resolver.try_resolve(repo, prefix)?.into_iter().collect())
2993 }
2994 RevsetCommitRef::Bookmarks(expression) => {
2995 let commit_ids = repo
2996 .view()
2997 .local_bookmarks_matching(&expression.to_matcher())
2998 .flat_map(|(_, target)| target.added_ids())
2999 .cloned()
3000 .collect();
3001 Ok(commit_ids)
3002 }
3003 RevsetCommitRef::RemoteBookmarks {
3004 symbol,
3005 remote_ref_state,
3006 } => {
3007 let name_matcher = symbol.name.to_matcher();
3008 let remote_matcher = symbol.remote.to_matcher();
3009 let commit_ids = repo
3010 .view()
3011 .remote_bookmarks_matching(&name_matcher, &remote_matcher)
3012 .filter(|(_, remote_ref)| {
3013 remote_ref_state.is_none_or(|state| remote_ref.state == state)
3014 })
3015 .flat_map(|(_, remote_ref)| remote_ref.target.added_ids())
3016 .cloned()
3017 .collect();
3018 Ok(commit_ids)
3019 }
3020 RevsetCommitRef::Tags(expression) => {
3021 let commit_ids = repo
3022 .view()
3023 .local_tags_matching(&expression.to_matcher())
3024 .flat_map(|(_, target)| target.added_ids())
3025 .cloned()
3026 .collect();
3027 Ok(commit_ids)
3028 }
3029 RevsetCommitRef::RemoteTags {
3030 symbol,
3031 remote_ref_state,
3032 } => {
3033 let name_matcher = symbol.name.to_matcher();
3034 let remote_matcher = symbol.remote.to_matcher();
3035 let commit_ids = repo
3036 .view()
3037 .remote_tags_matching(&name_matcher, &remote_matcher)
3038 .filter(|(_, remote_ref)| {
3039 remote_ref_state.is_none_or(|state| remote_ref.state == state)
3040 })
3041 .flat_map(|(_, remote_ref)| remote_ref.target.added_ids())
3042 .cloned()
3043 .collect();
3044 Ok(commit_ids)
3045 }
3046 RevsetCommitRef::GitRefs => {
3047 let mut commit_ids = vec![];
3048 for ref_target in repo.view().git_refs().values() {
3049 commit_ids.extend(ref_target.added_ids().cloned());
3050 }
3051 Ok(commit_ids)
3052 }
3053 RevsetCommitRef::GitHead => Ok(repo.view().git_head().added_ids().cloned().collect()),
3054 }
3055}
3056
3057struct ExpressionSymbolResolver<'a, 'b> {
3059 base_repo: &'a dyn Repo,
3060 repo_stack: Vec<Arc<ReadonlyRepo>>,
3061 symbol_resolver: &'a SymbolResolver<'b>,
3062}
3063
3064impl<'a, 'b> ExpressionSymbolResolver<'a, 'b> {
3065 fn new(base_repo: &'a dyn Repo, symbol_resolver: &'a SymbolResolver<'b>) -> Self {
3066 Self {
3067 base_repo,
3068 repo_stack: vec![],
3069 symbol_resolver,
3070 }
3071 }
3072
3073 fn repo(&self) -> &dyn Repo {
3074 self.repo_stack
3075 .last()
3076 .map_or(self.base_repo, |repo| repo.as_ref())
3077 }
3078}
3079
3080impl ExpressionStateFolder<UserExpressionState, ResolvedExpressionState>
3081 for ExpressionSymbolResolver<'_, '_>
3082{
3083 type Error = RevsetResolutionError;
3084
3085 fn fold_expression(
3086 &mut self,
3087 expression: &UserRevsetExpression,
3088 ) -> Result<Arc<ResolvedRevsetExpression>, Self::Error> {
3089 match expression {
3090 RevsetExpression::Present(candidates) => {
3092 self.fold_expression(candidates).or_else(|err| match err {
3093 RevsetResolutionError::NoSuchRevision { .. }
3094 | RevsetResolutionError::WorkspaceMissingWorkingCopy { .. } => {
3095 Ok(RevsetExpression::none())
3096 }
3097 RevsetResolutionError::EmptyString
3098 | RevsetResolutionError::AmbiguousCommitIdPrefix(_)
3099 | RevsetResolutionError::AmbiguousChangeIdPrefix(_)
3100 | RevsetResolutionError::DivergentChangeId { .. }
3101 | RevsetResolutionError::ConflictedRef { .. }
3102 | RevsetResolutionError::Backend(_)
3103 | RevsetResolutionError::Other(_) => Err(err),
3104 })
3105 }
3106 _ => fold_child_expression_state(self, expression),
3107 }
3108 }
3109
3110 fn fold_commit_ref(
3111 &mut self,
3112 commit_ref: &RevsetCommitRef,
3113 ) -> Result<Arc<ResolvedRevsetExpression>, Self::Error> {
3114 let commit_ids = resolve_commit_ref(self.repo(), commit_ref, self.symbol_resolver)?;
3115 Ok(RevsetExpression::commits(commit_ids))
3116 }
3117
3118 fn fold_at_operation(
3119 &mut self,
3120 operation: &String,
3121 candidates: &UserRevsetExpression,
3122 ) -> Result<Arc<ResolvedRevsetExpression>, Self::Error> {
3123 let repo = reload_repo_at_operation(self.repo(), operation)?;
3124 self.repo_stack.push(repo);
3125 let candidates = self.fold_expression(candidates)?;
3126 let visible_heads = self.repo().view().heads().iter().cloned().collect();
3127 self.repo_stack.pop();
3128 Ok(Arc::new(RevsetExpression::WithinVisibility {
3129 candidates,
3130 visible_heads,
3131 }))
3132 }
3133}
3134
3135fn resolve_symbols(
3136 repo: &dyn Repo,
3137 expression: &UserRevsetExpression,
3138 symbol_resolver: &SymbolResolver,
3139) -> Result<Arc<ResolvedRevsetExpression>, RevsetResolutionError> {
3140 let mut resolver = ExpressionSymbolResolver::new(repo, symbol_resolver);
3141 resolver.fold_expression(expression)
3142}
3143
3144fn resolve_visibility(
3154 repo: &dyn Repo,
3155 expression: &ResolvedRevsetExpression,
3156) -> ResolvedExpression {
3157 let context = VisibilityResolutionContext {
3158 referenced_commits: &[],
3159 visible_heads: &repo.view().heads().iter().cloned().collect_vec(),
3160 root: repo.store().root_commit_id(),
3161 };
3162 context.resolve(expression)
3163}
3164
3165#[derive(Clone, Debug)]
3166struct VisibilityResolutionContext<'a> {
3167 referenced_commits: &'a [CommitId],
3168 visible_heads: &'a [CommitId],
3169 root: &'a CommitId,
3170}
3171
3172impl VisibilityResolutionContext<'_> {
3173 fn resolve(&self, expression: &ResolvedRevsetExpression) -> ResolvedExpression {
3175 match expression {
3176 RevsetExpression::None => ResolvedExpression::Commits(vec![]),
3177 RevsetExpression::All => self.resolve_all(),
3178 RevsetExpression::VisibleHeads => self.resolve_visible_heads(),
3179 RevsetExpression::VisibleHeadsOrReferenced => {
3180 self.resolve_visible_heads_or_referenced()
3181 }
3182 RevsetExpression::Root => self.resolve_root(),
3183 RevsetExpression::Commits(commit_ids) => {
3184 ResolvedExpression::Commits(commit_ids.clone())
3185 }
3186 RevsetExpression::CommitRef(commit_ref) => match *commit_ref {},
3187 RevsetExpression::Ancestors {
3188 heads,
3189 generation,
3190 parents_range,
3191 } => ResolvedExpression::Ancestors {
3192 heads: self.resolve(heads).into(),
3193 generation: generation.clone(),
3194 parents_range: parents_range.clone(),
3195 },
3196 RevsetExpression::Descendants { roots, generation } => ResolvedExpression::DagRange {
3197 roots: self.resolve(roots).into(),
3198 heads: self.resolve_visible_heads_or_referenced().into(),
3199 generation_from_roots: generation.clone(),
3200 },
3201 RevsetExpression::Range {
3202 roots,
3203 heads,
3204 generation,
3205 parents_range,
3206 } => ResolvedExpression::Range {
3207 roots: self.resolve(roots).into(),
3208 heads: self.resolve(heads).into(),
3209 generation: generation.clone(),
3210 parents_range: parents_range.clone(),
3211 },
3212 RevsetExpression::DagRange { roots, heads } => ResolvedExpression::DagRange {
3213 roots: self.resolve(roots).into(),
3214 heads: self.resolve(heads).into(),
3215 generation_from_roots: GENERATION_RANGE_FULL,
3216 },
3217 RevsetExpression::Reachable { sources, domain } => ResolvedExpression::Reachable {
3218 sources: self.resolve(sources).into(),
3219 domain: self.resolve(domain).into(),
3220 },
3221 RevsetExpression::Heads(candidates) => {
3222 ResolvedExpression::Heads(self.resolve(candidates).into())
3223 }
3224 RevsetExpression::HeadsRange {
3225 roots,
3226 heads,
3227 parents_range,
3228 filter,
3229 } => ResolvedExpression::HeadsRange {
3230 roots: self.resolve(roots).into(),
3231 heads: self.resolve(heads).into(),
3232 parents_range: parents_range.clone(),
3233 filter: (!matches!(filter.as_ref(), RevsetExpression::All))
3234 .then(|| self.resolve_predicate(filter)),
3235 },
3236 RevsetExpression::Roots(candidates) => {
3237 ResolvedExpression::Roots(self.resolve(candidates).into())
3238 }
3239 RevsetExpression::ForkPoint(expression) => {
3240 ResolvedExpression::ForkPoint(self.resolve(expression).into())
3241 }
3242 RevsetExpression::Bisect(expression) => {
3243 ResolvedExpression::Bisect(self.resolve(expression).into())
3244 }
3245 RevsetExpression::Latest { candidates, count } => ResolvedExpression::Latest {
3246 candidates: self.resolve(candidates).into(),
3247 count: *count,
3248 },
3249 RevsetExpression::HasSize { candidates, count } => ResolvedExpression::HasSize {
3250 candidates: self.resolve(candidates).into(),
3251 count: *count,
3252 },
3253 RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_) => {
3254 ResolvedExpression::FilterWithin {
3257 candidates: self.resolve_all().into(),
3258 predicate: self.resolve_predicate(expression),
3259 }
3260 }
3261 RevsetExpression::Divergent => ResolvedExpression::FilterWithin {
3262 candidates: self.resolve_all().into(),
3263 predicate: ResolvedPredicateExpression::Divergent {
3264 visible_heads: self.visible_heads.to_owned(),
3265 },
3266 },
3267 RevsetExpression::AtOperation { operation, .. } => match *operation {},
3268 RevsetExpression::WithinReference {
3269 candidates,
3270 commits,
3271 } => {
3272 let context = VisibilityResolutionContext {
3273 referenced_commits: commits,
3274 visible_heads: self.visible_heads,
3275 root: self.root,
3276 };
3277 context.resolve(candidates)
3278 }
3279 RevsetExpression::WithinVisibility {
3280 candidates,
3281 visible_heads,
3282 } => {
3283 let context = VisibilityResolutionContext {
3284 referenced_commits: self.referenced_commits,
3285 visible_heads,
3286 root: self.root,
3287 };
3288 context.resolve(candidates)
3289 }
3290 RevsetExpression::Coalesce(expression1, expression2) => ResolvedExpression::Coalesce(
3291 self.resolve(expression1).into(),
3292 self.resolve(expression2).into(),
3293 ),
3294 RevsetExpression::Present(candidates) => self.resolve(candidates),
3296 RevsetExpression::NotIn(complement) => ResolvedExpression::Difference(
3297 self.resolve_all().into(),
3298 self.resolve(complement).into(),
3299 ),
3300 RevsetExpression::Union(expression1, expression2) => ResolvedExpression::Union(
3301 self.resolve(expression1).into(),
3302 self.resolve(expression2).into(),
3303 ),
3304 RevsetExpression::Intersection(expression1, expression2) => {
3305 match expression2.as_ref() {
3306 RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_) => {
3307 ResolvedExpression::FilterWithin {
3308 candidates: self.resolve(expression1).into(),
3309 predicate: self.resolve_predicate(expression2),
3310 }
3311 }
3312 _ => ResolvedExpression::Intersection(
3313 self.resolve(expression1).into(),
3314 self.resolve(expression2).into(),
3315 ),
3316 }
3317 }
3318 RevsetExpression::Difference(expression1, expression2) => {
3319 ResolvedExpression::Difference(
3320 self.resolve(expression1).into(),
3321 self.resolve(expression2).into(),
3322 )
3323 }
3324 }
3325 }
3326
3327 fn resolve_all(&self) -> ResolvedExpression {
3328 ResolvedExpression::Ancestors {
3329 heads: self.resolve_visible_heads_or_referenced().into(),
3330 generation: GENERATION_RANGE_FULL,
3331 parents_range: PARENTS_RANGE_FULL,
3332 }
3333 }
3334
3335 fn resolve_visible_heads(&self) -> ResolvedExpression {
3336 ResolvedExpression::Commits(self.visible_heads.to_owned())
3337 }
3338
3339 fn resolve_visible_heads_or_referenced(&self) -> ResolvedExpression {
3340 let commits = itertools::chain(self.referenced_commits, self.visible_heads)
3345 .cloned()
3346 .collect();
3347 ResolvedExpression::Commits(commits)
3348 }
3349
3350 fn resolve_root(&self) -> ResolvedExpression {
3351 ResolvedExpression::Commits(vec![self.root.to_owned()])
3352 }
3353
3354 fn resolve_predicate(
3359 &self,
3360 expression: &ResolvedRevsetExpression,
3361 ) -> ResolvedPredicateExpression {
3362 match expression {
3363 RevsetExpression::None
3364 | RevsetExpression::All
3365 | RevsetExpression::VisibleHeads
3366 | RevsetExpression::VisibleHeadsOrReferenced
3367 | RevsetExpression::Root
3368 | RevsetExpression::Commits(_)
3369 | RevsetExpression::CommitRef(_)
3370 | RevsetExpression::Ancestors { .. }
3371 | RevsetExpression::Descendants { .. }
3372 | RevsetExpression::Range { .. }
3373 | RevsetExpression::DagRange { .. }
3374 | RevsetExpression::Reachable { .. }
3375 | RevsetExpression::Heads(_)
3376 | RevsetExpression::HeadsRange { .. }
3377 | RevsetExpression::Roots(_)
3378 | RevsetExpression::ForkPoint(_)
3379 | RevsetExpression::Bisect(_)
3380 | RevsetExpression::HasSize { .. }
3381 | RevsetExpression::Latest { .. } => {
3382 ResolvedPredicateExpression::Set(self.resolve(expression).into())
3383 }
3384 RevsetExpression::Filter(predicate) => {
3385 ResolvedPredicateExpression::Filter(predicate.clone())
3386 }
3387 RevsetExpression::AsFilter(candidates) => self.resolve_predicate(candidates),
3388 RevsetExpression::Divergent => ResolvedPredicateExpression::Divergent {
3389 visible_heads: self.visible_heads.to_owned(),
3390 },
3391 RevsetExpression::AtOperation { operation, .. } => match *operation {},
3392 RevsetExpression::WithinReference { .. }
3394 | RevsetExpression::WithinVisibility { .. } => {
3395 ResolvedPredicateExpression::Set(self.resolve(expression).into())
3396 }
3397 RevsetExpression::Coalesce(_, _) => {
3398 ResolvedPredicateExpression::Set(self.resolve(expression).into())
3399 }
3400 RevsetExpression::Present(candidates) => self.resolve_predicate(candidates),
3402 RevsetExpression::NotIn(complement) => {
3403 ResolvedPredicateExpression::NotIn(self.resolve_predicate(complement).into())
3404 }
3405 RevsetExpression::Union(expression1, expression2) => {
3406 let predicate1 = self.resolve_predicate(expression1);
3407 let predicate2 = self.resolve_predicate(expression2);
3408 ResolvedPredicateExpression::Union(predicate1.into(), predicate2.into())
3409 }
3410 RevsetExpression::Intersection(expression1, expression2) => {
3411 let predicate1 = self.resolve_predicate(expression1);
3412 let predicate2 = self.resolve_predicate(expression2);
3413 ResolvedPredicateExpression::Intersection(predicate1.into(), predicate2.into())
3414 }
3415 RevsetExpression::Difference(expression1, expression2) => {
3416 let predicate1 = self.resolve_predicate(expression1);
3417 let predicate2 = self.resolve_predicate(expression2);
3418 let predicate2 = ResolvedPredicateExpression::NotIn(predicate2.into());
3419 ResolvedPredicateExpression::Intersection(predicate1.into(), predicate2.into())
3420 }
3421 }
3422 }
3423}
3424
3425pub trait Revset: fmt::Debug {
3426 fn stream<'a>(&self) -> LocalBoxStream<'a, Result<CommitId, RevsetEvaluationError>>
3429 where
3430 Self: 'a;
3431
3432 fn commit_change_ids<'a>(
3434 &self,
3435 ) -> Box<dyn Iterator<Item = Result<(CommitId, ChangeId), RevsetEvaluationError>> + 'a>
3436 where
3437 Self: 'a;
3438
3439 fn stream_graph<'a>(
3442 &self,
3443 ) -> LocalBoxStream<'a, Result<GraphNode<CommitId>, RevsetEvaluationError>>
3444 where
3445 Self: 'a;
3446
3447 fn is_empty(&self) -> bool;
3449
3450 fn count_estimate(&self) -> Result<(usize, Option<usize>), RevsetEvaluationError>;
3455
3456 fn containing_fn<'a>(&self) -> Box<RevsetContainingFn<'a>>
3462 where
3463 Self: 'a;
3464}
3465
3466pub type RevsetContainingFn<'a> = dyn Fn(&CommitId) -> Result<bool, RevsetEvaluationError> + 'a;
3468
3469pub trait RevsetStreamExt {
3470 fn commits(
3471 self,
3472 store: &Arc<Store>,
3473 ) -> impl Stream<Item = Result<Commit, RevsetEvaluationError>> + use<'_, Self>;
3474}
3475
3476impl<S: Stream<Item = Result<CommitId, RevsetEvaluationError>>> RevsetStreamExt for S {
3477 fn commits(
3478 self,
3479 store: &Arc<Store>,
3480 ) -> impl Stream<Item = Result<Commit, RevsetEvaluationError>> + use<'_, S> {
3481 self.map(async move |result| {
3482 let commit_id = result?;
3483 let commit = store
3484 .get_commit_async(&commit_id)
3485 .await
3486 .map_err(RevsetEvaluationError::Backend)?;
3487 Ok(commit)
3488 })
3489 .buffered(store.concurrency())
3490 }
3491}
3492
3493pub struct RevsetExtensions {
3495 symbol_resolvers: Vec<Box<dyn SymbolResolverExtension>>,
3496 function_map: HashMap<&'static str, RevsetFunction>,
3497}
3498
3499impl Default for RevsetExtensions {
3500 fn default() -> Self {
3501 Self::new()
3502 }
3503}
3504
3505impl RevsetExtensions {
3506 pub fn new() -> Self {
3507 Self {
3508 symbol_resolvers: vec![],
3509 function_map: BUILTIN_FUNCTION_MAP.clone(),
3510 }
3511 }
3512
3513 pub fn symbol_resolvers(&self) -> &[Box<dyn SymbolResolverExtension>] {
3514 &self.symbol_resolvers
3515 }
3516
3517 pub fn add_symbol_resolver(&mut self, symbol_resolver: Box<dyn SymbolResolverExtension>) {
3518 self.symbol_resolvers.push(symbol_resolver);
3519 }
3520
3521 pub fn add_custom_function(&mut self, name: &'static str, func: RevsetFunction) {
3522 match self.function_map.entry(name) {
3523 hash_map::Entry::Occupied(_) => {
3524 panic!("Conflict registering revset function '{name}'")
3525 }
3526 hash_map::Entry::Vacant(v) => v.insert(func),
3527 };
3528 }
3529}
3530
3531#[derive(Clone)]
3533pub struct RevsetParseContext<'a> {
3534 pub aliases_map: &'a RevsetAliasesMap,
3535 pub local_variables: HashMap<&'a str, ExpressionNode<'a>>,
3536 pub user_email: &'a str,
3537 pub date_pattern_context: DatePatternContext,
3538 pub default_ignored_remote: Option<&'a RemoteName>,
3540 pub fileset_aliases_map: &'a FilesetAliasesMap,
3541 pub use_glob_by_default: bool,
3542 pub extensions: &'a RevsetExtensions,
3543 pub workspace: Option<RevsetWorkspaceContext<'a>>,
3544}
3545
3546impl<'a> RevsetParseContext<'a> {
3547 fn to_lowering_context(&self) -> LoweringContext<'a> {
3548 let RevsetParseContext {
3549 aliases_map: _,
3550 local_variables: _,
3551 user_email,
3552 date_pattern_context,
3553 default_ignored_remote,
3554 fileset_aliases_map,
3555 use_glob_by_default,
3556 extensions,
3557 workspace,
3558 } = *self;
3559 LoweringContext {
3560 user_email,
3561 date_pattern_context,
3562 default_ignored_remote,
3563 fileset_aliases_map,
3564 use_glob_by_default,
3565 extensions,
3566 workspace,
3567 }
3568 }
3569}
3570
3571#[derive(Clone)]
3573pub struct LoweringContext<'a> {
3574 user_email: &'a str,
3575 date_pattern_context: DatePatternContext,
3576 default_ignored_remote: Option<&'a RemoteName>,
3577 fileset_aliases_map: &'a FilesetAliasesMap,
3578 use_glob_by_default: bool,
3579 extensions: &'a RevsetExtensions,
3580 workspace: Option<RevsetWorkspaceContext<'a>>,
3581}
3582
3583impl<'a> LoweringContext<'a> {
3584 pub fn user_email(&self) -> &'a str {
3585 self.user_email
3586 }
3587
3588 pub fn date_pattern_context(&self) -> &DatePatternContext {
3589 &self.date_pattern_context
3590 }
3591
3592 pub fn fileset_parse_context(&self) -> Option<FilesetParseContext<'_>> {
3593 Some(FilesetParseContext {
3594 aliases_map: self.fileset_aliases_map,
3595 path_converter: self.workspace?.path_converter,
3596 })
3597 }
3598
3599 pub fn symbol_resolvers(&self) -> &'a [impl AsRef<dyn SymbolResolverExtension> + use<>] {
3600 self.extensions.symbol_resolvers()
3601 }
3602}
3603
3604#[derive(Clone, Copy, Debug)]
3606pub struct RevsetWorkspaceContext<'a> {
3607 pub path_converter: &'a RepoPathUiConverter,
3608 pub workspace_name: &'a WorkspaceName,
3609}
3610
3611pub fn format_symbol(literal: &str) -> String {
3617 if revset_parser::is_identifier(literal) {
3618 literal.to_string()
3619 } else {
3620 format_string(literal)
3621 }
3622}
3623
3624pub fn format_string(literal: &str) -> String {
3626 format!(r#""{}""#, dsl_util::escape_string(literal))
3627}
3628
3629pub fn format_remote_symbol(name: &str, remote: &str) -> String {
3631 let name = format_symbol(name);
3632 let remote = format_symbol(remote);
3633 format!("{name}@{remote}")
3634}
3635
3636#[cfg(test)]
3637#[rustversion::attr(
3638 since(1.89),
3639 expect(clippy::cloned_ref_to_slice_refs, reason = "makes tests more readable")
3640)]
3641mod tests {
3642 use std::path::PathBuf;
3643
3644 use assert_matches::assert_matches;
3645
3646 use super::*;
3647 use crate::tests::TestResult;
3648
3649 fn parse(revset_str: &str) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
3650 parse_with_aliases(revset_str, [] as [(&str, &str); 0])
3651 }
3652
3653 fn parse_with_workspace(
3654 revset_str: &str,
3655 workspace_name: &WorkspaceName,
3656 ) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
3657 parse_with_aliases_and_workspace(revset_str, [] as [(&str, &str); 0], workspace_name)
3658 }
3659
3660 fn parse_with_aliases(
3661 revset_str: &str,
3662 aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
3663 ) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
3664 let mut aliases_map = RevsetAliasesMap::new();
3665 for (decl, defn) in aliases {
3666 aliases_map.insert(decl, defn)?;
3667 }
3668 let context = RevsetParseContext {
3669 aliases_map: &aliases_map,
3670 local_variables: HashMap::new(),
3671 user_email: "test.user@example.com",
3672 date_pattern_context: chrono::Utc::now().fixed_offset().into(),
3673 default_ignored_remote: Some("ignored".as_ref()),
3674 fileset_aliases_map: &FilesetAliasesMap::new(),
3675 use_glob_by_default: true,
3676 extensions: &RevsetExtensions::default(),
3677 workspace: None,
3678 };
3679 super::parse(&mut RevsetDiagnostics::new(), revset_str, &context)
3680 }
3681
3682 fn parse_with_aliases_and_workspace(
3683 revset_str: &str,
3684 aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
3685 workspace_name: &WorkspaceName,
3686 ) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
3687 let path_converter = RepoPathUiConverter::Fs {
3689 cwd: PathBuf::from("/"),
3690 base: PathBuf::from("/"),
3691 };
3692 let workspace_ctx = RevsetWorkspaceContext {
3693 path_converter: &path_converter,
3694 workspace_name,
3695 };
3696 let mut aliases_map = RevsetAliasesMap::new();
3697 for (decl, defn) in aliases {
3698 aliases_map.insert(decl, defn)?;
3699 }
3700 let context = RevsetParseContext {
3701 aliases_map: &aliases_map,
3702 local_variables: HashMap::new(),
3703 user_email: "test.user@example.com",
3704 date_pattern_context: chrono::Utc::now().fixed_offset().into(),
3705 default_ignored_remote: Some("ignored".as_ref()),
3706 fileset_aliases_map: &FilesetAliasesMap::new(),
3707 use_glob_by_default: true,
3708 extensions: &RevsetExtensions::default(),
3709 workspace: Some(workspace_ctx),
3710 };
3711 super::parse(&mut RevsetDiagnostics::new(), revset_str, &context)
3712 }
3713
3714 fn insta_settings() -> insta::Settings {
3715 let mut settings = insta::Settings::clone_current();
3716 for _ in 0..4 {
3719 settings.add_filter(
3720 r"(?x)
3721 \b([A-Z]\w*)\(\n
3722 \s*(.{1,60}),\n
3723 \s*\)",
3724 "$1($2)",
3725 );
3726 }
3727 settings
3728 }
3729
3730 #[test]
3731 #[expect(clippy::redundant_clone)] fn test_revset_expression_building() {
3733 let settings = insta_settings();
3734 let _guard = settings.bind_to_scope();
3735 let current_wc = UserRevsetExpression::working_copy(WorkspaceName::DEFAULT.to_owned());
3736 let foo_symbol = UserRevsetExpression::symbol("foo".to_string());
3737 let bar_symbol = UserRevsetExpression::symbol("bar".to_string());
3738 let baz_symbol = UserRevsetExpression::symbol("baz".to_string());
3739
3740 insta::assert_debug_snapshot!(
3741 current_wc,
3742 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
3743 insta::assert_debug_snapshot!(
3744 current_wc.heads(),
3745 @r#"Heads(CommitRef(WorkingCopy(WorkspaceNameBuf("default"))))"#);
3746 insta::assert_debug_snapshot!(
3747 current_wc.roots(),
3748 @r#"Roots(CommitRef(WorkingCopy(WorkspaceNameBuf("default"))))"#);
3749 insta::assert_debug_snapshot!(
3750 current_wc.parents(), @r#"
3751 Ancestors {
3752 heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3753 generation: 1..2,
3754 parents_range: 0..4294967295,
3755 }
3756 "#);
3757 insta::assert_debug_snapshot!(
3758 current_wc.ancestors(), @r#"
3759 Ancestors {
3760 heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3761 generation: 0..18446744073709551615,
3762 parents_range: 0..4294967295,
3763 }
3764 "#);
3765 insta::assert_debug_snapshot!(
3766 foo_symbol.children(), @r#"
3767 Descendants {
3768 roots: CommitRef(Symbol("foo")),
3769 generation: 1..2,
3770 }
3771 "#);
3772 insta::assert_debug_snapshot!(
3773 foo_symbol.descendants(), @r#"
3774 Descendants {
3775 roots: CommitRef(Symbol("foo")),
3776 generation: 0..18446744073709551615,
3777 }
3778 "#);
3779 insta::assert_debug_snapshot!(
3780 foo_symbol.dag_range_to(¤t_wc), @r#"
3781 DagRange {
3782 roots: CommitRef(Symbol("foo")),
3783 heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3784 }
3785 "#);
3786 insta::assert_debug_snapshot!(
3787 foo_symbol.connected(), @r#"
3788 DagRange {
3789 roots: CommitRef(Symbol("foo")),
3790 heads: CommitRef(Symbol("foo")),
3791 }
3792 "#);
3793 insta::assert_debug_snapshot!(
3794 foo_symbol.range(¤t_wc), @r#"
3795 Range {
3796 roots: CommitRef(Symbol("foo")),
3797 heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3798 generation: 0..18446744073709551615,
3799 parents_range: 0..4294967295,
3800 }
3801 "#);
3802 insta::assert_debug_snapshot!(
3803 foo_symbol.negated(),
3804 @r#"NotIn(CommitRef(Symbol("foo")))"#);
3805 insta::assert_debug_snapshot!(
3806 foo_symbol.union(¤t_wc), @r#"
3807 Union(
3808 CommitRef(Symbol("foo")),
3809 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3810 )
3811 "#);
3812 insta::assert_debug_snapshot!(
3813 UserRevsetExpression::union_all(&[]),
3814 @"None");
3815 insta::assert_debug_snapshot!(
3816 RevsetExpression::union_all(&[current_wc.clone()]),
3817 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
3818 insta::assert_debug_snapshot!(
3819 RevsetExpression::union_all(&[current_wc.clone(), foo_symbol.clone()]),
3820 @r#"
3821 Union(
3822 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3823 CommitRef(Symbol("foo")),
3824 )
3825 "#);
3826 insta::assert_debug_snapshot!(
3827 RevsetExpression::union_all(&[
3828 current_wc.clone(),
3829 foo_symbol.clone(),
3830 bar_symbol.clone(),
3831 ]),
3832 @r#"
3833 Union(
3834 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3835 Union(
3836 CommitRef(Symbol("foo")),
3837 CommitRef(Symbol("bar")),
3838 ),
3839 )
3840 "#);
3841 insta::assert_debug_snapshot!(
3842 RevsetExpression::union_all(&[
3843 current_wc.clone(),
3844 foo_symbol.clone(),
3845 bar_symbol.clone(),
3846 baz_symbol.clone(),
3847 ]),
3848 @r#"
3849 Union(
3850 Union(
3851 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3852 CommitRef(Symbol("foo")),
3853 ),
3854 Union(
3855 CommitRef(Symbol("bar")),
3856 CommitRef(Symbol("baz")),
3857 ),
3858 )
3859 "#);
3860 insta::assert_debug_snapshot!(
3861 foo_symbol.intersection(¤t_wc), @r#"
3862 Intersection(
3863 CommitRef(Symbol("foo")),
3864 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3865 )
3866 "#);
3867 insta::assert_debug_snapshot!(
3868 foo_symbol.minus(¤t_wc), @r#"
3869 Difference(
3870 CommitRef(Symbol("foo")),
3871 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3872 )
3873 "#);
3874 insta::assert_debug_snapshot!(
3875 UserRevsetExpression::coalesce(&[]),
3876 @"None");
3877 insta::assert_debug_snapshot!(
3878 RevsetExpression::coalesce(&[current_wc.clone()]),
3879 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
3880 insta::assert_debug_snapshot!(
3881 RevsetExpression::coalesce(&[current_wc.clone(), foo_symbol.clone()]),
3882 @r#"
3883 Coalesce(
3884 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3885 CommitRef(Symbol("foo")),
3886 )
3887 "#);
3888 insta::assert_debug_snapshot!(
3889 RevsetExpression::coalesce(&[
3890 current_wc.clone(),
3891 foo_symbol.clone(),
3892 bar_symbol.clone(),
3893 ]),
3894 @r#"
3895 Coalesce(
3896 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3897 Coalesce(
3898 CommitRef(Symbol("foo")),
3899 CommitRef(Symbol("bar")),
3900 ),
3901 )
3902 "#);
3903 }
3904
3905 #[test]
3906 fn test_parse_revset() -> TestResult {
3907 let settings = insta_settings();
3908 let _guard = settings.bind_to_scope();
3909 let main_workspace_name = WorkspaceNameBuf::from("main");
3910 let other_workspace_name = WorkspaceNameBuf::from("other");
3911
3912 insta::assert_debug_snapshot!(
3914 parse("@").unwrap_err().kind(),
3915 @"WorkingCopyWithoutWorkspace");
3916 insta::assert_debug_snapshot!(
3917 parse("main@")?,
3918 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
3919 insta::assert_debug_snapshot!(
3920 parse_with_workspace("@", &main_workspace_name)?,
3921 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
3922 insta::assert_debug_snapshot!(
3923 parse_with_workspace("main@", &other_workspace_name)?,
3924 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
3925 insta::assert_debug_snapshot!(
3927 parse("author_name(foo@)").unwrap_err().kind(),
3928 @r#"Expression("Invalid string expression")"#);
3929 insta::assert_debug_snapshot!(
3930 parse(r#"author_name("foo@")"#)?,
3931 @r#"Filter(AuthorName(Pattern(Exact("foo@"))))"#);
3932 insta::assert_debug_snapshot!(
3934 parse("foo")?,
3935 @r#"CommitRef(Symbol("foo"))"#);
3936 insta::assert_debug_snapshot!(
3938 parse("bookmarks()")?,
3939 @r#"CommitRef(Bookmarks(Pattern(Substring(""))))"#);
3940 insta::assert_debug_snapshot!(
3942 parse("tags()")?,
3943 @r#"CommitRef(Tags(Pattern(Substring(""))))"#);
3944 insta::assert_debug_snapshot!(parse("remote_bookmarks()")?, @r#"
3945 CommitRef(
3946 RemoteBookmarks {
3947 symbol: RemoteRefSymbolExpression {
3948 name: Pattern(Substring("")),
3949 remote: NotIn(Pattern(Exact("ignored"))),
3950 },
3951 remote_ref_state: None,
3952 },
3953 )
3954 "#);
3955 insta::assert_debug_snapshot!(parse("tracked_remote_bookmarks()")?, @r#"
3956 CommitRef(
3957 RemoteBookmarks {
3958 symbol: RemoteRefSymbolExpression {
3959 name: Pattern(Substring("")),
3960 remote: NotIn(Pattern(Exact("ignored"))),
3961 },
3962 remote_ref_state: Some(Tracked),
3963 },
3964 )
3965 "#);
3966 insta::assert_debug_snapshot!(parse("untracked_remote_bookmarks()")?, @r#"
3967 CommitRef(
3968 RemoteBookmarks {
3969 symbol: RemoteRefSymbolExpression {
3970 name: Pattern(Substring("")),
3971 remote: NotIn(Pattern(Exact("ignored"))),
3972 },
3973 remote_ref_state: Some(New),
3974 },
3975 )
3976 "#);
3977 insta::assert_debug_snapshot!(parse("remote_tags()")?, @r#"
3978 CommitRef(
3979 RemoteTags {
3980 symbol: RemoteRefSymbolExpression {
3981 name: Pattern(Substring("")),
3982 remote: NotIn(Pattern(Exact("ignored"))),
3983 },
3984 remote_ref_state: None,
3985 },
3986 )
3987 "#);
3988 insta::assert_debug_snapshot!(parse("tracked_remote_tags()")?, @r#"
3989 CommitRef(
3990 RemoteTags {
3991 symbol: RemoteRefSymbolExpression {
3992 name: Pattern(Substring("")),
3993 remote: NotIn(Pattern(Exact("ignored"))),
3994 },
3995 remote_ref_state: Some(Tracked),
3996 },
3997 )
3998 "#);
3999 insta::assert_debug_snapshot!(parse("untracked_remote_tags()")?, @r#"
4000 CommitRef(
4001 RemoteTags {
4002 symbol: RemoteRefSymbolExpression {
4003 name: Pattern(Substring("")),
4004 remote: NotIn(Pattern(Exact("ignored"))),
4005 },
4006 remote_ref_state: Some(New),
4007 },
4008 )
4009 "#);
4010 insta::assert_debug_snapshot!(
4012 parse("'foo'")?,
4013 @r#"CommitRef(Symbol("foo"))"#);
4014 insta::assert_debug_snapshot!(parse("foo-")?, @r#"
4016 Ancestors {
4017 heads: CommitRef(Symbol("foo")),
4018 generation: 1..2,
4019 parents_range: 0..4294967295,
4020 }
4021 "#);
4022 insta::assert_debug_snapshot!(parse("foo+")?, @r#"
4024 Descendants {
4025 roots: CommitRef(Symbol("foo")),
4026 generation: 1..2,
4027 }
4028 "#);
4029 insta::assert_debug_snapshot!(parse("::foo")?, @r#"
4031 Ancestors {
4032 heads: CommitRef(Symbol("foo")),
4033 generation: 0..18446744073709551615,
4034 parents_range: 0..4294967295,
4035 }
4036 "#);
4037 insta::assert_debug_snapshot!(parse("foo::")?, @r#"
4039 Descendants {
4040 roots: CommitRef(Symbol("foo")),
4041 generation: 0..18446744073709551615,
4042 }
4043 "#);
4044 insta::assert_debug_snapshot!(parse("foo::bar")?, @r#"
4046 DagRange {
4047 roots: CommitRef(Symbol("foo")),
4048 heads: CommitRef(Symbol("bar")),
4049 }
4050 "#);
4051 insta::assert_debug_snapshot!(parse("::")?, @"All");
4053 insta::assert_debug_snapshot!(parse("..foo")?, @r#"
4055 Range {
4056 roots: Root,
4057 heads: CommitRef(Symbol("foo")),
4058 generation: 0..18446744073709551615,
4059 parents_range: 0..4294967295,
4060 }
4061 "#);
4062 insta::assert_debug_snapshot!(parse("foo..")?, @r#"
4063 NotIn(
4064 Ancestors {
4065 heads: CommitRef(Symbol("foo")),
4066 generation: 0..18446744073709551615,
4067 parents_range: 0..4294967295,
4068 },
4069 )
4070 "#);
4071 insta::assert_debug_snapshot!(parse("foo..bar")?, @r#"
4072 Range {
4073 roots: CommitRef(Symbol("foo")),
4074 heads: CommitRef(Symbol("bar")),
4075 generation: 0..18446744073709551615,
4076 parents_range: 0..4294967295,
4077 }
4078 "#);
4079 insta::assert_debug_snapshot!(parse("..")?, @"NotIn(Root)");
4081 insta::assert_debug_snapshot!(
4083 parse("~ foo")?,
4084 @r#"NotIn(CommitRef(Symbol("foo")))"#);
4085 insta::assert_debug_snapshot!(parse("foo & bar")?, @r#"
4087 Intersection(
4088 CommitRef(Symbol("foo")),
4089 CommitRef(Symbol("bar")),
4090 )
4091 "#);
4092 insta::assert_debug_snapshot!(parse("foo | bar")?, @r#"
4094 Union(
4095 CommitRef(Symbol("foo")),
4096 CommitRef(Symbol("bar")),
4097 )
4098 "#);
4099 insta::assert_debug_snapshot!(parse("foo ~ bar")?, @r#"
4101 Difference(
4102 CommitRef(Symbol("foo")),
4103 CommitRef(Symbol("bar")),
4104 )
4105 "#);
4106 Ok(())
4107 }
4108
4109 #[test]
4110 fn test_parse_string_pattern() -> TestResult {
4111 let settings = insta_settings();
4112 let _guard = settings.bind_to_scope();
4113
4114 insta::assert_debug_snapshot!(
4115 parse(r#"bookmarks("foo")"#)?,
4116 @r#"CommitRef(Bookmarks(Pattern(Exact("foo"))))"#);
4117 insta::assert_debug_snapshot!(
4118 parse(r#"bookmarks(exact:"foo")"#)?,
4119 @r#"CommitRef(Bookmarks(Pattern(Exact("foo"))))"#);
4120 insta::assert_debug_snapshot!(
4121 parse(r#"bookmarks(substring:"foo")"#)?,
4122 @r#"CommitRef(Bookmarks(Pattern(Substring("foo"))))"#);
4123 insta::assert_debug_snapshot!(
4124 parse(r#"bookmarks(bad:"foo")"#).unwrap_err().kind(),
4125 @r#"Expression("Invalid string pattern")"#);
4126 insta::assert_debug_snapshot!(
4127 parse(r#"bookmarks(exact::"foo")"#).unwrap_err().kind(),
4128 @r#"Expression("Invalid string expression")"#);
4129 insta::assert_debug_snapshot!(
4130 parse(r#"bookmarks(exact:"foo"+)"#).unwrap_err().kind(),
4131 @r#"Expression("Expected string")"#);
4132
4133 insta::assert_debug_snapshot!(
4134 parse(r#"tags("foo")"#)?,
4135 @r#"CommitRef(Tags(Pattern(Exact("foo"))))"#);
4136 insta::assert_debug_snapshot!(
4137 parse(r#"tags(exact:"foo")"#)?,
4138 @r#"CommitRef(Tags(Pattern(Exact("foo"))))"#);
4139 insta::assert_debug_snapshot!(
4140 parse(r#"tags(substring:"foo")"#)?,
4141 @r#"CommitRef(Tags(Pattern(Substring("foo"))))"#);
4142 insta::assert_debug_snapshot!(
4143 parse(r#"tags(bad:"foo")"#).unwrap_err().kind(),
4144 @r#"Expression("Invalid string pattern")"#);
4145 insta::assert_debug_snapshot!(
4146 parse(r#"tags(exact::"foo")"#).unwrap_err().kind(),
4147 @r#"Expression("Invalid string expression")"#);
4148 insta::assert_debug_snapshot!(
4149 parse(r#"tags(exact:"foo"+)"#).unwrap_err().kind(),
4150 @r#"Expression("Expected string")"#);
4151
4152 assert_matches!(
4154 parse(r#"(exact:"foo")"#).unwrap_err().kind(),
4155 RevsetParseErrorKind::NotInfixOperator { .. }
4156 );
4157 Ok(())
4158 }
4159
4160 #[test]
4161 fn test_parse_compound_string_expression() -> TestResult {
4162 let settings = insta_settings();
4163 let _guard = settings.bind_to_scope();
4164
4165 insta::assert_debug_snapshot!(
4166 parse(r#"tags(~a)"#)?,
4167 @r#"
4168 CommitRef(
4169 Tags(NotIn(Pattern(Exact("a")))),
4170 )
4171 "#);
4172 insta::assert_debug_snapshot!(
4173 parse(r#"tags(a|b&c)"#)?,
4174 @r#"
4175 CommitRef(
4176 Tags(
4177 Union(
4178 Pattern(Exact("a")),
4179 Intersection(
4180 Pattern(Exact("b")),
4181 Pattern(Exact("c")),
4182 ),
4183 ),
4184 ),
4185 )
4186 "#);
4187 insta::assert_debug_snapshot!(
4188 parse(r#"tags(a|b|c)"#)?,
4189 @r#"
4190 CommitRef(
4191 Tags(
4192 Union(
4193 Pattern(Exact("a")),
4194 Union(
4195 Pattern(Exact("b")),
4196 Pattern(Exact("c")),
4197 ),
4198 ),
4199 ),
4200 )
4201 "#);
4202 insta::assert_debug_snapshot!(
4203 parse(r#"tags(a~(b|c))"#)?,
4204 @r#"
4205 CommitRef(
4206 Tags(
4207 Intersection(
4208 Pattern(Exact("a")),
4209 NotIn(
4210 Union(
4211 Pattern(Exact("b")),
4212 Pattern(Exact("c")),
4213 ),
4214 ),
4215 ),
4216 ),
4217 )
4218 "#);
4219 Ok(())
4220 }
4221
4222 #[test]
4223 fn test_parse_revset_function() -> TestResult {
4224 let settings = insta_settings();
4225 let _guard = settings.bind_to_scope();
4226
4227 insta::assert_debug_snapshot!(
4228 parse("parents(foo)")?, @r#"
4229 Ancestors {
4230 heads: CommitRef(Symbol("foo")),
4231 generation: 1..2,
4232 parents_range: 0..4294967295,
4233 }
4234 "#);
4235 insta::assert_debug_snapshot!(
4236 parse("parents(\"foo\")")?, @r#"
4237 Ancestors {
4238 heads: CommitRef(Symbol("foo")),
4239 generation: 1..2,
4240 parents_range: 0..4294967295,
4241 }
4242 "#);
4243 insta::assert_debug_snapshot!(
4244 parse("ancestors(parents(foo))")?, @r#"
4245 Ancestors {
4246 heads: Ancestors {
4247 heads: CommitRef(Symbol("foo")),
4248 generation: 1..2,
4249 parents_range: 0..4294967295,
4250 },
4251 generation: 0..18446744073709551615,
4252 parents_range: 0..4294967295,
4253 }
4254 "#);
4255 insta::assert_debug_snapshot!(
4256 parse("parents(foo, bar, baz)").unwrap_err().kind(), @r#"
4257 InvalidFunctionArguments {
4258 name: "parents",
4259 message: "Expected 1 to 2 arguments",
4260 }
4261 "#);
4262 insta::assert_debug_snapshot!(
4263 parse("parents(foo, 2)")?, @r#"
4264 Ancestors {
4265 heads: CommitRef(Symbol("foo")),
4266 generation: 2..3,
4267 parents_range: 0..4294967295,
4268 }
4269 "#);
4270 insta::assert_debug_snapshot!(
4271 parse("root()")?,
4272 @"Root");
4273 assert!(parse("root(a)").is_err());
4274 insta::assert_debug_snapshot!(
4275 parse(r#"description("")"#)?,
4276 @r#"Filter(Description(Pattern(Exact(""))))"#);
4277 insta::assert_debug_snapshot!(
4278 parse("description(foo)")?,
4279 @r#"Filter(Description(Pattern(Exact("foo"))))"#);
4280 insta::assert_debug_snapshot!(
4281 parse("description(visible_heads())").unwrap_err().kind(),
4282 @r#"Expression("Invalid string expression")"#);
4283 insta::assert_debug_snapshot!(
4284 parse("description(\"(foo)\")")?,
4285 @r#"Filter(Description(Pattern(Exact("(foo)"))))"#);
4286 assert!(parse("mine(foo)").is_err());
4287 insta::assert_debug_snapshot!(
4288 parse_with_workspace("empty()", WorkspaceName::DEFAULT)?,
4289 @"NotIn(Filter(File(All)))");
4290 assert!(parse_with_workspace("empty(foo)", WorkspaceName::DEFAULT).is_err());
4291 assert!(parse_with_workspace("file()", WorkspaceName::DEFAULT).is_err());
4292 insta::assert_debug_snapshot!(
4293 parse_with_workspace("files(foo)", WorkspaceName::DEFAULT)?,
4294 @r#"Filter(File(Pattern(PrefixPath("foo"))))"#);
4295 insta::assert_debug_snapshot!(
4296 parse_with_workspace("files(all())", WorkspaceName::DEFAULT)?,
4297 @"Filter(File(All))");
4298 insta::assert_debug_snapshot!(
4299 parse_with_workspace(r#"files(file:"foo")"#, WorkspaceName::DEFAULT)?,
4300 @r#"Filter(File(Pattern(FilePath("foo"))))"#);
4301 insta::assert_debug_snapshot!(
4302 parse_with_workspace("files(foo|bar&baz)", WorkspaceName::DEFAULT)?, @r#"
4303 Filter(
4304 File(
4305 UnionAll(
4306 [
4307 Pattern(PrefixPath("foo")),
4308 Intersection(
4309 Pattern(PrefixPath("bar")),
4310 Pattern(PrefixPath("baz")),
4311 ),
4312 ],
4313 ),
4314 ),
4315 )
4316 "#);
4317 insta::assert_debug_snapshot!(
4318 parse_with_workspace(r#"files(~(foo))"#, WorkspaceName::DEFAULT)?,
4319 @r#"
4320 Filter(
4321 File(
4322 Difference(
4323 All,
4324 Pattern(PrefixPath("foo")),
4325 ),
4326 ),
4327 )
4328 "#);
4329 insta::assert_debug_snapshot!(parse("signed()")?, @"Filter(Signed)");
4330 Ok(())
4331 }
4332
4333 #[test]
4334 fn test_parse_revset_change_commit_id_functions() -> TestResult {
4335 let settings = insta_settings();
4336 let _guard = settings.bind_to_scope();
4337
4338 insta::assert_debug_snapshot!(
4339 parse("change_id(z)")?,
4340 @r#"CommitRef(ChangeId(HexPrefix("0")))"#);
4341 insta::assert_debug_snapshot!(
4342 parse("change_id('zk')")?,
4343 @r#"CommitRef(ChangeId(HexPrefix("0f")))"#);
4344 insta::assert_debug_snapshot!(
4345 parse("change_id(01234)").unwrap_err().kind(),
4346 @r#"Expression("Invalid change ID prefix")"#);
4347
4348 insta::assert_debug_snapshot!(
4349 parse("commit_id(0)")?,
4350 @r#"CommitRef(CommitId(HexPrefix("0")))"#);
4351 insta::assert_debug_snapshot!(
4352 parse("commit_id('0f')")?,
4353 @r#"CommitRef(CommitId(HexPrefix("0f")))"#);
4354 insta::assert_debug_snapshot!(
4355 parse("commit_id(xyzzy)").unwrap_err().kind(),
4356 @r#"Expression("Invalid commit ID prefix")"#);
4357 Ok(())
4358 }
4359
4360 #[test]
4361 fn test_parse_revset_author_committer_functions() -> TestResult {
4362 let settings = insta_settings();
4363 let _guard = settings.bind_to_scope();
4364
4365 insta::assert_debug_snapshot!(
4366 parse("author(foo)")?, @r#"
4367 Union(
4368 Filter(AuthorName(Pattern(Exact("foo")))),
4369 Filter(AuthorEmail(Pattern(Exact("foo")))),
4370 )
4371 "#);
4372 insta::assert_debug_snapshot!(
4373 parse("author_name(foo)")?,
4374 @r#"Filter(AuthorName(Pattern(Exact("foo"))))"#);
4375 insta::assert_debug_snapshot!(
4376 parse("author_email(foo)")?,
4377 @r#"Filter(AuthorEmail(Pattern(Exact("foo"))))"#);
4378
4379 insta::assert_debug_snapshot!(
4380 parse("committer(foo)")?, @r#"
4381 Union(
4382 Filter(CommitterName(Pattern(Exact("foo")))),
4383 Filter(CommitterEmail(Pattern(Exact("foo")))),
4384 )
4385 "#);
4386 insta::assert_debug_snapshot!(
4387 parse("committer_name(foo)")?,
4388 @r#"Filter(CommitterName(Pattern(Exact("foo"))))"#);
4389 insta::assert_debug_snapshot!(
4390 parse("committer_email(foo)")?,
4391 @r#"Filter(CommitterEmail(Pattern(Exact("foo"))))"#);
4392
4393 insta::assert_debug_snapshot!(
4394 parse("mine()")?,
4395 @r#"Filter(AuthorEmail(Pattern(ExactI("test.user@example.com"))))"#);
4396 Ok(())
4397 }
4398
4399 #[test]
4400 fn test_parse_revset_keyword_arguments() -> TestResult {
4401 let settings = insta_settings();
4402 let _guard = settings.bind_to_scope();
4403
4404 insta::assert_debug_snapshot!(
4405 parse("remote_bookmarks(remote=foo)")?, @r#"
4406 CommitRef(
4407 RemoteBookmarks {
4408 symbol: RemoteRefSymbolExpression {
4409 name: Pattern(Substring("")),
4410 remote: Pattern(Exact("foo")),
4411 },
4412 remote_ref_state: None,
4413 },
4414 )
4415 "#);
4416 insta::assert_debug_snapshot!(
4417 parse("remote_bookmarks(foo, remote=bar)")?, @r#"
4418 CommitRef(
4419 RemoteBookmarks {
4420 symbol: RemoteRefSymbolExpression {
4421 name: Pattern(Exact("foo")),
4422 remote: Pattern(Exact("bar")),
4423 },
4424 remote_ref_state: None,
4425 },
4426 )
4427 "#);
4428 insta::assert_debug_snapshot!(
4429 parse("tracked_remote_bookmarks(foo, remote=bar)")?, @r#"
4430 CommitRef(
4431 RemoteBookmarks {
4432 symbol: RemoteRefSymbolExpression {
4433 name: Pattern(Exact("foo")),
4434 remote: Pattern(Exact("bar")),
4435 },
4436 remote_ref_state: Some(Tracked),
4437 },
4438 )
4439 "#);
4440 insta::assert_debug_snapshot!(
4441 parse("untracked_remote_bookmarks(foo, remote=bar)")?, @r#"
4442 CommitRef(
4443 RemoteBookmarks {
4444 symbol: RemoteRefSymbolExpression {
4445 name: Pattern(Exact("foo")),
4446 remote: Pattern(Exact("bar")),
4447 },
4448 remote_ref_state: Some(New),
4449 },
4450 )
4451 "#);
4452 insta::assert_debug_snapshot!(
4453 parse(r#"remote_bookmarks(remote=foo, bar)"#).unwrap_err().kind(),
4454 @r#"
4455 InvalidFunctionArguments {
4456 name: "remote_bookmarks",
4457 message: "Positional argument follows keyword argument",
4458 }
4459 "#);
4460 insta::assert_debug_snapshot!(
4461 parse(r#"remote_bookmarks("", foo, remote=bar)"#).unwrap_err().kind(),
4462 @r#"
4463 InvalidFunctionArguments {
4464 name: "remote_bookmarks",
4465 message: "Got multiple values for keyword \"remote\"",
4466 }
4467 "#);
4468 insta::assert_debug_snapshot!(
4469 parse(r#"remote_bookmarks(remote=bar, remote=bar)"#).unwrap_err().kind(),
4470 @r#"
4471 InvalidFunctionArguments {
4472 name: "remote_bookmarks",
4473 message: "Got multiple values for keyword \"remote\"",
4474 }
4475 "#);
4476 insta::assert_debug_snapshot!(
4477 parse(r#"remote_bookmarks(unknown=bar)"#).unwrap_err().kind(),
4478 @r#"
4479 InvalidFunctionArguments {
4480 name: "remote_bookmarks",
4481 message: "Unexpected keyword argument \"unknown\"",
4482 }
4483 "#);
4484 Ok(())
4485 }
4486
4487 #[test]
4488 fn test_expand_symbol_alias() -> TestResult {
4489 let settings = insta_settings();
4490 let _guard = settings.bind_to_scope();
4491
4492 insta::assert_debug_snapshot!(
4493 parse_with_aliases("AB|c", [("AB", "a|b")])?, @r#"
4494 Union(
4495 Union(
4496 CommitRef(Symbol("a")),
4497 CommitRef(Symbol("b")),
4498 ),
4499 CommitRef(Symbol("c")),
4500 )
4501 "#);
4502
4503 insta::assert_debug_snapshot!(
4505 parse_with_aliases_and_workspace("files(A)", [("A", "a")], WorkspaceName::DEFAULT)
4506 ?,
4507 @r#"Filter(File(Pattern(PrefixPath("a"))))"#);
4508
4509 insta::assert_debug_snapshot!(
4511 parse_with_aliases("author_name(A)", [("A", "a")])?,
4512 @r#"Filter(AuthorName(Pattern(Exact("a"))))"#);
4513 insta::assert_debug_snapshot!(
4514 parse_with_aliases("author_name(A)", [("A", "exact:a")])?,
4515 @r#"Filter(AuthorName(Pattern(Exact("a"))))"#);
4516 Ok(())
4517 }
4518
4519 #[test]
4520 fn test_expand_function_alias() -> TestResult {
4521 let settings = insta_settings();
4522 let _guard = settings.bind_to_scope();
4523
4524 insta::assert_debug_snapshot!(
4526 parse_with_aliases("F(a)", [("F(x)", "author_name(x)|committer_name(x)")])?,
4527 @r#"
4528 Union(
4529 Filter(AuthorName(Pattern(Exact("a")))),
4530 Filter(CommitterName(Pattern(Exact("a")))),
4531 )
4532 "#);
4533 Ok(())
4534 }
4535
4536 #[test]
4537 fn test_transform_expression() {
4538 let settings = insta_settings();
4539 let _guard = settings.bind_to_scope();
4540
4541 insta::assert_debug_snapshot!(
4543 transform_expression(
4544 &ResolvedRevsetExpression::root(),
4545 |_| ControlFlow::Break(None),
4546 |_| Some(RevsetExpression::none()),
4547 ), @"None");
4548
4549 insta::assert_debug_snapshot!(
4551 transform_expression(
4552 &ResolvedRevsetExpression::root(),
4553 |_| ControlFlow::Break(Some(RevsetExpression::all())),
4554 |_| Some(RevsetExpression::none()),
4555 ), @"Some(All)");
4556
4557 insta::assert_debug_snapshot!(
4559 transform_expression(
4560 &ResolvedRevsetExpression::root().heads(),
4561 |_| ControlFlow::Continue(()),
4562 |x| match x.as_ref() {
4563 RevsetExpression::Root => Some(RevsetExpression::none()),
4564 _ => None,
4565 },
4566 ), @"Some(Heads(None))");
4567
4568 insta::assert_debug_snapshot!(
4570 transform_expression(
4571 &ResolvedRevsetExpression::root().heads(),
4572 |_| ControlFlow::Continue(()),
4573 |x| match x.as_ref() {
4574 RevsetExpression::Heads(y) => Some(y.clone()),
4575 _ => None,
4576 },
4577 ), @"Some(Root)");
4578 }
4579
4580 #[test]
4581 fn test_resolve_referenced_commits() {
4582 let settings = insta_settings();
4583 let _guard = settings.bind_to_scope();
4584
4585 let visibility1 = Arc::new(ResolvedRevsetExpression::WithinVisibility {
4586 candidates: RevsetExpression::commit(CommitId::from_hex("100001")),
4587 visible_heads: vec![CommitId::from_hex("100000")],
4588 });
4589 let visibility2 = Arc::new(ResolvedRevsetExpression::WithinVisibility {
4590 candidates: RevsetExpression::filter(RevsetFilterPredicate::HasConflict),
4591 visible_heads: vec![CommitId::from_hex("200000")],
4592 });
4593 let commit3 = RevsetExpression::commit(CommitId::from_hex("000003"));
4594
4595 insta::assert_debug_snapshot!(
4598 resolve_referenced_commits(&visibility1.intersection(&RevsetExpression::all())), @r#"
4599 Some(
4600 WithinReference {
4601 candidates: Intersection(
4602 WithinVisibility {
4603 candidates: WithinReference {
4604 candidates: Commits(
4605 [
4606 CommitId("100001"),
4607 ],
4608 ),
4609 commits: [
4610 CommitId("100001"),
4611 ],
4612 },
4613 visible_heads: [
4614 CommitId("100000"),
4615 ],
4616 },
4617 All,
4618 ),
4619 commits: [
4620 CommitId("100000"),
4621 CommitId("100001"),
4622 ],
4623 },
4624 )
4625 "#);
4626
4627 insta::assert_debug_snapshot!(
4629 resolve_referenced_commits(
4630 &visibility2
4631 .intersection(&RevsetExpression::all())
4632 .union(&commit3),
4633 ), @r#"
4634 Some(
4635 WithinReference {
4636 candidates: Union(
4637 Intersection(
4638 WithinVisibility {
4639 candidates: Filter(HasConflict),
4640 visible_heads: [
4641 CommitId("200000"),
4642 ],
4643 },
4644 All,
4645 ),
4646 Commits(
4647 [
4648 CommitId("000003"),
4649 ],
4650 ),
4651 ),
4652 commits: [
4653 CommitId("000003"),
4654 CommitId("200000"),
4655 ],
4656 },
4657 )
4658 "#);
4659
4660 insta::assert_debug_snapshot!(
4662 resolve_referenced_commits(
4663 &visibility1
4664 .union(&visibility2)
4665 .union(&commit3)
4666 .intersection(&RevsetExpression::all())
4667 ), @r#"
4668 Some(
4669 WithinReference {
4670 candidates: Intersection(
4671 Union(
4672 Union(
4673 WithinVisibility {
4674 candidates: WithinReference {
4675 candidates: Commits(
4676 [
4677 CommitId("100001"),
4678 ],
4679 ),
4680 commits: [
4681 CommitId("100001"),
4682 ],
4683 },
4684 visible_heads: [
4685 CommitId("100000"),
4686 ],
4687 },
4688 WithinVisibility {
4689 candidates: Filter(HasConflict),
4690 visible_heads: [
4691 CommitId("200000"),
4692 ],
4693 },
4694 ),
4695 Commits(
4696 [
4697 CommitId("000003"),
4698 ],
4699 ),
4700 ),
4701 All,
4702 ),
4703 commits: [
4704 CommitId("000003"),
4705 CommitId("100000"),
4706 CommitId("100001"),
4707 CommitId("200000"),
4708 ],
4709 },
4710 )
4711 "#);
4712
4713 insta::assert_debug_snapshot!(
4715 resolve_referenced_commits(&Arc::new(ResolvedRevsetExpression::WithinVisibility {
4716 candidates: visibility1.clone(),
4717 visible_heads: vec![CommitId::from_hex("400000")],
4718 })), @r#"
4719 Some(
4720 WithinReference {
4721 candidates: WithinVisibility {
4722 candidates: WithinReference {
4723 candidates: WithinVisibility {
4724 candidates: WithinReference {
4725 candidates: Commits(
4726 [
4727 CommitId("100001"),
4728 ],
4729 ),
4730 commits: [
4731 CommitId("100001"),
4732 ],
4733 },
4734 visible_heads: [
4735 CommitId("100000"),
4736 ],
4737 },
4738 commits: [
4739 CommitId("100000"),
4740 CommitId("100001"),
4741 ],
4742 },
4743 visible_heads: [
4744 CommitId("400000"),
4745 ],
4746 },
4747 commits: [
4748 CommitId("400000"),
4749 CommitId("100000"),
4750 CommitId("100001"),
4751 ],
4752 },
4753 )
4754 "#);
4755
4756 let resolved = Arc::new(ResolvedRevsetExpression::WithinReference {
4758 candidates: RevsetExpression::none(),
4761 commits: vec![CommitId::from_hex("100000")],
4762 });
4763 insta::assert_debug_snapshot!(
4764 resolve_referenced_commits(&resolved), @"None");
4765 insta::assert_debug_snapshot!(
4766 resolve_referenced_commits(&resolved.intersection(&RevsetExpression::all())), @r#"
4767 Some(
4768 WithinReference {
4769 candidates: Intersection(
4770 WithinReference {
4771 candidates: None,
4772 commits: [
4773 CommitId("100000"),
4774 ],
4775 },
4776 All,
4777 ),
4778 commits: [
4779 CommitId("100000"),
4780 ],
4781 },
4782 )
4783 "#);
4784 insta::assert_debug_snapshot!(
4785 resolve_referenced_commits(&Arc::new(ResolvedRevsetExpression::WithinVisibility {
4786 candidates: resolved.clone(),
4787 visible_heads: vec![CommitId::from_hex("400000")],
4788 })), @r#"
4789 Some(
4790 WithinReference {
4791 candidates: WithinVisibility {
4792 candidates: WithinReference {
4793 candidates: None,
4794 commits: [
4795 CommitId("100000"),
4796 ],
4797 },
4798 visible_heads: [
4799 CommitId("400000"),
4800 ],
4801 },
4802 commits: [
4803 CommitId("400000"),
4804 CommitId("100000"),
4805 ],
4806 },
4807 )
4808 "#);
4809 }
4810
4811 #[test]
4812 fn test_optimize_subtree() -> TestResult {
4813 let settings = insta_settings();
4814 let _guard = settings.bind_to_scope();
4815
4816 insta::assert_debug_snapshot!(
4820 optimize(parse("parents(bookmarks() & all())")?), @r#"
4821 Ancestors {
4822 heads: CommitRef(Bookmarks(Pattern(Substring("")))),
4823 generation: 1..2,
4824 parents_range: 0..4294967295,
4825 }
4826 "#);
4827 insta::assert_debug_snapshot!(
4828 optimize(parse("children(bookmarks() & all())")?), @r#"
4829 Descendants {
4830 roots: CommitRef(Bookmarks(Pattern(Substring("")))),
4831 generation: 1..2,
4832 }
4833 "#);
4834 insta::assert_debug_snapshot!(
4835 optimize(parse("ancestors(bookmarks() & all())")?), @r#"
4836 Ancestors {
4837 heads: CommitRef(Bookmarks(Pattern(Substring("")))),
4838 generation: 0..18446744073709551615,
4839 parents_range: 0..4294967295,
4840 }
4841 "#);
4842 insta::assert_debug_snapshot!(
4843 optimize(parse("descendants(bookmarks() & all())")?), @r#"
4844 Descendants {
4845 roots: CommitRef(Bookmarks(Pattern(Substring("")))),
4846 generation: 0..18446744073709551615,
4847 }
4848 "#);
4849
4850 insta::assert_debug_snapshot!(
4851 optimize(parse("(bookmarks() & all())..(all() & tags())")?), @r#"
4852 Range {
4853 roots: CommitRef(Bookmarks(Pattern(Substring("")))),
4854 heads: CommitRef(Tags(Pattern(Substring("")))),
4855 generation: 0..18446744073709551615,
4856 parents_range: 0..4294967295,
4857 }
4858 "#);
4859 insta::assert_debug_snapshot!(
4860 optimize(parse("(bookmarks() & all())::(all() & tags())")?), @r#"
4861 DagRange {
4862 roots: CommitRef(Bookmarks(Pattern(Substring("")))),
4863 heads: CommitRef(Tags(Pattern(Substring("")))),
4864 }
4865 "#);
4866
4867 insta::assert_debug_snapshot!(
4868 optimize(parse("heads(bookmarks() & all())")?),
4869 @r#"
4870 Heads(
4871 CommitRef(Bookmarks(Pattern(Substring("")))),
4872 )
4873 "#);
4874 insta::assert_debug_snapshot!(
4875 optimize(parse("roots(bookmarks() & all())")?),
4876 @r#"
4877 Roots(
4878 CommitRef(Bookmarks(Pattern(Substring("")))),
4879 )
4880 "#);
4881
4882 insta::assert_debug_snapshot!(
4883 optimize(parse("latest(bookmarks() & all(), 2)")?), @r#"
4884 Latest {
4885 candidates: CommitRef(Bookmarks(Pattern(Substring("")))),
4886 count: 2,
4887 }
4888 "#);
4889
4890 insta::assert_debug_snapshot!(
4891 optimize(parse("present(foo ~ bar)")?), @r#"
4892 Present(
4893 Difference(
4894 CommitRef(Symbol("foo")),
4895 CommitRef(Symbol("bar")),
4896 ),
4897 )
4898 "#);
4899 insta::assert_debug_snapshot!(
4900 optimize(parse("present(bookmarks() & all())")?),
4901 @r#"
4902 Present(
4903 CommitRef(Bookmarks(Pattern(Substring("")))),
4904 )
4905 "#);
4906
4907 insta::assert_debug_snapshot!(
4908 optimize(parse("at_operation(@-, bookmarks() & all())")?), @r#"
4909 AtOperation {
4910 operation: "@-",
4911 candidates: CommitRef(Bookmarks(Pattern(Substring("")))),
4912 }
4913 "#);
4914 insta::assert_debug_snapshot!(
4915 optimize(Arc::new(RevsetExpression::WithinReference {
4916 candidates: parse("bookmarks() & all()")?,
4917 commits: vec![CommitId::from_hex("012345")],
4918 })), @r#"
4919 WithinReference {
4920 candidates: CommitRef(Bookmarks(Pattern(Substring("")))),
4921 commits: [
4922 CommitId("012345"),
4923 ],
4924 }
4925 "#);
4926 insta::assert_debug_snapshot!(
4927 optimize(Arc::new(RevsetExpression::WithinVisibility {
4928 candidates: parse("bookmarks() & all()")?,
4929 visible_heads: vec![CommitId::from_hex("012345")],
4930 })), @r#"
4931 WithinReference {
4932 candidates: WithinVisibility {
4933 candidates: CommitRef(Bookmarks(Pattern(Substring("")))),
4934 visible_heads: [
4935 CommitId("012345"),
4936 ],
4937 },
4938 commits: [
4939 CommitId("012345"),
4940 ],
4941 }
4942 "#);
4943
4944 insta::assert_debug_snapshot!(
4945 optimize(parse("~bookmarks() & all()")?),
4946 @r#"
4947 NotIn(
4948 CommitRef(Bookmarks(Pattern(Substring("")))),
4949 )
4950 "#);
4951 insta::assert_debug_snapshot!(
4952 optimize(parse("(bookmarks() & all()) | (all() & tags())")?), @r#"
4953 Union(
4954 CommitRef(Bookmarks(Pattern(Substring("")))),
4955 CommitRef(Tags(Pattern(Substring("")))),
4956 )
4957 "#);
4958 insta::assert_debug_snapshot!(
4959 optimize(parse("(bookmarks() & all()) & (all() & tags())")?), @r#"
4960 Intersection(
4961 CommitRef(Bookmarks(Pattern(Substring("")))),
4962 CommitRef(Tags(Pattern(Substring("")))),
4963 )
4964 "#);
4965 insta::assert_debug_snapshot!(
4966 optimize(parse("(bookmarks() & all()) ~ (all() & tags())")?), @r#"
4967 Difference(
4968 CommitRef(Bookmarks(Pattern(Substring("")))),
4969 CommitRef(Tags(Pattern(Substring("")))),
4970 )
4971 "#);
4972 Ok(())
4973 }
4974
4975 #[test]
4976 fn test_optimize_unchanged_subtree() -> TestResult {
4977 fn unwrap_union(
4978 expression: &UserRevsetExpression,
4979 ) -> (&Arc<UserRevsetExpression>, &Arc<UserRevsetExpression>) {
4980 match expression {
4981 RevsetExpression::Union(left, right) => (left, right),
4982 _ => panic!("unexpected expression: {expression:?}"),
4983 }
4984 }
4985
4986 let parsed = parse("foo-")?;
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() & tags()")?;
4996 let optimized = optimize(parsed.clone());
4997 assert!(Arc::ptr_eq(&parsed, &optimized));
4998
4999 let parsed = parse("(bookmarks() & all()) | tags()")?;
5001 let optimized = optimize(parsed.clone());
5002 assert_matches!(
5003 unwrap_union(&optimized).0.as_ref(),
5004 RevsetExpression::CommitRef(RevsetCommitRef::Bookmarks(_))
5005 );
5006 assert!(Arc::ptr_eq(
5007 unwrap_union(&parsed).1,
5008 unwrap_union(&optimized).1
5009 ));
5010
5011 let parsed = parse("bookmarks() | (all() & tags())")?;
5013 let optimized = optimize(parsed.clone());
5014 assert!(Arc::ptr_eq(
5015 unwrap_union(&parsed).0,
5016 unwrap_union(&optimized).0
5017 ));
5018 assert_matches!(
5019 unwrap_union(&optimized).1.as_ref(),
5020 RevsetExpression::CommitRef(RevsetCommitRef::Tags(_))
5021 );
5022 Ok(())
5023 }
5024
5025 #[test]
5026 fn test_optimize_basic() -> TestResult {
5027 let settings = insta_settings();
5028 let _guard = settings.bind_to_scope();
5029
5030 insta::assert_debug_snapshot!(optimize(parse("all() | none()")?), @"All");
5031 insta::assert_debug_snapshot!(optimize(parse("all() & none()")?), @"None");
5032 insta::assert_debug_snapshot!(optimize(parse("root() | all()")?), @"All");
5033 insta::assert_debug_snapshot!(optimize(parse("root() & all()")?), @"Root");
5034 insta::assert_debug_snapshot!(optimize(parse("none() | root()")?), @"Root");
5035 insta::assert_debug_snapshot!(optimize(parse("none() & root()")?), @"None");
5036 insta::assert_debug_snapshot!(optimize(parse("~none()")?), @"All");
5037 insta::assert_debug_snapshot!(optimize(parse("~~none()")?), @"None");
5038 insta::assert_debug_snapshot!(optimize(parse("~all()")?), @"None");
5039 insta::assert_debug_snapshot!(optimize(parse("~~all()")?), @"All");
5040 insta::assert_debug_snapshot!(optimize(parse("~~foo")?), @r#"CommitRef(Symbol("foo"))"#);
5041 insta::assert_debug_snapshot!(
5042 optimize(parse("(root() | none()) & (visible_heads() | ~~all())")?), @"Root");
5043 insta::assert_debug_snapshot!(
5044 optimize(UserRevsetExpression::commits(vec![])), @"None");
5045 insta::assert_debug_snapshot!(
5046 optimize(UserRevsetExpression::commits(vec![]).negated()), @"All");
5047 Ok(())
5048 }
5049
5050 #[test]
5051 fn test_optimize_difference() -> TestResult {
5052 let settings = insta_settings();
5053 let _guard = settings.bind_to_scope();
5054
5055 insta::assert_debug_snapshot!(optimize(parse("foo & ~bar")?), @r#"
5056 Difference(
5057 CommitRef(Symbol("foo")),
5058 CommitRef(Symbol("bar")),
5059 )
5060 "#);
5061 insta::assert_debug_snapshot!(optimize(parse("~foo & bar")?), @r#"
5062 Difference(
5063 CommitRef(Symbol("bar")),
5064 CommitRef(Symbol("foo")),
5065 )
5066 "#);
5067 insta::assert_debug_snapshot!(optimize(parse("~foo & bar & ~baz")?), @r#"
5068 Difference(
5069 Difference(
5070 CommitRef(Symbol("bar")),
5071 CommitRef(Symbol("foo")),
5072 ),
5073 CommitRef(Symbol("baz")),
5074 )
5075 "#);
5076 insta::assert_debug_snapshot!(optimize(parse("(all() & ~foo) & bar")?), @r#"
5077 Difference(
5078 CommitRef(Symbol("bar")),
5079 CommitRef(Symbol("foo")),
5080 )
5081 "#);
5082
5083 insta::assert_debug_snapshot!(
5085 optimize(parse("all() ~ foo")?),
5086 @r#"NotIn(CommitRef(Symbol("foo")))"#);
5087 insta::assert_debug_snapshot!(optimize(parse("foo ~ bar")?), @r#"
5088 Difference(
5089 CommitRef(Symbol("foo")),
5090 CommitRef(Symbol("bar")),
5091 )
5092 "#);
5093 insta::assert_debug_snapshot!(optimize(parse("(all() ~ foo) & bar")?), @r#"
5094 Difference(
5095 CommitRef(Symbol("bar")),
5096 CommitRef(Symbol("foo")),
5097 )
5098 "#);
5099
5100 insta::assert_debug_snapshot!(optimize(parse("::foo & ~::bar")?), @r#"
5102 Range {
5103 roots: CommitRef(Symbol("bar")),
5104 heads: CommitRef(Symbol("foo")),
5105 generation: 0..18446744073709551615,
5106 parents_range: 0..4294967295,
5107 }
5108 "#);
5109 insta::assert_debug_snapshot!(optimize(parse("~::foo & ::bar")?), @r#"
5110 Range {
5111 roots: CommitRef(Symbol("foo")),
5112 heads: CommitRef(Symbol("bar")),
5113 generation: 0..18446744073709551615,
5114 parents_range: 0..4294967295,
5115 }
5116 "#);
5117 insta::assert_debug_snapshot!(optimize(parse("foo..")?), @r#"
5118 Range {
5119 roots: CommitRef(Symbol("foo")),
5120 heads: VisibleHeadsOrReferenced,
5121 generation: 0..18446744073709551615,
5122 parents_range: 0..4294967295,
5123 }
5124 "#);
5125 insta::assert_debug_snapshot!(optimize(parse("foo..bar")?), @r#"
5126 Range {
5127 roots: CommitRef(Symbol("foo")),
5128 heads: CommitRef(Symbol("bar")),
5129 generation: 0..18446744073709551615,
5130 parents_range: 0..4294967295,
5131 }
5132 "#);
5133 insta::assert_debug_snapshot!(optimize(parse("foo.. & ::bar")?), @r#"
5134 Range {
5135 roots: CommitRef(Symbol("foo")),
5136 heads: CommitRef(Symbol("bar")),
5137 generation: 0..18446744073709551615,
5138 parents_range: 0..4294967295,
5139 }
5140 "#);
5141 insta::assert_debug_snapshot!(optimize(parse("foo.. & first_ancestors(bar)")?), @r#"
5142 Range {
5143 roots: CommitRef(Symbol("foo")),
5144 heads: CommitRef(Symbol("bar")),
5145 generation: 0..18446744073709551615,
5146 parents_range: 0..1,
5147 }
5148 "#);
5149
5150 insta::assert_debug_snapshot!(optimize(parse("foo & ~~bar")?), @r#"
5152 Intersection(
5153 CommitRef(Symbol("foo")),
5154 CommitRef(Symbol("bar")),
5155 )
5156 "#);
5157 insta::assert_debug_snapshot!(optimize(parse("foo & ~~~bar")?), @r#"
5158 Difference(
5159 CommitRef(Symbol("foo")),
5160 CommitRef(Symbol("bar")),
5161 )
5162 "#);
5163 insta::assert_debug_snapshot!(optimize(parse("~(all() & ~foo) & bar")?), @r#"
5164 Intersection(
5165 CommitRef(Symbol("foo")),
5166 CommitRef(Symbol("bar")),
5167 )
5168 "#);
5169
5170 insta::assert_debug_snapshot!(optimize(parse("~foo & ~bar")?), @r#"
5172 Difference(
5173 NotIn(CommitRef(Symbol("foo"))),
5174 CommitRef(Symbol("bar")),
5175 )
5176 "#);
5177
5178 insta::assert_debug_snapshot!(optimize(parse("a..b & c..d")?), @r#"
5180 Intersection(
5181 Range {
5182 roots: Union(
5183 CommitRef(Symbol("a")),
5184 CommitRef(Symbol("c")),
5185 ),
5186 heads: CommitRef(Symbol("b")),
5187 generation: 0..18446744073709551615,
5188 parents_range: 0..4294967295,
5189 },
5190 Ancestors {
5191 heads: CommitRef(Symbol("d")),
5192 generation: 0..18446744073709551615,
5193 parents_range: 0..4294967295,
5194 },
5195 )
5196 "#);
5197
5198 insta::assert_debug_snapshot!(optimize(parse("foo..bar ~ first_ancestors(baz)")?), @r#"
5200 Difference(
5201 Range {
5202 roots: CommitRef(Symbol("foo")),
5203 heads: CommitRef(Symbol("bar")),
5204 generation: 0..18446744073709551615,
5205 parents_range: 0..4294967295,
5206 },
5207 Ancestors {
5208 heads: CommitRef(Symbol("baz")),
5209 generation: 0..18446744073709551615,
5210 parents_range: 0..1,
5211 },
5212 )
5213 "#);
5214
5215 insta::assert_debug_snapshot!(optimize(parse("foo ~ ::a & (::b & bar & ::c) & (baz ~ ::d)")?), @r#"
5218 Intersection(
5219 Intersection(
5220 Intersection(
5221 Intersection(
5222 Range {
5223 roots: Union(
5224 CommitRef(Symbol("a")),
5225 CommitRef(Symbol("d")),
5226 ),
5227 heads: CommitRef(Symbol("b")),
5228 generation: 0..18446744073709551615,
5229 parents_range: 0..4294967295,
5230 },
5231 Ancestors {
5232 heads: CommitRef(Symbol("c")),
5233 generation: 0..18446744073709551615,
5234 parents_range: 0..4294967295,
5235 },
5236 ),
5237 CommitRef(Symbol("foo")),
5238 ),
5239 CommitRef(Symbol("bar")),
5240 ),
5241 CommitRef(Symbol("baz")),
5242 )
5243 "#);
5244 Ok(())
5245 }
5246
5247 #[test]
5248 fn test_optimize_not_in_ancestors() -> TestResult {
5249 let settings = insta_settings();
5250 let _guard = settings.bind_to_scope();
5251
5252 insta::assert_debug_snapshot!(optimize(parse("~(::foo)")?), @r#"
5254 Range {
5255 roots: CommitRef(Symbol("foo")),
5256 heads: VisibleHeadsOrReferenced,
5257 generation: 0..18446744073709551615,
5258 parents_range: 0..4294967295,
5259 }
5260 "#);
5261
5262 insta::assert_debug_snapshot!(optimize(parse("~(::foo-)")?), @r#"
5264 Range {
5265 roots: Ancestors {
5266 heads: CommitRef(Symbol("foo")),
5267 generation: 1..2,
5268 parents_range: 0..4294967295,
5269 },
5270 heads: VisibleHeadsOrReferenced,
5271 generation: 0..18446744073709551615,
5272 parents_range: 0..4294967295,
5273 }
5274 "#);
5275 insta::assert_debug_snapshot!(optimize(parse("~(::foo--)")?), @r#"
5276 Range {
5277 roots: Ancestors {
5278 heads: CommitRef(Symbol("foo")),
5279 generation: 2..3,
5280 parents_range: 0..4294967295,
5281 },
5282 heads: VisibleHeadsOrReferenced,
5283 generation: 0..18446744073709551615,
5284 parents_range: 0..4294967295,
5285 }
5286 "#);
5287
5288 insta::assert_debug_snapshot!(optimize(parse("~ancestors(foo, 1)")?), @r#"
5290 NotIn(
5291 Ancestors {
5292 heads: CommitRef(Symbol("foo")),
5293 generation: 0..1,
5294 parents_range: 0..4294967295,
5295 },
5296 )
5297 "#);
5298 insta::assert_debug_snapshot!(optimize(parse("~ancestors(foo-, 1)")?), @r#"
5299 NotIn(
5300 Ancestors {
5301 heads: CommitRef(Symbol("foo")),
5302 generation: 1..2,
5303 parents_range: 0..4294967295,
5304 },
5305 )
5306 "#);
5307 Ok(())
5308 }
5309
5310 #[test]
5311 fn test_optimize_filter_difference() -> TestResult {
5312 let settings = insta_settings();
5313 let _guard = settings.bind_to_scope();
5314
5315 insta::assert_debug_snapshot!(optimize(parse("~empty()")?), @"Filter(File(All))");
5317
5318 insta::assert_debug_snapshot!(
5320 optimize(parse("(author_name(foo) & ~bar) & baz")?), @r#"
5321 Intersection(
5322 Difference(
5323 CommitRef(Symbol("baz")),
5324 CommitRef(Symbol("bar")),
5325 ),
5326 Filter(AuthorName(Pattern(Exact("foo")))),
5327 )
5328 "#);
5329
5330 insta::assert_debug_snapshot!(
5332 optimize(parse("~foo & author_name(bar)")?), @r#"
5333 Intersection(
5334 NotIn(CommitRef(Symbol("foo"))),
5335 Filter(AuthorName(Pattern(Exact("bar")))),
5336 )
5337 "#);
5338 insta::assert_debug_snapshot!(
5339 optimize(parse("~foo & (author_name(bar) | baz)")?), @r#"
5340 Intersection(
5341 NotIn(CommitRef(Symbol("foo"))),
5342 AsFilter(
5343 Union(
5344 Filter(AuthorName(Pattern(Exact("bar")))),
5345 CommitRef(Symbol("baz")),
5346 ),
5347 ),
5348 )
5349 "#);
5350
5351 insta::assert_debug_snapshot!(
5353 optimize(parse("author_name(foo) ~ bar")?), @r#"
5354 Intersection(
5355 NotIn(CommitRef(Symbol("bar"))),
5356 Filter(AuthorName(Pattern(Exact("foo")))),
5357 )
5358 "#);
5359 Ok(())
5360 }
5361
5362 #[test]
5363 fn test_optimize_filter_intersection() -> TestResult {
5364 let settings = insta_settings();
5365 let _guard = settings.bind_to_scope();
5366
5367 insta::assert_debug_snapshot!(
5368 optimize(parse("author_name(foo)")?),
5369 @r#"Filter(AuthorName(Pattern(Exact("foo"))))"#);
5370
5371 insta::assert_debug_snapshot!(optimize(parse("foo & description(bar)")?), @r#"
5372 Intersection(
5373 CommitRef(Symbol("foo")),
5374 Filter(Description(Pattern(Exact("bar")))),
5375 )
5376 "#);
5377 insta::assert_debug_snapshot!(optimize(parse("author_name(foo) & bar")?), @r#"
5378 Intersection(
5379 CommitRef(Symbol("bar")),
5380 Filter(AuthorName(Pattern(Exact("foo")))),
5381 )
5382 "#);
5383 insta::assert_debug_snapshot!(
5384 optimize(parse("author_name(foo) & committer_name(bar)")?), @r#"
5385 AsFilter(
5386 Intersection(
5387 Filter(AuthorName(Pattern(Exact("foo")))),
5388 Filter(CommitterName(Pattern(Exact("bar")))),
5389 ),
5390 )
5391 "#);
5392 insta::assert_debug_snapshot!(optimize(parse("divergent() & foo")?), @r#"
5393 Intersection(
5394 CommitRef(Symbol("foo")),
5395 AsFilter(Divergent),
5396 )
5397 "#);
5398
5399 insta::assert_debug_snapshot!(
5400 optimize(parse("foo & description(bar) & author_name(baz)")?), @r#"
5401 Intersection(
5402 CommitRef(Symbol("foo")),
5403 AsFilter(
5404 Intersection(
5405 Filter(Description(Pattern(Exact("bar")))),
5406 Filter(AuthorName(Pattern(Exact("baz")))),
5407 ),
5408 ),
5409 )
5410 "#);
5411 insta::assert_debug_snapshot!(
5412 optimize(parse("committer_name(foo) & bar & author_name(baz)")?), @r#"
5413 Intersection(
5414 CommitRef(Symbol("bar")),
5415 AsFilter(
5416 Intersection(
5417 Filter(CommitterName(Pattern(Exact("foo")))),
5418 Filter(AuthorName(Pattern(Exact("baz")))),
5419 ),
5420 ),
5421 )
5422 "#);
5423 insta::assert_debug_snapshot!(
5424 optimize(parse_with_workspace(
5425 "committer_name(foo) & files(bar) & baz",
5426 WorkspaceName::DEFAULT)?,
5427 ), @r#"
5428 Intersection(
5429 CommitRef(Symbol("baz")),
5430 AsFilter(
5431 Intersection(
5432 Filter(CommitterName(Pattern(Exact("foo")))),
5433 Filter(File(Pattern(PrefixPath("bar")))),
5434 ),
5435 ),
5436 )
5437 "#);
5438 insta::assert_debug_snapshot!(
5439 optimize(parse_with_workspace(
5440 "committer_name(foo) & files(bar) & author_name(baz)",
5441 WorkspaceName::DEFAULT)?,
5442 ), @r#"
5443 AsFilter(
5444 Intersection(
5445 Intersection(
5446 Filter(CommitterName(Pattern(Exact("foo")))),
5447 Filter(File(Pattern(PrefixPath("bar")))),
5448 ),
5449 Filter(AuthorName(Pattern(Exact("baz")))),
5450 ),
5451 )
5452 "#);
5453 insta::assert_debug_snapshot!(
5454 optimize(parse_with_workspace(
5455 "foo & files(bar) & baz",
5456 WorkspaceName::DEFAULT)?,
5457 ), @r#"
5458 Intersection(
5459 Intersection(
5460 CommitRef(Symbol("foo")),
5461 CommitRef(Symbol("baz")),
5462 ),
5463 Filter(File(Pattern(PrefixPath("bar")))),
5464 )
5465 "#);
5466
5467 insta::assert_debug_snapshot!(
5468 optimize(parse("foo & description(bar) & author_name(baz) & qux")?), @r#"
5469 Intersection(
5470 Intersection(
5471 CommitRef(Symbol("foo")),
5472 CommitRef(Symbol("qux")),
5473 ),
5474 AsFilter(
5475 Intersection(
5476 Filter(Description(Pattern(Exact("bar")))),
5477 Filter(AuthorName(Pattern(Exact("baz")))),
5478 ),
5479 ),
5480 )
5481 "#);
5482 insta::assert_debug_snapshot!(
5483 optimize(parse("foo & description(bar) & parents(author_name(baz)) & qux")?),
5484 @r#"
5485 Intersection(
5486 Intersection(
5487 Intersection(
5488 CommitRef(Symbol("foo")),
5489 Ancestors {
5490 heads: Filter(AuthorName(Pattern(Exact("baz")))),
5491 generation: 1..2,
5492 parents_range: 0..4294967295,
5493 },
5494 ),
5495 CommitRef(Symbol("qux")),
5496 ),
5497 Filter(Description(Pattern(Exact("bar")))),
5498 )
5499 "#);
5500 insta::assert_debug_snapshot!(
5501 optimize(parse("foo & description(bar) & parents(author_name(baz) & qux)")?),
5502 @r#"
5503 Intersection(
5504 Intersection(
5505 CommitRef(Symbol("foo")),
5506 Ancestors {
5507 heads: Intersection(
5508 CommitRef(Symbol("qux")),
5509 Filter(AuthorName(Pattern(Exact("baz")))),
5510 ),
5511 generation: 1..2,
5512 parents_range: 0..4294967295,
5513 },
5514 ),
5515 Filter(Description(Pattern(Exact("bar")))),
5516 )
5517 "#);
5518
5519 insta::assert_debug_snapshot!(
5521 optimize(parse("(a & author_name(A)) & (b & author_name(B)) & (c & author_name(C))")?),
5522 @r#"
5523 Intersection(
5524 Intersection(
5525 Intersection(
5526 CommitRef(Symbol("a")),
5527 CommitRef(Symbol("b")),
5528 ),
5529 CommitRef(Symbol("c")),
5530 ),
5531 AsFilter(
5532 Intersection(
5533 Intersection(
5534 Filter(AuthorName(Pattern(Exact("A")))),
5535 Filter(AuthorName(Pattern(Exact("B")))),
5536 ),
5537 Filter(AuthorName(Pattern(Exact("C")))),
5538 ),
5539 ),
5540 )
5541 "#);
5542 insta::assert_debug_snapshot!(
5543 optimize(parse("(a & author_name(A)) & ((b & author_name(B)) & (c & author_name(C))) & d")?),
5544 @r#"
5545 Intersection(
5546 Intersection(
5547 Intersection(
5548 Intersection(
5549 CommitRef(Symbol("a")),
5550 CommitRef(Symbol("b")),
5551 ),
5552 CommitRef(Symbol("c")),
5553 ),
5554 CommitRef(Symbol("d")),
5555 ),
5556 AsFilter(
5557 Intersection(
5558 Intersection(
5559 Filter(AuthorName(Pattern(Exact("A")))),
5560 Filter(AuthorName(Pattern(Exact("B")))),
5561 ),
5562 Filter(AuthorName(Pattern(Exact("C")))),
5563 ),
5564 ),
5565 )
5566 "#);
5567
5568 insta::assert_debug_snapshot!(
5570 optimize(parse("foo & (all() & description(bar)) & (author_name(baz) & all())")?),
5571 @r#"
5572 Intersection(
5573 CommitRef(Symbol("foo")),
5574 AsFilter(
5575 Intersection(
5576 Filter(Description(Pattern(Exact("bar")))),
5577 Filter(AuthorName(Pattern(Exact("baz")))),
5578 ),
5579 ),
5580 )
5581 "#);
5582
5583 insta::assert_debug_snapshot!(
5585 optimize(parse("author_name(foo) & bar & at_operation(@-, committer_name(baz))")?),
5586 @r#"
5587 Intersection(
5588 Intersection(
5589 CommitRef(Symbol("bar")),
5590 AtOperation {
5591 operation: "@-",
5592 candidates: Filter(CommitterName(Pattern(Exact("baz")))),
5593 },
5594 ),
5595 Filter(AuthorName(Pattern(Exact("foo")))),
5596 )
5597 "#);
5598 Ok(())
5599 }
5600
5601 #[test]
5602 fn test_optimize_filter_subtree() -> TestResult {
5603 let settings = insta_settings();
5604 let _guard = settings.bind_to_scope();
5605
5606 insta::assert_debug_snapshot!(
5607 optimize(parse("(author_name(foo) | bar) & baz")?), @r#"
5608 Intersection(
5609 CommitRef(Symbol("baz")),
5610 AsFilter(
5611 Union(
5612 Filter(AuthorName(Pattern(Exact("foo")))),
5613 CommitRef(Symbol("bar")),
5614 ),
5615 ),
5616 )
5617 "#);
5618
5619 insta::assert_debug_snapshot!(
5621 optimize(parse("merges() & foo | bar")?), @r#"
5622 Union(
5623 Intersection(
5624 CommitRef(Symbol("foo")),
5625 Filter(ParentCount(2..4294967295)),
5626 ),
5627 CommitRef(Symbol("bar")),
5628 )
5629 "#);
5630
5631 insta::assert_debug_snapshot!(
5634 optimize(parse("merges() & foo | conflicts()")?), @r#"
5635 AsFilter(
5636 Union(
5637 Intersection(
5638 CommitRef(Symbol("foo")),
5639 Filter(ParentCount(2..4294967295)),
5640 ),
5641 Filter(HasConflict),
5642 ),
5643 )
5644 "#);
5645
5646 insta::assert_debug_snapshot!(
5648 optimize(parse("foo | conflicts() & merges() & signed()")?), @r#"
5649 AsFilter(
5650 Union(
5651 CommitRef(Symbol("foo")),
5652 Intersection(
5653 Intersection(
5654 Filter(HasConflict),
5655 Filter(ParentCount(2..4294967295)),
5656 ),
5657 Filter(Signed),
5658 ),
5659 ),
5660 )
5661 "#);
5662
5663 insta::assert_debug_snapshot!(
5664 optimize(parse("(foo | committer_name(bar)) & description(baz) & qux")?), @r#"
5665 Intersection(
5666 CommitRef(Symbol("qux")),
5667 AsFilter(
5668 Intersection(
5669 Union(
5670 CommitRef(Symbol("foo")),
5671 Filter(CommitterName(Pattern(Exact("bar")))),
5672 ),
5673 Filter(Description(Pattern(Exact("baz")))),
5674 ),
5675 ),
5676 )
5677 "#);
5678
5679 insta::assert_debug_snapshot!(
5680 optimize(parse(
5681 "(~present(author_name(foo) & description(bar)) | baz) & qux")?), @r#"
5682 Intersection(
5683 CommitRef(Symbol("qux")),
5684 AsFilter(
5685 Union(
5686 NotIn(
5687 Present(
5688 Intersection(
5689 Filter(AuthorName(Pattern(Exact("foo")))),
5690 Filter(Description(Pattern(Exact("bar")))),
5691 ),
5692 ),
5693 ),
5694 CommitRef(Symbol("baz")),
5695 ),
5696 ),
5697 )
5698 "#);
5699
5700 insta::assert_debug_snapshot!(
5702 optimize(parse(
5703 "(a & (author_name(A) | 0)) & (b & (author_name(B) | 1)) & (c & (author_name(C) | 2))")?),
5704 @r#"
5705 Intersection(
5706 Intersection(
5707 Intersection(
5708 CommitRef(Symbol("a")),
5709 CommitRef(Symbol("b")),
5710 ),
5711 CommitRef(Symbol("c")),
5712 ),
5713 AsFilter(
5714 Intersection(
5715 Intersection(
5716 Union(
5717 Filter(AuthorName(Pattern(Exact("A")))),
5718 CommitRef(Symbol("0")),
5719 ),
5720 Union(
5721 Filter(AuthorName(Pattern(Exact("B")))),
5722 CommitRef(Symbol("1")),
5723 ),
5724 ),
5725 Union(
5726 Filter(AuthorName(Pattern(Exact("C")))),
5727 CommitRef(Symbol("2")),
5728 ),
5729 ),
5730 ),
5731 )
5732 "#);
5733
5734 insta::assert_debug_snapshot!(optimize(parse("::foo | ::author_name(bar)")?), @r#"
5736 Ancestors {
5737 heads: HeadsRange {
5738 roots: None,
5739 heads: VisibleHeadsOrReferenced,
5740 parents_range: 0..4294967295,
5741 filter: AsFilter(
5742 Union(
5743 CommitRef(Symbol("foo")),
5744 Filter(AuthorName(Pattern(Exact("bar")))),
5745 ),
5746 ),
5747 },
5748 generation: 0..18446744073709551615,
5749 parents_range: 0..4294967295,
5750 }
5751 "#);
5752 Ok(())
5753 }
5754
5755 #[test]
5756 fn test_optimize_ancestors() -> TestResult {
5757 let settings = insta_settings();
5758 let _guard = settings.bind_to_scope();
5759
5760 insta::assert_debug_snapshot!(optimize(parse("foo--")?), @r#"
5762 Ancestors {
5763 heads: CommitRef(Symbol("foo")),
5764 generation: 2..3,
5765 parents_range: 0..4294967295,
5766 }
5767 "#);
5768 insta::assert_debug_snapshot!(optimize(parse("::(foo---)")?), @r#"
5769 Ancestors {
5770 heads: CommitRef(Symbol("foo")),
5771 generation: 3..18446744073709551615,
5772 parents_range: 0..4294967295,
5773 }
5774 "#);
5775 insta::assert_debug_snapshot!(optimize(parse("(::foo)---")?), @r#"
5776 Ancestors {
5777 heads: CommitRef(Symbol("foo")),
5778 generation: 3..18446744073709551615,
5779 parents_range: 0..4294967295,
5780 }
5781 "#);
5782
5783 insta::assert_debug_snapshot!(optimize(parse("foo---+")?), @r#"
5785 Descendants {
5786 roots: Ancestors {
5787 heads: CommitRef(Symbol("foo")),
5788 generation: 3..4,
5789 parents_range: 0..4294967295,
5790 },
5791 generation: 1..2,
5792 }
5793 "#);
5794
5795 insta::assert_debug_snapshot!(optimize(parse("foo..(bar--)")?), @r#"
5797 Range {
5798 roots: CommitRef(Symbol("foo")),
5799 heads: CommitRef(Symbol("bar")),
5800 generation: 2..18446744073709551615,
5801 parents_range: 0..4294967295,
5802 }
5803 "#);
5804 insta::assert_debug_snapshot!(optimize(parse("(foo--)..(bar---)")?), @r#"
5806 Range {
5807 roots: Ancestors {
5808 heads: CommitRef(Symbol("foo")),
5809 generation: 2..3,
5810 parents_range: 0..4294967295,
5811 },
5812 heads: CommitRef(Symbol("bar")),
5813 generation: 3..18446744073709551615,
5814 parents_range: 0..4294967295,
5815 }
5816 "#);
5817 insta::assert_debug_snapshot!(
5819 optimize(parse("~ancestors(foo, 2) & ::bar")?), @r#"
5820 Difference(
5821 Ancestors {
5822 heads: CommitRef(Symbol("bar")),
5823 generation: 0..18446744073709551615,
5824 parents_range: 0..4294967295,
5825 },
5826 Ancestors {
5827 heads: CommitRef(Symbol("foo")),
5828 generation: 0..2,
5829 parents_range: 0..4294967295,
5830 },
5831 )
5832 "#);
5833
5834 insta::assert_debug_snapshot!(optimize(parse("(foo..bar)--")?), @r#"
5837 Ancestors {
5838 heads: Range {
5839 roots: CommitRef(Symbol("foo")),
5840 heads: CommitRef(Symbol("bar")),
5841 generation: 0..18446744073709551615,
5842 parents_range: 0..4294967295,
5843 },
5844 generation: 2..3,
5845 parents_range: 0..4294967295,
5846 }
5847 "#);
5848 insta::assert_debug_snapshot!(optimize(parse("foo..(bar..baz)")?), @r#"
5849 Range {
5850 roots: CommitRef(Symbol("foo")),
5851 heads: HeadsRange {
5852 roots: CommitRef(Symbol("bar")),
5853 heads: CommitRef(Symbol("baz")),
5854 parents_range: 0..4294967295,
5855 filter: All,
5856 },
5857 generation: 0..18446744073709551615,
5858 parents_range: 0..4294967295,
5859 }
5860 "#);
5861
5862 insta::assert_debug_snapshot!(
5864 optimize(parse("ancestors(ancestors(foo), 0)")?), @r#"
5865 Ancestors {
5866 heads: CommitRef(Symbol("foo")),
5867 generation: 0..0,
5868 parents_range: 0..4294967295,
5869 }
5870 "#
5871 );
5872 insta::assert_debug_snapshot!(
5873 optimize(parse("ancestors(ancestors(foo, 0))")?), @r#"
5874 Ancestors {
5875 heads: CommitRef(Symbol("foo")),
5876 generation: 0..0,
5877 parents_range: 0..4294967295,
5878 }
5879 "#
5880 );
5881
5882 insta::assert_debug_snapshot!(
5884 optimize(parse("first_ancestors(first_ancestors(foo, 5), 5)")?), @r#"
5885 Ancestors {
5886 heads: CommitRef(Symbol("foo")),
5887 generation: 0..9,
5888 parents_range: 0..1,
5889 }
5890 "#
5891 );
5892 insta::assert_debug_snapshot!(
5893 optimize(parse("first_ancestors(first_parent(foo), 5)")?), @r#"
5894 Ancestors {
5895 heads: CommitRef(Symbol("foo")),
5896 generation: 1..6,
5897 parents_range: 0..1,
5898 }
5899 "#
5900 );
5901 insta::assert_debug_snapshot!(
5902 optimize(parse("first_ancestors(ancestors(foo, 5), 5)")?), @r#"
5903 Ancestors {
5904 heads: Ancestors {
5905 heads: CommitRef(Symbol("foo")),
5906 generation: 0..5,
5907 parents_range: 0..4294967295,
5908 },
5909 generation: 0..5,
5910 parents_range: 0..1,
5911 }
5912 "#
5913 );
5914 insta::assert_debug_snapshot!(
5915 optimize(parse("ancestors(first_ancestors(foo, 5), 5)")?), @r#"
5916 Ancestors {
5917 heads: Ancestors {
5918 heads: CommitRef(Symbol("foo")),
5919 generation: 0..5,
5920 parents_range: 0..1,
5921 },
5922 generation: 0..5,
5923 parents_range: 0..4294967295,
5924 }
5925 "#
5926 );
5927 Ok(())
5928 }
5929
5930 #[test]
5931 fn test_optimize_descendants() -> TestResult {
5932 let settings = insta_settings();
5933 let _guard = settings.bind_to_scope();
5934
5935 insta::assert_debug_snapshot!(optimize(parse("foo++")?), @r#"
5937 Descendants {
5938 roots: CommitRef(Symbol("foo")),
5939 generation: 2..3,
5940 }
5941 "#);
5942 insta::assert_debug_snapshot!(optimize(parse("(foo+++)::")?), @r#"
5943 Descendants {
5944 roots: CommitRef(Symbol("foo")),
5945 generation: 3..18446744073709551615,
5946 }
5947 "#);
5948 insta::assert_debug_snapshot!(optimize(parse("(foo::)+++")?), @r#"
5949 Descendants {
5950 roots: CommitRef(Symbol("foo")),
5951 generation: 3..18446744073709551615,
5952 }
5953 "#);
5954
5955 insta::assert_debug_snapshot!(optimize(parse("foo+++-")?), @r#"
5957 Ancestors {
5958 heads: Descendants {
5959 roots: CommitRef(Symbol("foo")),
5960 generation: 3..4,
5961 },
5962 generation: 1..2,
5963 parents_range: 0..4294967295,
5964 }
5965 "#);
5966
5967 insta::assert_debug_snapshot!(optimize(parse("(foo++)::bar")?), @r#"
5971 DagRange {
5972 roots: Descendants {
5973 roots: CommitRef(Symbol("foo")),
5974 generation: 2..3,
5975 },
5976 heads: CommitRef(Symbol("bar")),
5977 }
5978 "#);
5979 Ok(())
5980 }
5981
5982 #[test]
5983 fn test_optimize_flatten_intersection() -> TestResult {
5984 let settings = insta_settings();
5985 let _guard = settings.bind_to_scope();
5986
5987 insta::assert_debug_snapshot!(optimize(parse("a & ((b & c) & (d & e))")?), @r#"
5989 Intersection(
5990 Intersection(
5991 Intersection(
5992 Intersection(
5993 CommitRef(Symbol("a")),
5994 CommitRef(Symbol("b")),
5995 ),
5996 CommitRef(Symbol("c")),
5997 ),
5998 CommitRef(Symbol("d")),
5999 ),
6000 CommitRef(Symbol("e")),
6001 )
6002 "#);
6003 Ok(())
6004 }
6005
6006 #[test]
6007 fn test_optimize_ancestors_union() -> TestResult {
6008 let settings = insta_settings();
6009 let _guard = settings.bind_to_scope();
6010
6011 insta::assert_debug_snapshot!(optimize(parse("::a | ::b | ::c | ::d")?), @r#"
6013 Ancestors {
6014 heads: Union(
6015 Union(
6016 CommitRef(Symbol("a")),
6017 CommitRef(Symbol("b")),
6018 ),
6019 Union(
6020 CommitRef(Symbol("c")),
6021 CommitRef(Symbol("d")),
6022 ),
6023 ),
6024 generation: 0..18446744073709551615,
6025 parents_range: 0..4294967295,
6026 }
6027 "#);
6028 insta::assert_debug_snapshot!(optimize(parse("ancestors(a-) | ancestors(b)")?), @r#"
6029 Ancestors {
6030 heads: Union(
6031 Ancestors {
6032 heads: CommitRef(Symbol("a")),
6033 generation: 1..2,
6034 parents_range: 0..4294967295,
6035 },
6036 CommitRef(Symbol("b")),
6037 ),
6038 generation: 0..18446744073709551615,
6039 parents_range: 0..4294967295,
6040 }
6041 "#);
6042
6043 insta::assert_debug_snapshot!(optimize(parse("~::a- & ~::b & ~::c & ::d")?), @r#"
6045 Range {
6046 roots: Union(
6047 Union(
6048 Ancestors {
6049 heads: CommitRef(Symbol("a")),
6050 generation: 1..2,
6051 parents_range: 0..4294967295,
6052 },
6053 CommitRef(Symbol("b")),
6054 ),
6055 CommitRef(Symbol("c")),
6056 ),
6057 heads: CommitRef(Symbol("d")),
6058 generation: 0..18446744073709551615,
6059 parents_range: 0..4294967295,
6060 }
6061 "#);
6062 insta::assert_debug_snapshot!(optimize(parse("a..b ~ ::c- ~ ::d")?), @r#"
6063 Range {
6064 roots: Union(
6065 Union(
6066 CommitRef(Symbol("a")),
6067 Ancestors {
6068 heads: CommitRef(Symbol("c")),
6069 generation: 1..2,
6070 parents_range: 0..4294967295,
6071 },
6072 ),
6073 CommitRef(Symbol("d")),
6074 ),
6075 heads: CommitRef(Symbol("b")),
6076 generation: 0..18446744073709551615,
6077 parents_range: 0..4294967295,
6078 }
6079 "#);
6080
6081 insta::assert_debug_snapshot!(optimize(parse("ancestors(a, 2) | ancestors(b)")?), @r#"
6083 Union(
6084 Ancestors {
6085 heads: CommitRef(Symbol("a")),
6086 generation: 0..2,
6087 parents_range: 0..4294967295,
6088 },
6089 Ancestors {
6090 heads: CommitRef(Symbol("b")),
6091 generation: 0..18446744073709551615,
6092 parents_range: 0..4294967295,
6093 },
6094 )
6095 "#);
6096 Ok(())
6097 }
6098
6099 #[test]
6100 fn test_optimize_sort_negations_and_ancestors() -> TestResult {
6101 let settings = insta_settings();
6102 let _guard = settings.bind_to_scope();
6103
6104 insta::assert_debug_snapshot!(optimize(parse("~a & ::b & ~::c & d ~ e & f & ::g & ~::h")?), @r#"
6107 Difference(
6108 Difference(
6109 Intersection(
6110 Intersection(
6111 Intersection(
6112 Range {
6113 roots: Union(
6114 CommitRef(Symbol("c")),
6115 CommitRef(Symbol("h")),
6116 ),
6117 heads: CommitRef(Symbol("b")),
6118 generation: 0..18446744073709551615,
6119 parents_range: 0..4294967295,
6120 },
6121 Ancestors {
6122 heads: CommitRef(Symbol("g")),
6123 generation: 0..18446744073709551615,
6124 parents_range: 0..4294967295,
6125 },
6126 ),
6127 CommitRef(Symbol("d")),
6128 ),
6129 CommitRef(Symbol("f")),
6130 ),
6131 CommitRef(Symbol("a")),
6132 ),
6133 CommitRef(Symbol("e")),
6134 )
6135 "#);
6136 Ok(())
6137 }
6138
6139 #[test]
6140 fn test_optimize_heads_range() -> TestResult {
6141 let settings = insta_settings();
6142 let _guard = settings.bind_to_scope();
6143
6144 insta::assert_debug_snapshot!(optimize(parse("heads(::)")?), @"
6146 HeadsRange {
6147 roots: None,
6148 heads: VisibleHeadsOrReferenced,
6149 parents_range: 0..4294967295,
6150 filter: All,
6151 }
6152 ");
6153 insta::assert_debug_snapshot!(optimize(parse("heads(::foo)")?), @r#"
6154 HeadsRange {
6155 roots: None,
6156 heads: CommitRef(Symbol("foo")),
6157 parents_range: 0..4294967295,
6158 filter: All,
6159 }
6160 "#);
6161 insta::assert_debug_snapshot!(optimize(parse("heads(..)")?), @"
6164 HeadsRange {
6165 roots: None,
6166 heads: VisibleHeadsOrReferenced,
6167 parents_range: 0..4294967295,
6168 filter: NotIn(Root),
6169 }
6170 ");
6171 insta::assert_debug_snapshot!(optimize(parse("heads(foo..)")?), @r#"
6172 HeadsRange {
6173 roots: CommitRef(Symbol("foo")),
6174 heads: VisibleHeadsOrReferenced,
6175 parents_range: 0..4294967295,
6176 filter: All,
6177 }
6178 "#);
6179 insta::assert_debug_snapshot!(optimize(parse("heads(..bar)")?), @r#"
6180 HeadsRange {
6181 roots: Root,
6182 heads: CommitRef(Symbol("bar")),
6183 parents_range: 0..4294967295,
6184 filter: All,
6185 }
6186 "#);
6187 insta::assert_debug_snapshot!(optimize(parse("heads(foo..bar)")?), @r#"
6188 HeadsRange {
6189 roots: CommitRef(Symbol("foo")),
6190 heads: CommitRef(Symbol("bar")),
6191 parents_range: 0..4294967295,
6192 filter: All,
6193 }
6194 "#);
6195 insta::assert_debug_snapshot!(optimize(parse("heads(~::foo & ::bar)")?), @r#"
6196 HeadsRange {
6197 roots: CommitRef(Symbol("foo")),
6198 heads: CommitRef(Symbol("bar")),
6199 parents_range: 0..4294967295,
6200 filter: All,
6201 }
6202 "#);
6203 insta::assert_debug_snapshot!(optimize(parse("heads(~::foo)")?), @r#"
6204 HeadsRange {
6205 roots: CommitRef(Symbol("foo")),
6206 heads: VisibleHeadsOrReferenced,
6207 parents_range: 0..4294967295,
6208 filter: All,
6209 }
6210 "#);
6211 insta::assert_debug_snapshot!(optimize(parse("heads(a..b & c..d)")?), @r#"
6212 HeadsRange {
6213 roots: Union(
6214 CommitRef(Symbol("a")),
6215 CommitRef(Symbol("c")),
6216 ),
6217 heads: CommitRef(Symbol("b")),
6218 parents_range: 0..4294967295,
6219 filter: Ancestors {
6220 heads: CommitRef(Symbol("d")),
6221 generation: 0..18446744073709551615,
6222 parents_range: 0..4294967295,
6223 },
6224 }
6225 "#);
6226
6227 insta::assert_debug_snapshot!(optimize(parse("heads(first_ancestors(foo))")?), @r#"
6229 HeadsRange {
6230 roots: None,
6231 heads: CommitRef(Symbol("foo")),
6232 parents_range: 0..1,
6233 filter: All,
6234 }
6235 "#);
6236 insta::assert_debug_snapshot!(optimize(parse("heads(first_ancestors(foo) & bar..)")?), @r#"
6237 HeadsRange {
6238 roots: CommitRef(Symbol("bar")),
6239 heads: CommitRef(Symbol("foo")),
6240 parents_range: 0..1,
6241 filter: All,
6242 }
6243 "#);
6244 insta::assert_debug_snapshot!(optimize(parse("heads(foo.. & first_ancestors(bar) & ::baz)")?), @r#"
6245 HeadsRange {
6246 roots: CommitRef(Symbol("foo")),
6247 heads: CommitRef(Symbol("bar")),
6248 parents_range: 0..1,
6249 filter: Ancestors {
6250 heads: CommitRef(Symbol("baz")),
6251 generation: 0..18446744073709551615,
6252 parents_range: 0..4294967295,
6253 },
6254 }
6255 "#);
6256
6257 insta::assert_debug_snapshot!(optimize(parse("heads(ancestors(foo, 2))")?), @r#"
6259 Heads(
6260 Ancestors {
6261 heads: CommitRef(Symbol("foo")),
6262 generation: 0..2,
6263 parents_range: 0..4294967295,
6264 },
6265 )
6266 "#);
6267 insta::assert_debug_snapshot!(optimize(parse("heads(first_ancestors(foo, 2))")?), @r#"
6268 Heads(
6269 Ancestors {
6270 heads: CommitRef(Symbol("foo")),
6271 generation: 0..2,
6272 parents_range: 0..1,
6273 },
6274 )
6275 "#);
6276
6277 insta::assert_debug_snapshot!(optimize(parse("heads(ancestors(foo--))")?), @r#"
6279 HeadsRange {
6280 roots: None,
6281 heads: Ancestors {
6282 heads: CommitRef(Symbol("foo")),
6283 generation: 2..3,
6284 parents_range: 0..4294967295,
6285 },
6286 parents_range: 0..4294967295,
6287 filter: All,
6288 }
6289 "#);
6290 insta::assert_debug_snapshot!(optimize(parse("heads(first_ancestors(first_parent(foo, 2)))")?), @r#"
6291 HeadsRange {
6292 roots: None,
6293 heads: Ancestors {
6294 heads: CommitRef(Symbol("foo")),
6295 generation: 2..3,
6296 parents_range: 0..1,
6297 },
6298 parents_range: 0..1,
6299 filter: All,
6300 }
6301 "#);
6302
6303 insta::assert_debug_snapshot!(optimize(parse("heads(author_name(A) | author_name(B))")?), @r#"
6305 HeadsRange {
6306 roots: None,
6307 heads: VisibleHeadsOrReferenced,
6308 parents_range: 0..4294967295,
6309 filter: AsFilter(
6310 Union(
6311 Filter(AuthorName(Pattern(Exact("A")))),
6312 Filter(AuthorName(Pattern(Exact("B")))),
6313 ),
6314 ),
6315 }
6316 "#);
6317 insta::assert_debug_snapshot!(optimize(parse("heads(~author_name(A))")?), @r#"
6318 HeadsRange {
6319 roots: None,
6320 heads: VisibleHeadsOrReferenced,
6321 parents_range: 0..4294967295,
6322 filter: AsFilter(
6323 NotIn(
6324 Filter(AuthorName(Pattern(Exact("A")))),
6325 ),
6326 ),
6327 }
6328 "#);
6329 insta::assert_debug_snapshot!(optimize(parse("heads(~foo)")?), @r#"
6330 HeadsRange {
6331 roots: None,
6332 heads: VisibleHeadsOrReferenced,
6333 parents_range: 0..4294967295,
6334 filter: NotIn(CommitRef(Symbol("foo"))),
6335 }
6336 "#);
6337
6338 insta::assert_debug_snapshot!(optimize(parse("heads(author_name(A) & ::foo ~ author_name(B))")?), @r#"
6340 HeadsRange {
6341 roots: None,
6342 heads: CommitRef(Symbol("foo")),
6343 parents_range: 0..4294967295,
6344 filter: AsFilter(
6345 Difference(
6346 Filter(AuthorName(Pattern(Exact("A")))),
6347 Filter(AuthorName(Pattern(Exact("B")))),
6348 ),
6349 ),
6350 }
6351 "#);
6352
6353 insta::assert_debug_snapshot!(optimize(parse("heads(~foo & ~roots(bar) & ::baz)")?), @r#"
6355 HeadsRange {
6356 roots: None,
6357 heads: CommitRef(Symbol("baz")),
6358 parents_range: 0..4294967295,
6359 filter: Difference(
6360 NotIn(CommitRef(Symbol("foo"))),
6361 Roots(CommitRef(Symbol("bar"))),
6362 ),
6363 }
6364 "#);
6365 Ok(())
6366 }
6367
6368 #[test]
6369 fn test_optimize_ancestors_heads_range() -> TestResult {
6370 insta::assert_debug_snapshot!(optimize(parse("::description(bar)")?), @r#"
6372 Ancestors {
6373 heads: HeadsRange {
6374 roots: None,
6375 heads: VisibleHeadsOrReferenced,
6376 parents_range: 0..4294967295,
6377 filter: Filter(
6378 Description(
6379 Pattern(
6380 Exact(
6381 "bar",
6382 ),
6383 ),
6384 ),
6385 ),
6386 },
6387 generation: 0..18446744073709551615,
6388 parents_range: 0..4294967295,
6389 }
6390 "#);
6391 insta::assert_debug_snapshot!(optimize(parse("author_name(foo)..")?), @r#"
6392 Range {
6393 roots: HeadsRange {
6394 roots: None,
6395 heads: VisibleHeadsOrReferenced,
6396 parents_range: 0..4294967295,
6397 filter: Filter(
6398 AuthorName(
6399 Pattern(
6400 Exact(
6401 "foo",
6402 ),
6403 ),
6404 ),
6405 ),
6406 },
6407 heads: VisibleHeadsOrReferenced,
6408 generation: 0..18446744073709551615,
6409 parents_range: 0..4294967295,
6410 }
6411 "#);
6412
6413 insta::assert_debug_snapshot!(optimize(parse("::(foo..bar)")?), @r#"
6415 Ancestors {
6416 heads: HeadsRange {
6417 roots: CommitRef(
6418 Symbol(
6419 "foo",
6420 ),
6421 ),
6422 heads: CommitRef(
6423 Symbol(
6424 "bar",
6425 ),
6426 ),
6427 parents_range: 0..4294967295,
6428 filter: All,
6429 },
6430 generation: 0..18446744073709551615,
6431 parents_range: 0..4294967295,
6432 }
6433 "#);
6434
6435 insta::assert_debug_snapshot!(optimize(parse("ancestors(author_name(foo), 5)")?), @r#"
6437 Ancestors {
6438 heads: Filter(
6439 AuthorName(
6440 Pattern(
6441 Exact(
6442 "foo",
6443 ),
6444 ),
6445 ),
6446 ),
6447 generation: 0..5,
6448 parents_range: 0..4294967295,
6449 }
6450 "#);
6451 insta::assert_debug_snapshot!(optimize(parse("first_ancestors(author_name(foo))")?), @r#"
6452 Ancestors {
6453 heads: Filter(
6454 AuthorName(
6455 Pattern(
6456 Exact(
6457 "foo",
6458 ),
6459 ),
6460 ),
6461 ),
6462 generation: 0..18446744073709551615,
6463 parents_range: 0..1,
6464 }
6465 "#);
6466 Ok(())
6467 }
6468
6469 #[test]
6470 fn test_escape_string_literal() {
6471 assert_eq!(format_symbol("foo"), "foo");
6473 assert_eq!(format_symbol("foo.bar"), "foo.bar");
6474
6475 assert_eq!(format_symbol("foo@bar"), r#""foo@bar""#);
6477 assert_eq!(format_symbol("foo bar"), r#""foo bar""#);
6478 assert_eq!(format_symbol(" foo "), r#"" foo ""#);
6479 assert_eq!(format_symbol("(foo)"), r#""(foo)""#);
6480 assert_eq!(format_symbol("all:foo"), r#""all:foo""#);
6481
6482 assert_eq!(format_symbol("foo\"bar"), r#""foo\"bar""#);
6484 assert_eq!(format_symbol("foo\\bar"), r#""foo\\bar""#);
6485 assert_eq!(format_symbol("foo\\\"bar"), r#""foo\\\"bar""#);
6486 assert_eq!(format_symbol("foo\nbar"), r#""foo\nbar""#);
6487
6488 assert_eq!(format_symbol("foo\"bar"), r#""foo\"bar""#);
6491 assert_eq!(format_symbol("foo\\bar"), r#""foo\\bar""#);
6492 assert_eq!(format_symbol("foo\\\"bar"), r#""foo\\\"bar""#);
6493 assert_eq!(format_symbol("foo \x01 bar"), r#""foo \x01 bar""#);
6494 }
6495
6496 #[test]
6497 fn test_escape_remote_symbol() {
6498 assert_eq!(format_remote_symbol("foo", "bar"), "foo@bar");
6499 assert_eq!(
6500 format_remote_symbol(" foo ", "bar:baz"),
6501 r#"" foo "@"bar:baz""#
6502 );
6503 }
6504}