| #include "git-compat-util.h" |
| #include "diff.h" |
| #include "diff-provider-internal.h" |
| #include "replace-object.h" |
| #include "repository.h" |
| |
| /* |
| * The terminal provider: the builtin computation. A request that |
| * carries a fill callback is answered by loading the pair's content |
| * and running xdiff, so a walk that reaches it never falls through |
| * to the consumer. On a consult-only walk it passes, and the walk's |
| * fall-through outcome tells the consumer to compute. |
| */ |
| static enum diff_provider_disposition |
| builtin_consult(struct diff_provider *provider UNUSED, |
| const struct diff_provider_request *req, |
| diff_provider_fill_fn fill, void *fill_data, |
| xdl_emit_hunk_consume_func_t hunk_cb, void *cb_data) |
| { |
| xdemitconf_t xecfg = { .hunk_func = hunk_cb }; |
| xdemitcb_t ecb = { .priv = cb_data }; |
| mmfile_t old_file, new_file; |
| |
| if (!fill) |
| return DIFF_PROVIDER_DISP_PASS; |
| if (fill(fill_data, &old_file, &new_file) < 0) |
| return DIFF_PROVIDER_DISP_ERROR; |
| if (xdi_diff(&old_file, &new_file, req->xpp, &xecfg, &ecb) < 0) |
| return DIFF_PROVIDER_DISP_ERROR; |
| return DIFF_PROVIDER_DISP_ANSWERED; |
| } |
| |
| static struct diff_provider *builtin_provider_new(void) |
| { |
| struct diff_provider *p = xcalloc(1, sizeof(*p)); |
| |
| p->consult = builtin_consult; |
| p->computes = 1; |
| return p; |
| } |
| |
| /* |
| * The repository's chain, assembled on first walk. The composition |
| * is fixed, and the order is the authority resolution: the process |
| * outranks the store, and the builtin computation is the terminal |
| * provider, so the chain always ends in an implementor that can |
| * answer. Nothing is decided per repository here; each provider |
| * gates itself per request. |
| */ |
| static struct diff_provider *provider_chain(struct repository *r) |
| { |
| struct diff_provider **tail = &r->diff_providers; |
| |
| if (*tail) |
| return *tail; |
| *tail = diff_process_provider_new(); |
| tail = &(*tail)->next; |
| *tail = diff_hunks_store_provider_new(); |
| tail = &(*tail)->next; |
| *tail = builtin_provider_new(); |
| return r->diff_providers; |
| } |
| |
| void diff_providers_clear(struct repository *r) |
| { |
| struct diff_provider *p = r->diff_providers; |
| |
| while (p) { |
| struct diff_provider *next = p->next; |
| |
| if (p->release) |
| p->release(p); |
| free(p); |
| p = next; |
| } |
| r->diff_providers = NULL; |
| } |
| |
| /* |
| * The walk shared by diff_provider_consult() and |
| * diff_provider_emit_hunks(): consult the chain in order and map its |
| * dispositions onto the outcome set. The first answer ends the |
| * walk. A stop-no-record disposition (diff-provider-internal.h) |
| * is a refusal, not a pass: the provider does not answer, but rules |
| * the pair out of identity service and out of recording, so from |
| * then on the walk consults only the computing provider, and a walk |
| * that ends unanswered carries the no-record verdict. With a fill |
| * callback the terminal provider computes instead of passing, so an |
| * emit walk returns only answered or error. |
| */ |
| static enum diff_provider_outcome |
| walk_providers(const struct diff_provider_request *req, |
| diff_provider_fill_fn fill, void *fill_data, |
| xdl_emit_hunk_consume_func_t hunk_cb, void *cb_data) |
| { |
| struct diff_provider *p; |
| int no_record = 0; |
| |
| if (req->diffopt && req->diffopt->repo != req->repo) |
| BUG("diff provider request walks one repository's chain " |
| "with another repository's diff options"); |
| |
| /* |
| * An object replacement redirects a blob's content |
| * (OBJECT_INFO_LOOKUP_REPLACE) while leaving the id that names it |
| * unchanged, so an answer keyed on the raw id would be the |
| * pre-replacement diff. A replacement is therefore a parameter |
| * outside the recording key: no provider may serve a replaced pair |
| * from its identity, and a result computed for it must not be |
| * recorded under the raw id. Mark the walk no-record so the |
| * identity providers step aside and the builtin computes from the |
| * replaced content. The check is a no-op when the repository has |
| * no replace refs. |
| */ |
| if ((req->old_oid && |
| lookup_replace_object(req->repo, req->old_oid) != req->old_oid) || |
| (req->new_oid && |
| lookup_replace_object(req->repo, req->new_oid) != req->new_oid)) |
| no_record = 1; |
| |
| for (p = provider_chain(req->repo); p; p = p->next) { |
| enum diff_provider_disposition disp; |
| |
| if (no_record && !p->computes) |
| continue; |
| disp = p->consult(p, req, fill, fill_data, |
| hunk_cb, cb_data); |
| if (disp == DIFF_PROVIDER_DISP_ERROR && !p->computes) |
| BUG("only the computing provider may return the " |
| "error disposition"); |
| if (p->computes && !fill && disp != DIFF_PROVIDER_DISP_PASS) |
| BUG("the computing provider must pass on a " |
| "fill-less walk"); |
| switch (disp) { |
| case DIFF_PROVIDER_DISP_ANSWERED: |
| return DIFF_PROVIDER_ANSWERED; |
| case DIFF_PROVIDER_DISP_PASS: |
| continue; |
| case DIFF_PROVIDER_DISP_STOP_NO_RECORD: |
| no_record = 1; |
| continue; |
| case DIFF_PROVIDER_DISP_ERROR: |
| return DIFF_PROVIDER_ERROR; |
| } |
| } |
| return no_record ? DIFF_PROVIDER_UNANSWERED_NO_RECORD : |
| DIFF_PROVIDER_UNANSWERED; |
| } |
| |
| enum diff_provider_outcome |
| diff_provider_consult(const struct diff_provider_request *req, |
| xdl_emit_hunk_consume_func_t hunk_cb, void *cb_data) |
| { |
| return walk_providers(req, NULL, NULL, hunk_cb, cb_data); |
| } |
| |
| enum diff_provider_hunks_error |
| diff_provider_check_hunk(struct diff_provider_hunks_check *c, |
| long old_start, long old_count, |
| long new_start, long new_count) |
| { |
| if (old_start < 0 || old_count < 0 || |
| new_start < 0 || new_count < 0 || |
| old_start > INT32_MAX || old_count > INT32_MAX || |
| new_start > INT32_MAX || new_count > INT32_MAX || |
| (int64_t)old_start + old_count > INT32_MAX || |
| (int64_t)new_start + new_count > INT32_MAX) |
| return DIFF_PROVIDER_HUNKS_RANGE; |
| if (old_start < c->prev_old_end || new_start < c->prev_new_end) |
| return DIFF_PROVIDER_HUNKS_OVERLAP; |
| if (old_start - c->prev_old_end != new_start - c->prev_new_end) |
| return DIFF_PROVIDER_HUNKS_MISALIGNED; |
| /* |
| * With each field bounded to int32 above, the int64 sums cannot |
| * overflow even where long is 32-bit, and the range rule has |
| * already capped them at INT32_MAX. |
| */ |
| c->prev_old_end = (int64_t)old_start + old_count; |
| c->prev_new_end = (int64_t)new_start + new_count; |
| return DIFF_PROVIDER_HUNKS_OK; |
| } |
| |
| enum diff_provider_outcome |
| diff_provider_emit_hunks(const struct diff_provider_request *req, |
| diff_provider_fill_fn fill, void *fill_data, |
| xdl_emit_hunk_consume_func_t hunk_cb, |
| void *cb_data) |
| { |
| return walk_providers(req, fill, fill_data, hunk_cb, cb_data); |
| } |