1use std::{
2 cmp::{Ordering, Reverse},
3 collections::VecDeque,
4};
5
6use gix_date::SecondsSinceUnixEpoch;
7use gix_hash::ObjectId;
8use smallvec::SmallVec;
9
10#[derive(Default, Debug, Copy, Clone)]
11pub enum CommitTimeOrder {
13 #[default]
14 NewestFirst,
16 #[doc(alias = "Sort::REVERSE", alias = "git2")]
18 OldestFirst,
19}
20
21#[derive(Default, Debug, Copy, Clone)]
34pub enum Sorting {
35 #[default]
44 BreadthFirst,
45 ByCommitTime(CommitTimeOrder),
57 ByCommitTimeCutoff {
64 order: CommitTimeOrder,
66 seconds: gix_date::SecondsSinceUnixEpoch,
68 },
69}
70
71#[derive(Debug, thiserror::Error)]
73#[allow(missing_docs)]
74pub enum Error {
75 #[error(transparent)]
76 Find(#[from] gix_object::find::existing_iter::Error),
77 #[error(transparent)]
78 ObjectDecode(#[from] gix_object::decode::Error),
79 #[error(transparent)]
80 HiddenGraph(#[from] gix_revwalk::graph::get_or_insert_default::Error),
81}
82
83use Result as Either;
84
85type QueueKey<T> = Either<T, Reverse<T>>;
86type CommitDateQueue = gix_revwalk::PriorityQueue<QueueKey<SecondsSinceUnixEpoch>, ObjectId>;
87
88bitflags::bitflags! {
89 #[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
90 struct PaintFlags: u8 {
91 const VISIBLE = 1 << 0;
92 const HIDDEN = 1 << 1;
93 const STALE = 1 << 2;
94 }
95}
96
97#[derive(Debug, Clone, Copy, Eq, PartialEq)]
100struct GenThenTime {
101 generation: gix_revwalk::graph::Generation,
102 time: SecondsSinceUnixEpoch,
103}
104
105impl From<&gix_revwalk::graph::Commit<PaintFlags>> for GenThenTime {
106 fn from(commit: &gix_revwalk::graph::Commit<PaintFlags>) -> Self {
107 GenThenTime {
108 generation: commit.generation.unwrap_or(gix_commitgraph::GENERATION_NUMBER_INFINITY),
109 time: commit.commit_time,
110 }
111 }
112}
113
114impl Ord for GenThenTime {
115 fn cmp(&self, other: &Self) -> Ordering {
116 self.generation.cmp(&other.generation).then(self.time.cmp(&other.time))
117 }
118}
119
120impl PartialOrd<Self> for GenThenTime {
121 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
122 Some(self.cmp(other))
123 }
124}
125
126#[derive(Clone)]
128pub(super) struct State {
129 next: VecDeque<ObjectId>,
134 queue: CommitDateQueue,
139 buf: Vec<u8>,
141 seen: gix_hashtable::HashSet<ObjectId>,
143 hidden: gix_revwalk::graph::IdMap<()>,
145 hidden_tips: Vec<ObjectId>,
149 parents_buf: Vec<u8>,
151 parent_ids: SmallVec<[(ObjectId, SecondsSinceUnixEpoch); 2]>,
153}
154
155fn to_queue_key(i: i64, order: CommitTimeOrder) -> QueueKey<i64> {
156 match order {
157 CommitTimeOrder::NewestFirst => Ok(i),
158 CommitTimeOrder::OldestFirst => Err(Reverse(i)),
159 }
160}
161
162fn compute_hidden_frontier(
175 visible_tips: &[ObjectId],
176 hidden_tips: &[ObjectId],
177 objects: &impl gix_object::Find,
178 cache: Option<&gix_commitgraph::Graph>,
179) -> Result<gix_revwalk::graph::IdMap<()>, Error> {
180 let mut graph = gix_revwalk::Graph::<gix_revwalk::graph::Commit<PaintFlags>>::new(objects, cache);
181 let mut queue = gix_revwalk::PriorityQueue::<GenThenTime, ObjectId>::new();
182
183 for &visible in visible_tips {
184 graph.get_or_insert_full_commit(visible, |commit| {
185 commit.data |= PaintFlags::VISIBLE;
186 queue.insert(GenThenTime::from(&*commit), visible);
187 })?;
188 }
189 for &hidden in hidden_tips {
190 graph.get_or_insert_full_commit(hidden, |commit| {
191 commit.data |= PaintFlags::HIDDEN;
192 queue.insert(GenThenTime::from(&*commit), hidden);
193 })?;
194 }
195
196 while queue.iter_unordered().any(|id| {
197 graph
198 .get(id)
199 .is_some_and(|commit| !commit.data.contains(PaintFlags::STALE))
200 }) {
201 let (_info, commit_id) = match queue.pop() {
202 Some(v) => v,
203 None => break,
204 };
205 let commit = graph.get_mut(&commit_id).expect("queued commits are in the graph");
206 let mut flags = commit.data;
207 if flags == (PaintFlags::VISIBLE | PaintFlags::HIDDEN) {
208 flags |= PaintFlags::STALE;
209 }
210
211 for parent_id in commit.parents.clone() {
212 graph.get_or_insert_full_commit(parent_id, |parent| {
213 if (parent.data & flags) != flags {
214 parent.data |= flags;
215 queue.insert(GenThenTime::from(&*parent), parent_id);
216 }
217 })?;
218 }
219 }
220
221 Ok(graph
222 .detach()
223 .into_iter()
224 .filter_map(|(id, commit)| {
225 commit
226 .data
227 .contains(PaintFlags::VISIBLE | PaintFlags::HIDDEN)
228 .then_some((id, ()))
229 })
230 .collect())
231}
232
233mod init {
235 use super::{
236 collect_parents, compute_hidden_frontier, to_queue_key, CommitDateQueue, CommitTimeOrder, Error, Sorting, State,
237 };
238 use crate::commit::{Either, Info, ParentIds, Parents, Simple};
239 use gix_date::SecondsSinceUnixEpoch;
240 use gix_hash::{oid, ObjectId};
241 use gix_object::{CommitRefIter, FindExt};
242 use std::{cmp::Reverse, collections::VecDeque};
243
244 impl Default for State {
245 fn default() -> Self {
246 State {
247 next: Default::default(),
248 queue: gix_revwalk::PriorityQueue::new(),
249 buf: vec![],
250 seen: Default::default(),
251 hidden: Default::default(),
252 hidden_tips: Vec::new(),
253 parents_buf: vec![],
254 parent_ids: Default::default(),
255 }
256 }
257 }
258
259 impl State {
260 fn clear(&mut self) {
261 let Self {
262 next,
263 queue,
264 buf,
265 seen,
266 hidden,
267 hidden_tips,
268 parents_buf: _,
269 parent_ids: _,
270 } = self;
271 next.clear();
272 queue.clear();
273 buf.clear();
274 seen.clear();
275 hidden.clear();
276 hidden_tips.clear();
277 }
278 }
279
280 impl Sorting {
281 fn cutoff_time(&self) -> Option<SecondsSinceUnixEpoch> {
283 match self {
284 Sorting::ByCommitTimeCutoff { seconds, .. } => Some(*seconds),
285 _ => None,
286 }
287 }
288 }
289
290 impl<Find, Predicate> Simple<Find, Predicate>
292 where
293 Find: gix_object::Find,
294 {
295 pub fn sorting(mut self, sorting: Sorting) -> Result<Self, Error> {
297 self.sorting = sorting;
298 match self.sorting {
299 Sorting::BreadthFirst => self.queue_to_vecdeque(),
300 Sorting::ByCommitTime(order) | Sorting::ByCommitTimeCutoff { order, .. } => {
301 let state = &mut self.state;
302 for commit_id in state.next.drain(..) {
303 add_to_queue(
304 commit_id,
305 order,
306 sorting.cutoff_time(),
307 &mut state.queue,
308 &self.objects,
309 &mut state.buf,
310 )?;
311 }
312 }
313 }
314 Ok(self)
315 }
316
317 pub fn parents(mut self, mode: Parents) -> Self {
319 self.parents = mode;
320 if matches!(self.parents, Parents::First) {
321 self.queue_to_vecdeque();
322 }
323 self
324 }
325
326 pub fn hide(mut self, tips: impl IntoIterator<Item = ObjectId>) -> Result<Self, Error> {
329 self.state.hidden_tips = tips.into_iter().collect();
330 Ok(self)
331 }
332
333 pub fn commit_graph(mut self, cache: Option<gix_commitgraph::Graph>) -> Self {
338 self.cache = cache;
339 self
340 }
341
342 fn queue_to_vecdeque(&mut self) {
343 let state = &mut self.state;
344 state.next.extend(
345 std::mem::replace(&mut state.queue, gix_revwalk::PriorityQueue::new())
346 .into_iter_unordered()
347 .map(|(_time, id)| id),
348 );
349 }
350
351 fn visible_inputs_sorted(&self) -> Vec<ObjectId> {
352 let mut out: Vec<_> = self
353 .state
354 .next
355 .iter()
356 .copied()
357 .chain(self.state.queue.iter_unordered().copied())
358 .collect();
359 out.sort();
360 out.dedup();
361 out
362 }
363
364 fn compute_hidden_frontier(&mut self, hidden_tips: Vec<ObjectId>) -> Result<(), Error> {
365 self.state.hidden.clear();
366 if hidden_tips.is_empty() {
367 return Ok(());
368 }
369 let visible_tips = self.visible_inputs_sorted();
370 if visible_tips.is_empty() {
371 return Ok(());
372 }
373 self.state.hidden =
374 compute_hidden_frontier(&visible_tips, &hidden_tips, &self.objects, self.cache.as_ref())?;
375 self.state.next.retain(|id| !self.state.hidden.contains_key(id));
376 self.state.queue = std::mem::replace(&mut self.state.queue, gix_revwalk::PriorityQueue::new())
377 .into_iter_unordered()
378 .filter(|(_, id)| !self.state.hidden.contains_key(id))
379 .collect();
380 Ok(())
381 }
382 }
383
384 fn add_to_queue(
385 commit_id: ObjectId,
386 order: CommitTimeOrder,
387 cutoff_time: Option<SecondsSinceUnixEpoch>,
388 queue: &mut CommitDateQueue,
389 objects: &impl gix_object::Find,
390 buf: &mut Vec<u8>,
391 ) -> Result<(), Error> {
392 let commit_iter = objects.find_commit_iter(&commit_id, buf)?;
393 let time = commit_iter.committer()?.seconds();
394 let key = to_queue_key(time, order);
395 match (cutoff_time, order) {
396 (Some(cutoff_time), _) if time >= cutoff_time => queue.insert(key, commit_id),
397 (Some(_), _) => {}
398 (None, _) => queue.insert(key, commit_id),
399 }
400 Ok(())
401 }
402
403 impl<Find> Simple<Find, fn(&oid) -> bool>
405 where
406 Find: gix_object::Find,
407 {
408 pub fn new(tips: impl IntoIterator<Item = impl Into<ObjectId>>, find: Find) -> Self {
417 Self::filtered(tips, find, |_| true)
418 }
419 }
420
421 impl<Find, Predicate> Simple<Find, Predicate>
422 where
423 Find: gix_object::Find,
424 Predicate: FnMut(&oid) -> bool,
425 {
426 pub fn filtered(
437 tips: impl IntoIterator<Item = impl Into<ObjectId>>,
438 find: Find,
439 mut predicate: Predicate,
440 ) -> Self {
441 let tips = tips.into_iter();
442 let mut state = State::default();
443 {
444 state.clear();
445 state.next.reserve(tips.size_hint().0);
446 for tip in tips.map(Into::into) {
447 if state.seen.insert(tip) && predicate(&tip) {
448 state.next.push_back(tip);
449 }
450 }
451 }
452 Self {
453 objects: find,
454 cache: None,
455 predicate,
456 state,
457 parents: Default::default(),
458 sorting: Default::default(),
459 }
460 }
461 }
462
463 impl<Find, Predicate> Simple<Find, Predicate> {
465 pub fn commit_iter(&self) -> CommitRefIter<'_> {
467 CommitRefIter::from_bytes(self.commit_data())
468 }
469
470 pub fn commit_data(&self) -> &[u8] {
472 &self.state.buf
473 }
474 }
475
476 impl<Find, Predicate> Iterator for Simple<Find, Predicate>
477 where
478 Find: gix_object::Find,
479 Predicate: FnMut(&oid) -> bool,
480 {
481 type Item = Result<Info, Error>;
482
483 fn next(&mut self) -> Option<Self::Item> {
484 if !self.state.hidden_tips.is_empty() {
485 let hidden_tips = std::mem::take(&mut self.state.hidden_tips);
486 if let Err(err) = self.compute_hidden_frontier(hidden_tips) {
487 self.state.queue.clear();
488 self.state.next.clear();
489 return Some(Err(err));
490 }
491 }
492 if matches!(self.parents, Parents::First) {
493 self.next_by_topology()
494 } else {
495 match self.sorting {
496 Sorting::BreadthFirst => self.next_by_topology(),
497 Sorting::ByCommitTime(order) => self.next_by_commit_date(order, None),
498 Sorting::ByCommitTimeCutoff { seconds, order } => self.next_by_commit_date(order, seconds.into()),
499 }
500 }
501 }
502 }
503
504 impl<Find, Predicate> Simple<Find, Predicate>
506 where
507 Find: gix_object::Find,
508 Predicate: FnMut(&oid) -> bool,
509 {
510 fn next_by_commit_date(
511 &mut self,
512 order: CommitTimeOrder,
513 cutoff: Option<SecondsSinceUnixEpoch>,
514 ) -> Option<Result<Info, Error>> {
515 let state = &mut self.state;
516 let next = &mut state.queue;
517
518 loop {
519 let (commit_time, oid) = match next.pop()? {
520 (Ok(t) | Err(Reverse(t)), o) => (t, o),
521 };
522 if state.hidden.contains_key(&oid) {
523 continue;
524 }
525 let mut parents: ParentIds = Default::default();
526
527 match super::super::find(self.cache.as_ref(), &self.objects, &oid, &mut state.buf) {
528 Ok(Either::CachedCommit(commit)) => {
529 if !collect_parents(&mut state.parent_ids, self.cache.as_ref(), commit.iter_parents()) {
530 self.cache = None;
532 return self.next_by_commit_date(order, cutoff);
533 }
534 for (id, parent_commit_time) in state.parent_ids.drain(..) {
535 parents.push(id);
536 insert_into_seen_and_queue(
537 &mut state.seen,
538 &state.hidden,
539 id,
540 &mut self.predicate,
541 next,
542 order,
543 cutoff,
544 || parent_commit_time,
545 );
546 }
547 }
548 Ok(Either::CommitRefIter(commit_iter)) => {
549 for token in commit_iter {
550 match token {
551 Ok(gix_object::commit::ref_iter::Token::Tree { .. }) => continue,
552 Ok(gix_object::commit::ref_iter::Token::Parent { id }) => {
553 parents.push(id);
554 insert_into_seen_and_queue(
555 &mut state.seen,
556 &state.hidden,
557 id,
558 &mut self.predicate,
559 next,
560 order,
561 cutoff,
562 || {
563 let parent =
564 self.objects.find_commit_iter(id.as_ref(), &mut state.parents_buf).ok();
565 parent
566 .and_then(|parent| {
567 parent.committer().ok().map(|committer| committer.seconds())
568 })
569 .unwrap_or_default()
570 },
571 );
572 }
573 Ok(_unused_token) => break,
574 Err(err) => return Some(Err(err.into())),
575 }
576 }
577 }
578 Err(err) => return Some(Err(err.into())),
579 }
580
581 return Some(Ok(Info {
582 id: oid,
583 parent_ids: parents,
584 commit_time: Some(commit_time),
585 }));
586 }
587 }
588
589 fn next_by_topology(&mut self) -> Option<Result<Info, Error>> {
590 let state = &mut self.state;
591 let next = &mut state.next;
592
593 loop {
594 let oid = next.pop_front()?;
595 if state.hidden.contains_key(&oid) {
596 continue;
597 }
598 let mut parents: ParentIds = Default::default();
599
600 match super::super::find(self.cache.as_ref(), &self.objects, &oid, &mut state.buf) {
601 Ok(Either::CachedCommit(commit)) => {
602 if !collect_parents(&mut state.parent_ids, self.cache.as_ref(), commit.iter_parents()) {
603 self.cache = None;
605 return self.next_by_topology();
606 }
607
608 for (pid, _commit_time) in state.parent_ids.drain(..) {
609 parents.push(pid);
610 insert_into_seen_and_next(&mut state.seen, &state.hidden, pid, &mut self.predicate, next);
611 if matches!(self.parents, Parents::First) {
612 break;
613 }
614 }
615 }
616 Ok(Either::CommitRefIter(commit_iter)) => {
617 for token in commit_iter {
618 match token {
619 Ok(gix_object::commit::ref_iter::Token::Tree { .. }) => continue,
620 Ok(gix_object::commit::ref_iter::Token::Parent { id: pid }) => {
621 parents.push(pid);
622 insert_into_seen_and_next(
623 &mut state.seen,
624 &state.hidden,
625 pid,
626 &mut self.predicate,
627 next,
628 );
629 if matches!(self.parents, Parents::First) {
630 break;
631 }
632 }
633 Ok(_a_token_past_the_parents) => break,
634 Err(err) => return Some(Err(err.into())),
635 }
636 }
637 }
638 Err(err) => return Some(Err(err.into())),
639 }
640
641 return Some(Ok(Info {
642 id: oid,
643 parent_ids: parents,
644 commit_time: None,
645 }));
646 }
647 }
648 }
649
650 fn insert_into_seen_and_next(
651 seen: &mut gix_hashtable::HashSet<ObjectId>,
652 hidden: &gix_revwalk::graph::IdMap<()>,
653 parent_id: ObjectId,
654 predicate: &mut impl FnMut(&oid) -> bool,
655 next: &mut VecDeque<ObjectId>,
656 ) {
657 if hidden.contains_key(&parent_id) {
658 return;
659 }
660 if seen.insert(parent_id) && predicate(&parent_id) {
661 next.push_back(parent_id);
662 }
663 }
664
665 #[allow(clippy::too_many_arguments)]
666 fn insert_into_seen_and_queue(
667 seen: &mut gix_hashtable::HashSet<ObjectId>,
668 hidden: &gix_revwalk::graph::IdMap<()>,
669 parent_id: ObjectId,
670 predicate: &mut impl FnMut(&oid) -> bool,
671 queue: &mut CommitDateQueue,
672 order: CommitTimeOrder,
673 cutoff: Option<SecondsSinceUnixEpoch>,
674 get_parent_commit_time: impl FnOnce() -> gix_date::SecondsSinceUnixEpoch,
675 ) {
676 if hidden.contains_key(&parent_id) {
677 return;
678 }
679 if seen.insert(parent_id) && predicate(&parent_id) {
680 let parent_commit_time = get_parent_commit_time();
681 let key = to_queue_key(parent_commit_time, order);
682 match cutoff {
683 Some(cutoff_older_than) if parent_commit_time < cutoff_older_than => {}
684 Some(_) | None => queue.insert(key, parent_id),
685 }
686 }
687 }
688}
689
690fn collect_parents(
691 dest: &mut SmallVec<[(gix_hash::ObjectId, gix_date::SecondsSinceUnixEpoch); 2]>,
692 cache: Option<&gix_commitgraph::Graph>,
693 parents: gix_commitgraph::file::commit::Parents<'_>,
694) -> bool {
695 dest.clear();
696 let cache = cache.as_ref().expect("parents iter is available, backed by `cache`");
697 for parent_id in parents {
698 match parent_id {
699 Ok(pos) => dest.push({
700 let parent = cache.commit_at(pos);
701 (
702 parent.id().to_owned(),
703 parent.committer_timestamp() as gix_date::SecondsSinceUnixEpoch,
704 )
705 }),
706 Err(_err) => return false,
707 }
708 }
709 true
710}