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