Skip to content

fetch-pack: aggregate progress for parallel URI packs - #106

Closed
ttaylorr-oai wants to merge 14 commits into
codexfrom
tb/codex/parallel-packfile-uris
Closed

fetch-pack: aggregate progress for parallel URI packs#106
ttaylorr-oai wants to merge 14 commits into
codexfrom
tb/codex/parallel-packfile-uris

Conversation

@ttaylorr-oai

@ttaylorr-oai ttaylorr-oai commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Parallel URI-pack indexers each write their own progress to stderr. Their lines overwrite one another and leave a completion line for every pack; the reported throughput measures indexing of a downloaded file rather than the HTTP transfer.

Use Git's progress API to show one Fetching packs line with completed packs, downloaded bytes, and throughput. Count a pack after indexing and hash verification, exclude any existing prefix from resumed-download byte counts, and preserve worker diagnostics. Quiet and no-progress fetches do not request byte reports.

The new work is one commit. The earlier topic history was reviewed in #65; this comparison isolates the change from the currently enrolled source pin.

Validation

  • Developer build passes.
  • t0500-progress-display.sh: 18/18, including clearing a split line and repainting a display with an unknown total.
  • t5550-http-fetch-dumb.sh: 63/63, including exact byte counts for resumed and already-complete downloads.
  • t5702-protocol-v2.sh: 90/90, including aggregate progress, quiet/no-progress, and worker errors.
  • The local replay onto codex at 0d779fa987a6d214f24d68d36b2d22c32fe85fbc also passes its developer build and all 94 protocol-v2 tests, including URI authentication coverage.
  • A gated HTTP transfer shows byte progress before any pack completes, reports exactly 1,573,470 bytes, and passes git fsck --full. Three workers emitting 128 KiB of diagnostics each complete without a pipe deadlock.

The source topic needs integration with the newer HTTP authentication changes in codex. A local replay resolves the two overlapping files while retaining the newer authentication and error-response handling. This PR requests approval of the source topic; release-plan admission and the controller rebuild follow separately.

GitHub CI currently stops during setup because this older source topic's existing workflow references actions/github-script@v9, while repository policy requires full-SHA action pins (failed run). No GitHub build or test job ran. The release plan carries action pinning as a separate topic.

tnm-oai and others added 14 commits August 8, 2026 11:56
The --packfile mode accepts one --index-pack-arg=<arg> option per
argument passed to index-pack, but its documentation and option
dependency errors still refer to the plural --index-pack-args form.

Correct the spelling and describe the repeatable per-argument form.

Signed-off-by: Ted Nyman <tnyman@openai.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
finish_http_pack_request() passes its staging-file descriptor to
index-pack through child_process.in. start_command() takes ownership
of a supplied descriptor and closes it, even when starting the child
fails.

Do not close the descriptor again after run_command() returns.

Signed-off-by: Ted Nyman <tnyman@openai.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
A resumed pack request may already have all bytes of the remote pack.
A server can respond to the resulting Range request with HTTP 416
instead of returning an empty response.

Accept that response in each pack-download caller and let index-pack
validate the completed staging file. This can happen without concurrent
downloads when a previous attempt completed the transfer but failed
before indexing it.

Add a regression test that seeds a complete partial pack and checks that
http-fetch indexes it after the server returns HTTP 416.

Signed-off-by: Ted Nyman <tnyman@openai.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Pack requests stage downloads in a predictable partial-pack file so an
interrupted transfer can be resumed. Both packfile URI and ordinary dumb
HTTP requests use this staging path. Opening it in append mode forces
each write to the current end of the file, so concurrent responses can
append duplicate data and corrupt the pack.

Open the partial pack read-write without O_APPEND and seek once to its
current end. Each downloader then retains the offset matching the Range
it requested. Because the staging key must uniquely identify immutable
pack contents, overlapping responses write the same bytes at the same
offsets instead of extending the file with duplicate data.

Duplicate the staging descriptor for index-pack instead of reopening the
path after closing the stream. Another downloader may unlink the staging
path before indexing begins, but index-pack can still read the retained
descriptor.

Exercise resumed transfers and overlapping 200 and 206 responses, and
clarify the staging-key documentation.

Signed-off-by: Ted Nyman <tnyman@openai.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
On Windows, an open file must permit FILE_SHARE_DELETE before another
process can unlink it. MinGW's non-append O_RDWR open enables that
sharing mode only for an existing file; adding O_CREAT falls back to
_wopen(), which cannot set it.

First try opening the partial pack without O_CREAT. If it does not
exist, create it exclusively, close that descriptor, and retry through
the existing-file path. A racing creator retries after EEXIST.

This ensures that every retained descriptor permits another downloader
to unlink the staging path. Add an unlink-while-indexing test that does
not require FIFOs and can therefore run on MinGW.

Signed-off-by: Ted Nyman <tnyman@openai.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
When index-pack finds an existing keep file it reports pack rather than
keep. Accept either result from http-fetch, and only register a keep
lockfile when this fetch created it.

Read the pack/keep prefix and hash without consuming any following fsck
output, validate the reported pack hash against the advertised hash, and
exercise a packfile URI fetch with a pre-existing keep file.

Signed-off-by: Ted Nyman <tnyman@openai.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
In the following commit(s), some tests will need to distinguish between
`REF_DELTA`s and `OFS_DELTA`s to exercise a new '--no-ref-delta' option
for 'pack-objects'.

