diff: introduce a hunk provider interface

To learn which line ranges changed between two blobs, every consumer in the diff machinery loads both blobs and runs xdiff. There is no other way to supply that answer, even when it is known elsewhere: a cache may hold the ranges from the last time the pair was diffed, and a format-aware process may have its own idea of which lines changed. Either could answer from the blob object ids alone, but the loading and computing are hard-wired into each consumer, so such an answer has no place to enter. Introduce the hunk provider interface, diff-provider.h, between asking the question and computing the answer. A provider answers a request made of the pair's identity, its blob object ids and the parameters that determine the diff. A provider is either authoritative, so its answer may deliberately differ from the builtin diff, or not, so its answer must reproduce the builtin result exactly. Every answer served from identity passes diff_provider_check_hunk() before a consumer sees it: coordinates fit int32, hunks are ordered and non-overlapping, and the unchanged runs between them match on both sides. A failing answer is discarded and the pair falls through as unanswered. Providers are repository-lifecycle objects. Each repository owns a chain of them, built on first consultation and released from repo_clear(), so a submodule gets its own providers and no provider state outlives the repository it serves. The chain has a fixed composition, and each provider gates itself per request, passing when it does not apply. Chain order is the authority: the first answer wins. A provider may instead refuse a pair whose request is shaped by parameters its recording key cannot express. After a refusal, no later provider answers the pair from identity, and the consumer must not record what it computes for it. The last provider is the builtin computation, the only one that computes rather than answering from identity, so a walk given a fill callback always ends in an answer, refusal or not. The walk in diff-provider.c maps a provider's four dispositions (answer, pass, fail, refuse) onto the consumer-facing outcomes, and checks with BUG() that only the computing provider fails and that it passes on a walk with no fill callback. The implementor contract, the provider struct, its dispositions, and the shared check, lives in diff-provider-internal.h, as refs/refs-internal.h is to refs.h; consumers see only diff-provider.h. The consumer surface is two types. struct diff_provider_request names what is diffed and under which parameters; each later commit that consults on more state adds the field it keys on (the object ids and diff options, then the path). enum diff_provider_outcome flattens two dependent axes into four points: the response state (answered, unanswered, failed) and, only when unanswered, whether the caller may record what it computes. The record rule rides in the outcome, not a separate flag, so -Wswitch forces every consumer to place the no-record arm. A provider added later maps onto these values inside the walk, so consumer code is written once. diff_provider_emit_hunks() is the consumer entry: the caller states the request, a hunk callback, and a content-loading callback that reaches the terminal provider only when the ranges are computed. Blame's pass_blame_to_parent() is the first consumer, since it knows both blob ids before reading either blob; its loads move into the fill callback. With only the terminal provider registered, every request still computes, so behavior is unchanged. (Blame's -C/-M split detection diffs partial buffers with no blob identity and stays on xdi_diff().) Signed-off-by: Michael Montalbo <mmontalbo@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Michael Montalbo committed Aug 1, 2026 at 10:41 UTC b18db2edc5983a5ab492a5b035fa70dec5b9ff11
8 files changed +447 -8
Makefile
+1
@@ -1151,6 +1151,7 @@ LIB_OBJS += diff-delta.o
1151 LIB_OBJS += diff-merges.o
1152 LIB_OBJS += diff-lib.o
1153 LIB_OBJS += diff-no-index.o
1154 +LIB_OBJS += diff-provider.o
1155 LIB_OBJS += diff.o
1156 LIB_OBJS += diffcore-break.o
1157 LIB_OBJS += diffcore-delta.o
blame.c
+28 -8
@@ -23,6 +23,7 @@
23 #include "commit-slab.h"
24 #include "bloom.h"
25 #include "commit-graph.h"
26 +#include "diff-provider.h"
27
28 define_commit_slab(blame_suspects, struct blame_origin *);
29 static struct blame_suspects blame_suspects;
@@ -1936,6 +1937,28 @@ static int blame_chunk_cb(long start_a, long count_a,
1937 return 0;
1938 }
1939
1940 +struct blame_diff_fill_data {
1941 + struct blame_scoreboard *sb;
1942 + struct blame_origin *parent, *target;
1943 + int ignore_diffs;
1944 +};
1945 +
1946 +/*
1947 + * Content load for diff_provider_emit_hunks(): runs when the diff is
1948 + * computed.
1949 + */
1950 +static int blame_diff_fill(void *data, mmfile_t *old_file, mmfile_t *new_file)
1951 +{
1952 + struct blame_diff_fill_data *f = data;
1953 +
1954 + fill_origin_blob(&f->sb->revs->diffopt, f->parent, old_file,
1955 + &f->sb->num_read_blob, f->ignore_diffs);
1956 + fill_origin_blob(&f->sb->revs->diffopt, f->target, new_file,
1957 + &f->sb->num_read_blob, f->ignore_diffs);
1958 + f->sb->num_get_patch++;
1959 + return 0;
1960 +}
1961 +
1962 /*
1963 * We are looking at the origin 'target' and aiming to pass blame
1964 * for the lines it is suspected to its parent. Run diff to find
@@ -1945,9 +1968,11 @@ static void pass_blame_to_parent(struct blame_scoreboard *sb,
1968 struct blame_origin *target,
1969 struct blame_origin *parent, int ignore_diffs)
1970 {
1948 - mmfile_t file_p, file_o;
1971 struct blame_chunk_cb_data d;
1972 struct blame_entry *newdest = NULL;
1973 + struct blame_diff_fill_data fill_data = { sb, parent, target, ignore_diffs };
1974 + xpparam_t xpp = { .flags = sb->xdl_opts };
1975 + struct diff_provider_request req = { .repo = sb->repo, .xpp = &xpp };
1976
1977 if (!target->suspects)
1978 return; /* nothing remains for this target */
@@ -1958,13 +1983,8 @@ static void pass_blame_to_parent(struct blame_scoreboard *sb,
1983 d.ignore_diffs = ignore_diffs;
1984 d.dstq = &newdest; d.srcq = &target->suspects;
1985
1961 - fill_origin_blob(&sb->revs->diffopt, parent, &file_p,
1962 - &sb->num_read_blob, ignore_diffs);
1963 - fill_origin_blob(&sb->revs->diffopt, target, &file_o,
1964 - &sb->num_read_blob, ignore_diffs);
1965 - sb->num_get_patch++;
1966 -
1967 - if (diff_hunks(&file_p, &file_o, blame_chunk_cb, &d, sb->xdl_opts))
1986 + if (diff_provider_emit_hunks(&req, blame_diff_fill, &fill_data,
1987 + blame_chunk_cb, &d) == DIFF_PROVIDER_ERROR)
1988 die("unable to generate diff (%s -> %s)",
1989 oid_to_hex(&parent->commit->object.oid),
1990 oid_to_hex(&target->commit->object.oid));
diff-provider-internal.h new
+122
@@ -0,0 +1,122 @@
1 +#ifndef DIFF_PROVIDER_INTERNAL_H
2 +#define DIFF_PROVIDER_INTERNAL_H
3 +
4 +#include "diff-provider.h"
5 +
6 +/*
7 + * The implementor-facing half of the hunk provider interface: the
8 + * provider chain a repository owns, and the rules a provider applies
9 + * to its own answer before any consumer sees it. Provider
10 + * implementations include this header; consumers of the interface
11 + * use only diff-provider.h.
12 + */
13 +
14 +/*
15 + * A provider's verdict on one request. Only the chain walk
16 + * (diff-provider.c) sees these; it maps the dispositions of a whole
17 + * walk onto the public outcome set.
18 + */
19 +enum diff_provider_disposition {
20 + /*
21 + * The provider failed to produce the answer it owns. Only
22 + * the computing provider returns this: its compute leg is
23 + * the one part of a consultation that can fail, and the walk
24 + * ends with the public error outcome.
25 + */
26 + DIFF_PROVIDER_DISP_ERROR = -1,
27 +
28 + /*
29 + * Answered: every hunk of the pair has been emitted through
30 + * the consumer's callback.
31 + */
32 + DIFF_PROVIDER_DISP_ANSWERED = 0,
33 +
34 + /* Not this provider's request: the walk consults the next one. */
35 + DIFF_PROVIDER_DISP_PASS,
36 +
37 + /*
38 + * The pair must not be answered from identity nor recorded:
39 + * the request is shaped by parameters the provider's
40 + * recording key cannot express, so a recorded answer would
41 + * not match this request, and this request's result must not
42 + * be recorded under that key. The walk goes on, but consults
43 + * only the computing provider, and its fall-through outcome
44 + * tells the consumer not to record.
45 + */
46 + DIFF_PROVIDER_DISP_STOP_NO_RECORD,
47 +};
48 +
49 +/*
50 + * One provider in a repository's chain (repository.h). The chain is
51 + * assembled in diff-provider.c with a fixed composition; whether a
52 + * provider applies to a request is decided by nobody but the
53 + * provider, whose consult gates itself and passes. Chain position
54 + * carries the authority resolution: an earlier provider's answer or
55 + * refusal outranks every provider after it.
56 + */
57 +struct diff_provider {
58 + /*
59 + * Consult this provider for one request. fill is NULL on a
60 + * consult-only walk; only the computing provider reads it,
61 + * and it must pass when fill is NULL.
62 + */
63 + enum diff_provider_disposition
64 + (*consult)(struct diff_provider *provider,
65 + const struct diff_provider_request *req,
66 + diff_provider_fill_fn fill, void *fill_data,
67 + xdl_emit_hunk_consume_func_t hunk_cb,
68 + void *cb_data);
69 +
70 + /*
71 + * Tear down the provider's state, or NULL when it owns none.
72 + * Runs when the owning repository is cleared; the chain frees
73 + * the provider itself afterwards.
74 + */
75 + void (*release)(struct diff_provider *provider);
76 +
77 + void *state;
78 +
79 + /*
80 + * Set on the provider that loads content and computes rather
81 + * than answering from the request's identity. It alone is
82 + * still consulted after a stop-no-record: an identity answer
83 + * may no longer be served, but the computation must still
84 + * run.
85 + */
86 + unsigned computes:1;
87 +
88 + struct diff_provider *next;
89 +};
90 +
91 +/*
92 + * Incremental well-formedness check for a provider-supplied hunk
93 + * sequence, shared by every provider. Each coordinate, and each
94 + * hunk's end (its start plus count), must fit int32 (a consumer may
95 + * truncate to int, and a provider may serialize as such); hunks must
96 + * be in order and must not overlap; and the unchanged run between
97 + * hunks must be the same length on both sides, or a consumer that
98 + * walks the two files in lockstep desynchronizes. Every rule
99 + * constrains differences between coordinates, so the check applies
100 + * to 0-based and 1-based sequences alike.
101 + *
102 + * Feed the hunks in order to a zero-initialized struct; the first
103 + * nonzero return names the violated rule, and the whole sequence must
104 + * then be discarded unemitted.
105 + */
106 +struct diff_provider_hunks_check {
107 + int64_t prev_old_end, prev_new_end;
108 +};
109 +
110 +enum diff_provider_hunks_error {
111 + DIFF_PROVIDER_HUNKS_OK = 0,
112 + DIFF_PROVIDER_HUNKS_RANGE, /* negative or beyond int32 */
113 + DIFF_PROVIDER_HUNKS_OVERLAP, /* out of order or overlapping */
114 + DIFF_PROVIDER_HUNKS_MISALIGNED, /* unchanged runs differ in length */
115 +};
116 +
117 +enum diff_provider_hunks_error
118 +diff_provider_check_hunk(struct diff_provider_hunks_check *c,
119 + long old_start, long old_count,
120 + long new_start, long new_count);
121 +
122 +#endif /* DIFF_PROVIDER_INTERNAL_H */
diff-provider.c new
+154
@@ -0,0 +1,154 @@
1 +#include "git-compat-util.h"
2 +#include "diff-provider-internal.h"
3 +#include "repository.h"
4 +
5 +/*
6 + * The terminal provider: the builtin computation. A request that
7 + * carries a fill callback is answered by loading the pair's content
8 + * and running xdiff, so a walk that reaches it never falls through
9 + * to the consumer. On a consult-only walk it passes, and the walk's
10 + * fall-through outcome tells the consumer to compute.
11 + */
12 +static enum diff_provider_disposition
13 +builtin_consult(struct diff_provider *provider UNUSED,
14 + const struct diff_provider_request *req,
15 + diff_provider_fill_fn fill, void *fill_data,
16 + xdl_emit_hunk_consume_func_t hunk_cb, void *cb_data)
17 +{
18 + xdemitconf_t xecfg = { .hunk_func = hunk_cb };
19 + xdemitcb_t ecb = { .priv = cb_data };
20 + mmfile_t old_file, new_file;
21 +
22 + if (!fill)
23 + return DIFF_PROVIDER_DISP_PASS;
24 + if (fill(fill_data, &old_file, &new_file) < 0)
25 + return DIFF_PROVIDER_DISP_ERROR;
26 + if (xdi_diff(&old_file, &new_file, req->xpp, &xecfg, &ecb) < 0)
27 + return DIFF_PROVIDER_DISP_ERROR;
28 + return DIFF_PROVIDER_DISP_ANSWERED;
29 +}
30 +
31 +static struct diff_provider *builtin_provider_new(void)
32 +{
33 + struct diff_provider *p = xcalloc(1, sizeof(*p));
34 +
35 + p->consult = builtin_consult;
36 + p->computes = 1;
37 + return p;
38 +}
39 +
40 +/*
41 + * The repository's chain, assembled on first walk. The composition
42 + * is fixed; the builtin computation is the terminal provider, so the
43 + * chain always ends in an implementor that can answer. Nothing is
44 + * decided per repository here; each provider gates itself per
45 + * request.
46 + */
47 +static struct diff_provider *provider_chain(struct repository *r)
48 +{
49 + struct diff_provider **tail = &r->diff_providers;
50 +
51 + if (*tail)
52 + return *tail;
53 + *tail = builtin_provider_new();
54 + return r->diff_providers;
55 +}
56 +
57 +void diff_providers_clear(struct repository *r)
58 +{
59 + struct diff_provider *p = r->diff_providers;
60 +
61 + while (p) {
62 + struct diff_provider *next = p->next;
63 +
64 + if (p->release)
65 + p->release(p);
66 + free(p);
67 + p = next;
68 + }
69 + r->diff_providers = NULL;
70 +}
71 +
72 +/*
73 + * The walk behind diff_provider_emit_hunks(): consult the chain in
74 + * order and map its dispositions onto the outcome set. The first
75 + * answer ends the walk. A stop-no-record disposition
76 + * (diff-provider-internal.h) is a refusal, not a pass: the provider
77 + * does not answer, but rules the pair out of identity service and
78 + * out of recording, so from then on the walk consults only the
79 + * computing provider, and a walk that ends unanswered carries the
80 + * no-record verdict. With a fill callback the terminal provider
81 + * computes instead of passing, so an emit walk returns only
82 + * answered or error.
83 + */
84 +static enum diff_provider_outcome
85 +walk_providers(const struct diff_provider_request *req,
86 + diff_provider_fill_fn fill, void *fill_data,
87 + xdl_emit_hunk_consume_func_t hunk_cb, void *cb_data)
88 +{
89 + struct diff_provider *p;
90 + int no_record = 0;
91 +
92 + for (p = provider_chain(req->repo); p; p = p->next) {
93 + enum diff_provider_disposition disp;
94 +
95 + if (no_record && !p->computes)
96 + continue;
97 + disp = p->consult(p, req, fill, fill_data,
98 + hunk_cb, cb_data);
99 + if (disp == DIFF_PROVIDER_DISP_ERROR && !p->computes)
100 + BUG("only the computing provider may return the "
101 + "error disposition");
102 + if (p->computes && !fill && disp != DIFF_PROVIDER_DISP_PASS)
103 + BUG("the computing provider must pass on a "
104 + "fill-less walk");
105 + switch (disp) {
106 + case DIFF_PROVIDER_DISP_ANSWERED:
107 + return DIFF_PROVIDER_ANSWERED;
108 + case DIFF_PROVIDER_DISP_PASS:
109 + continue;
110 + case DIFF_PROVIDER_DISP_STOP_NO_RECORD:
111 + no_record = 1;
112 + continue;
113 + case DIFF_PROVIDER_DISP_ERROR:
114 + return DIFF_PROVIDER_ERROR;
115 + }
116 + }
117 + return no_record ? DIFF_PROVIDER_UNANSWERED_NO_RECORD :
118 + DIFF_PROVIDER_UNANSWERED;
119 +}
120 +
121 +enum diff_provider_hunks_error
122 +diff_provider_check_hunk(struct diff_provider_hunks_check *c,
123 + long old_start, long old_count,
124 + long new_start, long new_count)
125 +{
126 + if (old_start < 0 || old_count < 0 ||
127 + new_start < 0 || new_count < 0 ||
128 + old_start > INT32_MAX || old_count > INT32_MAX ||
129 + new_start > INT32_MAX || new_count > INT32_MAX ||
130 + (int64_t)old_start + old_count > INT32_MAX ||
131 + (int64_t)new_start + new_count > INT32_MAX)
132 + return DIFF_PROVIDER_HUNKS_RANGE;
133 + if (old_start < c->prev_old_end || new_start < c->prev_new_end)
134 + return DIFF_PROVIDER_HUNKS_OVERLAP;
135 + if (old_start - c->prev_old_end != new_start - c->prev_new_end)
136 + return DIFF_PROVIDER_HUNKS_MISALIGNED;
137 + /*
138 + * With each field bounded to int32 above, the int64 sums cannot
139 + * overflow even where long is 32-bit, and the range rule has
140 + * already capped them at INT32_MAX.
141 + */
142 + c->prev_old_end = (int64_t)old_start + old_count;
143 + c->prev_new_end = (int64_t)new_start + new_count;
144 + return DIFF_PROVIDER_HUNKS_OK;
145 +}
146 +
147 +enum diff_provider_outcome
148 +diff_provider_emit_hunks(const struct diff_provider_request *req,
149 + diff_provider_fill_fn fill, void *fill_data,
150 + xdl_emit_hunk_consume_func_t hunk_cb,
151 + void *cb_data)
152 +{
153 + return walk_providers(req, fill, fill_data, hunk_cb, cb_data);
154 +}
diff-provider.h new
+130
@@ -0,0 +1,130 @@
1 +#ifndef DIFF_PROVIDER_H
2 +#define DIFF_PROVIDER_H
3 +
4 +#include "xdiff-interface.h"
5 +
6 +/*
7 + * The hunk provider interface sits between naming a pair of file
8 + * versions to diff and computing their changed line ranges.
9 + * Consumers that operate on hunk coordinates route their diff
10 + * through here, so that a provider can answer for the pair before
11 + * its content is loaded.
12 + *
13 + * A hunk provider answers a consumer's request from the pair's
14 + * identity (its blob object ids) and the parameters that determine
15 + * the diff; a request no provider answers falls through to the
16 + * consumer's own computation. A provider is either authoritative for
17 + * its requests, meaning its answer may deliberately differ from the
18 + * builtin diff, or not, meaning its answer must reproduce the builtin
19 + * result exactly. The interface resolves that authority through a
20 + * provider chain owned by the repository, built on first consultation
21 + * and released by repo_clear(): chain order is the resolution, and
22 + * the builtin computation itself is the chain's terminal provider. A
23 + * consumer never names a provider; it reads the outcome below.
24 + * Every answer a provider serves from identity passes the shared
25 + * coordinate check (diff-provider-internal.h) before any consumer
26 + * sees it.
27 + */
28 +
29 +struct repository;
30 +
31 +/*
32 + * The result of a consultation: two dependent axes flattened into
33 + * their four valid points. The first axis is the state of the
34 + * response: the pair was answered, no provider answered, or (from
35 + * diff_provider_emit_hunks() alone) the attempt failed. The second
36 + * axis exists only in the unanswered state: whether what the caller
37 + * computes for this request may be recorded, the one rule the
38 + * interface imposes on an otherwise free caller. The rule travels
39 + * in the outcome because the knowledge is a provider's while the
40 + * recording is the caller's, and it shares the enum with the state,
41 + * rather than riding a separate flag, so that no meaningless
42 + * combination is representable and -Wswitch forces every consumer
43 + * that switches to place the no-record arm.
44 + *
45 + * These values describe consultations, not providers: the set does
46 + * not grow when a provider is added; a new provider maps onto these
47 + * values inside the interface, so consumer code is written once.
48 + * Each entry point returns a subrange of the set (stated at its
49 + * declaration); a switch over this enum should list every value and
50 + * omit "default:" so -Wswitch keeps it exhaustive, and a caller for
51 + * whom only one value is actionable may compare against that value
52 + * alone.
53 + */
54 +enum diff_provider_outcome {
55 + /*
56 + * Loading or diffing the pair failed. Returned only by
57 + * diff_provider_emit_hunks(), whose compute leg is the only
58 + * part of a consultation that can fail.
59 + */
60 + DIFF_PROVIDER_ERROR = -1,
61 +
62 + /*
63 + * The request is answered: every hunk of the pair has been
64 + * emitted through the callback. An authoritative provider
65 + * that finds the pair equivalent answers with no hunks at
66 + * all, so a callback that never fired is an answer, not an
67 + * accident.
68 + */
69 + DIFF_PROVIDER_ANSWERED = 0,
70 +
71 + /*
72 + * No provider answered. What happens next is the caller's
73 + * business, typically computing the diff itself; a result it
74 + * computes for this request may be recorded.
75 + */
76 + DIFF_PROVIDER_UNANSWERED,
77 +
78 + /*
79 + * No provider answered, and what the caller computes for
80 + * this request must not be recorded: either an authoritative
81 + * provider owns the pair and declined this request, or the
82 + * request is shaped by parameters outside the recording key,
83 + * the key a recorded result is later served by.
84 + */
85 + DIFF_PROVIDER_UNANSWERED_NO_RECORD,
86 +};
87 +
88 +/*
89 + * A consultation request. The interface consults providers from
90 + * these fields alone; no content is loaded before an answer.
91 + *
92 + * repo owns the provider chain the request walks. xpp carries the
93 + * parameters the diff runs with. Each provider gates itself on the
94 + * fields that concern it.
95 + */
96 +struct diff_provider_request {
97 + struct repository *repo;
98 + const xpparam_t *xpp;
99 +};
100 +
101 +/*
102 + * Load the pair's content. Called at most once per request, only
103 + * when the ranges are computed rather than provided. The buffers
104 + * borrow storage owned by the callback's owner.
105 + */
106 +typedef int (*diff_provider_fill_fn)(void *data, mmfile_t *old_file,
107 + mmfile_t *new_file);
108 +
109 +/*
110 + * Consult the providers and, when no identity answer serves the
111 + * request, load the pair's content through fill and compute its
112 + * exact changed ranges (context 0). Emits to hunk_cb either way and
113 + * returns DIFF_PROVIDER_ANSWERED, or DIFF_PROVIDER_ERROR when fill
114 + * or the diff fails. The unanswered outcomes are never returned: a
115 + * pair no provider answers is computed here instead of in the caller.
116 + */
117 +enum diff_provider_outcome
118 +diff_provider_emit_hunks(const struct diff_provider_request *req,
119 + diff_provider_fill_fn fill, void *fill_data,
120 + xdl_emit_hunk_consume_func_t hunk_cb,
121 + void *cb_data);
122 +
123 +/*
124 + * Release the repository's provider chain: stop any provider-owned
125 + * processes and free the providers. Called by repo_clear(); the
126 + * chain builds again on the next consultation.
127 + */
128 +void diff_providers_clear(struct repository *r);
129 +
130 +#endif /* DIFF_PROVIDER_H */
meson.build
+1
@@ -356,6 +356,7 @@ libgit_sources = [
356 'diff-merges.c',
357 'diff-lib.c',
358 'diff-no-index.c',
359 + 'diff-provider.c',
360 'diff.c',
361 'diffcore-break.c',
362 'diffcore-delta.c',
repository.c
+3
@@ -5,6 +5,7 @@
5 #include "odb.h"
6 #include "odb/source.h"
7 #include "config.h"
8 +#include "diff-provider.h"
9 #include "gettext.h"
10 #include "object.h"
11 #include "lockfile.h"
@@ -383,6 +384,8 @@ void repo_clear(struct repository *repo)
384 FREE_AND_NULL(repo->submodule_prefix);
385 FREE_AND_NULL(repo->ref_storage_payload);
386
387 + diff_providers_clear(repo);
388 +
389 odb_free(repo->objects);
390 repo->objects = NULL;
391
repository.h
+8
@@ -7,6 +7,7 @@
7 #include "environment.h"
8
9 struct config_set;
10 +struct diff_provider;
11 struct git_hash_algo;
12 struct index_state;
13 struct lock_file;
@@ -161,6 +162,13 @@ struct repository {
162 /* Repository's remotes and associated structures. */
163 struct remote_state *remote_state;
164
165 + /*
166 + * The repository's diff hunk provider chain, NULL until the
167 + * first consultation builds it (diff-provider.c); repo_clear()
168 + * releases it.
169 + */
170 + struct diff_provider *diff_providers;
171 +
172 /* Repository's current hash algorithm, as serialized on disk. */
173 const struct git_hash_algo *hash_algo;
174