1
+/*
2
+ * Precomputed diff hunks, keyed by diff input.
3
+ *
4
+ * A single store at .git/objects/info/diff-hunks maps an (old blob,
5
+ * new blob, xdl_opts) key to the hunk coordinates of diffing the pair.
6
+ * The key determines the diff result (only trim-stable pairs are
7
+ * recorded; see diff-hunks.h), so an entry is valid in any context it
8
+ * recurs in, independent of path. Reading is on by default
9
+ * (core.diffHunks); writing is off by default and enabled per run or
10
+ * by configuration (see diff_hunks_write_enabled), so an ordinary
11
+ * command populates the store only during a warming run the
12
+ * repository owner opts into.
13
+ *
14
+ * File layout:
15
+ * Header: "DHPF"(4) + version(1) + hash_version(1)
16
+ * + num_chunks(1) + reserved(1)
17
+ * Table of contents (chunk-format)
18
+ * DHIX chunk: sorted entries, each
19
+ * old_blob_oid, new_blob_oid, xdl_opts(4), hdat_offset(4)
20
+ * DHDT chunk: per entry, num_hunks(4) followed by that many 16-byte hunks
21
+ * Trailing hash checksum
22
+ */
23
+#include "git-compat-util.h"
24
+#include "chunk-format.h"
25
+#include "config.h"
26
+#include "csum-file.h"
27
+#include "diff-hunks.h"
28
+#include "diff-provider-internal.h"
29
+#include "gettext.h"
30
+#include "hash.h"
31
+#include "hashmap.h"
32
+#include "lockfile.h"
33
+#include "odb.h"
34
+#include "path.h"
35
+#include "repo-settings.h"
36
+#include "repository.h"
37
+#include "strbuf.h"
38
+#include "wrapper.h"
39
+
40
+#define DIFF_HUNKS_SIGNATURE 0x44485046 /* "DHPF" */
41
+/*
42
+ * Bump when the on-disk format changes, or when xdiff's emitted hunk
43
+ * coordinates change for a fixed (blobs, xdl_opts) key: an old store
44
+ * would otherwise serve stale hunks and change command output.
45
+ */
46
+#define DIFF_HUNKS_VERSION 1
47
+#define DIFF_HUNKS_HEADER_SIZE 8
48
+
49
+#define DIFF_HUNKS_CHUNKID_INDEX 0x44484958 /* "DHIX" */
50
+#define DIFF_HUNKS_CHUNKID_DATA 0x44484454 /* "DHDT" */
51
+
52
+/*
53
+ * Each hunk is 16 bytes on disk:
54
+ * old_start(4) old_count(4) new_start(4) new_count(4)
55
+ */
56
+#define DIFF_HUNKS_HUNK_SIZE (4 * sizeof(uint32_t))
57
+
58
+/*
59
+ * Result of a store lookup: num_hunks records encoded in the store's mmap,
60
+ * valid until the store is freed. Read them with nth_precomputed_hunk().
61
+ */
62
+struct precomputed_entry {
63
+ uint32_t num_hunks;
64
+ const unsigned char *hunk_data;
65
+};
66
+
67
+/* Decode a single hunk from the raw on-disk format. */
68
+static inline void decode_precomputed_hunk(const unsigned char *data,
69
+ struct precomputed_hunk *h)
70
+{
71
+ h->old_start = get_be32(data);
72
+ h->old_count = get_be32(data + 4);
73
+ h->new_start = get_be32(data + 8);
74
+ h->new_count = get_be32(data + 12);
75
+}
76
+
77
+/* Decode the nth hunk of a lookup result into *h. */
78
+static inline void nth_precomputed_hunk(const struct precomputed_entry *e,
79
+ uint32_t n, struct precomputed_hunk *h)
80
+{
81
+ decode_precomputed_hunk(e->hunk_data + (size_t)n * DIFF_HUNKS_HUNK_SIZE, h);
82
+}
83
+
84
+/* Byte length of the (old_oid, new_oid, xdl_opts) lookup key. */
85
+static size_t store_index_key_size(const struct git_hash_algo *algo)
86
+{
87
+ return 2 * algo->rawsz + sizeof(uint32_t);
88
+}
89
+
90
+/* Index entry: the lookup key followed by the 4-byte offset into DHDT. */
91
+static size_t store_index_entry_size(const struct git_hash_algo *algo)
92
+{
93
+ return store_index_key_size(algo) + sizeof(uint32_t);
94
+}
95
+
96
+/*
97
+ * The smallest a valid store file can be: the header, a table of contents
98
+ * with one entry per chunk plus a terminating entry, and the trailing
99
+ * checksum.
100
+ */
101
+static size_t store_min_size(const struct git_hash_algo *algo,
102
+ uint8_t num_chunks)
103
+{
104
+ size_t toc_size = (num_chunks + 1) * CHUNK_TOC_ENTRY_SIZE;
105
+
106
+ return DIFF_HUNKS_HEADER_SIZE + toc_size + algo->rawsz;
107
+}
108
+
109
+/*
110
+ * Decode an index entry's key into pointers to the two oids and the
111
+ * xdl_opts value (on-disk: old_oid, new_oid, then xdl_opts as a
112
+ * big-endian uint32).
113
+ */
114
+static void decode_store_index_key(const unsigned char *entry, unsigned int rawsz,
115
+ const unsigned char **old_hash,
116
+ const unsigned char **new_hash,
117
+ uint32_t *xdl_opts)
118
+{
119
+ *old_hash = entry;
120
+ *new_hash = entry + rawsz;
121
+ *xdl_opts = get_be32(entry + 2 * rawsz);
122
+}
123
+
124
+/* The DHDT offset stored in an index entry, in the field after its key. */
125
+static uint32_t index_entry_hdat_offset(const unsigned char *entry, size_t keysz)
126
+{
127
+ return get_be32(entry + keysz);
128
+}
129
+
130
+static char *diff_hunks_store_path(struct repository *r)
131
+{
132
+ return xstrfmt("%s/info/diff-hunks", repo_get_object_directory(r));
133
+}
134
+
135
+struct diff_hunks_store {
136
+ const unsigned char *data;
137
+ size_t data_len;
138
+ const struct git_hash_algo *hash_algo;
139
+ const unsigned char *index;
140
+ uint32_t num_entries;
141
+ const unsigned char *hdat;
142
+ size_t hdat_size;
143
+};
144
+
145
+static void free_store(struct diff_hunks_store *s)
146
+{
147
+ if (!s)
148
+ return;
149
+ if (s->data)
150
+ munmap((void *)s->data, s->data_len);
151
+ free(s);
152
+}
153
+
154
+/*
155
+ * Open, mmap, and parse the store at fname. Returns the parsed store
156
+ * or NULL on any error. The diff output is unaffected either way;
157
+ * corruption is reported by verify, not treated as fatal here.
158
+ */
159
+static struct diff_hunks_store *load_store_at(
160
+ const struct git_hash_algo *repo_algo, const char *fname)
161
+{
162
+ struct diff_hunks_store *s;
163
+ struct chunkfile *cf;
164
+ int fd;
165
+ struct stat st;
166
+ void *data;
167
+ const unsigned char *p;
168
+ uint8_t num_chunks;
169
+ size_t index_size, entry_size, data_len;
170
+
171
+ fd = git_open(fname);
172
+ if (fd < 0)
173
+ return NULL;
174
+ if (fstat(fd, &st) || st.st_size < DIFF_HUNKS_HEADER_SIZE) {
175
+ close(fd);
176
+ return NULL;
177
+ }
178
+ data_len = xsize_t(st.st_size);
179
+ data = xmmap(NULL, data_len, PROT_READ, MAP_PRIVATE, fd, 0);
180
+ close(fd);
181
+ p = data;
182
+
183
+ num_chunks = p[6];
184
+
185
+ /*
186
+ * Reject a file that is not a readable store: wrong signature,
187
+ * version, or object hash, or too small to hold the table of
188
+ * contents that read_table_of_contents() walks (it dereferences
189
+ * each entry before range-checking its offset).
190
+ */
191
+ if (get_be32(p) != DIFF_HUNKS_SIGNATURE ||
192
+ p[4] != DIFF_HUNKS_VERSION ||
193
+ p[5] != oid_version(repo_algo) ||
194
+ data_len < store_min_size(repo_algo, num_chunks)) {
195
+ munmap(data, data_len);
196
+ return NULL;
197
+ }
198
+
199
+ /*
200
+ * The trailing checksum is not verified here: the writer fsyncs
201
+ * and commits atomically, so a committed file is intact, and
202
+ * every record is bounds-checked at read (see precomputed_entry_at).
203
+ * The checksum is checked separately, by diff_hunks_verify().
204
+ */
205
+
206
+ CALLOC_ARRAY(s, 1);
207
+ s->data = data;
208
+ s->data_len = data_len;
209
+ s->hash_algo = repo_algo;
210
+
211
+ cf = init_chunkfile(NULL);
212
+ if (read_table_of_contents_quiet(cf, p, data_len,
213
+ DIFF_HUNKS_HEADER_SIZE, num_chunks, 1,
214
+ repo_algo) ||
215
+ pair_chunk(cf, DIFF_HUNKS_CHUNKID_INDEX, &s->index, &index_size) ||
216
+ pair_chunk(cf, DIFF_HUNKS_CHUNKID_DATA, &s->hdat, &s->hdat_size)) {
217
+ free_chunkfile(cf);
218
+ goto corrupt;
219
+ }
220
+ free_chunkfile(cf);
221
+
222
+ entry_size = store_index_entry_size(s->hash_algo);
223
+ if (index_size % entry_size)
224
+ goto corrupt;
225
+ s->num_entries = index_size / entry_size;
226
+ return s;
227
+
228
+corrupt:
229
+ free_store(s);
230
+ return NULL;
231
+}
232
+
233
+static struct diff_hunks_store *diff_hunks_store_load(struct repository *r)
234
+{
235
+ struct diff_hunks_store *s;
236
+ char *fname;
237
+
238
+ prepare_repo_settings(r);
239
+ if (!r->settings.core_diff_hunks)
240
+ return NULL;
241
+
242
+ fname = diff_hunks_store_path(r);
243
+ s = load_store_at(r->hash_algo, fname);
244
+ free(fname);
245
+ return s;
246
+}
247
+
248
+struct diff_hunks_store *repo_diff_hunks_store(struct repository *r)
249
+{
250
+ if (!r->objects)
251
+ return NULL;
252
+ if (r->objects->diff_hunks_store_attempted)
253
+ return r->objects->diff_hunks_store;
254
+ r->objects->diff_hunks_store_attempted = 1;
255
+ r->objects->diff_hunks_store = diff_hunks_store_load(r);
256
+ return r->objects->diff_hunks_store;
257
+}
258
+
259
+void close_diff_hunks_store(struct object_database *o)
260
+{
261
+ if (!o->diff_hunks_store)
262
+ return;
263
+ free_store(o->diff_hunks_store);
264
+ o->diff_hunks_store = NULL;
265
+}
266
+
267
+/*
268
+ * Fill *out with the hunk record at offset in the data chunk, and return
269
+ * 1 if the record is in bounds, 0 otherwise. The read path does not
270
+ * re-verify the checksum, and a valid checksum would not bound the count
271
+ * anyway, so a read must call this and use *out only when it returns
272
+ * non-zero.
273
+ *
274
+ * A record is a be32 hunk count followed by that many DIFF_HUNKS_HUNK_SIZE
275
+ * hunks. "remaining" tracks the bytes from offset to the end of the data
276
+ * chunk: it must hold the count, and after the count is consumed it must
277
+ * hold every hunk. The bounds are written as subtraction and division
278
+ * (never addition or multiplication) so a crafted offset or count cannot
279
+ * overflow them.
280
+ */
281
+static int precomputed_entry_at(const struct diff_hunks_store *s,
282
+ uint32_t offset, struct precomputed_entry *out)
283
+{
284
+ size_t remaining;
285
+ uint32_t num_hunks;
286
+
287
+ if (offset >= s->hdat_size)
288
+ return 0;
289
+ remaining = s->hdat_size - offset;
290
+ if (remaining < sizeof(uint32_t))
291
+ return 0;
292
+
293
+ num_hunks = get_be32(s->hdat + offset);
294
+ remaining -= sizeof(uint32_t);
295
+ if (num_hunks > remaining / DIFF_HUNKS_HUNK_SIZE)
296
+ return 0;
297
+
298
+ out->num_hunks = num_hunks;
299
+ out->hunk_data = s->hdat + offset + sizeof(uint32_t);
300
+ return 1;
301
+}
302
+
303
+struct lookup_key {
304
+ const struct object_id *old_oid;
305
+ const struct object_id *new_oid;
306
+ int xdl_opts;
307
+ unsigned int rawsz;
308
+};
309
+
310
+/*
311
+ * The store's total order over (old_oid, new_oid, xdl_opts), defined
312
+ * once so the write-side sort (writer_entry_cmp) and the read-side
313
+ * search (store_bsearch_cmp) order the keys identically.
314
+ */
315
+static int cmp_store_index_key(const unsigned char *old_a, const unsigned char *new_a,
316
+ uint32_t opts_a,
317
+ const unsigned char *old_b, const unsigned char *new_b,
318
+ uint32_t opts_b, unsigned int rawsz)
319
+{
320
+ int cmp = memcmp(old_a, old_b, rawsz);
321
+ if (!cmp)
322
+ cmp = memcmp(new_a, new_b, rawsz);
323
+ if (!cmp)
324
+ cmp = (opts_a > opts_b) - (opts_a < opts_b);
325
+ return cmp;
326
+}
327
+
328
+static int store_bsearch_cmp(const void *key, const void *entry_ptr)
329
+{
330
+ const struct lookup_key *k = key;
331
+ const unsigned char *old_hash, *new_hash;
332
+ uint32_t xdl_opts;
333
+
334
+ decode_store_index_key(entry_ptr, k->rawsz, &old_hash, &new_hash,
335
+ &xdl_opts);
336
+ return cmp_store_index_key(k->old_oid->hash, k->new_oid->hash,
337
+ (uint32_t)k->xdl_opts,
338
+ old_hash, new_hash, xdl_opts, k->rawsz);
339
+}
340
+
341
+static int store_get_one(struct diff_hunks_store *s, const struct lookup_key *key,
342
+ struct precomputed_entry *out)
343
+{
344
+ size_t entry_size = store_index_entry_size(s->hash_algo);
345
+ const unsigned char *found;
346
+
347
+ found = bsearch(key, s->index, s->num_entries, entry_size,
348
+ store_bsearch_cmp);
349
+ if (!found)
350
+ return 0;
351
+ return precomputed_entry_at(s,
352
+ index_entry_hdat_offset(found, store_index_key_size(s->hash_algo)),
353
+ out);
354
+}
355
+
356
+static int diff_hunks_store_get(struct diff_hunks_store *s,
357
+ const struct object_id *old_oid,
358
+ const struct object_id *new_oid,
359
+ int xdl_opts,
360
+ struct precomputed_entry *out)
361
+{
362
+ struct lookup_key key;
363
+
364
+ if (!s)
365
+ return 0;
366
+ /* The null OID names no blob and cannot key an entry. */
367
+ if (is_null_oid(old_oid) || is_null_oid(new_oid))
368
+ return 0;
369
+
370
+ key.old_oid = old_oid;
371
+ key.new_oid = new_oid;
372
+ key.xdl_opts = xdl_opts;
373
+ key.rawsz = s->hash_algo->rawsz;
374
+
375
+ return store_get_one(s, &key, out);
376
+}
377
+
378
+/*
379
+ * A recorded hunk sequence must satisfy the provider interface's
380
+ * shared check (diff_provider_check_hunk()) before it may be replayed:
381
+ * coordinates decode from be32 into long, which is 32-bit on some
382
+ * platforms, so a crafted value can decode negative or out of order.
383
+ * An entry that fails reads as a miss, so the caller recomputes.
384
+ */
385
+static int replayable_hunks(const struct precomputed_entry *e)
386
+{
387
+ struct diff_provider_hunks_check c = { 0 };
388
+ uint32_t i;
389
+
390
+ /*
391
+ * Replaying a record with no hunks would assert the blob pair
392
+ * equivalent, a claim the store must never make (the writer
393
+ * refuses to record one), so such a record is invalid.
394
+ */
395
+ if (!e->num_hunks)
396
+ return 0;
397
+ for (i = 0; i < e->num_hunks; i++) {
398
+ struct precomputed_hunk h;
399
+ nth_precomputed_hunk(e, i, &h);
400
+ if (diff_provider_check_hunk(&c, h.old_start, h.old_count,
401
+ h.new_start, h.new_count))
402
+ return 0;
403
+ }
404
+ return 1;
405
+}
406
+
407
+int diff_hunks_replay(struct diff_hunks_store *s,
408
+ const struct object_id *old_oid,
409
+ const struct object_id *new_oid,
410
+ int xdl_opts,
411
+ xdl_emit_hunk_consume_func_t hunk_func, void *cb_data)
412
+{
413
+ struct precomputed_entry e;
414
+ uint32_t i;
415
+
416
+ if (!diff_hunks_store_get(s, old_oid, new_oid, xdl_opts, &e) ||
417
+ !replayable_hunks(&e))
418
+ return 0;
419
+ for (i = 0; i < e.num_hunks; i++) {
420
+ struct precomputed_hunk h;
421
+ nth_precomputed_hunk(&e, i, &h);
422
+ hunk_func(h.old_start, h.old_count,
423
+ h.new_start, h.new_count, cb_data);
424
+ }
425
+ return 1;
426
+}
427
+
428
+/* Validate one store file. Returns 0 if valid or absent, -1 on any error. */
429
+static int verify_store_at(struct repository *r, const char *fname)
430
+{
431
+ struct diff_hunks_store *s;
432
+ size_t entry_size;
433
+ uint32_t i;
434
+ int fd;
435
+ int ret = 0;
436
+
437
+ /*
438
+ * A file that cannot be opened is not evidence of corruption:
439
+ * report the open error, and reserve the corruption diagnostics
440
+ * below for a file that was read and failed to parse.
441
+ */
442
+ fd = git_open(fname);
443
+ if (fd < 0) {
444
+ if (errno == ENOENT)
445
+ return 0; /* absent is valid */
446
+ return error_errno(_("unable to open diff-hunks store %s"),
447
+ fname);
448
+ }
449
+ close(fd);
450
+ s = load_store_at(r->hash_algo, fname);
451
+ if (!s)
452
+ return error(_("diff-hunks store failed to load (corrupt "
453
+ "header or hash mismatch): %s"), fname);
454
+ if (!hashfile_checksum_valid(r->hash_algo, s->data, s->data_len)) {
455
+ error(_("diff-hunks store has incorrect checksum and is "
456
+ "likely corrupt: %s"), fname);
457
+ free_store(s);
458
+ return -1;
459
+ }
460
+
461
+ entry_size = store_index_entry_size(s->hash_algo);
462
+ for (i = 0; i < s->num_entries; i++) {
463
+ const unsigned char *ep = s->index + st_mult(entry_size, i);
464
+ size_t keysz = store_index_key_size(s->hash_algo);
465
+ uint32_t offset = index_entry_hdat_offset(ep, keysz);
466
+ struct precomputed_entry pe;
467
+
468
+ /*
469
+ * Keyed by (old_oid, new_oid, xdl_opts), increasing. memcmp
470
+ * matches cmp_store_index_key's integer comparison of
471
+ * xdl_opts because it is non-negative, so its big-endian
472
+ * bytes order the same as its value.
473
+ */
474
+ if (i > 0 && memcmp(ep - entry_size, ep, keysz) >= 0) {
475
+ error(_("diff-hunks entry %u not in sorted order"), i);
476
+ ret = -1;
477
+ }
478
+ if (!precomputed_entry_at(s, offset, &pe)) {
479
+ error(_("diff-hunks entry %u has out-of-bounds hunk "
480
+ "data"), i);
481
+ ret = -1;
482
+ } else if (!replayable_hunks(&pe)) {
483
+ error(_("diff-hunks entry %u holds an invalid hunk "
484
+ "sequence"), i);
485
+ ret = -1;
486
+ }
487
+ }
488
+
489
+ free_store(s);
490
+ return ret;
491
+}
492
+
493
+int diff_hunks_verify(struct repository *r)
494
+{
495
+ char *fname = diff_hunks_store_path(r);
496
+ int ret = 0;
497
+
498
+ if (verify_store_at(r, fname))
499
+ ret = -1;
500
+ free(fname);
501
+ return ret;
502
+}
503
+
504
+int diff_hunks_clear(struct repository *r)
505
+{
506
+ char *fname = diff_hunks_store_path(r);
507
+ int ret = 0;
508
+
509
+ if (unlink(fname) && errno != ENOENT)
510
+ ret = error_errno(_("unable to remove %s"), fname);
511
+ free(fname);
512
+ return ret;
513
+}
514
+
515
+struct writer_entry {
516
+ struct object_id old_oid;
517
+ struct object_id new_oid;
518
+ int xdl_opts;
519
+ uint32_t hdat_offset;
520
+};
521
+
522
+struct diff_hunks_writer {
523
+ struct repository *r;
524
+ struct writer_entry *entries;
525
+ size_t nr, alloc;
526
+ size_t seed_nr; /* nr after seeding; finish skips a no-op flush */
527
+ unsigned force_flush : 1; /* seed pruned: rewrite even a no-op warm */
528
+ struct strbuf hdat;
529
+ struct hashmap dedup; /* hunk block content -> offset in hdat */
530
+};
531
+
532
+/* A record of one distinct hunk block already present in hdat. */
533
+struct dedup_entry {
534
+ struct hashmap_entry ent;
535
+ uint32_t offset;
536
+ uint32_t len;
537
+};
538
+
539
+static int dedup_cmp(const void *cmp_data,
540
+ const struct hashmap_entry *a,
541
+ const struct hashmap_entry *b,
542
+ const void *keydata UNUSED)
543
+{
544
+ const struct diff_hunks_writer *writer = cmp_data;
545
+ const struct dedup_entry *ea = container_of(a, const struct dedup_entry, ent);
546
+ const struct dedup_entry *eb = container_of(b, const struct dedup_entry, ent);
547
+
548
+ if (ea->len != eb->len)
549
+ return 1;
550
+ return memcmp(writer->hdat.buf + ea->offset,
551
+ writer->hdat.buf + eb->offset, ea->len);
552
+}
553
+
554
+static struct diff_hunks_writer *diff_hunks_writer_new(struct repository *r)
555
+{
556
+ struct diff_hunks_writer *w;
557
+
558
+ CALLOC_ARRAY(w, 1);
559
+ w->r = r;
560
+ strbuf_init(&w->hdat, 0);
561
+ hashmap_init(&w->dedup, dedup_cmp, w, 0);
562
+ return w;
563
+}
564
+
565
+static void strbuf_put_be32(struct strbuf *sb, uint32_t val)
566
+{
567
+ unsigned char buf[4];
568
+ put_be32(buf, val);
569
+ strbuf_add(sb, buf, 4);
570
+}
571
+
572
+/*
573
+ * The hunk block just appended at `start` is deduplicated: if an
574
+ * identical block is already in hdat, this copy is dropped and the
575
+ * earlier offset returned; otherwise it is kept and remembered.
576
+ * Distinct keys that diff to the same hunks then share one block.
577
+ */
578
+static uint32_t intern_block(struct diff_hunks_writer *w, size_t start)
579
+{
580
+ size_t len = w->hdat.len - start;
581
+ struct dedup_entry key, *found, *added;
582
+
583
+ hashmap_entry_init(&key.ent, memhash(w->hdat.buf + start, len));
584
+ key.offset = (uint32_t)start;
585
+ key.len = (uint32_t)len;
586
+
587
+ found = hashmap_get_entry(&w->dedup, &key, ent, NULL);
588
+ if (found) {
589
+ strbuf_setlen(&w->hdat, start);
590
+ return found->offset;
591
+ }
592
+
593
+ added = xmalloc(sizeof(*added));
594
+ hashmap_entry_init(&added->ent, key.ent.hash);
595
+ added->offset = key.offset;
596
+ added->len = key.len;
597
+ hashmap_add(&w->dedup, &added->ent);
598
+ return key.offset;
599
+}
600
+
601
+int diff_hunks_writer_add(struct diff_hunks_writer *w,
602
+ const struct object_id *old_oid,
603
+ const struct object_id *new_oid,
604
+ int xdl_opts,
605
+ const struct precomputed_hunk *hunks,
606
+ size_t nr_hunks)
607
+{
608
+ struct writer_entry *e;
609
+ size_t i, block_start;
610
+
611
+ if (!w)
612
+ return 0;
613
+ /*
614
+ * The block appended for this entry is sizeof(uint32_t) +
615
+ * nr_hunks * DIFF_HUNKS_HUNK_SIZE bytes. Bound nr_hunks so that
616
+ * length fits the uint32_t the dedup index records (and so the
617
+ * count itself fits the uint32_t written to the store).
618
+ */
619
+ if (!nr_hunks ||
620
+ nr_hunks > (UINT32_MAX - sizeof(uint32_t)) / DIFF_HUNKS_HUNK_SIZE ||
621
+ is_null_oid(old_oid) || is_null_oid(new_oid))
622
+ return 0;
623
+ if (w->hdat.len > UINT32_MAX)
624
+ return 0;
625
+ /*
626
+ * Coordinates are stored as 32-bit values; a result that cannot
627
+ * round-trip is dropped rather than silently truncated.
628
+ */
629
+ for (i = 0; i < nr_hunks; i++)
630
+ if ((uintmax_t)hunks[i].old_start > (uintmax_t)INT32_MAX ||
631
+ (uintmax_t)hunks[i].old_count > (uintmax_t)INT32_MAX ||
632
+ (uintmax_t)hunks[i].new_start > (uintmax_t)INT32_MAX ||
633
+ (uintmax_t)hunks[i].new_count > (uintmax_t)INT32_MAX)
634
+ return 0;
635
+
636
+ ALLOC_GROW(w->entries, w->nr + 1, w->alloc);
637
+ e = &w->entries[w->nr++];
638
+ oidcpy(&e->old_oid, old_oid);
639
+ oidcpy(&e->new_oid, new_oid);
640
+ e->xdl_opts = xdl_opts;
641
+
642
+ block_start = w->hdat.len;
643
+ strbuf_put_be32(&w->hdat, (uint32_t)nr_hunks);
644
+ for (i = 0; i < nr_hunks; i++) {
645
+ strbuf_put_be32(&w->hdat, hunks[i].old_start);
646
+ strbuf_put_be32(&w->hdat, hunks[i].old_count);
647
+ strbuf_put_be32(&w->hdat, hunks[i].new_start);
648
+ strbuf_put_be32(&w->hdat, hunks[i].new_count);
649
+ }
650
+ e->hdat_offset = intern_block(w, block_start);
651
+ return 1;
652
+}
653
+
654
+/*
655
+ * Seed the writer with fname's entries so a rewrite preserves them,
656
+ * setting *pruned when the rewrite will not carry the whole file
657
+ * forward: the file failed its checksum and was discarded outright, or
658
+ * individual entries were dropped because they failed the replayable
659
+ * check or the writer refused them (a key naming no blob). A
660
+ * rewrite re-checksums, so corruption must not be carried forward:
661
+ * that would launder it into a checksum-valid file that verify can no
662
+ * longer catch. This path already reads the whole file, so verify the
663
+ * checksum here (the reader keeps trusting committed files, without
664
+ * re-checksumming); an invalid
665
+ * entry reads as a miss anyway, so dropping it heals the store rather
666
+ * than losing anything a reader could use.
667
+ */
668
+static void diff_hunks_writer_seed(struct diff_hunks_writer *w,
669
+ const char *fname, int *pruned)
670
+{
671
+ struct diff_hunks_store *s = load_store_at(w->r->hash_algo, fname);
672
+ unsigned int rawsz;
673
+ size_t entry_size, keysz;
674
+ struct precomputed_hunk *hunks = NULL;
675
+ size_t hunks_alloc = 0;
676
+ uint32_t i, dropped = 0;
677
+
678
+ if (!s)
679
+ return;
680
+ if (!hashfile_checksum_valid(w->r->hash_algo, s->data, s->data_len)) {
681
+ warning(_("diff-hunks store %s failed its checksum; "
682
+ "discarding it"), fname);
683
+ free_store(s);
684
+ *pruned = 1;
685
+ return;
686
+ }
687
+ rawsz = s->hash_algo->rawsz;
688
+ entry_size = store_index_entry_size(s->hash_algo);
689
+ keysz = store_index_key_size(s->hash_algo);
690
+
691
+ for (i = 0; i < s->num_entries; i++) {
692
+ const unsigned char *ep = s->index + st_mult(entry_size, i);
693
+ const unsigned char *old_hash, *new_hash;
694
+ struct object_id old_oid, new_oid;
695
+ uint32_t xdl_opts, j;
696
+ struct precomputed_entry pe;
697
+
698
+ decode_store_index_key(ep, rawsz, &old_hash, &new_hash,
699
+ &xdl_opts);
700
+ oidread(&old_oid, old_hash, s->hash_algo);
701
+ oidread(&new_oid, new_hash, s->hash_algo);
702
+ if (!precomputed_entry_at(s, index_entry_hdat_offset(ep, keysz), &pe) ||
703
+ !replayable_hunks(&pe)) {
704
+ dropped++;
705
+ continue;
706
+ }
707
+ ALLOC_GROW(hunks, pe.num_hunks, hunks_alloc);
708
+ for (j = 0; j < pe.num_hunks; j++)
709
+ nth_precomputed_hunk(&pe, j, &hunks[j]);
710
+ if (!diff_hunks_writer_add(w, &old_oid, &new_oid,
711
+ (int)xdl_opts, hunks, pe.num_hunks))
712
+ dropped++;
713
+ }
714
+ if (dropped) {
715
+ warning(Q_("diff-hunks store %s: dropping %u invalid entry",
716
+ "diff-hunks store %s: dropping %u invalid entries",
717
+ dropped), fname, dropped);
718
+ *pruned = 1;
719
+ }
720
+ free(hunks);
721
+ free_store(s);
722
+}
723
+
724
+/*
725
+ * Writing is off by default. It is enabled per invocation by the
726
+ * GIT_DIFF_HUNKS_WRITE environment variable, or persistently by the
727
+ * diffHunks.write config, with the environment variable winning when
728
+ * set. Only a warming run (a diff or log the repository owner chooses
729
+ * to run with writing on) enables it, so ordinary reads never mutate
730
+ * the store.
731
+ */
732
+static int diff_hunks_write_enabled(struct repository *r)
733
+{
734
+ const char *env = getenv("GIT_DIFF_HUNKS_WRITE");
735
+ int val;
736
+
737
+ if (env) {
738
+ /*
739
+ * This is a warming opt-in, so an unparseable value must not
740
+ * abort an ordinary read command: treat it as disabled.
741
+ */
742
+ val = git_parse_maybe_bool(env);
743
+ return val < 0 ? 0 : val;
744
+ }
745
+ if (!repo_config_get_bool(r, "diffhunks.write", &val))
746
+ return val;
747
+ return 0;
748
+}
749
+
750
+struct diff_hunks_writer *diff_hunks_writer_maybe_new(struct repository *r)
751
+{
752
+ struct diff_hunks_writer *w;
753
+ char *fname;
754
+ int pruned;
755
+
756
+ if (!diff_hunks_write_enabled(r))
757
+ return NULL;
758
+ /*
759
+ * Seed from the existing store so a flush merges with it rather
760
+ * than replacing it: a later warm adds newly computed pairs
761
+ * without discarding what earlier warms recorded.
762
+ */
763
+ w = diff_hunks_writer_new(r);
764
+ fname = diff_hunks_store_path(r);
765
+ pruned = 0;
766
+ diff_hunks_writer_seed(w, fname, &pruned);
767
+ free(fname);
768
+ w->seed_nr = w->nr;
769
+ /*
770
+ * A pruning seed means the file on disk holds material the
771
+ * rewrite must not preserve; flush even if this warm computes
772
+ * nothing new, so the store on disk is repaired rather than
773
+ * left serving what the seed refused.
774
+ */
775
+ w->force_flush = !!pruned;
776
+ return w;
777
+}
778
+
779
+static int writer_entry_cmp(const void *va, const void *vb, void *ctx)
780
+{
781
+ const struct writer_entry *a = va, *b = vb;
782
+ unsigned int rawsz = *(const unsigned int *)ctx;
783
+ return cmp_store_index_key(a->old_oid.hash, a->new_oid.hash,
784
+ (uint32_t)a->xdl_opts,
785
+ b->old_oid.hash, b->new_oid.hash,
786
+ (uint32_t)b->xdl_opts,
787
+ rawsz);
788
+}
789
+
790
+struct write_ctx {
791
+ struct diff_hunks_writer *w;
792
+ unsigned int rawsz;
793
+};
794
+
795
+static int write_index_chunk(struct hashfile *f, void *data)
796
+{
797
+ struct write_ctx *ctx = data;
798
+ size_t i;
799
+
800
+ for (i = 0; i < ctx->w->nr; i++) {
801
+ hashwrite(f, ctx->w->entries[i].old_oid.hash, ctx->rawsz);
802
+ hashwrite(f, ctx->w->entries[i].new_oid.hash, ctx->rawsz);
803
+ hashwrite_be32(f, ctx->w->entries[i].xdl_opts);
804
+ hashwrite_be32(f, ctx->w->entries[i].hdat_offset);
805
+ }
806
+ return 0;
807
+}
808
+
809
+static int write_data_chunk(struct hashfile *f, void *data)
810
+{
811
+ struct write_ctx *ctx = data;
812
+ hashwrite(f, ctx->w->hdat.buf, ctx->w->hdat.len);
813
+ return 0;
814
+}
815
+
816
+/* Sort, dedup, and write the accumulated entries to the file at fname. */
817
+static int diff_hunks_writer_flush(struct diff_hunks_writer *w, char *fname)
818
+{
819
+ struct lock_file lk = LOCK_INIT;
820
+ struct hashfile *f;
821
+ struct chunkfile *cf;
822
+ unsigned int rawsz = w->r->hash_algo->rawsz;
823
+ struct write_ctx ctx = { w, rawsz };
824
+ size_t entry_size;
825
+
826
+ QSORT_S(w->entries, w->nr, writer_entry_cmp, &rawsz);
827
+
828
+ /*
829
+ * The same blob pair recurs across history (reverts, cherry-
830
+ * picks); identical keys carry identical hunks, so keep one of
831
+ * each. The index must stay duplicate-free for binary search.
832
+ */
833
+ if (w->nr > 1) {
834
+ size_t kept = 1, i;
835
+ for (i = 1; i < w->nr; i++)
836
+ if (writer_entry_cmp(&w->entries[kept - 1],
837
+ &w->entries[i], &rawsz))
838
+ w->entries[kept++] = w->entries[i];
839
+ w->nr = kept;
840
+ }
841
+
842
+ if (safe_create_leading_directories(w->r, fname)) {
843
+ error(_("unable to create directory for %s"), fname);
844
+ return -1;
845
+ }
846
+ if (hold_lock_file_for_update(&lk, fname, 0) < 0) {
847
+ error_errno(_("unable to lock %s"), fname);
848
+ return -1;
849
+ }
850
+ adjust_shared_perm(w->r, get_lock_file_path(&lk));
851
+ f = hashfd(w->r->hash_algo, get_lock_file_fd(&lk),
852
+ get_lock_file_path(&lk));
853
+
854
+ entry_size = store_index_entry_size(w->r->hash_algo);
855
+ cf = init_chunkfile(f);
856
+ add_chunk(cf, DIFF_HUNKS_CHUNKID_INDEX, w->nr * entry_size,
857
+ write_index_chunk);
858
+ add_chunk(cf, DIFF_HUNKS_CHUNKID_DATA, w->hdat.len, write_data_chunk);
859
+
860
+ hashwrite_be32(f, DIFF_HUNKS_SIGNATURE);
861
+ hashwrite_u8(f, DIFF_HUNKS_VERSION);
862
+ hashwrite_u8(f, oid_version(w->r->hash_algo));
863
+ hashwrite_u8(f, get_num_chunks(cf));
864
+ hashwrite_u8(f, 0); /* reserved */
865
+
866
+ write_chunkfile(cf, &ctx);
867
+ free_chunkfile(cf);
868
+
869
+ /*
870
+ * fsync per the user's configuration (like commit-graph and the
871
+ * multi-pack-index), then commit atomically. Readers trust the
872
+ * committed file rather than re-checksumming it; diff_hunks_verify()
873
+ * checks the checksum separately.
874
+ */
875
+ finalize_hashfile(f, NULL, FSYNC_COMPONENT_DIFF_HUNKS,
876
+ CSUM_HASH_IN_STREAM | CSUM_FSYNC);
877
+ /*
878
+ * This same process may hold the current store mmapped (a warm
879
+ * that also reads); the commit below renames over it, which must
880
+ * never land on a live mapping (Windows refuses it). Close the
881
+ * store and clear the load-attempted flag first, so the next
882
+ * read loads the committed file.
883
+ */
884
+ if (w->r->objects) {
885
+ close_diff_hunks_store(w->r->objects);
886
+ w->r->objects->diff_hunks_store_attempted = 0;
887
+ }
888
+ if (commit_lock_file(&lk)) {
889
+ error_errno(_("unable to write %s"), fname);
890
+ return -1;
891
+ }
892
+ return 0;
893
+}
894
+
895
+static void diff_hunks_writer_free(struct diff_hunks_writer *w)
896
+{
897
+ if (!w)
898
+ return;
899
+ hashmap_clear_and_free(&w->dedup, struct dedup_entry, ent);
900
+ free(w->entries);
901
+ strbuf_release(&w->hdat);
902
+ free(w);
903
+}
904
+
905
+void diff_hunks_writer_finish(struct diff_hunks_writer *w)
906
+{
907
+ if (!w)
908
+ return;
909
+ /* Skip the flush when the warm recorded nothing beyond its seed. */
910
+ if (w->nr != w->seed_nr || w->force_flush) {
911
+ char *fname = diff_hunks_store_path(w->r);
912
+ diff_hunks_writer_flush(w, fname);
913
+ free(fname);
914
+ }
915
+ diff_hunks_writer_free(w);
916
+}