Installs pre-commit hooks for OCaml projects that run dune fmt automatically
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

precommit: optimize fix command with commit range and parallelism

- Add find_oldest_ai_commit to locate the first commit needing rewrite
- Use commit range (oldest^..HEAD) instead of rewriting entire history
- Process multiple repos in parallel with Eio.Fiber.all

This dramatically speeds up `precommit fix` for repos where AI
attribution is only in recent commits.

+72 -22
+23 -20
bin/main.ml
··· 356 356 info "Aborted"; 357 357 exit 0 358 358 end; 359 - let fixed = ref 0 in 360 - let errors = ref 0 in 361 - List.iter 362 - (fun d -> 363 - let backup = Precommit.backup_branch ~process_mgr ~fs d in 364 - success "%s: backed up to %s" d backup; 365 - match Precommit.rewrite_ai_attribution ~process_mgr ~fs d with 366 - | Ok _ -> 367 - incr fixed; 368 - success "%s: attribution removed" d 369 - | Error msg -> 370 - incr errors; 371 - error "%s" msg) 372 - affected; 359 + let fixed = Atomic.make 0 in 360 + let errors = Atomic.make 0 in 361 + Eio.Fiber.all 362 + (List.map 363 + (fun d () -> 364 + let backup = Precommit.backup_branch ~process_mgr ~fs d in 365 + success "%s: backed up to %s" d backup; 366 + match Precommit.rewrite_ai_attribution ~process_mgr ~fs d with 367 + | Ok _ -> 368 + Atomic.incr fixed; 369 + success "%s: attribution removed" d 370 + | Error msg -> 371 + Atomic.incr errors; 372 + error "%s" msg) 373 + affected); 373 374 Format.pp_print_newline Format.std_formatter (); 374 - if !errors > 0 then begin 375 - error "%d repo%s fixed, %d error%s" !fixed 376 - (if !fixed = 1 then "" else "s") 377 - !errors 378 - (if !errors = 1 then "" else "s"); 375 + let n_fixed = Atomic.get fixed in 376 + let n_errors = Atomic.get errors in 377 + if n_errors > 0 then begin 378 + error "%d repo%s fixed, %d error%s" n_fixed 379 + (if n_fixed = 1 then "" else "s") 380 + n_errors 381 + (if n_errors = 1 then "" else "s"); 379 382 exit 1 380 383 end 381 - else success "%d repo%s fixed" !fixed (if !fixed = 1 then "" else "s") 384 + else success "%d repo%s fixed" n_fixed (if n_fixed = 1 then "" else "s") 382 385 end 383 386 384 387 let yes =
+49 -2
lib/precommit.ml
··· 332 332 in 333 333 backup_name 334 334 335 + let find_oldest_ai_commit ~process_mgr ~fs dir = 336 + let grep_args = 337 + ai_message_patterns 338 + |> List.map (fun p -> "--grep='" ^ p ^ "'") 339 + |> String.concat " " 340 + in 341 + let msg_cmd = 342 + Printf.sprintf "git log --format='%%H' --reverse %s 2>/dev/null | head -1" 343 + grep_args 344 + in 345 + let author_cmd = 346 + ai_author_patterns 347 + |> List.map (fun a -> 348 + Printf.sprintf 349 + "git log --format='%%H' --reverse --author='%s' 2>/dev/null | head -1" 350 + a) 351 + |> String.concat "; " 352 + in 353 + let msg_oldest = 354 + match run_in_dir_opt ~process_mgr ~fs dir msg_cmd with 355 + | Ok (h :: _) when h <> "" -> Some h 356 + | _ -> None 357 + in 358 + let author_oldest = 359 + match run_in_dir_opt ~process_mgr ~fs dir author_cmd with 360 + | Ok lines -> List.find_opt (fun h -> h <> "") lines 361 + | _ -> None 362 + in 363 + match (msg_oldest, author_oldest) with 364 + | Some m, Some a -> ( 365 + (* Find which is older by checking commit order *) 366 + let cmd = 367 + Printf.sprintf 368 + "git merge-base --is-ancestor %s %s && echo %s || echo %s" m a m a 369 + in 370 + match run_in_dir_opt ~process_mgr ~fs dir cmd with 371 + | Ok (h :: _) -> Some h 372 + | _ -> Some m) 373 + | Some h, None | None, Some h -> Some h 374 + | None, None -> None 375 + 335 376 let rewrite_ai_attribution ~process_mgr ~fs dir = 336 377 if not (file_exists ~fs (Filename.concat dir ".git")) then 337 378 Error (Printf.sprintf "%s: No .git directory found" dir) 338 379 else 380 + (* Find the oldest commit with AI attribution to limit rewrite scope *) 381 + let commit_range = 382 + match find_oldest_ai_commit ~process_mgr ~fs dir with 383 + | Some oldest -> Printf.sprintf "%s^..HEAD" oldest 384 + | None -> "HEAD" 385 + in 339 386 (* Build sed command to delete all AI attribution patterns from messages. 340 387 Escape forward slashes in patterns for sed compatibility. *) 341 388 let escape_slashes s = ··· 368 415 let cmd = 369 416 Printf.sprintf 370 417 "FILTER_BRANCH_SQUELCH_WARNING=1 git filter-branch -f --env-filter \ 371 - '%s' --msg-filter \"sed %s\" -- HEAD 2>&1" 372 - env_filter sed_args 418 + '%s' --msg-filter \"sed %s\" -- %s 2>&1" 419 + env_filter sed_args commit_range 373 420 in 374 421 match run_in_dir_opt ~process_mgr ~fs dir cmd with 375 422 | Error e -> Error (Printf.sprintf "%s: %s" dir e)