1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
|
#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);
}
|