155 lines
4.9 KiB
C
155 lines
4.9 KiB
C
#include "git-compat-util.h"
|
|
#include "diff-provider-internal.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; 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 = 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 behind 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;
|
|
|
|
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_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);
|
|
}
|