gitoxide_core/repository/revision/
explain.rs1use anyhow::bail;
2use gix::{
3 Exn,
4 bstr::{BStr, BString},
5 revision::plumbing::{
6 spec,
7 spec::parse::{
8 Delegate, delegate,
9 delegate::{PeelTo, ReflogLookup, SiblingBranch, Traversal},
10 },
11 },
12};
13
14pub fn explain(spec: std::ffi::OsString, mut out: impl std::io::Write) -> anyhow::Result<()> {
15 let mut explain = Explain::new(&mut out);
16 let spec = gix::path::os_str_into_bstr(&spec)?;
17 gix::revision::plumbing::spec::parse(spec, &mut explain).map_err(gix::Error::from)?;
18 if let Some(err) = explain.err {
19 bail!(err);
20 }
21 Ok(())
22}
23
24struct Explain<'a> {
25 out: &'a mut dyn std::io::Write,
26 call: usize,
27 ref_name: Option<BString>,
28 oid_prefix: Option<gix::hash::Prefix>,
29 has_implicit_anchor: bool,
30 err: Option<String>,
31}
32
33impl<'a> Explain<'a> {
34 fn new(out: &'a mut impl std::io::Write) -> Self {
35 Explain {
36 out,
37 call: 0,
38 ref_name: None,
39 oid_prefix: None,
40 has_implicit_anchor: false,
41 err: None,
42 }
43 }
44 fn prefix(&mut self) -> Result<(), Exn> {
45 self.call += 1;
46 write!(self.out, "{:02}. ", self.call).ok();
47 Ok(())
48 }
49 fn revision_name(&self) -> BString {
50 self.ref_name.clone().unwrap_or_else(|| {
51 self.oid_prefix
52 .expect("parser must have set some object value")
53 .to_string()
54 .into()
55 })
56 }
57}
58
59impl delegate::Revision for Explain<'_> {
60 fn find_ref(&mut self, name: &BStr) -> Result<(), Exn> {
61 self.prefix()?;
62 self.ref_name = Some(name.into());
63 writeln!(self.out, "Lookup the '{name}' reference").ok();
64 Ok(())
65 }
66
67 fn disambiguate_prefix(
68 &mut self,
69 prefix: gix::hash::Prefix,
70 hint: Option<delegate::PrefixHint<'_>>,
71 ) -> Result<(), Exn> {
72 self.prefix()?;
73 self.oid_prefix = Some(prefix);
74 writeln!(
75 self.out,
76 "Disambiguate the '{}' object name ({})",
77 prefix,
78 match hint {
79 None => "any object".to_string(),
80 Some(delegate::PrefixHint::MustBeCommit) => "commit".into(),
81 Some(delegate::PrefixHint::DescribeAnchor { ref_name, generation }) =>
82 format!("commit {generation} generations in future of reference {ref_name:?}"),
83 }
84 )
85 .ok();
86 Ok(())
87 }
88
89 fn reflog(&mut self, query: ReflogLookup) -> Result<(), Exn> {
90 self.prefix()?;
91 self.has_implicit_anchor = true;
92 let ref_name: &BStr = self.ref_name.as_ref().map_or_else(|| "HEAD".into(), AsRef::as_ref);
93 match query {
94 ReflogLookup::Entry(no) => writeln!(self.out, "Find entry {no} in reflog of '{ref_name}' reference").ok(),
95 ReflogLookup::Date(time) => writeln!(
96 self.out,
97 "Find entry closest to time {} in reflog of '{}' reference",
98 time.format_or_unix(gix::date::time::format::ISO8601),
99 ref_name
100 )
101 .ok(),
102 };
103 Ok(())
104 }
105
106 fn nth_checked_out_branch(&mut self, branch_no: usize) -> Result<(), Exn> {
107 self.prefix()?;
108 self.has_implicit_anchor = true;
109 writeln!(self.out, "Find the {branch_no}th checked-out branch of 'HEAD'").ok();
110 Ok(())
111 }
112
113 fn sibling_branch(&mut self, kind: SiblingBranch) -> Result<(), Exn> {
114 self.prefix()?;
115 self.has_implicit_anchor = true;
116 let ref_info = match self.ref_name.as_ref() {
117 Some(ref_name) => format!("'{ref_name}'"),
118 None => "behind 'HEAD'".into(),
119 };
120 writeln!(
121 self.out,
122 "Lookup the remote '{}' branch of local reference {}",
123 match kind {
124 SiblingBranch::Upstream => "upstream",
125 SiblingBranch::Push => "push",
126 },
127 ref_info
128 )
129 .ok();
130 Ok(())
131 }
132}
133
134impl delegate::Navigate for Explain<'_> {
135 fn traverse(&mut self, kind: Traversal) -> Result<(), Exn> {
136 self.prefix()?;
137 let name = self.revision_name();
138 writeln!(
139 self.out,
140 "{}",
141 match kind {
142 Traversal::NthAncestor(no) => format!("Traverse to the {no}. ancestor of revision named '{name}'"),
143 Traversal::NthParent(no) => format!("Select the {no}. parent of revision named '{name}'"),
144 }
145 )
146 .ok();
147 Ok(())
148 }
149
150 fn peel_until(&mut self, kind: PeelTo<'_>) -> Result<(), Exn> {
151 self.prefix()?;
152 writeln!(
153 self.out,
154 "{}",
155 match kind {
156 PeelTo::ValidObject => "Assure the current object exists".to_string(),
157 PeelTo::RecursiveTagObject => "Follow the current annotated tag until an object is found".into(),
158 PeelTo::ObjectKind(kind) => format!("Peel the current object until it is a {kind}"),
159 PeelTo::Path(path) => format!("Lookup the object at '{path}' from the current tree-ish"),
160 }
161 )
162 .ok();
163 Ok(())
164 }
165
166 fn find(&mut self, regex: &BStr, negated: bool) -> Result<(), Exn> {
167 self.prefix()?;
168 self.has_implicit_anchor = true;
169 let negate_text = if negated { "does not match" } else { "matches" };
170 writeln!(
171 self.out,
172 "{}",
173 match self
174 .ref_name
175 .as_ref()
176 .map(ToString::to_string)
177 .or_else(|| self.oid_prefix.map(|p| p.to_string()))
178 {
179 Some(obj_name) => format!(
180 "Follow the ancestry of revision '{obj_name}' until a commit message {negate_text} regex '{regex}'"
181 ),
182 None => format!(
183 "Find the most recent commit from any reference including 'HEAD' that {negate_text} regex '{regex}'"
184 ),
185 }
186 )
187 .ok();
188 Ok(())
189 }
190
191 fn index_lookup(&mut self, path: &BStr, stage: u8) -> Result<(), Exn> {
192 self.prefix()?;
193 self.has_implicit_anchor = true;
194 writeln!(
195 self.out,
196 "Lookup the index at path '{}' stage {} ({})",
197 path,
198 stage,
199 match stage {
200 0 => "base",
201 1 => "ours",
202 2 => "theirs",
203 _ => unreachable!("BUG: parser assures of that"),
204 }
205 )
206 .ok();
207 Ok(())
208 }
209}
210
211impl delegate::Kind for Explain<'_> {
212 fn kind(&mut self, kind: spec::Kind) -> Result<(), Exn> {
213 self.prefix()?;
214 self.call = 0;
215 writeln!(
216 self.out,
217 "Set revision specification to {} mode",
218 match kind {
219 spec::Kind::RangeBetween => "range",
220 spec::Kind::ReachableToMergeBase => "merge-base",
221 spec::Kind::ExcludeReachable => "exclude",
222 spec::Kind::IncludeReachableFromParents => "include parents",
223 spec::Kind::ExcludeReachableFromParents => "exclude parents",
224 spec::Kind::IncludeReachable =>
225 unreachable!("BUG: 'single' mode is implied but cannot be set explicitly"),
226 }
227 )
228 .ok();
229 Ok(())
230 }
231}
232
233impl Delegate for Explain<'_> {
234 fn done(&mut self) -> Result<(), Exn> {
235 if !self.has_implicit_anchor && self.ref_name.is_none() && self.oid_prefix.is_none() {
236 self.err = Some("Incomplete specification lacks its anchor, like a reference or object name".into());
237 }
238 Ok(())
239 }
240}