Persistent store with Git semantics: lazy reads, delayed writes, content-addressing
at inline-small-objects 385 lines 11 kB view raw
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)