Git object storage and pack files for Eio
at main 102 lines 4.1 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 user information (author/committer). *) 18 19type tz_offset = { sign : [ `Plus | `Minus ]; hours : int; minutes : int } 20type t = { name : string; email : string; date : int64 * tz_offset option } 21 22let v ~name ~email ~date ?tz_offset () = 23 { name; email; date = (date, tz_offset) } 24 25let name t = t.name 26let email t = t.email 27let date t = fst t.date 28let tz_offset t = snd t.date 29 30let pp_tz_offset ppf { sign; hours; minutes } = 31 let sign_char = match sign with `Plus -> '+' | `Minus -> '-' in 32 Fmt.pf ppf "%c%02d%02d" sign_char hours minutes 33 34let pp ppf t = 35 Fmt.pf ppf "%s <%s> %Ld" t.name t.email (fst t.date); 36 match snd t.date with 37 | Some tz -> Fmt.pf ppf " %a" pp_tz_offset tz 38 | None -> Fmt.pf ppf " +0000" 39 40let equal a b = 41 String.equal a.name b.name 42 && String.equal a.email b.email 43 && Int64.equal (fst a.date) (fst b.date) 44 45let compare a b = 46 match String.compare a.name b.name with 47 | 0 -> ( 48 match String.compare a.email b.email with 49 | 0 -> Int64.compare (fst a.date) (fst b.date) 50 | n -> n) 51 | n -> n 52 53(** Encode user to git format: "Name <email> timestamp timezone" *) 54let to_string t = 55 let tz_str = 56 match snd t.date with 57 | Some { sign; hours; minutes } -> 58 let sign_char = match sign with `Plus -> '+' | `Minus -> '-' in 59 Fmt.str "%c%02d%02d" sign_char hours minutes 60 | None -> "+0000" 61 in 62 Fmt.str "%s <%s> %Ld %s" t.name t.email (fst t.date) tz_str 63 64(** Parse user from git format. *) 65let of_string s = 66 (* Format: "Name <email> timestamp timezone" *) 67 match String.rindex_opt s '<' with 68 | None -> Error (`Msg ("Invalid user format: " ^ s)) 69 | Some lt_pos -> ( 70 match String.index_from_opt s lt_pos '>' with 71 | None -> Error (`Msg ("Invalid user format: " ^ s)) 72 | Some gt_pos -> ( 73 let name = String.trim (String.sub s 0 lt_pos) in 74 let email = String.sub s (lt_pos + 1) (gt_pos - lt_pos - 1) in 75 let rest = String.sub s (gt_pos + 2) (String.length s - gt_pos - 2) in 76 let parts = String.split_on_char ' ' rest in 77 match parts with 78 | [ timestamp; tz ] -> ( 79 match Int64.of_string_opt timestamp with 80 | None -> Error (`Msg ("Invalid timestamp: " ^ timestamp)) 81 | Some ts -> 82 let tz_offset = 83 if String.length tz >= 5 then 84 let sign = 85 match tz.[0] with 86 | '+' -> `Plus 87 | '-' -> `Minus 88 | _ -> `Plus 89 in 90 let hours = 91 int_of_string_opt (String.sub tz 1 2) 92 |> Option.value ~default:0 93 in 94 let minutes = 95 int_of_string_opt (String.sub tz 3 2) 96 |> Option.value ~default:0 97 in 98 Some { sign; hours; minutes } 99 else None 100 in 101 Ok { name; email; date = (ts, tz_offset) }) 102 | _ -> Error (`Msg ("Invalid user date format: " ^ rest))))