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
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
|
/*
* Precomputed diff hunks, keyed by diff input.
*
* A single store at .git/objects/info/diff-hunks maps an (old blob,
* new blob, xdl_opts) key to the hunk coordinates of diffing the pair.
* The key determines the diff result (only trim-stable pairs are
* recorded; see diff-hunks.h), so an entry is valid in any context it
* recurs in, independent of path. Reading is on by default
* (core.diffHunks); writing is off by default and enabled per run or
* by configuration (see diff_hunks_write_enabled), so an ordinary
* command populates the store only during a warming run the
* repository owner opts into.
*
* File layout:
* Header: "DHPF"(4) + version(1) + hash_version(1)
* + num_chunks(1) + reserved(1)
* Table of contents (chunk-format)
* DHIX chunk: sorted entries, each
* old_blob_oid, new_blob_oid, xdl_opts(4), hdat_offset(4)
* DHDT chunk: per entry, num_hunks(4) followed by that many 16-byte hunks
* Trailing hash checksum
*/
#include "git-compat-util.h"
#include "chunk-format.h"
#include "config.h"
#include "csum-file.h"
#include "diff-hunks.h"
#include "diff-provider-internal.h"
#include "diff.h"
#include "gettext.h"
#include "hash.h"
#include "hashmap.h"
#include "lockfile.h"
#include "odb.h"
#include "path.h"
#include "repo-settings.h"
#include "repository.h"
#include "strbuf.h"
#include "wrapper.h"
#define DIFF_HUNKS_SIGNATURE 0x44485046 /* "DHPF" */
/*
* Bump when the on-disk format changes, or when xdiff's emitted hunk
* coordinates change for a fixed (blobs, xdl_opts) key: an old store
* would otherwise serve stale hunks and change command output.
*/
#define DIFF_HUNKS_VERSION 1
#define DIFF_HUNKS_HEADER_SIZE 8
#define DIFF_HUNKS_CHUNKID_INDEX 0x44484958 /* "DHIX" */
#define DIFF_HUNKS_CHUNKID_DATA 0x44484454 /* "DHDT" */
/*
* Each hunk is 16 bytes on disk:
* old_start(4) old_count(4) new_start(4) new_count(4)
*/
#define DIFF_HUNKS_HUNK_SIZE (4 * sizeof(uint32_t))
/*
* Result of a store lookup: num_hunks records encoded in the store's mmap,
* valid until the store is freed. Read them with nth_precomputed_hunk().
*/
struct precomputed_entry {
uint32_t num_hunks;
const unsigned char *hunk_data;
};
/* Decode a single hunk from the raw on-disk format. */
static inline void decode_precomputed_hunk(const unsigned char *data,
struct precomputed_hunk *h)
{
h->old_start = get_be32(data);
h->old_count = get_be32(data + 4);
h->new_start = get_be32(data + 8);
h->new_count = get_be32(data + 12);
}
/* Decode the nth hunk of a lookup result into *h. */
static inline void nth_precomputed_hunk(const struct precomputed_entry *e,
uint32_t n, struct precomputed_hunk *h)
{
decode_precomputed_hunk(e->hunk_data + (size_t)n * DIFF_HUNKS_HUNK_SIZE, h);
}
/* Byte length of the (old_oid, new_oid, xdl_opts) lookup key. */
static size_t store_index_key_size(const struct git_hash_algo *algo)
{
return 2 * algo->rawsz + sizeof(uint32_t);
}
/* Index entry: the lookup key followed by the 4-byte offset into DHDT. */
static size_t store_index_entry_size(const struct git_hash_algo *algo)
{
return store_index_key_size(algo) + sizeof(uint32_t);
}
/*
* The smallest a valid store file can be: the header, a table of contents
* with one entry per chunk plus a terminating entry, and the trailing
* checksum.
*/
static size_t store_min_size(const struct git_hash_algo *algo,
uint8_t num_chunks)
{
size_t toc_size = (num_chunks + 1) * CHUNK_TOC_ENTRY_SIZE;
return DIFF_HUNKS_HEADER_SIZE + toc_size + algo->rawsz;
}
/*
* Decode an index entry's key into pointers to the two oids and the
* xdl_opts value (on-disk: old_oid, new_oid, then xdl_opts as a
* big-endian uint32).
*/
static void decode_store_index_key(const unsigned char *entry, unsigned int rawsz,
const unsigned char **old_hash,
const unsigned char **new_hash,
uint32_t *xdl_opts)
{
*old_hash = entry;
*new_hash = entry + rawsz;
*xdl_opts = get_be32(entry + 2 * rawsz);
}
/* The DHDT offset stored in an index entry, in the field after its key. */
static uint32_t index_entry_hdat_offset(const unsigned char *entry, size_t keysz)
{
return get_be32(entry + keysz);
}
static char *diff_hunks_store_path(struct repository *r)
{
return xstrfmt("%s/info/diff-hunks", repo_get_object_directory(r));
}
struct diff_hunks_store {
const unsigned char *data;
size_t data_len;
const struct git_hash_algo *hash_algo;
const unsigned char *index;
uint32_t num_entries;
const unsigned char *hdat;
size_t hdat_size;
/* Consultation counters; see diff_hunks_read_stats(). */
unsigned long read_hits;
unsigned long read_misses;
};
static void free_store(struct diff_hunks_store *s)
{
if (!s)
return;
if (s->data)
munmap((void *)s->data, s->data_len);
free(s);
}
/*
* Open, mmap, and parse the store at fname. Returns the parsed store
* or NULL on any error. The diff output is unaffected either way;
* corruption is reported by verify, not treated as fatal here.
*/
static struct diff_hunks_store *load_store_at(
const struct git_hash_algo *repo_algo, const char *fname)
{
struct diff_hunks_store *s;
struct chunkfile *cf;
int fd;
struct stat st;
void *data;
const unsigned char *p;
uint8_t num_chunks;
size_t index_size, entry_size, data_len;
fd = git_open(fname);
if (fd < 0)
return NULL;
if (fstat(fd, &st) || st.st_size < DIFF_HUNKS_HEADER_SIZE) {
close(fd);
return NULL;
}
data_len = xsize_t(st.st_size);
data = xmmap(NULL, data_len, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
p = data;
num_chunks = p[6];
/*
* Reject a file that is not a readable store: wrong signature,
* version, or object hash, or too small to hold the table of
* contents that read_table_of_contents() walks (it dereferences
* each entry before range-checking its offset).
*/
if (get_be32(p) != DIFF_HUNKS_SIGNATURE ||
p[4] != DIFF_HUNKS_VERSION ||
p[5] != oid_version(repo_algo) ||
data_len < store_min_size(repo_algo, num_chunks)) {
munmap(data, data_len);
return NULL;
}
/*
* The trailing checksum is not verified here: the writer fsyncs
* and commits atomically, so a committed file is intact, and
* every record is bounds-checked at read (see precomputed_entry_at).
* The checksum is checked separately, by diff_hunks_verify().
*/
CALLOC_ARRAY(s, 1);
s->data = data;
s->data_len = data_len;
s->hash_algo = repo_algo;
cf = init_chunkfile(NULL);
if (read_table_of_contents_quiet(cf, p, data_len,
DIFF_HUNKS_HEADER_SIZE, num_chunks, 1,
repo_algo) ||
pair_chunk(cf, DIFF_HUNKS_CHUNKID_INDEX, &s->index, &index_size) ||
pair_chunk(cf, DIFF_HUNKS_CHUNKID_DATA, &s->hdat, &s->hdat_size)) {
free_chunkfile(cf);
goto corrupt;
}
free_chunkfile(cf);
entry_size = store_index_entry_size(s->hash_algo);
if (index_size % entry_size)
goto corrupt;
s->num_entries = index_size / entry_size;
return s;
corrupt:
free_store(s);
return NULL;
}
static struct diff_hunks_store *diff_hunks_store_load(struct repository *r)
{
struct diff_hunks_store *s;
char *fname;
prepare_repo_settings(r);
if (!r->settings.core_diff_hunks)
return NULL;
fname = diff_hunks_store_path(r);
s = load_store_at(r->hash_algo, fname);
free(fname);
return s;
}
struct diff_hunks_store *repo_diff_hunks_store(struct repository *r)
{
if (!r->objects)
return NULL;
if (r->objects->diff_hunks_store_attempted)
return r->objects->diff_hunks_store;
r->objects->diff_hunks_store_attempted = 1;
r->objects->diff_hunks_store = diff_hunks_store_load(r);
return r->objects->diff_hunks_store;
}
void diff_hunks_read_stats(struct repository *r,
unsigned long *hits, unsigned long *misses)
{
struct diff_hunks_store *s = repo_diff_hunks_store(r);
*hits = s ? s->read_hits : 0;
*misses = s ? s->read_misses : 0;
}
void close_diff_hunks_store(struct object_database *o)
{
if (!o->diff_hunks_store)
return;
free_store(o->diff_hunks_store);
o->diff_hunks_store = NULL;
}
/*
* Fill *out with the hunk record at offset in the data chunk, and return
* 1 if the record is in bounds, 0 otherwise. The read path does not
* re-verify the checksum, and a valid checksum would not bound the count
* anyway, so a read must call this and use *out only when it returns
* non-zero.
*
* A record is a be32 hunk count followed by that many DIFF_HUNKS_HUNK_SIZE
* hunks. "remaining" tracks the bytes from offset to the end of the data
* chunk: it must hold the count, and after the count is consumed it must
* hold every hunk. The bounds are written as subtraction and division
* (never addition or multiplication) so a crafted offset or count cannot
* overflow them.
*/
static int precomputed_entry_at(const struct diff_hunks_store *s,
uint32_t offset, struct precomputed_entry *out)
{
size_t remaining;
uint32_t num_hunks;
if (offset >= s->hdat_size)
return 0;
remaining = s->hdat_size - offset;
if (remaining < sizeof(uint32_t))
return 0;
num_hunks = get_be32(s->hdat + offset);
remaining -= sizeof(uint32_t);
if (num_hunks > remaining / DIFF_HUNKS_HUNK_SIZE)
return 0;
out->num_hunks = num_hunks;
out->hunk_data = s->hdat + offset + sizeof(uint32_t);
return 1;
}
struct lookup_key {
const struct object_id *old_oid;
const struct object_id *new_oid;
int xdl_opts;
unsigned int rawsz;
};
/*
* The store's total order over (old_oid, new_oid, xdl_opts), defined
* once so the write-side sort (writer_entry_cmp) and the read-side
* search (store_bsearch_cmp) order the keys identically.
*/
static int cmp_store_index_key(const unsigned char *old_a, const unsigned char *new_a,
uint32_t opts_a,
const unsigned char *old_b, const unsigned char *new_b,
uint32_t opts_b, unsigned int rawsz)
{
int cmp = memcmp(old_a, old_b, rawsz);
if (!cmp)
cmp = memcmp(new_a, new_b, rawsz);
if (!cmp)
cmp = (opts_a > opts_b) - (opts_a < opts_b);
return cmp;
}
static int store_bsearch_cmp(const void *key, const void *entry_ptr)
{
const struct lookup_key *k = key;
const unsigned char *old_hash, *new_hash;
uint32_t xdl_opts;
decode_store_index_key(entry_ptr, k->rawsz, &old_hash, &new_hash,
&xdl_opts);
return cmp_store_index_key(k->old_oid->hash, k->new_oid->hash,
(uint32_t)k->xdl_opts,
old_hash, new_hash, xdl_opts, k->rawsz);
}
static int store_get_one(struct diff_hunks_store *s, const struct lookup_key *key,
struct precomputed_entry *out)
{
size_t entry_size = store_index_entry_size(s->hash_algo);
const unsigned char *found;
found = bsearch(key, s->index, s->num_entries, entry_size,
store_bsearch_cmp);
if (!found)
return 0;
return precomputed_entry_at(s,
index_entry_hdat_offset(found, store_index_key_size(s->hash_algo)),
out);
}
static int diff_hunks_store_get(struct diff_hunks_store *s,
const struct object_id *old_oid,
const struct object_id *new_oid,
int xdl_opts,
struct precomputed_entry *out)
{
struct lookup_key key;
if (!s)
return 0;
/* The null OID names no blob and cannot key an entry. */
if (is_null_oid(old_oid) || is_null_oid(new_oid))
return 0;
key.old_oid = old_oid;
key.new_oid = new_oid;
key.xdl_opts = xdl_opts;
key.rawsz = s->hash_algo->rawsz;
return store_get_one(s, &key, out);
}
/*
* A recorded hunk sequence must satisfy the provider interface's
* shared check (diff_provider_check_hunk()) before it may be replayed:
* coordinates decode from be32 into long, which is 32-bit on some
* platforms, so a crafted value can decode negative or out of order.
* An entry that fails reads as a miss, so the caller recomputes.
*/
static int replayable_hunks(const struct precomputed_entry *e)
{
struct diff_provider_hunks_check c = { 0 };
uint32_t i;
/*
* Replaying a record with no hunks would assert the blob pair
* equivalent, a claim the store must never make (the writer
* refuses to record one), so such a record is invalid.
*/
if (!e->num_hunks)
return 0;
for (i = 0; i < e->num_hunks; i++) {
struct precomputed_hunk h;
nth_precomputed_hunk(e, i, &h);
if (diff_provider_check_hunk(&c, h.old_start, h.old_count,
h.new_start, h.new_count))
return 0;
}
return 1;
}
int diff_hunks_replay(struct diff_hunks_store *s,
const struct object_id *old_oid,
const struct object_id *new_oid,
int xdl_opts,
xdl_emit_hunk_consume_func_t hunk_func, void *cb_data)
{
struct precomputed_entry e;
uint32_t i;
if (!s)
return 0;
if (!diff_hunks_store_get(s, old_oid, new_oid, xdl_opts, &e) ||
!replayable_hunks(&e)) {
s->read_misses++;
return 0;
}
for (i = 0; i < e.num_hunks; i++) {
struct precomputed_hunk h;
nth_precomputed_hunk(&e, i, &h);
hunk_func(h.old_start, h.old_count,
h.new_start, h.new_count, cb_data);
}
s->read_hits++;
return 1;
}
/*
* The store's consult implementation. The store is not
* authoritative, so it serves a recorded pair or passes; what the
* recording key cannot express, it excludes here with the
* stop-no-record disposition. None of those legs reaches
* diff_hunks_replay(), so none of them counts as a miss.
*/
static enum diff_provider_disposition
diff_hunks_store_consult(struct diff_provider *provider UNUSED,
const struct diff_provider_request *req,
diff_provider_fill_fn fill UNUSED,
void *fill_data UNUSED,
xdl_emit_hunk_consume_func_t hunk_cb, void *cb_data)
{
/*
* xpparam_t is the consult's parameter input. Its flags are
* the store key's xdl_opts; ignore_regex (-I) and anchors
* (--anchored) shape the diff outside the key, so such a
* request is neither served nor recorded.
*
* Adding an xpparam_t field fires this assert (its size no
* longer matches the reference struct). To clear it: (1) add
* the field to the reference struct below; then (2) decide how
* it affects the key: make it part of the key, or exclude
* diffs that use it here with the disposition below. The
* assert only tracks size: a same-size reorder or a changed
* field meaning slips past, so re-read the fields when it
* fires.
*/
(void)BUILD_ASSERT_OR_ZERO(sizeof(xpparam_t) == sizeof(struct {
unsigned long flags;
regex_t **ignore_regex;
size_t ignore_regex_nr;
char **anchors;
size_t anchors_nr;
}));
if (req->xpp->ignore_regex_nr || req->xpp->anchors_nr)
return DIFF_PROVIDER_DISP_STOP_NO_RECORD;
/*
* Break detection (-B) rescores the pair outside xpparam_t, so
* it is outside the key for the same reason.
*/
if (req->diffopt && req->diffopt->break_opt != -1)
return DIFF_PROVIDER_DISP_STOP_NO_RECORD;
if (!req->old_oid || !req->new_oid)
return DIFF_PROVIDER_DISP_PASS;
if (diff_hunks_replay(repo_diff_hunks_store(req->repo),
req->old_oid, req->new_oid,
req->xpp->flags, hunk_cb, cb_data))
return DIFF_PROVIDER_DISP_ANSWERED;
return DIFF_PROVIDER_DISP_PASS;
}
/*
* The provider borrows the repository's store through
* repo_diff_hunks_store() per request; the object database owns the
* file and tears it down, so there is nothing to release here.
*/
struct diff_provider *diff_hunks_store_provider_new(void)
{
struct diff_provider *p = xcalloc(1, sizeof(*p));
p->consult = diff_hunks_store_consult;
return p;
}
/* Validate one store file. Returns 0 if valid or absent, -1 on any error. */
static int verify_store_at(struct repository *r, const char *fname)
{
struct diff_hunks_store *s;
size_t entry_size;
uint32_t i;
int fd;
int ret = 0;
/*
* A file that cannot be opened is not evidence of corruption:
* report the open error, and reserve the corruption diagnostics
* below for a file that was read and failed to parse.
*/
fd = git_open(fname);
if (fd < 0) {
if (errno == ENOENT)
return 0; /* absent is valid */
return error_errno(_("unable to open diff-hunks store %s"),
fname);
}
close(fd);
s = load_store_at(r->hash_algo, fname);
if (!s)
return error(_("diff-hunks store failed to load (corrupt "
"header or hash mismatch): %s"), fname);
if (!hashfile_checksum_valid(r->hash_algo, s->data, s->data_len)) {
error(_("diff-hunks store has incorrect checksum and is "
"likely corrupt: %s"), fname);
free_store(s);
return -1;
}
entry_size = store_index_entry_size(s->hash_algo);
for (i = 0; i < s->num_entries; i++) {
const unsigned char *ep = s->index + st_mult(entry_size, i);
size_t keysz = store_index_key_size(s->hash_algo);
uint32_t offset = index_entry_hdat_offset(ep, keysz);
struct precomputed_entry pe;
/*
* Keyed by (old_oid, new_oid, xdl_opts), increasing. memcmp
* matches cmp_store_index_key's integer comparison of
* xdl_opts because it is non-negative, so its big-endian
* bytes order the same as its value.
*/
if (i > 0 && memcmp(ep - entry_size, ep, keysz) >= 0) {
error(_("diff-hunks entry %u not in sorted order"), i);
ret = -1;
}
if (!precomputed_entry_at(s, offset, &pe)) {
error(_("diff-hunks entry %u has out-of-bounds hunk "
"data"), i);
ret = -1;
} else if (!replayable_hunks(&pe)) {
error(_("diff-hunks entry %u holds an invalid hunk "
"sequence"), i);
ret = -1;
}
}
free_store(s);
return ret;
}
int diff_hunks_verify(struct repository *r)
{
char *fname = diff_hunks_store_path(r);
int ret = 0;
if (verify_store_at(r, fname))
ret = -1;
free(fname);
return ret;
}
int diff_hunks_clear(struct repository *r)
{
char *fname = diff_hunks_store_path(r);
int ret = 0;
if (unlink(fname) && errno != ENOENT)
ret = error_errno(_("unable to remove %s"), fname);
free(fname);
return ret;
}
struct writer_entry {
struct object_id old_oid;
struct object_id new_oid;
int xdl_opts;
uint32_t hdat_offset;
};
struct diff_hunks_writer {
struct repository *r;
struct writer_entry *entries;
size_t nr, alloc;
size_t seed_nr; /* nr after seeding; finish skips a no-op flush */
unsigned force_flush : 1; /* seed pruned: rewrite even a no-op warm */
struct strbuf hdat;
struct hashmap dedup; /* hunk block content -> offset in hdat */
};
/* A record of one distinct hunk block already present in hdat. */
struct dedup_entry {
struct hashmap_entry ent;
uint32_t offset;
uint32_t len;
};
static int dedup_cmp(const void *cmp_data,
const struct hashmap_entry *a,
const struct hashmap_entry *b,
const void *keydata UNUSED)
{
const struct diff_hunks_writer *writer = cmp_data;
const struct dedup_entry *ea = container_of(a, const struct dedup_entry, ent);
const struct dedup_entry *eb = container_of(b, const struct dedup_entry, ent);
if (ea->len != eb->len)
return 1;
return memcmp(writer->hdat.buf + ea->offset,
writer->hdat.buf + eb->offset, ea->len);
}
static struct diff_hunks_writer *diff_hunks_writer_new(struct repository *r)
{
struct diff_hunks_writer *w;
CALLOC_ARRAY(w, 1);
w->r = r;
strbuf_init(&w->hdat, 0);
hashmap_init(&w->dedup, dedup_cmp, w, 0);
return w;
}
static void strbuf_put_be32(struct strbuf *sb, uint32_t val)
{
unsigned char buf[4];
put_be32(buf, val);
strbuf_add(sb, buf, 4);
}
/*
* The hunk block just appended at `start` is deduplicated: if an
* identical block is already in hdat, this copy is dropped and the
* earlier offset returned; otherwise it is kept and remembered.
* Distinct keys that diff to the same hunks then share one block.
*/
static uint32_t intern_block(struct diff_hunks_writer *w, size_t start)
{
size_t len = w->hdat.len - start;
struct dedup_entry key, *found, *added;
hashmap_entry_init(&key.ent, memhash(w->hdat.buf + start, len));
key.offset = (uint32_t)start;
key.len = (uint32_t)len;
found = hashmap_get_entry(&w->dedup, &key, ent, NULL);
if (found) {
strbuf_setlen(&w->hdat, start);
return found->offset;
}
added = xmalloc(sizeof(*added));
hashmap_entry_init(&added->ent, key.ent.hash);
added->offset = key.offset;
added->len = key.len;
hashmap_add(&w->dedup, &added->ent);
return key.offset;
}
int diff_hunks_writer_add(struct diff_hunks_writer *w,
const struct object_id *old_oid,
const struct object_id *new_oid,
int xdl_opts,
const struct precomputed_hunk *hunks,
size_t nr_hunks)
{
struct writer_entry *e;
size_t i, block_start;
if (!w)
return 0;
/*
* The block appended for this entry is sizeof(uint32_t) +
* nr_hunks * DIFF_HUNKS_HUNK_SIZE bytes. Bound nr_hunks so that
* length fits the uint32_t the dedup index records (and so the
* count itself fits the uint32_t written to the store).
*/
if (!nr_hunks ||
nr_hunks > (UINT32_MAX - sizeof(uint32_t)) / DIFF_HUNKS_HUNK_SIZE ||
is_null_oid(old_oid) || is_null_oid(new_oid))
return 0;
if (w->hdat.len > UINT32_MAX)
return 0;
/*
* Coordinates are stored as 32-bit values; a result that cannot
* round-trip is dropped rather than silently truncated.
*/
for (i = 0; i < nr_hunks; i++)
if ((uintmax_t)hunks[i].old_start > (uintmax_t)INT32_MAX ||
(uintmax_t)hunks[i].old_count > (uintmax_t)INT32_MAX ||
(uintmax_t)hunks[i].new_start > (uintmax_t)INT32_MAX ||
(uintmax_t)hunks[i].new_count > (uintmax_t)INT32_MAX)
return 0;
ALLOC_GROW(w->entries, w->nr + 1, w->alloc);
e = &w->entries[w->nr++];
oidcpy(&e->old_oid, old_oid);
oidcpy(&e->new_oid, new_oid);
e->xdl_opts = xdl_opts;
block_start = w->hdat.len;
strbuf_put_be32(&w->hdat, (uint32_t)nr_hunks);
for (i = 0; i < nr_hunks; i++) {
strbuf_put_be32(&w->hdat, hunks[i].old_start);
strbuf_put_be32(&w->hdat, hunks[i].old_count);
strbuf_put_be32(&w->hdat, hunks[i].new_start);
strbuf_put_be32(&w->hdat, hunks[i].new_count);
}
e->hdat_offset = intern_block(w, block_start);
return 1;
}
void diff_hunks_writer_record_stable(struct diff_hunks_writer *w,
const struct object_id *old_oid,
const struct object_id *new_oid,
int xdl_opts,
const struct precomputed_hunk *trimmed,
size_t nr_trimmed,
const struct precomputed_hunk *full,
size_t nr_full)
{
size_t i;
if (!w)
return;
/*
* Record only a trim-stable pair, one whose trimmed and
* untrimmed diffs are identical, so the single entry answers
* any consumer at any context (see the top of this file). A
* pair where the two diffs differ is never recorded and every
* consumer computes it.
*/
if (nr_trimmed != nr_full)
return;
for (i = 0; i < nr_trimmed; i++)
if (trimmed[i].old_start != full[i].old_start ||
trimmed[i].old_count != full[i].old_count ||
trimmed[i].new_start != full[i].new_start ||
trimmed[i].new_count != full[i].new_count)
return;
diff_hunks_writer_add(w, old_oid, new_oid, xdl_opts,
trimmed, nr_trimmed);
}
/*
* Seed the writer with fname's entries so a rewrite preserves them,
* setting *pruned when the rewrite will not carry the whole file
* forward: the file failed its checksum and was discarded outright, or
* individual entries were dropped because they failed the replayable
* check or the writer refused them (a key naming no blob). A
* rewrite re-checksums, so corruption must not be carried forward:
* that would launder it into a checksum-valid file that verify can no
* longer catch. This path already reads the whole file, so verify the
* checksum here (the reader keeps trusting committed files, without
* re-checksumming); an invalid
* entry reads as a miss anyway, so dropping it heals the store rather
* than losing anything a reader could use.
*/
static void diff_hunks_writer_seed(struct diff_hunks_writer *w,
const char *fname, int *pruned)
{
struct diff_hunks_store *s = load_store_at(w->r->hash_algo, fname);
unsigned int rawsz;
size_t entry_size, keysz;
struct precomputed_hunk *hunks = NULL;
size_t hunks_alloc = 0;
uint32_t i, dropped = 0;
if (!s)
return;
if (!hashfile_checksum_valid(w->r->hash_algo, s->data, s->data_len)) {
warning(_("diff-hunks store %s failed its checksum; "
"discarding it"), fname);
free_store(s);
*pruned = 1;
return;
}
rawsz = s->hash_algo->rawsz;
entry_size = store_index_entry_size(s->hash_algo);
keysz = store_index_key_size(s->hash_algo);
for (i = 0; i < s->num_entries; i++) {
const unsigned char *ep = s->index + st_mult(entry_size, i);
const unsigned char *old_hash, *new_hash;
struct object_id old_oid, new_oid;
uint32_t xdl_opts, j;
struct precomputed_entry pe;
decode_store_index_key(ep, rawsz, &old_hash, &new_hash,
&xdl_opts);
oidread(&old_oid, old_hash, s->hash_algo);
oidread(&new_oid, new_hash, s->hash_algo);
if (!precomputed_entry_at(s, index_entry_hdat_offset(ep, keysz), &pe) ||
!replayable_hunks(&pe)) {
dropped++;
continue;
}
ALLOC_GROW(hunks, pe.num_hunks, hunks_alloc);
for (j = 0; j < pe.num_hunks; j++)
nth_precomputed_hunk(&pe, j, &hunks[j]);
if (!diff_hunks_writer_add(w, &old_oid, &new_oid,
(int)xdl_opts, hunks, pe.num_hunks))
dropped++;
}
if (dropped) {
warning(Q_("diff-hunks store %s: dropping %u invalid entry",
"diff-hunks store %s: dropping %u invalid entries",
dropped), fname, dropped);
*pruned = 1;
}
free(hunks);
free_store(s);
}
/*
* Writing is off by default. It is enabled per invocation by the
* GIT_DIFF_HUNKS_WRITE environment variable, or persistently by the
* diffHunks.write config, with the environment variable winning when
* set. Only a warming run (a diff or log the repository owner chooses
* to run with writing on) enables it, so ordinary reads never mutate
* the store.
*/
static int diff_hunks_write_enabled(struct repository *r)
{
const char *env = getenv("GIT_DIFF_HUNKS_WRITE");
int val;
if (env) {
/*
* This is a warming opt-in, so an unparseable value must not
* abort an ordinary read command: treat it as disabled.
*/
val = git_parse_maybe_bool(env);
return val < 0 ? 0 : val;
}
if (!repo_config_get_bool(r, "diffhunks.write", &val))
return val;
return 0;
}
struct diff_hunks_writer *diff_hunks_writer_maybe_new(struct repository *r)
{
struct diff_hunks_writer *w;
char *fname;
int pruned;
if (!diff_hunks_write_enabled(r))
return NULL;
/*
* Seed from the existing store so a flush merges with it rather
* than replacing it: a later warm adds newly computed pairs
* without discarding what earlier warms recorded.
*/
w = diff_hunks_writer_new(r);
fname = diff_hunks_store_path(r);
pruned = 0;
diff_hunks_writer_seed(w, fname, &pruned);
free(fname);
w->seed_nr = w->nr;
/*
* A pruning seed means the file on disk holds material the
* rewrite must not preserve; flush even if this warm computes
* nothing new, so the store on disk is repaired rather than
* left serving what the seed refused.
*/
w->force_flush = !!pruned;
return w;
}
static int writer_entry_cmp(const void *va, const void *vb, void *ctx)
{
const struct writer_entry *a = va, *b = vb;
unsigned int rawsz = *(const unsigned int *)ctx;
return cmp_store_index_key(a->old_oid.hash, a->new_oid.hash,
(uint32_t)a->xdl_opts,
b->old_oid.hash, b->new_oid.hash,
(uint32_t)b->xdl_opts,
rawsz);
}
struct write_ctx {
struct diff_hunks_writer *w;
unsigned int rawsz;
};
static int write_index_chunk(struct hashfile *f, void *data)
{
struct write_ctx *ctx = data;
size_t i;
for (i = 0; i < ctx->w->nr; i++) {
hashwrite(f, ctx->w->entries[i].old_oid.hash, ctx->rawsz);
hashwrite(f, ctx->w->entries[i].new_oid.hash, ctx->rawsz);
hashwrite_be32(f, ctx->w->entries[i].xdl_opts);
hashwrite_be32(f, ctx->w->entries[i].hdat_offset);
}
return 0;
}
static int write_data_chunk(struct hashfile *f, void *data)
{
struct write_ctx *ctx = data;
hashwrite(f, ctx->w->hdat.buf, ctx->w->hdat.len);
return 0;
}
/* Sort, dedup, and write the accumulated entries to the file at fname. */
static int diff_hunks_writer_flush(struct diff_hunks_writer *w, char *fname)
{
struct lock_file lk = LOCK_INIT;
struct hashfile *f;
struct chunkfile *cf;
unsigned int rawsz = w->r->hash_algo->rawsz;
struct write_ctx ctx = { w, rawsz };
size_t entry_size;
QSORT_S(w->entries, w->nr, writer_entry_cmp, &rawsz);
/*
* The same blob pair recurs across history (reverts, cherry-
* picks); identical keys carry identical hunks, so keep one of
* each. The index must stay duplicate-free for binary search.
*/
if (w->nr > 1) {
size_t kept = 1, i;
for (i = 1; i < w->nr; i++)
if (writer_entry_cmp(&w->entries[kept - 1],
&w->entries[i], &rawsz))
w->entries[kept++] = w->entries[i];
w->nr = kept;
}
if (safe_create_leading_directories(w->r, fname)) {
error(_("unable to create directory for %s"), fname);
return -1;
}
if (hold_lock_file_for_update(&lk, fname, 0) < 0) {
error_errno(_("unable to lock %s"), fname);
return -1;
}
adjust_shared_perm(w->r, get_lock_file_path(&lk));
f = hashfd(w->r->hash_algo, get_lock_file_fd(&lk),
get_lock_file_path(&lk));
entry_size = store_index_entry_size(w->r->hash_algo);
cf = init_chunkfile(f);
add_chunk(cf, DIFF_HUNKS_CHUNKID_INDEX, w->nr * entry_size,
write_index_chunk);
add_chunk(cf, DIFF_HUNKS_CHUNKID_DATA, w->hdat.len, write_data_chunk);
hashwrite_be32(f, DIFF_HUNKS_SIGNATURE);
hashwrite_u8(f, DIFF_HUNKS_VERSION);
hashwrite_u8(f, oid_version(w->r->hash_algo));
hashwrite_u8(f, get_num_chunks(cf));
hashwrite_u8(f, 0); /* reserved */
write_chunkfile(cf, &ctx);
free_chunkfile(cf);
/*
* fsync per the user's configuration (like commit-graph and the
* multi-pack-index), then commit atomically. Readers trust the
* committed file rather than re-checksumming it; diff_hunks_verify()
* checks the checksum separately.
*/
finalize_hashfile(f, NULL, FSYNC_COMPONENT_DIFF_HUNKS,
CSUM_HASH_IN_STREAM | CSUM_FSYNC);
/*
* This same process may hold the current store mmapped (a warm
* that also reads); the commit below renames over it, which must
* never land on a live mapping (Windows refuses it). Close the
* store and clear the load-attempted flag first, so the next
* read loads the committed file.
*/
if (w->r->objects) {
close_diff_hunks_store(w->r->objects);
w->r->objects->diff_hunks_store_attempted = 0;
}
if (commit_lock_file(&lk)) {
error_errno(_("unable to write %s"), fname);
return -1;
}
return 0;
}
static void diff_hunks_writer_free(struct diff_hunks_writer *w)
{
if (!w)
return;
hashmap_clear_and_free(&w->dedup, struct dedup_entry, ent);
free(w->entries);
strbuf_release(&w->hdat);
free(w);
}
void diff_hunks_writer_finish(struct diff_hunks_writer *w)
{
if (!w)
return;
/* Skip the flush when the warm recorded nothing beyond its seed. */
if (w->nr != w->seed_nr || w->force_flush) {
char *fname = diff_hunks_store_path(w->r);
diff_hunks_writer_flush(w, fname);
free(fname);
}
diff_hunks_writer_free(w);
}
|