1
+/*
2
+ * The process provider of the hunk provider interface: consult a
3
+ * long-running external process via the pkt-line protocol for the
4
+ * hunks of a blob pair. The process answers from the pair's object
5
+ * names alone: it can serve a persistent cache keyed on the pair, or
6
+ * fetch the blobs from the repository itself (e.g. via "git cat-file
7
+ * --batch") and compute its own notion of which lines changed. The
8
+ * provider sits at the head of its repository's chain and gates
9
+ * itself per request; its state is the repository's pool of running
10
+ * processes, one per configured command, stopped when the provider
11
+ * is released.
12
+ *
13
+ * Protocol: pkt-line over stdin/stdout, following the pattern of
14
+ * the long-running filter process protocol (see convert.c).
15
+ *
16
+ * Handshake:
17
+ * git> git-diff-client / version=1 / flush
18
+ * process< git-diff-server / version=1 / flush
19
+ * git> capability=hunks-by-oid / flush
20
+ * process< capability=hunks-by-oid / flush
21
+ *
22
+ * Per-pair, when both sides are stored blobs:
23
+ * git> command=hunks-by-oid / pathname=<path>
24
+ * git> old-oid=<hex> / new-oid=<hex> / flush
25
+ * process< hunk <old_start> <old_count> <new_start> <new_count>
26
+ * process< ... / flush
27
+ * process< status=success / flush
28
+ *
29
+ * No content is sent. Because Git holds no content for the exchange,
30
+ * the answer is used as the process sent it: the hunks are not re-run
31
+ * through xdiff's compaction, and a status=success response with zero
32
+ * hunks asserts that the blobs are equivalent, including their
33
+ * trailing newlines. A process that cannot answer from the object names
34
+ * (or cannot rule out a trailing-newline-only difference) responds
35
+ * status=need-content; the pair then gets the builtin answer, served
36
+ * from the diff-hunks store or computed. A later
37
+ * protocol extension can define a content-carrying request for such
38
+ * processes and for sides that are not stored blobs.
39
+ */
40
+
41
+#include "git-compat-util.h"
42
+#include "diff.h"
43
+#include "diff-provider-internal.h"
44
+#include "gettext.h"
45
+#include "hex.h"
46
+#include "odb.h"
47
+#include "repository.h"
48
+#include "sigchain.h"
49
+#include "userdiff.h"
50
+#include "sub-process.h"
51
+#include "pkt-line.h"
52
+#include "strbuf.h"
53
+
54
+#define CAP_OID_HUNKS (1u << 0)
55
+
56
+/*
57
+ * The provider's state: the repository's diff processes, keyed by
58
+ * their command string, so drivers that configure the same command
59
+ * share one process. An entry whose process failed stays in the
60
+ * pool with the failed bit set, so the command is not retried while
61
+ * the entry lives; the pool and its entries last until the provider
62
+ * is released.
63
+ */
64
+struct diff_process_state {
65
+ struct hashmap subprocesses;
66
+};
67
+
68
+struct diff_subprocess {
69
+ struct subprocess_entry subprocess;
70
+ /*
71
+ * Owns the string subprocess.cmd and the hashmap key borrow: the
72
+ * entry outlives the userdiff config a re-read may replace.
73
+ */
74
+ char *cmd;
75
+ unsigned int supported_capabilities;
76
+ unsigned failed : 1;
77
+};
78
+
79
+static int start_diff_process_fn(struct subprocess_entry *subprocess)
80
+{
81
+ static int versions[] = { 1, 0 };
82
+ static struct subprocess_capability capabilities[] = {
83
+ { "hunks-by-oid", CAP_OID_HUNKS },
84
+ { NULL, 0 }
85
+ };
86
+ struct diff_subprocess *entry =
87
+ container_of(subprocess, struct diff_subprocess, subprocess);
88
+
89
+ return subprocess_handshake(subprocess, "git-diff",
90
+ versions, NULL,
91
+ capabilities,
92
+ &entry->supported_capabilities);
93
+}
94
+
95
+/*
96
+ * The pool entry for a command, or NULL when its process fails to
97
+ * start here: the failure leaves a failed entry in the pool, so only
98
+ * the request that observed it maps it to an error and later
99
+ * requests pass the provider by.
100
+ */
101
+static struct diff_subprocess *get_or_launch_process(
102
+ struct diff_process_state *state,
103
+ struct userdiff_driver *drv)
104
+{
105
+ struct subprocess_entry *running;
106
+ struct diff_subprocess *entry;
107
+
108
+ running = subprocess_find_entry(&state->subprocesses, drv->process);
109
+ if (running) {
110
+ entry = container_of(running, struct diff_subprocess,
111
+ subprocess);
112
+ return entry->failed ? NULL : entry;
113
+ }
114
+
115
+ entry = xcalloc(1, sizeof(*entry));
116
+ entry->cmd = xstrdup(drv->process);
117
+ if (subprocess_start_command(&entry->subprocess, entry->cmd,
118
+ start_diff_process_fn))
119
+ entry->failed = 1;
120
+ hashmap_entry_init(&entry->subprocess.ent, strhash(entry->cmd));
121
+ hashmap_add(&state->subprocesses, &entry->subprocess.ent);
122
+ if (entry->failed) {
123
+ warning(_("diff process '%s' failed to start;"
124
+ " using the builtin diff"), drv->process);
125
+ return NULL;
126
+ }
127
+ return entry;
128
+}
129
+
130
+/*
131
+ * A hunk in the diff process's presentation coordinates: the line
132
+ * numbering it reports over the protocol. Kept distinct from struct
133
+ * xdl_hunk (xdiff's coordinates) so that only translated hunks ever
134
+ * reach a consumer; diff_process_hunk_to_xdl() is the single
135
+ * crossing point.
136
+ */
137
+struct diff_process_hunk {
138
+ long old_start, old_count;
139
+ long new_start, new_count;
140
+};
141
+
142
+/*
143
+ * Parse one non-negative decimal field of a hunk line into *out and
144
+ * advance *line past it. Fields must be plain decimal with no leading
145
+ * whitespace or sign (isdigit() takes an unsigned char to stay defined
146
+ * for high-bit bytes). The first three fields are followed by a single
147
+ * space; the last (is_last) is followed by end-of-string or a space.
148
+ * Trailing space-separated tokens after the last field are allowed and
149
+ * ignored, so a future protocol version can append fields (e.g. a
150
+ * "moved" marker) without an older Git rejecting the line, mirroring
151
+ * the request-side rule that processes ignore unknown keys.
152
+ *
153
+ * A value that overflows strtol() is not a parse failure: the line is
154
+ * well-formed, so the stream stays in protocol sync. It is reported
155
+ * through *out_of_range, and the caller skips the pair the same way
156
+ * it skips any other out-of-range coordinate.
157
+ */
158
+static int parse_hunk_field(const char **line, long *out, int is_last,
159
+ int *out_of_range)
160
+{
161
+ const char *p = *line;
162
+ char *end;
163
+
164
+ if (!isdigit((unsigned char)*p))
165
+ return -1;
166
+ errno = 0;
167
+ *out = strtol(p, &end, 10);
168
+ if (end == p)
169
+ return -1;
170
+ if (errno == ERANGE)
171
+ *out_of_range = 1;
172
+ else if (errno)
173
+ return -1;
174
+ if (is_last) {
175
+ if (*end != '\0' && *end != ' ')
176
+ return -1;
177
+ } else {
178
+ if (*end != ' ')
179
+ return -1;
180
+ end++;
181
+ }
182
+ *line = end;
183
+ return 0;
184
+}
185
+
186
+static int parse_hunk_line(const char *line,
187
+ struct diff_process_hunk *presented,
188
+ int *out_of_range)
189
+{
190
+ *out_of_range = 0;
191
+ /* Format: "hunk <old_start> <old_count> <new_start> <new_count>" */
192
+ if (!skip_prefix(line, "hunk ", &line))
193
+ return -1;
194
+ if (parse_hunk_field(&line, &presented->old_start, 0, out_of_range) ||
195
+ parse_hunk_field(&line, &presented->old_count, 0, out_of_range) ||
196
+ parse_hunk_field(&line, &presented->new_start, 0, out_of_range) ||
197
+ parse_hunk_field(&line, &presented->new_count, 1, out_of_range))
198
+ return -1;
199
+ return 0;
200
+}
201
+
202
+/*
203
+ * Translate a hunk from the diff process's presentation coordinates
204
+ * into xdiff's.
205
+ *
206
+ * Protocol starts are already 1-based positions (the line a change
207
+ * sits before), the same numbering xdiff uses, so the only adjustment
208
+ * is for an empty file side: "git diff" addresses it with a start of 0
209
+ * and a count of 0 (e.g. "0 0 1 5" adds five lines to an empty old
210
+ * side), and since xdiff uses start-1 as an array index that 0 becomes
211
+ * 1 here. This is NOT the full inverse of xdl_emit_hunk_hdr()
212
+ * (xdiff/xutils.c): that emitter shifts a count-0 range to start-1 for
213
+ * the displayed "@@" header, but the protocol keeps the unshifted
214
+ * 1-based position for a mid-file insert or delete. This is the single
215
+ * point where presentation coordinates become xdiff coordinates, so
216
+ * any consumer of these coordinates may assume 1-based starts.
217
+ *
218
+ * Returns -1 for a start of 0 paired with a nonzero count, which names
219
+ * no line in either coordinate system. (parse_hunk_line() already
220
+ * guarantees non-negative starts and counts.)
221
+ */
222
+static int diff_process_hunk_to_xdl(const struct diff_process_hunk *presented,
223
+ struct xdl_hunk *xdl)
224
+{
225
+ long old_start = presented->old_start;
226
+ long new_start = presented->new_start;
227
+
228
+ if ((!old_start && presented->old_count) ||
229
+ (!new_start && presented->new_count))
230
+ return -1;
231
+ if (!old_start)
232
+ old_start = 1;
233
+ if (!new_start)
234
+ new_start = 1;
235
+
236
+ xdl->old_start = old_start;
237
+ xdl->old_count = presented->old_count;
238
+ xdl->new_start = new_start;
239
+ xdl->new_count = presented->new_count;
240
+ return 0;
241
+}
242
+
243
+/*
244
+ * Validate the process's hunks (already in xdiff coordinates) before they
245
+ * bypass the diff algorithm. The content-independent rules (in-order,
246
+ * non-overlapping, lockstep-aligned, int32-bounded coordinates) are the
247
+ * provider interface's shared rule, diff_provider_check_hunk(); this
248
+ * function adds the two checks that need the blobs' line counts (a hunk
249
+ * past the end of a file, the run after the last hunk) and the
250
+ * per-rule diagnostics naming the process. On a bad response we warn
251
+ * and the caller falls back to the builtin diff. Returns 0 if valid,
252
+ * -1 (after warning) otherwise.
253
+ *
254
+ * old_lines/new_lines bound the line count of each side, or are
255
+ * negative when no bound is known. An oid-only answer arrives without
256
+ * content, so its caller passes upper bounds derived from the blobs'
257
+ * byte sizes, which caps coordinate magnitude but cannot support the
258
+ * run-after-the-last-hunk check: that one compares exact line counts,
259
+ * so it runs only when lines_exact is set, which no caller does today.
260
+ * It is kept for a content-carrying request, whose loaded buffers
261
+ * would provide exact counts.
262
+ */
263
+static int validate_external_hunks(const struct xdl_hunk *hunks, size_t nr,
264
+ long old_lines, long new_lines,
265
+ int lines_exact,
266
+ const char *process, const char *path)
267
+{
268
+ struct diff_provider_hunks_check c = { 0 };
269
+ size_t i;
270
+
271
+ for (i = 0; i < nr; i++) {
272
+ const struct xdl_hunk *h = &hunks[i];
273
+
274
+ if (old_lines >= 0 &&
275
+ (h->old_count > old_lines - h->old_start + 1 ||
276
+ h->new_count > new_lines - h->new_start + 1)) {
277
+ warning(_("diff process '%s' returned a hunk past the "
278
+ "end of '%s'; using the builtin diff"),
279
+ process, path);
280
+ return -1;
281
+ }
282
+ switch (diff_provider_check_hunk(&c, h->old_start,
283
+ h->old_count, h->new_start,
284
+ h->new_count)) {
285
+ case DIFF_PROVIDER_HUNKS_OK:
286
+ break;
287
+ case DIFF_PROVIDER_HUNKS_RANGE:
288
+ warning(_("diff process '%s' returned out-of-range "
289
+ "coordinates for '%s'; using the builtin diff"),
290
+ process, path);
291
+ return -1;
292
+ case DIFF_PROVIDER_HUNKS_OVERLAP:
293
+ warning(_("diff process '%s' returned overlapping hunks "
294
+ "for '%s'; using the builtin diff"),
295
+ process, path);
296
+ return -1;
297
+ case DIFF_PROVIDER_HUNKS_MISALIGNED:
298
+ warning(_("diff process '%s' returned hunks that leave "
299
+ "'%s' misaligned; using the builtin diff"),
300
+ process, path);
301
+ return -1;
302
+ }
303
+ }
304
+ if (lines_exact &&
305
+ old_lines - c.prev_old_end != new_lines - c.prev_new_end) {
306
+ warning(_("diff process '%s' returned hunks that leave '%s' "
307
+ "misaligned; using the builtin diff"),
308
+ process, path);
309
+ return -1;
310
+ }
311
+ return 0;
312
+}
313
+
314
+/*
315
+ * The most lines a blob can hold, from its size alone: every line,
316
+ * even an empty one, costs at least one byte, so a blob of N bytes
317
+ * holds at most N lines. Returns -1 when the size is unavailable,
318
+ * leaving the response bounded only by the shared int32 rule. A size
319
+ * beyond INT32_MAX clamps to it, which loses nothing: a coordinate
320
+ * that large fails the shared rule anyway. In a partial clone the
321
+ * size lookup must not fetch the blob from the promisor remote:
322
+ * validating an answer that exists to avoid loading content must not
323
+ * itself download that content, so a missing blob reads as size
324
+ * unavailable instead.
325
+ */
326
+static long blob_line_cap(struct repository *r, const struct object_id *oid)
327
+{
328
+ unsigned long size;
329
+ struct object_info oi = OBJECT_INFO_INIT;
330
+
331
+ oi.sizep = &size;
332
+ if (odb_read_object_info_extended(r->objects, oid, &oi,
333
+ OBJECT_INFO_SKIP_FETCH_OBJECT) < 0)
334
+ return -1;
335
+ if (size > INT32_MAX)
336
+ return INT32_MAX;
337
+ return (long)size;
338
+}
339
+
340
+/*
341
+ * The driver whose process a consultation for path would ask, or NULL
342
+ * when none applies (no driver, process not allowed, or xpp carries
343
+ * options the process is never told about). Needs no content, so
344
+ * the driver is picked before any blob is loaded.
345
+ */
346
+static struct userdiff_driver *diff_process_driver(struct diff_options *diffopt,
347
+ const char *path,
348
+ const xpparam_t *xpp)
349
+{
350
+ struct userdiff_driver *drv;
351
+
352
+ if (!diffopt || !path)
353
+ return NULL;
354
+ if (!diffopt->flags.allow_diff_process || diffopt->ignore_driver_algorithm)
355
+ return NULL;
356
+ /*
357
+ * Whitespace-ignoring, regex-ignore (-I) and anchored options
358
+ * change which lines count as different, but the process is never
359
+ * told about them, so its hunks could not honor them. A forced
360
+ * diff algorithm (an option or configured algorithm setting)
361
+ * requests a specific builtin computation, which an
362
+ * authoritative answer would override. Rather than silently
363
+ * override the user's request, fall back to the builtin diff,
364
+ * which does honor these flags. Key this off xpp (the
365
+ * parameters this diff actually runs with) rather than diffopt,
366
+ * so a caller like blame, which keeps its algorithm and
367
+ * whitespace flags outside diffopt, is covered without a
368
+ * separate guard of its own.
369
+ */
370
+ if ((xpp->flags & (XDF_WHITESPACE_FLAGS | XDF_IGNORE_BLANK_LINES |
371
+ XDF_DIFF_ALGORITHM_MASK)) ||
372
+ xpp->ignore_regex_nr || xpp->anchors_nr)
373
+ return NULL;
374
+
375
+ /*
376
+ * A path the protocol cannot carry never selects a process: an
377
+ * embedded newline would let the rest of the path forge further
378
+ * request keys, and the pathname must fit one packet. Passing
379
+ * here keeps the cost local to the path; a failed write would
380
+ * instead cost the whole command its process.
381
+ */
382
+ if (strchr(path, '\n') ||
383
+ strlen(path) > LARGE_PACKET_DATA_MAX - strlen("pathname=\n"))
384
+ return NULL;
385
+
386
+ drv = userdiff_find_by_path(diffopt->repo->index, path);
387
+ if (!drv || !drv->process)
388
+ return NULL;
389
+ return drv;
390
+}
391
+
392
+/*
393
+ * Without content there is no size-derived bound on a response, so cap
394
+ * accumulation at a constant instead. A response that exceeds the
395
+ * cap is a protocol error: the process is disabled for the rest of
396
+ * the command and the caller falls back to the builtin diff.
397
+ */
398
+#define OID_HUNKS_MAX (1 << 20)
399
+
400
+enum diff_process_result {
401
+ DIFF_PROCESS_ERROR = -1, /* failed; caller falls back to builtin */
402
+ DIFF_PROCESS_OK = 0, /* the process supplied hunks */
403
+ DIFF_PROCESS_SKIP, /* process did not apply: use builtin */
404
+ DIFF_PROCESS_EQUIVALENT, /* process says files are equivalent */
405
+};
406
+
407
+/*
408
+ * Ask drv's diff process to answer the request from the blob pair's
409
+ * object ids alone (the "hunks-by-oid" capability): no content is
410
+ * loaded or sent. On DIFF_PROCESS_OK the process's hunks are emitted
411
+ * through hunk_cb in 0-based emission coordinates, validated for order,
412
+ * overlap, and lockstep alignment first; because Git holds no content,
413
+ * the answer is used as the process sent it, without xdiff's compaction.
414
+ * DIFF_PROCESS_EQUIVALENT means the process asserts the pair equal.
415
+ * DIFF_PROCESS_SKIP covers everything that should fall through to the
416
+ * builtin computation: a missing capability, a missing object id, a
417
+ * status=need-content answer, or an invalid response.
418
+ */
419
+static enum diff_process_result diff_process_query_hunks(
420
+ struct diff_process_state *state,
421
+ struct userdiff_driver *drv,
422
+ const struct diff_provider_request *req,
423
+ xdl_emit_hunk_consume_func_t hunk_cb,
424
+ void *cb_data)
425
+{
426
+ const char *path = req->path;
427
+ struct diff_subprocess *entry;
428
+ struct child_process *process;
429
+ int fd_in, fd_out;
430
+ struct packet_reader reader;
431
+ struct strbuf status = STRBUF_INIT;
432
+ struct xdl_hunk *hunks = NULL;
433
+ struct diff_process_hunk presented;
434
+ struct xdl_hunk hunk;
435
+ size_t nr_hunks = 0, alloc_hunks = 0, i;
436
+ int bad_coords = 0;
437
+ long old_cap, new_cap;
438
+ enum diff_process_result res;
439
+
440
+ if (!req->old_oid || !req->new_oid)
441
+ return DIFF_PROCESS_SKIP;
442
+
443
+ entry = get_or_launch_process(state, drv);
444
+ if (!entry)
445
+ return DIFF_PROCESS_ERROR;
446
+ if (!(entry->supported_capabilities & CAP_OID_HUNKS))
447
+ return DIFF_PROCESS_SKIP;
448
+
449
+ process = subprocess_get_child_process(&entry->subprocess);
450
+ fd_in = process->in;
451
+ fd_out = process->out;
452
+
453
+ sigchain_push(SIGPIPE, SIG_IGN);
454
+
455
+ if (packet_write_fmt_gently(fd_in, "command=hunks-by-oid\n") ||
456
+ packet_write_fmt_gently(fd_in, "pathname=%s\n", path) ||
457
+ packet_write_fmt_gently(fd_in, "old-oid=%s\n",
458
+ oid_to_hex(req->old_oid)) ||
459
+ packet_write_fmt_gently(fd_in, "new-oid=%s\n",
460
+ oid_to_hex(req->new_oid)) ||
461
+ packet_flush_gently(fd_in))
462
+ goto comm_error;
463
+
464
+ packet_reader_init(&reader, fd_out, NULL, 0,
465
+ PACKET_READ_CHOMP_NEWLINE |
466
+ PACKET_READ_GENTLE_ON_EOF |
467
+ PACKET_READ_GENTLE_ON_READ_ERROR);
468
+ for (;;) {
469
+ enum packet_read_status rs = packet_reader_read(&reader);
470
+ int out_of_range;
471
+
472
+ if (rs == PACKET_READ_FLUSH)
473
+ break;
474
+ /*
475
+ * Only a hunk line may precede the flush. EOF and a
476
+ * malformed frame end the session; an empty packet, which
477
+ * a length-only read cannot tell from a flush, would
478
+ * truncate the hunk section here and leave the status
479
+ * section to poison the next request, so it is a protocol
480
+ * error too.
481
+ */
482
+ if (rs != PACKET_READ_NORMAL || !reader.pktlen)
483
+ goto comm_error;
484
+ if (parse_hunk_line(reader.line, &presented,
485
+ &out_of_range) < 0)
486
+ goto comm_error;
487
+ if (bad_coords)
488
+ continue;
489
+ if (out_of_range ||
490
+ diff_process_hunk_to_xdl(&presented, &hunk) < 0) {
491
+ /*
492
+ * Semantically invalid coordinates in a well-formed
493
+ * response: the stream stays in protocol sync, so
494
+ * drain the rest and fall back for this file while
495
+ * keeping the process alive, the same treatment
496
+ * validate_external_hunks() failures receive.
497
+ */
498
+ bad_coords = 1;
499
+ continue;
500
+ }
501
+ if (nr_hunks >= OID_HUNKS_MAX) {
502
+ warning(_("diff process '%s' sent too many hunks"
503
+ " for '%s'; disabling it for the"
504
+ " remainder of this command"),
505
+ drv->process, path);
506
+ goto disable;
507
+ }
508
+ ALLOC_GROW(hunks, nr_hunks + 1, alloc_hunks);
509
+ hunks[nr_hunks++] = hunk;
510
+ }
511
+
512
+ if (subprocess_read_status_gently(fd_out, &status))
513
+ goto comm_error;
514
+
515
+ if (!strcmp(status.buf, "success")) {
516
+ if (bad_coords) {
517
+ warning(_("diff process '%s' returned out-of-range "
518
+ "coordinates for '%s'; using the builtin diff"),
519
+ drv->process, path);
520
+ res = DIFF_PROCESS_SKIP;
521
+ goto out;
522
+ }
523
+ if (!nr_hunks) {
524
+ res = DIFF_PROCESS_EQUIVALENT;
525
+ goto out;
526
+ }
527
+ /*
528
+ * Bound the coordinates by the blobs' sizes, read from the
529
+ * object database without loading content. Either both
530
+ * bounds hold or neither is applied: a partial bound would
531
+ * misclassify a response that the other side's size would
532
+ * have caught.
533
+ */
534
+ old_cap = blob_line_cap(req->repo, req->old_oid);
535
+ new_cap = blob_line_cap(req->repo, req->new_oid);
536
+ if (old_cap < 0 || new_cap < 0)
537
+ old_cap = new_cap = -1;
538
+ if (validate_external_hunks(hunks, nr_hunks, old_cap, new_cap,
539
+ 0, drv->process, path) < 0) {
540
+ res = DIFF_PROCESS_SKIP;
541
+ goto out;
542
+ }
543
+ /*
544
+ * Replay in the coordinates a hunk consumer receives from
545
+ * xdiff's emission: 0-based starts. The answer is used as
546
+ * the process sent it; with no content in hand it cannot be
547
+ * re-run through xdiff's compaction.
548
+ */
549
+ for (i = 0; i < nr_hunks; i++)
550
+ hunk_cb(hunks[i].old_start - 1, hunks[i].old_count,
551
+ hunks[i].new_start - 1, hunks[i].new_count,
552
+ cb_data);
553
+ res = DIFF_PROCESS_OK;
554
+ goto out;
555
+ }
556
+ if (!strcmp(status.buf, "need-content")) {
557
+ /*
558
+ * The process cannot answer this pair from its object names;
559
+ * the caller computes the diff itself.
560
+ */
561
+ res = DIFF_PROCESS_SKIP;
562
+ goto out;
563
+ }
564
+ if (!strcmp(status.buf, "abort")) {
565
+ /* The process withdrew: stop asking it for this session. */
566
+ entry->supported_capabilities &= ~CAP_OID_HUNKS;
567
+ res = DIFF_PROCESS_SKIP;
568
+ goto out;
569
+ }
570
+ /*
571
+ * An unrecognized status is a protocol error, not a per-pair
572
+ * failure: this Git did not request anything it does not know,
573
+ * so the process is answering some other protocol, and asking
574
+ * it again would warn on every pair of the traversal.
575
+ */
576
+ warning(_("diff process '%s' sent unrecognized status '%s' for "
577
+ "'%s'; disabling it for the remainder of this command"),
578
+ drv->process, status.buf, path);
579
+ goto disable;
580
+out:
581
+ free(hunks);
582
+ strbuf_release(&status);
583
+ sigchain_pop(SIGPIPE);
584
+ return res;
585
+
586
+comm_error:
587
+ warning(_("diff process '%s' failed for '%s'; disabling it"
588
+ " for the remainder of this command"),
589
+ drv->process, path);
590
+disable:
591
+ subprocess_stop_command(&entry->subprocess);
592
+ entry->failed = 1;
593
+ free(hunks);
594
+ strbuf_release(&status);
595
+ sigchain_pop(SIGPIPE);
596
+ return DIFF_PROCESS_ERROR;
597
+}
598
+
599
+/*
600
+ * The process outranks every later provider through its chain
601
+ * position: when it answers, the walk ends, so no later provider
602
+ * serves the pair, and an answered pair is never recorded. When it
603
+ * does not answer (it defers with need-content, lacks the
604
+ * capability, or failed), the caller computes the builtin diff for
605
+ * that pair. The store holds builtin results and nothing else, so
606
+ * an identity answer for such a pair equals what the caller would
607
+ * compute. Every non-answer is therefore a pass: a refusal would
608
+ * suppress that equal answer, and would keep a warming run from
609
+ * recording the builtin result the caller computes anyway.
610
+ */
611
+static enum diff_provider_disposition
612
+diff_process_consult(struct diff_provider *provider,
613
+ const struct diff_provider_request *req,
614
+ diff_provider_fill_fn fill UNUSED, void *fill_data UNUSED,
615
+ xdl_emit_hunk_consume_func_t hunk_cb, void *cb_data)
616
+{
617
+ struct diff_process_state *state = provider->state;
618
+ struct userdiff_driver *drv;
619
+ struct subprocess_entry *running;
620
+
621
+ drv = diff_process_driver(req->diffopt, req->path, req->xpp);
622
+ if (!drv)
623
+ return DIFF_PROVIDER_DISP_PASS;
624
+ running = subprocess_find_entry(&state->subprocesses, drv->process);
625
+ if (running && container_of(running, struct diff_subprocess,
626
+ subprocess)->failed)
627
+ return DIFF_PROVIDER_DISP_PASS;
628
+
629
+ switch (diff_process_query_hunks(state, drv, req,
630
+ hunk_cb, cb_data)) {
631
+ case DIFF_PROCESS_OK:
632
+ case DIFF_PROCESS_EQUIVALENT:
633
+ return DIFF_PROVIDER_DISP_ANSWERED;
634
+ case DIFF_PROCESS_SKIP:
635
+ case DIFF_PROCESS_ERROR:
636
+ break;
637
+ }
638
+ return DIFF_PROVIDER_DISP_PASS;
639
+}
640
+
641
+static void diff_process_release(struct diff_provider *provider)
642
+{
643
+ struct diff_process_state *state = provider->state;
644
+ struct hashmap_iter iter;
645
+ struct diff_subprocess *entry;
646
+
647
+ /* A failed entry's process is already stopped or never ran. */
648
+ hashmap_for_each_entry(&state->subprocesses, &iter, entry,
649
+ subprocess.ent) {
650
+ if (!entry->failed)
651
+ subprocess_stop_command(&entry->subprocess);
652
+ free(entry->cmd);
653
+ }
654
+ hashmap_clear_and_free(&state->subprocesses,
655
+ struct diff_subprocess, subprocess.ent);
656
+ free(state);
657
+}
658
+
659
+struct diff_provider *diff_process_provider_new(void)
660
+{
661
+ struct diff_process_state *state = xcalloc(1, sizeof(*state));
662
+ struct diff_provider *p = xcalloc(1, sizeof(*p));
663
+
664
+ hashmap_init(&state->subprocesses, cmd2process_cmp, NULL, 0);
665
+ p->consult = diff_process_consult;
666
+ p->release = diff_process_release;
667
+ p->state = state;
668
+ return p;
669
+}