Skip to main content

git_branchless_revset/
eval.rs

1use std::collections::HashMap;
2use std::fmt::Display;
3use std::num::ParseIntError;
4use std::sync::Arc;
5
6use eden_dag::errors::BackendError;
7use itertools::Itertools;
8use lib::core::effects::{Effects, OperationType};
9use thiserror::Error;
10
11use lib::core::dag::{CommitSet, Dag};
12use lib::core::formatting::Pluralize;
13use lib::git::{ConfigRead, Repo, RepoError, ResolvedReferenceInfo};
14use tracing::instrument;
15
16use super::Expr;
17use super::builtins::FUNCTIONS;
18use super::parser::{ParseError, parse};
19use super::pattern::{Pattern, PatternError};
20
21#[derive(Debug)]
22pub(super) struct Context<'a> {
23    pub effects: &'a Effects,
24    pub repo: &'a Repo,
25    pub dag: &'a mut Dag,
26}
27
28#[derive(Debug, Error)]
29pub enum EvalError {
30    #[error("no commit, branch, or reference with the name '{name}' could be found")]
31    UnboundName { name: String },
32
33    #[error(
34        "no function with the name '{name}' could be found; these functions are available: {}",
35        available_names.join(", "),
36    )]
37    UnboundFunction {
38        name: String,
39        available_names: Vec<&'static str>,
40    },
41
42    #[error(
43        "invalid number of arguments to {function_name}: expected {} but got {actual_arity}",
44        expected_arities.iter().map(|arity| arity.to_string()).collect::<Vec<_>>().join("/"),
45    )]
46    ArityMismatch {
47        function_name: String,
48        expected_arities: Vec<usize>,
49        actual_arity: usize,
50    },
51
52    #[error(
53        "expected '{expr}' to evaluate to {}, but got {actual_len}",
54        Pluralize {
55            determiner: None,
56            amount: *expected_len,
57            unit: ("element", "elements"),
58        }
59    )]
60    UnexpectedSetLength {
61        expr: String,
62        expected_len: usize,
63        actual_len: usize,
64    },
65
66    #[error("failed to parse alias expression '{alias}'\n{source}")]
67    ParseAlias { alias: String, source: ParseError },
68
69    #[error("not an integer: {from}")]
70    ParseInt {
71        #[from]
72        from: ParseIntError,
73    },
74
75    #[error("expected an integer, but got a call to function: {function_name}")]
76    ExpectedNumberNotFunction { function_name: String },
77
78    #[error("expected a text-matching pattern, but got a call to function: {function_name}")]
79    ExpectedPatternNotFunction { function_name: String },
80
81    #[error("there was no latest command run with `git test`; try running `git test` first")]
82    NoLatestTestCommand,
83
84    #[error(transparent)]
85    PatternError(#[from] PatternError),
86
87    #[error(transparent)]
88    RepoError(#[from] RepoError),
89
90    #[error("query error: {from}")]
91    DagError {
92        #[from]
93        from: eden_dag::Error,
94    },
95
96    #[error(transparent)]
97    OtherError(eyre::Error),
98}
99
100pub(super) fn make_dag_backend_error(error: impl Display) -> eden_dag::Error {
101    let error = format!("error: {error}");
102    let error = BackendError::Generic(error);
103    eden_dag::Error::Backend(Box::new(error))
104}
105
106pub type EvalResult = Result<CommitSet, EvalError>;
107
108/// Evaluate the provided revset expression.
109#[instrument]
110pub fn eval(effects: &Effects, repo: &Repo, dag: &mut Dag, expr: &Expr) -> EvalResult {
111    let (effects, _progress) =
112        effects.start_operation(OperationType::EvaluateRevset(Arc::new(expr.to_string())));
113
114    let mut ctx = Context {
115        effects: &effects,
116        repo,
117        dag,
118    };
119    let commits = eval_inner(&mut ctx, expr, &None)?;
120    Ok(commits)
121}
122
123#[instrument]
124fn eval_inner(ctx: &mut Context, expr: &Expr, commitset_hint: &Option<&CommitSet>) -> EvalResult {
125    match expr {
126        Expr::Name(name) => eval_name(ctx, name),
127        Expr::FunctionCall(name, args) => {
128            let result = eval_fn(ctx, name, args, commitset_hint)?;
129            let result = ctx
130                .dag
131                .filter_visible_commits(result)
132                .map_err(EvalError::OtherError)?;
133            Ok(result)
134        }
135    }
136}
137
138#[instrument]
139pub(super) fn eval_name(ctx: &mut Context, name: &str) -> EvalResult {
140    if name == "." || name == "@" {
141        let head_info = ctx.repo.get_head_info()?;
142        return match head_info {
143            ResolvedReferenceInfo {
144                oid: Some(oid),
145                reference_name: _,
146            } => Ok(oid.into()),
147            ResolvedReferenceInfo {
148                oid: None,
149                reference_name: _,
150            } => Ok(CommitSet::empty()),
151        };
152    }
153
154    let commit = ctx.repo.revparse_single_commit(name);
155    let commit_set = match commit {
156        Ok(Some(commit)) => {
157            let commit_set: CommitSet = commit.get_oid().into();
158            commit_set
159        }
160        Ok(None) | Err(_) => {
161            return Err(EvalError::UnboundName {
162                name: name.to_owned(),
163            });
164        }
165    };
166
167    ctx.dag
168        .sync_from_oids(
169            ctx.effects,
170            ctx.repo,
171            CommitSet::empty(),
172            commit_set.clone(),
173        )
174        .map_err(EvalError::OtherError)?;
175    Ok(commit_set)
176}
177
178#[instrument]
179pub(super) fn eval_fn(
180    ctx: &mut Context,
181    name: &str,
182    args: &[Expr],
183    commitset_hint: &Option<&CommitSet>,
184) -> EvalResult {
185    if let Some(function) = FUNCTIONS.get(name) {
186        return function(ctx, name, args, commitset_hint);
187    }
188
189    let alias_key = format!("branchless.revsets.alias.{name}");
190    let alias_template: Option<String> = ctx
191        .repo
192        .get_readonly_config()
193        .map_err(EvalError::RepoError)?
194        .get(alias_key)
195        .map_err(EvalError::OtherError)?;
196    if let Some(alias_template) = alias_template {
197        let alias_expr = parse(&alias_template).map_err(|err| EvalError::ParseAlias {
198            alias: alias_template.clone(),
199            source: err,
200        })?;
201        let arg_map: HashMap<String, Expr> = args
202            .iter()
203            .enumerate()
204            .map(|(i, arg)| (format!("${}", i + 1), arg.clone()))
205            .collect();
206        let alias_expr = alias_expr.replace_names(&arg_map);
207        let commits = eval_inner(ctx, &alias_expr, commitset_hint)?;
208        return Ok(commits);
209    }
210
211    Err(EvalError::UnboundFunction {
212        name: name.to_owned(),
213        available_names: FUNCTIONS.keys().sorted().copied().collect(),
214    })
215}
216
217#[instrument]
218pub(super) fn eval0(
219    ctx: &mut Context,
220    function_name: &str,
221    args: &[Expr],
222) -> Result<(), EvalError> {
223    match args {
224        [] => Ok(()),
225
226        args => Err(EvalError::ArityMismatch {
227            function_name: function_name.to_string(),
228            expected_arities: vec![0],
229            actual_arity: args.len(),
230        }),
231    }
232}
233
234#[instrument]
235pub(super) fn eval0_or_1(
236    ctx: &mut Context,
237    function_name: &str,
238    args: &[Expr],
239) -> Result<Option<CommitSet>, EvalError> {
240    match args {
241        [] => Ok(None),
242        [expr] => {
243            let arg = eval_inner(ctx, expr, &None)?;
244            Ok(Some(arg))
245        }
246        args => Err(EvalError::ArityMismatch {
247            function_name: function_name.to_string(),
248            expected_arities: vec![0, 1],
249            actual_arity: args.len(),
250        }),
251    }
252}
253
254#[instrument]
255pub(super) fn eval1(ctx: &mut Context, function_name: &str, args: &[Expr]) -> EvalResult {
256    match args {
257        [arg] => {
258            let lhs = eval_inner(ctx, arg, &None)?;
259            Ok(lhs)
260        }
261
262        args => Err(EvalError::ArityMismatch {
263            function_name: function_name.to_string(),
264            expected_arities: vec![1],
265            actual_arity: args.len(),
266        }),
267    }
268}
269
270#[instrument]
271pub(super) fn eval0_or_1_pattern(
272    ctx: &mut Context,
273    function_name: &str,
274    args: &[Expr],
275) -> Result<Option<Pattern>, EvalError> {
276    match args {
277        [] => Ok(None),
278        [_] => eval1_pattern(ctx, function_name, args).map(Some),
279        args => Err(EvalError::ArityMismatch {
280            function_name: function_name.to_string(),
281            expected_arities: vec![0, 1],
282            actual_arity: args.len(),
283        }),
284    }
285}
286
287#[instrument]
288pub(super) fn eval1_pattern(
289    _ctx: &mut Context,
290    function_name: &str,
291    args: &[Expr],
292) -> Result<Pattern, EvalError> {
293    match args {
294        [Expr::Name(pattern)] => Ok(Pattern::new(pattern)?),
295
296        [Expr::FunctionCall(name, _args)] => Err(EvalError::ExpectedPatternNotFunction {
297            function_name: name.clone().into_owned(),
298        }),
299
300        args => Err(EvalError::ArityMismatch {
301            function_name: function_name.to_string(),
302            expected_arities: vec![1],
303            actual_arity: args.len(),
304        }),
305    }
306}
307
308#[instrument]
309pub(super) fn eval2(
310    ctx: &mut Context,
311    function_name: &str,
312    args: &[Expr],
313    limit_rhs: bool,
314) -> Result<(CommitSet, CommitSet), EvalError> {
315    match args {
316        [lhs, rhs] => {
317            let lhs = eval_inner(ctx, lhs, &None)?;
318            let rhs = eval_inner(ctx, rhs, &limit_rhs.then_some(&lhs))?;
319            Ok((lhs, rhs))
320        }
321
322        args => Err(EvalError::ArityMismatch {
323            function_name: function_name.to_string(),
324            expected_arities: vec![2],
325            actual_arity: args.len(),
326        }),
327    }
328}
329
330#[instrument]
331pub(super) fn eval_number_rhs(
332    ctx: &mut Context,
333    function_name: &str,
334    args: &[Expr],
335) -> Result<(CommitSet, usize), EvalError> {
336    match args {
337        [lhs, Expr::Name(name)] => {
338            let lhs = eval_inner(ctx, lhs, &None)?;
339            let number: usize = { name.parse()? };
340            Ok((lhs, number))
341        }
342
343        [_lhs, Expr::FunctionCall(name, _args)] => Err(EvalError::ExpectedNumberNotFunction {
344            function_name: name.clone().into_owned(),
345        }),
346
347        args => Err(EvalError::ArityMismatch {
348            function_name: function_name.to_string(),
349            expected_arities: vec![2],
350            actual_arity: args.len(),
351        }),
352    }
353}
354
355#[cfg(test)]
356mod tests {
357    use std::borrow::Cow;
358
359    use lib::core::eventlog::{EventLogDb, EventReplayer};
360    use lib::core::formatting::Glyphs;
361    use lib::core::repo_ext::RepoExt;
362    use lib::git::Commit;
363    use lib::testing::{GitRunOptions, make_git};
364
365    use super::*;
366
367    fn eval_and_sort<'a>(
368        effects: &Effects,
369        repo: &'a Repo,
370        dag: &mut Dag,
371        expr: &Expr,
372    ) -> eyre::Result<Vec<Commit<'a>>> {
373        let result = eval(effects, repo, dag, expr)?;
374        let mut commits: Vec<Commit> = dag
375            .commit_set_to_vec(&result)?
376            .into_iter()
377            .map(|oid| repo.find_commit_or_fail(oid))
378            .try_collect()?;
379        commits.sort_by_key(|commit| (commit.get_message_pretty(), commit.get_time()));
380        Ok(commits)
381    }
382
383    #[test]
384    fn test_eval() -> eyre::Result<()> {
385        let git = make_git()?;
386        git.init_repo()?;
387
388        let test1_oid = git.commit_file("test1", 1)?;
389        git.detach_head()?;
390        let test2_oid = git.commit_file("test2", 2)?;
391        let _test3_oid = git.commit_file("test3", 3)?;
392
393        git.run(&["checkout", "master"])?;
394        git.commit_file("test4", 4)?;
395        git.detach_head()?;
396        git.commit_file("test5", 5)?;
397        git.commit_file("test6", 6)?;
398        git.run(&["checkout", "HEAD~"])?;
399        let test7_oid = git.commit_file("test7", 7)?;
400
401        let effects = Effects::new_suppress_for_test(Glyphs::text());
402        let repo = git.get_repo()?;
403        let conn = repo.get_db_conn()?;
404        let event_log_db = EventLogDb::new(&conn)?;
405        let event_replayer = EventReplayer::from_event_log_db(&effects, &repo, &event_log_db)?;
406        let event_cursor = event_replayer.make_default_cursor();
407        let references_snapshot = repo.get_references_snapshot()?;
408        let mut dag = Dag::open_and_sync(
409            &effects,
410            &repo,
411            &event_replayer,
412            event_cursor,
413            &references_snapshot,
414        )?;
415
416        {
417            let expr = Expr::FunctionCall(Cow::Borrowed("all"), vec![]);
418            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
419            Ok(
420                [
421                    Commit {
422                        inner: Commit {
423                            id: f777ecc9b0db5ed372b2615695191a8a17f79f24,
424                            summary: "create initial.txt",
425                        },
426                    },
427                    Commit {
428                        inner: Commit {
429                            id: 62fc20d2a290daea0d52bdc2ed2ad4be6491010e,
430                            summary: "create test1.txt",
431                        },
432                    },
433                    Commit {
434                        inner: Commit {
435                            id: 96d1c37a3d4363611c49f7e52186e189a04c531f,
436                            summary: "create test2.txt",
437                        },
438                    },
439                    Commit {
440                        inner: Commit {
441                            id: 70deb1e28791d8e7dd5a1f0c871a51b91282562f,
442                            summary: "create test3.txt",
443                        },
444                    },
445                    Commit {
446                        inner: Commit {
447                            id: bf0d52a607f693201512a43b6b5a70b2a275e0ad,
448                            summary: "create test4.txt",
449                        },
450                    },
451                    Commit {
452                        inner: Commit {
453                            id: 848121cb21bf9af8b064c91bc8930bd16d624a22,
454                            summary: "create test5.txt",
455                        },
456                    },
457                    Commit {
458                        inner: Commit {
459                            id: f0abf649939928fe5475179fd84e738d3d3725dc,
460                            summary: "create test6.txt",
461                        },
462                    },
463                    Commit {
464                        inner: Commit {
465                            id: ba07500a4adc661dc06a748d200ef92120e1b355,
466                            summary: "create test7.txt",
467                        },
468                    },
469                ],
470            )
471            "###);
472        }
473
474        {
475            let expr = Expr::FunctionCall(Cow::Borrowed("none"), vec![]);
476            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
477            Ok(
478                [],
479            )
480            "###);
481        }
482
483        {
484            let expr = Expr::FunctionCall(
485                Cow::Borrowed("union"),
486                vec![
487                    Expr::Name(Cow::Owned(test1_oid.to_string())),
488                    Expr::Name(Cow::Owned(test2_oid.to_string())),
489                ],
490            );
491            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
492            Ok(
493                [
494                    Commit {
495                        inner: Commit {
496                            id: 62fc20d2a290daea0d52bdc2ed2ad4be6491010e,
497                            summary: "create test1.txt",
498                        },
499                    },
500                    Commit {
501                        inner: Commit {
502                            id: 96d1c37a3d4363611c49f7e52186e189a04c531f,
503                            summary: "create test2.txt",
504                        },
505                    },
506                ],
507            )
508            "###);
509        }
510
511        {
512            let expr = Expr::FunctionCall(
513                Cow::Borrowed("intersection"),
514                vec![
515                    Expr::Name(Cow::Owned(test1_oid.to_string())),
516                    Expr::Name(Cow::Owned(test2_oid.to_string())),
517                ],
518            );
519            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
520            Ok(
521                [],
522            )
523            "###);
524
525            let expr = Expr::FunctionCall(
526                Cow::Borrowed("intersection"),
527                vec![
528                    Expr::Name(Cow::Owned(test1_oid.to_string())),
529                    Expr::Name(Cow::Owned(test1_oid.to_string())),
530                ],
531            );
532            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
533            Ok(
534                [
535                    Commit {
536                        inner: Commit {
537                            id: 62fc20d2a290daea0d52bdc2ed2ad4be6491010e,
538                            summary: "create test1.txt",
539                        },
540                    },
541                ],
542            )
543            "###);
544        }
545
546        {
547            let expr = Expr::FunctionCall(
548                Cow::Borrowed("difference"),
549                vec![
550                    Expr::Name(Cow::Owned(test1_oid.to_string())),
551                    Expr::Name(Cow::Owned(test2_oid.to_string())),
552                ],
553            );
554            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
555            Ok(
556                [
557                    Commit {
558                        inner: Commit {
559                            id: 62fc20d2a290daea0d52bdc2ed2ad4be6491010e,
560                            summary: "create test1.txt",
561                        },
562                    },
563                ],
564            )
565            "###);
566
567            let expr = Expr::FunctionCall(
568                Cow::Borrowed("difference"),
569                vec![
570                    Expr::Name(Cow::Owned(test1_oid.to_string())),
571                    Expr::Name(Cow::Owned(test1_oid.to_string())),
572                ],
573            );
574            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
575            Ok(
576                [],
577            )
578            "###);
579        }
580
581        {
582            let expr = Expr::FunctionCall(
583                Cow::Borrowed("siblings"),
584                vec![Expr::Name(Cow::Owned(test2_oid.to_string()))],
585            );
586            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
587            Ok(
588                [
589                    Commit {
590                        inner: Commit {
591                            id: bf0d52a607f693201512a43b6b5a70b2a275e0ad,
592                            summary: "create test4.txt",
593                        },
594                    },
595                ],
596            )
597            "###);
598        }
599
600        {
601            let expr = Expr::FunctionCall(Cow::Borrowed("stack"), vec![]);
602            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
603            Ok(
604                [
605                    Commit {
606                        inner: Commit {
607                            id: 848121cb21bf9af8b064c91bc8930bd16d624a22,
608                            summary: "create test5.txt",
609                        },
610                    },
611                    Commit {
612                        inner: Commit {
613                            id: f0abf649939928fe5475179fd84e738d3d3725dc,
614                            summary: "create test6.txt",
615                        },
616                    },
617                    Commit {
618                        inner: Commit {
619                            id: ba07500a4adc661dc06a748d200ef92120e1b355,
620                            summary: "create test7.txt",
621                        },
622                    },
623                ],
624            )
625            "###);
626        }
627
628        {
629            let expr = Expr::FunctionCall(Cow::Borrowed("main"), vec![]);
630            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
631            Ok(
632                [
633                    Commit {
634                        inner: Commit {
635                            id: bf0d52a607f693201512a43b6b5a70b2a275e0ad,
636                            summary: "create test4.txt",
637                        },
638                    },
639                ],
640            )
641            "###);
642        }
643
644        {
645            let expr = Expr::FunctionCall(Cow::Borrowed("public"), vec![]);
646            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
647            Ok(
648                [
649                    Commit {
650                        inner: Commit {
651                            id: f777ecc9b0db5ed372b2615695191a8a17f79f24,
652                            summary: "create initial.txt",
653                        },
654                    },
655                    Commit {
656                        inner: Commit {
657                            id: 62fc20d2a290daea0d52bdc2ed2ad4be6491010e,
658                            summary: "create test1.txt",
659                        },
660                    },
661                    Commit {
662                        inner: Commit {
663                            id: bf0d52a607f693201512a43b6b5a70b2a275e0ad,
664                            summary: "create test4.txt",
665                        },
666                    },
667                ],
668            )
669            "###);
670        }
671
672        {
673            let expr = Expr::FunctionCall(
674                Cow::Borrowed("stack"),
675                vec![Expr::Name(Cow::Owned(test2_oid.to_string()))],
676            );
677            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
678            Ok(
679                [
680                    Commit {
681                        inner: Commit {
682                            id: 96d1c37a3d4363611c49f7e52186e189a04c531f,
683                            summary: "create test2.txt",
684                        },
685                    },
686                    Commit {
687                        inner: Commit {
688                            id: 70deb1e28791d8e7dd5a1f0c871a51b91282562f,
689                            summary: "create test3.txt",
690                        },
691                    },
692                ],
693            )
694            "###);
695        }
696
697        {
698            let expr = Expr::FunctionCall(Cow::Borrowed("draft"), vec![]);
699            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
700            Ok(
701                [
702                    Commit {
703                        inner: Commit {
704                            id: 96d1c37a3d4363611c49f7e52186e189a04c531f,
705                            summary: "create test2.txt",
706                        },
707                    },
708                    Commit {
709                        inner: Commit {
710                            id: 70deb1e28791d8e7dd5a1f0c871a51b91282562f,
711                            summary: "create test3.txt",
712                        },
713                    },
714                    Commit {
715                        inner: Commit {
716                            id: 848121cb21bf9af8b064c91bc8930bd16d624a22,
717                            summary: "create test5.txt",
718                        },
719                    },
720                    Commit {
721                        inner: Commit {
722                            id: f0abf649939928fe5475179fd84e738d3d3725dc,
723                            summary: "create test6.txt",
724                        },
725                    },
726                    Commit {
727                        inner: Commit {
728                            id: ba07500a4adc661dc06a748d200ef92120e1b355,
729                            summary: "create test7.txt",
730                        },
731                    },
732                ],
733            )
734            "###);
735        }
736
737        {
738            let expr = Expr::FunctionCall(
739                Cow::Borrowed("not"),
740                vec![Expr::FunctionCall(Cow::Borrowed("draft"), vec![])],
741            );
742            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
743            Ok(
744                [
745                    Commit {
746                        inner: Commit {
747                            id: f777ecc9b0db5ed372b2615695191a8a17f79f24,
748                            summary: "create initial.txt",
749                        },
750                    },
751                    Commit {
752                        inner: Commit {
753                            id: 62fc20d2a290daea0d52bdc2ed2ad4be6491010e,
754                            summary: "create test1.txt",
755                        },
756                    },
757                    Commit {
758                        inner: Commit {
759                            id: bf0d52a607f693201512a43b6b5a70b2a275e0ad,
760                            summary: "create test4.txt",
761                        },
762                    },
763                ],
764            )
765            "###);
766        }
767
768        {
769            let expr = Expr::FunctionCall(
770                Cow::Borrowed("parents.nth"),
771                vec![
772                    Expr::Name(Cow::Owned(test7_oid.to_string())),
773                    Expr::Name(Cow::Borrowed("1")),
774                ],
775            );
776            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
777            Ok(
778                [
779                    Commit {
780                        inner: Commit {
781                            id: 848121cb21bf9af8b064c91bc8930bd16d624a22,
782                            summary: "create test5.txt",
783                        },
784                    },
785                ],
786            )
787            "###);
788        }
789
790        {
791            let expr = Expr::FunctionCall(
792                Cow::Borrowed("ancestors.nth"),
793                vec![
794                    Expr::Name(Cow::Owned(test7_oid.to_string())),
795                    Expr::Name(Cow::Borrowed("2")),
796                ],
797            );
798            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
799            Ok(
800                [
801                    Commit {
802                        inner: Commit {
803                            id: bf0d52a607f693201512a43b6b5a70b2a275e0ad,
804                            summary: "create test4.txt",
805                        },
806                    },
807                ],
808            )
809            "###);
810        }
811
812        {
813            let expr = Expr::FunctionCall(
814                Cow::Borrowed("message"),
815                vec![Expr::Name(Cow::Borrowed("test4"))],
816            );
817            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
818            Ok(
819                [
820                    Commit {
821                        inner: Commit {
822                            id: bf0d52a607f693201512a43b6b5a70b2a275e0ad,
823                            summary: "create test4.txt",
824                        },
825                    },
826                ],
827            )
828            "###);
829        }
830
831        {
832            let expr = Expr::FunctionCall(
833                Cow::Borrowed("message"),
834                vec![Expr::Name(Cow::Borrowed("exact:create test4.txt"))],
835            );
836            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
837            Ok(
838                [
839                    Commit {
840                        inner: Commit {
841                            id: bf0d52a607f693201512a43b6b5a70b2a275e0ad,
842                            summary: "create test4.txt",
843                        },
844                    },
845                ],
846            )
847            "###);
848        }
849
850        {
851            let expr = Expr::FunctionCall(
852                Cow::Borrowed("message"),
853                vec![Expr::Name(Cow::Borrowed("regex:^create test4.txt$"))],
854            );
855            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
856            Ok(
857                [
858                    Commit {
859                        inner: Commit {
860                            id: bf0d52a607f693201512a43b6b5a70b2a275e0ad,
861                            summary: "create test4.txt",
862                        },
863                    },
864                ],
865            )
866            "###);
867        }
868
869        {
870            let expr = Expr::FunctionCall(
871                Cow::Borrowed("paths.changed"),
872                vec![Expr::Name(Cow::Borrowed("glob:test[1-3].txt"))],
873            );
874            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
875            Ok(
876                [
877                    Commit {
878                        inner: Commit {
879                            id: 62fc20d2a290daea0d52bdc2ed2ad4be6491010e,
880                            summary: "create test1.txt",
881                        },
882                    },
883                    Commit {
884                        inner: Commit {
885                            id: 96d1c37a3d4363611c49f7e52186e189a04c531f,
886                            summary: "create test2.txt",
887                        },
888                    },
889                    Commit {
890                        inner: Commit {
891                            id: 70deb1e28791d8e7dd5a1f0c871a51b91282562f,
892                            summary: "create test3.txt",
893                        },
894                    },
895                ],
896            )
897            "###);
898        }
899
900        {
901            let expr = Expr::FunctionCall(
902                Cow::Borrowed("exactly"),
903                vec![
904                    Expr::FunctionCall(Cow::Borrowed("stack"), vec![]),
905                    Expr::Name(Cow::Borrowed("3")),
906                ],
907            );
908            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
909            Ok(
910                [
911                    Commit {
912                        inner: Commit {
913                            id: 848121cb21bf9af8b064c91bc8930bd16d624a22,
914                            summary: "create test5.txt",
915                        },
916                    },
917                    Commit {
918                        inner: Commit {
919                            id: f0abf649939928fe5475179fd84e738d3d3725dc,
920                            summary: "create test6.txt",
921                        },
922                    },
923                    Commit {
924                        inner: Commit {
925                            id: ba07500a4adc661dc06a748d200ef92120e1b355,
926                            summary: "create test7.txt",
927                        },
928                    },
929                ],
930            )
931            "###);
932        }
933
934        {
935            let expr = Expr::FunctionCall(
936                Cow::Borrowed("exactly"),
937                vec![
938                    Expr::FunctionCall(Cow::Borrowed("stack"), vec![]),
939                    Expr::Name(Cow::Borrowed("2")),
940                ],
941            );
942            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
943            Err(
944                UnexpectedSetLength {
945                    expr: "stack()",
946                    expected_len: 2,
947                    actual_len: 3,
948                },
949            )
950            "###);
951        }
952
953        Ok(())
954    }
955
956    #[test]
957    fn test_eval_author_committer() -> eyre::Result<()> {
958        let git = make_git()?;
959        git.init_repo()?;
960
961        git.write_file_txt("test1", "test\n")?;
962        git.run(&["add", "test1.txt"])?;
963        git.run_with_options(
964            &["commit", "-m", "test1"],
965            &GitRunOptions {
966                env: {
967                    [
968                        ("GIT_AUTHOR_NAME", "Foo"),
969                        ("GIT_AUTHOR_EMAIL", "foo@example.com"),
970                        ("GIT_COMMITTER_NAME", "Bar"),
971                        ("GIT_COMMITTER_EMAIL", "bar@example.com"),
972                    ]
973                    .iter()
974                    .map(|(k, v)| (k.to_string(), v.to_string()))
975                    .collect()
976                },
977                ..Default::default()
978            },
979        )?;
980
981        git.write_file_txt("test2", "test\n")?;
982        git.run(&["add", "test2.txt"])?;
983        git.run_with_options(
984            &["commit", "-m", "test2"],
985            &GitRunOptions {
986                env: {
987                    [
988                        ("GIT_AUTHOR_NAME", "Bar"),
989                        ("GIT_AUTHOR_EMAIL", "bar@example.com"),
990                        ("GIT_COMMITTER_NAME", "Foo"),
991                        ("GIT_COMMITTER_EMAIL", "foo@example.com"),
992                    ]
993                    .iter()
994                    .map(|(k, v)| (k.to_string(), v.to_string()))
995                    .collect()
996                },
997                ..Default::default()
998            },
999        )?;
1000
1001        let effects = Effects::new_suppress_for_test(Glyphs::text());
1002        let repo = git.get_repo()?;
1003        let conn = repo.get_db_conn()?;
1004        let event_log_db = EventLogDb::new(&conn)?;
1005        let event_replayer = EventReplayer::from_event_log_db(&effects, &repo, &event_log_db)?;
1006        let event_cursor = event_replayer.make_default_cursor();
1007        let references_snapshot = repo.get_references_snapshot()?;
1008        let mut dag = Dag::open_and_sync(
1009            &effects,
1010            &repo,
1011            &event_replayer,
1012            event_cursor,
1013            &references_snapshot,
1014        )?;
1015
1016        {
1017            let expr = Expr::FunctionCall(
1018                Cow::Borrowed("author.name"),
1019                vec![Expr::Name(Cow::Borrowed("Foo"))],
1020            );
1021            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
1022            Ok(
1023                [
1024                    Commit {
1025                        inner: Commit {
1026                            id: 9ee1994c0737c221efc07acd8d73590d336ee46d,
1027                            summary: "test1",
1028                        },
1029                    },
1030                ],
1031            )
1032            "###);
1033        }
1034
1035        {
1036            let expr = Expr::FunctionCall(
1037                Cow::Borrowed("author.email"),
1038                vec![Expr::Name(Cow::Borrowed("foo"))],
1039            );
1040            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
1041            Ok(
1042                [
1043                    Commit {
1044                        inner: Commit {
1045                            id: 9ee1994c0737c221efc07acd8d73590d336ee46d,
1046                            summary: "test1",
1047                        },
1048                    },
1049                ],
1050            )
1051            "###);
1052        }
1053
1054        {
1055            let expr = Expr::FunctionCall(
1056                Cow::Borrowed("author.date"),
1057                vec![Expr::Name(Cow::Borrowed("before:today"))],
1058            );
1059            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
1060            Ok(
1061                [
1062                    Commit {
1063                        inner: Commit {
1064                            id: f777ecc9b0db5ed372b2615695191a8a17f79f24,
1065                            summary: "create initial.txt",
1066                        },
1067                    },
1068                    Commit {
1069                        inner: Commit {
1070                            id: 9ee1994c0737c221efc07acd8d73590d336ee46d,
1071                            summary: "test1",
1072                        },
1073                    },
1074                    Commit {
1075                        inner: Commit {
1076                            id: 05ff2fc6b3e7917ac6800b18077c211e173e8fb4,
1077                            summary: "test2",
1078                        },
1079                    },
1080                ],
1081            )
1082            "###);
1083        }
1084
1085        {
1086            let expr = Expr::FunctionCall(
1087                Cow::Borrowed("author.date"),
1088                vec![Expr::Name(Cow::Borrowed("after:yesterday"))],
1089            );
1090            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
1091            Ok(
1092                [],
1093            )
1094            "###);
1095        }
1096
1097        {
1098            let expr = Expr::FunctionCall(
1099                Cow::Borrowed("committer.name"),
1100                vec![Expr::Name(Cow::Borrowed("Foo"))],
1101            );
1102            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
1103            Ok(
1104                [
1105                    Commit {
1106                        inner: Commit {
1107                            id: 05ff2fc6b3e7917ac6800b18077c211e173e8fb4,
1108                            summary: "test2",
1109                        },
1110                    },
1111                ],
1112            )
1113            "###);
1114        }
1115
1116        {
1117            let expr = Expr::FunctionCall(
1118                Cow::Borrowed("committer.email"),
1119                vec![Expr::Name(Cow::Borrowed("foo"))],
1120            );
1121            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
1122            Ok(
1123                [
1124                    Commit {
1125                        inner: Commit {
1126                            id: 05ff2fc6b3e7917ac6800b18077c211e173e8fb4,
1127                            summary: "test2",
1128                        },
1129                    },
1130                ],
1131            )
1132            "###);
1133        }
1134
1135        {
1136            let expr = Expr::FunctionCall(
1137                Cow::Borrowed("committer.date"),
1138                vec![Expr::Name(Cow::Borrowed("before:today"))],
1139            );
1140            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
1141            Ok(
1142                [
1143                    Commit {
1144                        inner: Commit {
1145                            id: f777ecc9b0db5ed372b2615695191a8a17f79f24,
1146                            summary: "create initial.txt",
1147                        },
1148                    },
1149                    Commit {
1150                        inner: Commit {
1151                            id: 9ee1994c0737c221efc07acd8d73590d336ee46d,
1152                            summary: "test1",
1153                        },
1154                    },
1155                    Commit {
1156                        inner: Commit {
1157                            id: 05ff2fc6b3e7917ac6800b18077c211e173e8fb4,
1158                            summary: "test2",
1159                        },
1160                    },
1161                ],
1162            )
1163            "###);
1164        }
1165
1166        {
1167            let expr = Expr::FunctionCall(
1168                Cow::Borrowed("committer.date"),
1169                vec![Expr::Name(Cow::Borrowed("after:yesterday"))],
1170            );
1171            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
1172            Ok(
1173                [],
1174            )
1175            "###);
1176        }
1177
1178        Ok(())
1179    }
1180
1181    #[test]
1182    fn test_eval_current() -> eyre::Result<()> {
1183        let git = make_git()?;
1184        git.init_repo()?;
1185
1186        git.detach_head()?;
1187        let test1_oid = git.commit_file("test1", 1)?;
1188        let _test2_oid = git.commit_file("test2", 2)?;
1189        let test3_oid = git.commit_file("test3", 3)?;
1190        let test4_oid = git.commit_file("test4", 4)?;
1191
1192        git.branchless(
1193            "move",
1194            &["-s", &test3_oid.to_string(), "-d", &test1_oid.to_string()],
1195        )?;
1196        git.branchless("reword", &["-m", "test4 has been rewritten twice"])?;
1197
1198        let effects = Effects::new_suppress_for_test(Glyphs::text());
1199        let repo = git.get_repo()?;
1200        let conn = repo.get_db_conn()?;
1201        let event_log_db = EventLogDb::new(&conn)?;
1202        let event_replayer = EventReplayer::from_event_log_db(&effects, &repo, &event_log_db)?;
1203        let event_cursor = event_replayer.make_default_cursor();
1204        let references_snapshot = repo.get_references_snapshot()?;
1205        let mut dag = Dag::open_and_sync(
1206            &effects,
1207            &repo,
1208            &event_replayer,
1209            event_cursor,
1210            &references_snapshot,
1211        )?;
1212
1213        {
1214            let original_test3_oid = &test3_oid.to_string();
1215            let original_test4_oid = &test4_oid.to_string();
1216
1217            let expr = Expr::FunctionCall(
1218                Cow::Borrowed("union"),
1219                vec![
1220                    Expr::Name(Cow::Borrowed(original_test3_oid)),
1221                    Expr::Name(Cow::Borrowed(original_test4_oid)),
1222                ],
1223            );
1224            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
1225            Ok(
1226                [],
1227            )
1228            "###);
1229
1230            let expr = Expr::FunctionCall(
1231                Cow::Borrowed("current"),
1232                vec![Expr::FunctionCall(
1233                    Cow::Borrowed("union"),
1234                    vec![
1235                        Expr::Name(Cow::Borrowed(original_test3_oid)),
1236                        Expr::Name(Cow::Borrowed(original_test4_oid)),
1237                    ],
1238                )],
1239            );
1240            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
1241            Ok(
1242                [
1243                    Commit {
1244                        inner: Commit {
1245                            id: 4838e49b08954becdd17c0900c1179c2c654c627,
1246                            summary: "create test3.txt",
1247                        },
1248                    },
1249                    Commit {
1250                        inner: Commit {
1251                            id: 619162078182d2c6d80ff604b81e7c2afc3295b7,
1252                            summary: "test4 has been rewritten twice",
1253                        },
1254                    },
1255                ],
1256            )
1257            "###);
1258        }
1259        Ok(())
1260    }
1261
1262    #[test]
1263    fn test_eval_merges() -> eyre::Result<()> {
1264        let git = make_git()?;
1265        git.init_repo()?;
1266
1267        git.detach_head()?;
1268        let test1_oid = git.commit_file("test1", 1)?;
1269        let test2_oid = git.commit_file("test2", 2)?;
1270        git.run(&["checkout", "HEAD~2"])?;
1271        git.run(&["merge", "--no-ff", &test1_oid.to_string()])?;
1272        git.run(&["merge", "--no-ff", &test2_oid.to_string()])?;
1273
1274        let effects = Effects::new_suppress_for_test(Glyphs::text());
1275        let repo = git.get_repo()?;
1276        let conn = repo.get_db_conn()?;
1277        let event_log_db = EventLogDb::new(&conn)?;
1278        let event_replayer = EventReplayer::from_event_log_db(&effects, &repo, &event_log_db)?;
1279        let event_cursor = event_replayer.make_default_cursor();
1280        let references_snapshot = repo.get_references_snapshot()?;
1281        let mut dag = Dag::open_and_sync(
1282            &effects,
1283            &repo,
1284            &event_replayer,
1285            event_cursor,
1286            &references_snapshot,
1287        )?;
1288
1289        {
1290            let expr = Expr::FunctionCall(Cow::Borrowed("merges"), vec![]);
1291            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
1292            Ok(
1293                [
1294                    Commit {
1295                        inner: Commit {
1296                            id: f486a8b756cd3a928241576aa87827284f3e14d1,
1297                            summary: "Merge commit '62fc20d2a290daea0d52bdc2ed2ad4be6491010e' into HEAD",
1298                        },
1299                    },
1300                    Commit {
1301                        inner: Commit {
1302                            id: 0b75bdca271fdc188e68bca6e054013bbc2a373c,
1303                            summary: "Merge commit '96d1c37a3d4363611c49f7e52186e189a04c531f' into HEAD",
1304                        },
1305                    },
1306                ],
1307            )
1308            "###);
1309
1310            let expr = Expr::FunctionCall(
1311                Cow::Borrowed("not"),
1312                vec![Expr::FunctionCall(Cow::Borrowed("merges"), vec![])],
1313            );
1314            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
1315            Ok(
1316                [
1317                    Commit {
1318                        inner: Commit {
1319                            id: f777ecc9b0db5ed372b2615695191a8a17f79f24,
1320                            summary: "create initial.txt",
1321                        },
1322                    },
1323                    Commit {
1324                        inner: Commit {
1325                            id: 62fc20d2a290daea0d52bdc2ed2ad4be6491010e,
1326                            summary: "create test1.txt",
1327                        },
1328                    },
1329                    Commit {
1330                        inner: Commit {
1331                            id: 96d1c37a3d4363611c49f7e52186e189a04c531f,
1332                            summary: "create test2.txt",
1333                        },
1334                    },
1335                ],
1336            )
1337            "###);
1338        }
1339        Ok(())
1340    }
1341
1342    #[test]
1343    fn test_eval_branches_with_pattern() -> eyre::Result<()> {
1344        let git = make_git()?;
1345        git.init_repo()?;
1346
1347        git.detach_head()?;
1348        git.commit_file("test1", 1)?;
1349        git.run(&["branch", "test-1"])?;
1350        git.commit_file("test2", 2)?;
1351        git.run(&["branch", "test-2"])?;
1352
1353        let effects = Effects::new_suppress_for_test(Glyphs::text());
1354        let repo = git.get_repo()?;
1355        let conn = repo.get_db_conn()?;
1356        let event_log_db = EventLogDb::new(&conn)?;
1357        let event_replayer = EventReplayer::from_event_log_db(&effects, &repo, &event_log_db)?;
1358        let event_cursor = event_replayer.make_default_cursor();
1359        let references_snapshot = repo.get_references_snapshot()?;
1360        let mut dag = Dag::open_and_sync(
1361            &effects,
1362            &repo,
1363            &event_replayer,
1364            event_cursor,
1365            &references_snapshot,
1366        )?;
1367
1368        {
1369            let expr = Expr::FunctionCall(Cow::Borrowed("branches"), vec![]);
1370            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
1371            Ok(
1372                [
1373                    Commit {
1374                        inner: Commit {
1375                            id: f777ecc9b0db5ed372b2615695191a8a17f79f24,
1376                            summary: "create initial.txt",
1377                        },
1378                    },
1379                    Commit {
1380                        inner: Commit {
1381                            id: 62fc20d2a290daea0d52bdc2ed2ad4be6491010e,
1382                            summary: "create test1.txt",
1383                        },
1384                    },
1385                    Commit {
1386                        inner: Commit {
1387                            id: 96d1c37a3d4363611c49f7e52186e189a04c531f,
1388                            summary: "create test2.txt",
1389                        },
1390                    },
1391                ],
1392            )
1393            "###);
1394
1395            let expr = Expr::FunctionCall(
1396                Cow::Borrowed("branches"),
1397                vec![Expr::Name(Cow::Borrowed("glob:test*"))],
1398            );
1399            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
1400            Ok(
1401                [
1402                    Commit {
1403                        inner: Commit {
1404                            id: 62fc20d2a290daea0d52bdc2ed2ad4be6491010e,
1405                            summary: "create test1.txt",
1406                        },
1407                    },
1408                    Commit {
1409                        inner: Commit {
1410                            id: 96d1c37a3d4363611c49f7e52186e189a04c531f,
1411                            summary: "create test2.txt",
1412                        },
1413                    },
1414                ],
1415            )
1416            "###);
1417        }
1418        Ok(())
1419    }
1420
1421    #[test]
1422    fn test_eval_aliases() -> eyre::Result<()> {
1423        let git = make_git()?;
1424        git.init_repo()?;
1425
1426        git.detach_head()?;
1427        let _test1_oid = git.commit_file("test1", 1)?;
1428        let _test2_oid = git.commit_file("test2", 2)?;
1429        let _test3_oid = git.commit_file("test3", 3)?;
1430
1431        let effects = Effects::new_suppress_for_test(Glyphs::text());
1432        let repo = git.get_repo()?;
1433        let conn = repo.get_db_conn()?;
1434        let event_log_db = EventLogDb::new(&conn)?;
1435        let event_replayer = EventReplayer::from_event_log_db(&effects, &repo, &event_log_db)?;
1436        let event_cursor = event_replayer.make_default_cursor();
1437        let references_snapshot = repo.get_references_snapshot()?;
1438        let mut dag = Dag::open_and_sync(
1439            &effects,
1440            &repo,
1441            &event_replayer,
1442            event_cursor,
1443            &references_snapshot,
1444        )?;
1445
1446        {
1447            git.run(&[
1448                "config",
1449                "branchless.revsets.alias.simpleAlias",
1450                "roots($1)",
1451            ])?;
1452
1453            let expr = Expr::FunctionCall(
1454                Cow::Borrowed("simpleAlias"),
1455                vec![Expr::FunctionCall(Cow::Borrowed("stack"), vec![])],
1456            );
1457            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
1458            Ok(
1459                [
1460                    Commit {
1461                        inner: Commit {
1462                            id: 62fc20d2a290daea0d52bdc2ed2ad4be6491010e,
1463                            summary: "create test1.txt",
1464                        },
1465                    },
1466                ],
1467            )
1468            "###);
1469        }
1470
1471        {
1472            git.run(&[
1473                "config",
1474                "branchless.revsets.alias.complexAlias",
1475                "children($1) & parents($1)",
1476            ])?;
1477
1478            let expr = Expr::FunctionCall(
1479                Cow::Borrowed("complexAlias"),
1480                vec![Expr::FunctionCall(Cow::Borrowed("stack"), vec![])],
1481            );
1482            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
1483            Ok(
1484                [
1485                    Commit {
1486                        inner: Commit {
1487                            id: 96d1c37a3d4363611c49f7e52186e189a04c531f,
1488                            summary: "create test2.txt",
1489                        },
1490                    },
1491                ],
1492            )
1493            "###);
1494        }
1495
1496        {
1497            git.run(&["config", "branchless.revsets.alias.parseError", "foo("])?;
1498
1499            let (_stdout, _stderr) = git.branchless_with_options(
1500                "query",
1501                &["parseError()"],
1502                &GitRunOptions {
1503                    expected_exit_code: 1,
1504                    ..Default::default()
1505                },
1506            )?;
1507            insta::assert_snapshot!(_stderr, @r###"
1508            Evaluation error for expression 'parseError()': failed to parse alias expression 'foo('
1509            parse error: Unrecognized EOF found at 4
1510            Expected one of a commit/branch/tag, a string literal, "(", ")", "..", ":" or "::"
1511            "###);
1512        }
1513
1514        {
1515            // Check for macro hygiene: arguments from outer nested aliases
1516            // should not be available inside inner aliases.
1517            //
1518            // 1. User input: `outerAlias(a, b)` (2 arguments provided)
1519            // 2. Expands to: `innerAlias(a)` (only uses 1 arg)
1520            // 3. Expands to: `builtin(a, $2)` (uses 2 args)
1521            //
1522            // In this case, there is no $2 available for step 3, so we want to
1523            // ensure that $2 is not resolved from step 1 and that it instead
1524            // fails.
1525            git.run(&[
1526                "config",
1527                "branchless.revsets.alias.outerAlias",
1528                "innerAlias($1)",
1529            ])?;
1530
1531            git.run(&[
1532                "config",
1533                "branchless.revsets.alias.innerAlias",
1534                "intersection($1, $2)",
1535            ])?;
1536
1537            let expr = Expr::FunctionCall(
1538                Cow::Borrowed("outerAlias"),
1539                vec![
1540                    Expr::FunctionCall(Cow::Borrowed("stack"), vec![]),
1541                    Expr::FunctionCall(Cow::Borrowed("nonsense"), vec![]),
1542                ],
1543            );
1544            insta::assert_debug_snapshot!(eval_and_sort(&effects, &repo, &mut dag, &expr), @r###"
1545            Err(
1546                UnboundName {
1547                    name: "$2",
1548                },
1549            )
1550            "###);
1551        }
1552
1553        Ok(())
1554    }
1555}