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 object_hash: gix_hash::Kind,
145 seen: gix_hashtable::HashSet<ObjectId>,
147 hidden: gix_revwalk::graph::IdMap<()>,
149 hidden_tips: Vec<ObjectId>,
153 parents_buf: Vec<u8>,
155 parent_ids: SmallVec<[(ObjectId, SecondsSinceUnixEpoch); 2]>,
157}
158
159fn to_queue_key(i: i64, order: CommitTimeOrder) -> QueueKey<i64> {
160 match order {
161 CommitTimeOrder::NewestFirst => Ok(i),
162 CommitTimeOrder::OldestFirst => Err(Reverse(i)),
163 }
164}
165
166fn compute_hidden_frontier(
179 visible_tips: &[ObjectId],
180 hidden_tips: &[ObjectId],
181 objects: &impl gix_object::Find,
182 cache: Option<&gix_commitgraph::Graph>,
183) -> Result<gix_revwalk::graph::IdMap<()>, Error> {
184 let mut graph = gix_revwalk::Graph::<gix_revwalk::graph::Commit<PaintFlags>>::new(objects, cache);
185 let mut queue = gix_revwalk::PriorityQueue::<GenThenTime, ObjectId>::new();
186
187 for &visible in visible_tips {
188 graph.get_or_insert_full_commit(visible, |commit| {
189 commit.data |= PaintFlags::VISIBLE;
190 queue.insert(GenThenTime::from(&*commit), visible);
191 })?;
192 }
193 for &hidden in hidden_tips {
194 graph.get_or_insert_full_commit(hidden, |commit| {
195 commit.data |= PaintFlags::HIDDEN;
196 queue.insert(GenThenTime::from(&*commit), hidden);
197 })?;
198 }
199
200 while queue.iter_unordered().any(|id| {
201 graph
202 .get(id)
203 .is_some_and(|commit| !commit.data.contains(PaintFlags::STALE))
204 }) {
205 let (_info, commit_id) = match queue.pop() {
206 Some(v) => v,
207 None => break,
208 };
209 let commit = graph.get_mut(&commit_id).expect("queued commits are in the graph");
210 let mut flags = commit.data;
211 if flags == (PaintFlags::VISIBLE | PaintFlags::HIDDEN) {
212 flags |= PaintFlags::STALE;
213 }
214
215 for parent_id in commit.parents.clone() {
216 graph.get_or_insert_full_commit(parent_id, |parent| {
217 if (parent.data & flags) != flags {
218 parent.data |= flags;
219 queue.insert(GenThenTime::from(&*parent), parent_id);
220 }
221 })?;
222 }
223 }
224
225 Ok(graph
226 .detach()
227 .into_iter()
228 .filter_map(|(id, commit)| {
229 commit
230 .data
231 .contains(PaintFlags::VISIBLE | PaintFlags::HIDDEN)
232 .then_some((id, ()))
233 })
234 .collect())
235}
236
237mod init {
239 use super::{
240 collect_parents, compute_hidden_frontier, to_queue_key, CommitDateQueue, CommitTimeOrder, Error, Sorting, State,
241 };
242 use crate::commit::{Either, Info, ParentIds, Parents, Simple};
243 use gix_date::SecondsSinceUnixEpoch;
244 use gix_hash::{oid, ObjectId};
245 use gix_object::{CommitRefIter, FindExt};
246 use std::{cmp::Reverse, collections::VecDeque};
247
248 impl Default for State {
249 fn default() -> Self {
250 State {
251 next: Default::default(),
252 queue: gix_revwalk::PriorityQueue::new(),
253 buf: vec![],
254 object_hash: gix_hash::Kind::Sha1,
255 seen: Default::default(),
256 hidden: Default::default(),
257 hidden_tips: Vec::new(),
258 parents_buf: vec![],
259 parent_ids: Default::default(),
260 }
261 }
262 }
263
264 impl State {
265 fn clear(&mut self) {
266 let Self {
267 next,
268 queue,
269 buf,
270 object_hash,
271 seen,
272 hidden,
273 hidden_tips,
274 parents_buf: _,
275 parent_ids: _,
276 } = self;
277 next.clear();
278 queue.clear();
279 buf.clear();
280 *object_hash = gix_hash::Kind::Sha1;
281 seen.clear();
282 hidden.clear();
283 hidden_tips.clear();
284 }
285 }
286
287 impl Sorting {
288 fn cutoff_time(&self) -> Option<SecondsSinceUnixEpoch> {
290 match self {
291 Sorting::ByCommitTimeCutoff { seconds, .. } => Some(*seconds),
292 _ => None,
293 }
294 }
295 }
296
297 impl<Find, Predicate> Simple<Find, Predicate>
299 where
300 Find: gix_object::Find,
301 {
302 pub fn sorting(mut self, sorting: Sorting) -> Result<Self, Error> {
304 self.sorting = sorting;
305 match self.sorting {
306 Sorting::BreadthFirst => self.queue_to_vecdeque(),
307 Sorting::ByCommitTime(order) | Sorting::ByCommitTimeCutoff { order, .. } => {
308 let state = &mut self.state;
309 for commit_id in state.next.drain(..) {
310 add_to_queue(
311 commit_id,
312 order,
313 sorting.cutoff_time(),
314 &mut state.queue,
315 &self.objects,
316 &mut state.buf,
317 )?;
318 }
319 }
320 }
321 Ok(self)
322 }
323
324 pub fn parents(mut self, mode: Parents) -> Self {
326 self.parents = mode;
327 if matches!(self.parents, Parents::First) {
328 self.queue_to_vecdeque();
329 }
330 self
331 }
332
333 pub fn hide(mut self, tips: impl IntoIterator<Item = ObjectId>) -> Result<Self, Error> {
336 self.state.hidden_tips = tips.into_iter().collect();
337 Ok(self)
338 }
339
340 pub fn commit_graph(mut self, cache: Option<gix_commitgraph::Graph>) -> Self {
345 self.cache = cache;
346 self
347 }
348
349 fn queue_to_vecdeque(&mut self) {
350 let state = &mut self.state;
351 state.next.extend(
352 std::mem::replace(&mut state.queue, gix_revwalk::PriorityQueue::new())
353 .into_iter_unordered()
354 .map(|(_time, id)| id),
355 );
356 }
357
358 fn visible_inputs_sorted(&self) -> Vec<ObjectId> {
359 let mut out: Vec<_> = self
360 .state
361 .next
362 .iter()
363 .copied()
364 .chain(self.state.queue.iter_unordered().copied())
365 .collect();
366 out.sort();
367 out.dedup();
368 out
369 }
370
371 fn compute_hidden_frontier(&mut self, hidden_tips: Vec<ObjectId>) -> Result<(), Error> {
372 self.state.hidden.clear();
373 if hidden_tips.is_empty() {
374 return Ok(());
375 }
376 let visible_tips = self.visible_inputs_sorted();
377 if visible_tips.is_empty() {
378 return Ok(());
379 }
380 self.state.hidden =
381 compute_hidden_frontier(&visible_tips, &hidden_tips, &self.objects, self.cache.as_ref())?;
382 self.state.next.retain(|id| !self.state.hidden.contains_key(id));
383 self.state.queue = std::mem::replace(&mut self.state.queue, gix_revwalk::PriorityQueue::new())
384 .into_iter_unordered()
385 .filter(|(_, id)| !self.state.hidden.contains_key(id))
386 .collect();
387 Ok(())
388 }
389 }
390
391 fn add_to_queue(
392 commit_id: ObjectId,
393 order: CommitTimeOrder,
394 cutoff_time: Option<SecondsSinceUnixEpoch>,
395 queue: &mut CommitDateQueue,
396 objects: &impl gix_object::Find,
397 buf: &mut Vec<u8>,
398 ) -> Result<(), Error> {
399 let commit_iter = objects.find_commit_iter(&commit_id, buf)?;
400 let time = commit_iter.committer()?.seconds();
401 let key = to_queue_key(time, order);
402 match (cutoff_time, order) {
403 (Some(cutoff_time), _) if time >= cutoff_time => queue.insert(key, commit_id),
404 (Some(_), _) => {}
405 (None, _) => queue.insert(key, commit_id),
406 }
407 Ok(())
408 }
409
410 impl<Find> Simple<Find, fn(&oid) -> bool>
412 where
413 Find: gix_object::Find,
414 {
415 pub fn new(tips: impl IntoIterator<Item = impl Into<ObjectId>>, find: Find) -> Self {
424 Self::filtered(tips, find, |_| true)
425 }
426 }
427
428 impl<Find, Predicate> Simple<Find, Predicate>
429 where
430 Find: gix_object::Find,
431 Predicate: FnMut(&oid) -> bool,
432 {
433 pub fn filtered(
444 tips: impl IntoIterator<Item = impl Into<ObjectId>>,
445 find: Find,
446 mut predicate: Predicate,
447 ) -> Self {
448 let tips = tips.into_iter();
449 let mut state = State::default();
450 {
451 state.clear();
452 state.next.reserve(tips.size_hint().0);
453 for tip in tips.map(Into::into) {
454 if state.seen.insert(tip) && predicate(&tip) {
455 state.next.push_back(tip);
456 }
457 }
458 }
459 Self {
460 objects: find,
461 cache: None,
462 predicate,
463 state,
464 parents: Default::default(),
465 sorting: Default::default(),
466 }
467 }
468 }
469
470 impl<Find, Predicate> Simple<Find, Predicate> {
472 pub fn commit_iter(&self) -> CommitRefIter<'_> {
474 CommitRefIter::from_bytes(self.commit_data(), self.state.object_hash)
475 }
476
477 pub fn commit_data(&self) -> &[u8] {
479 &self.state.buf
480 }
481 }
482
483 impl<Find, Predicate> Iterator for Simple<Find, Predicate>
484 where
485 Find: gix_object::Find,
486 Predicate: FnMut(&oid) -> bool,
487 {
488 type Item = Result<Info, Error>;
489
490 fn next(&mut self) -> Option<Self::Item> {
491 if !self.state.hidden_tips.is_empty() {
492 let hidden_tips = std::mem::take(&mut self.state.hidden_tips);
493 if let Err(err) = self.compute_hidden_frontier(hidden_tips) {
494 self.state.queue.clear();
495 self.state.next.clear();
496 return Some(Err(err));
497 }
498 }
499 if matches!(self.parents, Parents::First) {
500 self.next_by_topology()
501 } else {
502 match self.sorting {
503 Sorting::BreadthFirst => self.next_by_topology(),
504 Sorting::ByCommitTime(order) => self.next_by_commit_date(order, None),
505 Sorting::ByCommitTimeCutoff { seconds, order } => self.next_by_commit_date(order, seconds.into()),
506 }
507 }
508 }
509 }
510
511 impl<Find, Predicate> Simple<Find, Predicate>
513 where
514 Find: gix_object::Find,
515 Predicate: FnMut(&oid) -> bool,
516 {
517 fn next_by_commit_date(
518 &mut self,
519 order: CommitTimeOrder,
520 cutoff: Option<SecondsSinceUnixEpoch>,
521 ) -> Option<Result<Info, Error>> {
522 let state = &mut self.state;
523 let next = &mut state.queue;
524
525 loop {
526 let (commit_time, oid) = match next.pop()? {
527 (Ok(t) | Err(Reverse(t)), o) => (t, o),
528 };
529 state.object_hash = oid.kind();
530 if state.hidden.contains_key(&oid) {
531 continue;
532 }
533 let mut parents: ParentIds = Default::default();
534
535 match super::super::find(self.cache.as_ref(), &self.objects, &oid, &mut state.buf) {
536 Ok(Either::CachedCommit(commit)) => {
537 if !collect_parents(&mut state.parent_ids, self.cache.as_ref(), commit.iter_parents()) {
538 self.cache = None;
540 return self.next_by_commit_date(order, cutoff);
541 }
542 for (id, parent_commit_time) in state.parent_ids.drain(..) {
543 parents.push(id);
544 insert_into_seen_and_queue(
545 &mut state.seen,
546 &state.hidden,
547 id,
548 &mut self.predicate,
549 next,
550 order,
551 cutoff,
552 || parent_commit_time,
553 );
554 }
555 }
556 Ok(Either::CommitRefIter(commit_iter)) => {
557 for token in commit_iter {
558 match token {
559 Ok(gix_object::commit::ref_iter::Token::Tree { .. }) => continue,
560 Ok(gix_object::commit::ref_iter::Token::Parent { id }) => {
561 parents.push(id);
562 insert_into_seen_and_queue(
563 &mut state.seen,
564 &state.hidden,
565 id,
566 &mut self.predicate,
567 next,
568 order,
569 cutoff,
570 || {
571 let parent =
572 self.objects.find_commit_iter(id.as_ref(), &mut state.parents_buf).ok();
573 parent
574 .and_then(|parent| {
575 parent.committer().ok().map(|committer| committer.seconds())
576 })
577 .unwrap_or_default()
578 },
579 );
580 }
581 Ok(_unused_token) => break,
582 Err(err) => return Some(Err(err.into())),
583 }
584 }
585 }
586 Err(err) => return Some(Err(err.into())),
587 }
588
589 return Some(Ok(Info {
590 id: oid,
591 parent_ids: parents,
592 commit_time: Some(commit_time),
593 }));
594 }
595 }
596
597 fn next_by_topology(&mut self) -> Option<Result<Info, Error>> {
598 let state = &mut self.state;
599 let next = &mut state.next;
600
601 loop {
602 let oid = next.pop_front()?;
603 state.object_hash = oid.kind();
604 if state.hidden.contains_key(&oid) {
605 continue;
606 }
607 let mut parents: ParentIds = Default::default();
608
609 match super::super::find(self.cache.as_ref(), &self.objects, &oid, &mut state.buf) {
610 Ok(Either::CachedCommit(commit)) => {
611 if !collect_parents(&mut state.parent_ids, self.cache.as_ref(), commit.iter_parents()) {
612 self.cache = None;
614 return self.next_by_topology();
615 }
616
617 for (pid, _commit_time) in state.parent_ids.drain(..) {
618 parents.push(pid);
619 insert_into_seen_and_next(&mut state.seen, &state.hidden, pid, &mut self.predicate, next);
620 if matches!(self.parents, Parents::First) {
621 break;
622 }
623 }
624 }
625 Ok(Either::CommitRefIter(commit_iter)) => {
626 for token in commit_iter {
627 match token {
628 Ok(gix_object::commit::ref_iter::Token::Tree { .. }) => continue,
629 Ok(gix_object::commit::ref_iter::Token::Parent { id: pid }) => {
630 parents.push(pid);
631 insert_into_seen_and_next(
632 &mut state.seen,
633 &state.hidden,
634 pid,
635 &mut self.predicate,
636 next,
637 );
638 if matches!(self.parents, Parents::First) {
639 break;
640 }
641 }
642 Ok(_a_token_past_the_parents) => break,
643 Err(err) => return Some(Err(err.into())),
644 }
645 }
646 }
647 Err(err) => return Some(Err(err.into())),
648 }
649
650 return Some(Ok(Info {
651 id: oid,
652 parent_ids: parents,
653 commit_time: None,
654 }));
655 }
656 }
657 }
658
659 fn insert_into_seen_and_next(
660 seen: &mut gix_hashtable::HashSet<ObjectId>,
661 hidden: &gix_revwalk::graph::IdMap<()>,
662 parent_id: ObjectId,
663 predicate: &mut impl FnMut(&oid) -> bool,
664 next: &mut VecDeque<ObjectId>,
665 ) {
666 if hidden.contains_key(&parent_id) {
667 return;
668 }
669 if seen.insert(parent_id) && predicate(&parent_id) {
670 next.push_back(parent_id);
671 }
672 }
673
674 #[allow(clippy::too_many_arguments)]
675 fn insert_into_seen_and_queue(
676 seen: &mut gix_hashtable::HashSet<ObjectId>,
677 hidden: &gix_revwalk::graph::IdMap<()>,
678 parent_id: ObjectId,
679 predicate: &mut impl FnMut(&oid) -> bool,
680 queue: &mut CommitDateQueue,
681 order: CommitTimeOrder,
682 cutoff: Option<SecondsSinceUnixEpoch>,
683 get_parent_commit_time: impl FnOnce() -> gix_date::SecondsSinceUnixEpoch,
684 ) {
685 if hidden.contains_key(&parent_id) {
686 return;
687 }
688 if seen.insert(parent_id) && predicate(&parent_id) {
689 let parent_commit_time = get_parent_commit_time();
690 let key = to_queue_key(parent_commit_time, order);
691 match cutoff {
692 Some(cutoff_older_than) if parent_commit_time < cutoff_older_than => {}
693 Some(_) | None => queue.insert(key, parent_id),
694 }
695 }
696 }
697}
698
699fn collect_parents(
700 dest: &mut SmallVec<[(gix_hash::ObjectId, gix_date::SecondsSinceUnixEpoch); 2]>,
701 cache: Option<&gix_commitgraph::Graph>,
702 parents: gix_commitgraph::file::commit::Parents<'_>,
703) -> bool {
704 dest.clear();
705 let cache = cache.as_ref().expect("parents iter is available, backed by `cache`");
706 for parent_id in parents {
707 match parent_id {
708 Ok(pos) => dest.push({
709 let parent = cache.commit_at(pos);
710 (
711 parent.id().to_owned(),
712 parent.committer_timestamp() as gix_date::SecondsSinceUnixEpoch,
713 )
714 }),
715 Err(_err) => return false,
716 }
717 }
718 true
719}