Persistent store with Git semantics: lazy reads, delayed writes, content-addressing
at main 312 lines 11 kB view raw
1module Make (F : Codec.S) = struct 2 type hash = F.hash 3 type path = string list 4 type concrete = [ `Contents of string | `Tree of (string * concrete) list ] 5 6 (* Internal tree representation with lazy loading *) 7 type node_state = 8 | Loaded of F.node 9 | Lazy of { backend : hash Backend.t; hash : hash } 10 | Shallow of hash 11 | Pruned of hash 12 13 type tree_node = 14 | Contents of string 15 | Node of { 16 mutable state : node_state; 17 mutable children : (string * tree_node) list; (* modifications *) 18 mutable removed : string list; 19 } 20 21 type t = tree_node 22 23 let empty () = 24 Node { state = Loaded F.empty_node; children = []; removed = [] } 25 26 let of_hash ~backend hash = 27 Node { state = Lazy { backend; hash }; children = []; removed = [] } 28 29 let shallow hash = Node { state = Shallow hash; children = []; removed = [] } 30 let pruned hash = Node { state = Pruned hash; children = []; removed = [] } 31 32 let rec of_concrete : concrete -> t = function 33 | `Contents s -> Contents s 34 | `Tree entries -> 35 let children = 36 List.map (fun (name, c) -> (name, of_concrete c)) entries 37 in 38 Node { state = Loaded F.empty_node; children; removed = [] } 39 40 (* Force loading of a lazy node *) 41 let force_node state = 42 match state with 43 | Loaded n -> Some n 44 | Lazy { backend; hash } -> ( 45 match backend.read hash with 46 | Some data -> ( 47 match F.node_of_bytes data with Ok n -> Some n | Error _ -> None) 48 | None -> None) 49 | Shallow _ -> None 50 | Pruned _ -> None 51 52 (* Navigate to a path, returning the node and remaining path *) 53 let rec navigate t path = 54 match (t, path) with 55 | _, [] -> Some (t, []) 56 | Contents _, _ :: _ -> None 57 | Node node, name :: rest -> ( 58 (* Check modifications first *) 59 match List.assoc_opt name node.children with 60 | Some child -> navigate child rest 61 | None -> ( 62 if List.mem name node.removed then None 63 else 64 (* Try to load from underlying node *) 65 match force_node node.state with 66 | None -> None 67 | Some loaded -> ( 68 match F.find loaded name with 69 | None -> None 70 | Some (`Contents hash) -> ( 71 (* Load the content blob *) 72 match node.state with 73 | Lazy { backend; _ } -> ( 74 match backend.read hash with 75 | Some data -> navigate (Contents data) rest 76 | None -> None) 77 | _ -> None) 78 | Some (`Node hash) -> ( 79 match node.state with 80 | Lazy { backend; _ } -> 81 let child = of_hash ~backend hash in 82 navigate child rest 83 | _ -> None)))) 84 85 let find t path = 86 match navigate t path with Some (Contents s, []) -> Some s | _ -> None 87 88 let find_tree t path = 89 match navigate t path with Some ((Node _ as n), []) -> Some n | _ -> None 90 91 let mem t path = Option.is_some (navigate t path) 92 93 let mem_tree t path = 94 match navigate t path with Some (Node _, []) -> true | _ -> false 95 96 let list t path = 97 match navigate t path with 98 | Some (Node node, []) -> ( 99 match force_node node.state with 100 | None -> [] 101 | Some loaded -> 102 let base_entries = 103 F.list loaded 104 |> List.filter (fun (name, _) -> 105 (not (List.mem name node.removed)) 106 && not (List.mem_assoc name node.children)) 107 |> List.map (fun (name, kind) -> 108 let k = 109 match kind with 110 | `Node _ -> `Node 111 | `Contents _ -> `Contents 112 in 113 (name, k)) 114 in 115 let child_entries = 116 List.map 117 (fun (name, child) -> 118 let k = 119 match child with Node _ -> `Node | Contents _ -> `Contents 120 in 121 (name, k)) 122 node.children 123 in 124 List.sort 125 (fun (a, _) (b, _) -> String.compare a b) 126 (base_entries @ child_entries)) 127 | _ -> [] 128 129 (* Add contents at path, creating intermediate nodes as needed *) 130 let rec add_at t path value = 131 match (t, path) with 132 | _, [] -> value 133 | Contents _, _ :: _ -> 134 (* Replace contents with a tree *) 135 add_at (empty ()) path value 136 | Node node, [ name ] -> 137 let children = 138 (name, value) :: List.filter (fun (n, _) -> n <> name) node.children 139 in 140 let removed = List.filter (( <> ) name) node.removed in 141 Node { node with children; removed } 142 | Node node, name :: rest -> 143 let child = 144 match List.assoc_opt name node.children with 145 | Some c -> c 146 | None -> ( 147 if List.mem name node.removed then empty () 148 else 149 match force_node node.state with 150 | None -> empty () 151 | Some loaded -> ( 152 match F.find loaded name with 153 | Some (`Node hash) -> ( 154 match node.state with 155 | Lazy { backend; _ } -> of_hash ~backend hash 156 | _ -> empty ()) 157 | _ -> empty ())) 158 in 159 let new_child = add_at child rest value in 160 let children = 161 (name, new_child) 162 :: List.filter (fun (n, _) -> n <> name) node.children 163 in 164 Node { node with children } 165 166 let add t path contents = add_at t path (Contents contents) 167 let add_tree t path subtree = add_at t path subtree 168 169 let rec remove t path = 170 match (t, path) with 171 | _, [] -> empty () 172 | Contents _, _ :: _ -> t 173 | Node node, [ name ] -> 174 let children = List.filter (fun (n, _) -> n <> name) node.children in 175 let removed = 176 if List.mem name node.removed then node.removed 177 else name :: node.removed 178 in 179 Node { node with children; removed } 180 | Node node, name :: rest -> 181 let child = 182 match List.assoc_opt name node.children with 183 | Some c -> c 184 | None -> ( 185 if List.mem name node.removed then empty () 186 else 187 match force_node node.state with 188 | None -> empty () 189 | Some loaded -> ( 190 match F.find loaded name with 191 | Some (`Node hash) -> ( 192 match node.state with 193 | Lazy { backend; _ } -> of_hash ~backend hash 194 | _ -> empty ()) 195 | _ -> empty ())) 196 in 197 let new_child = remove child rest in 198 let children = 199 (name, new_child) 200 :: List.filter (fun (n, _) -> n <> name) node.children 201 in 202 Node { node with children } 203 204 let rec to_concrete t = 205 match t with 206 | Contents s -> `Contents s 207 | Node node -> 208 let entries = 209 match force_node node.state with 210 | None -> [] 211 | Some loaded -> 212 F.list loaded 213 |> List.filter_map (fun (name, _kind) -> 214 if List.mem name node.removed then None 215 else if List.mem_assoc name node.children then None 216 else 217 (* Would need to recursively load - simplified here *) 218 None) 219 in 220 let child_entries = 221 List.map 222 (fun (name, child) -> (name, to_concrete child)) 223 node.children 224 in 225 let all = 226 List.sort 227 (fun (a, _) (b, _) -> String.compare a b) 228 (entries @ child_entries) 229 in 230 `Tree all 231 232 (* Write tree to backend and return hash *) 233 let rec write_tree t ~(backend : hash Backend.t) : hash = 234 match t with 235 | Contents s -> 236 let h = F.hash_contents s in 237 backend.write h s; 238 h 239 | Node node -> 240 (* First, get the base node *) 241 let base = 242 match force_node node.state with Some n -> n | None -> F.empty_node 243 in 244 (* Apply removals *) 245 let base = 246 List.fold_left (fun n name -> F.remove n name) base node.removed 247 in 248 (* Apply additions (recursively writing children) *) 249 let final = 250 List.fold_left 251 (fun n (name, child) -> 252 let child_hash = write_tree child ~backend in 253 let kind = 254 match child with 255 | Contents _ -> `Contents child_hash 256 | Node _ -> `Node child_hash 257 in 258 F.add n name kind) 259 base node.children 260 in 261 let data = F.bytes_of_node final in 262 let h = F.hash_node final in 263 backend.write h data; 264 h 265 266 let hash t ~backend = write_tree t ~backend 267 268 type 'a force = [ `True | `False of hash -> 'a | `Shallow of hash -> 'a ] 269 270 let fold ?(force = `True) t init f = 271 let rec go path t acc = 272 match t with 273 | Contents s -> f path (`Contents s) acc 274 | Node node -> ( 275 let acc = f path `Tree acc in 276 match force with 277 | `True -> ( 278 match force_node node.state with 279 | None -> acc 280 | Some _loaded -> 281 (* Fold over children *) 282 List.fold_left 283 (fun acc (name, child) -> go (path @ [ name ]) child acc) 284 acc node.children) 285 | `False fn -> ( 286 match node.state with 287 | Lazy { hash; _ } -> fn hash 288 | Shallow hash -> fn hash 289 | Pruned hash -> fn hash 290 | Loaded _ -> 291 List.fold_left 292 (fun acc (name, child) -> go (path @ [ name ]) child acc) 293 acc node.children) 294 | `Shallow fn -> ( 295 match node.state with 296 | Shallow hash -> fn hash 297 | _ -> 298 List.fold_left 299 (fun acc (name, child) -> go (path @ [ name ]) child acc) 300 acc node.children)) 301 in 302 go [] t init 303 304 let clear ?depth:_ _t = () 305 306 let equal t1 t2 = 307 (* Simple structural equality - could be optimized with hash comparison *) 308 to_concrete t1 = to_concrete t2 309end 310 311module Git = Make (Codec.Git) 312module Mst = Make (Codec.Mst)