Installs pre-commit hooks for OCaml projects that run dune fmt automatically
at main 136 lines 4.9 kB view raw
1(** Pre-commit hook initialization for OCaml projects. 2 3 Installs git hooks directly without requiring the pre-commit tool. *) 4 5(** {1 Context} *) 6 7type ctx 8(** Filesystem context for operations. Contains the working directory for 9 relative paths and the full filesystem for walking up to parent directories. 10*) 11 12val ctx : cwd:_ Eio.Path.t -> fs:_ Eio.Path.t -> ctx 13(** [ctx ~cwd ~fs] creates a context from the working directory and full 14 filesystem paths. *) 15 16(** {1 Hook Templates} *) 17 18val pre_commit_hook : string 19(** Shell script for the pre-commit hook. Runs [dune fmt] on staged OCaml files 20 and fails if formatting changes are needed. *) 21 22val commit_msg_hook : string 23(** Shell script for the commit-msg hook. Checks for emojis (rejected) and 24 removes lines containing "claude" (case-insensitive). *) 25 26(** {1 Types} *) 27 28type hook_status = { 29 has_pre_commit : bool; 30 has_commit_msg : bool; 31 has_ocamlformat : bool; 32 formatting_disabled : bool; 33 is_ocaml_project : bool; 34 is_git_repo : bool; 35} 36(** Status of hooks in a directory. [formatting_disabled] is [true] if 37 dune-project contains "(formatting disabled)". *) 38 39(** {1 Operations} *) 40 41type hooks = { fmt : bool; ai : bool } 42(** Which hooks to install. *) 43 44val all_hooks : hooks 45(** Both fmt and ai hooks. *) 46 47val init : 48 ctx -> 49 dry_run:bool -> 50 force:bool -> 51 hooks:hooks -> 52 unit -> 53 (unit, string) result 54(** [init ctx ~dry_run ~force ~hooks ()] installs git hooks in the current 55 repository. 56 57 Creates (depending on [hooks]): 58 - [.git/hooks/pre-commit] - runs [dune fmt] on staged OCaml files 59 - [.git/hooks/commit-msg] - checks for emojis and removes Claude attribution 60 - [.ocamlformat] - if missing, creates with default version (unless [force]) 61 62 If [dry_run] is [true], prints what would be done without making changes. If 63 [force] is [true], skips dune-project check and .ocamlformat creation. 64 65 Returns [Error msg] if not in a git repository. *) 66 67val init_in_dir : 68 ctx -> 69 dry_run:bool -> 70 force:bool -> 71 hooks:hooks -> 72 string -> 73 (unit, string) result 74(** [init_in_dir ctx ~dry_run ~force ~hooks dir] installs hooks in the specified 75 directory. *) 76 77val status : ctx -> unit -> hook_status 78(** [status ctx ()] checks hook status in the current directory. *) 79 80val status_in_dir : ctx -> string -> hook_status 81(** [status_in_dir ctx dir] checks hook status in the specified directory. *) 82 83val list_subdirs : fs:_ Eio.Path.t -> string -> string list 84(** [list_subdirs ~fs dir] lists subdirectories (excluding hidden ones). *) 85 86val git_projects : ctx -> string -> string list 87(** [git_projects ctx dir] recursively scans [dir] for directories containing a 88 [.git] entry. Stops recursing into a directory once a [.git] is found. 89 Hidden directories are skipped. If [dir] is inside a git repository (by 90 checking parent directories), it is included even if it doesn't contain 91 [.git] directly. *) 92 93val check_all : ctx -> string list -> (string * hook_status) list 94(** [check_all ctx dirs] checks hook status for all directories and returns a 95 list of (directory, status) pairs. *) 96 97(** {1 Tabular Output} *) 98 99val format_status_header : unit -> string 100(** Returns the header row for the status table. *) 101 102val format_status_separator : unit -> string 103(** Returns a separator line for the status table. *) 104 105val format_status_row : string -> hook_status -> string 106(** [format_status_row dir status] formats a single status row. *) 107 108val pp_status_table : Format.formatter -> (string * hook_status) list -> unit 109(** [pp_status_table ppf statuses] prints a formatted table of hook statuses. *) 110 111(** {1 History Checks} *) 112 113type ai_commit = { hash : string; subject : string } 114(** A commit with AI attribution. *) 115 116val check_ai_attribution : ctx -> string -> ai_commit list 117(** [check_ai_attribution ctx dir] uses ocaml-git to find commits that contain 118 AI attribution patterns in the commit message. Only checks commits authored 119 by the current user (determined from git config). *) 120 121(** {1 History Rewriting} *) 122 123val current_branch : ctx -> string -> string option 124(** [current_branch ctx dir] returns the current branch name, or [None] if HEAD 125 is detached or no git root is found. Uses ocaml-git. *) 126 127val backup_branch : ctx -> string -> string 128(** [backup_branch ctx dir] creates a backup branch named 129 [backup/<branch>-before-fix-<timestamp>] and returns the backup name. Uses 130 ocaml-git. Raises [Failure] if no git root is found. *) 131 132val rewrite_ai_attribution : ctx -> string -> (int, string) result 133(** [rewrite_ai_attribution ctx dir] uses ocaml-git to rewrite commits and 134 remove [Co-Authored-By:.*claude] lines from commit messages. Only rewrites 135 commits authored by the current user. Returns [Ok n] where [n] is the number 136 of commits rewritten, or [Error msg] on failure. *)