Git object storage and pack files for Eio
at main 110 lines 3.7 kB view raw
1(* Copyright (c) 2015 Daniel C. Bünzli 2 Copyright (c) 2020-2024 Romain Calascibetta <romain.calascibetta@gmail.com> 3 Copyright (c) 2024-2026 Thomas Gazagnaire <thomas@gazagnaire.org> 4 5 Permission to use, copy, modify, and distribute this software for any 6 purpose with or without fee is hereby granted, provided that the above 7 copyright notice and this permission notice appear in all copies. 8 9 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *) 16 17(** Git references (branches, tags, HEAD). *) 18 19type t = string 20 21let head = "HEAD" 22let master = "refs/heads/master" 23let main = "refs/heads/main" 24 25let of_string s = 26 if String.length s = 0 then Error (`Msg "Empty reference") 27 else if String.contains s '\x00' then 28 Error (`Msg "Reference contains null byte") 29 else if s.[0] = '/' then Error (`Msg "Absolute reference not allowed") 30 else 31 (* Collapse consecutive slashes *) 32 let buf = Buffer.create (String.length s) in 33 let last_was_slash = ref false in 34 String.iter 35 (fun c -> 36 if c = '/' then ( 37 if not !last_was_slash then Buffer.add_char buf c; 38 last_was_slash := true) 39 else ( 40 Buffer.add_char buf c; 41 last_was_slash := false)) 42 s; 43 Ok (Buffer.contents buf) 44 45let of_string_exn s = 46 match of_string s with Ok r -> r | Error (`Msg m) -> invalid_arg m 47 48let v = of_string_exn 49let to_string t = t 50let pp ppf t = Fmt.string ppf t 51let equal = String.equal 52let compare = String.compare 53let hash = Hashtbl.hash 54 55let segs t = 56 String.split_on_char '/' t |> List.filter (fun s -> String.length s > 0) 57 58let ( / ) t seg = 59 if String.contains seg '\x00' || String.contains seg '/' then 60 invalid_arg "Invalid segment"; 61 if t.[String.length t - 1] = '/' then t ^ seg else t ^ "/" ^ seg 62 63let ( // ) t1 t2 = 64 if t2.[0] = '/' then t2 65 else if t1.[String.length t1 - 1] = '/' then t1 ^ t2 66 else t1 ^ "/" ^ t2 67 68(** Reference contents: either a direct hash or a symbolic reference. *) 69type contents = Hash of Hash.t | Ref of t 70 71let contents_equal a b = 72 match (a, b) with 73 | Hash a, Hash b -> Hash.equal a b 74 | Ref a, Ref b -> equal a b 75 | _ -> false 76 77let contents_compare a b = 78 match (a, b) with 79 | Hash a, Hash b -> Hash.compare a b 80 | Ref a, Ref b -> compare a b 81 | Hash _, Ref _ -> -1 82 | Ref _, Hash _ -> 1 83 84let pp_contents ppf = function 85 | Hash h -> Hash.pp ppf h 86 | Ref r -> Fmt.pf ppf "ref: %s" r 87 88(** Parse reference file contents. *) 89let contents_of_string s = 90 let s = String.trim s in 91 if String.length s = 0 then Error (`Msg "Empty reference contents") 92 else if String.length s >= 5 && String.sub s 0 5 = "ref: " then 93 let ref_path = String.sub s 5 (String.length s - 5) in 94 Result.map (fun r -> Ref r) (of_string ref_path) 95 else 96 (* Try to parse as a hex hash *) 97 try Ok (Hash (Hash.of_hex s)) 98 with Invalid_argument _ -> 99 Error (`Msg ("Invalid reference contents: " ^ s)) 100 101let contents_of_string_exn s = 102 match contents_of_string s with Ok c -> c | Error (`Msg m) -> failwith m 103 104(** Encode reference contents. *) 105let contents_to_string = function 106 | Hash h -> Hash.to_hex h ^ "\n" 107 | Ref r -> "ref: " ^ r ^ "\n" 108 109module Set = Set.Make (String) 110module Map = Map.Make (String)