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