gix/revision/spec/parse/
error.rs

1use std::collections::HashSet;
2
3use gix_hash::ObjectId;
4
5use super::Error;
6use crate::{bstr, bstr::BString, ext::ObjectIdExt, Repository};
7
8/// Additional information about candidates that caused ambiguity.
9#[derive(Debug)]
10pub enum CandidateInfo {
11    /// An error occurred when looking up the object in the database.
12    FindError {
13        /// The reported error.
14        source: crate::object::find::existing::Error,
15    },
16    /// The candidate is an object of the given `kind`.
17    Object {
18        /// The kind of the object.
19        kind: gix_object::Kind,
20    },
21    /// The candidate is a tag.
22    Tag {
23        /// The name of the tag.
24        name: BString,
25    },
26    /// The candidate is a commit.
27    Commit {
28        /// The date of the commit.
29        date: String,
30        /// The subject line.
31        title: BString,
32    },
33}
34
35impl std::fmt::Display for CandidateInfo {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        match self {
38            CandidateInfo::FindError { source } => write!(f, "lookup error: {source}"),
39            CandidateInfo::Tag { name } => write!(f, "tag {name:?}"),
40            CandidateInfo::Object { kind } => std::fmt::Display::fmt(kind, f),
41            CandidateInfo::Commit { date, title } => {
42                write!(
43                    f,
44                    "commit {} {title:?}",
45                    gix_date::parse_header(date)
46                        .unwrap_or_default()
47                        .format(gix_date::time::format::SHORT)
48                )
49            }
50        }
51    }
52}
53
54impl Error {
55    pub(crate) fn ambiguous(candidates: HashSet<ObjectId>, prefix: gix_hash::Prefix, repo: &Repository) -> Self {
56        #[derive(PartialOrd, Ord, Eq, PartialEq, Copy, Clone)]
57        enum Order {
58            Tag,
59            Commit,
60            Tree,
61            Blob,
62            Invalid,
63        }
64        let candidates = {
65            let mut c: Vec<_> = candidates
66                .into_iter()
67                .map(|oid| {
68                    let obj = repo.find_object(oid);
69                    let order = match &obj {
70                        Err(_) => Order::Invalid,
71                        Ok(obj) => match obj.kind {
72                            gix_object::Kind::Tag => Order::Tag,
73                            gix_object::Kind::Commit => Order::Commit,
74                            gix_object::Kind::Tree => Order::Tree,
75                            gix_object::Kind::Blob => Order::Blob,
76                        },
77                    };
78                    (oid, obj, order)
79                })
80                .collect();
81            c.sort_by(|lhs, rhs| lhs.2.cmp(&rhs.2).then_with(|| lhs.0.cmp(&rhs.0)));
82            c
83        };
84        Error::AmbiguousPrefix {
85            prefix,
86            info: candidates
87                .into_iter()
88                .map(|(oid, find_result, _)| {
89                    let info = match find_result {
90                        Ok(obj) => match obj.kind {
91                            gix_object::Kind::Tree | gix_object::Kind::Blob => CandidateInfo::Object { kind: obj.kind },
92                            gix_object::Kind::Tag => {
93                                let tag = obj.to_tag_ref();
94                                CandidateInfo::Tag { name: tag.name.into() }
95                            }
96                            gix_object::Kind::Commit => {
97                                use bstr::ByteSlice;
98                                let commit = obj.to_commit_ref();
99                                CandidateInfo::Commit {
100                                    date: commit.committer().time.trim().into(),
101                                    title: commit.message().title.trim().into(),
102                                }
103                            }
104                        },
105                        Err(err) => CandidateInfo::FindError { source: err },
106                    };
107                    (oid.attach(repo).shorten().unwrap_or_else(|_| oid.into()), info)
108                })
109                .collect(),
110        }
111    }
112
113    pub(crate) fn from_errors(errors: Vec<Self>) -> Self {
114        match errors.len() {
115            0 => unreachable!(
116                "BUG: cannot create something from nothing, must have recorded some errors to call from_errors()"
117            ),
118            1 => errors.into_iter().next().expect("one"),
119            _ => {
120                let mut it = errors.into_iter().rev();
121                let mut recent = Error::Multi {
122                    current: Box::new(it.next().expect("at least one error")),
123                    next: None,
124                };
125                for err in it {
126                    recent = Error::Multi {
127                        current: Box::new(err),
128                        next: Some(Box::new(recent)),
129                    }
130                }
131                recent
132            }
133        }
134    }
135}