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