Persistent store with Git semantics: lazy reads, delayed writes, content-addressing
at inode 228 lines 7.8 kB view raw
1module Make (F : Codec.S) = struct 2 type hash = F.hash 3 4 module Tree = Tree.Make (F) 5 module Commit = Commit.Make (F) 6 7 type t = { backend : hash Backend.t } 8 9 let create ?cache ~backend () = 10 let backend = 11 match cache with 12 | Some n when n > 0 -> Backend.cached ~capacity:n backend 13 | _ -> backend 14 in 15 { backend } 16 let backend t = t.backend 17 18 let tree t ?at () = 19 match at with 20 | None -> Tree.empty () 21 | Some h -> Tree.of_hash ~backend:t.backend h 22 23 let read_commit t h = 24 match t.backend.read h with 25 | None -> None 26 | Some data -> ( 27 match Commit.of_bytes data with Ok c -> Some c | Error _ -> None) 28 29 let read_tree t h = Tree.of_hash ~backend:t.backend h 30 31 let checkout t ~branch = 32 match t.backend.get_ref ("refs/heads/" ^ branch) with 33 | None -> None 34 | Some commit_hash -> ( 35 match read_commit t commit_hash with 36 | None -> None 37 | Some commit -> Some (read_tree t (Commit.tree commit))) 38 39 let commit ?inline_threshold ?inode t ~tree ~parents ~message ~author = 40 (* This is where delayed writes happen *) 41 let tree_hash = Tree.hash ?inline_threshold ?inode tree ~backend:t.backend in 42 let c = Commit.v ~tree:tree_hash ~parents ~author ~message () in 43 let data = Commit.to_bytes c in 44 let h = Commit.hash c in 45 t.backend.write h data; 46 h 47 48 let head t ~branch = t.backend.get_ref ("refs/heads/" ^ branch) 49 let set_head t ~branch h = t.backend.set_ref ("refs/heads/" ^ branch) h 50 51 let branches t = 52 t.backend.list_refs () 53 |> List.filter_map (fun r -> 54 if String.length r > 11 && String.sub r 0 11 = "refs/heads/" then 55 Some (String.sub r 11 (String.length r - 11)) 56 else None) 57 58 let update_branch t ~branch ~old ~new_ = 59 t.backend.test_and_set_ref ("refs/heads/" ^ branch) ~test:old 60 ~set:(Some new_) 61 62 (* Simple ancestry check - walks parent chain *) 63 let is_ancestor t ~ancestor ~descendant = 64 let rec walk visited h = 65 if F.hash_equal h ancestor then true 66 else if List.exists (F.hash_equal h) visited then false 67 else 68 match read_commit t h with 69 | None -> false 70 | Some c -> 71 let visited = h :: visited in 72 List.exists (walk visited) (Commit.parents c) 73 in 74 F.hash_equal ancestor descendant || walk [] descendant 75 76 (* Find merge base using simple BFS *) 77 let merge_base t h1 h2 = 78 let rec ancestors_of h visited = 79 if List.exists (F.hash_equal h) visited then visited 80 else 81 match read_commit t h with 82 | None -> h :: visited 83 | Some c -> 84 let visited = h :: visited in 85 List.fold_left 86 (fun acc p -> ancestors_of p acc) 87 visited (Commit.parents c) 88 in 89 let ancestors1 = ancestors_of h1 [] in 90 let rec find_common h = 91 if List.exists (F.hash_equal h) ancestors1 then Some h 92 else 93 match read_commit t h with 94 | None -> None 95 | Some c -> ( 96 match Commit.parents c with [] -> None | p :: _ -> find_common p) 97 in 98 find_common h2 99 100 let commits_between t ~base ~head = 101 let rec count h n = 102 if F.hash_equal h base then n 103 else 104 match read_commit t h with 105 | None -> n 106 | Some c -> ( 107 match Commit.parents c with [] -> n | p :: _ -> count p (n + 1)) 108 in 109 count head 0 110 111 type diff_entry = 112 [ `Add of Tree.path * hash 113 | `Remove of Tree.path 114 | `Change of Tree.path * hash * hash ] 115 116 let diff t ~old ~new_ = 117 let old_tree = read_tree t old in 118 let new_tree = read_tree t new_ in 119 120 let rec diff_trees prefix old_tree new_tree = 121 let old_entries = Tree.list old_tree [] in 122 let new_entries = Tree.list new_tree [] in 123 124 let old_names = List.map fst old_entries in 125 let new_names = List.map fst new_entries in 126 127 (* Entries only in old -> Remove *) 128 let removed = 129 old_names 130 |> List.filter (fun name -> not (List.mem name new_names)) 131 |> List.to_seq 132 |> Seq.map (fun name -> `Remove (prefix @ [ name ])) 133 in 134 135 (* Entries only in new -> Add *) 136 let added = 137 new_names 138 |> List.filter (fun name -> not (List.mem name old_names)) 139 |> List.to_seq 140 |> Seq.filter_map (fun name -> 141 match Tree.find new_tree [ name ] with 142 | Some content -> 143 let hash = F.hash_contents content in 144 Some (`Add (prefix @ [ name ], hash)) 145 | None -> 146 (* It's a subtree - handled by added_subtrees recursion below *) 147 None) 148 in 149 150 (* Entries in both -> check for changes *) 151 let common = 152 List.filter (fun name -> List.mem name new_names) old_names 153 in 154 let changes = 155 common |> List.to_seq 156 |> Seq.flat_map (fun name -> 157 let path = prefix @ [ name ] in 158 let old_kind = List.assoc name old_entries in 159 let new_kind = List.assoc name new_entries in 160 match (old_kind, new_kind) with 161 | `Contents, `Contents -> ( 162 match 163 (Tree.find old_tree [ name ], Tree.find new_tree [ name ]) 164 with 165 | Some old_c, Some new_c -> 166 let old_h = F.hash_contents old_c in 167 let new_h = F.hash_contents new_c in 168 if F.hash_equal old_h new_h then Seq.empty 169 else Seq.return (`Change (path, old_h, new_h)) 170 | _ -> Seq.empty) 171 | `Node, `Node -> ( 172 match 173 ( Tree.find_tree old_tree [ name ], 174 Tree.find_tree new_tree [ name ] ) 175 with 176 | Some old_sub, Some new_sub -> diff_trees path old_sub new_sub 177 | _ -> Seq.empty) 178 | `Contents, `Node -> 179 (* Changed from contents to tree - remove old contents *) 180 Seq.return (`Remove path) 181 |> Seq.append 182 (match Tree.find_tree new_tree [ name ] with 183 | Some sub -> diff_trees path (Tree.empty ()) sub 184 | None -> Seq.empty) 185 | `Node, `Contents -> 186 (* Changed from tree to contents - add new contents *) 187 (match Tree.find new_tree [ name ] with 188 | Some c -> 189 let new_h = F.hash_contents c in 190 Seq.return (`Add (path, new_h)) 191 | None -> Seq.empty) 192 |> Seq.append 193 (match Tree.find_tree old_tree [ name ] with 194 | Some sub -> diff_trees path sub (Tree.empty ()) 195 | None -> Seq.empty)) 196 in 197 198 (* Also recurse into added subtrees *) 199 let added_subtrees = 200 new_names 201 |> List.filter (fun name -> not (List.mem name old_names)) 202 |> List.to_seq 203 |> Seq.flat_map (fun name -> 204 match Tree.find_tree new_tree [ name ] with 205 | Some sub -> diff_trees (prefix @ [ name ]) (Tree.empty ()) sub 206 | None -> Seq.empty) 207 in 208 209 (* Also recurse into removed subtrees *) 210 let removed_subtrees = 211 old_names 212 |> List.filter (fun name -> not (List.mem name new_names)) 213 |> List.to_seq 214 |> Seq.flat_map (fun name -> 215 match Tree.find_tree old_tree [ name ] with 216 | Some sub -> diff_trees (prefix @ [ name ]) sub (Tree.empty ()) 217 | None -> Seq.empty) 218 in 219 220 Seq.append removed 221 (Seq.append added 222 (Seq.append changes (Seq.append added_subtrees removed_subtrees))) 223 in 224 diff_trees [] old_tree new_tree 225end 226 227module Git = Make (Codec.Git) 228module Mst = Make (Codec.Mst)