forked from
gazagnaire.org/irmin
Persistent store with Git semantics: lazy reads, delayed writes, content-addressing
1(** Irmin CLI - content-addressed storage. *)
2
3open Cmdliner
4
5(* === Logging setup via vlog === *)
6
7let setup = Vlog.setup "irmin"
8
9(* === Common arguments === *)
10
11let repo =
12 let doc = "Repository directory." in
13 Arg.(value & opt string "." & info [ "r"; "repo" ] ~docv:"DIR" ~doc)
14
15let branch =
16 let doc = "Branch name." in
17 Arg.(value & opt string "main" & info [ "b"; "branch" ] ~docv:"NAME" ~doc)
18
19let output =
20 let doc = "Output format: $(b,human) for terminal, $(b,json) for scripts." in
21 Arg.(
22 value
23 & opt (enum [ ("human", `Human); ("json", `Json) ]) `Human
24 & info [ "o"; "output" ] ~docv:"FORMAT" ~doc)
25
26let message =
27 let doc = "Commit message." in
28 Arg.(
29 value & opt (some string) None & info [ "m"; "message" ] ~docv:"MSG" ~doc)
30
31(* === init === *)
32
33let init_path =
34 let doc = "Path for new repository." in
35 Arg.(value & pos 0 string "." & info [] ~docv:"PATH" ~doc)
36
37let init_backend =
38 let doc =
39 "Backend type: $(b,git) for Git-compatible, $(b,mst) for \
40 ATProto-compatible."
41 in
42 Arg.(
43 value
44 & opt (enum [ ("git", `Git); ("mst", `Mst) ]) `Git
45 & info [ "backend" ] ~docv:"TYPE" ~doc)
46
47let init_cmd =
48 let doc = "Initialise a new repository." in
49 let man =
50 [
51 `S Manpage.s_examples;
52 `Pre " irmin init myrepo";
53 `Pre " irmin init --backend mst atproto-store";
54 ]
55 in
56 Cmd.v
57 (Cmd.info "init" ~doc ~man)
58 Term.(
59 const (fun () backend path -> Cmd_init.run ~backend path)
60 $ setup $ init_backend $ init_path)
61
62(* === get === *)
63
64let get_path =
65 let doc = "Path to read." in
66 Arg.(required & pos 0 (some string) None & info [] ~docv:"PATH" ~doc)
67
68let get_cmd =
69 let doc = "Read content at a path." in
70 let man =
71 [
72 `S Manpage.s_examples;
73 `Pre " irmin get README.md";
74 `Pre " irmin get src/main.ml -b feature";
75 ]
76 in
77 Cmd.v (Cmd.info "get" ~doc ~man)
78 Term.(
79 const (fun () repo branch output path ->
80 Stdlib.exit (Cmd_get.run ~repo ~branch ~output path))
81 $ setup $ repo $ branch $ output $ get_path)
82
83(* === set === *)
84
85let set_path =
86 let doc = "Path to write." in
87 Arg.(required & pos 0 (some string) None & info [] ~docv:"PATH" ~doc)
88
89let set_content =
90 let doc = "Content to write. Reads from stdin if omitted." in
91 Arg.(value & pos 1 (some string) None & info [] ~docv:"CONTENT" ~doc)
92
93let set_cmd =
94 let doc = "Write content at a path." in
95 let man =
96 [
97 `S Manpage.s_examples;
98 `Pre " irmin set README.md '# Hello'";
99 `Pre " echo 'data' | irmin set config.txt";
100 `Pre " irmin set -m 'Add docs' README.md '# Project'";
101 ]
102 in
103 Cmd.v (Cmd.info "set" ~doc ~man)
104 Term.(
105 const (fun () repo branch message path content ->
106 Cmd_set.run ~repo ~branch ~message path content)
107 $ setup $ repo $ branch $ message $ set_path $ set_content)
108
109(* === del === *)
110
111let del_path =
112 let doc = "Path to delete." in
113 Arg.(required & pos 0 (some string) None & info [] ~docv:"PATH" ~doc)
114
115let del_cmd =
116 let doc = "Delete a path." in
117 let man = [ `S Manpage.s_examples; `Pre " irmin del old-file.txt" ] in
118 Cmd.v (Cmd.info "del" ~doc ~man)
119 Term.(
120 const (fun () repo branch message path ->
121 Stdlib.exit (Cmd_del.run ~repo ~branch ~message path))
122 $ setup $ repo $ branch $ message $ del_path)
123
124(* === list === *)
125
126let list_prefix =
127 let doc = "Path prefix to list." in
128 Arg.(value & pos 0 (some string) None & info [] ~docv:"PREFIX" ~doc)
129
130let list_cmd =
131 let doc = "List paths." in
132 let man =
133 [ `S Manpage.s_examples; `Pre " irmin list"; `Pre " irmin list src/" ]
134 in
135 Cmd.v
136 (Cmd.info "list" ~doc ~man)
137 Term.(
138 const (fun () repo branch output prefix ->
139 Stdlib.exit (Cmd_list.run ~repo ~branch ~output prefix))
140 $ setup $ repo $ branch $ output $ list_prefix)
141
142(* === tree === *)
143
144let tree_path =
145 let doc = "Path to show tree from." in
146 Arg.(value & pos 0 (some string) None & info [] ~docv:"PATH" ~doc)
147
148let tree_cmd =
149 let doc = "Show tree structure." in
150 let man =
151 [ `S Manpage.s_examples; `Pre " irmin tree"; `Pre " irmin tree src/" ]
152 in
153 Cmd.v
154 (Cmd.info "tree" ~doc ~man)
155 Term.(
156 const (fun () repo branch output path ->
157 Stdlib.exit (Cmd_tree.run ~repo ~branch ~output path))
158 $ setup $ repo $ branch $ output $ tree_path)
159
160(* === log === *)
161
162let log_limit =
163 let doc = "Maximum commits to show." in
164 Arg.(value & opt (some int) None & info [ "n" ] ~docv:"N" ~doc)
165
166let log_cmd =
167 let doc = "Show commit history." in
168 let man =
169 [
170 `S Manpage.s_examples; `Pre " irmin log"; `Pre " irmin log -n 5 -o json";
171 ]
172 in
173 Cmd.v (Cmd.info "log" ~doc ~man)
174 Term.(
175 const (fun () repo branch output limit ->
176 Stdlib.exit (Cmd_log.run ~repo ~branch ~output ~limit ()))
177 $ setup $ repo $ branch $ output $ log_limit)
178
179(* === branches === *)
180
181let branches_cmd =
182 let doc = "List branches." in
183 Cmd.v (Cmd.info "branches" ~doc)
184 Term.(
185 const (fun () repo output -> Stdlib.exit (Cmd_branches.run ~repo ~output ()))
186 $ setup $ repo $ output)
187
188(* === checkout === *)
189
190let checkout_branch =
191 let doc = "Branch to checkout or create." in
192 Arg.(required & pos 0 (some string) None & info [] ~docv:"BRANCH" ~doc)
193
194let create_flag =
195 let doc = "Create a new branch." in
196 Arg.(value & flag & info [ "c"; "create" ] ~doc)
197
198let checkout_cmd =
199 let doc = "Switch to a branch." in
200 let man =
201 [
202 `S Manpage.s_examples;
203 `Pre " irmin checkout main";
204 `Pre " irmin checkout -c feature";
205 ]
206 in
207 Cmd.v
208 (Cmd.info "checkout" ~doc ~man)
209 Term.(
210 const (fun () repo create branch ->
211 Stdlib.exit (Cmd_checkout.run ~repo ~create branch))
212 $ setup $ repo $ create_flag $ checkout_branch)
213
214(* === proof === *)
215
216let proof_key =
217 let doc = "Key to produce/verify proof for." in
218 Arg.(required & opt (some string) None & info [ "k"; "key" ] ~docv:"KEY" ~doc)
219
220let proof_data =
221 let doc = "Data entries as KEY=VALUE." in
222 Arg.(value & pos_all string [] & info [] ~docv:"KEY=VALUE" ~doc)
223
224let proof_produce_cmd =
225 let doc = "Produce a Merkle proof for a key." in
226 let man =
227 [
228 `S Manpage.s_description;
229 `P
230 "Produces a Merkle proof for reading a key from an MST (Merkle Search\n\
231 \ Tree). The proof contains only the data needed to verify the \
232 read.";
233 `S Manpage.s_examples;
234 `Pre " irmin proof produce -k mykey foo=bar baz=qux";
235 `Pre " irmin proof produce -k post/123 -o json 'post/123=Hello'";
236 ]
237 in
238 Cmd.v
239 (Cmd.info "produce" ~doc ~man)
240 Term.(
241 const (fun () output key data ->
242 Stdlib.exit (Cmd_proof.produce ~output ~key data))
243 $ setup $ output $ proof_key $ proof_data)
244
245let proof_verify_cmd =
246 let doc = "Verify a Merkle proof for a key." in
247 let man =
248 [
249 `S Manpage.s_description;
250 `P
251 "Verifies that a Merkle proof correctly proves a read operation.\n\
252 \ Returns exit code 0 if valid, 1 if invalid.";
253 `S Manpage.s_examples;
254 `Pre " irmin proof verify -k mykey foo=bar baz=qux";
255 ]
256 in
257 Cmd.v
258 (Cmd.info "verify" ~doc ~man)
259 Term.(
260 const (fun () output key data ->
261 Stdlib.exit (Cmd_proof.verify ~output ~key data))
262 $ setup $ output $ proof_key $ proof_data)
263
264let proof_cmd =
265 let doc = "MST Merkle proofs (ATProto-compatible)." in
266 let man =
267 [
268 `S Manpage.s_description;
269 `P
270 "Commands for working with Merkle proofs using the MST (Merkle Search\n\
271 \ Tree) format, compatible with ATProto's repository sync \
272 protocol.";
273 `P "Proofs allow verifying tree operations without full data access.";
274 ]
275 in
276 Cmd.group (Cmd.info "proof" ~doc ~man) [ proof_produce_cmd; proof_verify_cmd ]
277
278(* === import === *)
279
280let import_file =
281 let doc = "File to import (CAR or plain content)." in
282 Arg.(required & pos 0 (some string) None & info [] ~docv:"FILE" ~doc)
283
284let import_cmd =
285 let doc = "Import data from file." in
286 let man =
287 [
288 `S Manpage.s_description;
289 `P "Import data from external files. Format is auto-detected:";
290 `I ("$(b,.car)", "CAR file (ATProto blocks)");
291 `I ("$(b,other)", "Plain content added at path");
292 `S Manpage.s_examples;
293 `Pre " irmin import repo.car";
294 `Pre " irmin import data.json";
295 ]
296 in
297 Cmd.v
298 (Cmd.info "import" ~doc ~man)
299 Term.(
300 const (fun () repo branch file ->
301 Stdlib.exit (Cmd_import.run ~repo ~branch file))
302 $ setup $ repo $ branch $ import_file)
303
304(* === export === *)
305
306let export_output =
307 let doc = "Output file path." in
308 Arg.(
309 required & opt (some string) None & info [ "o"; "output" ] ~docv:"FILE" ~doc)
310
311let export_cmd =
312 let doc = "Export store to file." in
313 let man =
314 [
315 `S Manpage.s_description;
316 `P "Export store contents. Format determined by extension:";
317 `I ("$(b,.car)", "CAR file (ATProto format)");
318 `S Manpage.s_examples;
319 `Pre " irmin export -o backup.car";
320 ]
321 in
322 Cmd.v
323 (Cmd.info "export" ~doc ~man)
324 Term.(
325 const (fun () repo branch output ->
326 Stdlib.exit (Cmd_export.run ~repo ~branch ~output ()))
327 $ setup $ repo $ branch $ export_output)
328
329(* === info === *)
330
331let info_file =
332 let doc = "File to inspect (optional, defaults to store info)." in
333 Arg.(value & pos 0 (some string) None & info [] ~docv:"FILE" ~doc)
334
335let info_cmd =
336 let doc = "Show store or file information." in
337 let man =
338 [
339 `S Manpage.s_description;
340 `P "Display information about the store or a specific file.";
341 `S Manpage.s_examples;
342 `Pre " irmin info";
343 `Pre " irmin info repo.car";
344 ]
345 in
346 Cmd.v
347 (Cmd.info "info" ~doc ~man)
348 Term.(
349 const (fun () repo file -> Stdlib.exit (Cmd_info.run ~repo file))
350 $ setup $ repo $ info_file)
351
352(* === Main === *)
353
354let main_cmd =
355 let doc = "Content-addressed storage" in
356 let man =
357 [
358 `S Manpage.s_description;
359 `P "Irmin is a content-addressed store with Git-like semantics.";
360 `P "Configuration is auto-detected from the repository:";
361 `I ("$(b,.git/)", "Git backend (SHA-1, Git-compatible)");
362 `I ("$(b,.irmin/config)", "Custom backend configuration");
363 `S Manpage.s_see_also;
364 `P "$(b,git)(1)";
365 ]
366 in
367 let info = Cmd.info "irmin" ~version:Monopam_info.version ~doc ~man in
368 Cmd.group info
369 [
370 init_cmd;
371 get_cmd;
372 set_cmd;
373 del_cmd;
374 list_cmd;
375 tree_cmd;
376 log_cmd;
377 branches_cmd;
378 checkout_cmd;
379 import_cmd;
380 export_cmd;
381 info_cmd;
382 proof_cmd;
383 ]
384
385let () = Stdlib.exit (Cmd.eval main_cmd)