A monorepo management tool for the agentic ages
1(** Git operations wrapped with Eio and robust error handling.
2
3 All git commands are executed via [Eio.Process] with proper logging
4 and error context. Errors are wrapped in [Eio.Exn.Io] with context
5 chains for debugging. *)
6
7(** {1 Error Types} *)
8
9type error =
10 | Command_failed of {
11 cmd : string list;
12 exit_code : int;
13 stdout : string;
14 stderr : string;
15 }
16 | Not_a_repository
17 | Remote_exists of string
18 | Remote_not_found of string
19 | Branch_exists of string
20 | Branch_not_found of string
21 | Merge_conflict of { branch : string; conflicting_files : string list }
22 | Rebase_conflict of { onto : string; hint : string }
23 | Uncommitted_changes
24 | Not_on_branch
25 | Detached_head
26
27val pp_error : Format.formatter -> error -> unit
28
29type Eio.Exn.err += E of error
30
31val err : error -> exn
32(** [err e] creates an [Eio.Exn.Io] exception with the given error. *)
33
34(** {1 Types} *)
35
36type proc_mgr = [ `Generic | `Unix ] Eio.Process.mgr_ty Eio.Resource.t
37type path = Eio.Fs.dir_ty Eio.Path.t
38
39(** {1 Low-level execution} *)
40
41val run :
42 proc_mgr:proc_mgr ->
43 ?cwd:path ->
44 ?audit:Audit.context ->
45 string list ->
46 (string, error) result
47(** [run ~proc_mgr args] executes [git args] and returns stdout on success.
48 If [audit] is provided, records the operation to the audit context. *)
49
50val run_exn :
51 proc_mgr:proc_mgr ->
52 ?cwd:path ->
53 ?audit:Audit.context ->
54 string list ->
55 string
56(** [run_exn ~proc_mgr args] executes [git args] and returns stdout.
57 Raises on failure with context. If [audit] is provided, records the operation. *)
58
59val run_lines :
60 proc_mgr:proc_mgr ->
61 ?cwd:path ->
62 ?audit:Audit.context ->
63 string list ->
64 string list
65(** [run_lines ~proc_mgr args] executes and splits output by newlines.
66 If [audit] is provided, records the operation. *)
67
68(** {1 Queries - Safe read-only operations} *)
69
70val is_repository : path -> bool
71(** [is_repository path] checks if [path] contains a [.git] directory. *)
72
73val current_branch :
74 proc_mgr:proc_mgr ->
75 cwd:path ->
76 string option
77(** [current_branch] returns [Some branch] if on a branch, [None] if detached. *)
78
79val current_branch_exn :
80 proc_mgr:proc_mgr ->
81 cwd:path ->
82 string
83(** [current_branch_exn] returns current branch or raises [Not_on_branch]. *)
84
85val current_head :
86 proc_mgr:proc_mgr ->
87 cwd:path ->
88 string
89(** [current_head] returns the current HEAD SHA. *)
90
91val has_uncommitted_changes :
92 proc_mgr:proc_mgr ->
93 cwd:path ->
94 bool
95(** [has_uncommitted_changes] returns true if there are staged or unstaged changes. *)
96
97val remote_exists :
98 proc_mgr:proc_mgr ->
99 cwd:path ->
100 string ->
101 bool
102(** [remote_exists ~proc_mgr ~cwd name] checks if remote [name] exists. *)
103
104val branch_exists :
105 proc_mgr:proc_mgr ->
106 cwd:path ->
107 string ->
108 bool
109(** [branch_exists ~proc_mgr ~cwd name] checks if branch [name] exists. *)
110
111val rev_parse :
112 proc_mgr:proc_mgr ->
113 cwd:path ->
114 string ->
115 string option
116(** [rev_parse ~proc_mgr ~cwd ref] returns the SHA for [ref], or [None]. *)
117
118val rev_parse_exn :
119 proc_mgr:proc_mgr ->
120 cwd:path ->
121 string ->
122 string
123(** [rev_parse_exn] returns SHA or raises. *)
124
125val rev_parse_short :
126 proc_mgr:proc_mgr ->
127 cwd:path ->
128 string ->
129 string
130(** [rev_parse_short] returns abbreviated SHA. *)
131
132val ls_remote_default_branch :
133 proc_mgr:proc_mgr ->
134 cwd:path ->
135 url:string ->
136 string
137(** [ls_remote_default_branch ~proc_mgr ~cwd ~url] detects the default branch of remote. *)
138
139val list_remotes :
140 proc_mgr:proc_mgr ->
141 cwd:path ->
142 string list
143(** [list_remotes] returns all remote names. *)
144
145val remote_url :
146 proc_mgr:proc_mgr ->
147 cwd:path ->
148 string ->
149 string option
150(** [remote_url ~proc_mgr ~cwd name] returns the URL for remote [name]. *)
151
152val log_oneline :
153 proc_mgr:proc_mgr ->
154 cwd:path ->
155 ?max_count:int ->
156 string ->
157 string ->
158 string list
159(** [log_oneline ~proc_mgr ~cwd from_ref to_ref] returns commit summaries. *)
160
161val diff_stat :
162 proc_mgr:proc_mgr ->
163 cwd:path ->
164 string ->
165 string ->
166 string
167(** [diff_stat ~proc_mgr ~cwd from_ref to_ref] returns diff statistics. *)
168
169val ls_tree :
170 proc_mgr:proc_mgr ->
171 cwd:path ->
172 tree:string ->
173 path:string ->
174 bool
175(** [ls_tree ~proc_mgr ~cwd ~tree ~path] checks if [path] exists in [tree]. *)
176
177val rev_list_count :
178 proc_mgr:proc_mgr ->
179 cwd:path ->
180 string ->
181 string ->
182 int
183(** [rev_list_count ~proc_mgr ~cwd from_ref to_ref] counts commits between refs. *)
184
185(** {1 Idempotent mutations - Safe to re-run} *)
186
187val ensure_remote :
188 proc_mgr:proc_mgr ->
189 cwd:path ->
190 name:string ->
191 url:string ->
192 [ `Created | `Existed | `Updated ]
193(** [ensure_remote] adds remote if missing, updates URL if different. *)
194
195val ensure_branch :
196 proc_mgr:proc_mgr ->
197 cwd:path ->
198 name:string ->
199 start_point:string ->
200 [ `Created | `Existed ]
201(** [ensure_branch] creates branch if it doesn't exist. *)
202
203val ensure_vendored_remotes :
204 proc_mgr:proc_mgr ->
205 cwd:path ->
206 Config.vendored_package list ->
207 int
208(** [ensure_vendored_remotes ~proc_mgr ~cwd packages] ensures remotes exist for
209 all vendored packages. Returns the number of remotes created.
210 Use this to recreate remotes after cloning a workspace. *)
211
212(** {1 State-changing operations} *)
213
214val init :
215 proc_mgr:proc_mgr ->
216 cwd:path ->
217 unit
218(** [init] initializes a new git repository. *)
219
220val fetch :
221 proc_mgr:proc_mgr ->
222 cwd:path ->
223 remote:string ->
224 unit
225(** [fetch] fetches from a remote. *)
226
227val fetch_with_tags :
228 proc_mgr:proc_mgr ->
229 cwd:path ->
230 remote:string ->
231 unit
232(** [fetch_with_tags] fetches from a remote including all tags. *)
233
234val resolve_branch_or_tag :
235 proc_mgr:proc_mgr ->
236 cwd:path ->
237 remote:string ->
238 ref_name:string ->
239 string
240(** [resolve_branch_or_tag] tries to resolve a ref first as a remote tracking
241 branch (remote/ref_name), then as a tag (refs/tags/ref_name). Returns the
242 resolved ref or raises an exception if neither exists. *)
243
244val checkout :
245 proc_mgr:proc_mgr ->
246 cwd:path ->
247 string ->
248 unit
249(** [checkout] switches to a branch or commit. *)
250
251val checkout_orphan :
252 proc_mgr:proc_mgr ->
253 cwd:path ->
254 string ->
255 unit
256(** [checkout_orphan] creates and switches to a new orphan branch. *)
257
258val read_tree_prefix :
259 proc_mgr:proc_mgr ->
260 cwd:path ->
261 prefix:string ->
262 tree:string ->
263 unit
264(** [read_tree_prefix] reads a tree into the index with a path prefix. *)
265
266val checkout_index :
267 proc_mgr:proc_mgr ->
268 cwd:path ->
269 unit
270(** [checkout_index] checks out files from the index to working directory. *)
271
272val rm_rf :
273 proc_mgr:proc_mgr ->
274 cwd:path ->
275 target:string ->
276 unit
277(** [rm_rf] removes files/directories from git tracking. *)
278
279val rm_cached_rf :
280 proc_mgr:proc_mgr ->
281 cwd:path ->
282 unit
283(** [rm_cached_rf] removes all files from index (for orphan branch setup). *)
284
285val add_all :
286 proc_mgr:proc_mgr ->
287 cwd:path ->
288 unit
289(** [add_all] stages all changes. *)
290
291val commit :
292 proc_mgr:proc_mgr ->
293 cwd:path ->
294 message:string ->
295 unit
296(** [commit] creates a commit with the given message. *)
297
298val commit_allow_empty :
299 proc_mgr:proc_mgr ->
300 cwd:path ->
301 message:string ->
302 unit
303(** [commit_allow_empty] creates a commit even if there are no changes. *)
304
305val branch_create :
306 proc_mgr:proc_mgr ->
307 cwd:path ->
308 name:string ->
309 start_point:string ->
310 unit
311(** [branch_create] creates a new branch at [start_point]. *)
312
313val branch_force :
314 proc_mgr:proc_mgr ->
315 cwd:path ->
316 name:string ->
317 point:string ->
318 unit
319(** [branch_force] moves branch to point (creates if needed). *)
320
321val remote_add :
322 proc_mgr:proc_mgr ->
323 cwd:path ->
324 name:string ->
325 url:string ->
326 unit
327(** [remote_add] adds a new remote. *)
328
329val remote_set_url :
330 proc_mgr:proc_mgr ->
331 cwd:path ->
332 name:string ->
333 url:string ->
334 unit
335(** [remote_set_url] updates the URL of an existing remote. *)
336
337val merge_allow_unrelated :
338 proc_mgr:proc_mgr ->
339 cwd:path ->
340 branch:string ->
341 message:string ->
342 (unit, [ `Conflict of string list ]) result
343(** [merge_allow_unrelated] merges with [--allow-unrelated-histories].
344 Returns [Error (`Conflict files)] if there are conflicts. *)
345
346val rebase :
347 proc_mgr:proc_mgr ->
348 cwd:path ->
349 onto:string ->
350 (unit, [ `Conflict of string ]) result
351(** [rebase] rebases current branch onto [onto].
352 Returns [Error (`Conflict hint)] if there are conflicts. *)
353
354val rebase_abort :
355 proc_mgr:proc_mgr ->
356 cwd:path ->
357 unit
358(** [rebase_abort] aborts an in-progress rebase. *)
359
360val merge_abort :
361 proc_mgr:proc_mgr ->
362 cwd:path ->
363 unit
364(** [merge_abort] aborts an in-progress merge. *)
365
366val reset_hard :
367 proc_mgr:proc_mgr ->
368 cwd:path ->
369 string ->
370 unit
371(** [reset_hard] does a hard reset to the given ref. *)
372
373val clean_fd :
374 proc_mgr:proc_mgr ->
375 cwd:path ->
376 unit
377(** [clean_fd] removes untracked files and directories. *)
378
379val filter_repo_to_subdirectory :
380 proc_mgr:proc_mgr ->
381 cwd:path ->
382 branch:string ->
383 subdirectory:string ->
384 unit
385(** [filter_repo_to_subdirectory ~proc_mgr ~cwd ~branch ~subdirectory]
386 rewrites the history of [branch] so all files are moved into [subdirectory].
387 Uses git-filter-repo for fast history rewriting. Preserves full commit history. *)
388
389val filter_repo_from_subdirectory :
390 proc_mgr:proc_mgr ->
391 cwd:path ->
392 branch:string ->
393 subdirectory:string ->
394 unit
395(** [filter_repo_from_subdirectory ~proc_mgr ~cwd ~branch ~subdirectory]
396 rewrites the history of [branch] extracting only files from [subdirectory]
397 and placing them at the repository root. This is the inverse of
398 [filter_repo_to_subdirectory]. Uses git-filter-repo --subdirectory-filter.
399 Preserves full commit history for files that were in the subdirectory. *)