Repository navigation
Conversation
A forced push of a rebased branch can resend an unchanged blob even though the receiver has it through the advertised old tip. Add object-count tests for --force, --force-with-lease and forced refspecs. Confirm that the blob is unchanged by the rebase and record the current three-object pack: the rebased commit, its tree and the unchanged blob. Disable push bitmaps to exercise normal enumeration. Also cover local replacement refs, which must not cause objects missing from the receiver to be omitted. Assisted-by: GPT-6 Astra, GPT-6.1 Sol Signed-off-by: Marc Strapetz <marc.strapetz@syntevo.com>
A receiver-known commit can be excluded from a revision walk without becoming a boundary commit. Its tree then need not be inspected, so objects shared with the outgoing history can still be packed. Accept --inspect-have <commit> lines in --revs input and carry the selected tips through the object database pack-generation options. Compare their root trees with the outgoing tips using the existing sparse tree marking. This identifies additional receiver-known objects without walking the selected commits' histories and avoids descending into identical or receiver-only subtrees. Resolve the selected commits in pack-objects, where replacement refs are already disabled, so local replacements cannot substitute trees the receiver does not have. Keep successful bitmap enumeration on its existing path and skip the optional inspection in partial clones. Cover identical root trees, shared blobs under different roots, retained changed objects, subtree pruning, non-commit input, partial clone fallback and shared objects in earlier outgoing commits. Assisted-by: GPT-6 Astra, GPT-6.1 Sol Signed-off-by: Marc Strapetz <marc.strapetz@syntevo.com>
Rebasing a pushed branch can put its old tip outside the boundary traversed by pack-objects. Even with that tip among the negative revisions, unchanged blobs may then be sent again. For forced non-fast-forward branch updates, pass locally available advertised old tips as haves_to_inspect. Limit this to refs selected for sending; leave fast-forward and rejected updates out. Restrict the hints to branches, which name commits, and skip shallow repositories, whose thin-pack pushes already inspect negative tips with --objects-edge-aggressive. Update the rebase tests to expect two packed objects instead of three: the rebased commit and its tree. The unchanged blob is already present on the receiver. Assisted-by: GPT-6 Astra, GPT-6.1 Sol Signed-off-by: Marc Strapetz <marc.strapetz@syntevo.com>
Author
|
/preview |
|
Preview email sent as pull.2281.git.1793029243.gitgitgadget@gmail.com |
Author
|
/submit |
|
Submitted as pull.2281.git.1793029565.gitgitgadget@gmail.com To fetch this version into To fetch this version to local tag |
|
Junio C Hamano wrote on the Git mailing list (how to reply to this email): "Marc Strapetz via GitGitGadget" <gitgitgadget@gmail.com> writes:
> A common feature-branch workflow is to push a branch, rebase it onto an
> updated upstream, and force-push the rewritten commits. Large blobs added or
> changed on the feature branch may survive the rebase unchanged. Although the
> receiver already has those blobs through the old branch tip, Git can pack
> and send them again. This can turn a small history rewrite into a large
> upload.
This sounds like the exact problem push negotiation was designed to
address. |
|
Marc Strapetz wrote on the Git mailing list (how to reply to this email): On 26/10/2026 18:26, Junio C Hamano wrote:
> This sounds like the exact problem push negotiation was designed to
> address.
Thanks for pointing this out. This series aims to optimize Git's default
packing for forced non-fast-forward updates, without adding a
negotiation phase. I should have explained that scope more clearly in
the cover letter:
`pack-objects` can resend unchanged objects from an advertised old
branch tip even though `send-pack` already supplies that tip as an
exclusion. This series improves default packing for forced updates
by applying the existing sparse tree marking to selected old tips.
It targets this inefficiency without the additional negotiation
exchanges of `push.negotiate` or the broader recursive tree marking
of `--objects-edge-aggressive`. Neither is enabled by default for
ordinary pushes from non-shallow repositories. This series targets
a narrower opportunity within that default behavior.
A divergent old tip can lie outside the boundary reached by the
ordinary revision walk. Passing it to `pack-objects` as
`--inspect-have` lets us compare its root tree with the outgoing
trees, pruning identical and receiver-only subtrees. The additional
inspection is limited to forced non-fast-forward updates of
existing branches with locally available old tips. Large blobs
retained across a rebase are a particularly costly example of the
redundant transfer this avoids.
I would also add the following section at the end to explain how this
relates to the existing optimizations:
`push.negotiate` discovers additional common commits through a
separate fetch negotiation, potentially requiring multiple protocol
round trips. This can reduce the pack when ref advertisement alone
is insufficient, but adds latency even when the advertised tips
already suffice. For small pushes that latency can outweigh the
transfer savings. Even if negotiation achieves the same pack-size
reduction, improving the default packing path avoids the need to
pay that additional protocol cost.
`--objects-edge-aggressive` already considers excluded tips beyond
the ordinary walk's boundary, but recursively marks their trees.
The decision to enable it automatically only for shallow pushes was
made for performance reasons: the 2014 series [1] records a
regression from 4.785 to 23.322 seconds with 22,400 refs [2], and
keeps non-shallow repositories on `--objects-edge` for better
performance in the general case [3]. This series instead limits
inspection to the old tips of branches being forcibly updated and
uses sparse comparison to prune identical and receiver-only
subtrees.
The additional inspection requires no protocol exchange or walk of
the old tip's commit history. Ordinary fast-forward pushes keep
their existing path, including those made with `--force`. No server
or wire-protocol change is needed.
[1]
https://lore.kernel.org/git/1419462340-769147-3-git-send-email-sandals@crustytoothpaste.net/T/
[2]
https://github.com/git/git/commit/1684c1b219e02c91655ad929f752f4f864c72faf
[3]
https://github.com/git/git/commit/2dacf26d0985521c0f30e535963a45257b63ea21
|
|
User |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A common feature-branch workflow is to push a branch, rebase it onto an
updated upstream, and force-push the rewritten commits. Large blobs added
or changed on the feature branch may survive the rebase unchanged.
Although the receiver already has those blobs through the old branch
tip, Git can pack and send them again. This can turn a small history
rewrite into a large upload.
This series teaches
send-packto request inspection of the old tip'stree before packing, so more receiver-known objects can be excluded.
The additional inspection is enabled only for forced non-fast-forward
updates of existing branches with locally available old tips. Ordinary
fast-forward pushes do not enable it, including ones made with
--force.No server or wire-protocol change is needed.
The old tip is already a negative revision, but it may lie outside the
boundary visited by the ordinary revision walk. Its tree is then not
necessarily inspected. Passing it to
pack-objectsas--inspect-haveletsus compare its root tree with the outgoing trees using the existing
sparse tree marking. This excludes additional receiver-known objects
without walking the old tip's commit history, and prunes identical and
receiver-only subtrees.
Shallow pushes keep their existing aggressive edge handling. Partial
clones skip the extra tree inspection to avoid fetching missing objects,
and successful bitmap enumeration keeps its existing path. Old tips are
resolved in
pack-objects, where replacement refs are already disabled.I compared upstream
7e34d8174dcda3435d1404c9a30bef46afe6c13ewith thepatched source using trees from Chromium commit
5b05cc16e960a021389ef9b543ab38a037c38d60. A depth-twoclone supplies the objects; the measured fixture has a complete,
non-shallow history with a common base and sibling receiver/outgoing
commits.
Both builds used GCC 15.2.0 and
-g -O2 -Wallon an Intel i7-13700H underWSL2. Measurements used a warm cache, sparse enumeration, no bitmaps,
one packing thread, two warm-up pairs and 15 measured pairs with
alternating build order. Values below are upstream/patched; total wall
times are medians in milliseconds, including process startup.
In the single-file case, enumeration increases from 0.758 to 0.982 ms,
but pack preparation decreases from 1.637 to 0.458 ms. In the last
case, 506,468 regular files receive distinct small replacement blobs;
total packing time increases by about 8%, saving only 115 pack bytes.
These are isolated local
pack-objectsmeasurements, not end-to-endpushes: network transfer and receiving-server work are excluded. Where
redundant large blobs materially inflate the pack, avoiding their
transfer should further reduce end-to-end push time, especially on
slower connections; that benefit was not measured here.
Most objects in this fixture are already excluded through the common
base, so the pack-size savings are small. The widespread-change case
tests inspection overhead on Chromium's directory structure, rather
than realistic compression of a whole-project rewrite. Feedback on this
cost and the scope of the inspection would be particularly useful.
Local verification passed
t5322-pack-objects-sparse.sh(19 tests) andt5516-fetch-push.sh(132 tests). Coverage includes identical trees,shared blobs under different trees, retained changed objects, subtree
pruning, partial clones and local replacement refs.
cc: Marc Strapetz marc.strapetz@syntevo.com