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: recursive git project scanning and unified check table

- Add find_git_projects to recursively discover git repos from a root dir
- Update collect_dirs to use recursive scanning instead of one-level list_subdirs
- Replace per-project bullet+table check output with a single table indexed by project

+58 -34
+39 -34
bin/main.ml
··· 73 73 74 74 let collect_dirs ~fs ~recursive dirs = 75 75 if recursive then 76 - List.concat_map 77 - (fun d -> 78 - let subs = Precommit.list_subdirs ~fs d in 79 - if subs = [] then [ d ] else subs) 80 - dirs 76 + List.concat_map (fun d -> Precommit.find_git_projects ~fs d) dirs 81 77 else dirs 82 78 83 79 (* {1 Init command} *) ··· 227 223 let dirs = collect_dirs ~fs ~recursive dirs in 228 224 let total_commits = ref 0 in 229 225 let repos_with_issues = ref 0 in 230 - (* Get terminal width, default to 80 if not available *) 231 226 let term_width = get_terminal_width () in 232 - (* Reserve space for: border (2) + hash column (~9) + padding (4) *) 233 - let subject_max = max 20 (term_width - 15) in 227 + (* Reserve space for: border + project column + hash column + padding *) 228 + let subject_max = max 20 (term_width - 35) in 229 + let all_rows = ref [] in 234 230 List.iter 235 231 (fun d -> 236 232 let commits = Precommit.check_ai_attribution ~process_mgr ~fs d in 237 233 if commits <> [] then begin 238 234 incr repos_with_issues; 239 235 total_commits := !total_commits + List.length commits; 240 - Fmt.pf Fmt.stdout "@.%a %a@." 241 - Fmt.(styled (`Fg `Red) string) 242 - "●" 243 - Fmt.(styled `Bold string) 244 - d; 245 - let rows = 246 - List.map 247 - (fun (c : Precommit.ai_commit) -> 248 - [ 249 - Tty.Span.styled Tty.Style.(fg Tty.Color.yellow) c.hash; 250 - Tty.Span.text (truncate_subject subject_max c.subject); 251 - ]) 252 - commits 253 - in 254 - let table = 255 - Tty.Table.( 256 - of_rows ~border:Tty.Border.rounded 257 - [ column ~align:`Left "hash"; column ~align:`Left "subject" ] 258 - rows) 259 - in 260 - Tty.Table.pp Format.std_formatter table; 261 - Format.pp_print_newline Format.std_formatter () 236 + let first = ref true in 237 + List.iter 238 + (fun (c : Precommit.ai_commit) -> 239 + let project_cell = 240 + if !first then begin 241 + first := false; 242 + Tty.Span.styled Tty.Style.bold d 243 + end 244 + else Tty.Span.text "" 245 + in 246 + all_rows := 247 + !all_rows 248 + @ [ 249 + [ 250 + project_cell; 251 + Tty.Span.styled Tty.Style.(fg Tty.Color.yellow) c.hash; 252 + Tty.Span.text (truncate_subject subject_max c.subject); 253 + ]; 254 + ]) 255 + commits 262 256 end) 263 257 dirs; 258 + if !all_rows <> [] then begin 259 + let table = 260 + Tty.Table.( 261 + of_rows ~border:Tty.Border.rounded 262 + [ 263 + column ~align:`Left "project"; 264 + column ~align:`Left "hash"; 265 + column ~align:`Left "subject"; 266 + ] 267 + !all_rows) 268 + in 269 + Tty.Table.pp Format.std_formatter table; 270 + Format.pp_print_newline Format.std_formatter () 271 + end; 264 272 (* Summary *) 265 273 if !total_commits > 0 then begin 266 - Fmt.pf Fmt.stdout "@.%a@." Fmt.(styled `Bold string) "Summary:"; 267 - Fmt.pf Fmt.stdout " %a %d commit%s with AI attribution in %d repo%s@." 268 - Fmt.(styled (`Fg `Red) string) 269 - "✗" !total_commits 274 + error "%d commit%s with AI attribution in %d repo%s" !total_commits 270 275 (if !total_commits = 1 then "" else "s") 271 276 !repos_with_issues 272 277 (if !repos_with_issues = 1 then "" else "s");
+14
lib/precommit.ml
··· 196 196 if is_directory ~fs path then Some path else None) 197 197 |> List.sort String.compare 198 198 199 + let rec find_git_projects ~fs dir = 200 + let git_dir = Filename.concat dir ".git" in 201 + if file_exists ~fs git_dir then [ dir ] 202 + else 203 + let entries = try Eio.Path.read_dir Eio.Path.(fs / dir) with _ -> [] in 204 + entries 205 + |> List.filter_map (fun name -> 206 + if String.length name > 0 && name.[0] = '.' then None 207 + else 208 + let path = Filename.concat dir name in 209 + if is_directory ~fs path then Some path else None) 210 + |> List.sort String.compare 211 + |> List.concat_map (fun sub -> find_git_projects ~fs sub) 212 + 199 213 let run_in_dir ~process_mgr ~fs dir cmd = 200 214 let cwd = Eio.Path.(fs / dir) in 201 215 let output =
+5
lib/precommit.mli
··· 72 72 val list_subdirs : fs:_ Eio.Path.t -> string -> string list 73 73 (** [list_subdirs ~fs dir] lists subdirectories (excluding hidden ones). *) 74 74 75 + val find_git_projects : fs:_ Eio.Path.t -> string -> string list 76 + (** [find_git_projects ~fs dir] recursively scans [dir] for directories 77 + containing a [.git] entry. Stops recursing into a directory once a [.git] is 78 + found. Hidden directories are skipped. *) 79 + 75 80 val check_all : fs:_ Eio.Path.t -> string list -> (string * hook_status) list 76 81 (** [check_all ~fs dirs] checks hook status for all directories and returns a 77 82 list of (directory, status) pairs. *)