1use std::{fmt::Formatter, ops::Index};
2
3use gix_hash::oid;
4use smallvec::SmallVec;
5
6use crate::Graph;
7
8pub type IdMap<T> = gix_hashtable::HashMap<gix_hash::ObjectId, T>;
10
11pub mod commit;
13
14mod errors {
15 pub mod insert_parents {
17 use crate::graph::commit::iter_parents;
18
19 #[derive(Debug, thiserror::Error)]
21 #[allow(missing_docs)]
22 pub enum Error {
23 #[error(transparent)]
24 Lookup(#[from] gix_object::find::existing_iter::Error),
25 #[error("A commit could not be decoded during traversal")]
26 Decode(#[from] gix_object::decode::Error),
27 #[error(transparent)]
28 Parent(#[from] iter_parents::Error),
29 }
30 }
31
32 pub mod get_or_insert_default {
34 use crate::graph::commit::to_owned;
35
36 #[derive(Debug, thiserror::Error)]
38 #[allow(missing_docs)]
39 pub enum Error {
40 #[error(transparent)]
41 Lookup(#[from] gix_object::find::existing_iter::Error),
42 #[error(transparent)]
43 ToOwned(#[from] to_owned::Error),
44 }
45 }
46}
47pub use errors::{get_or_insert_default, insert_parents};
48use gix_date::SecondsSinceUnixEpoch;
49
50pub type Generation = u32;
55
56impl<T: std::fmt::Debug> std::fmt::Debug for Graph<'_, '_, T> {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 std::fmt::Debug::fmt(&self.map, f)
59 }
60}
61
62impl<'cache, T: Default> Graph<'_, 'cache, T> {
63 pub fn try_lookup_or_insert(
68 &mut self,
69 id: gix_hash::ObjectId,
70 update_data: impl FnOnce(&mut T),
71 ) -> Result<Option<LazyCommit<'_, 'cache>>, get_or_insert_default::Error> {
72 self.try_lookup_or_insert_default(id, T::default, update_data)
73 }
74}
75
76impl<'cache, T> Graph<'_, 'cache, T> {
78 pub fn len(&self) -> usize {
80 self.map.len()
81 }
82
83 pub fn is_empty(&self) -> bool {
85 self.map.is_empty()
86 }
87
88 pub fn contains(&self, id: &gix_hash::oid) -> bool {
90 self.map.contains_key(id.as_ref())
91 }
92
93 pub fn get(&self, id: &gix_hash::oid) -> Option<&T> {
95 self.map.get(id)
96 }
97
98 pub fn get_mut(&mut self, id: &gix_hash::oid) -> Option<&mut T> {
100 self.map.get_mut(id)
101 }
102
103 pub fn insert(&mut self, id: gix_hash::ObjectId, value: T) -> Option<T> {
105 self.map.insert(id, value)
106 }
107
108 pub fn insert_data<E>(
112 &mut self,
113 id: gix_hash::ObjectId,
114 mut make_data: impl FnMut(LazyCommit<'_, 'cache>) -> Result<T, E>,
115 ) -> Result<Option<T>, E>
116 where
117 E: From<gix_object::find::existing_iter::Error>,
118 {
119 let value = make_data(self.lookup(&id).map_err(E::from)?)?;
120 Ok(self.map.insert(id, value))
121 }
122
123 pub fn clear(&mut self) {
125 self.map.clear();
126 }
127
128 pub fn insert_parents(
133 &mut self,
134 id: &gix_hash::oid,
135 new_parent_data: &mut dyn FnMut(gix_hash::ObjectId, SecondsSinceUnixEpoch) -> T,
136 update_existing: &mut dyn FnMut(gix_hash::ObjectId, &mut T),
137 first_parent: bool,
138 ) -> Result<(), insert_parents::Error> {
139 let commit = self.lookup(id)?;
140 let parents: SmallVec<[_; 2]> = commit.iter_parents().collect();
141 for parent_id in parents {
142 let parent_id = parent_id?;
143 match self.map.entry(parent_id) {
144 gix_hashtable::hash_map::Entry::Vacant(entry) => {
145 let parent = match try_lookup(&parent_id, &*self.find, self.cache, &mut self.parent_buf)? {
146 Some(p) => p,
147 None => continue, };
149
150 let parent_commit_date = parent.committer_timestamp().unwrap_or_default();
151 entry.insert(new_parent_data(parent_id, parent_commit_date));
152 }
153 gix_hashtable::hash_map::Entry::Occupied(mut entry) => {
154 update_existing(parent_id, entry.get_mut());
155 }
156 }
157 if first_parent {
158 break;
159 }
160 }
161 Ok(())
162 }
163
164 #[allow(clippy::type_complexity)]
170 pub fn insert_parents_with_lookup<E>(
171 &mut self,
172 id: &gix_hash::oid,
173 parent_data: &mut dyn FnMut(gix_hash::ObjectId, LazyCommit<'_, 'cache>, Option<&mut T>) -> Result<T, E>,
174 ) -> Result<(), E>
175 where
176 E: From<gix_object::find::existing_iter::Error>
177 + From<gix_object::decode::Error>
178 + From<commit::iter_parents::Error>,
179 {
180 let commit = self.lookup(id).map_err(E::from)?;
181 let parents: SmallVec<[_; 2]> = commit.iter_parents().collect();
182 for parent_id in parents {
183 let parent_id = parent_id.map_err(E::from)?;
184 let parent = match try_lookup(&parent_id, &*self.find, self.cache, &mut self.parent_buf).map_err(E::from)? {
185 Some(p) => p,
186 None => continue, };
188
189 match self.map.entry(parent_id) {
190 gix_hashtable::hash_map::Entry::Vacant(entry) => {
191 entry.insert(parent_data(parent_id, parent, None)?);
192 }
193 gix_hashtable::hash_map::Entry::Occupied(mut entry) => {
194 parent_data(parent_id, parent, Some(entry.get_mut()))?;
195 }
196 }
197 }
198 Ok(())
199 }
200
201 pub fn detach(self) -> IdMap<T> {
203 self.map
204 }
205}
206
207impl<'find, 'cache, T> Graph<'find, 'cache, T> {
209 pub fn new(objects: impl gix_object::Find + 'find, cache: Option<&'cache gix_commitgraph::Graph>) -> Self {
218 Graph {
219 find: Box::new(objects),
220 cache,
221 map: gix_hashtable::HashMap::default(),
222 buf: Vec::new(),
223 parent_buf: Vec::new(),
224 }
225 }
226}
227
228impl<T> Graph<'_, '_, Commit<T>> {
230 pub fn get_or_insert_commit_default(
236 &mut self,
237 id: gix_hash::ObjectId,
238 new_data: impl FnOnce() -> T,
239 update_data: impl FnOnce(&mut T),
240 ) -> Result<Option<&mut Commit<T>>, get_or_insert_default::Error> {
241 match self.map.entry(id) {
242 gix_hashtable::hash_map::Entry::Vacant(entry) => {
243 let res = try_lookup(&id, &*self.find, self.cache, &mut self.buf)?;
244 let commit = match res {
245 None => return Ok(None),
246 Some(commit) => commit,
247 };
248 let mut commit = commit.to_owned(new_data)?;
249 update_data(&mut commit.data);
250 entry.insert(commit);
251 }
252 gix_hashtable::hash_map::Entry::Occupied(mut entry) => {
253 update_data(&mut entry.get_mut().data);
254 }
255 }
256 Ok(self.map.get_mut(&id))
257 }
258
259 pub fn clear_commit_data(&mut self, mut clear: impl FnMut(&mut T)) {
261 self.map.values_mut().for_each(|c| clear(&mut c.data));
262 }
263}
264
265impl<T: Default> Graph<'_, '_, Commit<T>> {
267 pub fn get_or_insert_commit(
276 &mut self,
277 id: gix_hash::ObjectId,
278 update_data: impl FnOnce(&mut T),
279 ) -> Result<Option<&mut Commit<T>>, get_or_insert_default::Error> {
280 self.get_or_insert_commit_default(id, T::default, update_data)
281 }
282
283 pub fn get_or_insert_full_commit(
288 &mut self,
289 id: gix_hash::ObjectId,
290 update_commit: impl FnOnce(&mut Commit<T>),
291 ) -> Result<Option<&mut Commit<T>>, get_or_insert_default::Error> {
292 match self.map.entry(id) {
293 gix_hashtable::hash_map::Entry::Vacant(entry) => {
294 let res = try_lookup(&id, &*self.find, self.cache, &mut self.buf)?;
295 let commit = match res {
296 None => return Ok(None),
297 Some(commit) => commit,
298 };
299 let mut commit = commit.to_owned(T::default)?;
300 update_commit(&mut commit);
301 entry.insert(commit);
302 }
303 gix_hashtable::hash_map::Entry::Occupied(mut entry) => {
304 update_commit(entry.get_mut());
305 }
306 }
307 Ok(self.map.get_mut(&id))
308 }
309}
310
311impl<'cache, T> Graph<'_, 'cache, T> {
313 pub fn try_lookup_or_insert_default(
324 &mut self,
325 id: gix_hash::ObjectId,
326 default: impl FnOnce() -> T,
327 update_data: impl FnOnce(&mut T),
328 ) -> Result<Option<LazyCommit<'_, 'cache>>, get_or_insert_default::Error> {
329 let res = try_lookup(&id, &*self.find, self.cache, &mut self.buf)?;
330 Ok(res.inspect(|_commit| match self.map.entry(id) {
331 gix_hashtable::hash_map::Entry::Vacant(entry) => {
332 let mut data = default();
333 update_data(&mut data);
334 entry.insert(data);
335 }
336 gix_hashtable::hash_map::Entry::Occupied(mut entry) => {
337 update_data(entry.get_mut());
338 }
339 }))
340 }
341
342 pub fn try_lookup(
347 &mut self,
348 id: &gix_hash::oid,
349 ) -> Result<Option<LazyCommit<'_, 'cache>>, gix_object::find::existing_iter::Error> {
350 try_lookup(id, &*self.find, self.cache, &mut self.buf)
351 }
352
353 pub fn lookup(
355 &mut self,
356 id: &gix_hash::oid,
357 ) -> Result<LazyCommit<'_, 'cache>, gix_object::find::existing_iter::Error> {
358 self.try_lookup(id)?
359 .ok_or(gix_object::find::existing_iter::Error::NotFound { oid: id.to_owned() })
360 }
361}
362
363fn try_lookup<'graph, 'cache>(
364 id: &gix_hash::oid,
365 objects: &dyn gix_object::Find,
366 cache: Option<&'cache gix_commitgraph::Graph>,
367 buf: &'graph mut Vec<u8>,
368) -> Result<Option<LazyCommit<'graph, 'cache>>, gix_object::find::existing_iter::Error> {
369 if let Some(cache) = cache {
370 if let Some(pos) = cache.lookup(id) {
371 return Ok(Some(LazyCommit {
372 backing: Either::Right((cache, pos)),
373 }));
374 }
375 }
376 #[allow(clippy::manual_map)]
377 Ok(
378 match objects
379 .try_find(id, buf)
380 .map_err(gix_object::find::existing_iter::Error::Find)?
381 {
382 Some(data) => data.kind.is_commit().then_some(LazyCommit {
383 backing: Either::Left(buf),
384 }),
385 None => None,
386 },
387 )
388}
389
390impl<'a, T> Index<&'a gix_hash::oid> for Graph<'_, '_, T> {
391 type Output = T;
392
393 fn index(&self, index: &'a oid) -> &Self::Output {
394 &self.map[index]
395 }
396}
397
398pub struct Commit<T> {
400 pub parents: SmallVec<[gix_hash::ObjectId; 1]>,
402 pub commit_time: SecondsSinceUnixEpoch,
404 pub generation: Option<u32>,
406 pub data: T,
408}
409
410impl<T> std::fmt::Debug for Commit<T>
411where
412 T: std::fmt::Debug,
413{
414 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
415 f.debug_struct("Commit")
416 .field("parents", &self.parents)
417 .field("commit_time", &self.commit_time)
418 .field("generation", &self.generation)
419 .field("data", &self.data)
420 .finish()
421 }
422}
423
424impl<T> Clone for Commit<T>
425where
426 T: Clone,
427{
428 fn clone(&self) -> Self {
429 Commit {
430 parents: self.parents.clone(),
431 commit_time: self.commit_time,
432 generation: self.generation,
433 data: self.data.clone(),
434 }
435 }
436}
437
438pub struct LazyCommit<'graph, 'cache> {
442 backing: Either<&'graph [u8], (&'cache gix_commitgraph::Graph, gix_commitgraph::Position)>,
443}
444
445enum Either<T, U> {
446 Left(T),
447 Right(U),
448}