1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
use crate::{bstr::BString, object, reference};

/// A hint to know what to do if refs and object names are equal.
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
pub enum RefsHint {
    /// This is the default, and leads to specs that look like objects identified by full hex sha and are objects to be used
    /// instead of similarly named references. The latter is not typical but can absolutely happen by accident.
    /// If the object prefix is shorter than the maximum hash length of the repository, use the reference instead, which is
    /// preferred as there are many valid object names like `beef` and `cafe` that are short and both valid and typical prefixes
    /// for objects.
    /// Git chooses this as default as well, even though it means that every object prefix is also looked up as ref.
    #[default]
    PreferObjectOnFullLengthHexShaUseRefOtherwise,
    /// No matter what, if it looks like an object prefix and has an object, use it.
    /// Note that no ref-lookup is made here which is the fastest option.
    PreferObject,
    /// When an object is found for a given prefix, also check if a reference exists with that name and if it does,
    /// use that moving forward.
    PreferRef,
    /// If there is an ambiguous situation, instead of silently choosing one over the other, fail instead.
    Fail,
}

/// A hint to know which object kind to prefer if multiple objects match a prefix.
///
/// This disambiguation mechanism is applied only if there is no disambiguation hints in the spec itself.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ObjectKindHint {
    /// Pick objects that are commits themselves.
    Commit,
    /// Pick objects that can be peeled into a commit, i.e. commits themselves or tags which are peeled until a commit is found.
    Committish,
    /// Pick objects that are trees themselves.
    Tree,
    /// Pick objects that can be peeled into a tree, i.e. trees themselves or tags which are peeled until a tree is found or commits
    /// whose tree is chosen.
    Treeish,
    /// Pick objects that are blobs.
    Blob,
}

/// Options for use in [`revision::Spec::from_bstr()`][crate::revision::Spec::from_bstr()].
#[derive(Debug, Default, Copy, Clone)]
pub struct Options {
    /// What to do if both refs and object names match the same input.
    pub refs_hint: RefsHint,
    /// The hint to use when encountering multiple object matching a prefix.
    ///
    /// If `None`, the rev-spec itself must disambiguate the object by drilling down to desired kinds or applying
    /// other disambiguating transformations.
    pub object_kind_hint: Option<ObjectKindHint>,
}

/// The error returned by [`crate::Repository::rev_parse()`].
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
    #[error("The rev-spec is malformed and misses a ref name")]
    Malformed,
    #[error("Unborn heads do not have a reflog yet")]
    UnbornHeadsHaveNoRefLog,
    #[error("This feature will be implemented once {dependency}")]
    Planned { dependency: &'static str },
    #[error("Reference {reference:?} does not have a reference log, cannot {action}")]
    MissingRefLog { reference: BString, action: &'static str },
    #[error("HEAD has {available} prior checkouts and checkout number {desired} is out of range")]
    PriorCheckoutOutOfRange { desired: usize, available: usize },
    #[error("Reference {:?} has {available} ref-log entries and entry number {desired} is out of range", reference.name.as_bstr())]
    RefLogEntryOutOfRange {
        reference: gix_ref::Reference,
        desired: usize,
        available: usize,
    },
    #[error(
        "Commit {oid} has {available} ancestors along the first parent and ancestor number {desired} is out of range"
    )]
    AncestorOutOfRange {
        oid: gix_hash::Prefix,
        desired: usize,
        available: usize,
    },
    #[error("Commit {oid} has {available} parents and parent number {desired} is out of range")]
    ParentOutOfRange {
        oid: gix_hash::Prefix,
        desired: usize,
        available: usize,
    },
    #[error("Path {desired_path:?} did not exist in index at stage {desired_stage}{}{}", stage_hint.map(|actual|format!(". It does exist at stage {actual}")).unwrap_or_default(), exists.then(|| ". It exists on disk").unwrap_or(". It does not exist on disk"))]
    IndexLookup {
        desired_path: BString,
        desired_stage: gix_index::entry::Stage,
        stage_hint: Option<gix_index::entry::Stage>,
        exists: bool,
    },
    #[error(transparent)]
    FindHead(#[from] reference::find::existing::Error),
    #[error(transparent)]
    Index(#[from] crate::worktree::open_index::Error),
    #[error(transparent)]
    RevWalkIterInit(#[from] crate::reference::iter::init::Error),
    #[error(transparent)]
    RevWalkAllReferences(#[from] gix_ref::packed::buffer::open::Error),
    #[cfg(feature = "revparse-regex")]
    #[error(transparent)]
    InvalidRegex(#[from] regex::Error),
    #[cfg_attr(
        feature = "revparse-regex",
        error("None of {commits_searched} commits from {oid} matched regex {regex:?}")
    )]
    #[cfg_attr(
        not(feature = "revparse-regex"),
        error("None of {commits_searched} commits from {oid} matched text {regex:?}")
    )]
    NoRegexMatch {
        regex: BString,
        oid: gix_hash::Prefix,
        commits_searched: usize,
    },
    #[cfg_attr(
        feature = "revparse-regex",
        error("None of {commits_searched} commits reached from all references matched regex {regex:?}")
    )]
    #[cfg_attr(
        not(feature = "revparse-regex"),
        error("None of {commits_searched} commits reached from all references matched text {regex:?}")
    )]
    NoRegexMatchAllRefs { regex: BString, commits_searched: usize },
    #[error(
    "The short hash {prefix} matched both the reference {} and at least one object", reference.name)]
    AmbiguousRefAndObject {
        /// The prefix to look for.
        prefix: gix_hash::Prefix,
        /// The reference matching the prefix.
        reference: gix_ref::Reference,
    },
    #[error(transparent)]
    IdFromHex(#[from] gix_hash::decode::Error),
    #[error(transparent)]
    FindReference(#[from] gix_ref::file::find::existing::Error),
    #[error(transparent)]
    FindObject(#[from] object::find::existing::Error),
    #[error(transparent)]
    LookupPrefix(#[from] gix_odb::store::prefix::lookup::Error),
    #[error(transparent)]
    PeelToKind(#[from] object::peel::to_kind::Error),
    #[error("Object {oid} was a {actual}, but needed it to be a {expected}")]
    ObjectKind {
        oid: gix_hash::Prefix,
        actual: gix_object::Kind,
        expected: gix_object::Kind,
    },
    #[error(transparent)]
    Parse(#[from] gix_revision::spec::parse::Error),
    #[error("An object prefixed {prefix} could not be found")]
    PrefixNotFound { prefix: gix_hash::Prefix },
    #[error("Short id {prefix} is ambiguous. Candidates are:\n{}", info.iter().map(|(oid, info)| format!("\t{oid} {info}")).collect::<Vec<_>>().join("\n"))]
    AmbiguousPrefix {
        prefix: gix_hash::Prefix,
        info: Vec<(gix_hash::Prefix, super::error::CandidateInfo)>,
    },
    #[error("Could not find path {path:?} in tree {tree} of parent object {object}")]
    PathNotFound {
        object: gix_hash::Prefix,
        tree: gix_hash::Prefix,
        path: BString,
    },
    #[error("{current}")]
    Multi {
        current: Box<dyn std::error::Error + Send + Sync + 'static>,
        #[source]
        next: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
    },
    #[error(transparent)]
    Traverse(#[from] gix_traverse::commit::ancestors::Error),
    #[error(transparent)]
    Walk(#[from] crate::revision::walk::Error),
    #[error("Spec does not contain a single object id")]
    SingleNotFound,
}