@@ -16,6 +16,7 @@ use tracing::{debug, trace};
1616use unicode_width:: UnicodeWidthStr ;
1717
1818use crate :: cli:: reporter:: { HookInitReporter , HookInstallReporter , HookRunReporter } ;
19+ use crate :: cli:: run:: diff:: DiffTracker ;
1920use crate :: cli:: run:: keeper:: WorkTreeKeeper ;
2021use crate :: cli:: run:: {
2122 CollectOptions , FileTagCache , HookFileFilter , ProjectFiles , RunInput , Selectors ,
@@ -220,6 +221,7 @@ pub(crate) async fn run(
220221 show_diff_on_failure,
221222 fail_fast,
222223 dry_run,
224+ should_stash,
223225 verbose,
224226 printer,
225227 )
@@ -445,6 +447,7 @@ async fn run_hooks<'paths>(
445447 show_diff_on_failure : bool ,
446448 fail_fast : Option < bool > ,
447449 dry_run : bool ,
450+ worktree_cleaned : bool ,
448451 verbose : bool ,
449452 printer : Printer ,
450453) -> Result < ExitStatus > {
@@ -481,28 +484,30 @@ async fn run_hooks<'paths>(
481484 hooks. sort_by ( |a, b| a. priority . cmp ( & b. priority ) . then ( a. idx . cmp ( & b. idx ) ) ) ;
482485
483486 session. render_project_header ( project, projects_len) ?;
484- let mut prev_diff = None ;
487+ // The worktree is only known clean at the start of the whole run. Once
488+ // an earlier project leaves a diff behind, later projects need a fresh
489+ // per-project snapshot to avoid attributing that diff to their hooks.
490+ let mut diff_tracker = if worktree_cleaned && !session. file_modified {
491+ DiffTracker :: clean_baseline ( project. path ( ) )
492+ } else {
493+ DiffTracker :: unknown_baseline ( project. path ( ) )
494+ } ;
485495
486496 let project_fail_fast = fail_fast. or ( project. config ( ) . fail_fast ) . unwrap_or ( false ) ;
487497
488498 for group_hooks in PriorityGroups :: new ( hooks) {
489499 let group_may_modify_files =
490500 !session. dry_run && group_hooks. iter ( ) . any ( |hook| hooks:: may_modify_files ( hook) ) ;
491- if group_may_modify_files && prev_diff . is_none ( ) {
492- prev_diff = Some ( git :: get_diff ( project . path ( ) ) . await ? ) ;
493- }
501+ diff_tracker
502+ . prepare_for_group ( group_may_modify_files )
503+ . await ? ;
494504
495505 let group_results = session
496506 . run_priority_group ( group_hooks, & project_input, tag_cache)
497507 . await ?;
498508
499509 let hook_fail_fast = session
500- . finish_priority_group (
501- group_results,
502- project,
503- group_may_modify_files,
504- & mut prev_diff,
505- )
510+ . finish_priority_group ( group_results, group_may_modify_files, & mut diff_tracker)
506511 . await ?;
507512
508513 if !session. success && ( project_fail_fast || hook_fail_fast) {
@@ -614,26 +619,17 @@ impl<'a> HookRunSession<'a> {
614619 async fn finish_priority_group (
615620 & mut self ,
616621 mut group_results : Vec < RunResult > ,
617- project : & Project ,
618622 group_may_modify_files : bool ,
619- prev_diff : & mut Option < Vec < u8 > > ,
623+ diff_tracker : & mut DiffTracker < ' _ > ,
620624 ) -> Result < bool > {
621625 // Print results in a stable order (same order as config within the project).
622626 group_results. sort_unstable_by_key ( |a| a. hook . idx ) ;
623627
624628 // Check if any files were modified by this group of hooks.
625629 let all_skipped = group_results. iter ( ) . all ( |r| r. status . is_skipped ( ) ) ;
626- let group_modified_files = if group_may_modify_files && !all_skipped {
627- let prev_diff = prev_diff
628- . as_mut ( )
629- . expect ( "previous diff must be captured before file-modifying hooks run" ) ;
630- let curr_diff = git:: get_diff ( project. path ( ) ) . await ?;
631- let group_modified_files = curr_diff != * prev_diff;
632- * prev_diff = curr_diff;
633- group_modified_files
634- } else {
635- false
636- } ;
630+ let group_modified_files = diff_tracker
631+ . changed_after_group ( group_may_modify_files, all_skipped)
632+ . await ?;
637633
638634 self . file_modified |= group_modified_files;
639635
0 commit comments