| /* |
| * The process provider of the hunk provider interface: consult a |
| * long-running external process via the pkt-line protocol for the |
| * hunks of a blob pair. The process answers from the pair's object |
| * names alone: it can serve a persistent cache keyed on the pair, or |
| * fetch the blobs from the repository itself (e.g. via "git cat-file |
| * --batch") and compute its own notion of which lines changed. The |
| * provider sits at the head of its repository's chain and gates |
| * itself per request; its state is the repository's pool of running |
| * processes, one per configured command, stopped when the provider |
| * is released. |
| * |
| * Protocol: pkt-line over stdin/stdout, following the pattern of |
| * the long-running filter process protocol (see convert.c). |
| * |
| * Handshake: |
| * git> git-diff-client / version=1 / flush |
| * process< git-diff-server / version=1 / flush |
| * git> capability=hunks-by-oid / flush |
| * process< capability=hunks-by-oid / flush |
| * |
| * Per-pair, when both sides are stored blobs: |
| * git> command=hunks-by-oid / pathname=<path> |
| * git> old-oid=<hex> / new-oid=<hex> / flush |
| * process< hunk <old_start> <old_count> <new_start> <new_count> |
| * process< ... / flush |
| * process< status=success / flush |
| * |
| * No content is sent. Because Git holds no content for the exchange, |
| * the answer is used as the process sent it: the hunks are not re-run |
| * through xdiff's compaction, and a status=success response with zero |
| * hunks asserts that the blobs are equivalent, including their |
| * trailing newlines. A process that cannot answer from the object names |
| * (or cannot rule out a trailing-newline-only difference) responds |
| * status=need-content; the pair then gets the builtin answer, served |
| * from the diff-hunks store or computed. A later |
| * protocol extension can define a content-carrying request for such |
| * processes and for sides that are not stored blobs. |
| */ |
| |
| #include "git-compat-util.h" |
| #include "diff.h" |
| #include "diff-provider-internal.h" |
| #include "gettext.h" |
| #include "hex.h" |
| #include "odb.h" |
| #include "repository.h" |
| #include "sigchain.h" |
| #include "userdiff.h" |
| #include "sub-process.h" |
| #include "pkt-line.h" |
| #include "strbuf.h" |
| |
| #define CAP_OID_HUNKS (1u << 0) |
| |
| /* |
| * The provider's state: the repository's diff processes, keyed by |
| * their command string, so drivers that configure the same command |
| * share one process. An entry whose process failed stays in the |
| * pool with the failed bit set, so the command is not retried while |
| * the entry lives; the pool and its entries last until the provider |
| * is released. |
| */ |
| struct diff_process_state { |
| struct hashmap subprocesses; |
| }; |
| |
| struct diff_subprocess { |
| struct subprocess_entry subprocess; |
| /* |
| * Owns the string subprocess.cmd and the hashmap key borrow: the |
| * entry outlives the userdiff config a re-read may replace. |
| */ |
| char *cmd; |
| unsigned int supported_capabilities; |
| unsigned failed : 1; |
| }; |
| |
| static int start_diff_process_fn(struct subprocess_entry *subprocess) |
| { |
| static int versions[] = { 1, 0 }; |
| static struct subprocess_capability capabilities[] = { |
| { "hunks-by-oid", CAP_OID_HUNKS }, |
| { NULL, 0 } |
| }; |
| struct diff_subprocess *entry = |
| container_of(subprocess, struct diff_subprocess, subprocess); |
| |
| return subprocess_handshake(subprocess, "git-diff", |
| versions, NULL, |
| capabilities, |
| &entry->supported_capabilities); |
| } |
| |
| /* |
| * The pool entry for a command, or NULL when its process fails to |
| * start here: the failure leaves a failed entry in the pool, so only |
| * the request that observed it maps it to an error and later |
| * requests pass the provider by. |
| */ |
| static struct diff_subprocess *get_or_launch_process( |
| struct diff_process_state *state, |
| struct userdiff_driver *drv) |
| { |
| struct subprocess_entry *running; |
| struct diff_subprocess *entry; |
| |
| running = subprocess_find_entry(&state->subprocesses, drv->process); |
| if (running) { |
| entry = container_of(running, struct diff_subprocess, |
| subprocess); |
| return entry->failed ? NULL : entry; |
| } |
| |
| entry = xcalloc(1, sizeof(*entry)); |
| entry->cmd = xstrdup(drv->process); |
| if (subprocess_start_command(&entry->subprocess, entry->cmd, |
| start_diff_process_fn)) |
| entry->failed = 1; |
| hashmap_entry_init(&entry->subprocess.ent, strhash(entry->cmd)); |
| hashmap_add(&state->subprocesses, &entry->subprocess.ent); |
| if (entry->failed) { |
| warning(_("diff process '%s' failed to start;" |
| " using the builtin diff"), drv->process); |
| return NULL; |
| } |
| return entry; |
| } |
| |
| /* |
| * A hunk in the diff process's presentation coordinates: the line |
| * numbering it reports over the protocol. Kept distinct from struct |
| * xdl_hunk (xdiff's coordinates) so that only translated hunks ever |
| * reach a consumer; diff_process_hunk_to_xdl() is the single |
| * crossing point. |
| */ |
| struct diff_process_hunk { |
| long old_start, old_count; |
| long new_start, new_count; |
| }; |
| |
| /* |
| * Parse one non-negative decimal field of a hunk line into *out and |
| * advance *line past it. Fields must be plain decimal with no leading |
| * whitespace or sign (isdigit() takes an unsigned char to stay defined |
| * for high-bit bytes). The first three fields are followed by a single |
| * space; the last (is_last) is followed by end-of-string or a space. |
| * Trailing space-separated tokens after the last field are allowed and |
| * ignored, so a future protocol version can append fields (e.g. a |
| * "moved" marker) without an older Git rejecting the line, mirroring |
| * the request-side rule that processes ignore unknown keys. |
| * |
| * A value that overflows strtol() is not a parse failure: the line is |
| * well-formed, so the stream stays in protocol sync. It is reported |
| * through *out_of_range, and the caller skips the pair the same way |
| * it skips any other out-of-range coordinate. |
| */ |
| static int parse_hunk_field(const char **line, long *out, int is_last, |
| int *out_of_range) |
| { |
| const char *p = *line; |
| char *end; |
| |
| if (!isdigit((unsigned char)*p)) |
| return -1; |
| errno = 0; |
| *out = strtol(p, &end, 10); |
| if (end == p) |
| return -1; |
| if (errno == ERANGE) |
| *out_of_range = 1; |
| else if (errno) |
| return -1; |
| if (is_last) { |
| if (*end != '\0' && *end != ' ') |
| return -1; |
| } else { |
| if (*end != ' ') |
| return -1; |
| end++; |
| } |
| *line = end; |
| return 0; |
| } |
| |
| static int parse_hunk_line(const char *line, |
| struct diff_process_hunk *presented, |
| int *out_of_range) |
| { |
| *out_of_range = 0; |
| /* Format: "hunk <old_start> <old_count> <new_start> <new_count>" */ |
| if (!skip_prefix(line, "hunk ", &line)) |
| return -1; |
| if (parse_hunk_field(&line, &presented->old_start, 0, out_of_range) || |
| parse_hunk_field(&line, &presented->old_count, 0, out_of_range) || |
| parse_hunk_field(&line, &presented->new_start, 0, out_of_range) || |
| parse_hunk_field(&line, &presented->new_count, 1, out_of_range)) |
| return -1; |
| return 0; |
| } |
| |
| /* |
| * Translate a hunk from the diff process's presentation coordinates |
| * into xdiff's. |
| * |
| * Protocol starts are already 1-based positions (the line a change |
| * sits before), the same numbering xdiff uses, so the only adjustment |
| * is for an empty file side: "git diff" addresses it with a start of 0 |
| * and a count of 0 (e.g. "0 0 1 5" adds five lines to an empty old |
| * side), and since xdiff uses start-1 as an array index that 0 becomes |
| * 1 here. This is NOT the full inverse of xdl_emit_hunk_hdr() |
| * (xdiff/xutils.c): that emitter shifts a count-0 range to start-1 for |
| * the displayed "@@" header, but the protocol keeps the unshifted |
| * 1-based position for a mid-file insert or delete. This is the single |
| * point where presentation coordinates become xdiff coordinates, so |
| * any consumer of these coordinates may assume 1-based starts. |
| * |
| * Returns -1 for a start of 0 paired with a nonzero count, which names |
| * no line in either coordinate system. (parse_hunk_line() already |
| * guarantees non-negative starts and counts.) |
| */ |
| static int diff_process_hunk_to_xdl(const struct diff_process_hunk *presented, |
| struct xdl_hunk *xdl) |
| { |
| long old_start = presented->old_start; |
| long new_start = presented->new_start; |
| |
| if ((!old_start && presented->old_count) || |
| (!new_start && presented->new_count)) |
| return -1; |
| if (!old_start) |
| old_start = 1; |
| if (!new_start) |
| new_start = 1; |
| |
| xdl->old_start = old_start; |
| xdl->old_count = presented->old_count; |
| xdl->new_start = new_start; |
| xdl->new_count = presented->new_count; |
| return 0; |
| } |
| |
| /* |
| * Validate the process's hunks (already in xdiff coordinates) before they |
| * bypass the diff algorithm. The content-independent rules (in-order, |
| * non-overlapping, lockstep-aligned, int32-bounded coordinates) are the |
| * provider interface's shared rule, diff_provider_check_hunk(); this |
| * function adds the two checks that need the blobs' line counts (a hunk |
| * past the end of a file, the run after the last hunk) and the |
| * per-rule diagnostics naming the process. On a bad response we warn |
| * and the caller falls back to the builtin diff. Returns 0 if valid, |
| * -1 (after warning) otherwise. |
| * |
| * old_lines/new_lines bound the line count of each side, or are |
| * negative when no bound is known. An oid-only answer arrives without |
| * content, so its caller passes upper bounds derived from the blobs' |
| * byte sizes, which caps coordinate magnitude but cannot support the |
| * run-after-the-last-hunk check: that one compares exact line counts, |
| * so it runs only when lines_exact is set, which no caller does today. |
| * It is kept for a content-carrying request, whose loaded buffers |
| * would provide exact counts. |
| */ |
| static int validate_external_hunks(const struct xdl_hunk *hunks, size_t nr, |
| long old_lines, long new_lines, |
| int lines_exact, |
| const char *process, const char *path) |
| { |
| struct diff_provider_hunks_check c = { 0 }; |
| size_t i; |
| |
| for (i = 0; i < nr; i++) { |
| const struct xdl_hunk *h = &hunks[i]; |
| |
| if (old_lines >= 0 && |
| (h->old_count > old_lines - h->old_start + 1 || |
| h->new_count > new_lines - h->new_start + 1)) { |
| warning(_("diff process '%s' returned a hunk past the " |
| "end of '%s'; using the builtin diff"), |
| process, path); |
| return -1; |
| } |
| switch (diff_provider_check_hunk(&c, h->old_start, |
| h->old_count, h->new_start, |
| h->new_count)) { |
| case DIFF_PROVIDER_HUNKS_OK: |
| break; |
| case DIFF_PROVIDER_HUNKS_RANGE: |
| warning(_("diff process '%s' returned out-of-range " |
| "coordinates for '%s'; using the builtin diff"), |
| process, path); |
| return -1; |
| case DIFF_PROVIDER_HUNKS_OVERLAP: |
| warning(_("diff process '%s' returned overlapping hunks " |
| "for '%s'; using the builtin diff"), |
| process, path); |
| return -1; |
| case DIFF_PROVIDER_HUNKS_MISALIGNED: |
| warning(_("diff process '%s' returned hunks that leave " |
| "'%s' misaligned; using the builtin diff"), |
| process, path); |
| return -1; |
| } |
| } |
| if (lines_exact && |
| old_lines - c.prev_old_end != new_lines - c.prev_new_end) { |
| warning(_("diff process '%s' returned hunks that leave '%s' " |
| "misaligned; using the builtin diff"), |
| process, path); |
| return -1; |
| } |
| return 0; |
| } |
| |
| /* |
| * The most lines a blob can hold, from its size alone: every line, |
| * even an empty one, costs at least one byte, so a blob of N bytes |
| * holds at most N lines. Returns -1 when the size is unavailable, |
| * leaving the response bounded only by the shared int32 rule. A size |
| * beyond INT32_MAX clamps to it, which loses nothing: a coordinate |
| * that large fails the shared rule anyway. In a partial clone the |
| * size lookup must not fetch the blob from the promisor remote: |
| * validating an answer that exists to avoid loading content must not |
| * itself download that content, so a missing blob reads as size |
| * unavailable instead. |
| */ |
| static long blob_line_cap(struct repository *r, const struct object_id *oid) |
| { |
| size_t size; |
| struct object_info oi = OBJECT_INFO_INIT; |
| |
| oi.sizep = &size; |
| if (odb_read_object_info_extended(r->objects, oid, &oi, |
| OBJECT_INFO_SKIP_FETCH_OBJECT) < 0) |
| return -1; |
| if (size > INT32_MAX) |
| return INT32_MAX; |
| return (long)size; |
| } |
| |
| /* |
| * The driver whose process a consultation for path would ask, or NULL |
| * when none applies (no driver, process not allowed, or xpp carries |
| * options the process is never told about). Needs no content, so |
| * the driver is picked before any blob is loaded. |
| */ |
| static struct userdiff_driver *diff_process_driver(struct diff_options *diffopt, |
| const char *path, |
| const xpparam_t *xpp) |
| { |
| struct userdiff_driver *drv; |
| |
| if (!diffopt || !path) |
| return NULL; |
| if (!diffopt->flags.allow_diff_process || diffopt->ignore_driver_algorithm) |
| return NULL; |
| /* |
| * Whitespace-ignoring, regex-ignore (-I) and anchored options |
| * change which lines count as different, but the process is never |
| * told about them, so its hunks could not honor them. A forced |
| * diff algorithm (an option or configured algorithm setting) |
| * requests a specific builtin computation, which an |
| * authoritative answer would override. Rather than silently |
| * override the user's request, fall back to the builtin diff, |
| * which does honor these flags. Key this off xpp (the |
| * parameters this diff actually runs with) rather than diffopt, |
| * so a caller like blame, which keeps its algorithm and |
| * whitespace flags outside diffopt, is covered without a |
| * separate guard of its own. |
| */ |
| if ((xpp->flags & (XDF_WHITESPACE_FLAGS | XDF_IGNORE_BLANK_LINES | |
| XDF_DIFF_ALGORITHM_MASK)) || |
| xpp->ignore_regex_nr || xpp->anchors_nr) |
| return NULL; |
| |
| /* |
| * A path the protocol cannot carry never selects a process: an |
| * embedded newline would let the rest of the path forge further |
| * request keys, and the pathname must fit one packet. Passing |
| * here keeps the cost local to the path; a failed write would |
| * instead cost the whole command its process. |
| */ |
| if (strchr(path, '\n') || |
| strlen(path) > LARGE_PACKET_DATA_MAX - strlen("pathname=\n")) |
| return NULL; |
| |
| drv = userdiff_find_by_path(diffopt->repo->index, path); |
| if (!drv || !drv->process) |
| return NULL; |
| return drv; |
| } |
| |
| /* |
| * Without content there is no size-derived bound on a response, so cap |
| * accumulation at a constant instead. A response that exceeds the |
| * cap is a protocol error: the process is disabled for the rest of |
| * the command and the caller falls back to the builtin diff. |
| */ |
| #define OID_HUNKS_MAX (1 << 20) |
| |
| enum diff_process_result { |
| DIFF_PROCESS_ERROR = -1, /* failed; caller falls back to builtin */ |
| DIFF_PROCESS_OK = 0, /* the process supplied hunks */ |
| DIFF_PROCESS_SKIP, /* process did not apply: use builtin */ |
| DIFF_PROCESS_EQUIVALENT, /* process says files are equivalent */ |
| }; |
| |
| /* |
| * Ask drv's diff process to answer the request from the blob pair's |
| * object ids alone (the "hunks-by-oid" capability): no content is |
| * loaded or sent. On DIFF_PROCESS_OK the process's hunks are emitted |
| * through hunk_cb in 0-based emission coordinates, validated for order, |
| * overlap, and lockstep alignment first; because Git holds no content, |
| * the answer is used as the process sent it, without xdiff's compaction. |
| * DIFF_PROCESS_EQUIVALENT means the process asserts the pair equal. |
| * DIFF_PROCESS_SKIP covers everything that should fall through to the |
| * builtin computation: a missing capability, a missing object id, a |
| * status=need-content answer, or an invalid response. |
| */ |
| static enum diff_process_result diff_process_query_hunks( |
| struct diff_process_state *state, |
| struct userdiff_driver *drv, |
| const struct diff_provider_request *req, |
| xdl_emit_hunk_consume_func_t hunk_cb, |
| void *cb_data) |
| { |
| const char *path = req->path; |
| struct diff_subprocess *entry; |
| struct child_process *process; |
| int fd_in, fd_out; |
| struct packet_reader reader; |
| struct strbuf status = STRBUF_INIT; |
| struct xdl_hunk *hunks = NULL; |
| struct diff_process_hunk presented; |
| struct xdl_hunk hunk; |
| size_t nr_hunks = 0, alloc_hunks = 0, i; |
| int bad_coords = 0; |
| long old_cap, new_cap; |
| enum diff_process_result res; |
| |
| if (!req->old_oid || !req->new_oid) |
| return DIFF_PROCESS_SKIP; |
| |
| entry = get_or_launch_process(state, drv); |
| if (!entry) |
| return DIFF_PROCESS_ERROR; |
| if (!(entry->supported_capabilities & CAP_OID_HUNKS)) |
| return DIFF_PROCESS_SKIP; |
| |
| process = subprocess_get_child_process(&entry->subprocess); |
| fd_in = process->in; |
| fd_out = process->out; |
| |
| sigchain_push(SIGPIPE, SIG_IGN); |
| |
| if (packet_write_fmt_gently(fd_in, "command=hunks-by-oid\n") || |
| packet_write_fmt_gently(fd_in, "pathname=%s\n", path) || |
| packet_write_fmt_gently(fd_in, "old-oid=%s\n", |
| oid_to_hex(req->old_oid)) || |
| packet_write_fmt_gently(fd_in, "new-oid=%s\n", |
| oid_to_hex(req->new_oid)) || |
| packet_flush_gently(fd_in)) |
| goto comm_error; |
| |
| packet_reader_init(&reader, fd_out, NULL, 0, |
| PACKET_READ_CHOMP_NEWLINE | |
| PACKET_READ_GENTLE_ON_EOF | |
| PACKET_READ_GENTLE_ON_READ_ERROR); |
| for (;;) { |
| enum packet_read_status rs = packet_reader_read(&reader); |
| int out_of_range; |
| |
| if (rs == PACKET_READ_FLUSH) |
| break; |
| /* |
| * Only a hunk line may precede the flush. EOF and a |
| * malformed frame end the session; an empty packet, which |
| * a length-only read cannot tell from a flush, would |
| * truncate the hunk section here and leave the status |
| * section to poison the next request, so it is a protocol |
| * error too. |
| */ |
| if (rs != PACKET_READ_NORMAL || !reader.pktlen) |
| goto comm_error; |
| if (parse_hunk_line(reader.line, &presented, |
| &out_of_range) < 0) |
| goto comm_error; |
| if (bad_coords) |
| continue; |
| if (out_of_range || |
| diff_process_hunk_to_xdl(&presented, &hunk) < 0) { |
| /* |
| * Semantically invalid coordinates in a well-formed |
| * response: the stream stays in protocol sync, so |
| * drain the rest and fall back for this file while |
| * keeping the process alive, the same treatment |
| * validate_external_hunks() failures receive. |
| */ |
| bad_coords = 1; |
| continue; |
| } |
| if (nr_hunks >= OID_HUNKS_MAX) { |
| warning(_("diff process '%s' sent too many hunks" |
| " for '%s'; disabling it for the" |
| " remainder of this command"), |
| drv->process, path); |
| goto disable; |
| } |
| ALLOC_GROW(hunks, nr_hunks + 1, alloc_hunks); |
| hunks[nr_hunks++] = hunk; |
| } |
| |
| if (subprocess_read_status_gently(fd_out, &status)) |
| goto comm_error; |
| |
| if (!strcmp(status.buf, "success")) { |
| if (bad_coords) { |
| warning(_("diff process '%s' returned out-of-range " |
| "coordinates for '%s'; using the builtin diff"), |
| drv->process, path); |
| res = DIFF_PROCESS_SKIP; |
| goto out; |
| } |
| if (!nr_hunks) { |
| res = DIFF_PROCESS_EQUIVALENT; |
| goto out; |
| } |
| /* |
| * Bound the coordinates by the blobs' sizes, read from the |
| * object database without loading content. Either both |
| * bounds hold or neither is applied: a partial bound would |
| * misclassify a response that the other side's size would |
| * have caught. |
| */ |
| old_cap = blob_line_cap(req->repo, req->old_oid); |
| new_cap = blob_line_cap(req->repo, req->new_oid); |
| if (old_cap < 0 || new_cap < 0) |
| old_cap = new_cap = -1; |
| if (validate_external_hunks(hunks, nr_hunks, old_cap, new_cap, |
| 0, drv->process, path) < 0) { |
| res = DIFF_PROCESS_SKIP; |
| goto out; |
| } |
| /* |
| * Replay in the coordinates a hunk consumer receives from |
| * xdiff's emission: 0-based starts. The answer is used as |
| * the process sent it; with no content in hand it cannot be |
| * re-run through xdiff's compaction. |
| */ |
| for (i = 0; i < nr_hunks; i++) |
| hunk_cb(hunks[i].old_start - 1, hunks[i].old_count, |
| hunks[i].new_start - 1, hunks[i].new_count, |
| cb_data); |
| res = DIFF_PROCESS_OK; |
| goto out; |
| } |
| if (!strcmp(status.buf, "need-content")) { |
| /* |
| * The process cannot answer this pair from its object names; |
| * the caller computes the diff itself. |
| */ |
| res = DIFF_PROCESS_SKIP; |
| goto out; |
| } |
| if (!strcmp(status.buf, "abort")) { |
| /* The process withdrew: stop asking it for this session. */ |
| entry->supported_capabilities &= ~CAP_OID_HUNKS; |
| res = DIFF_PROCESS_SKIP; |
| goto out; |
| } |
| /* |
| * An unrecognized status is a protocol error, not a per-pair |
| * failure: this Git did not request anything it does not know, |
| * so the process is answering some other protocol, and asking |
| * it again would warn on every pair of the traversal. |
| */ |
| warning(_("diff process '%s' sent unrecognized status '%s' for " |
| "'%s'; disabling it for the remainder of this command"), |
| drv->process, status.buf, path); |
| goto disable; |
| out: |
| free(hunks); |
| strbuf_release(&status); |
| sigchain_pop(SIGPIPE); |
| return res; |
| |
| comm_error: |
| warning(_("diff process '%s' failed for '%s'; disabling it" |
| " for the remainder of this command"), |
| drv->process, path); |
| disable: |
| subprocess_stop_command(&entry->subprocess); |
| entry->failed = 1; |
| free(hunks); |
| strbuf_release(&status); |
| sigchain_pop(SIGPIPE); |
| return DIFF_PROCESS_ERROR; |
| } |
| |
| /* |
| * The process outranks every later provider through its chain |
| * position: when it answers, the walk ends, so no later provider |
| * serves the pair, and an answered pair is never recorded. When it |
| * does not answer (it defers with need-content, lacks the |
| * capability, or failed), the caller computes the builtin diff for |
| * that pair. The store holds builtin results and nothing else, so |
| * an identity answer for such a pair equals what the caller would |
| * compute. Every non-answer is therefore a pass: a refusal would |
| * suppress that equal answer, and would keep a warming run from |
| * recording the builtin result the caller computes anyway. |
| */ |
| static enum diff_provider_disposition |
| diff_process_consult(struct diff_provider *provider, |
| 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) |
| { |
| struct diff_process_state *state = provider->state; |
| struct userdiff_driver *drv; |
| struct subprocess_entry *running; |
| |
| drv = diff_process_driver(req->diffopt, req->path, req->xpp); |
| if (!drv) |
| return DIFF_PROVIDER_DISP_PASS; |
| running = subprocess_find_entry(&state->subprocesses, drv->process); |
| if (running && container_of(running, struct diff_subprocess, |
| subprocess)->failed) |
| return DIFF_PROVIDER_DISP_PASS; |
| |
| switch (diff_process_query_hunks(state, drv, req, |
| hunk_cb, cb_data)) { |
| case DIFF_PROCESS_OK: |
| case DIFF_PROCESS_EQUIVALENT: |
| return DIFF_PROVIDER_DISP_ANSWERED; |
| case DIFF_PROCESS_SKIP: |
| case DIFF_PROCESS_ERROR: |
| break; |
| } |
| return DIFF_PROVIDER_DISP_PASS; |
| } |
| |
| static void diff_process_release(struct diff_provider *provider) |
| { |
| struct diff_process_state *state = provider->state; |
| struct hashmap_iter iter; |
| struct diff_subprocess *entry; |
| |
| /* A failed entry's process is already stopped or never ran. */ |
| hashmap_for_each_entry(&state->subprocesses, &iter, entry, |
| subprocess.ent) { |
| if (!entry->failed) |
| subprocess_stop_command(&entry->subprocess); |
| free(entry->cmd); |
| } |
| hashmap_clear_and_free(&state->subprocesses, |
| struct diff_subprocess, subprocess.ent); |
| free(state); |
| } |
| |
| struct diff_provider *diff_process_provider_new(void) |
| { |
| struct diff_process_state *state = xcalloc(1, sizeof(*state)); |
| struct diff_provider *p = xcalloc(1, sizeof(*p)); |
| |
| hashmap_init(&state->subprocesses, cmd2process_cmp, NULL, 0); |
| p->consult = diff_process_consult; |
| p->release = diff_process_release; |
| p->state = state; |
| return p; |
| } |