Source file compute.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
let result_is_error = function Ok _v -> false | Error _v -> true
let result_get_ok = function Ok v -> v | Error _v -> invalid_arg "Result.get_ok"
type directory_entry_kind =
| File
| Dir
module Make (SHA1 : sig
val digest_string_to_hex : string -> string
end) (OS : sig
val contents : string -> string list option
val typ : string -> directory_entry_kind option
val read_file : string -> string option
val permissions : string -> int option
val base : string -> string
end) =
struct
module Git = struct
let target_kind_to_git = function
| Object.Kind.Content _hash_type -> "blob"
| Directory -> "tree"
| Release -> "tag"
| Revision -> "commit"
| Snapshot -> "refs"
let id_to_bytes id =
String.init
(String.length id / 2)
(fun i ->
let s = String.sub id (2 * i) 2 in
Char.chr @@ int_of_string @@ "0x" ^ s )
let object_to_swhid (obj : string) object_type =
let scheme = Object.Scheme_version.default in
let hash = SHA1.digest_string_to_hex obj in
match Object.Hash.of_string hash with
| Error _msg as e -> e
| Ok hash ->
let core_identifier =
Object.Core_identifier.mk scheme object_type hash
in
Ok (Object.mk core_identifier [])
let fmt (git_type, len) =
match git_type with
| "blob" | "commit" | "extid" | "raw_extrinsic_metadata" | "snapshot"
| "tag" | "tree" ->
Format.fprintf fmt "%s %d\x00" git_type len
| git_type ->
invalid_arg
(Format.sprintf "invalid git object type `%s` (Git.object_header)"
git_type )
let object_from_contents_strtarget target_kind contents =
let len = String.length contents in
Format.asprintf "%a%s" object_header (target_kind, len) contents
let object_from_contents target_kind contents =
object_from_contents_strtarget (target_kind_to_git target_kind) contents
let string_split_on_char sep s =
let r = ref [] in
let j = ref (String.length s) in
for i = String.length s - 1 downto 0 do
if String.unsafe_get s i = sep then begin
r := String.sub s (i + 1) (!j - i - 1) :: !r;
j := i
end
done;
String.sub s 0 !j :: !r
let escape_newlines snippet =
String.concat "\n " (string_split_on_char '\n' snippet)
let abs x = if x >= 0 then x else -x
let format_offset fmt (offset, negative_utc) =
let sign =
if offset < 0 || (offset = 0 && negative_utc) then "-" else "+"
in
let offset = abs offset in
let hours = offset / 60 in
let minutes = offset mod 60 in
Format.fprintf fmt "%s%02d%02d" sign hours minutes
let format_author_data fmt (author, date) =
Format.fprintf fmt "%s" author;
match date with
| None -> ()
| Some (timestamp, tz_offset, negative_utc) ->
Format.fprintf fmt " %Ld %a" timestamp format_offset
(tz_offset, negative_utc)
end
type directory_entry =
{ typ : directory_entry_kind
; permissions : int
; name : string
; target : Object.Core_identifier.t
}
type date =
{ timestamp : Int64.t
; tz_offset : int
; negative_utc : bool
}
let content_identifier content =
let typ = Object.Kind.Content "sha1_git" in
let git_object = Git.object_from_contents typ content in
Git.object_to_swhid git_object typ
let directory_identifier entries =
let entries =
List.sort
(fun entry1 entry2 ->
String.compare
(if entry1.typ = Dir then entry1.name ^ "/" else entry1.name)
(if entry2.typ = Dir then entry2.name ^ "/" else entry2.name) )
entries
in
let content =
Format.asprintf "%a"
(Format.pp_print_list
~pp_sep:(fun _fmt () -> ())
(fun fmt entry ->
Format.fprintf fmt "%o %s%c%s" entry.permissions entry.name '\x00'
(Git.id_to_bytes
( Object.Hash.to_string
@@ Object.Core_identifier.get_hash entry.target ) ) ) )
entries
in
let typ = Object.Kind.Directory in
let git_object = Git.object_from_contents typ content in
Git.object_to_swhid git_object typ
let rec list_find_opt p = function
| [] -> None
| x :: l -> if p x then Some x else list_find_opt p l
let rec directory_identifier_deep name =
match OS.contents name with
| None -> Error (Format.sprintf "can't get contents of `%s`" name)
| Some contents -> (
let entries =
List.map
(fun name ->
let typ = OS.typ name in
let target =
match typ with
| Some File -> begin
match OS.read_file name with
| None -> Error (Format.sprintf "can't read file `%s`" name)
| Some content -> content_identifier content
end
| Some Dir -> directory_identifier_deep name
| None ->
Error (Format.sprintf "can't get type of file `%s`" name)
in
let permissions = OS.permissions name in
match (typ, permissions, target) with
| Some typ, Some permissions, Ok target ->
let name = OS.base name in
let target = Object.get_core target in
Ok { typ; permissions; target; name }
| _ -> Error "can't compute directory deep identifier" )
contents
in
match list_find_opt result_is_error entries with
| Some (Error _ as e) -> e
| Some _ -> assert false
| None -> directory_identifier (List.map result_get_ok entries) )
let option_map f = function None -> None | Some v -> Some (f v)
let release_identifier target target_kind ~name ~author date ~message =
let buff = Buffer.create 512 in
let fmt = Format.formatter_of_buffer buff in
Format.fprintf fmt "object %a%ctype %s%ctag %s%c" Object.Hash.pp target '\n'
(Git.target_kind_to_git target_kind)
'\n' (Git.escape_newlines name) '\n';
begin
match author with
| None -> ()
| Some author ->
Format.fprintf fmt "tagger %a%c" Git.format_author_data
( Git.escape_newlines author
, option_map
(fun o -> (o.timestamp, o.tz_offset, o.negative_utc))
date )
'\n'
end;
begin
match message with
| None -> ()
| Some message -> Format.fprintf fmt "%c%s" '\n' message
end;
Format.pp_print_flush fmt ();
let content = Buffer.contents buff in
let typ = Object.Kind.Release in
let git_object = Git.object_from_contents typ content in
Git.object_to_swhid git_object typ
let revision_identifier directory parents ~author ~author_date ~committer
~committer_date ~message =
let buff = Buffer.create 512 in
let fmt = Format.formatter_of_buffer buff in
Format.fprintf fmt "tree %a%c" Object.Hash.pp directory '\n';
List.iter
(fun parent -> Format.fprintf fmt "parent %a%c" Object.Hash.pp parent '\n')
parents;
Format.fprintf fmt "author %a%c" Git.format_author_data
( Git.escape_newlines author
, option_map
(fun o -> (o.timestamp, o.tz_offset, o.negative_utc))
author_date )
'\n';
Format.fprintf fmt "committer %a%c" Git.format_author_data
( Git.escape_newlines committer
, option_map
(fun o -> (o.timestamp, o.tz_offset, o.negative_utc))
committer_date )
'\n';
Array.iter
(fun (k, v) -> Format.fprintf fmt "%s %s%c" k (Git.escape_newlines v) '\n')
extra_headers;
begin
match message with
| None -> ()
| Some message -> Format.fprintf fmt "%c%s" '\n' message
end;
Format.pp_print_flush fmt ();
let content = Buffer.contents buff in
let typ = Object.Kind.Revision in
let git_object = Git.object_from_contents typ content in
Git.object_to_swhid git_object typ
let snapshot_identifier (branches : (string * (string * string) option) list)
=
let branches =
List.sort
(fun (name1, _target) (name2, _target) -> String.compare name1 name2)
branches
in
let buff = Buffer.create 512 in
let fmt = Format.formatter_of_buffer buff in
List.iter
(fun (branch_name, target) ->
let target, target_kind, target_id_len =
match target with
| None -> ("", "dangling", 0)
| Some (target, target_kind) -> (
match target_kind with
| "content" | "directory" | "revision" | "release" | "snapshot" ->
(Git.id_to_bytes target, target_kind, 20)
| "alias" -> (target, "alias", String.length target)
| target_kind ->
invalid_arg
(Format.sprintf
"invalid target type: `%s` (Compute.snapshot_identifier)"
target_kind ) )
in
Format.fprintf fmt "%s %s%c%d:%s" target_kind branch_name '\x00'
target_id_len target )
branches;
Format.pp_print_flush fmt ();
let content = Buffer.contents buff in
let git_object = Git.object_from_contents_strtarget "snapshot" content in
Git.object_to_swhid git_object Object.Kind.Snapshot
end