git_branchless_revset/
resolve.rs

1use std::fmt::Write;
2
3use eyre::WrapErr;
4use git_branchless_opts::{ResolveRevsetOptions, Revset};
5use lib::core::config::get_smartlog_default_revset;
6use lib::core::dag::{CommitSet, Dag};
7use lib::core::effects::Effects;
8use lib::git::Repo;
9use thiserror::Error;
10use tracing::instrument;
11
12use crate::eval::EvalError;
13use crate::parser::ParseError;
14use crate::Expr;
15use crate::{eval, parse};
16
17/// The result of attempting to resolve commits.
18#[allow(clippy::enum_variant_names)]
19#[derive(Debug, Error)]
20pub enum ResolveError {
21    #[error("parse error in {expr:?}: {source}")]
22    ParseError { expr: String, source: ParseError },
23
24    #[error("evaluation error in {expr:?}: {source}")]
25    EvalError { expr: String, source: EvalError },
26
27    #[error("DAG query error: {source}")]
28    DagError { source: eden_dag::Error },
29
30    #[error(transparent)]
31    OtherError { source: eyre::Error },
32}
33
34impl ResolveError {
35    pub fn describe(self, effects: &Effects) -> eyre::Result<()> {
36        match self {
37            ResolveError::ParseError { expr, source } => {
38                writeln!(
39                    effects.get_error_stream(),
40                    "Parse error for expression '{expr}': {source}"
41                )?;
42                Ok(())
43            }
44            ResolveError::EvalError { expr, source } => {
45                writeln!(
46                    effects.get_error_stream(),
47                    "Evaluation error for expression '{expr}': {source}"
48                )?;
49                Ok(())
50            }
51            ResolveError::DagError { source } => Err(source.into()),
52            ResolveError::OtherError { source } => Err(source),
53        }
54    }
55}
56
57/// Check for syntax errors in the provided revsets without actually evaluating them.
58pub fn check_revset_syntax(repo: &Repo, revsets: &[Revset]) -> Result<(), ParseError> {
59    for Revset(revset) in revsets {
60        if let Ok(Some(_)) = repo.revparse_single_commit(revset) {
61            continue;
62        }
63        let _expr: Expr = parse(revset)?;
64    }
65    Ok(())
66}
67
68/// Parse strings which refer to commits, such as:
69///
70/// - Full OIDs.
71/// - Short OIDs.
72/// - Reference names.
73#[instrument]
74pub fn resolve_commits(
75    effects: &Effects,
76    repo: &Repo,
77    dag: &mut Dag,
78    revsets: &[Revset],
79    options: &ResolveRevsetOptions,
80) -> Result<Vec<CommitSet>, ResolveError> {
81    let mut dag_with_obsolete = if options.show_hidden_commits {
82        Some(
83            dag.clear_obsolete_commits(repo)
84                .map_err(|err| ResolveError::OtherError { source: err })?,
85        )
86    } else {
87        None
88    };
89    let dag = dag_with_obsolete.as_mut().unwrap_or(dag);
90
91    let mut commit_sets = Vec::new();
92    for Revset(revset) in revsets {
93        // NB: also update `check_parse_revsets`
94
95        // Handle syntax that's supported by Git, but which we haven't
96        // implemented in the revset language.
97        if let Ok(Some(commit)) = repo.revparse_single_commit(revset) {
98            let commit_set = CommitSet::from(commit.get_oid());
99            dag.sync_from_oids(effects, repo, CommitSet::empty(), commit_set.clone())
100                .map_err(|err| ResolveError::OtherError { source: err })?;
101            commit_sets.push(commit_set);
102            continue;
103        }
104
105        let expr = parse(revset).map_err(|err| ResolveError::ParseError {
106            expr: revset.clone(),
107            source: err,
108        })?;
109        let commits = eval(effects, repo, dag, &expr).map_err(|err| ResolveError::EvalError {
110            expr: revset.clone(),
111            source: err,
112        })?;
113
114        commit_sets.push(commits);
115    }
116    Ok(commit_sets)
117}
118
119/// Resolve the set of commits that would appear in the smartlog by default (if
120/// the user doesn't specify a revset).
121pub fn resolve_default_smartlog_commits(
122    effects: &Effects,
123    repo: &Repo,
124    dag: &mut Dag,
125) -> eyre::Result<CommitSet> {
126    let revset = Revset(get_smartlog_default_revset(repo)?);
127    let results = resolve_commits(
128        effects,
129        repo,
130        dag,
131        &[revset],
132        &ResolveRevsetOptions::default(),
133    )
134    .wrap_err("Resolving default smartlog commits")?;
135    let commits = results.first().unwrap();
136    Ok(commits.clone())
137}