@@ -120,13 +120,25 @@ pub fn should_exclude_file(path: &str) -> bool {
120120 false
121121}
122122
123+ // Check if a commit matches the author filter pattern (case-insensitive partial match)
124+ fn matches_author ( commit : & Git2Commit , pattern : & str ) -> bool {
125+ let author = commit. author ( ) ;
126+ let name = author. name ( ) . unwrap_or ( "" ) ;
127+ let email = author. email ( ) . unwrap_or ( "" ) ;
128+ let pattern_lower = pattern. to_lowercase ( ) ;
129+
130+ name. to_lowercase ( ) . contains ( & pattern_lower)
131+ || email. to_lowercase ( ) . contains ( & pattern_lower)
132+ }
133+
123134pub struct GitRepository {
124135 repo : Repository ,
125136 commit_cache : RefCell < Option < Vec < Oid > > > ,
126137 // Shared index for both cache-based playback (asc/desc) and range playback.
127138 // These modes are mutually exclusive based on CLI arguments.
128139 commit_index : RefCell < usize > ,
129140 commit_range : RefCell < Option < Vec < Oid > > > ,
141+ author_filter : Option < String > ,
130142}
131143
132144#[ derive( Debug , Clone ) ]
@@ -252,6 +264,7 @@ impl GitRepository {
252264 commit_cache : RefCell :: new ( None ) ,
253265 commit_index : RefCell :: new ( 0 ) ,
254266 commit_range : RefCell :: new ( None ) ,
267+ author_filter : None ,
255268 } )
256269 }
257270
@@ -267,35 +280,16 @@ impl GitRepository {
267280 }
268281
269282 pub fn random_commit ( & self ) -> Result < CommitMetadata > {
270- // Check if cache exists, if not populate it
271- let mut cache = self . commit_cache . borrow_mut ( ) ;
272- if cache. is_none ( ) {
273- let mut revwalk = self . repo . revwalk ( ) ?;
274- revwalk. push_head ( ) ?;
275-
276- let mut candidates = Vec :: new ( ) ;
277- for oid in revwalk. filter_map ( |oid| oid. ok ( ) ) {
278- if let Ok ( commit) = self . repo . find_commit ( oid) {
279- if commit. parent_count ( ) <= 1 {
280- candidates. push ( oid) ;
281- }
282- }
283- }
284-
285- if candidates. is_empty ( ) {
286- anyhow:: bail!( "No non-merge commits found in repository" ) ;
287- }
288-
289- * cache = Some ( candidates) ;
290- }
283+ self . populate_cache ( ) ?;
291284
285+ let cache = self . commit_cache . borrow ( ) ;
292286 let candidates = cache. as_ref ( ) . unwrap ( ) ;
287+
293288 let selected_oid = candidates
294289 . get ( rand:: rng ( ) . random_range ( 0 ..candidates. len ( ) ) )
295290 . context ( "Failed to select random commit" ) ?;
296291
297292 let commit = self . repo . find_commit ( * selected_oid) ?;
298- drop ( cache) ; // Release the borrow before calling extract_metadata_with_changes
299293 Self :: extract_metadata_with_changes ( & self . repo , & commit)
300294 }
301295
@@ -323,8 +317,6 @@ impl GitRepository {
323317 * index += 1 ;
324318
325319 let commit = self . repo . find_commit ( * selected_oid) ?;
326- drop ( index) ;
327- drop ( cache) ;
328320 Self :: extract_metadata_with_changes ( & self . repo , & commit)
329321 }
330322
@@ -349,15 +341,17 @@ impl GitRepository {
349341 * index += 1 ;
350342
351343 let commit = self . repo . find_commit ( * selected_oid) ?;
352- drop ( index) ;
353- drop ( cache) ;
354344 Self :: extract_metadata_with_changes ( & self . repo , & commit)
355345 }
356346
357347 pub fn reset_index ( & self ) {
358348 * self . commit_index . borrow_mut ( ) = 0 ;
359349 }
360350
351+ pub fn set_author_filter ( & mut self , author : Option < String > ) {
352+ self . author_filter = author;
353+ }
354+
361355 pub fn set_commit_range ( & self , range : & str ) -> Result < ( ) > {
362356 let commits = self . parse_commit_range ( range) ?;
363357 * self . commit_range . borrow_mut ( ) = Some ( commits) ;
@@ -382,8 +376,6 @@ impl GitRepository {
382376 * index += 1 ;
383377
384378 let commit = self . repo . find_commit ( * selected_oid) ?;
385- drop ( index) ;
386- drop ( range) ;
387379 Self :: extract_metadata_with_changes ( & self . repo , & commit)
388380 }
389381
@@ -406,8 +398,6 @@ impl GitRepository {
406398 * index += 1 ;
407399
408400 let commit = self . repo . find_commit ( * selected_oid) ?;
409- drop ( index) ;
410- drop ( range) ;
411401 Self :: extract_metadata_with_changes ( & self . repo , & commit)
412402 }
413403
@@ -424,10 +414,42 @@ impl GitRepository {
424414 . context ( "Failed to select random commit" ) ?;
425415
426416 let commit = self . repo . find_commit ( * selected_oid) ?;
427- drop ( range) ;
428417 Self :: extract_metadata_with_changes ( & self . repo , & commit)
429418 }
430419
420+ // Collect non-merge commits from a revwalk, applying author filter if set
421+ fn collect_commits_from_revwalk (
422+ & self ,
423+ revwalk : git2:: Revwalk ,
424+ context : & str ,
425+ ) -> Result < Vec < Oid > > {
426+ let mut commits = Vec :: new ( ) ;
427+ for oid in revwalk. filter_map ( |oid| oid. ok ( ) ) {
428+ if let Ok ( commit) = self . repo . find_commit ( oid) {
429+ if commit. parent_count ( ) <= 1 {
430+ if let Some ( ref pattern) = self . author_filter {
431+ if !matches_author ( & commit, pattern) {
432+ continue ;
433+ }
434+ }
435+ commits. push ( oid) ;
436+ }
437+ }
438+ }
439+
440+ if commits. is_empty ( ) {
441+ if self . author_filter . is_some ( ) {
442+ anyhow:: bail!(
443+ "No commits found matching the author filter {}" ,
444+ context
445+ ) ;
446+ }
447+ anyhow:: bail!( "No non-merge commits found {}" , context) ;
448+ }
449+
450+ Ok ( commits)
451+ }
452+
431453 fn parse_commit_range ( & self , range : & str ) -> Result < Vec < Oid > > {
432454 // Reject symmetric difference operator (not supported)
433455 if range. contains ( "..." ) {
@@ -467,15 +489,7 @@ impl GitRepository {
467489 revwalk. hide ( start_oid) ?;
468490 }
469491
470- let mut commits = Vec :: new ( ) ;
471- for oid in revwalk. filter_map ( |oid| oid. ok ( ) ) {
472- if let Ok ( commit) = self . repo . find_commit ( oid) {
473- if commit. parent_count ( ) <= 1 {
474- commits. push ( oid) ;
475- }
476- }
477- }
478-
492+ let mut commits = self . collect_commits_from_revwalk ( revwalk, "in range" ) ?;
479493 commits. reverse ( ) ;
480494 Ok ( commits)
481495 }
@@ -486,19 +500,7 @@ impl GitRepository {
486500 let mut revwalk = self . repo . revwalk ( ) ?;
487501 revwalk. push_head ( ) ?;
488502
489- let mut candidates = Vec :: new ( ) ;
490- for oid in revwalk. filter_map ( |oid| oid. ok ( ) ) {
491- if let Ok ( commit) = self . repo . find_commit ( oid) {
492- if commit. parent_count ( ) <= 1 {
493- candidates. push ( oid) ;
494- }
495- }
496- }
497-
498- if candidates. is_empty ( ) {
499- anyhow:: bail!( "No non-merge commits found in repository" ) ;
500- }
501-
503+ let candidates = self . collect_commits_from_revwalk ( revwalk, "in repository" ) ?;
502504 * cache = Some ( candidates) ;
503505 }
504506 Ok ( ( ) )
0 commit comments