Persistent store with Git semantics: lazy reads, delayed writes, content-addressing
at perf 140 lines 4.9 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. 14 Inode marker \x02 and inlined-tree marker \x01 are internal formats 15 that cannot be stored in a git repository. *) 16let detect_object_type data = 17 if String.length data >= 5 && String.sub data 0 5 = "tree " then `Commit 18 else if String.length data >= 2 && data.[0] >= '1' && data.[0] <= '7' then 19 (* Tree entries start with mode like "100644 " or "40000 " *) 20 `Tree 21 else if 22 String.length data >= 1 && (data.[0] = '\x01' || data.[0] = '\x02') 23 then 24 failwith 25 "git_interop: cannot write internal tree format (inlined or inode) to \ 26 git; use inline_threshold:0 for git backends" 27 else `Blob 28 29let git_value_of_data data = 30 match detect_object_type data with 31 | `Blob -> Git.Value.blob (Git.Blob.of_string data) 32 | `Tree -> Git.Value.tree (Git.Tree.of_string_exn data) 33 | `Commit -> Git.Value.commit (Git.Commit.of_string_exn data) 34 35let test_and_set_ref repo name ~test ~set = 36 let current = Git.Repository.read_ref repo name in 37 let matches = 38 match (test, current) with 39 | None, None -> true 40 | Some t, Some c -> Git.Hash.equal (git_hash_of_sha1 t) c 41 | _ -> false 42 in 43 if matches then ( 44 (match set with 45 | None -> Git.Repository.delete_ref repo name 46 | Some h -> Git.Repository.write_ref repo name (git_hash_of_sha1 h)); 47 true) 48 else false 49 50(* Create Git backend from a Git.Repository.t *) 51let git_backend (repo : Git.Repository.t) : Hash.sha1 Backend.t = 52 { 53 read = 54 (fun hash -> 55 let git_hash = git_hash_of_sha1 hash in 56 match Git.Repository.read repo git_hash with 57 | Ok value -> Some (Git.Value.to_string_without_header value) 58 | Error _ -> None); 59 write = 60 (fun _expected_hash data -> 61 ignore (Git.Repository.write repo (git_value_of_data data))); 62 exists = 63 (fun hash -> 64 let git_hash = git_hash_of_sha1 hash in 65 Git.Repository.exists repo git_hash); 66 get_ref = 67 (fun name -> 68 Option.map sha1_of_git_hash (Git.Repository.read_ref repo name)); 69 set_ref = 70 (fun name hash -> 71 Git.Repository.write_ref repo name (git_hash_of_sha1 hash)); 72 test_and_set_ref = test_and_set_ref repo; 73 list_refs = (fun () -> Git.Repository.list_refs repo); 74 write_batch = 75 (fun objects -> 76 List.iter 77 (fun (_expected_hash, data) -> 78 ignore (Git.Repository.write repo (git_value_of_data data))) 79 objects); 80 flush = (fun () -> ()); 81 close = (fun () -> ()); 82 } 83 84(* Public API *) 85 86let import_git ~sw:_ ~fs ~git_dir = 87 let repo = Git.Repository.open_bare ~fs git_dir in 88 let backend = git_backend repo in 89 Store.Git.create ~backend () 90 91let open_git ~sw:_ ~fs ~path = 92 let repo = Git.Repository.open_repo ~fs path in 93 let backend = git_backend repo in 94 Store.Git.create ~backend () 95 96let init_git ~sw:_ ~fs ~path = 97 let repo = Git.Repository.init ~fs path in 98 let backend = git_backend repo in 99 Store.Git.create ~backend () 100 101let read_object ~sw:_ ~fs ~git_dir hash : 102 (string * string, [> `Msg of string ]) result = 103 let repo = Git.Repository.open_bare ~fs git_dir in 104 let git_hash = git_hash_of_sha1 hash in 105 match Git.Repository.read repo git_hash with 106 | Ok value -> 107 let kind = 108 match Git.Value.kind value with 109 | `Blob -> "blob" 110 | `Tree -> "tree" 111 | `Commit -> "commit" 112 | `Tag -> "tag" 113 in 114 Ok (kind, Git.Value.to_string_without_header value) 115 | Error (`Msg m) -> Error (`Msg m) 116 117let write_object ~sw:_ ~fs ~git_dir ~typ data = 118 let repo = Git.Repository.open_bare ~fs git_dir in 119 let value = 120 match typ with 121 | "blob" -> Git.Value.blob (Git.Blob.of_string data) 122 | "tree" -> Git.Value.tree (Git.Tree.of_string_exn data) 123 | "commit" -> Git.Value.commit (Git.Commit.of_string_exn data) 124 | "tag" -> Git.Value.tag (Git.Tag.of_string_exn data) 125 | _ -> invalid_arg ("unknown object type: " ^ typ) 126 in 127 let git_hash = Git.Repository.write repo value in 128 sha1_of_git_hash git_hash 129 130let read_ref ~sw:_ ~fs ~git_dir name = 131 let repo = Git.Repository.open_bare ~fs git_dir in 132 Option.map sha1_of_git_hash (Git.Repository.read_ref repo name) 133 134let write_ref ~sw:_ ~fs ~git_dir name hash = 135 let repo = Git.Repository.open_bare ~fs git_dir in 136 Git.Repository.write_ref repo name (git_hash_of_sha1 hash) 137 138let list_refs ~sw:_ ~fs ~git_dir = 139 let repo = Git.Repository.open_bare ~fs git_dir in 140 Git.Repository.list_refs repo