1#![allow(missing_docs)]
16
17use std::any::Any;
18use std::collections::hash_map;
19use std::collections::HashMap;
20use std::convert::Infallible;
21use std::fmt;
22use std::ops::Range;
23use std::rc::Rc;
24use std::sync::Arc;
25
26use itertools::Itertools as _;
27use once_cell::sync::Lazy;
28use thiserror::Error;
29
30use crate::backend::BackendError;
31use crate::backend::ChangeId;
32use crate::backend::CommitId;
33use crate::commit::Commit;
34use crate::dsl_util;
35use crate::dsl_util::collect_similar;
36use crate::dsl_util::AliasExpandError as _;
37use crate::fileset;
38use crate::fileset::FilesetDiagnostics;
39use crate::fileset::FilesetExpression;
40use crate::graph::GraphNode;
41use crate::hex_util::to_forward_hex;
42use crate::id_prefix::IdPrefixContext;
43use crate::id_prefix::IdPrefixIndex;
44use crate::object_id::HexPrefix;
45use crate::object_id::PrefixResolution;
46use crate::op_store::RemoteRefState;
47use crate::op_walk;
48use crate::ref_name::RemoteRefSymbol;
49use crate::ref_name::RemoteRefSymbolBuf;
50use crate::ref_name::WorkspaceName;
51use crate::ref_name::WorkspaceNameBuf;
52use crate::repo::ReadonlyRepo;
53use crate::repo::Repo;
54use crate::repo::RepoLoaderError;
55use crate::repo_path::RepoPathUiConverter;
56use crate::revset_parser;
57pub use crate::revset_parser::expect_literal;
58pub use crate::revset_parser::parse_program;
59pub use crate::revset_parser::parse_symbol;
60pub use crate::revset_parser::BinaryOp;
61pub use crate::revset_parser::ExpressionKind;
62pub use crate::revset_parser::ExpressionNode;
63pub use crate::revset_parser::FunctionCallNode;
64pub use crate::revset_parser::RevsetAliasesMap;
65pub use crate::revset_parser::RevsetDiagnostics;
66pub use crate::revset_parser::RevsetParseError;
67pub use crate::revset_parser::RevsetParseErrorKind;
68pub use crate::revset_parser::UnaryOp;
69use crate::store::Store;
70use crate::str_util::StringPattern;
71use crate::time_util::DatePattern;
72use crate::time_util::DatePatternContext;
73
74#[derive(Debug, Error)]
76pub enum RevsetResolutionError {
77 #[error("Revision `{name}` doesn't exist")]
78 NoSuchRevision {
79 name: String,
80 candidates: Vec<String>,
81 },
82 #[error("Workspace `{}` doesn't have a working-copy commit", name.as_symbol())]
83 WorkspaceMissingWorkingCopy { name: WorkspaceNameBuf },
84 #[error("An empty string is not a valid revision")]
85 EmptyString,
86 #[error("Commit ID prefix `{0}` is ambiguous")]
87 AmbiguousCommitIdPrefix(String),
88 #[error("Change ID prefix `{0}` is ambiguous")]
89 AmbiguousChangeIdPrefix(String),
90 #[error("Unexpected error from commit backend")]
91 Backend(#[source] BackendError),
92 #[error(transparent)]
93 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
94}
95
96#[derive(Debug, Error)]
98pub enum RevsetEvaluationError {
99 #[error("Unexpected error from commit backend")]
100 Backend(#[from] BackendError),
101 #[error(transparent)]
102 Other(Box<dyn std::error::Error + Send + Sync>),
103}
104
105impl RevsetEvaluationError {
106 pub fn into_backend_error(self) -> BackendError {
109 match self {
110 Self::Backend(err) => err,
111 Self::Other(err) => BackendError::Other(err),
112 }
113 }
114}
115
116pub const GENERATION_RANGE_FULL: Range<u64> = 0..u64::MAX;
118pub const GENERATION_RANGE_EMPTY: Range<u64> = 0..0;
119
120#[derive(Clone, Copy, Debug, Eq, PartialEq)]
125pub enum RevsetModifier {
126 All,
129}
130
131#[derive(Clone, Debug)]
133pub enum RevsetCommitRef {
134 WorkingCopy(WorkspaceNameBuf),
135 WorkingCopies,
136 Symbol(String),
137 RemoteSymbol(RemoteRefSymbolBuf),
138 Bookmarks(StringPattern),
139 RemoteBookmarks {
140 bookmark_pattern: StringPattern,
141 remote_pattern: StringPattern,
142 remote_ref_state: Option<RemoteRefState>,
143 },
144 Tags(StringPattern),
145 GitRefs,
146 GitHead,
147}
148
149pub trait RevsetFilterExtension: std::fmt::Debug + Any {
151 fn as_any(&self) -> &dyn Any;
152
153 fn matches_commit(&self, commit: &Commit) -> bool;
155}
156
157#[derive(Clone, Debug)]
158pub enum RevsetFilterPredicate {
159 ParentCount(Range<u32>),
161 Description(StringPattern),
163 Subject(StringPattern),
165 AuthorName(StringPattern),
167 AuthorEmail(StringPattern),
169 AuthorDate(DatePattern),
171 CommitterName(StringPattern),
173 CommitterEmail(StringPattern),
175 CommitterDate(DatePattern),
177 File(FilesetExpression),
179 DiffContains {
181 text: StringPattern,
182 files: FilesetExpression,
183 },
184 HasConflict,
186 Signed,
188 Extension(Rc<dyn RevsetFilterExtension>),
190}
191
192mod private {
193 pub trait ExpressionState {
195 type CommitRef: Clone;
196 type Operation: Clone;
197 }
198
199 #[derive(Debug)]
201 pub enum UserExpressionState {}
202 #[derive(Debug)]
203 pub enum ResolvedExpressionState {}
204}
205
206use private::ExpressionState;
207use private::ResolvedExpressionState;
208use private::UserExpressionState;
209
210impl ExpressionState for UserExpressionState {
211 type CommitRef = RevsetCommitRef;
212 type Operation = String;
213}
214
215impl ExpressionState for ResolvedExpressionState {
216 type CommitRef = Infallible;
217 type Operation = Infallible;
218}
219
220pub type UserRevsetExpression = RevsetExpression<UserExpressionState>;
222pub type ResolvedRevsetExpression = RevsetExpression<ResolvedExpressionState>;
224
225#[derive(Clone, Debug)]
230pub enum RevsetExpression<St: ExpressionState> {
231 None,
232 All,
233 VisibleHeads,
234 Root,
235 Commits(Vec<CommitId>),
236 CommitRef(St::CommitRef),
237 Ancestors {
238 heads: Rc<Self>,
239 generation: Range<u64>,
240 },
241 Descendants {
242 roots: Rc<Self>,
243 generation: Range<u64>,
244 },
245 Range {
247 roots: Rc<Self>,
248 heads: Rc<Self>,
249 generation: Range<u64>,
250 },
251 DagRange {
253 roots: Rc<Self>,
254 heads: Rc<Self>,
255 },
257 Reachable {
259 sources: Rc<Self>,
260 domain: Rc<Self>,
261 },
262 Heads(Rc<Self>),
263 Roots(Rc<Self>),
264 ForkPoint(Rc<Self>),
265 Latest {
266 candidates: Rc<Self>,
267 count: usize,
268 },
269 Filter(RevsetFilterPredicate),
270 AsFilter(Rc<Self>),
272 AtOperation {
274 operation: St::Operation,
275 candidates: Rc<Self>,
276 },
277 WithinVisibility {
279 candidates: Rc<Self>,
280 visible_heads: Vec<CommitId>,
282 },
283 Coalesce(Rc<Self>, Rc<Self>),
284 Present(Rc<Self>),
285 NotIn(Rc<Self>),
286 Union(Rc<Self>, Rc<Self>),
287 Intersection(Rc<Self>, Rc<Self>),
288 Difference(Rc<Self>, Rc<Self>),
289}
290
291impl<St: ExpressionState> RevsetExpression<St> {
294 pub fn none() -> Rc<Self> {
295 Rc::new(Self::None)
296 }
297
298 pub fn all() -> Rc<Self> {
299 Rc::new(Self::All)
300 }
301
302 pub fn visible_heads() -> Rc<Self> {
303 Rc::new(Self::VisibleHeads)
304 }
305
306 pub fn root() -> Rc<Self> {
307 Rc::new(Self::Root)
308 }
309
310 pub fn commit(commit_id: CommitId) -> Rc<Self> {
311 Self::commits(vec![commit_id])
312 }
313
314 pub fn commits(commit_ids: Vec<CommitId>) -> Rc<Self> {
315 Rc::new(Self::Commits(commit_ids))
316 }
317
318 pub fn filter(predicate: RevsetFilterPredicate) -> Rc<Self> {
319 Rc::new(Self::Filter(predicate))
320 }
321
322 pub fn is_empty() -> Rc<Self> {
324 Self::filter(RevsetFilterPredicate::File(FilesetExpression::all())).negated()
325 }
326}
327
328impl<St: ExpressionState<CommitRef = RevsetCommitRef>> RevsetExpression<St> {
330 pub fn working_copy(name: WorkspaceNameBuf) -> Rc<Self> {
331 Rc::new(Self::CommitRef(RevsetCommitRef::WorkingCopy(name)))
332 }
333
334 pub fn working_copies() -> Rc<Self> {
335 Rc::new(Self::CommitRef(RevsetCommitRef::WorkingCopies))
336 }
337
338 pub fn symbol(value: String) -> Rc<Self> {
339 Rc::new(Self::CommitRef(RevsetCommitRef::Symbol(value)))
340 }
341
342 pub fn remote_symbol(value: RemoteRefSymbolBuf) -> Rc<Self> {
343 let commit_ref = RevsetCommitRef::RemoteSymbol(value);
344 Rc::new(Self::CommitRef(commit_ref))
345 }
346
347 pub fn bookmarks(pattern: StringPattern) -> Rc<Self> {
348 Rc::new(Self::CommitRef(RevsetCommitRef::Bookmarks(pattern)))
349 }
350
351 pub fn remote_bookmarks(
352 bookmark_pattern: StringPattern,
353 remote_pattern: StringPattern,
354 remote_ref_state: Option<RemoteRefState>,
355 ) -> Rc<Self> {
356 Rc::new(Self::CommitRef(RevsetCommitRef::RemoteBookmarks {
357 bookmark_pattern,
358 remote_pattern,
359 remote_ref_state,
360 }))
361 }
362
363 pub fn tags(pattern: StringPattern) -> Rc<Self> {
364 Rc::new(Self::CommitRef(RevsetCommitRef::Tags(pattern)))
365 }
366
367 pub fn git_refs() -> Rc<Self> {
368 Rc::new(Self::CommitRef(RevsetCommitRef::GitRefs))
369 }
370
371 pub fn git_head() -> Rc<Self> {
372 Rc::new(Self::CommitRef(RevsetCommitRef::GitHead))
373 }
374}
375
376impl<St: ExpressionState> RevsetExpression<St> {
378 pub fn latest(self: &Rc<Self>, count: usize) -> Rc<Self> {
379 Rc::new(Self::Latest {
380 candidates: self.clone(),
381 count,
382 })
383 }
384
385 pub fn heads(self: &Rc<Self>) -> Rc<Self> {
387 Rc::new(Self::Heads(self.clone()))
388 }
389
390 pub fn roots(self: &Rc<Self>) -> Rc<Self> {
392 Rc::new(Self::Roots(self.clone()))
393 }
394
395 pub fn parents(self: &Rc<Self>) -> Rc<Self> {
397 self.ancestors_at(1)
398 }
399
400 pub fn ancestors(self: &Rc<Self>) -> Rc<Self> {
402 self.ancestors_range(GENERATION_RANGE_FULL)
403 }
404
405 pub fn ancestors_at(self: &Rc<Self>, generation: u64) -> Rc<Self> {
408 self.ancestors_range(generation..(generation + 1))
409 }
410
411 pub fn ancestors_range(self: &Rc<Self>, generation_range: Range<u64>) -> Rc<Self> {
413 Rc::new(Self::Ancestors {
414 heads: self.clone(),
415 generation: generation_range,
416 })
417 }
418
419 pub fn children(self: &Rc<Self>) -> Rc<Self> {
421 self.descendants_at(1)
422 }
423
424 pub fn descendants(self: &Rc<Self>) -> Rc<Self> {
426 self.descendants_range(GENERATION_RANGE_FULL)
427 }
428
429 pub fn descendants_at(self: &Rc<Self>, generation: u64) -> Rc<Self> {
432 self.descendants_range(generation..(generation + 1))
433 }
434
435 pub fn descendants_range(self: &Rc<Self>, generation_range: Range<u64>) -> Rc<Self> {
437 Rc::new(Self::Descendants {
438 roots: self.clone(),
439 generation: generation_range,
440 })
441 }
442
443 pub fn fork_point(self: &Rc<Self>) -> Rc<Self> {
445 Rc::new(Self::ForkPoint(self.clone()))
446 }
447
448 pub fn filtered(self: &Rc<Self>, predicate: RevsetFilterPredicate) -> Rc<Self> {
450 self.intersection(&Self::filter(predicate))
451 }
452
453 pub fn dag_range_to(self: &Rc<Self>, heads: &Rc<Self>) -> Rc<Self> {
456 Rc::new(Self::DagRange {
457 roots: self.clone(),
458 heads: heads.clone(),
459 })
460 }
461
462 pub fn connected(self: &Rc<Self>) -> Rc<Self> {
465 self.dag_range_to(self)
466 }
467
468 pub fn reachable(self: &Rc<Self>, domain: &Rc<Self>) -> Rc<Self> {
471 Rc::new(Self::Reachable {
472 sources: self.clone(),
473 domain: domain.clone(),
474 })
475 }
476
477 pub fn range(self: &Rc<Self>, heads: &Rc<Self>) -> Rc<Self> {
479 Rc::new(Self::Range {
480 roots: self.clone(),
481 heads: heads.clone(),
482 generation: GENERATION_RANGE_FULL,
483 })
484 }
485
486 pub fn present(self: &Rc<Self>) -> Rc<Self> {
488 Rc::new(Self::Present(self.clone()))
489 }
490
491 pub fn negated(self: &Rc<Self>) -> Rc<Self> {
493 Rc::new(Self::NotIn(self.clone()))
494 }
495
496 pub fn union(self: &Rc<Self>, other: &Rc<Self>) -> Rc<Self> {
498 Rc::new(Self::Union(self.clone(), other.clone()))
499 }
500
501 pub fn union_all(expressions: &[Rc<Self>]) -> Rc<Self> {
503 match expressions {
504 [] => Self::none(),
505 [expression] => expression.clone(),
506 _ => {
507 let (left, right) = expressions.split_at(expressions.len() / 2);
509 Self::union(&Self::union_all(left), &Self::union_all(right))
510 }
511 }
512 }
513
514 pub fn intersection(self: &Rc<Self>, other: &Rc<Self>) -> Rc<Self> {
516 Rc::new(Self::Intersection(self.clone(), other.clone()))
517 }
518
519 pub fn minus(self: &Rc<Self>, other: &Rc<Self>) -> Rc<Self> {
521 Rc::new(Self::Difference(self.clone(), other.clone()))
522 }
523
524 pub fn coalesce(expressions: &[Rc<Self>]) -> Rc<Self> {
527 match expressions {
528 [] => Self::none(),
529 [expression] => expression.clone(),
530 _ => {
531 let (left, right) = expressions.split_at(expressions.len() / 2);
533 Rc::new(Self::Coalesce(Self::coalesce(left), Self::coalesce(right)))
534 }
535 }
536 }
537}
538
539impl<St: ExpressionState<CommitRef = RevsetCommitRef>> RevsetExpression<St> {
540 pub fn as_symbol(&self) -> Option<&str> {
542 match self {
543 RevsetExpression::CommitRef(RevsetCommitRef::Symbol(name)) => Some(name),
544 _ => None,
545 }
546 }
547}
548
549impl UserRevsetExpression {
550 pub fn resolve_user_expression(
553 &self,
554 repo: &dyn Repo,
555 symbol_resolver: &dyn SymbolResolver,
556 ) -> Result<Rc<ResolvedRevsetExpression>, RevsetResolutionError> {
557 resolve_symbols(repo, self, symbol_resolver)
558 }
559}
560
561impl ResolvedRevsetExpression {
562 pub fn evaluate<'index>(
564 self: Rc<Self>,
565 repo: &'index dyn Repo,
566 ) -> Result<Box<dyn Revset + 'index>, RevsetEvaluationError> {
567 optimize(self).evaluate_unoptimized(repo)
568 }
569
570 pub fn evaluate_unoptimized<'index>(
575 &self,
576 repo: &'index dyn Repo,
577 ) -> Result<Box<dyn Revset + 'index>, RevsetEvaluationError> {
578 let expr = self.to_backend_expression(repo);
579 repo.index().evaluate_revset(&expr, repo.store())
580 }
581
582 pub fn to_backend_expression(&self, repo: &dyn Repo) -> ResolvedExpression {
585 resolve_visibility(repo, self)
586 }
587}
588
589#[derive(Clone, Debug)]
590pub enum ResolvedPredicateExpression {
591 Filter(RevsetFilterPredicate),
593 Set(Box<ResolvedExpression>),
596 NotIn(Box<ResolvedPredicateExpression>),
597 Union(
598 Box<ResolvedPredicateExpression>,
599 Box<ResolvedPredicateExpression>,
600 ),
601}
602
603#[derive(Clone, Debug)]
611pub enum ResolvedExpression {
612 Commits(Vec<CommitId>),
613 Ancestors {
614 heads: Box<Self>,
615 generation: Range<u64>,
616 },
617 Range {
619 roots: Box<Self>,
620 heads: Box<Self>,
621 generation: Range<u64>,
622 },
623 DagRange {
625 roots: Box<Self>,
626 heads: Box<Self>,
627 generation_from_roots: Range<u64>,
628 },
629 Reachable {
631 sources: Box<Self>,
632 domain: Box<Self>,
633 },
634 Heads(Box<Self>),
635 Roots(Box<Self>),
636 ForkPoint(Box<Self>),
637 Latest {
638 candidates: Box<Self>,
639 count: usize,
640 },
641 Coalesce(Box<Self>, Box<Self>),
642 Union(Box<Self>, Box<Self>),
643 FilterWithin {
645 candidates: Box<Self>,
646 predicate: ResolvedPredicateExpression,
647 },
648 Intersection(Box<Self>, Box<Self>),
650 Difference(Box<Self>, Box<Self>),
651}
652
653pub type RevsetFunction = fn(
654 &mut RevsetDiagnostics,
655 &FunctionCallNode,
656 &LoweringContext,
657) -> Result<Rc<UserRevsetExpression>, RevsetParseError>;
658
659static BUILTIN_FUNCTION_MAP: Lazy<HashMap<&'static str, RevsetFunction>> = Lazy::new(|| {
660 let mut map: HashMap<&'static str, RevsetFunction> = HashMap::new();
663 map.insert("parents", |diagnostics, function, context| {
664 let [arg] = function.expect_exact_arguments()?;
665 let expression = lower_expression(diagnostics, arg, context)?;
666 Ok(expression.parents())
667 });
668 map.insert("children", |diagnostics, function, context| {
669 let [arg] = function.expect_exact_arguments()?;
670 let expression = lower_expression(diagnostics, arg, context)?;
671 Ok(expression.children())
672 });
673 map.insert("ancestors", |diagnostics, function, context| {
674 let ([heads_arg], [depth_opt_arg]) = function.expect_arguments()?;
675 let heads = lower_expression(diagnostics, heads_arg, context)?;
676 let generation = if let Some(depth_arg) = depth_opt_arg {
677 let depth = expect_literal(diagnostics, "integer", depth_arg)?;
678 0..depth
679 } else {
680 GENERATION_RANGE_FULL
681 };
682 Ok(heads.ancestors_range(generation))
683 });
684 map.insert("descendants", |diagnostics, function, context| {
685 let ([roots_arg], [depth_opt_arg]) = function.expect_arguments()?;
686 let roots = lower_expression(diagnostics, roots_arg, context)?;
687 let generation = if let Some(depth_arg) = depth_opt_arg {
688 let depth = expect_literal(diagnostics, "integer", depth_arg)?;
689 0..depth
690 } else {
691 GENERATION_RANGE_FULL
692 };
693 Ok(roots.descendants_range(generation))
694 });
695 map.insert("connected", |diagnostics, function, context| {
696 let [arg] = function.expect_exact_arguments()?;
697 let candidates = lower_expression(diagnostics, arg, context)?;
698 Ok(candidates.connected())
699 });
700 map.insert("reachable", |diagnostics, function, context| {
701 let [source_arg, domain_arg] = function.expect_exact_arguments()?;
702 let sources = lower_expression(diagnostics, source_arg, context)?;
703 let domain = lower_expression(diagnostics, domain_arg, context)?;
704 Ok(sources.reachable(&domain))
705 });
706 map.insert("none", |_diagnostics, function, _context| {
707 function.expect_no_arguments()?;
708 Ok(RevsetExpression::none())
709 });
710 map.insert("all", |_diagnostics, function, _context| {
711 function.expect_no_arguments()?;
712 Ok(RevsetExpression::all())
713 });
714 map.insert("working_copies", |_diagnostics, function, _context| {
715 function.expect_no_arguments()?;
716 Ok(RevsetExpression::working_copies())
717 });
718 map.insert("heads", |diagnostics, function, context| {
719 let [arg] = function.expect_exact_arguments()?;
720 let candidates = lower_expression(diagnostics, arg, context)?;
721 Ok(candidates.heads())
722 });
723 map.insert("roots", |diagnostics, function, context| {
724 let [arg] = function.expect_exact_arguments()?;
725 let candidates = lower_expression(diagnostics, arg, context)?;
726 Ok(candidates.roots())
727 });
728 map.insert("visible_heads", |_diagnostics, function, _context| {
729 function.expect_no_arguments()?;
730 Ok(RevsetExpression::visible_heads())
731 });
732 map.insert("root", |_diagnostics, function, _context| {
733 function.expect_no_arguments()?;
734 Ok(RevsetExpression::root())
735 });
736 map.insert("bookmarks", |diagnostics, function, _context| {
737 let ([], [opt_arg]) = function.expect_arguments()?;
738 let pattern = if let Some(arg) = opt_arg {
739 expect_string_pattern(diagnostics, arg)?
740 } else {
741 StringPattern::everything()
742 };
743 Ok(RevsetExpression::bookmarks(pattern))
744 });
745 map.insert("remote_bookmarks", |diagnostics, function, _context| {
746 parse_remote_bookmarks_arguments(diagnostics, function, None)
747 });
748 map.insert(
749 "tracked_remote_bookmarks",
750 |diagnostics, function, _context| {
751 parse_remote_bookmarks_arguments(diagnostics, function, Some(RemoteRefState::Tracked))
752 },
753 );
754 map.insert(
755 "untracked_remote_bookmarks",
756 |diagnostics, function, _context| {
757 parse_remote_bookmarks_arguments(diagnostics, function, Some(RemoteRefState::New))
758 },
759 );
760 map.insert("tags", |diagnostics, function, _context| {
761 let ([], [opt_arg]) = function.expect_arguments()?;
762 let pattern = if let Some(arg) = opt_arg {
763 expect_string_pattern(diagnostics, arg)?
764 } else {
765 StringPattern::everything()
766 };
767 Ok(RevsetExpression::tags(pattern))
768 });
769 map.insert("git_refs", |_diagnostics, function, _context| {
770 function.expect_no_arguments()?;
771 Ok(RevsetExpression::git_refs())
772 });
773 map.insert("git_head", |_diagnostics, function, _context| {
774 function.expect_no_arguments()?;
775 Ok(RevsetExpression::git_head())
776 });
777 map.insert("latest", |diagnostics, function, context| {
778 let ([candidates_arg], [count_opt_arg]) = function.expect_arguments()?;
779 let candidates = lower_expression(diagnostics, candidates_arg, context)?;
780 let count = if let Some(count_arg) = count_opt_arg {
781 expect_literal(diagnostics, "integer", count_arg)?
782 } else {
783 1
784 };
785 Ok(candidates.latest(count))
786 });
787 map.insert("fork_point", |diagnostics, function, context| {
788 let [expression_arg] = function.expect_exact_arguments()?;
789 let expression = lower_expression(diagnostics, expression_arg, context)?;
790 Ok(RevsetExpression::fork_point(&expression))
791 });
792 map.insert("merges", |_diagnostics, function, _context| {
793 function.expect_no_arguments()?;
794 Ok(RevsetExpression::filter(
795 RevsetFilterPredicate::ParentCount(2..u32::MAX),
796 ))
797 });
798 map.insert("description", |diagnostics, function, _context| {
799 let [arg] = function.expect_exact_arguments()?;
800 let pattern = expect_string_pattern(diagnostics, arg)?;
801 Ok(RevsetExpression::filter(
802 RevsetFilterPredicate::Description(pattern),
803 ))
804 });
805 map.insert("subject", |diagnostics, function, _context| {
806 let [arg] = function.expect_exact_arguments()?;
807 let pattern = expect_string_pattern(diagnostics, arg)?;
808 let predicate = RevsetFilterPredicate::Subject(pattern);
809 Ok(RevsetExpression::filter(predicate))
810 });
811 map.insert("author", |diagnostics, function, _context| {
812 let [arg] = function.expect_exact_arguments()?;
813 let pattern = expect_string_pattern(diagnostics, arg)?;
814 let name_predicate = RevsetFilterPredicate::AuthorName(pattern.clone());
815 let email_predicate = RevsetFilterPredicate::AuthorEmail(pattern);
816 Ok(RevsetExpression::filter(name_predicate)
817 .union(&RevsetExpression::filter(email_predicate)))
818 });
819 map.insert("author_name", |diagnostics, function, _context| {
820 let [arg] = function.expect_exact_arguments()?;
821 let pattern = expect_string_pattern(diagnostics, arg)?;
822 let predicate = RevsetFilterPredicate::AuthorName(pattern);
823 Ok(RevsetExpression::filter(predicate))
824 });
825 map.insert("author_email", |diagnostics, function, _context| {
826 let [arg] = function.expect_exact_arguments()?;
827 let pattern = expect_string_pattern(diagnostics, arg)?;
828 let predicate = RevsetFilterPredicate::AuthorEmail(pattern);
829 Ok(RevsetExpression::filter(predicate))
830 });
831 map.insert("author_date", |diagnostics, function, context| {
832 let [arg] = function.expect_exact_arguments()?;
833 let pattern = expect_date_pattern(diagnostics, arg, context.date_pattern_context())?;
834 Ok(RevsetExpression::filter(RevsetFilterPredicate::AuthorDate(
835 pattern,
836 )))
837 });
838 map.insert("signed", |_diagnostics, function, _context| {
839 function.expect_no_arguments()?;
840 let predicate = RevsetFilterPredicate::Signed;
841 Ok(RevsetExpression::filter(predicate))
842 });
843 map.insert("mine", |_diagnostics, function, context| {
844 function.expect_no_arguments()?;
845 let predicate =
849 RevsetFilterPredicate::AuthorEmail(StringPattern::exact_i(context.user_email));
850 Ok(RevsetExpression::filter(predicate))
851 });
852 map.insert("committer", |diagnostics, function, _context| {
853 let [arg] = function.expect_exact_arguments()?;
854 let pattern = expect_string_pattern(diagnostics, arg)?;
855 let name_predicate = RevsetFilterPredicate::CommitterName(pattern.clone());
856 let email_predicate = RevsetFilterPredicate::CommitterEmail(pattern);
857 Ok(RevsetExpression::filter(name_predicate)
858 .union(&RevsetExpression::filter(email_predicate)))
859 });
860 map.insert("committer_name", |diagnostics, function, _context| {
861 let [arg] = function.expect_exact_arguments()?;
862 let pattern = expect_string_pattern(diagnostics, arg)?;
863 let predicate = RevsetFilterPredicate::CommitterName(pattern);
864 Ok(RevsetExpression::filter(predicate))
865 });
866 map.insert("committer_email", |diagnostics, function, _context| {
867 let [arg] = function.expect_exact_arguments()?;
868 let pattern = expect_string_pattern(diagnostics, arg)?;
869 let predicate = RevsetFilterPredicate::CommitterEmail(pattern);
870 Ok(RevsetExpression::filter(predicate))
871 });
872 map.insert("committer_date", |diagnostics, function, context| {
873 let [arg] = function.expect_exact_arguments()?;
874 let pattern = expect_date_pattern(diagnostics, arg, context.date_pattern_context())?;
875 Ok(RevsetExpression::filter(
876 RevsetFilterPredicate::CommitterDate(pattern),
877 ))
878 });
879 map.insert("empty", |_diagnostics, function, _context| {
880 function.expect_no_arguments()?;
881 Ok(RevsetExpression::is_empty())
882 });
883 map.insert("files", |diagnostics, function, context| {
884 let ctx = context.workspace.as_ref().ok_or_else(|| {
885 RevsetParseError::with_span(
886 RevsetParseErrorKind::FsPathWithoutWorkspace,
887 function.args_span, )
889 })?;
890 let [arg] = function.expect_exact_arguments()?;
891 let expr = expect_fileset_expression(diagnostics, arg, ctx.path_converter)?;
892 Ok(RevsetExpression::filter(RevsetFilterPredicate::File(expr)))
893 });
894 map.insert("diff_contains", |diagnostics, function, context| {
895 let ([text_arg], [files_opt_arg]) = function.expect_arguments()?;
896 let text = expect_string_pattern(diagnostics, text_arg)?;
897 let files = if let Some(files_arg) = files_opt_arg {
898 let ctx = context.workspace.as_ref().ok_or_else(|| {
899 RevsetParseError::with_span(
900 RevsetParseErrorKind::FsPathWithoutWorkspace,
901 files_arg.span,
902 )
903 })?;
904 expect_fileset_expression(diagnostics, files_arg, ctx.path_converter)?
905 } else {
906 FilesetExpression::all()
909 };
910 Ok(RevsetExpression::filter(
911 RevsetFilterPredicate::DiffContains { text, files },
912 ))
913 });
914 map.insert("conflicts", |_diagnostics, function, _context| {
915 function.expect_no_arguments()?;
916 Ok(RevsetExpression::filter(RevsetFilterPredicate::HasConflict))
917 });
918 map.insert("present", |diagnostics, function, context| {
919 let [arg] = function.expect_exact_arguments()?;
920 let expression = lower_expression(diagnostics, arg, context)?;
921 Ok(expression.present())
922 });
923 map.insert("at_operation", |diagnostics, function, context| {
924 let [op_arg, cand_arg] = function.expect_exact_arguments()?;
925 let operation =
927 revset_parser::expect_expression_with(diagnostics, op_arg, |_diagnostics, node| {
928 Ok(node.span.as_str().to_owned())
929 })?;
930 let candidates = lower_expression(diagnostics, cand_arg, context)?;
931 Ok(Rc::new(RevsetExpression::AtOperation {
932 operation,
933 candidates,
934 }))
935 });
936 map.insert("coalesce", |diagnostics, function, context| {
937 let ([], args) = function.expect_some_arguments()?;
938 let expressions: Vec<_> = args
939 .iter()
940 .map(|arg| lower_expression(diagnostics, arg, context))
941 .try_collect()?;
942 Ok(RevsetExpression::coalesce(&expressions))
943 });
944 map
945});
946
947pub fn expect_fileset_expression(
949 diagnostics: &mut RevsetDiagnostics,
950 node: &ExpressionNode,
951 path_converter: &RepoPathUiConverter,
952) -> Result<FilesetExpression, RevsetParseError> {
953 revset_parser::expect_expression_with(diagnostics, node, |diagnostics, node| {
957 let mut inner_diagnostics = FilesetDiagnostics::new();
958 let expression = fileset::parse(&mut inner_diagnostics, node.span.as_str(), path_converter)
959 .map_err(|err| {
960 RevsetParseError::expression("In fileset expression", node.span).with_source(err)
961 })?;
962 diagnostics.extend_with(inner_diagnostics, |diag| {
963 RevsetParseError::expression("In fileset expression", node.span).with_source(diag)
964 });
965 Ok(expression)
966 })
967}
968
969pub fn expect_string_pattern(
970 diagnostics: &mut RevsetDiagnostics,
971 node: &ExpressionNode,
972) -> Result<StringPattern, RevsetParseError> {
973 revset_parser::expect_pattern_with(
974 diagnostics,
975 "string pattern",
976 node,
977 |_diagnostics, value, kind| match kind {
978 Some(kind) => StringPattern::from_str_kind(value, kind),
979 None => Ok(StringPattern::Substring(value.to_owned())),
980 },
981 )
982}
983
984pub fn expect_date_pattern(
985 diagnostics: &mut RevsetDiagnostics,
986 node: &ExpressionNode,
987 context: &DatePatternContext,
988) -> Result<DatePattern, RevsetParseError> {
989 revset_parser::expect_pattern_with(
990 diagnostics,
991 "date pattern",
992 node,
993 |_diagnostics, value, kind| -> Result<_, Box<dyn std::error::Error + Send + Sync>> {
994 match kind {
995 None => Err("Date pattern must specify 'after' or 'before'".into()),
996 Some(kind) => Ok(context.parse_relative(value, kind)?),
997 }
998 },
999 )
1000}
1001
1002fn parse_remote_bookmarks_arguments(
1003 diagnostics: &mut RevsetDiagnostics,
1004 function: &FunctionCallNode,
1005 remote_ref_state: Option<RemoteRefState>,
1006) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
1007 let ([], [bookmark_opt_arg, remote_opt_arg]) =
1008 function.expect_named_arguments(&["", "remote"])?;
1009 let bookmark_pattern = if let Some(bookmark_arg) = bookmark_opt_arg {
1010 expect_string_pattern(diagnostics, bookmark_arg)?
1011 } else {
1012 StringPattern::everything()
1013 };
1014 let remote_pattern = if let Some(remote_arg) = remote_opt_arg {
1015 expect_string_pattern(diagnostics, remote_arg)?
1016 } else {
1017 StringPattern::everything()
1018 };
1019 Ok(RevsetExpression::remote_bookmarks(
1020 bookmark_pattern,
1021 remote_pattern,
1022 remote_ref_state,
1023 ))
1024}
1025
1026fn lower_function_call(
1028 diagnostics: &mut RevsetDiagnostics,
1029 function: &FunctionCallNode,
1030 context: &LoweringContext,
1031) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
1032 let function_map = &context.extensions.function_map;
1033 if let Some(func) = function_map.get(function.name) {
1034 func(diagnostics, function, context)
1035 } else {
1036 Err(RevsetParseError::with_span(
1037 RevsetParseErrorKind::NoSuchFunction {
1038 name: function.name.to_owned(),
1039 candidates: collect_similar(function.name, function_map.keys()),
1040 },
1041 function.name_span,
1042 ))
1043 }
1044}
1045
1046pub fn lower_expression(
1049 diagnostics: &mut RevsetDiagnostics,
1050 node: &ExpressionNode,
1051 context: &LoweringContext,
1052) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
1053 match &node.kind {
1054 ExpressionKind::Identifier(name) => Ok(RevsetExpression::symbol((*name).to_owned())),
1055 ExpressionKind::String(name) => Ok(RevsetExpression::symbol(name.to_owned())),
1056 ExpressionKind::StringPattern { .. } => Err(RevsetParseError::with_span(
1057 RevsetParseErrorKind::NotInfixOperator {
1058 op: ":".to_owned(),
1059 similar_op: "::".to_owned(),
1060 description: "DAG range".to_owned(),
1061 },
1062 node.span,
1063 )),
1064 ExpressionKind::RemoteSymbol(symbol) => Ok(RevsetExpression::remote_symbol(symbol.clone())),
1065 ExpressionKind::AtWorkspace(name) => Ok(RevsetExpression::working_copy(name.into())),
1066 ExpressionKind::AtCurrentWorkspace => {
1067 let ctx = context.workspace.as_ref().ok_or_else(|| {
1068 RevsetParseError::with_span(
1069 RevsetParseErrorKind::WorkingCopyWithoutWorkspace,
1070 node.span,
1071 )
1072 })?;
1073 Ok(RevsetExpression::working_copy(
1074 ctx.workspace_name.to_owned(),
1075 ))
1076 }
1077 ExpressionKind::DagRangeAll => Ok(RevsetExpression::all()),
1078 ExpressionKind::RangeAll => {
1079 Ok(RevsetExpression::root().range(&RevsetExpression::visible_heads()))
1080 }
1081 ExpressionKind::Unary(op, arg_node) => {
1082 let arg = lower_expression(diagnostics, arg_node, context)?;
1083 match op {
1084 UnaryOp::Negate => Ok(arg.negated()),
1085 UnaryOp::DagRangePre => Ok(arg.ancestors()),
1086 UnaryOp::DagRangePost => Ok(arg.descendants()),
1087 UnaryOp::RangePre => Ok(RevsetExpression::root().range(&arg)),
1088 UnaryOp::RangePost => Ok(arg.range(&RevsetExpression::visible_heads())),
1089 UnaryOp::Parents => Ok(arg.parents()),
1090 UnaryOp::Children => Ok(arg.children()),
1091 }
1092 }
1093 ExpressionKind::Binary(op, lhs_node, rhs_node) => {
1094 let lhs = lower_expression(diagnostics, lhs_node, context)?;
1095 let rhs = lower_expression(diagnostics, rhs_node, context)?;
1096 match op {
1097 BinaryOp::Intersection => Ok(lhs.intersection(&rhs)),
1098 BinaryOp::Difference => Ok(lhs.minus(&rhs)),
1099 BinaryOp::DagRange => Ok(lhs.dag_range_to(&rhs)),
1100 BinaryOp::Range => Ok(lhs.range(&rhs)),
1101 }
1102 }
1103 ExpressionKind::UnionAll(nodes) => {
1104 let expressions: Vec<_> = nodes
1105 .iter()
1106 .map(|node| lower_expression(diagnostics, node, context))
1107 .try_collect()?;
1108 Ok(RevsetExpression::union_all(&expressions))
1109 }
1110 ExpressionKind::FunctionCall(function) => {
1111 lower_function_call(diagnostics, function, context)
1112 }
1113 ExpressionKind::Modifier(modifier) => {
1114 let name = modifier.name;
1115 Err(RevsetParseError::expression(
1116 format!("Modifier `{name}:` is not allowed in sub expression"),
1117 modifier.name_span,
1118 ))
1119 }
1120 ExpressionKind::AliasExpanded(id, subst) => {
1121 let mut inner_diagnostics = RevsetDiagnostics::new();
1122 let expression = lower_expression(&mut inner_diagnostics, subst, context)
1123 .map_err(|e| e.within_alias_expansion(*id, node.span))?;
1124 diagnostics.extend_with(inner_diagnostics, |diag| {
1125 diag.within_alias_expansion(*id, node.span)
1126 });
1127 Ok(expression)
1128 }
1129 }
1130}
1131
1132pub fn parse(
1133 diagnostics: &mut RevsetDiagnostics,
1134 revset_str: &str,
1135 context: &RevsetParseContext,
1136) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
1137 let node = parse_program(revset_str)?;
1138 let node =
1139 dsl_util::expand_aliases_with_locals(node, context.aliases_map, &context.local_variables)?;
1140 lower_expression(diagnostics, &node, &context.to_lowering_context())
1141 .map_err(|err| err.extend_function_candidates(context.aliases_map.function_names()))
1142}
1143
1144pub fn parse_with_modifier(
1145 diagnostics: &mut RevsetDiagnostics,
1146 revset_str: &str,
1147 context: &RevsetParseContext,
1148) -> Result<(Rc<UserRevsetExpression>, Option<RevsetModifier>), RevsetParseError> {
1149 let node = parse_program(revset_str)?;
1150 let node =
1151 dsl_util::expand_aliases_with_locals(node, context.aliases_map, &context.local_variables)?;
1152 revset_parser::expect_program_with(
1153 diagnostics,
1154 &node,
1155 |diagnostics, node| lower_expression(diagnostics, node, &context.to_lowering_context()),
1156 |_diagnostics, name, span| match name {
1157 "all" => Ok(RevsetModifier::All),
1158 _ => Err(RevsetParseError::with_span(
1159 RevsetParseErrorKind::NoSuchModifier(name.to_owned()),
1160 span,
1161 )),
1162 },
1163 )
1164 .map_err(|err| err.extend_function_candidates(context.aliases_map.function_names()))
1165}
1166
1167type TransformedExpression<St> = Option<Rc<RevsetExpression<St>>>;
1169
1170fn transform_expression_bottom_up<St: ExpressionState>(
1172 expression: &Rc<RevsetExpression<St>>,
1173 mut f: impl FnMut(&Rc<RevsetExpression<St>>) -> TransformedExpression<St>,
1174) -> TransformedExpression<St> {
1175 try_transform_expression::<St, Infallible>(
1176 expression,
1177 |_| Ok(None),
1178 |expression| Ok(f(expression)),
1179 )
1180 .unwrap()
1181}
1182
1183fn try_transform_expression<St: ExpressionState, E>(
1196 expression: &Rc<RevsetExpression<St>>,
1197 mut pre: impl FnMut(&Rc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1198 mut post: impl FnMut(&Rc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1199) -> Result<TransformedExpression<St>, E> {
1200 fn transform_child_rec<St: ExpressionState, E>(
1201 expression: &Rc<RevsetExpression<St>>,
1202 pre: &mut impl FnMut(&Rc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1203 post: &mut impl FnMut(&Rc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1204 ) -> Result<TransformedExpression<St>, E> {
1205 Ok(match expression.as_ref() {
1206 RevsetExpression::None => None,
1207 RevsetExpression::All => None,
1208 RevsetExpression::VisibleHeads => None,
1209 RevsetExpression::Root => None,
1210 RevsetExpression::Commits(_) => None,
1211 RevsetExpression::CommitRef(_) => None,
1212 RevsetExpression::Ancestors { heads, generation } => transform_rec(heads, pre, post)?
1213 .map(|heads| RevsetExpression::Ancestors {
1214 heads,
1215 generation: generation.clone(),
1216 }),
1217 RevsetExpression::Descendants { roots, generation } => transform_rec(roots, pre, post)?
1218 .map(|roots| RevsetExpression::Descendants {
1219 roots,
1220 generation: generation.clone(),
1221 }),
1222 RevsetExpression::Range {
1223 roots,
1224 heads,
1225 generation,
1226 } => transform_rec_pair((roots, heads), pre, post)?.map(|(roots, heads)| {
1227 RevsetExpression::Range {
1228 roots,
1229 heads,
1230 generation: generation.clone(),
1231 }
1232 }),
1233 RevsetExpression::DagRange { roots, heads } => {
1234 transform_rec_pair((roots, heads), pre, post)?
1235 .map(|(roots, heads)| RevsetExpression::DagRange { roots, heads })
1236 }
1237 RevsetExpression::Reachable { sources, domain } => {
1238 transform_rec_pair((sources, domain), pre, post)?
1239 .map(|(sources, domain)| RevsetExpression::Reachable { sources, domain })
1240 }
1241 RevsetExpression::Heads(candidates) => {
1242 transform_rec(candidates, pre, post)?.map(RevsetExpression::Heads)
1243 }
1244 RevsetExpression::Roots(candidates) => {
1245 transform_rec(candidates, pre, post)?.map(RevsetExpression::Roots)
1246 }
1247 RevsetExpression::ForkPoint(expression) => {
1248 transform_rec(expression, pre, post)?.map(RevsetExpression::ForkPoint)
1249 }
1250 RevsetExpression::Latest { candidates, count } => transform_rec(candidates, pre, post)?
1251 .map(|candidates| RevsetExpression::Latest {
1252 candidates,
1253 count: *count,
1254 }),
1255 RevsetExpression::Filter(_) => None,
1256 RevsetExpression::AsFilter(candidates) => {
1257 transform_rec(candidates, pre, post)?.map(RevsetExpression::AsFilter)
1258 }
1259 RevsetExpression::AtOperation {
1260 operation,
1261 candidates,
1262 } => transform_rec(candidates, pre, post)?.map(|candidates| {
1263 RevsetExpression::AtOperation {
1264 operation: operation.clone(),
1265 candidates,
1266 }
1267 }),
1268 RevsetExpression::WithinVisibility {
1269 candidates,
1270 visible_heads,
1271 } => transform_rec(candidates, pre, post)?.map(|candidates| {
1272 RevsetExpression::WithinVisibility {
1273 candidates,
1274 visible_heads: visible_heads.clone(),
1275 }
1276 }),
1277 RevsetExpression::Coalesce(expression1, expression2) => transform_rec_pair(
1278 (expression1, expression2),
1279 pre,
1280 post,
1281 )?
1282 .map(|(expression1, expression2)| RevsetExpression::Coalesce(expression1, expression2)),
1283 RevsetExpression::Present(candidates) => {
1284 transform_rec(candidates, pre, post)?.map(RevsetExpression::Present)
1285 }
1286 RevsetExpression::NotIn(complement) => {
1287 transform_rec(complement, pre, post)?.map(RevsetExpression::NotIn)
1288 }
1289 RevsetExpression::Union(expression1, expression2) => {
1290 transform_rec_pair((expression1, expression2), pre, post)?.map(
1291 |(expression1, expression2)| RevsetExpression::Union(expression1, expression2),
1292 )
1293 }
1294 RevsetExpression::Intersection(expression1, expression2) => {
1295 transform_rec_pair((expression1, expression2), pre, post)?.map(
1296 |(expression1, expression2)| {
1297 RevsetExpression::Intersection(expression1, expression2)
1298 },
1299 )
1300 }
1301 RevsetExpression::Difference(expression1, expression2) => {
1302 transform_rec_pair((expression1, expression2), pre, post)?.map(
1303 |(expression1, expression2)| {
1304 RevsetExpression::Difference(expression1, expression2)
1305 },
1306 )
1307 }
1308 }
1309 .map(Rc::new))
1310 }
1311
1312 #[expect(clippy::type_complexity)]
1313 fn transform_rec_pair<St: ExpressionState, E>(
1314 (expression1, expression2): (&Rc<RevsetExpression<St>>, &Rc<RevsetExpression<St>>),
1315 pre: &mut impl FnMut(&Rc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1316 post: &mut impl FnMut(&Rc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1317 ) -> Result<Option<(Rc<RevsetExpression<St>>, Rc<RevsetExpression<St>>)>, E> {
1318 match (
1319 transform_rec(expression1, pre, post)?,
1320 transform_rec(expression2, pre, post)?,
1321 ) {
1322 (Some(new_expression1), Some(new_expression2)) => {
1323 Ok(Some((new_expression1, new_expression2)))
1324 }
1325 (Some(new_expression1), None) => Ok(Some((new_expression1, expression2.clone()))),
1326 (None, Some(new_expression2)) => Ok(Some((expression1.clone(), new_expression2))),
1327 (None, None) => Ok(None),
1328 }
1329 }
1330
1331 fn transform_rec<St: ExpressionState, E>(
1332 expression: &Rc<RevsetExpression<St>>,
1333 pre: &mut impl FnMut(&Rc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1334 post: &mut impl FnMut(&Rc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1335 ) -> Result<TransformedExpression<St>, E> {
1336 if let Some(new_expression) = pre(expression)? {
1337 return Ok(Some(new_expression));
1338 }
1339 if let Some(new_expression) = transform_child_rec(expression, pre, post)? {
1340 Ok(Some(post(&new_expression)?.unwrap_or(new_expression)))
1342 } else {
1343 post(expression)
1344 }
1345 }
1346
1347 transform_rec(expression, &mut pre, &mut post)
1348}
1349
1350trait ExpressionStateFolder<InSt: ExpressionState, OutSt: ExpressionState> {
1355 type Error;
1356
1357 fn fold_expression(
1360 &mut self,
1361 expression: &RevsetExpression<InSt>,
1362 ) -> Result<Rc<RevsetExpression<OutSt>>, Self::Error> {
1363 fold_child_expression_state(self, expression)
1364 }
1365
1366 fn fold_commit_ref(
1368 &mut self,
1369 commit_ref: &InSt::CommitRef,
1370 ) -> Result<Rc<RevsetExpression<OutSt>>, Self::Error>;
1371
1372 fn fold_at_operation(
1374 &mut self,
1375 operation: &InSt::Operation,
1376 candidates: &RevsetExpression<InSt>,
1377 ) -> Result<Rc<RevsetExpression<OutSt>>, Self::Error>;
1378}
1379
1380fn fold_child_expression_state<InSt, OutSt, F>(
1382 folder: &mut F,
1383 expression: &RevsetExpression<InSt>,
1384) -> Result<Rc<RevsetExpression<OutSt>>, F::Error>
1385where
1386 InSt: ExpressionState,
1387 OutSt: ExpressionState,
1388 F: ExpressionStateFolder<InSt, OutSt> + ?Sized,
1389{
1390 let expression: Rc<_> = match expression {
1391 RevsetExpression::None => RevsetExpression::None.into(),
1392 RevsetExpression::All => RevsetExpression::All.into(),
1393 RevsetExpression::VisibleHeads => RevsetExpression::VisibleHeads.into(),
1394 RevsetExpression::Root => RevsetExpression::Root.into(),
1395 RevsetExpression::Commits(ids) => RevsetExpression::Commits(ids.clone()).into(),
1396 RevsetExpression::CommitRef(commit_ref) => folder.fold_commit_ref(commit_ref)?,
1397 RevsetExpression::Ancestors { heads, generation } => {
1398 let heads = folder.fold_expression(heads)?;
1399 let generation = generation.clone();
1400 RevsetExpression::Ancestors { heads, generation }.into()
1401 }
1402 RevsetExpression::Descendants { roots, generation } => {
1403 let roots = folder.fold_expression(roots)?;
1404 let generation = generation.clone();
1405 RevsetExpression::Descendants { roots, generation }.into()
1406 }
1407 RevsetExpression::Range {
1408 roots,
1409 heads,
1410 generation,
1411 } => {
1412 let roots = folder.fold_expression(roots)?;
1413 let heads = folder.fold_expression(heads)?;
1414 let generation = generation.clone();
1415 RevsetExpression::Range {
1416 roots,
1417 heads,
1418 generation,
1419 }
1420 .into()
1421 }
1422 RevsetExpression::DagRange { roots, heads } => {
1423 let roots = folder.fold_expression(roots)?;
1424 let heads = folder.fold_expression(heads)?;
1425 RevsetExpression::DagRange { roots, heads }.into()
1426 }
1427 RevsetExpression::Reachable { sources, domain } => {
1428 let sources = folder.fold_expression(sources)?;
1429 let domain = folder.fold_expression(domain)?;
1430 RevsetExpression::Reachable { sources, domain }.into()
1431 }
1432 RevsetExpression::Heads(heads) => {
1433 let heads = folder.fold_expression(heads)?;
1434 RevsetExpression::Heads(heads).into()
1435 }
1436 RevsetExpression::Roots(roots) => {
1437 let roots = folder.fold_expression(roots)?;
1438 RevsetExpression::Roots(roots).into()
1439 }
1440 RevsetExpression::ForkPoint(expression) => {
1441 let expression = folder.fold_expression(expression)?;
1442 RevsetExpression::ForkPoint(expression).into()
1443 }
1444 RevsetExpression::Latest { candidates, count } => {
1445 let candidates = folder.fold_expression(candidates)?;
1446 let count = *count;
1447 RevsetExpression::Latest { candidates, count }.into()
1448 }
1449 RevsetExpression::Filter(predicate) => RevsetExpression::Filter(predicate.clone()).into(),
1450 RevsetExpression::AsFilter(candidates) => {
1451 let candidates = folder.fold_expression(candidates)?;
1452 RevsetExpression::AsFilter(candidates).into()
1453 }
1454 RevsetExpression::AtOperation {
1455 operation,
1456 candidates,
1457 } => folder.fold_at_operation(operation, candidates)?,
1458 RevsetExpression::WithinVisibility {
1459 candidates,
1460 visible_heads,
1461 } => {
1462 let candidates = folder.fold_expression(candidates)?;
1463 let visible_heads = visible_heads.clone();
1464 RevsetExpression::WithinVisibility {
1465 candidates,
1466 visible_heads,
1467 }
1468 .into()
1469 }
1470 RevsetExpression::Coalesce(expression1, expression2) => {
1471 let expression1 = folder.fold_expression(expression1)?;
1472 let expression2 = folder.fold_expression(expression2)?;
1473 RevsetExpression::Coalesce(expression1, expression2).into()
1474 }
1475 RevsetExpression::Present(candidates) => {
1476 let candidates = folder.fold_expression(candidates)?;
1477 RevsetExpression::Present(candidates).into()
1478 }
1479 RevsetExpression::NotIn(complement) => {
1480 let complement = folder.fold_expression(complement)?;
1481 RevsetExpression::NotIn(complement).into()
1482 }
1483 RevsetExpression::Union(expression1, expression2) => {
1484 let expression1 = folder.fold_expression(expression1)?;
1485 let expression2 = folder.fold_expression(expression2)?;
1486 RevsetExpression::Union(expression1, expression2).into()
1487 }
1488 RevsetExpression::Intersection(expression1, expression2) => {
1489 let expression1 = folder.fold_expression(expression1)?;
1490 let expression2 = folder.fold_expression(expression2)?;
1491 RevsetExpression::Intersection(expression1, expression2).into()
1492 }
1493 RevsetExpression::Difference(expression1, expression2) => {
1494 let expression1 = folder.fold_expression(expression1)?;
1495 let expression2 = folder.fold_expression(expression2)?;
1496 RevsetExpression::Difference(expression1, expression2).into()
1497 }
1498 };
1499 Ok(expression)
1500}
1501
1502fn internalize_filter<St: ExpressionState>(
1511 expression: &Rc<RevsetExpression<St>>,
1512) -> TransformedExpression<St> {
1513 fn is_filter<St: ExpressionState>(expression: &RevsetExpression<St>) -> bool {
1514 matches!(
1515 expression,
1516 RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_)
1517 )
1518 }
1519
1520 fn is_filter_tree<St: ExpressionState>(expression: &RevsetExpression<St>) -> bool {
1521 is_filter(expression) || as_filter_intersection(expression).is_some()
1522 }
1523
1524 #[expect(clippy::type_complexity)]
1526 fn as_filter_intersection<St: ExpressionState>(
1527 expression: &RevsetExpression<St>,
1528 ) -> Option<(&Rc<RevsetExpression<St>>, &Rc<RevsetExpression<St>>)> {
1529 if let RevsetExpression::Intersection(expression1, expression2) = expression {
1530 is_filter(expression2).then_some((expression1, expression2))
1531 } else {
1532 None
1533 }
1534 }
1535
1536 fn intersect_down<St: ExpressionState>(
1541 expression1: &Rc<RevsetExpression<St>>,
1542 expression2: &Rc<RevsetExpression<St>>,
1543 ) -> TransformedExpression<St> {
1544 let recurse = |e1, e2| intersect_down(e1, e2).unwrap_or_else(|| e1.intersection(e2));
1545 match (expression1.as_ref(), expression2.as_ref()) {
1546 (_, e2) if is_filter(e2) => None,
1548 (e1, _) if is_filter(e1) => Some(expression2.intersection(expression1)),
1550 (e1, e2) => match (as_filter_intersection(e1), as_filter_intersection(e2)) {
1551 (_, Some((c2, f2))) => Some(recurse(expression1, c2).intersection(f2)),
1554 (Some((c1, f1)), _) => Some(recurse(c1, expression2).intersection(f1)),
1557 (None, None) => None,
1558 },
1559 }
1560 }
1561
1562 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
1568 RevsetExpression::Present(e) => {
1569 is_filter_tree(e).then(|| Rc::new(RevsetExpression::AsFilter(expression.clone())))
1570 }
1571 RevsetExpression::NotIn(e) => {
1572 is_filter_tree(e).then(|| Rc::new(RevsetExpression::AsFilter(expression.clone())))
1573 }
1574 RevsetExpression::Union(e1, e2) => (is_filter_tree(e1) || is_filter_tree(e2))
1575 .then(|| Rc::new(RevsetExpression::AsFilter(expression.clone()))),
1576 RevsetExpression::Intersection(expression1, expression2) => {
1577 intersect_down(expression1, expression2)
1578 }
1579 _ => None,
1581 })
1582}
1583
1584fn fold_redundant_expression<St: ExpressionState>(
1589 expression: &Rc<RevsetExpression<St>>,
1590) -> TransformedExpression<St> {
1591 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
1592 RevsetExpression::NotIn(outer) => match outer.as_ref() {
1593 RevsetExpression::NotIn(inner) => Some(inner.clone()),
1594 _ => None,
1595 },
1596 RevsetExpression::Intersection(expression1, expression2) => {
1597 match (expression1.as_ref(), expression2.as_ref()) {
1598 (_, RevsetExpression::All) => Some(expression1.clone()),
1599 (RevsetExpression::All, _) => Some(expression2.clone()),
1600 _ => None,
1601 }
1602 }
1603 _ => None,
1604 })
1605}
1606
1607fn to_difference_range<St: ExpressionState>(
1608 expression: &Rc<RevsetExpression<St>>,
1609 complement: &Rc<RevsetExpression<St>>,
1610) -> TransformedExpression<St> {
1611 match (expression.as_ref(), complement.as_ref()) {
1612 (
1614 RevsetExpression::Ancestors { heads, generation },
1615 RevsetExpression::Ancestors {
1616 heads: roots,
1617 generation: GENERATION_RANGE_FULL,
1618 },
1619 ) => Some(Rc::new(RevsetExpression::Range {
1620 roots: roots.clone(),
1621 heads: heads.clone(),
1622 generation: generation.clone(),
1623 })),
1624 (
1626 RevsetExpression::Ancestors { heads, generation },
1627 RevsetExpression::Ancestors {
1628 heads: roots,
1629 generation:
1630 Range {
1631 start: roots_start,
1632 end: u64::MAX,
1633 },
1634 },
1635 ) => Some(Rc::new(RevsetExpression::Range {
1636 roots: roots.ancestors_at(*roots_start),
1637 heads: heads.clone(),
1638 generation: generation.clone(),
1639 })),
1640 _ => None,
1641 }
1642}
1643
1644fn fold_difference<St: ExpressionState>(
1647 expression: &Rc<RevsetExpression<St>>,
1648) -> TransformedExpression<St> {
1649 fn to_difference<St: ExpressionState>(
1650 expression: &Rc<RevsetExpression<St>>,
1651 complement: &Rc<RevsetExpression<St>>,
1652 ) -> Rc<RevsetExpression<St>> {
1653 to_difference_range(expression, complement).unwrap_or_else(|| expression.minus(complement))
1654 }
1655
1656 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
1657 RevsetExpression::Intersection(expression1, expression2) => {
1658 match (expression1.as_ref(), expression2.as_ref()) {
1659 (_, RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_)) => None,
1661 (_, RevsetExpression::NotIn(complement)) => {
1662 Some(to_difference(expression1, complement))
1663 }
1664 (RevsetExpression::NotIn(complement), _) => {
1665 Some(to_difference(expression2, complement))
1666 }
1667 _ => None,
1668 }
1669 }
1670 _ => None,
1671 })
1672}
1673
1674fn fold_not_in_ancestors<St: ExpressionState>(
1679 expression: &Rc<RevsetExpression<St>>,
1680) -> TransformedExpression<St> {
1681 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
1682 RevsetExpression::NotIn(complement)
1683 if matches!(complement.as_ref(), RevsetExpression::Ancestors { .. }) =>
1684 {
1685 to_difference_range(&RevsetExpression::visible_heads().ancestors(), complement)
1688 }
1689 _ => None,
1690 })
1691}
1692
1693fn unfold_difference<St: ExpressionState>(
1698 expression: &Rc<RevsetExpression<St>>,
1699) -> TransformedExpression<St> {
1700 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
1701 RevsetExpression::Range {
1703 roots,
1704 heads,
1705 generation,
1706 } => {
1707 let heads_ancestors = Rc::new(RevsetExpression::Ancestors {
1708 heads: heads.clone(),
1709 generation: generation.clone(),
1710 });
1711 Some(heads_ancestors.intersection(&roots.ancestors().negated()))
1712 }
1713 RevsetExpression::Difference(expression1, expression2) => {
1714 Some(expression1.intersection(&expression2.negated()))
1715 }
1716 _ => None,
1717 })
1718}
1719
1720fn fold_generation<St: ExpressionState>(
1723 expression: &Rc<RevsetExpression<St>>,
1724) -> TransformedExpression<St> {
1725 fn add_generation(generation1: &Range<u64>, generation2: &Range<u64>) -> Range<u64> {
1726 if generation1.is_empty() || generation2.is_empty() {
1728 GENERATION_RANGE_EMPTY
1729 } else {
1730 let start = u64::saturating_add(generation1.start, generation2.start);
1731 let end = u64::saturating_add(generation1.end, generation2.end - 1);
1732 start..end
1733 }
1734 }
1735
1736 transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
1737 RevsetExpression::Ancestors {
1738 heads,
1739 generation: generation1,
1740 } => {
1741 match heads.as_ref() {
1742 RevsetExpression::Ancestors {
1746 heads,
1747 generation: generation2,
1748 } => Some(Rc::new(RevsetExpression::Ancestors {
1749 heads: heads.clone(),
1750 generation: add_generation(generation1, generation2),
1751 })),
1752 _ => None,
1753 }
1754 }
1755 RevsetExpression::Descendants {
1756 roots,
1757 generation: generation1,
1758 } => {
1759 match roots.as_ref() {
1760 RevsetExpression::Descendants {
1764 roots,
1765 generation: generation2,
1766 } => Some(Rc::new(RevsetExpression::Descendants {
1767 roots: roots.clone(),
1768 generation: add_generation(generation1, generation2),
1769 })),
1770 _ => None,
1771 }
1772 }
1773 _ => None,
1775 })
1776}
1777
1778pub fn optimize<St: ExpressionState>(
1781 expression: Rc<RevsetExpression<St>>,
1782) -> Rc<RevsetExpression<St>> {
1783 let expression = unfold_difference(&expression).unwrap_or(expression);
1784 let expression = fold_redundant_expression(&expression).unwrap_or(expression);
1785 let expression = fold_generation(&expression).unwrap_or(expression);
1786 let expression = internalize_filter(&expression).unwrap_or(expression);
1787 let expression = fold_difference(&expression).unwrap_or(expression);
1788 fold_not_in_ancestors(&expression).unwrap_or(expression)
1789}
1790
1791pub fn walk_revs<'index>(
1795 repo: &'index dyn Repo,
1796 wanted: &[CommitId],
1797 unwanted: &[CommitId],
1798) -> Result<Box<dyn Revset + 'index>, RevsetEvaluationError> {
1799 RevsetExpression::commits(unwanted.to_vec())
1800 .range(&RevsetExpression::commits(wanted.to_vec()))
1801 .evaluate(repo)
1802}
1803
1804fn reload_repo_at_operation(
1805 repo: &dyn Repo,
1806 op_str: &str,
1807) -> Result<Arc<ReadonlyRepo>, RevsetResolutionError> {
1808 let base_repo = repo.base_repo();
1812 let operation = op_walk::resolve_op_with_repo(base_repo, op_str)
1813 .map_err(|err| RevsetResolutionError::Other(err.into()))?;
1814 base_repo.reload_at(&operation).map_err(|err| match err {
1815 RepoLoaderError::Backend(err) => RevsetResolutionError::Backend(err),
1816 RepoLoaderError::IndexRead(_)
1817 | RepoLoaderError::OpHeadResolution(_)
1818 | RepoLoaderError::OpHeadsStoreError(_)
1819 | RepoLoaderError::OpStore(_)
1820 | RepoLoaderError::TransactionCommit(_) => RevsetResolutionError::Other(err.into()),
1821 })
1822}
1823
1824fn resolve_remote_bookmark(repo: &dyn Repo, symbol: RemoteRefSymbol<'_>) -> Option<Vec<CommitId>> {
1825 let target = &repo.view().get_remote_bookmark(symbol).target;
1826 target
1827 .is_present()
1828 .then(|| target.added_ids().cloned().collect())
1829}
1830
1831fn all_formatted_bookmark_symbols(
1832 repo: &dyn Repo,
1833 include_synced_remotes: bool,
1834) -> impl Iterator<Item = String> + use<'_> {
1835 let view = repo.view();
1836 view.bookmarks().flat_map(move |(name, bookmark_target)| {
1837 let local_target = bookmark_target.local_target;
1838 let local_symbol = local_target
1839 .is_present()
1840 .then(|| format_symbol(name.as_str()));
1841 let remote_symbols = bookmark_target
1842 .remote_refs
1843 .into_iter()
1844 .filter(move |&(_, remote_ref)| {
1845 include_synced_remotes
1846 || !remote_ref.is_tracked()
1847 || remote_ref.target != *local_target
1848 })
1849 .map(move |(remote, _)| format_remote_symbol(name.as_str(), remote.as_str()));
1850 local_symbol.into_iter().chain(remote_symbols)
1851 })
1852}
1853
1854fn make_no_such_symbol_error(repo: &dyn Repo, name: String) -> RevsetResolutionError {
1855 let bookmark_names = all_formatted_bookmark_symbols(repo, name.contains('@'));
1857 let candidates = collect_similar(&name, bookmark_names);
1858 RevsetResolutionError::NoSuchRevision { name, candidates }
1859}
1860
1861pub trait SymbolResolver {
1862 fn resolve_symbol(
1864 &self,
1865 repo: &dyn Repo,
1866 symbol: &str,
1867 ) -> Result<Vec<CommitId>, RevsetResolutionError>;
1868}
1869
1870pub struct FailingSymbolResolver;
1872
1873impl SymbolResolver for FailingSymbolResolver {
1874 fn resolve_symbol(
1875 &self,
1876 _repo: &dyn Repo,
1877 symbol: &str,
1878 ) -> Result<Vec<CommitId>, RevsetResolutionError> {
1879 Err(RevsetResolutionError::NoSuchRevision {
1880 name: format!(
1881 "Won't resolve symbol {symbol:?}. When creating revsets programmatically, avoid \
1882 using RevsetExpression::symbol(); use RevsetExpression::commits() instead."
1883 ),
1884 candidates: Default::default(),
1885 })
1886 }
1887}
1888
1889pub trait PartialSymbolResolver {
1893 fn resolve_symbol(
1894 &self,
1895 repo: &dyn Repo,
1896 symbol: &str,
1897 ) -> Result<Option<Vec<CommitId>>, RevsetResolutionError>;
1898}
1899
1900struct TagResolver;
1901
1902impl PartialSymbolResolver for TagResolver {
1903 fn resolve_symbol(
1904 &self,
1905 repo: &dyn Repo,
1906 symbol: &str,
1907 ) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
1908 let target = repo.view().get_tag(symbol.as_ref());
1909 Ok(target
1910 .is_present()
1911 .then(|| target.added_ids().cloned().collect()))
1912 }
1913}
1914
1915struct BookmarkResolver;
1916
1917impl PartialSymbolResolver for BookmarkResolver {
1918 fn resolve_symbol(
1919 &self,
1920 repo: &dyn Repo,
1921 symbol: &str,
1922 ) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
1923 let target = repo.view().get_local_bookmark(symbol.as_ref());
1924 Ok(target
1925 .is_present()
1926 .then(|| target.added_ids().cloned().collect()))
1927 }
1928}
1929
1930struct GitRefResolver;
1931
1932impl PartialSymbolResolver for GitRefResolver {
1933 fn resolve_symbol(
1934 &self,
1935 repo: &dyn Repo,
1936 symbol: &str,
1937 ) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
1938 let view = repo.view();
1939 for git_ref_prefix in &["", "refs/"] {
1940 let target = view.get_git_ref([git_ref_prefix, symbol].concat().as_ref());
1941 if target.is_present() {
1942 return Ok(Some(target.added_ids().cloned().collect()));
1943 }
1944 }
1945
1946 Ok(None)
1947 }
1948}
1949
1950const DEFAULT_RESOLVERS: &[&'static dyn PartialSymbolResolver] =
1951 &[&TagResolver, &BookmarkResolver, &GitRefResolver];
1952
1953struct CommitPrefixResolver<'a> {
1954 context_repo: &'a dyn Repo,
1955 context: Option<&'a IdPrefixContext>,
1956}
1957
1958impl PartialSymbolResolver for CommitPrefixResolver<'_> {
1959 fn resolve_symbol(
1960 &self,
1961 repo: &dyn Repo,
1962 symbol: &str,
1963 ) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
1964 if let Some(prefix) = HexPrefix::new(symbol) {
1965 let index = self
1966 .context
1967 .map(|ctx| ctx.populate(self.context_repo))
1968 .transpose()
1969 .map_err(|err| RevsetResolutionError::Other(err.into()))?
1970 .unwrap_or(IdPrefixIndex::empty());
1971 match index.resolve_commit_prefix(repo, &prefix) {
1972 PrefixResolution::AmbiguousMatch => Err(
1973 RevsetResolutionError::AmbiguousCommitIdPrefix(symbol.to_owned()),
1974 ),
1975 PrefixResolution::SingleMatch(id) => Ok(Some(vec![id])),
1976 PrefixResolution::NoMatch => Ok(None),
1977 }
1978 } else {
1979 Ok(None)
1980 }
1981 }
1982}
1983
1984struct ChangePrefixResolver<'a> {
1985 context_repo: &'a dyn Repo,
1986 context: Option<&'a IdPrefixContext>,
1987}
1988
1989impl PartialSymbolResolver for ChangePrefixResolver<'_> {
1990 fn resolve_symbol(
1991 &self,
1992 repo: &dyn Repo,
1993 symbol: &str,
1994 ) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
1995 if let Some(prefix) = to_forward_hex(symbol).as_deref().and_then(HexPrefix::new) {
1996 let index = self
1997 .context
1998 .map(|ctx| ctx.populate(self.context_repo))
1999 .transpose()
2000 .map_err(|err| RevsetResolutionError::Other(err.into()))?
2001 .unwrap_or(IdPrefixIndex::empty());
2002 match index.resolve_change_prefix(repo, &prefix) {
2003 PrefixResolution::AmbiguousMatch => Err(
2004 RevsetResolutionError::AmbiguousChangeIdPrefix(symbol.to_owned()),
2005 ),
2006 PrefixResolution::SingleMatch(ids) => Ok(Some(ids)),
2007 PrefixResolution::NoMatch => Ok(None),
2008 }
2009 } else {
2010 Ok(None)
2011 }
2012 }
2013}
2014
2015pub trait SymbolResolverExtension {
2022 fn new_resolvers<'a>(
2027 &self,
2028 context_repo: &'a dyn Repo,
2029 ) -> Vec<Box<dyn PartialSymbolResolver + 'a>>;
2030}
2031
2032pub struct DefaultSymbolResolver<'a> {
2035 commit_id_resolver: CommitPrefixResolver<'a>,
2036 change_id_resolver: ChangePrefixResolver<'a>,
2037 extensions: Vec<Box<dyn PartialSymbolResolver + 'a>>,
2038}
2039
2040impl<'a> DefaultSymbolResolver<'a> {
2041 pub fn new(
2044 context_repo: &'a dyn Repo,
2045 extensions: &[impl AsRef<dyn SymbolResolverExtension>],
2046 ) -> Self {
2047 DefaultSymbolResolver {
2048 commit_id_resolver: CommitPrefixResolver {
2049 context_repo,
2050 context: None,
2051 },
2052 change_id_resolver: ChangePrefixResolver {
2053 context_repo,
2054 context: None,
2055 },
2056 extensions: extensions
2057 .iter()
2058 .flat_map(|ext| ext.as_ref().new_resolvers(context_repo))
2059 .collect(),
2060 }
2061 }
2062
2063 pub fn with_id_prefix_context(mut self, id_prefix_context: &'a IdPrefixContext) -> Self {
2064 self.commit_id_resolver.context = Some(id_prefix_context);
2065 self.change_id_resolver.context = Some(id_prefix_context);
2066 self
2067 }
2068
2069 fn partial_resolvers(&self) -> impl Iterator<Item = &(dyn PartialSymbolResolver + 'a)> {
2070 let prefix_resolvers: [&dyn PartialSymbolResolver; 2] =
2071 [&self.commit_id_resolver, &self.change_id_resolver];
2072 itertools::chain!(
2073 DEFAULT_RESOLVERS.iter().copied(),
2074 prefix_resolvers,
2075 self.extensions.iter().map(|e| e.as_ref())
2076 )
2077 }
2078}
2079
2080impl SymbolResolver for DefaultSymbolResolver<'_> {
2081 fn resolve_symbol(
2082 &self,
2083 repo: &dyn Repo,
2084 symbol: &str,
2085 ) -> Result<Vec<CommitId>, RevsetResolutionError> {
2086 if symbol.is_empty() {
2087 return Err(RevsetResolutionError::EmptyString);
2088 }
2089
2090 for partial_resolver in self.partial_resolvers() {
2091 if let Some(ids) = partial_resolver.resolve_symbol(repo, symbol)? {
2092 return Ok(ids);
2093 }
2094 }
2095
2096 Err(make_no_such_symbol_error(repo, format_symbol(symbol)))
2097 }
2098}
2099
2100fn resolve_commit_ref(
2101 repo: &dyn Repo,
2102 commit_ref: &RevsetCommitRef,
2103 symbol_resolver: &dyn SymbolResolver,
2104) -> Result<Vec<CommitId>, RevsetResolutionError> {
2105 match commit_ref {
2106 RevsetCommitRef::Symbol(symbol) => symbol_resolver.resolve_symbol(repo, symbol),
2107 RevsetCommitRef::RemoteSymbol(symbol) => resolve_remote_bookmark(repo, symbol.as_ref())
2108 .ok_or_else(|| make_no_such_symbol_error(repo, symbol.to_string())),
2109 RevsetCommitRef::WorkingCopy(name) => {
2110 if let Some(commit_id) = repo.view().get_wc_commit_id(name) {
2111 Ok(vec![commit_id.clone()])
2112 } else {
2113 Err(RevsetResolutionError::WorkspaceMissingWorkingCopy { name: name.clone() })
2114 }
2115 }
2116 RevsetCommitRef::WorkingCopies => {
2117 let wc_commits = repo.view().wc_commit_ids().values().cloned().collect_vec();
2118 Ok(wc_commits)
2119 }
2120 RevsetCommitRef::Bookmarks(pattern) => {
2121 let commit_ids = repo
2122 .view()
2123 .local_bookmarks_matching(pattern)
2124 .flat_map(|(_, target)| target.added_ids())
2125 .cloned()
2126 .collect();
2127 Ok(commit_ids)
2128 }
2129 RevsetCommitRef::RemoteBookmarks {
2130 bookmark_pattern,
2131 remote_pattern,
2132 remote_ref_state,
2133 } => {
2134 let commit_ids = repo
2136 .view()
2137 .remote_bookmarks_matching(bookmark_pattern, remote_pattern)
2138 .filter(|(_, remote_ref)| {
2139 remote_ref_state.is_none_or(|state| remote_ref.state == state)
2140 })
2141 .filter(|&(symbol, _)| !crate::git::is_special_git_remote(symbol.remote))
2142 .flat_map(|(_, remote_ref)| remote_ref.target.added_ids())
2143 .cloned()
2144 .collect();
2145 Ok(commit_ids)
2146 }
2147 RevsetCommitRef::Tags(pattern) => {
2148 let commit_ids = repo
2149 .view()
2150 .tags_matching(pattern)
2151 .flat_map(|(_, target)| target.added_ids())
2152 .cloned()
2153 .collect();
2154 Ok(commit_ids)
2155 }
2156 RevsetCommitRef::GitRefs => {
2157 let mut commit_ids = vec![];
2158 for ref_target in repo.view().git_refs().values() {
2159 commit_ids.extend(ref_target.added_ids().cloned());
2160 }
2161 Ok(commit_ids)
2162 }
2163 RevsetCommitRef::GitHead => Ok(repo.view().git_head().added_ids().cloned().collect()),
2164 }
2165}
2166
2167struct ExpressionSymbolResolver<'a> {
2169 base_repo: &'a dyn Repo,
2170 repo_stack: Vec<Arc<ReadonlyRepo>>,
2171 symbol_resolver: &'a dyn SymbolResolver,
2172}
2173
2174impl<'a> ExpressionSymbolResolver<'a> {
2175 fn new(base_repo: &'a dyn Repo, symbol_resolver: &'a dyn SymbolResolver) -> Self {
2176 ExpressionSymbolResolver {
2177 base_repo,
2178 repo_stack: vec![],
2179 symbol_resolver,
2180 }
2181 }
2182
2183 fn repo(&self) -> &dyn Repo {
2184 self.repo_stack
2185 .last()
2186 .map_or(self.base_repo, |repo| repo.as_ref())
2187 }
2188}
2189
2190impl ExpressionStateFolder<UserExpressionState, ResolvedExpressionState>
2191 for ExpressionSymbolResolver<'_>
2192{
2193 type Error = RevsetResolutionError;
2194
2195 fn fold_expression(
2196 &mut self,
2197 expression: &UserRevsetExpression,
2198 ) -> Result<Rc<ResolvedRevsetExpression>, Self::Error> {
2199 match expression {
2200 RevsetExpression::Present(candidates) => {
2202 self.fold_expression(candidates).or_else(|err| match err {
2203 RevsetResolutionError::NoSuchRevision { .. }
2204 | RevsetResolutionError::WorkspaceMissingWorkingCopy { .. } => {
2205 Ok(RevsetExpression::none())
2206 }
2207 RevsetResolutionError::EmptyString
2208 | RevsetResolutionError::AmbiguousCommitIdPrefix(_)
2209 | RevsetResolutionError::AmbiguousChangeIdPrefix(_)
2210 | RevsetResolutionError::Backend(_)
2211 | RevsetResolutionError::Other(_) => Err(err),
2212 })
2213 }
2214 _ => fold_child_expression_state(self, expression),
2215 }
2216 }
2217
2218 fn fold_commit_ref(
2219 &mut self,
2220 commit_ref: &RevsetCommitRef,
2221 ) -> Result<Rc<ResolvedRevsetExpression>, Self::Error> {
2222 let commit_ids = resolve_commit_ref(self.repo(), commit_ref, self.symbol_resolver)?;
2223 Ok(RevsetExpression::commits(commit_ids))
2224 }
2225
2226 fn fold_at_operation(
2227 &mut self,
2228 operation: &String,
2229 candidates: &UserRevsetExpression,
2230 ) -> Result<Rc<ResolvedRevsetExpression>, Self::Error> {
2231 let repo = reload_repo_at_operation(self.repo(), operation)?;
2232 self.repo_stack.push(repo);
2233 let candidates = self.fold_expression(candidates)?;
2234 let visible_heads = self.repo().view().heads().iter().cloned().collect();
2235 self.repo_stack.pop();
2236 Ok(Rc::new(RevsetExpression::WithinVisibility {
2237 candidates,
2238 visible_heads,
2239 }))
2240 }
2241}
2242
2243fn resolve_symbols(
2244 repo: &dyn Repo,
2245 expression: &UserRevsetExpression,
2246 symbol_resolver: &dyn SymbolResolver,
2247) -> Result<Rc<ResolvedRevsetExpression>, RevsetResolutionError> {
2248 let mut resolver = ExpressionSymbolResolver::new(repo, symbol_resolver);
2249 resolver.fold_expression(expression)
2250}
2251
2252fn resolve_visibility(
2262 repo: &dyn Repo,
2263 expression: &ResolvedRevsetExpression,
2264) -> ResolvedExpression {
2265 let context = VisibilityResolutionContext {
2266 visible_heads: &repo.view().heads().iter().cloned().collect_vec(),
2267 root: repo.store().root_commit_id(),
2268 };
2269 context.resolve(expression)
2270}
2271
2272#[derive(Clone, Debug)]
2273struct VisibilityResolutionContext<'a> {
2274 visible_heads: &'a [CommitId],
2275 root: &'a CommitId,
2276}
2277
2278impl VisibilityResolutionContext<'_> {
2279 fn resolve(&self, expression: &ResolvedRevsetExpression) -> ResolvedExpression {
2281 match expression {
2282 RevsetExpression::None => ResolvedExpression::Commits(vec![]),
2283 RevsetExpression::All => self.resolve_all(),
2284 RevsetExpression::VisibleHeads => self.resolve_visible_heads(),
2285 RevsetExpression::Root => self.resolve_root(),
2286 RevsetExpression::Commits(commit_ids) => {
2287 ResolvedExpression::Commits(commit_ids.clone())
2288 }
2289 RevsetExpression::CommitRef(commit_ref) => match *commit_ref {},
2290 RevsetExpression::Ancestors { heads, generation } => ResolvedExpression::Ancestors {
2291 heads: self.resolve(heads).into(),
2292 generation: generation.clone(),
2293 },
2294 RevsetExpression::Descendants { roots, generation } => ResolvedExpression::DagRange {
2295 roots: self.resolve(roots).into(),
2296 heads: self.resolve_visible_heads().into(),
2297 generation_from_roots: generation.clone(),
2298 },
2299 RevsetExpression::Range {
2300 roots,
2301 heads,
2302 generation,
2303 } => ResolvedExpression::Range {
2304 roots: self.resolve(roots).into(),
2305 heads: self.resolve(heads).into(),
2306 generation: generation.clone(),
2307 },
2308 RevsetExpression::DagRange { roots, heads } => ResolvedExpression::DagRange {
2309 roots: self.resolve(roots).into(),
2310 heads: self.resolve(heads).into(),
2311 generation_from_roots: GENERATION_RANGE_FULL,
2312 },
2313 RevsetExpression::Reachable { sources, domain } => ResolvedExpression::Reachable {
2314 sources: self.resolve(sources).into(),
2315 domain: self.resolve(domain).into(),
2316 },
2317 RevsetExpression::Heads(candidates) => {
2318 ResolvedExpression::Heads(self.resolve(candidates).into())
2319 }
2320 RevsetExpression::Roots(candidates) => {
2321 ResolvedExpression::Roots(self.resolve(candidates).into())
2322 }
2323 RevsetExpression::ForkPoint(expression) => {
2324 ResolvedExpression::ForkPoint(self.resolve(expression).into())
2325 }
2326 RevsetExpression::Latest { candidates, count } => ResolvedExpression::Latest {
2327 candidates: self.resolve(candidates).into(),
2328 count: *count,
2329 },
2330 RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_) => {
2331 ResolvedExpression::FilterWithin {
2334 candidates: self.resolve_all().into(),
2335 predicate: self.resolve_predicate(expression),
2336 }
2337 }
2338 RevsetExpression::AtOperation { operation, .. } => match *operation {},
2339 RevsetExpression::WithinVisibility {
2340 candidates,
2341 visible_heads,
2342 } => {
2343 let context = VisibilityResolutionContext {
2344 visible_heads,
2345 root: self.root,
2346 };
2347 context.resolve(candidates)
2348 }
2349 RevsetExpression::Coalesce(expression1, expression2) => ResolvedExpression::Coalesce(
2350 self.resolve(expression1).into(),
2351 self.resolve(expression2).into(),
2352 ),
2353 RevsetExpression::Present(candidates) => self.resolve(candidates),
2355 RevsetExpression::NotIn(complement) => ResolvedExpression::Difference(
2356 self.resolve_all().into(),
2357 self.resolve(complement).into(),
2358 ),
2359 RevsetExpression::Union(expression1, expression2) => ResolvedExpression::Union(
2360 self.resolve(expression1).into(),
2361 self.resolve(expression2).into(),
2362 ),
2363 RevsetExpression::Intersection(expression1, expression2) => {
2364 match expression2.as_ref() {
2365 RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_) => {
2366 ResolvedExpression::FilterWithin {
2367 candidates: self.resolve(expression1).into(),
2368 predicate: self.resolve_predicate(expression2),
2369 }
2370 }
2371 _ => ResolvedExpression::Intersection(
2372 self.resolve(expression1).into(),
2373 self.resolve(expression2).into(),
2374 ),
2375 }
2376 }
2377 RevsetExpression::Difference(expression1, expression2) => {
2378 ResolvedExpression::Difference(
2379 self.resolve(expression1).into(),
2380 self.resolve(expression2).into(),
2381 )
2382 }
2383 }
2384 }
2385
2386 fn resolve_all(&self) -> ResolvedExpression {
2387 ResolvedExpression::Ancestors {
2395 heads: self.resolve_visible_heads().into(),
2396 generation: GENERATION_RANGE_FULL,
2397 }
2398 }
2399
2400 fn resolve_visible_heads(&self) -> ResolvedExpression {
2401 ResolvedExpression::Commits(self.visible_heads.to_owned())
2402 }
2403
2404 fn resolve_root(&self) -> ResolvedExpression {
2405 ResolvedExpression::Commits(vec![self.root.to_owned()])
2406 }
2407
2408 fn resolve_predicate(
2413 &self,
2414 expression: &ResolvedRevsetExpression,
2415 ) -> ResolvedPredicateExpression {
2416 match expression {
2417 RevsetExpression::None
2418 | RevsetExpression::All
2419 | RevsetExpression::VisibleHeads
2420 | RevsetExpression::Root
2421 | RevsetExpression::Commits(_)
2422 | RevsetExpression::CommitRef(_)
2423 | RevsetExpression::Ancestors { .. }
2424 | RevsetExpression::Descendants { .. }
2425 | RevsetExpression::Range { .. }
2426 | RevsetExpression::DagRange { .. }
2427 | RevsetExpression::Reachable { .. }
2428 | RevsetExpression::Heads(_)
2429 | RevsetExpression::Roots(_)
2430 | RevsetExpression::ForkPoint(_)
2431 | RevsetExpression::Latest { .. } => {
2432 ResolvedPredicateExpression::Set(self.resolve(expression).into())
2433 }
2434 RevsetExpression::Filter(predicate) => {
2435 ResolvedPredicateExpression::Filter(predicate.clone())
2436 }
2437 RevsetExpression::AsFilter(candidates) => self.resolve_predicate(candidates),
2438 RevsetExpression::AtOperation { operation, .. } => match *operation {},
2439 RevsetExpression::WithinVisibility { .. } => {
2441 ResolvedPredicateExpression::Set(self.resolve(expression).into())
2442 }
2443 RevsetExpression::Coalesce(_, _) => {
2444 ResolvedPredicateExpression::Set(self.resolve(expression).into())
2445 }
2446 RevsetExpression::Present(candidates) => self.resolve_predicate(candidates),
2448 RevsetExpression::NotIn(complement) => {
2449 ResolvedPredicateExpression::NotIn(self.resolve_predicate(complement).into())
2450 }
2451 RevsetExpression::Union(expression1, expression2) => {
2452 let predicate1 = self.resolve_predicate(expression1);
2453 let predicate2 = self.resolve_predicate(expression2);
2454 ResolvedPredicateExpression::Union(predicate1.into(), predicate2.into())
2455 }
2456 RevsetExpression::Intersection(..) | RevsetExpression::Difference(..) => {
2459 ResolvedPredicateExpression::Set(self.resolve(expression).into())
2460 }
2461 }
2462 }
2463}
2464
2465pub trait Revset: fmt::Debug {
2466 fn iter<'a>(&self) -> Box<dyn Iterator<Item = Result<CommitId, RevsetEvaluationError>> + 'a>
2468 where
2469 Self: 'a;
2470
2471 fn commit_change_ids<'a>(
2473 &self,
2474 ) -> Box<dyn Iterator<Item = Result<(CommitId, ChangeId), RevsetEvaluationError>> + 'a>
2475 where
2476 Self: 'a;
2477
2478 fn iter_graph<'a>(
2479 &self,
2480 ) -> Box<dyn Iterator<Item = Result<GraphNode<CommitId>, RevsetEvaluationError>> + 'a>
2481 where
2482 Self: 'a;
2483
2484 fn is_empty(&self) -> bool;
2486
2487 fn count_estimate(&self) -> Result<(usize, Option<usize>), RevsetEvaluationError>;
2492
2493 fn containing_fn<'a>(&self) -> Box<RevsetContainingFn<'a>>
2499 where
2500 Self: 'a;
2501}
2502
2503pub type RevsetContainingFn<'a> = dyn Fn(&CommitId) -> Result<bool, RevsetEvaluationError> + 'a;
2505
2506pub trait RevsetIteratorExt<I> {
2507 fn commits(self, store: &Arc<Store>) -> RevsetCommitIterator<I>;
2508}
2509
2510impl<I: Iterator<Item = Result<CommitId, RevsetEvaluationError>>> RevsetIteratorExt<I> for I {
2511 fn commits(self, store: &Arc<Store>) -> RevsetCommitIterator<I> {
2512 RevsetCommitIterator {
2513 iter: self,
2514 store: store.clone(),
2515 }
2516 }
2517}
2518
2519pub struct RevsetCommitIterator<I> {
2520 store: Arc<Store>,
2521 iter: I,
2522}
2523
2524impl<I: Iterator<Item = Result<CommitId, RevsetEvaluationError>>> Iterator
2525 for RevsetCommitIterator<I>
2526{
2527 type Item = Result<Commit, RevsetEvaluationError>;
2528
2529 fn next(&mut self) -> Option<Self::Item> {
2530 self.iter.next().map(|commit_id| {
2531 let commit_id = commit_id?;
2532 self.store
2533 .get_commit(&commit_id)
2534 .map_err(RevsetEvaluationError::Backend)
2535 })
2536 }
2537}
2538
2539pub struct RevsetExtensions {
2541 symbol_resolvers: Vec<Box<dyn SymbolResolverExtension>>,
2542 function_map: HashMap<&'static str, RevsetFunction>,
2543}
2544
2545impl Default for RevsetExtensions {
2546 fn default() -> Self {
2547 Self::new()
2548 }
2549}
2550
2551impl RevsetExtensions {
2552 pub fn new() -> Self {
2553 Self {
2554 symbol_resolvers: vec![],
2555 function_map: BUILTIN_FUNCTION_MAP.clone(),
2556 }
2557 }
2558
2559 pub fn symbol_resolvers(&self) -> &[impl AsRef<dyn SymbolResolverExtension> + use<>] {
2560 &self.symbol_resolvers
2561 }
2562
2563 pub fn add_symbol_resolver(&mut self, symbol_resolver: Box<dyn SymbolResolverExtension>) {
2564 self.symbol_resolvers.push(symbol_resolver);
2565 }
2566
2567 pub fn add_custom_function(&mut self, name: &'static str, func: RevsetFunction) {
2568 match self.function_map.entry(name) {
2569 hash_map::Entry::Occupied(_) => {
2570 panic!("Conflict registering revset function '{name}'")
2571 }
2572 hash_map::Entry::Vacant(v) => v.insert(func),
2573 };
2574 }
2575}
2576
2577#[derive(Clone)]
2579pub struct RevsetParseContext<'a> {
2580 pub aliases_map: &'a RevsetAliasesMap,
2581 pub local_variables: HashMap<&'a str, ExpressionNode<'a>>,
2582 pub user_email: &'a str,
2583 pub date_pattern_context: DatePatternContext,
2584 pub extensions: &'a RevsetExtensions,
2585 pub workspace: Option<RevsetWorkspaceContext<'a>>,
2586}
2587
2588impl<'a> RevsetParseContext<'a> {
2589 fn to_lowering_context(&self) -> LoweringContext<'a> {
2590 let RevsetParseContext {
2591 aliases_map: _,
2592 local_variables: _,
2593 user_email,
2594 date_pattern_context,
2595 extensions,
2596 workspace,
2597 } = *self;
2598 LoweringContext {
2599 user_email,
2600 date_pattern_context,
2601 extensions,
2602 workspace,
2603 }
2604 }
2605}
2606
2607#[derive(Clone)]
2609pub struct LoweringContext<'a> {
2610 user_email: &'a str,
2611 date_pattern_context: DatePatternContext,
2612 extensions: &'a RevsetExtensions,
2613 workspace: Option<RevsetWorkspaceContext<'a>>,
2614}
2615
2616impl<'a> LoweringContext<'a> {
2617 pub fn user_email(&self) -> &'a str {
2618 self.user_email
2619 }
2620
2621 pub fn date_pattern_context(&self) -> &DatePatternContext {
2622 &self.date_pattern_context
2623 }
2624
2625 pub fn symbol_resolvers(&self) -> &'a [impl AsRef<dyn SymbolResolverExtension> + use<>] {
2626 self.extensions.symbol_resolvers()
2627 }
2628}
2629
2630#[derive(Clone, Copy, Debug)]
2632pub struct RevsetWorkspaceContext<'a> {
2633 pub path_converter: &'a RepoPathUiConverter,
2634 pub workspace_name: &'a WorkspaceName,
2635}
2636
2637pub fn format_symbol(literal: &str) -> String {
2643 if revset_parser::is_identifier(literal) {
2644 literal.to_string()
2645 } else {
2646 format_string(literal)
2647 }
2648}
2649
2650pub fn format_string(literal: &str) -> String {
2652 format!(r#""{}""#, dsl_util::escape_string(literal))
2653}
2654
2655pub fn format_remote_symbol(name: &str, remote: &str) -> String {
2657 let name = format_symbol(name);
2658 let remote = format_symbol(remote);
2659 format!("{name}@{remote}")
2660}
2661
2662#[cfg(test)]
2663mod tests {
2664 use std::path::PathBuf;
2665
2666 use assert_matches::assert_matches;
2667
2668 use super::*;
2669
2670 fn parse(revset_str: &str) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
2671 parse_with_aliases(revset_str, [] as [(&str, &str); 0])
2672 }
2673
2674 fn parse_with_workspace(
2675 revset_str: &str,
2676 workspace_name: &WorkspaceName,
2677 ) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
2678 parse_with_aliases_and_workspace(revset_str, [] as [(&str, &str); 0], workspace_name)
2679 }
2680
2681 fn parse_with_aliases(
2682 revset_str: &str,
2683 aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
2684 ) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
2685 let mut aliases_map = RevsetAliasesMap::new();
2686 for (decl, defn) in aliases {
2687 aliases_map.insert(decl, defn).unwrap();
2688 }
2689 let context = RevsetParseContext {
2690 aliases_map: &aliases_map,
2691 local_variables: HashMap::new(),
2692 user_email: "test.user@example.com",
2693 date_pattern_context: chrono::Utc::now().fixed_offset().into(),
2694 extensions: &RevsetExtensions::default(),
2695 workspace: None,
2696 };
2697 super::parse(&mut RevsetDiagnostics::new(), revset_str, &context)
2698 }
2699
2700 fn parse_with_aliases_and_workspace(
2701 revset_str: &str,
2702 aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
2703 workspace_name: &WorkspaceName,
2704 ) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
2705 let path_converter = RepoPathUiConverter::Fs {
2707 cwd: PathBuf::from("/"),
2708 base: PathBuf::from("/"),
2709 };
2710 let workspace_ctx = RevsetWorkspaceContext {
2711 path_converter: &path_converter,
2712 workspace_name,
2713 };
2714 let mut aliases_map = RevsetAliasesMap::new();
2715 for (decl, defn) in aliases {
2716 aliases_map.insert(decl, defn).unwrap();
2717 }
2718 let context = RevsetParseContext {
2719 aliases_map: &aliases_map,
2720 local_variables: HashMap::new(),
2721 user_email: "test.user@example.com",
2722 date_pattern_context: chrono::Utc::now().fixed_offset().into(),
2723 extensions: &RevsetExtensions::default(),
2724 workspace: Some(workspace_ctx),
2725 };
2726 super::parse(&mut RevsetDiagnostics::new(), revset_str, &context)
2727 }
2728
2729 fn parse_with_modifier(
2730 revset_str: &str,
2731 ) -> Result<(Rc<UserRevsetExpression>, Option<RevsetModifier>), RevsetParseError> {
2732 parse_with_aliases_and_modifier(revset_str, [] as [(&str, &str); 0])
2733 }
2734
2735 fn parse_with_aliases_and_modifier(
2736 revset_str: &str,
2737 aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
2738 ) -> Result<(Rc<UserRevsetExpression>, Option<RevsetModifier>), RevsetParseError> {
2739 let mut aliases_map = RevsetAliasesMap::new();
2740 for (decl, defn) in aliases {
2741 aliases_map.insert(decl, defn).unwrap();
2742 }
2743 let context = RevsetParseContext {
2744 aliases_map: &aliases_map,
2745 local_variables: HashMap::new(),
2746 user_email: "test.user@example.com",
2747 date_pattern_context: chrono::Utc::now().fixed_offset().into(),
2748 extensions: &RevsetExtensions::default(),
2749 workspace: None,
2750 };
2751 super::parse_with_modifier(&mut RevsetDiagnostics::new(), revset_str, &context)
2752 }
2753
2754 fn insta_settings() -> insta::Settings {
2755 let mut settings = insta::Settings::clone_current();
2756 for _ in 0..4 {
2759 settings.add_filter(
2760 r"(?x)
2761 \b([A-Z]\w*)\(\n
2762 \s*(.{1,60}),\n
2763 \s*\)",
2764 "$1($2)",
2765 );
2766 }
2767 settings
2768 }
2769
2770 #[test]
2771 #[expect(clippy::redundant_clone)] fn test_revset_expression_building() {
2773 let settings = insta_settings();
2774 let _guard = settings.bind_to_scope();
2775 let current_wc = UserRevsetExpression::working_copy(WorkspaceName::DEFAULT.to_owned());
2776 let foo_symbol = UserRevsetExpression::symbol("foo".to_string());
2777 let bar_symbol = UserRevsetExpression::symbol("bar".to_string());
2778 let baz_symbol = UserRevsetExpression::symbol("baz".to_string());
2779
2780 insta::assert_debug_snapshot!(
2781 current_wc,
2782 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
2783 insta::assert_debug_snapshot!(
2784 current_wc.heads(),
2785 @r#"Heads(CommitRef(WorkingCopy(WorkspaceNameBuf("default"))))"#);
2786 insta::assert_debug_snapshot!(
2787 current_wc.roots(),
2788 @r#"Roots(CommitRef(WorkingCopy(WorkspaceNameBuf("default"))))"#);
2789 insta::assert_debug_snapshot!(
2790 current_wc.parents(), @r#"
2791 Ancestors {
2792 heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2793 generation: 1..2,
2794 }
2795 "#);
2796 insta::assert_debug_snapshot!(
2797 current_wc.ancestors(), @r#"
2798 Ancestors {
2799 heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2800 generation: 0..18446744073709551615,
2801 }
2802 "#);
2803 insta::assert_debug_snapshot!(
2804 foo_symbol.children(), @r#"
2805 Descendants {
2806 roots: CommitRef(Symbol("foo")),
2807 generation: 1..2,
2808 }
2809 "#);
2810 insta::assert_debug_snapshot!(
2811 foo_symbol.descendants(), @r#"
2812 Descendants {
2813 roots: CommitRef(Symbol("foo")),
2814 generation: 0..18446744073709551615,
2815 }
2816 "#);
2817 insta::assert_debug_snapshot!(
2818 foo_symbol.dag_range_to(¤t_wc), @r#"
2819 DagRange {
2820 roots: CommitRef(Symbol("foo")),
2821 heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2822 }
2823 "#);
2824 insta::assert_debug_snapshot!(
2825 foo_symbol.connected(), @r#"
2826 DagRange {
2827 roots: CommitRef(Symbol("foo")),
2828 heads: CommitRef(Symbol("foo")),
2829 }
2830 "#);
2831 insta::assert_debug_snapshot!(
2832 foo_symbol.range(¤t_wc), @r#"
2833 Range {
2834 roots: CommitRef(Symbol("foo")),
2835 heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2836 generation: 0..18446744073709551615,
2837 }
2838 "#);
2839 insta::assert_debug_snapshot!(
2840 foo_symbol.negated(),
2841 @r#"NotIn(CommitRef(Symbol("foo")))"#);
2842 insta::assert_debug_snapshot!(
2843 foo_symbol.union(¤t_wc), @r#"
2844 Union(
2845 CommitRef(Symbol("foo")),
2846 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2847 )
2848 "#);
2849 insta::assert_debug_snapshot!(
2850 UserRevsetExpression::union_all(&[]),
2851 @"None");
2852 insta::assert_debug_snapshot!(
2853 RevsetExpression::union_all(&[current_wc.clone()]),
2854 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
2855 insta::assert_debug_snapshot!(
2856 RevsetExpression::union_all(&[current_wc.clone(), foo_symbol.clone()]),
2857 @r#"
2858 Union(
2859 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2860 CommitRef(Symbol("foo")),
2861 )
2862 "#);
2863 insta::assert_debug_snapshot!(
2864 RevsetExpression::union_all(&[
2865 current_wc.clone(),
2866 foo_symbol.clone(),
2867 bar_symbol.clone(),
2868 ]),
2869 @r#"
2870 Union(
2871 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2872 Union(
2873 CommitRef(Symbol("foo")),
2874 CommitRef(Symbol("bar")),
2875 ),
2876 )
2877 "#);
2878 insta::assert_debug_snapshot!(
2879 RevsetExpression::union_all(&[
2880 current_wc.clone(),
2881 foo_symbol.clone(),
2882 bar_symbol.clone(),
2883 baz_symbol.clone(),
2884 ]),
2885 @r#"
2886 Union(
2887 Union(
2888 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2889 CommitRef(Symbol("foo")),
2890 ),
2891 Union(
2892 CommitRef(Symbol("bar")),
2893 CommitRef(Symbol("baz")),
2894 ),
2895 )
2896 "#);
2897 insta::assert_debug_snapshot!(
2898 foo_symbol.intersection(¤t_wc), @r#"
2899 Intersection(
2900 CommitRef(Symbol("foo")),
2901 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2902 )
2903 "#);
2904 insta::assert_debug_snapshot!(
2905 foo_symbol.minus(¤t_wc), @r#"
2906 Difference(
2907 CommitRef(Symbol("foo")),
2908 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2909 )
2910 "#);
2911 insta::assert_debug_snapshot!(
2912 UserRevsetExpression::coalesce(&[]),
2913 @"None");
2914 insta::assert_debug_snapshot!(
2915 RevsetExpression::coalesce(&[current_wc.clone()]),
2916 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
2917 insta::assert_debug_snapshot!(
2918 RevsetExpression::coalesce(&[current_wc.clone(), foo_symbol.clone()]),
2919 @r#"
2920 Coalesce(
2921 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2922 CommitRef(Symbol("foo")),
2923 )
2924 "#);
2925 insta::assert_debug_snapshot!(
2926 RevsetExpression::coalesce(&[
2927 current_wc.clone(),
2928 foo_symbol.clone(),
2929 bar_symbol.clone(),
2930 ]),
2931 @r#"
2932 Coalesce(
2933 CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2934 Coalesce(
2935 CommitRef(Symbol("foo")),
2936 CommitRef(Symbol("bar")),
2937 ),
2938 )
2939 "#);
2940 }
2941
2942 #[test]
2943 fn test_parse_revset() {
2944 let settings = insta_settings();
2945 let _guard = settings.bind_to_scope();
2946 let main_workspace_name = WorkspaceNameBuf::from("main");
2947 let other_workspace_name = WorkspaceNameBuf::from("other");
2948
2949 insta::assert_debug_snapshot!(
2951 parse("@").unwrap_err().kind(),
2952 @"WorkingCopyWithoutWorkspace");
2953 insta::assert_debug_snapshot!(
2954 parse("main@").unwrap(),
2955 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
2956 insta::assert_debug_snapshot!(
2957 parse_with_workspace("@", &main_workspace_name).unwrap(),
2958 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
2959 insta::assert_debug_snapshot!(
2960 parse_with_workspace("main@", &other_workspace_name).unwrap(),
2961 @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
2962 insta::assert_debug_snapshot!(
2964 parse("author_name(foo@)").unwrap_err().kind(),
2965 @r#"Expression("Expected expression of string pattern")"#);
2966 insta::assert_debug_snapshot!(
2967 parse(r#"author_name("foo@")"#).unwrap(),
2968 @r#"Filter(AuthorName(Substring("foo@")))"#);
2969 insta::assert_debug_snapshot!(
2971 parse("foo").unwrap(),
2972 @r#"CommitRef(Symbol("foo"))"#);
2973 insta::assert_debug_snapshot!(
2975 parse("bookmarks()").unwrap(),
2976 @r#"CommitRef(Bookmarks(Substring("")))"#);
2977 insta::assert_debug_snapshot!(
2979 parse("tags()").unwrap(),
2980 @r#"CommitRef(Tags(Substring("")))"#);
2981 insta::assert_debug_snapshot!(parse("remote_bookmarks()").unwrap(), @r#"
2982 CommitRef(
2983 RemoteBookmarks {
2984 bookmark_pattern: Substring(""),
2985 remote_pattern: Substring(""),
2986 remote_ref_state: None,
2987 },
2988 )
2989 "#);
2990 insta::assert_debug_snapshot!(parse("tracked_remote_bookmarks()").unwrap(), @r#"
2991 CommitRef(
2992 RemoteBookmarks {
2993 bookmark_pattern: Substring(""),
2994 remote_pattern: Substring(""),
2995 remote_ref_state: Some(Tracked),
2996 },
2997 )
2998 "#);
2999 insta::assert_debug_snapshot!(parse("untracked_remote_bookmarks()").unwrap(), @r#"
3000 CommitRef(
3001 RemoteBookmarks {
3002 bookmark_pattern: Substring(""),
3003 remote_pattern: Substring(""),
3004 remote_ref_state: Some(New),
3005 },
3006 )
3007 "#);
3008 insta::assert_debug_snapshot!(
3010 parse("'foo'").unwrap(),
3011 @r#"CommitRef(Symbol("foo"))"#);
3012 insta::assert_debug_snapshot!(parse("foo-").unwrap(), @r#"
3014 Ancestors {
3015 heads: CommitRef(Symbol("foo")),
3016 generation: 1..2,
3017 }
3018 "#);
3019 insta::assert_debug_snapshot!(parse("foo+").unwrap(), @r#"
3021 Descendants {
3022 roots: CommitRef(Symbol("foo")),
3023 generation: 1..2,
3024 }
3025 "#);
3026 insta::assert_debug_snapshot!(parse("::foo").unwrap(), @r#"
3028 Ancestors {
3029 heads: CommitRef(Symbol("foo")),
3030 generation: 0..18446744073709551615,
3031 }
3032 "#);
3033 insta::assert_debug_snapshot!(parse("foo::").unwrap(), @r#"
3035 Descendants {
3036 roots: CommitRef(Symbol("foo")),
3037 generation: 0..18446744073709551615,
3038 }
3039 "#);
3040 insta::assert_debug_snapshot!(parse("foo::bar").unwrap(), @r#"
3042 DagRange {
3043 roots: CommitRef(Symbol("foo")),
3044 heads: CommitRef(Symbol("bar")),
3045 }
3046 "#);
3047 insta::assert_debug_snapshot!(parse("::").unwrap(), @"All");
3049 insta::assert_debug_snapshot!(parse("..foo").unwrap(), @r#"
3051 Range {
3052 roots: Root,
3053 heads: CommitRef(Symbol("foo")),
3054 generation: 0..18446744073709551615,
3055 }
3056 "#);
3057 insta::assert_debug_snapshot!(parse("foo..").unwrap(), @r#"
3058 Range {
3059 roots: CommitRef(Symbol("foo")),
3060 heads: VisibleHeads,
3061 generation: 0..18446744073709551615,
3062 }
3063 "#);
3064 insta::assert_debug_snapshot!(parse("foo..bar").unwrap(), @r#"
3065 Range {
3066 roots: CommitRef(Symbol("foo")),
3067 heads: CommitRef(Symbol("bar")),
3068 generation: 0..18446744073709551615,
3069 }
3070 "#);
3071 insta::assert_debug_snapshot!(parse("..").unwrap(), @r"
3073 Range {
3074 roots: Root,
3075 heads: VisibleHeads,
3076 generation: 0..18446744073709551615,
3077 }
3078 ");
3079 insta::assert_debug_snapshot!(
3081 parse("~ foo").unwrap(),
3082 @r#"NotIn(CommitRef(Symbol("foo")))"#);
3083 insta::assert_debug_snapshot!(parse("foo & bar").unwrap(), @r#"
3085 Intersection(
3086 CommitRef(Symbol("foo")),
3087 CommitRef(Symbol("bar")),
3088 )
3089 "#);
3090 insta::assert_debug_snapshot!(parse("foo | bar").unwrap(), @r#"
3092 Union(
3093 CommitRef(Symbol("foo")),
3094 CommitRef(Symbol("bar")),
3095 )
3096 "#);
3097 insta::assert_debug_snapshot!(parse("foo ~ bar").unwrap(), @r#"
3099 Difference(
3100 CommitRef(Symbol("foo")),
3101 CommitRef(Symbol("bar")),
3102 )
3103 "#);
3104 }
3105
3106 #[test]
3107 fn test_parse_revset_with_modifier() {
3108 let settings = insta_settings();
3109 let _guard = settings.bind_to_scope();
3110
3111 insta::assert_debug_snapshot!(
3112 parse_with_modifier("all:foo").unwrap(), @r#"
3113 (
3114 CommitRef(Symbol("foo")),
3115 Some(All),
3116 )
3117 "#);
3118
3119 insta::assert_debug_snapshot!(
3121 parse_with_modifier(r#"exact:"foo""#).unwrap_err().kind(),
3122 @r#"NoSuchModifier("exact")"#);
3123 }
3124
3125 #[test]
3126 fn test_parse_string_pattern() {
3127 let settings = insta_settings();
3128 let _guard = settings.bind_to_scope();
3129
3130 insta::assert_debug_snapshot!(
3131 parse(r#"bookmarks("foo")"#).unwrap(),
3132 @r#"CommitRef(Bookmarks(Substring("foo")))"#);
3133 insta::assert_debug_snapshot!(
3134 parse(r#"bookmarks(exact:"foo")"#).unwrap(),
3135 @r#"CommitRef(Bookmarks(Exact("foo")))"#);
3136 insta::assert_debug_snapshot!(
3137 parse(r#"bookmarks(substring:"foo")"#).unwrap(),
3138 @r#"CommitRef(Bookmarks(Substring("foo")))"#);
3139 insta::assert_debug_snapshot!(
3140 parse(r#"bookmarks(bad:"foo")"#).unwrap_err().kind(),
3141 @r#"Expression("Invalid string pattern")"#);
3142 insta::assert_debug_snapshot!(
3143 parse(r#"bookmarks(exact::"foo")"#).unwrap_err().kind(),
3144 @r#"Expression("Expected expression of string pattern")"#);
3145 insta::assert_debug_snapshot!(
3146 parse(r#"bookmarks(exact:"foo"+)"#).unwrap_err().kind(),
3147 @r#"Expression("Expected expression of string pattern")"#);
3148
3149 insta::assert_debug_snapshot!(
3150 parse(r#"tags("foo")"#).unwrap(),
3151 @r#"CommitRef(Tags(Substring("foo")))"#);
3152 insta::assert_debug_snapshot!(
3153 parse(r#"tags(exact:"foo")"#).unwrap(),
3154 @r#"CommitRef(Tags(Exact("foo")))"#);
3155 insta::assert_debug_snapshot!(
3156 parse(r#"tags(substring:"foo")"#).unwrap(),
3157 @r#"CommitRef(Tags(Substring("foo")))"#);
3158 insta::assert_debug_snapshot!(
3159 parse(r#"tags(bad:"foo")"#).unwrap_err().kind(),
3160 @r#"Expression("Invalid string pattern")"#);
3161 insta::assert_debug_snapshot!(
3162 parse(r#"tags(exact::"foo")"#).unwrap_err().kind(),
3163 @r#"Expression("Expected expression of string pattern")"#);
3164 insta::assert_debug_snapshot!(
3165 parse(r#"tags(exact:"foo"+)"#).unwrap_err().kind(),
3166 @r#"Expression("Expected expression of string pattern")"#);
3167
3168 assert_matches!(
3170 parse(r#"(exact:"foo")"#).unwrap_err().kind(),
3171 RevsetParseErrorKind::NotInfixOperator { .. }
3172 );
3173 }
3174
3175 #[test]
3176 fn test_parse_revset_function() {
3177 let settings = insta_settings();
3178 let _guard = settings.bind_to_scope();
3179
3180 insta::assert_debug_snapshot!(
3181 parse("parents(foo)").unwrap(), @r#"
3182 Ancestors {
3183 heads: CommitRef(Symbol("foo")),
3184 generation: 1..2,
3185 }
3186 "#);
3187 insta::assert_debug_snapshot!(
3188 parse("parents(\"foo\")").unwrap(), @r#"
3189 Ancestors {
3190 heads: CommitRef(Symbol("foo")),
3191 generation: 1..2,
3192 }
3193 "#);
3194 insta::assert_debug_snapshot!(
3195 parse("ancestors(parents(foo))").unwrap(), @r#"
3196 Ancestors {
3197 heads: Ancestors {
3198 heads: CommitRef(Symbol("foo")),
3199 generation: 1..2,
3200 },
3201 generation: 0..18446744073709551615,
3202 }
3203 "#);
3204 insta::assert_debug_snapshot!(
3205 parse("parents(foo,foo)").unwrap_err().kind(), @r#"
3206 InvalidFunctionArguments {
3207 name: "parents",
3208 message: "Expected 1 arguments",
3209 }
3210 "#);
3211 insta::assert_debug_snapshot!(
3212 parse("root()").unwrap(),
3213 @"Root");
3214 assert!(parse("root(a)").is_err());
3215 insta::assert_debug_snapshot!(
3216 parse(r#"description("")"#).unwrap(),
3217 @r#"Filter(Description(Substring("")))"#);
3218 insta::assert_debug_snapshot!(
3219 parse("description(foo)").unwrap(),
3220 @r#"Filter(Description(Substring("foo")))"#);
3221 insta::assert_debug_snapshot!(
3222 parse("description(visible_heads())").unwrap_err().kind(),
3223 @r#"Expression("Expected expression of string pattern")"#);
3224 insta::assert_debug_snapshot!(
3225 parse("description(\"(foo)\")").unwrap(),
3226 @r#"Filter(Description(Substring("(foo)")))"#);
3227 assert!(parse("mine(foo)").is_err());
3228 insta::assert_debug_snapshot!(
3229 parse_with_workspace("empty()", WorkspaceName::DEFAULT).unwrap(),
3230 @"NotIn(Filter(File(All)))");
3231 assert!(parse_with_workspace("empty(foo)", WorkspaceName::DEFAULT).is_err());
3232 assert!(parse_with_workspace("file()", WorkspaceName::DEFAULT).is_err());
3233 insta::assert_debug_snapshot!(
3234 parse_with_workspace("files(foo)", WorkspaceName::DEFAULT).unwrap(),
3235 @r#"Filter(File(Pattern(PrefixPath("foo"))))"#);
3236 insta::assert_debug_snapshot!(
3237 parse_with_workspace("files(all())", WorkspaceName::DEFAULT).unwrap(),
3238 @"Filter(File(All))");
3239 insta::assert_debug_snapshot!(
3240 parse_with_workspace(r#"files(file:"foo")"#, WorkspaceName::DEFAULT).unwrap(),
3241 @r#"Filter(File(Pattern(FilePath("foo"))))"#);
3242 insta::assert_debug_snapshot!(
3243 parse_with_workspace("files(foo|bar&baz)", WorkspaceName::DEFAULT).unwrap(), @r#"
3244 Filter(
3245 File(
3246 UnionAll(
3247 [
3248 Pattern(PrefixPath("foo")),
3249 Intersection(
3250 Pattern(PrefixPath("bar")),
3251 Pattern(PrefixPath("baz")),
3252 ),
3253 ],
3254 ),
3255 ),
3256 )
3257 "#);
3258 insta::assert_debug_snapshot!(parse("signed()").unwrap(), @"Filter(Signed)");
3259 }
3260
3261 #[test]
3262 fn test_parse_revset_author_committer_functions() {
3263 let settings = insta_settings();
3264 let _guard = settings.bind_to_scope();
3265
3266 insta::assert_debug_snapshot!(
3267 parse("author(foo)").unwrap(), @r#"
3268 Union(
3269 Filter(AuthorName(Substring("foo"))),
3270 Filter(AuthorEmail(Substring("foo"))),
3271 )
3272 "#);
3273 insta::assert_debug_snapshot!(
3274 parse("author_name(foo)").unwrap(),
3275 @r#"Filter(AuthorName(Substring("foo")))"#);
3276 insta::assert_debug_snapshot!(
3277 parse("author_email(foo)").unwrap(),
3278 @r#"Filter(AuthorEmail(Substring("foo")))"#);
3279
3280 insta::assert_debug_snapshot!(
3281 parse("committer(foo)").unwrap(), @r#"
3282 Union(
3283 Filter(CommitterName(Substring("foo"))),
3284 Filter(CommitterEmail(Substring("foo"))),
3285 )
3286 "#);
3287 insta::assert_debug_snapshot!(
3288 parse("committer_name(foo)").unwrap(),
3289 @r#"Filter(CommitterName(Substring("foo")))"#);
3290 insta::assert_debug_snapshot!(
3291 parse("committer_email(foo)").unwrap(),
3292 @r#"Filter(CommitterEmail(Substring("foo")))"#);
3293
3294 insta::assert_debug_snapshot!(
3295 parse("mine()").unwrap(),
3296 @r#"Filter(AuthorEmail(ExactI("test.user@example.com")))"#);
3297 }
3298
3299 #[test]
3300 fn test_parse_revset_keyword_arguments() {
3301 let settings = insta_settings();
3302 let _guard = settings.bind_to_scope();
3303
3304 insta::assert_debug_snapshot!(
3305 parse("remote_bookmarks(remote=foo)").unwrap(), @r#"
3306 CommitRef(
3307 RemoteBookmarks {
3308 bookmark_pattern: Substring(""),
3309 remote_pattern: Substring("foo"),
3310 remote_ref_state: None,
3311 },
3312 )
3313 "#);
3314 insta::assert_debug_snapshot!(
3315 parse("remote_bookmarks(foo, remote=bar)").unwrap(), @r#"
3316 CommitRef(
3317 RemoteBookmarks {
3318 bookmark_pattern: Substring("foo"),
3319 remote_pattern: Substring("bar"),
3320 remote_ref_state: None,
3321 },
3322 )
3323 "#);
3324 insta::assert_debug_snapshot!(
3325 parse("tracked_remote_bookmarks(foo, remote=bar)").unwrap(), @r#"
3326 CommitRef(
3327 RemoteBookmarks {
3328 bookmark_pattern: Substring("foo"),
3329 remote_pattern: Substring("bar"),
3330 remote_ref_state: Some(Tracked),
3331 },
3332 )
3333 "#);
3334 insta::assert_debug_snapshot!(
3335 parse("untracked_remote_bookmarks(foo, remote=bar)").unwrap(), @r#"
3336 CommitRef(
3337 RemoteBookmarks {
3338 bookmark_pattern: Substring("foo"),
3339 remote_pattern: Substring("bar"),
3340 remote_ref_state: Some(New),
3341 },
3342 )
3343 "#);
3344 insta::assert_debug_snapshot!(
3345 parse(r#"remote_bookmarks(remote=foo, bar)"#).unwrap_err().kind(),
3346 @r#"
3347 InvalidFunctionArguments {
3348 name: "remote_bookmarks",
3349 message: "Positional argument follows keyword argument",
3350 }
3351 "#);
3352 insta::assert_debug_snapshot!(
3353 parse(r#"remote_bookmarks("", foo, remote=bar)"#).unwrap_err().kind(),
3354 @r#"
3355 InvalidFunctionArguments {
3356 name: "remote_bookmarks",
3357 message: "Got multiple values for keyword \"remote\"",
3358 }
3359 "#);
3360 insta::assert_debug_snapshot!(
3361 parse(r#"remote_bookmarks(remote=bar, remote=bar)"#).unwrap_err().kind(),
3362 @r#"
3363 InvalidFunctionArguments {
3364 name: "remote_bookmarks",
3365 message: "Got multiple values for keyword \"remote\"",
3366 }
3367 "#);
3368 insta::assert_debug_snapshot!(
3369 parse(r#"remote_bookmarks(unknown=bar)"#).unwrap_err().kind(),
3370 @r#"
3371 InvalidFunctionArguments {
3372 name: "remote_bookmarks",
3373 message: "Unexpected keyword argument \"unknown\"",
3374 }
3375 "#);
3376 }
3377
3378 #[test]
3379 fn test_expand_symbol_alias() {
3380 let settings = insta_settings();
3381 let _guard = settings.bind_to_scope();
3382
3383 insta::assert_debug_snapshot!(
3384 parse_with_aliases("AB|c", [("AB", "a|b")]).unwrap(), @r#"
3385 Union(
3386 Union(
3387 CommitRef(Symbol("a")),
3388 CommitRef(Symbol("b")),
3389 ),
3390 CommitRef(Symbol("c")),
3391 )
3392 "#);
3393
3394 insta::assert_debug_snapshot!(
3396 parse_with_aliases_and_workspace("files(A)", [("A", "a")], WorkspaceName::DEFAULT)
3397 .unwrap(),
3398 @r#"Filter(File(Pattern(PrefixPath("a"))))"#);
3399
3400 insta::assert_debug_snapshot!(
3402 parse_with_aliases("author_name(A)", [("A", "a")]).unwrap(),
3403 @r#"Filter(AuthorName(Substring("a")))"#);
3404 insta::assert_debug_snapshot!(
3407 parse_with_aliases("author_name(A)", [("A", "(exact:a)")]).unwrap(),
3408 @r#"Filter(AuthorName(Exact("a")))"#);
3409
3410 insta::assert_debug_snapshot!(
3412 parse_with_aliases_and_modifier("A-", [("A", "all:a")]).unwrap_err().kind(),
3413 @r#"InAliasExpansion("A")"#);
3414 }
3415
3416 #[test]
3417 fn test_expand_function_alias() {
3418 let settings = insta_settings();
3419 let _guard = settings.bind_to_scope();
3420
3421 insta::assert_debug_snapshot!(
3423 parse_with_aliases("F(a)", [("F(x)", "author_name(x)|committer_name(x)")]).unwrap(),
3424 @r#"
3425 Union(
3426 Filter(AuthorName(Substring("a"))),
3427 Filter(CommitterName(Substring("a"))),
3428 )
3429 "#);
3430 }
3431
3432 #[test]
3433 fn test_optimize_subtree() {
3434 let settings = insta_settings();
3435 let _guard = settings.bind_to_scope();
3436
3437 insta::assert_debug_snapshot!(
3441 optimize(parse("parents(bookmarks() & all())").unwrap()), @r#"
3442 Ancestors {
3443 heads: CommitRef(Bookmarks(Substring(""))),
3444 generation: 1..2,
3445 }
3446 "#);
3447 insta::assert_debug_snapshot!(
3448 optimize(parse("children(bookmarks() & all())").unwrap()), @r#"
3449 Descendants {
3450 roots: CommitRef(Bookmarks(Substring(""))),
3451 generation: 1..2,
3452 }
3453 "#);
3454 insta::assert_debug_snapshot!(
3455 optimize(parse("ancestors(bookmarks() & all())").unwrap()), @r#"
3456 Ancestors {
3457 heads: CommitRef(Bookmarks(Substring(""))),
3458 generation: 0..18446744073709551615,
3459 }
3460 "#);
3461 insta::assert_debug_snapshot!(
3462 optimize(parse("descendants(bookmarks() & all())").unwrap()), @r#"
3463 Descendants {
3464 roots: CommitRef(Bookmarks(Substring(""))),
3465 generation: 0..18446744073709551615,
3466 }
3467 "#);
3468
3469 insta::assert_debug_snapshot!(
3470 optimize(parse("(bookmarks() & all())..(all() & tags())").unwrap()), @r#"
3471 Range {
3472 roots: CommitRef(Bookmarks(Substring(""))),
3473 heads: CommitRef(Tags(Substring(""))),
3474 generation: 0..18446744073709551615,
3475 }
3476 "#);
3477 insta::assert_debug_snapshot!(
3478 optimize(parse("(bookmarks() & all())::(all() & tags())").unwrap()), @r#"
3479 DagRange {
3480 roots: CommitRef(Bookmarks(Substring(""))),
3481 heads: CommitRef(Tags(Substring(""))),
3482 }
3483 "#);
3484
3485 insta::assert_debug_snapshot!(
3486 optimize(parse("heads(bookmarks() & all())").unwrap()),
3487 @r#"Heads(CommitRef(Bookmarks(Substring(""))))"#);
3488 insta::assert_debug_snapshot!(
3489 optimize(parse("roots(bookmarks() & all())").unwrap()),
3490 @r#"Roots(CommitRef(Bookmarks(Substring(""))))"#);
3491
3492 insta::assert_debug_snapshot!(
3493 optimize(parse("latest(bookmarks() & all(), 2)").unwrap()), @r#"
3494 Latest {
3495 candidates: CommitRef(Bookmarks(Substring(""))),
3496 count: 2,
3497 }
3498 "#);
3499
3500 insta::assert_debug_snapshot!(
3501 optimize(parse("present(foo ~ bar)").unwrap()), @r#"
3502 Present(
3503 Difference(
3504 CommitRef(Symbol("foo")),
3505 CommitRef(Symbol("bar")),
3506 ),
3507 )
3508 "#);
3509 insta::assert_debug_snapshot!(
3510 optimize(parse("present(bookmarks() & all())").unwrap()),
3511 @r#"Present(CommitRef(Bookmarks(Substring(""))))"#);
3512
3513 insta::assert_debug_snapshot!(
3514 optimize(parse("at_operation(@-, bookmarks() & all())").unwrap()), @r#"
3515 AtOperation {
3516 operation: "@-",
3517 candidates: CommitRef(Bookmarks(Substring(""))),
3518 }
3519 "#);
3520 insta::assert_debug_snapshot!(
3521 optimize(Rc::new(RevsetExpression::WithinVisibility {
3522 candidates: parse("bookmarks() & all()").unwrap(),
3523 visible_heads: vec![CommitId::from_hex("012345")],
3524 })), @r#"
3525 WithinVisibility {
3526 candidates: CommitRef(Bookmarks(Substring(""))),
3527 visible_heads: [
3528 CommitId("012345"),
3529 ],
3530 }
3531 "#);
3532
3533 insta::assert_debug_snapshot!(
3534 optimize(parse("~bookmarks() & all()").unwrap()),
3535 @r#"NotIn(CommitRef(Bookmarks(Substring(""))))"#);
3536 insta::assert_debug_snapshot!(
3537 optimize(parse("(bookmarks() & all()) | (all() & tags())").unwrap()), @r#"
3538 Union(
3539 CommitRef(Bookmarks(Substring(""))),
3540 CommitRef(Tags(Substring(""))),
3541 )
3542 "#);
3543 insta::assert_debug_snapshot!(
3544 optimize(parse("(bookmarks() & all()) & (all() & tags())").unwrap()), @r#"
3545 Intersection(
3546 CommitRef(Bookmarks(Substring(""))),
3547 CommitRef(Tags(Substring(""))),
3548 )
3549 "#);
3550 insta::assert_debug_snapshot!(
3551 optimize(parse("(bookmarks() & all()) ~ (all() & tags())").unwrap()), @r#"
3552 Difference(
3553 CommitRef(Bookmarks(Substring(""))),
3554 CommitRef(Tags(Substring(""))),
3555 )
3556 "#);
3557 }
3558
3559 #[test]
3560 fn test_optimize_unchanged_subtree() {
3561 fn unwrap_union(
3562 expression: &UserRevsetExpression,
3563 ) -> (&Rc<UserRevsetExpression>, &Rc<UserRevsetExpression>) {
3564 match expression {
3565 RevsetExpression::Union(left, right) => (left, right),
3566 _ => panic!("unexpected expression: {expression:?}"),
3567 }
3568 }
3569
3570 let parsed = parse("foo-").unwrap();
3572 let optimized = optimize(parsed.clone());
3573 assert!(Rc::ptr_eq(&parsed, &optimized));
3574
3575 let parsed = parse("bookmarks() | tags()").unwrap();
3576 let optimized = optimize(parsed.clone());
3577 assert!(Rc::ptr_eq(&parsed, &optimized));
3578
3579 let parsed = parse("bookmarks() & tags()").unwrap();
3580 let optimized = optimize(parsed.clone());
3581 assert!(Rc::ptr_eq(&parsed, &optimized));
3582
3583 let parsed = parse("(bookmarks() & all()) | tags()").unwrap();
3585 let optimized = optimize(parsed.clone());
3586 assert_matches!(
3587 unwrap_union(&optimized).0.as_ref(),
3588 RevsetExpression::CommitRef(RevsetCommitRef::Bookmarks(_))
3589 );
3590 assert!(Rc::ptr_eq(
3591 unwrap_union(&parsed).1,
3592 unwrap_union(&optimized).1
3593 ));
3594
3595 let parsed = parse("bookmarks() | (all() & tags())").unwrap();
3597 let optimized = optimize(parsed.clone());
3598 assert!(Rc::ptr_eq(
3599 unwrap_union(&parsed).0,
3600 unwrap_union(&optimized).0
3601 ));
3602 assert_matches!(
3603 unwrap_union(&optimized).1.as_ref(),
3604 RevsetExpression::CommitRef(RevsetCommitRef::Tags(_))
3605 );
3606 }
3607
3608 #[test]
3609 fn test_optimize_difference() {
3610 let settings = insta_settings();
3611 let _guard = settings.bind_to_scope();
3612
3613 insta::assert_debug_snapshot!(optimize(parse("foo & ~bar").unwrap()), @r#"
3614 Difference(
3615 CommitRef(Symbol("foo")),
3616 CommitRef(Symbol("bar")),
3617 )
3618 "#);
3619 insta::assert_debug_snapshot!(optimize(parse("~foo & bar").unwrap()), @r#"
3620 Difference(
3621 CommitRef(Symbol("bar")),
3622 CommitRef(Symbol("foo")),
3623 )
3624 "#);
3625 insta::assert_debug_snapshot!(optimize(parse("~foo & bar & ~baz").unwrap()), @r#"
3626 Difference(
3627 Difference(
3628 CommitRef(Symbol("bar")),
3629 CommitRef(Symbol("foo")),
3630 ),
3631 CommitRef(Symbol("baz")),
3632 )
3633 "#);
3634 insta::assert_debug_snapshot!(optimize(parse("(all() & ~foo) & bar").unwrap()), @r#"
3635 Difference(
3636 CommitRef(Symbol("bar")),
3637 CommitRef(Symbol("foo")),
3638 )
3639 "#);
3640
3641 insta::assert_debug_snapshot!(
3643 optimize(parse("all() ~ foo").unwrap()),
3644 @r#"NotIn(CommitRef(Symbol("foo")))"#);
3645 insta::assert_debug_snapshot!(optimize(parse("foo ~ bar").unwrap()), @r#"
3646 Difference(
3647 CommitRef(Symbol("foo")),
3648 CommitRef(Symbol("bar")),
3649 )
3650 "#);
3651 insta::assert_debug_snapshot!(optimize(parse("(all() ~ foo) & bar").unwrap()), @r#"
3652 Difference(
3653 CommitRef(Symbol("bar")),
3654 CommitRef(Symbol("foo")),
3655 )
3656 "#);
3657
3658 insta::assert_debug_snapshot!(optimize(parse("::foo & ~::bar").unwrap()), @r#"
3660 Range {
3661 roots: CommitRef(Symbol("bar")),
3662 heads: CommitRef(Symbol("foo")),
3663 generation: 0..18446744073709551615,
3664 }
3665 "#);
3666 insta::assert_debug_snapshot!(optimize(parse("~::foo & ::bar").unwrap()), @r#"
3667 Range {
3668 roots: CommitRef(Symbol("foo")),
3669 heads: CommitRef(Symbol("bar")),
3670 generation: 0..18446744073709551615,
3671 }
3672 "#);
3673 insta::assert_debug_snapshot!(optimize(parse("foo..").unwrap()), @r#"
3674 Range {
3675 roots: CommitRef(Symbol("foo")),
3676 heads: VisibleHeads,
3677 generation: 0..18446744073709551615,
3678 }
3679 "#);
3680 insta::assert_debug_snapshot!(optimize(parse("foo..bar").unwrap()), @r#"
3681 Range {
3682 roots: CommitRef(Symbol("foo")),
3683 heads: CommitRef(Symbol("bar")),
3684 generation: 0..18446744073709551615,
3685 }
3686 "#);
3687
3688 insta::assert_debug_snapshot!(optimize(parse("foo & ~~bar").unwrap()), @r#"
3690 Intersection(
3691 CommitRef(Symbol("foo")),
3692 CommitRef(Symbol("bar")),
3693 )
3694 "#);
3695 insta::assert_debug_snapshot!(optimize(parse("foo & ~~~bar").unwrap()), @r#"
3696 Difference(
3697 CommitRef(Symbol("foo")),
3698 CommitRef(Symbol("bar")),
3699 )
3700 "#);
3701 insta::assert_debug_snapshot!(optimize(parse("~(all() & ~foo) & bar").unwrap()), @r#"
3702 Intersection(
3703 CommitRef(Symbol("foo")),
3704 CommitRef(Symbol("bar")),
3705 )
3706 "#);
3707
3708 insta::assert_debug_snapshot!(optimize(parse("~foo & ~bar").unwrap()), @r#"
3710 Difference(
3711 NotIn(CommitRef(Symbol("foo"))),
3712 CommitRef(Symbol("bar")),
3713 )
3714 "#);
3715 }
3716
3717 #[test]
3718 fn test_optimize_not_in_ancestors() {
3719 let settings = insta_settings();
3720 let _guard = settings.bind_to_scope();
3721
3722 insta::assert_debug_snapshot!(optimize(parse("~(::foo)").unwrap()), @r#"
3724 Range {
3725 roots: CommitRef(Symbol("foo")),
3726 heads: VisibleHeads,
3727 generation: 0..18446744073709551615,
3728 }
3729 "#);
3730
3731 insta::assert_debug_snapshot!(optimize(parse("~(::foo-)").unwrap()), @r#"
3733 Range {
3734 roots: Ancestors {
3735 heads: CommitRef(Symbol("foo")),
3736 generation: 1..2,
3737 },
3738 heads: VisibleHeads,
3739 generation: 0..18446744073709551615,
3740 }
3741 "#);
3742 insta::assert_debug_snapshot!(optimize(parse("~(::foo--)").unwrap()), @r#"
3743 Range {
3744 roots: Ancestors {
3745 heads: CommitRef(Symbol("foo")),
3746 generation: 2..3,
3747 },
3748 heads: VisibleHeads,
3749 generation: 0..18446744073709551615,
3750 }
3751 "#);
3752
3753 insta::assert_debug_snapshot!(optimize(parse("~ancestors(foo, 1)").unwrap()), @r#"
3755 NotIn(
3756 Ancestors {
3757 heads: CommitRef(Symbol("foo")),
3758 generation: 0..1,
3759 },
3760 )
3761 "#);
3762 insta::assert_debug_snapshot!(optimize(parse("~ancestors(foo-, 1)").unwrap()), @r#"
3763 NotIn(
3764 Ancestors {
3765 heads: CommitRef(Symbol("foo")),
3766 generation: 1..2,
3767 },
3768 )
3769 "#);
3770 }
3771
3772 #[test]
3773 fn test_optimize_filter_difference() {
3774 let settings = insta_settings();
3775 let _guard = settings.bind_to_scope();
3776
3777 insta::assert_debug_snapshot!(optimize(parse("~empty()").unwrap()), @"Filter(File(All))");
3779
3780 insta::assert_debug_snapshot!(
3782 optimize(parse("(author_name(foo) & ~bar) & baz").unwrap()), @r#"
3783 Intersection(
3784 Difference(
3785 CommitRef(Symbol("baz")),
3786 CommitRef(Symbol("bar")),
3787 ),
3788 Filter(AuthorName(Substring("foo"))),
3789 )
3790 "#);
3791
3792 insta::assert_debug_snapshot!(
3794 optimize(parse("~foo & author_name(bar)").unwrap()), @r#"
3795 Intersection(
3796 NotIn(CommitRef(Symbol("foo"))),
3797 Filter(AuthorName(Substring("bar"))),
3798 )
3799 "#);
3800 insta::assert_debug_snapshot!(
3801 optimize(parse("~foo & (author_name(bar) | baz)").unwrap()), @r#"
3802 Intersection(
3803 NotIn(CommitRef(Symbol("foo"))),
3804 AsFilter(
3805 Union(
3806 Filter(AuthorName(Substring("bar"))),
3807 CommitRef(Symbol("baz")),
3808 ),
3809 ),
3810 )
3811 "#);
3812
3813 insta::assert_debug_snapshot!(
3815 optimize(parse("author_name(foo) ~ bar").unwrap()), @r#"
3816 Intersection(
3817 NotIn(CommitRef(Symbol("bar"))),
3818 Filter(AuthorName(Substring("foo"))),
3819 )
3820 "#);
3821 }
3822
3823 #[test]
3824 fn test_optimize_filter_intersection() {
3825 let settings = insta_settings();
3826 let _guard = settings.bind_to_scope();
3827
3828 insta::assert_debug_snapshot!(
3829 optimize(parse("author_name(foo)").unwrap()),
3830 @r#"Filter(AuthorName(Substring("foo")))"#);
3831
3832 insta::assert_debug_snapshot!(optimize(parse("foo & description(bar)").unwrap()), @r#"
3833 Intersection(
3834 CommitRef(Symbol("foo")),
3835 Filter(Description(Substring("bar"))),
3836 )
3837 "#);
3838 insta::assert_debug_snapshot!(optimize(parse("author_name(foo) & bar").unwrap()), @r#"
3839 Intersection(
3840 CommitRef(Symbol("bar")),
3841 Filter(AuthorName(Substring("foo"))),
3842 )
3843 "#);
3844 insta::assert_debug_snapshot!(
3845 optimize(parse("author_name(foo) & committer_name(bar)").unwrap()), @r#"
3846 Intersection(
3847 Filter(AuthorName(Substring("foo"))),
3848 Filter(CommitterName(Substring("bar"))),
3849 )
3850 "#);
3851
3852 insta::assert_debug_snapshot!(
3853 optimize(parse("foo & description(bar) & author_name(baz)").unwrap()), @r#"
3854 Intersection(
3855 Intersection(
3856 CommitRef(Symbol("foo")),
3857 Filter(Description(Substring("bar"))),
3858 ),
3859 Filter(AuthorName(Substring("baz"))),
3860 )
3861 "#);
3862 insta::assert_debug_snapshot!(
3863 optimize(parse("committer_name(foo) & bar & author_name(baz)").unwrap()), @r#"
3864 Intersection(
3865 Intersection(
3866 CommitRef(Symbol("bar")),
3867 Filter(CommitterName(Substring("foo"))),
3868 ),
3869 Filter(AuthorName(Substring("baz"))),
3870 )
3871 "#);
3872 insta::assert_debug_snapshot!(
3873 optimize(parse_with_workspace(
3874 "committer_name(foo) & files(bar) & baz",
3875 WorkspaceName::DEFAULT).unwrap(),
3876 ), @r#"
3877 Intersection(
3878 Intersection(
3879 CommitRef(Symbol("baz")),
3880 Filter(CommitterName(Substring("foo"))),
3881 ),
3882 Filter(File(Pattern(PrefixPath("bar")))),
3883 )
3884 "#);
3885 insta::assert_debug_snapshot!(
3886 optimize(parse_with_workspace(
3887 "committer_name(foo) & files(bar) & author_name(baz)",
3888 WorkspaceName::DEFAULT).unwrap(),
3889 ), @r#"
3890 Intersection(
3891 Intersection(
3892 Filter(CommitterName(Substring("foo"))),
3893 Filter(File(Pattern(PrefixPath("bar")))),
3894 ),
3895 Filter(AuthorName(Substring("baz"))),
3896 )
3897 "#);
3898 insta::assert_debug_snapshot!(
3899 optimize(parse_with_workspace(
3900 "foo & files(bar) & baz",
3901 WorkspaceName::DEFAULT).unwrap(),
3902 ), @r#"
3903 Intersection(
3904 Intersection(
3905 CommitRef(Symbol("foo")),
3906 CommitRef(Symbol("baz")),
3907 ),
3908 Filter(File(Pattern(PrefixPath("bar")))),
3909 )
3910 "#);
3911
3912 insta::assert_debug_snapshot!(
3913 optimize(parse("foo & description(bar) & author_name(baz) & qux").unwrap()), @r#"
3914 Intersection(
3915 Intersection(
3916 Intersection(
3917 CommitRef(Symbol("foo")),
3918 CommitRef(Symbol("qux")),
3919 ),
3920 Filter(Description(Substring("bar"))),
3921 ),
3922 Filter(AuthorName(Substring("baz"))),
3923 )
3924 "#);
3925 insta::assert_debug_snapshot!(
3926 optimize(parse("foo & description(bar) & parents(author_name(baz)) & qux").unwrap()),
3927 @r#"
3928 Intersection(
3929 Intersection(
3930 Intersection(
3931 CommitRef(Symbol("foo")),
3932 Ancestors {
3933 heads: Filter(AuthorName(Substring("baz"))),
3934 generation: 1..2,
3935 },
3936 ),
3937 CommitRef(Symbol("qux")),
3938 ),
3939 Filter(Description(Substring("bar"))),
3940 )
3941 "#);
3942 insta::assert_debug_snapshot!(
3943 optimize(parse("foo & description(bar) & parents(author_name(baz) & qux)").unwrap()),
3944 @r#"
3945 Intersection(
3946 Intersection(
3947 CommitRef(Symbol("foo")),
3948 Ancestors {
3949 heads: Intersection(
3950 CommitRef(Symbol("qux")),
3951 Filter(AuthorName(Substring("baz"))),
3952 ),
3953 generation: 1..2,
3954 },
3955 ),
3956 Filter(Description(Substring("bar"))),
3957 )
3958 "#);
3959
3960 insta::assert_debug_snapshot!(
3962 optimize(parse("(a & author_name(A)) & (b & author_name(B)) & (c & author_name(C))").unwrap()),
3963 @r#"
3964 Intersection(
3965 Intersection(
3966 Intersection(
3967 Intersection(
3968 Intersection(
3969 CommitRef(Symbol("a")),
3970 CommitRef(Symbol("b")),
3971 ),
3972 CommitRef(Symbol("c")),
3973 ),
3974 Filter(AuthorName(Substring("A"))),
3975 ),
3976 Filter(AuthorName(Substring("B"))),
3977 ),
3978 Filter(AuthorName(Substring("C"))),
3979 )
3980 "#);
3981 insta::assert_debug_snapshot!(
3982 optimize(parse("(a & author_name(A)) & ((b & author_name(B)) & (c & author_name(C))) & d").unwrap()),
3983 @r#"
3984 Intersection(
3985 Intersection(
3986 Intersection(
3987 Intersection(
3988 Intersection(
3989 CommitRef(Symbol("a")),
3990 Intersection(
3991 CommitRef(Symbol("b")),
3992 CommitRef(Symbol("c")),
3993 ),
3994 ),
3995 CommitRef(Symbol("d")),
3996 ),
3997 Filter(AuthorName(Substring("A"))),
3998 ),
3999 Filter(AuthorName(Substring("B"))),
4000 ),
4001 Filter(AuthorName(Substring("C"))),
4002 )
4003 "#);
4004
4005 insta::assert_debug_snapshot!(
4007 optimize(parse("foo & (all() & description(bar)) & (author_name(baz) & all())").unwrap()),
4008 @r#"
4009 Intersection(
4010 Intersection(
4011 CommitRef(Symbol("foo")),
4012 Filter(Description(Substring("bar"))),
4013 ),
4014 Filter(AuthorName(Substring("baz"))),
4015 )
4016 "#);
4017
4018 insta::assert_debug_snapshot!(
4020 optimize(parse("author_name(foo) & bar & at_operation(@-, committer_name(baz))").unwrap()),
4021 @r#"
4022 Intersection(
4023 Intersection(
4024 CommitRef(Symbol("bar")),
4025 AtOperation {
4026 operation: "@-",
4027 candidates: Filter(CommitterName(Substring("baz"))),
4028 },
4029 ),
4030 Filter(AuthorName(Substring("foo"))),
4031 )
4032 "#);
4033 }
4034
4035 #[test]
4036 fn test_optimize_filter_subtree() {
4037 let settings = insta_settings();
4038 let _guard = settings.bind_to_scope();
4039
4040 insta::assert_debug_snapshot!(
4041 optimize(parse("(author_name(foo) | bar) & baz").unwrap()), @r#"
4042 Intersection(
4043 CommitRef(Symbol("baz")),
4044 AsFilter(
4045 Union(
4046 Filter(AuthorName(Substring("foo"))),
4047 CommitRef(Symbol("bar")),
4048 ),
4049 ),
4050 )
4051 "#);
4052
4053 insta::assert_debug_snapshot!(
4054 optimize(parse("(foo | committer_name(bar)) & description(baz) & qux").unwrap()), @r#"
4055 Intersection(
4056 Intersection(
4057 CommitRef(Symbol("qux")),
4058 AsFilter(
4059 Union(
4060 CommitRef(Symbol("foo")),
4061 Filter(CommitterName(Substring("bar"))),
4062 ),
4063 ),
4064 ),
4065 Filter(Description(Substring("baz"))),
4066 )
4067 "#);
4068
4069 insta::assert_debug_snapshot!(
4070 optimize(parse("(~present(author_name(foo) & bar) | baz) & qux").unwrap()), @r#"
4071 Intersection(
4072 CommitRef(Symbol("qux")),
4073 AsFilter(
4074 Union(
4075 AsFilter(
4076 NotIn(
4077 AsFilter(
4078 Present(
4079 Intersection(
4080 CommitRef(Symbol("bar")),
4081 Filter(AuthorName(Substring("foo"))),
4082 ),
4083 ),
4084 ),
4085 ),
4086 ),
4087 CommitRef(Symbol("baz")),
4088 ),
4089 ),
4090 )
4091 "#);
4092
4093 insta::assert_debug_snapshot!(
4095 optimize(parse(
4096 "(a & (author_name(A) | 0)) & (b & (author_name(B) | 1)) & (c & (author_name(C) | 2))").unwrap()),
4097 @r#"
4098 Intersection(
4099 Intersection(
4100 Intersection(
4101 Intersection(
4102 Intersection(
4103 CommitRef(Symbol("a")),
4104 CommitRef(Symbol("b")),
4105 ),
4106 CommitRef(Symbol("c")),
4107 ),
4108 AsFilter(
4109 Union(
4110 Filter(AuthorName(Substring("A"))),
4111 CommitRef(Symbol("0")),
4112 ),
4113 ),
4114 ),
4115 AsFilter(
4116 Union(
4117 Filter(AuthorName(Substring("B"))),
4118 CommitRef(Symbol("1")),
4119 ),
4120 ),
4121 ),
4122 AsFilter(
4123 Union(
4124 Filter(AuthorName(Substring("C"))),
4125 CommitRef(Symbol("2")),
4126 ),
4127 ),
4128 )
4129 "#);
4130 }
4131
4132 #[test]
4133 fn test_optimize_ancestors() {
4134 let settings = insta_settings();
4135 let _guard = settings.bind_to_scope();
4136
4137 insta::assert_debug_snapshot!(optimize(parse("foo--").unwrap()), @r#"
4139 Ancestors {
4140 heads: CommitRef(Symbol("foo")),
4141 generation: 2..3,
4142 }
4143 "#);
4144 insta::assert_debug_snapshot!(optimize(parse("::(foo---)").unwrap()), @r#"
4145 Ancestors {
4146 heads: CommitRef(Symbol("foo")),
4147 generation: 3..18446744073709551615,
4148 }
4149 "#);
4150 insta::assert_debug_snapshot!(optimize(parse("(::foo)---").unwrap()), @r#"
4151 Ancestors {
4152 heads: CommitRef(Symbol("foo")),
4153 generation: 3..18446744073709551615,
4154 }
4155 "#);
4156
4157 insta::assert_debug_snapshot!(optimize(parse("foo---+").unwrap()), @r#"
4159 Descendants {
4160 roots: Ancestors {
4161 heads: CommitRef(Symbol("foo")),
4162 generation: 3..4,
4163 },
4164 generation: 1..2,
4165 }
4166 "#);
4167
4168 insta::assert_debug_snapshot!(optimize(parse("foo..(bar--)").unwrap()), @r#"
4170 Range {
4171 roots: CommitRef(Symbol("foo")),
4172 heads: CommitRef(Symbol("bar")),
4173 generation: 2..18446744073709551615,
4174 }
4175 "#);
4176 insta::assert_debug_snapshot!(optimize(parse("(foo--)..(bar---)").unwrap()), @r#"
4178 Range {
4179 roots: Ancestors {
4180 heads: CommitRef(Symbol("foo")),
4181 generation: 2..3,
4182 },
4183 heads: CommitRef(Symbol("bar")),
4184 generation: 3..18446744073709551615,
4185 }
4186 "#);
4187 insta::assert_debug_snapshot!(
4189 optimize(parse("~ancestors(foo, 2) & ::bar").unwrap()), @r#"
4190 Difference(
4191 Ancestors {
4192 heads: CommitRef(Symbol("bar")),
4193 generation: 0..18446744073709551615,
4194 },
4195 Ancestors {
4196 heads: CommitRef(Symbol("foo")),
4197 generation: 0..2,
4198 },
4199 )
4200 "#);
4201
4202 insta::assert_debug_snapshot!(optimize(parse("(foo..bar)--").unwrap()), @r#"
4205 Ancestors {
4206 heads: Range {
4207 roots: CommitRef(Symbol("foo")),
4208 heads: CommitRef(Symbol("bar")),
4209 generation: 0..18446744073709551615,
4210 },
4211 generation: 2..3,
4212 }
4213 "#);
4214 insta::assert_debug_snapshot!(optimize(parse("foo..(bar..baz)").unwrap()), @r#"
4215 Range {
4216 roots: CommitRef(Symbol("foo")),
4217 heads: Range {
4218 roots: CommitRef(Symbol("bar")),
4219 heads: CommitRef(Symbol("baz")),
4220 generation: 0..18446744073709551615,
4221 },
4222 generation: 0..18446744073709551615,
4223 }
4224 "#);
4225
4226 insta::assert_debug_snapshot!(
4228 optimize(parse("ancestors(ancestors(foo), 0)").unwrap()), @r#"
4229 Ancestors {
4230 heads: CommitRef(Symbol("foo")),
4231 generation: 0..0,
4232 }
4233 "#
4234 );
4235 insta::assert_debug_snapshot!(
4236 optimize(parse("ancestors(ancestors(foo, 0))").unwrap()), @r#"
4237 Ancestors {
4238 heads: CommitRef(Symbol("foo")),
4239 generation: 0..0,
4240 }
4241 "#
4242 );
4243 }
4244
4245 #[test]
4246 fn test_optimize_descendants() {
4247 let settings = insta_settings();
4248 let _guard = settings.bind_to_scope();
4249
4250 insta::assert_debug_snapshot!(optimize(parse("foo++").unwrap()), @r#"
4252 Descendants {
4253 roots: CommitRef(Symbol("foo")),
4254 generation: 2..3,
4255 }
4256 "#);
4257 insta::assert_debug_snapshot!(optimize(parse("(foo+++)::").unwrap()), @r#"
4258 Descendants {
4259 roots: CommitRef(Symbol("foo")),
4260 generation: 3..18446744073709551615,
4261 }
4262 "#);
4263 insta::assert_debug_snapshot!(optimize(parse("(foo::)+++").unwrap()), @r#"
4264 Descendants {
4265 roots: CommitRef(Symbol("foo")),
4266 generation: 3..18446744073709551615,
4267 }
4268 "#);
4269
4270 insta::assert_debug_snapshot!(optimize(parse("foo+++-").unwrap()), @r#"
4272 Ancestors {
4273 heads: Descendants {
4274 roots: CommitRef(Symbol("foo")),
4275 generation: 3..4,
4276 },
4277 generation: 1..2,
4278 }
4279 "#);
4280
4281 insta::assert_debug_snapshot!(optimize(parse("(foo++)::bar").unwrap()), @r#"
4285 DagRange {
4286 roots: Descendants {
4287 roots: CommitRef(Symbol("foo")),
4288 generation: 2..3,
4289 },
4290 heads: CommitRef(Symbol("bar")),
4291 }
4292 "#);
4293 }
4294
4295 #[test]
4296 fn test_escape_string_literal() {
4297 assert_eq!(format_symbol("foo"), "foo");
4299 assert_eq!(format_symbol("foo.bar"), "foo.bar");
4300
4301 assert_eq!(format_symbol("foo@bar"), r#""foo@bar""#);
4303 assert_eq!(format_symbol("foo bar"), r#""foo bar""#);
4304 assert_eq!(format_symbol(" foo "), r#"" foo ""#);
4305 assert_eq!(format_symbol("(foo)"), r#""(foo)""#);
4306 assert_eq!(format_symbol("all:foo"), r#""all:foo""#);
4307
4308 assert_eq!(format_symbol("foo\"bar"), r#""foo\"bar""#);
4310 assert_eq!(format_symbol("foo\\bar"), r#""foo\\bar""#);
4311 assert_eq!(format_symbol("foo\\\"bar"), r#""foo\\\"bar""#);
4312 assert_eq!(format_symbol("foo\nbar"), r#""foo\nbar""#);
4313
4314 assert_eq!(format_symbol("foo\"bar"), r#""foo\"bar""#);
4317 assert_eq!(format_symbol("foo\\bar"), r#""foo\\bar""#);
4318 assert_eq!(format_symbol("foo\\\"bar"), r#""foo\\\"bar""#);
4319 assert_eq!(format_symbol("foo \x01 bar"), r#""foo \x01 bar""#);
4320 }
4321
4322 #[test]
4323 fn test_escape_remote_symbol() {
4324 assert_eq!(format_remote_symbol("foo", "bar"), "foo@bar");
4325 assert_eq!(
4326 format_remote_symbol(" foo ", "bar:baz"),
4327 r#"" foo "@"bar:baz""#
4328 );
4329 }
4330}