Source file git_index.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
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
[@@@warning "-32"]
let io_buffer_size = 65536
type _ hash = SHA1 : Digestif.SHA1.t hash
let module_of : type oid. oid hash -> (module Digestif.S with type t = oid) =
function
| SHA1 -> (module Digestif.SHA1)
module Varint = struct
let decode buffer ~off =
let p = ref off in
let c = ref (Char.code (Bigstringaf.get buffer !p)) in
let r = ref (!c land 127) in
incr p;
while !c land 128 <> 0 do
incr r;
c := Char.code (Bigstringaf.get buffer !p);
incr p;
r := (!r lsl 7) + (!c land 127)
done;
!r, !p
let encode buffer ~off value =
let res = Bytes.create 16 in
let p = ref 15 in
let v = ref value in
Bytes.set res !p (Char.chr (!v land 127));
while
v := !v asr 7;
!v <> 0
do
decr p;
decr v;
Bytes.set res !p (Char.chr (128 lor (!v land 127)))
done;
Bigstringaf.blit_from_string ~src_off:!p
(Bytes.unsafe_to_string res)
~dst_off:off buffer ~len:(16 - !p);
16 - !p
end
module Entry = struct
type time = { sec : int32; nsec : int32 }
let pp_time ppf t =
Fmt.pf ppf "{ @[<hov>sec= %ld;@ nsec= %ld;@] }" t.sec t.nsec
type stat = {
sd_ctime : time;
sd_mtime : time;
sd_dev : int;
sd_ino : int;
sd_uid : int;
sd_gid : int;
sd_size : int;
}
let pp_stat ppf stat =
Fmt.pf ppf
"{ @[<hov>sd_ctime= %a;@ sd_mtime= %a;@ sd_dev= %d;@ sd_ino= %d;@ \
sd_uid= %d;@ sd_gid= %d;@ sd_size= %d;@] }"
pp_time stat.sd_ctime pp_time stat.sd_mtime stat.sd_dev stat.sd_ino
stat.sd_uid stat.sd_gid stat.sd_size
let _ce_stagemask = 0x3000
let _ce_extended = 0x4000
let _ce_valid = 0x8000
let _ce_stageshift = 12
let _ce_update = 1 lsl 16
let _ce_remove = 1 lsl 17
let _ce_uptodate = 1 lsl 18
let _ce_added = 1 lsl 19
let _ce_hashed = 1 lsl 20
let _ce_fsmonitor_valid = 1 lsl 21
let _ce_wt_remove = 1 lsl 22
let _ce_conflicted = 1 lsl 23
let _ce_unpacked = 1 lsl 24
let _ce_new_skip_worktree = 1 lsl 25
let _ce_matched = 1 lsl 26
let _ce_update_in_base = 1 lsl 27
let _ce_strip_name = 1 lsl 28
let _ce_intent_to_add = 1 lsl 29
let _ce_skip_worktree = 1 lsl 30
let _ce_extended_flags = _ce_intent_to_add lor _ce_skip_worktree
let _ce_namemask = 0x0fff
let _ce_extended = 0x4000
type 'oid t = {
ce_stat : stat;
ce_mode : int;
ce_length : int;
mutable ce_flags : int;
oid : 'oid;
name : Fpath.t;
}
let path { name; _ } = name
let oid { oid; _ } = oid
let mode { ce_mode; _ } = ce_mode
let compare a b =
let res = Fpath.compare a.name b.name in
if res = 0 then
let a = (a.ce_flags land _ce_stagemask) lsr _ce_stageshift in
let b = (b.ce_flags land _ce_stagemask) lsr _ce_stageshift in
if a < b then -1 else if a > b then 1 else 0
else res
let mode_of_unix_kind kind perm =
match kind with
| Unix.S_REG ->
0x8000 lor if perm land 0o100 <> 0 then 0o755 else 0o644
| Unix.S_DIR -> 0o160000
| Unix.S_CHR -> 0x2000
| Unix.S_BLK -> 0x3000
| Unix.S_FIFO -> 0x1000
| Unix.S_LNK -> 0o120000
| Unix.S_SOCK -> Fmt.invalid_arg "Git does not handle socket"
let oid_of_blob :
type oid. hash:oid hash -> Fpath.t -> (oid, [> Rresult.R.msg ]) result =
fun ~hash path ->
let module Hash = (val module_of hash) in
try
let ic = open_in_bin (Fpath.to_string path) in
let tmp = Bytes.create io_buffer_size in
let ctx = Hash.empty in
let rec go ctx =
let len = input ic tmp 0 (Bytes.length tmp) in
if len = 0 then Hash.get ctx
else
let ctx = Hash.feed_bytes ctx tmp ~off:0 ~len in
go ctx
in
let len = in_channel_length ic in
let ctx = Hash.feed_string ctx (Fmt.str "blob %d\000" len) in
let hash = go ctx in
close_in ic;
Rresult.R.ok hash
with exn ->
Rresult.R.error_msgf "%a: %s" Fpath.pp path (Printexc.to_string exn)
let oid_of_link :
type oid. hash:oid hash -> Fpath.t -> (oid, [> Rresult.R.msg ]) result =
fun ~hash path ->
let module Hash = (val module_of hash) in
let contents = Unix.readlink (Fpath.to_string path) in
let contents =
Astring.String.map (function '\\' -> '/' | c -> c) contents
in
let ctx = Hash.empty in
let ctx =
Hash.feed_string ctx
(Fmt.str "blob %d\000%s" (String.length contents) contents)
in
Rresult.R.ok (Hash.get ctx)
let make ~hash path =
try
let stat = Unix.lstat (Fpath.to_string path) in
let ctime_nsec, ctime_sec = Float.modf stat.Unix.st_ctime in
let ctime_sec = Float.to_int ctime_sec in
let ctime_nsec = Float.to_int (ctime_nsec *. 1_000_000_000.) in
let mtime_nsec, mtime_sec = Float.modf stat.Unix.st_mtime in
let mtime_sec = Float.to_int mtime_sec in
let mtime_nsec = Float.to_int (mtime_nsec *. 1_000_000_000.) in
let open Rresult in
(match stat.Unix.st_kind with
| Unix.S_DIR -> Fmt.invalid_arg "Git sub-module are not implemented"
| Unix.S_REG -> oid_of_blob ~hash path
| Unix.S_LNK -> oid_of_link ~hash path
| _kind -> Fmt.invalid_arg "Invalid kind")
>>| fun oid ->
{
ce_stat =
{
sd_ctime =
{ sec = Int32.of_int ctime_sec; nsec = Int32.of_int ctime_nsec };
sd_mtime =
{ sec = Int32.of_int mtime_sec; nsec = Int32.of_int mtime_nsec };
sd_dev = stat.Unix.st_dev;
sd_ino = stat.Unix.st_ino;
sd_uid = stat.Unix.st_uid;
sd_gid = stat.Unix.st_gid;
sd_size = stat.Unix.st_size;
};
ce_mode = mode_of_unix_kind stat.Unix.st_kind stat.Unix.st_perm;
ce_length = String.length (Fpath.to_string path);
ce_flags = 0;
oid;
name = path;
}
with Unix.Unix_error (err, _, _) ->
Rresult.R.error_msgf "%a: %s" Fpath.pp path (Unix.error_message err)
let pp ~pp_oid ppf t =
Fmt.pf ppf
"{ @[<hov>ce_stat= %a;@ ce_mode= 0o%03o;@ ce_length= %d;@ ce_flags= \
%08x;@ oid= %a;@ name= %a;@] }"
pp_stat t.ce_stat t.ce_mode t.ce_length t.ce_flags pp_oid t.oid Fpath.pp
t.name
let stage t = (_ce_stagemask land t.ce_flags) asr _ce_stageshift
let uptodate t = t.ce_flags land _ce_uptodate
let skip_worktree t = t.ce_flags land _ce_skip_worktree
let mark_uptodate t = t.ce_flags <- t.ce_flags lor _ce_uptodate
let intent_to_add t = t.ce_flags land _ce_intent_to_add
let permissions t = if t.ce_mode land 0o100 <> 0 then 0o755 else 0o644
let strlen buffer ~off =
let res = ref off in
while Bigstringaf.get buffer !res <> '\000' do
incr res
done;
!res
let _offset_of_data =
8
+ 8
+ 4
+ 4
+ 4
+ 4
+ 4
+ 4
let align_padding_size size len = ((size + len + 8) land lnot 7) - (size + len)
let align_flex_name len = (_offset_of_data + len + 8) land lnot 7
let ondisk_cache_entry_size len = align_flex_name len
let ondisk_data_size ~uid_ln flags len =
uid_ln + (if flags land _ce_extended <> 0 then 4 else 2) + len
let ondisk_ce_size ~uid_ln ce =
ondisk_cache_entry_size (ondisk_data_size ~uid_ln ce.ce_flags ce.ce_length)
let load :
type oid.
version:int ->
hash:oid hash ->
?previous:oid t ->
off:int ->
Bigstringaf.t ->
oid t * int =
fun ~version ~hash ?previous ~off buffer ->
let module Hash = (val module_of hash) in
let ctime =
{
sec = Bigstringaf.get_int32_be buffer off;
nsec = Bigstringaf.get_int32_be buffer (off + 4);
}
in
let mtime =
{
sec = Bigstringaf.get_int32_be buffer (off + 8);
nsec = Bigstringaf.get_int32_be buffer (off + 12);
}
in
let dev = Bigstringaf.get_int32_be buffer (off + 16) in
let ino = Bigstringaf.get_int32_be buffer (off + 20) in
let mode = Bigstringaf.get_int32_be buffer (off + 24) in
let uid = Bigstringaf.get_int32_be buffer (off + 28) in
let gid = Bigstringaf.get_int32_be buffer (off + 32) in
let size = Bigstringaf.get_int32_be buffer (off + 36) in
let flags = Bigstringaf.get_int16_be buffer (off + 40 + Hash.digest_size) in
let expand_name_field = version = 4 in
let name_length = flags land _ce_namemask in
let flags, name_offset =
if flags land _ce_extended <> 0 then
( flags
lor Bigstringaf.get_int16_be buffer (off + 40 + Hash.digest_size + 2)
lsl 16,
off + 40 + Hash.digest_size + 4 )
else flags, off + 40 + Hash.digest_size + 2
in
let name =
let name_offset, prefix =
if expand_name_field then
let strip_length, name_offset =
Varint.decode buffer ~off:name_offset
in
match previous with
| Some { ce_length; name = previous_name; _ } ->
if ce_length < strip_length then
Fmt.invalid_arg
"Malformed name field in the index, near path %a" Fpath.pp
previous_name;
( name_offset,
String.sub
(Fpath.to_string previous_name)
0 (ce_length - strip_length) )
| None -> name_offset, ""
else name_offset, ""
in
let name_length =
if name_length = _ce_namemask then strlen buffer ~off:name_offset
else name_length
in
prefix ^ Bigstringaf.substring buffer ~off:name_offset ~len:name_length
in
let oid =
Bigstringaf.substring buffer ~off:(off + 40) ~len:Hash.digest_size
in
let oid = Hash.of_raw_string oid in
let ce =
{
ce_stat =
{
sd_ctime = ctime;
sd_mtime = mtime;
sd_dev = Int32.to_int dev;
sd_ino = Int32.to_int ino;
sd_uid = Int32.to_int uid;
sd_gid = Int32.to_int gid;
sd_size = Int32.to_int size;
};
ce_mode = Int32.to_int mode;
ce_flags = flags land lnot _ce_namemask;
ce_length = String.length name;
oid;
name = Fpath.v name;
}
in
let entry_size = ondisk_ce_size ~uid_ln:Hash.digest_size ce in
ce, off + entry_size
let load ~version ~hash ?previous ~off buffer =
let open Rresult in
try R.ok (load ~version ~hash ?previous ~off buffer) with
| Invalid_argument err -> R.error_msg err
| exn -> raise exn
let padding = Bigstringaf.of_string ~off:0 ~len:1 "\x00"
let store :
type oid.
version:int ->
hash:oid hash ->
?previous:oid t ->
oid t ->
Bigstringaf.t list =
fun ~version:_ ~hash ?previous ce ->
let module Hash = (val module_of hash) in
let size =
_offset_of_data + ondisk_data_size ~uid_ln:Hash.digest_size ce.ce_flags 0
in
let ce_payload = Bigstringaf.create (40 + Hash.digest_size + 4) in
Bigstringaf.set_int32_be ce_payload 0 ce.ce_stat.sd_ctime.sec;
Bigstringaf.set_int32_be ce_payload 4 ce.ce_stat.sd_ctime.nsec;
Bigstringaf.set_int32_be ce_payload 8 ce.ce_stat.sd_mtime.sec;
Bigstringaf.set_int32_be ce_payload 12 ce.ce_stat.sd_mtime.nsec;
Bigstringaf.set_int32_be ce_payload 16 (Int32.of_int ce.ce_stat.sd_dev);
Bigstringaf.set_int32_be ce_payload 20 (Int32.of_int ce.ce_stat.sd_ino);
Bigstringaf.set_int32_be ce_payload 24 (Int32.of_int ce.ce_mode);
Bigstringaf.set_int32_be ce_payload 28 (Int32.of_int ce.ce_stat.sd_uid);
Bigstringaf.set_int32_be ce_payload 32 (Int32.of_int ce.ce_stat.sd_gid);
Bigstringaf.set_int32_be ce_payload 36 (Int32.of_int ce.ce_stat.sd_size);
Bigstringaf.blit_from_string
(Hash.to_raw_string ce.oid)
~src_off:0 ce_payload ~dst_off:40 ~len:Hash.digest_size;
let flags = ce.ce_flags land lnot _ce_namemask in
let flags =
flags
lor if ce.ce_length >= _ce_namemask then _ce_namemask else ce.ce_length
in
Bigstringaf.set_int16_be ce_payload (40 + Hash.digest_size)
(flags land 0xffff);
let prelude =
if ce.ce_flags land _ce_extended <> 0 then (
Bigstringaf.set_int16_be ce_payload
(40 + Hash.digest_size + 2)
(flags asr 16);
ce_payload)
else Bigstringaf.sub ce_payload ~off:0 ~len:(40 + Hash.digest_size + 2)
in
match previous with
| None ->
let name = Fpath.to_string ce.name in
let name =
Bigstringaf.of_string ~off:0 ~len:(String.length name) name
in
let padding =
String.make (align_padding_size size ce.ce_length) '\x00'
in
let padding =
Bigstringaf.of_string ~off:0 ~len:(String.length padding) padding
in
[ prelude; name; padding ]
| Some previous ->
let common = ref 0 in
let a = Fpath.to_string ce.name in
let b = Fpath.to_string previous.name in
while
!common < ce.ce_length
&& !common < previous.ce_length
&& a.[!common] = b.[!common]
do
incr common
done;
let common = !common in
let to_remove = previous.ce_length - common in
let prefix_size = Bigstringaf.create 16 in
let len = Varint.encode prefix_size ~off:0 to_remove in
let prefix_size = Bigstringaf.sub prefix_size ~off:0 ~len in
[
prelude;
prefix_size;
Bigstringaf.of_string ~off:common ~len:(ce.ce_length - common) a;
padding;
]
end
type 'oid t = { mutable entries : 'oid Entry.t array; version : int }
let empty_index_file :
type oid. version:int -> hash:oid hash -> Bigstringaf.t * oid =
fun ~version ~hash ->
let module Hash = (val module_of hash) in
let res = Bigstringaf.create (12 + Hash.digest_size) in
Bigstringaf.set_int32_be res 0 0x44495243l;
Bigstringaf.set_int32_be res 4 (Int32.of_int version);
Bigstringaf.set_int32_be res 8 0l;
let hash = Hash.digest_bigstring (Bigstringaf.sub ~off:0 ~len:12 res) in
let hash = Hash.to_raw_string hash in
Bigstringaf.blit_from_string hash ~src_off:0 res ~dst_off:12
~len:Hash.digest_size;
res, Hash.of_raw_string hash
let make : type oid. ?version:int -> oid hash -> oid t =
fun ?(version = 2) _ -> { entries = [||]; version }
let exists t path =
let rec go n =
if n >= Array.length t.entries then false
else if Fpath.equal t.entries.(n).name path then true
else go (succ n)
in
go 0
let find t path =
let rec go n =
if n >= Array.length t.entries then None
else if Fpath.equal t.entries.(n).name path then Some t.entries.(n)
else go (succ n)
in
go 0
let pos_of_entry t path =
let rec go first last =
if last > first then
let next = first + ((last - first) lsr 1) in
let cmp =
let a = Fpath.to_string path
and b = Fpath.to_string t.entries.(next).name in
let alen = String.length a and blen = String.length b in
let m = min alen blen in
let v = String.(compare (sub a 0 m) (sub b 0 m)) in
if v <> 0 then v
else if alen < blen then -1
else if alen > blen then 1
else 0
in
if cmp = 0 then next
else if cmp < 0 then go first next
else go (next + 1) last
else -first - 1
in
go 0 (Array.length t.entries)
let replace t entry =
if not (exists t entry.Entry.name) then Fmt.invalid_arg "Git_index.replace";
let rec go n =
if Fpath.equal t.entries.(n).Entry.name entry.Entry.name then
t.entries.(n) <- entry
else go (succ n)
in
go 0
let add :
type oid.
hash:oid hash -> Fpath.t -> oid t -> (unit, [> Rresult.R.msg ]) result =
fun ~hash path t ->
let open Rresult in
Entry.make ~hash path >>= fun entry ->
match find t path with
| Some _ ->
replace t entry;
R.ok ()
| None ->
let pos = pos_of_entry t path in
let pos =
if pos >= 0 then
pos
else -pos - 1
in
let init entry i =
if i < pos then t.entries.(i)
else if i = pos then entry
else t.entries.(i - 1)
in
let res = Array.init (Array.length t.entries + 1) (init entry) in
t.entries <- res;
R.ok ()
let rem : Fpath.t -> 'oid t -> unit =
fun path t ->
let pos = pos_of_entry t path in
let pos = if pos < 0 then -pos - 1 else pos in
let rec go pos =
if
pos < Array.length t.entries
&& Fpath.compare t.entries.(pos).name path = 0
then (
let res =
Array.init
(Array.length t.entries - 1)
(fun i -> if i < pos then t.entries.(i) else t.entries.(i + 1))
in
t.entries <- res;
go pos)
in
go pos
let ( <.> ) f g x = f (g x)
type 'oid elt = [ `Tree of Fpath.t | `Blob of 'oid Entry.t ]
let create_graph : 'oid t -> ([ 'oid elt | `Root ], 'oid elt list) Hashtbl.t =
fun t ->
let tbl = Hashtbl.create ~random:false (Array.length t.entries) in
let put tbl k v =
try
let vs = Hashtbl.find tbl k in
if
not
(List.exists
(fun v' ->
match v, v' with
| `Tree p, `Tree p' -> Fpath.equal p p'
| `Blob e, `Blob e' -> Fpath.equal e.Entry.name e'.Entry.name
| _ -> false)
vs)
then Hashtbl.replace tbl k (v :: vs)
with Not_found -> Hashtbl.add tbl k [ v ]
in
Hashtbl.add tbl `Root [];
let insert ({ Entry.name; _ } as entry) =
match Fpath.segs name with
| [] -> ()
| [ _ ] ->
Hashtbl.add tbl (`Blob entry) [];
put tbl `Root (`Blob entry)
| segs ->
let segs = (List.rev <.> List.tl <.> List.rev) segs in
let bottom =
List.fold_left
(function
| `Root ->
fun seg ->
put tbl `Root (`Tree (Fpath.v seg));
`Tree (Fpath.v seg)
| `Tree tree as bottom ->
fun seg ->
put tbl bottom (`Tree Fpath.(tree / seg));
`Tree Fpath.(tree / seg))
`Root segs
in
put tbl
(bottom :> [ `Blob of _ Entry.t | `Tree of Fpath.t | `Root ])
(`Blob entry);
Hashtbl.add tbl (`Blob entry) []
in
Array.iter insert t.entries;
tbl
let sort :
([ `Root | 'oid elt ], 'oid elt list) Hashtbl.t ->
([ `Root | 'oid elt ] list, [> Rresult.R.msg ]) result =
fun tbl ->
let find_nodes tbl =
let fold k vs acc = match vs with [] -> k :: acc | _ -> acc in
Hashtbl.fold fold tbl []
in
let remove_nodes nodes tbl = List.iter (Hashtbl.remove tbl) nodes in
let remove_dep tbl dep =
let go dep tbl k =
let deps = Hashtbl.find tbl k in
let deps =
if List.exists (( = ) dep) deps then List.filter (( <> ) dep) deps
else deps
in
Hashtbl.remove tbl k;
Hashtbl.add tbl k deps
in
let ks = Hashtbl.fold (fun k _ a -> k :: a) tbl [] in
List.iter (go dep tbl) ks
in
let rec go deps tbl acc =
match deps with
| [] -> acc
| `Root :: deps ->
let nodes = find_nodes tbl in
remove_nodes nodes tbl;
go (List.append deps nodes) tbl (List.append acc nodes)
| (#elt as dep) :: deps ->
remove_dep tbl dep;
let nodes = find_nodes tbl in
remove_nodes nodes tbl;
go (List.append deps nodes) tbl (List.append acc nodes)
in
let bases = find_nodes tbl in
remove_nodes bases tbl;
let sorted_ks = go bases tbl [] in
let sorted_ks = List.append bases sorted_ks in
let remaining = Hashtbl.fold (fun k _ a -> k :: a) tbl [] in
let pp_v ppf = function
| `Root -> Fmt.pf ppf "<root>"
| `Tree p -> Fmt.pf ppf "<tree:%a>" Fpath.pp p
| `Blob { Entry.name = p; _ } -> Fmt.pf ppf "<blob:%a>" Fpath.pp p
in
match remaining with
| [] -> Ok sorted_ks
| _ ->
Rresult.R.error_msgf "Git_index.sort: cycle (remaining; @[<hov>%a@])"
Fmt.(Dump.hashtbl pp_v (Dump.list pp_v))
tbl
let ( >>? ) x f =
let open Lwt.Infix in
x >>= function Ok x -> f x | Error err -> Lwt.return_error err
let fold :
type oid.
f:
([ oid elt | `Root ] ->
oid elt list ->
'a ->
('a, ([> Rresult.R.msg ] as 'err)) result Lwt.t) ->
'a ->
oid t ->
('a, 'err) result Lwt.t =
fun ~f acc t ->
let graph = create_graph t in
let rec go acc = function
| [] -> Lwt.return_ok acc
| `Root :: rest ->
let vs = Option.value (Hashtbl.find_opt graph `Root) ~default:[] in
f `Root vs acc >>? fun acc -> go acc rest
| (`Tree _ as tree) :: rest ->
let vs = Option.value (Hashtbl.find_opt graph tree) ~default:[] in
f tree vs acc >>? fun acc -> go acc rest
| (`Blob _ as blob) :: rest -> f blob [] acc >>? fun acc -> go acc rest
in
match sort (Hashtbl.copy graph) with
| Ok lst -> go acc lst
| Error err -> Lwt.return_error err
let buffer =
let open Rresult in
if Bigstringaf.get_int32_be buffer 0 <> 0x44495243l then
R.error_msgf "Invalid DIRC file (%08lx)" (Bigstringaf.get_int32_be buffer 0)
else
let version = Int32.to_int (Bigstringaf.get_int32_be buffer 4) in
if version < 2 || version > 4 then R.error_msgf "Invalid version of DIRC"
else
let entries = Int32.to_int (Bigstringaf.get_int32_be buffer 8) in
R.ok (version, entries)
let ( .%[] ) : string -> int -> int32 =
fun str off -> Int32.of_int (Char.code str.[off])
let load_extension ~off buffer =
let _size = Int32.to_int (Bigstringaf.get_int32_be buffer (off + 4)) in
let ext =
let str = Bigstringaf.substring buffer ~off ~len:4 in
let ( lsl ) = Int32.shift_left and ( lor ) = Int32.logor in
(str.%[0] lsl 24) lor (str.%[1] lsl 16) lor (str.%[2] lsl 8) lor str.%[3]
in
match ext with
| 0x54524545l -> assert false
| ext -> Fmt.invalid_arg "Invalid or unsupported extension: %08lx" ext
let load :
type oid. hash:oid hash -> Fpath.t -> (oid t, [> Rresult.R.msg ]) result =
fun ~hash path ->
let open Rresult in
let module Hash = (val module_of hash) in
let load path =
try
let fd = Unix.openfile (Fpath.to_string path) Unix.[ O_RDONLY ] 0o600 in
let stat = Unix.fstat fd in
let mmap =
Unix.map_file fd ~pos:0L Bigarray.char Bigarray.c_layout false
[| stat.Unix.st_size |]
in
Unix.close fd;
Ok mmap
with
| Unix.Unix_error (err, _, _) ->
R.error_msgf "Git_index.load: %s" (Unix.error_message err)
| exn -> raise exn
in
load path >>= fun mmap ->
let mmap = Bigarray.array1_of_genarray mmap in
load_header mmap >>= fun (version, nr) ->
let rec go i off acc =
if i <= 0 then R.ok (List.rev acc, off)
else
match acc with
| previous :: _ ->
Entry.load ~version ~hash ~previous ~off mmap >>= fun (entry, off) ->
go (pred i) off (entry :: acc)
| [] ->
Entry.load ~version ~hash ~off mmap >>= fun (entry, off) ->
go (pred i) off (entry :: acc)
in
go nr 12 [] >>= fun (entries, _off) ->
let oid0 =
Hash.digest_bigstring ~off:0
~len:(Bigstringaf.length mmap - Hash.digest_size)
mmap
in
let oid1 =
let off = Bigstringaf.length mmap - Hash.digest_size in
let len = Hash.digest_size in
Hash.of_raw_string (Bigstringaf.substring mmap ~off ~len)
in
if Hash.equal oid0 oid1 then R.ok { entries = Array.of_list entries; version }
else R.error_msgf "Invalid hash (%a <> %a)" Hash.pp oid0 Hash.pp oid1
let store :
type fd oid.
hash:oid hash -> append:(fd -> Bigstringaf.t -> fd) -> fd -> oid t -> fd =
fun ~hash ~append fd t ->
let module Hash = (val module_of hash) in
let extended =
let v = ref false in
for i = 0 to Array.length t.entries - 1 do
t.entries.(i).ce_flags <-
t.entries.(i).ce_flags land lnot Entry._ce_extended;
if t.entries.(i).ce_flags land Entry._ce_extended_flags <> 0 then (
v := true;
t.entries.(i).ce_flags <- t.entries.(i).ce_flags lor Entry._ce_extended)
done;
!v
in
let version = if extended then 3 else 2 in
let = Bigstringaf.create 12 in
Bigstringaf.set_int32_be header 0 0x44495243l;
Bigstringaf.set_int32_be header 4 (Int32.of_int version);
Bigstringaf.set_int32_be header 8 (Int32.of_int (Array.length t.entries));
let rec go fd ctx i =
if i >= Array.length t.entries then fd, ctx
else
let entry = t.entries.(i) in
let previous =
if version < 4 || i = 0 then None else Some t.entries.(i - 1)
in
let payloads = Entry.store ~version ~hash ?previous entry in
let fd = List.fold_left append fd payloads in
let ctx = Hash.feedi_bigstring ctx (fun f -> List.iter f payloads) in
go fd ctx (succ i)
in
let ctx = Hash.empty in
let fd = append fd header in
let ctx = Hash.feed_bigstring ctx header in
let fd, ctx = go fd ctx 0 in
let hash = Hash.get ctx in
let hash = Hash.to_raw_string hash in
append fd (Bigstringaf.of_string ~off:0 ~len:Hash.digest_size hash)
let store_to_path :
type oid.
hash:oid hash -> Fpath.t -> oid t -> (unit, [> Rresult.R.msg ]) result =
fun ~hash path t ->
try
let fd =
Unix.openfile (Fpath.to_string path)
Unix.[ O_WRONLY; O_CREAT; O_TRUNC ]
0o600
in
let append fd buf =
let rec go fd buf off len =
if len > 0 then
let str = Bigstringaf.substring ~off ~len buf in
let len' = Unix.write fd (Bytes.unsafe_of_string str) 0 len in
go fd buf (off + len') (len - len')
in
go fd buf 0 (Bigstringaf.length buf);
fd
in
let _ = store ~hash ~append fd t in
Unix.close fd;
Rresult.R.ok ()
with Unix.Unix_error (err, _, _) ->
Rresult.R.error_msgf "Git_index.store_to_path %a: %s" Fpath.pp path
(Unix.error_message err)