Skip to content

Commit ad90800

Browse files
committed
rn-135: add MIDX version 2 article
1 parent 318b737 commit ad90800

1 file changed

Lines changed: 253 additions & 2 deletions

File tree

rev_news/drafts/edition-135.md

Lines changed: 253 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,260 @@ This edition covers what happened during the months of April and May 2026.
4949
### Reviews
5050
-->
5151

52-
<!---
5352
### Support
54-
-->
53+
54+
+ [MIDX woes, was Re: [ANNOUNCE] Git v2.54.0-rc2](https://lore.kernel.org/git/8c1def10-9039-aecd-4ce4-fb4676b47e9b@gmx.de)
55+
56+
Shortly after the `v2.54.0-rc2` release candidate was announced,
57+
Johannes Schindelin, the Git for Windows maintainer who is usually
58+
called Dscho, wrote a follow-up to the announcement, retitled "MIDX
59+
woes", to report an unpleasant discovery: fetching with
60+
`v2.54.0-rc2` into an existing repository made that repository
61+
unusable for Git `v2.53.0`, which would now bail out with:
62+
63+
```
64+
fatal: multi-pack-index version 2 not recognized
65+
```
66+
67+
Dscho asked whether `v2.54.0-rc2` was forcefully writing a brand-new
68+
MIDX version that the immediately preceding release could not even
69+
read. He pointed out that, if so, this would cause "substantial
70+
problems" in setups where libgit2 or JGit is used interchangeably
71+
with Git, when users need to downgrade Git, or when several Git
72+
versions live side by side on the same system, for instance through
73+
GitHub Desktop, which bundles its own copy of Git.
74+
75+
The multi-pack-index (MIDX) is an on-disk file at
76+
`.git/objects/pack/multi-pack-index` (and possibly chained files)
77+
that indexes objects across several pack files at once. It is meant
78+
to be a purely optional acceleration layer: when present and
79+
readable, lookups can avoid scanning each pack's own `.idx` index
80+
file; when absent or unreadable, Git is supposed to fall back to the
81+
underlying `.idx` files. Several high-impact features (auto
82+
maintenance, `git multi-pack-index`, reachability bitmaps, geometric
83+
repack, etc.) build on top of it, and modern Git distributions
84+
write or update it as part of routine operations, including the
85+
maintenance step that runs after a `git fetch`.
86+
87+
The "version 2 not recognized" error came from `b2ec8e90c2` (`midx:
88+
do not require packs to be sorted in lexicographic order`,
89+
2026-02-24). That commit relaxed an internal ordering constraint
90+
and, because the relaxation makes the on-disk file unreadable by
91+
other tools that still expect the older invariant, guarded the new
92+
behaviour behind a bump in the MIDX on-disk format version (from v1
93+
to v2). The commit message explicitly justified the bump by claiming
94+
that "older versions of Git know how to gracefully degrade and
95+
ignore any MIDX(s) they consider corrupt". As the discussion would
96+
reveal, this assumption turned out to be too optimistic.
97+
98+
Junio Hamano, the Git maintainer, picked up the thread and pointed
99+
directly at `b2ec8e90c2` as the likely culprit. Reading the commit
100+
message back to itself, he observed that the format-version bump
101+
"seems to be doing more harm to 'older versions of Git' that 'know
102+
how to gracefully degrade' by not allowing them to degrade", and he
103+
asked Taylor Blau (the author of the MIDX v2 work and the area's
104+
principal maintainer) whether the release notes should at least
105+
carry recovery instructions, such as `rm -f .git/objects/pack/*.midx`.
106+
107+
Jeff King, alias Peff, replied within hours with a deeper
108+
diagnosis. The MIDX *should* be optional, he wrote. If loading the
109+
file returns an error, callers should silently fall back to the
110+
regular `.idx` files, but that property is not actually held by the
111+
load path, which contains a few `die()` calls instead. He
112+
demonstrated by applying a small patch on top of v2.53.0 that
113+
replaces the two relevant `die()` calls in
114+
`load_multi_pack_index_one()` (one for the signature mismatch, one
115+
for the unknown version) with `error()` plus a `goto cleanup_fail`,
116+
producing the desired behaviour: the user sees `version 2 not
117+
recognized` printed once and then everything works anyway. "But of
118+
course we can't go back in time now to fix it (and earlier
119+
versions)", he noted.
120+
121+
Peff also surveyed the third-party implementations Dscho had worried
122+
about:
123+
124+
- JGit, on inspection of its source, throws an exception that is
125+
apparently caught and handled correctly (he verified with the
126+
`jgit` CLI on hand).
127+
- libgit2 returns from a helper called `midx_error()` when the
128+
signature or version do not match. Reading the code, Peff
129+
believed it would quietly fall back to the underlying packs.
130+
131+
His conclusion: "it really is just our old versions that are the
132+
problem".
133+
134+
He then asked the natural follow-up question: how hard would it be
135+
to revert the default written MIDX version back to v1? In a second
136+
message a few minutes later he answered himself with a
137+
near-one-liner in `midx-write.c` changing the default initializer of
138+
`write_midx_context.version` from `MIDX_VERSION_V2` to
139+
`MIDX_VERSION_V1`, plus minor adjustments to the test suite: in
140+
`t/t5319-multi-pack-index.sh`, the expected header would once again
141+
say "header: ... 1 ..." rather than "header: ... 2 ..."; and in
142+
`t/t5335-compact-multi-pack-index.sh`, since MIDX compaction
143+
*requires* the v2 format, the test would now opt back into v2
144+
explicitly via `git config --global midx.version 2`. Peff observed
145+
that an existing `midx.version` config knob lets users opt into v2
146+
manually, and he left the strategic decision to Taylor.
147+
148+
Derrick Stolee underlined the part of Dscho's report that he
149+
considered most striking: the bad file is written automatically as
150+
part of normal maintenance after a fetch, so removing the broken
151+
MIDX by hand "will not keep the repo in a good state". The next
152+
fetch will simply regenerate it. He agreed that a graceful fallback
153+
(with a visible warning) belongs in Git too, and that the immediate
154+
fix should be to stop writing v2 by default so that a 2.53/2.54
155+
mixed deployment stops poisoning the repository at every fetch.
156+
157+
Junio, after asking Derrick to clarify the "good state" sentence (he
158+
initially read it as "the MIDX is no longer optional"), eventually
159+
agreed: defaulting back to v1 *and* leaving the more thorough
160+
graceful-degradation work for later was the right split for the
161+
remaining rc window. In a later round of the same sub-thread,
162+
Derrick clarified that what he had meant was that the deletion was
163+
not a *durable* fix on its own. The maintenance step would keep
164+
regenerating the v2 file unless the default version was also lowered
165+
(or `midx.version` set to `1`).
166+
167+
Taylor Blau then weighed in, apologetic about the "trouble here", and
168+
laid out a clean three-step plan for the project:
169+
170+
1. **Immediate (before 2.54)**: revert the default MIDX format to
171+
V1, so a 2.54.0 release does not regress the case where multiple
172+
Git versions are used against the same repository.
173+
2. **Medium term (after 2.54)**: implement the graceful-degradation
174+
idea Peff sketched in `load_multi_pack_index_one()`, so that
175+
unknown versions cause Git to ignore the MIDX instead of dying.
176+
This won't help current 2.53 and earlier users, but it would
177+
make a future flip from V1 to V2 by default truly painless from
178+
2.55 onward.
179+
3. **Long term (2.56 or later)**: make V2 the default once enough
180+
versions in the field can already cope with it.
181+
182+
Peff acknowledged the plan, only adding a caveat: two releases may
183+
be "not very long, especially for people who are using OS packages",
184+
e.g. people moving across Debian stable releases. But that could be
185+
sorted out later.
186+
187+
To make sure something concrete was in the rc, Junio took Peff's
188+
near-one-liner, polished the commit message, and proposed
189+
[a first version](https://lore.kernel.org/git/xmqq8qam217m.fsf_-_@gitster.g)
190+
titled "MIDX: keep the default version to MIDX v1" (later renamed
191+
"MIDX: revert the default version to v1"). The patch simply
192+
initialised `write_midx_context.version` to `MIDX_VERSION_V1`, fixed
193+
up the expected on-disk header in `t/t5319-multi-pack-index.sh`, and
194+
opted `t/t5335-compact-multi-pack-index.sh` into V2 explicitly via
195+
`git config --global midx.version 2` so the compaction tests
196+
continued to exercise the new format.
197+
198+
In parallel, Junio also floated
199+
[a second patch](https://lore.kernel.org/git/xmqqh5pa22h0.fsf@gitster.g)
200+
that would have weakened the two `die()` calls in
201+
`load_multi_pack_index_one()` to `error()` + `goto cleanup_fail`,
202+
implementing Peff's earlier suggestion. He himself was unsure about
203+
that one, though, observing that doing so during the rc period would
204+
effectively promise that the MIDX is forever an optional component,
205+
and that the error messages should at least be reworded to make
206+
clear that they mean "we are ignoring this corrupt file" rather than
207+
"this is a fatal corruption". After a follow-up exchange with Peff
208+
about how dense the rest of `load_multi_pack_index_one()` is with
209+
`die()` calls (Peff confessed he had not actually looked past the
210+
two lines he had touched, and Junio confessed he had not either
211+
until he had to reply), they agreed that the right fix is *at the
212+
caller side*. The loader function genuinely is reporting "this MIDX
213+
is broken", and it is the caller's responsibility to decide whether
214+
to continue without it. The reword-and-soften idea was put aside as
215+
"an issue for much later".
216+
217+
Peff replied to Junio's first patch with a small but elegant
218+
counter-proposal: rather than defaulting to V1 *always* (which would
219+
force users of the new `git multi-pack-index compact` feature to set
220+
`midx.version=2` manually), make `write_midx_internal()` pick V1 by
221+
default but switch to V2 automatically when the caller has set the
222+
`MIDX_WRITE_COMPACT` flag. Concretely, in
223+
[his refined version of the patch](https://lore.kernel.org/git/20260416200659.GB1887222@coredump.intra.peff.net),
224+
he removed the V2 initialiser from the `write_midx_context`
225+
declaration, and inserted the following just below, and just above
226+
the existing
227+
`repo_config_get_int(ctx.repo, "midx.version", &ctx.version)`
228+
lookup that lets a user override the choice:
229+
230+
```
231+
ctx.version = opts->flags & MIDX_WRITE_COMPACT ?
232+
MIDX_VERSION_V2 :
233+
MIDX_VERSION_V1;
234+
```
235+
236+
The companion documentation update in
237+
`Documentation/git-multi-pack-index.adoc` adds a single sentence to
238+
the `compact::` description noting that compaction "requires writing
239+
a version-2 midx that cannot be read by versions of Git prior to
240+
v2.54", and the only test fallout is in
241+
`t/t5319-multi-pack-index.sh`, where the expected header version
242+
flips back from `2` to `1`. Notably,
243+
`t/t5335-compact-multi-pack-index.sh` needs no change. Compaction
244+
continues to "just work" because the new auto-select picks V2 for
245+
it.
246+
247+
Peff also confessed there are probably some gaps in V2 testing in
248+
`t5319` left behind by this flip (the bulk of those tests now
249+
exercise V1 again), but argued that filling them in could be done
250+
post-release.
251+
252+
Junio said he had already merged the original "revert" version into
253+
his `jch` and `next` integration branches, but had not pushed `next`
254+
out for external testing yet, so he chucked the original and applied
255+
this version instead, agreeing that "compact is the only thing that
256+
needs v2" was a better workaround.
257+
258+
The only remaining nit was stylistic: Junio preferred writing the
259+
ternary as
260+
261+
```
262+
ctx.version = ((opts->flags & MIDX_WRITE_COMPACT)
263+
? MIDX_VERSION_V2
264+
: MIDX_VERSION_V1);
265+
```
266+
267+
so that the extra parentheses make the precedence of `&` vs `?:`
268+
obvious, and so that a multi-line ternary is easier to spot when `?`
269+
and `:` are aligned at the start of the line. Peff replied that he
270+
liked keeping the `?` at the end of the first line, because then it
271+
is clear from the first line alone that it is a conditional rather
272+
than a direct assignment, but said he did not strongly care and that
273+
Junio could mark it up while applying. By the time Peff fetched
274+
`next` to send that reply, Junio had already done exactly that.
275+
276+
Taylor reviewed Peff's refined patch in parallel: he acked the
277+
short- and medium-term plan ("sorry again for the mess here"),
278+
suggested a small wording tweak ("Git 2.53 and earlier" rather than
279+
"Git 2.53" in the log message), and noted that he found the
280+
"auto-select V2 only when the feature requires it" behaviour a
281+
little "magical", though "less magical and more 'do the sensible
282+
thing by default'" once you remember that anyone running compaction
283+
already knows the trade-offs. Peff agreed about the wording but
284+
noted that the patch had already been pushed to `next`. They also
285+
exchanged a short note about extending the V2-specific coverage in
286+
`t5319` going forward, which Peff suggested Taylor could pick up
287+
post-2.54.
288+
289+
The next day, Junio
290+
[announced an update to `master`](https://lore.kernel.org/git/xmqq5x5py5ql.fsf@gitster.g),
291+
containing Peff's "MIDX: revert the default version to v1", along
292+
with a batch of documentation typo and grammar fixes from Elijah
293+
Newren and a CodeQL CI bump from Dscho. He also announced that 2.54
294+
final would be tagged on Monday, April 20th, and that he would be
295+
offline for a week or two afterwards. Elijah replied to flag a
296+
separate pair of bugs (NULL pointer dereference and read past end of
297+
string in the diffstat code path) that had just come up in
298+
[a separate thread](https://lore.kernel.org/git/pull.2093.git.1776443163041.gitgitgadget@gmail.com/),
299+
in case Junio wanted to consider squeezing the fix into the release
300+
or holding it for 2.54.1.
301+
302+
The story of v2.54 thus closed with a near-miss compatibility break
303+
caught before release, fixed in a way that keeps the new
304+
infrastructure available to those who actually need it, and
305+
documented for everyone who will read the release notes later.
55306

56307
## Developer Spotlight: Matthias Aßhauer
57308

0 commit comments

Comments
 (0)