Existing tools report delta relationships, but not how their bases are
represented in the pack.

Teach 'test-tool pack-deltas' a '--list-deltas' mode. For each delta
entry, print the object ID, its REF_DELTA or OFS_DELTA type, and the
base object ID or pack offset, respectively. This lets tests inspect
pack headers without open-coding a parser.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
Some consumers of 'pack-objects' may wish to avoid packs which contain
`REF_DELTA` entries. For instance, a 'receive-pack' implementation which
retains the resulting pack without building an index of object IDs may
prefer every delta base to be discoverable from an earlier entry in the
same pack.

Teach 'pack-objects' a new `--no-ref-delta` option to avoid writing
`REF_DELTA` entries, without changing whether `OFS_DELTA` is allowed.

When used without `--delta-base-offset`, no delta representation
remains, so avoid delta search entirely. Otherwise, allow new deltas
whose bases appear earlier in the same pack.

For now, disable delta- and bitmap-reuse under `--no-ref-delta`, since
either may copy an existing `REF_DELTA` entry. This is overly
pessimistic, but simplifies the changes in this commit. The next commit
re-enables reuse in the cases which do not require `REF_DELTA`.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
The previous commit disables delta- and bitmap-reuse entirely whenever
pack-objects is given '--no-ref-delta' for the sake of simplicity. This
is overly pessimistic.

When '--delta-base-offset' is also given, delta reuse can remain
enabled. A reused delta whose base is written earlier in the output can
be encoded as an `OFS_DELTA`, even when its source copy was encoded as a
`REF_DELTA`.

Preferred bases and external thin-pack bases are different: neither
appears in the output, so deltas against either still require encoding
the object as a `REF_DELTA`, and thus cannot be reused.

Without '--delta-base-offset', delta reuse remains disabled, since no
delta representation remains.

Bitmap reuse follows a different path, since selected entries may be
copied without passing through the code which chooses a delta
representation. When given '--no-ref-delta', we must inspect candidate
objects individually, and leave `REF_DELTA` entries to the normal object
path outside of pack-reuse.

We must likewise avoid the special-case for reusing either the single or
preferred pack corresponding to the bitmap by whole `eword_t`'s at a
time.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
Add a 'no-ref-delta' receive-pack capability and teach send-pack to pass
'--no-ref-delta' to 'pack-objects' when the server advertises it.

Keep this separate from 'ofs-delta' so that a server may request that
`send-pack` omit `REF_DELTA` without also accepting `OFS_DELTA`.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
A client can rely on a server no-ref-delta promise only if it checks
every received pack. Add --no-ref-delta to reject a REF_DELTA while
parsing the pack.

OFS_DELTA remains accepted. By definition, an OFS_DELTA refers to an
earlier entry in the same pack, independently of this option.

Test file and stdin input containing REF_DELTA. Retain coverage that
accepts OFS_DELTA.

Signed-off-by: Friel <friel@openai.com>
The existing no-ref-delta capability lets a receive-pack server ask
send-pack not to emit REF_DELTA entries. Protocol v2 fetch needs the
same guarantee in the other direction before a client can treat
packfile URI responses as independent packs.

Add uploadpack.allowNoRefDelta and advertise no-ref-delta as a fetch
feature when it is enabled. When a client requests it, pass
--no-ref-delta to the inline pack-objects process. The same promise
covers every configured packfile URI; upload-pack cannot inspect those
packs, so the administrator must create or verify them accordingly.

The promise remains independent of ofs-delta. Test advertisement,
rejection of an unadvertised request, and the pack-objects argument.

Signed-off-by: Friel <friel@openai.com>
Packfile URI responses are indexed in advertised order because a later
URI pack can contain a REF_DELTA whose base is installed by an earlier
response pack. That ordering prevents independent URI packs from using
more than one indexer.

When upload-pack advertises no-ref-delta and more than one URI job is
configured, request it with packfile-uris. Pass --no-ref-delta to the
inline indexer and every URI indexer, then run URI packs concurrently.
Keep each URI indexer at one thread so the configured job count also
bounds indexer threads.

Precreate each expected keep path before starting its http-fetch child,
record newly-created paths in the existing pack-lock list, and omit the
child's --keep argument. Existing fetch cleanup then owns keeps created
by this process while pre-existing keeps remain untouched. Mark both
helper process layers for exit cleanup.

Use a small poll loop to refill a finished slot until all URI packs are
indexed. One job, one URI, or callers without pack-lock ownership retain
the serial path. A promised URI pack that contains a REF_DELTA fails
closed in index-pack.

Test the negotiated request, inline verifier, and three URI indexers.

Signed-off-by: Friel <friel@openai.com>
Each URI indexer writes its own progress to stderr. With concurrent
workers, their updates overwrite one another and leave a completion line
for every pack. The indexers also read from downloaded files, so their
throughput does not measure the HTTP transfer.

Report cumulative download bytes through http-fetch's existing stdout
pipe and render one progress display in fetch-pack. Count packs after
indexing and hash verification, and use Git's progress machinery for
bytes and throughput. Count newly received bytes, excluding any existing
prefix when a download resumes.

Suppress the URI indexers' verbose progress and drain both worker pipes
while they run. Parse complete stdout records incrementally so partial
reads cannot block the other workers. Clear the progress line before
forwarding diagnostics, and defer repainting until the diagnostic's line
ends. Quiet and no-progress fetches do not request byte reports.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants