Persistent store with Git semantics: lazy reads, delayed writes, content-addressing
at inode 148 lines 5.4 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 29(* Create Git backend from a Git.Repository.t *) 30let git_backend (repo : Git.Repository.t) : Hash.sha1 Backend.t = 31 { 32 read = 33 (fun hash -> 34 let git_hash = git_hash_of_sha1 hash in 35 match Git.Repository.read repo git_hash with 36 | Ok value -> Some (Git.Value.to_string_without_header value) 37 | Error _ -> None); 38 write = 39 (fun _expected_hash data -> 40 (* Detect object type and write with correct Git wrapper *) 41 let value = 42 match detect_object_type data with 43 | `Blob -> Git.Value.blob (Git.Blob.of_string data) 44 | `Tree -> Git.Value.tree (Git.Tree.of_string_exn data) 45 | `Commit -> Git.Value.commit (Git.Commit.of_string_exn data) 46 in 47 let _git_hash = Git.Repository.write repo value in 48 ()); 49 exists = 50 (fun hash -> 51 let git_hash = git_hash_of_sha1 hash in 52 Git.Repository.exists repo git_hash); 53 get_ref = 54 (fun name -> 55 Option.map sha1_of_git_hash (Git.Repository.read_ref repo name)); 56 set_ref = 57 (fun name hash -> 58 Git.Repository.write_ref repo name (git_hash_of_sha1 hash)); 59 test_and_set_ref = 60 (fun name ~test ~set -> 61 let current = Git.Repository.read_ref repo name in 62 let matches = 63 match (test, current) with 64 | None, None -> true 65 | Some t, Some c -> Git.Hash.equal (git_hash_of_sha1 t) c 66 | _ -> false 67 in 68 if matches then ( 69 (match set with 70 | None -> Git.Repository.delete_ref repo name 71 | Some h -> Git.Repository.write_ref repo name (git_hash_of_sha1 h)); 72 true) 73 else false); 74 list_refs = (fun () -> Git.Repository.list_refs repo); 75 write_batch = 76 (fun objects -> 77 List.iter 78 (fun (_expected_hash, data) -> 79 let value = 80 match detect_object_type data with 81 | `Blob -> Git.Value.blob (Git.Blob.of_string data) 82 | `Tree -> Git.Value.tree (Git.Tree.of_string_exn data) 83 | `Commit -> Git.Value.commit (Git.Commit.of_string_exn data) 84 in 85 let _git_hash = Git.Repository.write repo value in 86 ()) 87 objects); 88 flush = (fun () -> ()); 89 close = (fun () -> ()); 90 } 91 92(* Public API *) 93 94let import_git ~sw:_ ~fs ~git_dir = 95 let repo = Git.Repository.open_bare ~fs git_dir in 96 let backend = git_backend repo in 97 Store.Git.create ~backend () 98 99let open_git ~sw:_ ~fs ~path = 100 let repo = Git.Repository.open_repo ~fs path in 101 let backend = git_backend repo in 102 Store.Git.create ~backend () 103 104let init_git ~sw:_ ~fs ~path = 105 let repo = Git.Repository.init ~fs path in 106 let backend = git_backend repo in 107 Store.Git.create ~backend () 108 109let read_object ~sw:_ ~fs ~git_dir hash : 110 (string * string, [> `Msg of string ]) result = 111 let repo = Git.Repository.open_bare ~fs git_dir in 112 let git_hash = git_hash_of_sha1 hash in 113 match Git.Repository.read repo git_hash with 114 | Ok value -> 115 let kind = 116 match Git.Value.kind value with 117 | `Blob -> "blob" 118 | `Tree -> "tree" 119 | `Commit -> "commit" 120 | `Tag -> "tag" 121 in 122 Ok (kind, Git.Value.to_string_without_header value) 123 | Error (`Msg m) -> Error (`Msg m) 124 125let write_object ~sw:_ ~fs ~git_dir ~typ data = 126 let repo = Git.Repository.open_bare ~fs git_dir in 127 let value = 128 match typ with 129 | "blob" -> Git.Value.blob (Git.Blob.of_string data) 130 | "tree" -> Git.Value.tree (Git.Tree.of_string_exn data) 131 | "commit" -> Git.Value.commit (Git.Commit.of_string_exn data) 132 | "tag" -> Git.Value.tag (Git.Tag.of_string_exn data) 133 | _ -> invalid_arg ("unknown object type: " ^ typ) 134 in 135 let git_hash = Git.Repository.write repo value in 136 sha1_of_git_hash git_hash 137 138let read_ref ~sw:_ ~fs ~git_dir name = 139 let repo = Git.Repository.open_bare ~fs git_dir in 140 Option.map sha1_of_git_hash (Git.Repository.read_ref repo name) 141 142let write_ref ~sw:_ ~fs ~git_dir name hash = 143 let repo = Git.Repository.open_bare ~fs git_dir in 144 Git.Repository.write_ref repo name (git_hash_of_sha1 hash) 145 146let list_refs ~sw:_ ~fs ~git_dir = 147 let repo = Git.Repository.open_bare ~fs git_dir in 148 Git.Repository.list_refs repo