Persistent store with Git semantics: lazy reads, delayed writes, content-addressing
at main 132 lines 4.5 kB view raw
1(** Git interoperability using ocaml-git. 2 3 Provides bidirectional support for reading and writing Git repositories. *) 4 5(* Convert between irmin Hash.sha1 and Git.Hash.t *) 6let git_hash_of_sha1 (h : Hash.sha1) : Git.Hash.t = 7 Git.Hash.of_raw_string (Hash.to_bytes h) 8 9let sha1_of_git_hash (h : Git.Hash.t) : Hash.sha1 = 10 Hash.sha1_of_bytes (Git.Hash.to_raw_string h) 11 12(* Detect object type from content. 13 Commits start with "tree ", trees have binary format with mode prefixes. *) 14let detect_object_type data = 15 if String.length data >= 5 && String.sub data 0 5 = "tree " then `Commit 16 else if String.length data >= 2 && data.[0] >= '1' && data.[0] <= '7' then 17 (* Tree entries start with mode like "100644 " or "40000 " *) 18 `Tree 19 else `Blob 20 21let git_value_of_data data = 22 match detect_object_type data with 23 | `Blob -> Git.Value.blob (Git.Blob.of_string data) 24 | `Tree -> Git.Value.tree (Git.Tree.of_string_exn data) 25 | `Commit -> Git.Value.commit (Git.Commit.of_string_exn data) 26 27let test_and_set_ref repo name ~test ~set = 28 let current = Git.Repository.read_ref repo name in 29 let matches = 30 match (test, current) with 31 | None, None -> true 32 | Some t, Some c -> Git.Hash.equal (git_hash_of_sha1 t) c 33 | _ -> false 34 in 35 if matches then ( 36 (match set with 37 | None -> Git.Repository.delete_ref repo name 38 | Some h -> Git.Repository.write_ref repo name (git_hash_of_sha1 h)); 39 true) 40 else false 41 42(* Create Git backend from a Git.Repository.t *) 43let git_backend (repo : Git.Repository.t) : Hash.sha1 Backend.t = 44 { 45 read = 46 (fun hash -> 47 let git_hash = git_hash_of_sha1 hash in 48 match Git.Repository.read repo git_hash with 49 | Ok value -> Some (Git.Value.to_string_without_header value) 50 | Error _ -> None); 51 write = 52 (fun _expected_hash data -> 53 ignore (Git.Repository.write repo (git_value_of_data data))); 54 exists = 55 (fun hash -> 56 let git_hash = git_hash_of_sha1 hash in 57 Git.Repository.exists repo git_hash); 58 get_ref = 59 (fun name -> 60 Option.map sha1_of_git_hash (Git.Repository.read_ref repo name)); 61 set_ref = 62 (fun name hash -> 63 Git.Repository.write_ref repo name (git_hash_of_sha1 hash)); 64 test_and_set_ref = test_and_set_ref repo; 65 list_refs = (fun () -> Git.Repository.list_refs repo); 66 write_batch = 67 (fun objects -> 68 List.iter 69 (fun (_expected_hash, data) -> 70 ignore (Git.Repository.write repo (git_value_of_data data))) 71 objects); 72 flush = (fun () -> ()); 73 close = (fun () -> ()); 74 } 75 76(* Public API *) 77 78let import_git ~sw:_ ~fs ~git_dir = 79 let repo = Git.Repository.open_bare ~fs git_dir in 80 let backend = git_backend repo in 81 Store.Git.create ~backend 82 83let open_git ~sw:_ ~fs ~path = 84 let repo = Git.Repository.open_repo ~fs path in 85 let backend = git_backend repo in 86 Store.Git.create ~backend 87 88let init_git ~sw:_ ~fs ~path = 89 let repo = Git.Repository.init ~fs path in 90 let backend = git_backend repo in 91 Store.Git.create ~backend 92 93let read_object ~sw:_ ~fs ~git_dir hash : 94 (string * string, [> `Msg of string ]) result = 95 let repo = Git.Repository.open_bare ~fs git_dir in 96 let git_hash = git_hash_of_sha1 hash in 97 match Git.Repository.read repo git_hash with 98 | Ok value -> 99 let kind = 100 match Git.Value.kind value with 101 | `Blob -> "blob" 102 | `Tree -> "tree" 103 | `Commit -> "commit" 104 | `Tag -> "tag" 105 in 106 Ok (kind, Git.Value.to_string_without_header value) 107 | Error (`Msg m) -> Error (`Msg m) 108 109let write_object ~sw:_ ~fs ~git_dir ~typ data = 110 let repo = Git.Repository.open_bare ~fs git_dir in 111 let value = 112 match typ with 113 | "blob" -> Git.Value.blob (Git.Blob.of_string data) 114 | "tree" -> Git.Value.tree (Git.Tree.of_string_exn data) 115 | "commit" -> Git.Value.commit (Git.Commit.of_string_exn data) 116 | "tag" -> Git.Value.tag (Git.Tag.of_string_exn data) 117 | _ -> invalid_arg ("unknown object type: " ^ typ) 118 in 119 let git_hash = Git.Repository.write repo value in 120 sha1_of_git_hash git_hash 121 122let read_ref ~sw:_ ~fs ~git_dir name = 123 let repo = Git.Repository.open_bare ~fs git_dir in 124 Option.map sha1_of_git_hash (Git.Repository.read_ref repo name) 125 126let write_ref ~sw:_ ~fs ~git_dir name hash = 127 let repo = Git.Repository.open_bare ~fs git_dir in 128 Git.Repository.write_ref repo name (git_hash_of_sha1 hash) 129 130let list_refs ~sw:_ ~fs ~git_dir = 131 let repo = Git.Repository.open_bare ~fs git_dir in 132 Git.Repository.list_refs repo