11use super :: { CommitId , SharedCommitFilterFn } ;
22use crate :: error:: Result ;
3- use git2:: { Commit , Oid , Repository } ;
3+ use git2:: { Repository , Revwalk , Sort } ;
44use gix:: revision:: Walk ;
5- use std:: {
6- cmp:: Ordering ,
7- collections:: { BinaryHeap , HashSet } ,
8- } ;
95
10- struct TimeOrderedCommit < ' a > ( Commit < ' a > ) ;
11-
12- impl Eq for TimeOrderedCommit < ' _ > { }
13-
14- impl PartialEq for TimeOrderedCommit < ' _ > {
15- fn eq ( & self , other : & Self ) -> bool {
16- self . 0 . time ( ) . eq ( & other. 0 . time ( ) )
17- }
18- }
19-
20- impl PartialOrd for TimeOrderedCommit < ' _ > {
21- fn partial_cmp ( & self , other : & Self ) -> Option < Ordering > {
22- Some ( self . cmp ( other) )
23- }
24- }
25-
26- impl Ord for TimeOrderedCommit < ' _ > {
27- fn cmp ( & self , other : & Self ) -> Ordering {
28- self . 0 . time ( ) . cmp ( & other. 0 . time ( ) )
29- }
30- }
31-
32- ///
6+ /// Visit commits in topological order, and date order where possible.
7+ /// If a filter is provided, only commits that pass the filter are returned.
338pub struct LogWalker < ' a > {
34- commits : BinaryHeap < TimeOrderedCommit < ' a > > ,
35- visited : HashSet < Oid > ,
9+ /// Revision walk engine
10+ walk : Revwalk < ' a > ,
11+ /// Total number of commits that have been visited
12+ visited_count : usize ,
13+ /// Upper limit on buffer size
3614 limit : usize ,
15+ /// The source of commits
3716 repo : & ' a Repository ,
17+ /// Filter on which commits will be returned when reading
3818 filter : Option < SharedCommitFilterFn > ,
3919}
4020
4121impl < ' a > LogWalker < ' a > {
42- ///
22+ /// Create a new log walker
23+ /// with an upper limit on number of commits visited in one batch.
4324 pub fn new ( repo : & ' a Repository , limit : usize ) -> Result < Self > {
44- let c = repo. head ( ) ?. peel_to_commit ( ) ?;
25+ let head = repo. head ( ) ?. peel_to_commit ( ) ?;
4526
46- let mut commits = BinaryHeap :: with_capacity ( 10 ) ;
47- commits. push ( TimeOrderedCommit ( c) ) ;
27+ let mut walk = repo. revwalk ( ) ?;
28+ // TOPOLOGICAL + TIME guarantees parents come after children,
29+ // and ties/independent branches are ordered by timestamp (--date-order).
30+ walk. set_sorting ( Sort :: TOPOLOGICAL | Sort :: TIME ) ?;
31+ walk. push ( head. id ( ) ) ?;
4832
4933 Ok ( Self {
50- commits,
34+ walk,
35+ visited_count : 0 ,
5136 limit,
52- visited : HashSet :: with_capacity ( 1000 ) ,
5337 repo,
5438 filter : None ,
5539 } )
5640 }
5741
58- ///
59- pub fn visited ( & self ) -> usize {
60- self . visited . len ( )
42+ /// Number of visited commits
43+ pub const fn visited ( & self ) -> usize {
44+ self . visited_count
6145 }
6246
63- ///
47+ /// Add a filter to use when reading commits
6448 #[ must_use]
6549 pub fn filter (
6650 self ,
@@ -69,16 +53,14 @@ impl<'a> LogWalker<'a> {
6953 Self { filter, ..self }
7054 }
7155
72- ///
56+ /// Get a batch of commits
7357 pub fn read ( & mut self , out : & mut Vec < CommitId > ) -> Result < usize > {
7458 let mut count = 0_usize ;
7559
76- while let Some ( c) = self . commits . pop ( ) {
77- for p in c. 0 . parents ( ) {
78- self . visit ( p) ;
79- }
60+ for oid_result in self . walk . by_ref ( ) {
61+ let oid = oid_result?;
62+ let id: CommitId = oid. into ( ) ;
8063
81- let id: CommitId = c. 0 . id ( ) . into ( ) ;
8264 let commit_should_be_included =
8365 if let Some ( ref filter) = self . filter {
8466 filter ( self . repo , & id) ?
@@ -90,6 +72,7 @@ impl<'a> LogWalker<'a> {
9072 out. push ( id) ;
9173 }
9274
75+ self . visited_count += 1 ;
9376 count += 1 ;
9477 if count == self . limit {
9578 break ;
@@ -98,13 +81,6 @@ impl<'a> LogWalker<'a> {
9881
9982 Ok ( count)
10083 }
101-
102- //
103- fn visit ( & mut self , c : Commit < ' a > ) {
104- if self . visited . insert ( c. id ( ) ) {
105- self . commits . push ( TimeOrderedCommit ( c) ) ;
106- }
107- }
10884}
10985
11086/// This is separate from `LogWalker` because filtering currently (June 2024) works through
0 commit comments