git/diff-provider.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);
}