Git object storage and pack files for Eio
at main 171 lines 5.5 kB view raw
1(* Copyright (c) 2013-2017 Thomas Gazagnaire <thomas@gazagnaire.org> 2 Copyright (c) 2017-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 tag objects. *) 18 19type kind = Blob | Commit | Tag | Tree 20 21let kind_to_string = function 22 | Blob -> "blob" 23 | Commit -> "commit" 24 | Tag -> "tag" 25 | Tree -> "tree" 26 27let kind_of_string = function 28 | "blob" -> Ok Blob 29 | "commit" -> Ok Commit 30 | "tag" -> Ok Tag 31 | "tree" -> Ok Tree 32 | s -> Error (`Msg ("Invalid tag kind: " ^ s)) 33 34let kind_of_string_exn s = 35 match kind_of_string s with Ok k -> k | Error (`Msg m) -> invalid_arg m 36 37type t = { 38 obj : Hash.t; 39 kind : kind; 40 tag : string; 41 tagger : User.t option; 42 message : string option; 43} 44 45let v obj kind ?tagger ~tag message = { obj; kind; tag; tagger; message } 46let obj t = t.obj 47let kind t = t.kind 48let name t = t.tag 49let tagger t = t.tagger 50let message t = t.message 51 52let pp ppf t = 53 Fmt.pf ppf "@[<v>object %a@," Hash.pp t.obj; 54 Fmt.pf ppf "type %s@," (kind_to_string t.kind); 55 Fmt.pf ppf "tag %s@," t.tag; 56 (match t.tagger with 57 | Some tagger -> Fmt.pf ppf "tagger %a@," User.pp tagger 58 | None -> ()); 59 (match t.message with Some msg -> Fmt.pf ppf "@,%s" msg | None -> ()); 60 Fmt.pf ppf "@]" 61 62let equal a b = 63 Hash.equal a.obj b.obj && a.kind = b.kind && String.equal a.tag b.tag 64 65let compare a b = 66 match Hash.compare a.obj b.obj with 67 | 0 -> ( 68 match Stdlib.compare a.kind b.kind with 69 | 0 -> String.compare a.tag b.tag 70 | n -> n) 71 | n -> n 72 73let hash t = Hashtbl.hash t 74 75(** Encode tag to git format. *) 76let to_string t = 77 let buf = Buffer.create 256 in 78 Buffer.add_string buf "object "; 79 Buffer.add_string buf (Hash.to_hex t.obj); 80 Buffer.add_char buf '\n'; 81 Buffer.add_string buf "type "; 82 Buffer.add_string buf (kind_to_string t.kind); 83 Buffer.add_char buf '\n'; 84 Buffer.add_string buf "tag "; 85 Buffer.add_string buf t.tag; 86 Buffer.add_char buf '\n'; 87 (match t.tagger with 88 | Some tagger -> 89 Buffer.add_string buf "tagger "; 90 Buffer.add_string buf (User.to_string tagger); 91 Buffer.add_char buf '\n' 92 | None -> ()); 93 (match t.message with 94 | Some msg -> 95 Buffer.add_char buf '\n'; 96 Buffer.add_string buf msg 97 | None -> ()); 98 Buffer.contents buf 99 100(** Parse a single header line. *) 101let parse_header line = 102 match String.index_opt line ' ' with 103 | None -> None 104 | Some pos -> 105 let key = String.sub line 0 pos in 106 let value = String.sub line (pos + 1) (String.length line - pos - 1) in 107 Some (key, value) 108 109(** Parse tag from git format. *) 110let of_string s = 111 let lines = String.split_on_char '\n' s in 112 let rec parse_headers obj kind tag tagger = function 113 | [] -> ( 114 (* No message *) 115 match (obj, kind, tag) with 116 | Some obj, Some kind, Some tag -> 117 Ok { obj; kind; tag; tagger; message = None } 118 | None, _, _ -> Error (`Msg "Missing object in tag") 119 | _, None, _ -> Error (`Msg "Missing type in tag") 120 | _, _, None -> Error (`Msg "Missing tag name in tag")) 121 | "" :: rest -> ( 122 (* Empty line marks start of message *) 123 let message = 124 match rest with [] -> None | _ -> Some (String.concat "\n" rest) 125 in 126 match (obj, kind, tag) with 127 | Some obj, Some kind, Some tag -> 128 Ok { obj; kind; tag; tagger; message } 129 | None, _, _ -> Error (`Msg "Missing object in tag") 130 | _, None, _ -> Error (`Msg "Missing type in tag") 131 | _, _, None -> Error (`Msg "Missing tag name in tag")) 132 | line :: rest -> ( 133 match parse_header line with 134 | None -> Error (`Msg ("Invalid header line: " ^ line)) 135 | Some ("object", hex) -> 136 let obj = Hash.of_hex hex in 137 parse_headers (Some obj) kind tag tagger rest 138 | Some ("type", type_str) -> ( 139 match kind_of_string type_str with 140 | Ok k -> parse_headers obj (Some k) tag tagger rest 141 | Error _ as e -> e) 142 | Some ("tag", tag_name) -> 143 parse_headers obj kind (Some tag_name) tagger rest 144 | Some ("tagger", user_str) -> ( 145 match User.of_string user_str with 146 | Ok user -> parse_headers obj kind tag (Some user) rest 147 | Error _ as e -> e) 148 | Some (_key, _value) -> 149 (* Skip unknown headers *) 150 parse_headers obj kind tag tagger rest) 151 in 152 parse_headers None None None None lines 153 154let of_string_exn s = 155 match of_string s with Ok t -> t | Error (`Msg m) -> failwith m 156 157let digest t = 158 let s = to_string t in 159 Hash.digest_string ~kind:`Tag s 160 161module Set = Set.Make (struct 162 type nonrec t = t 163 164 let compare = compare 165end) 166 167module Map = Map.Make (struct 168 type nonrec t = t 169 170 let compare = compare 171end)