| 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 | } |