Re: [PATCH] send-pack: avoid sending the whole tree when pushing from a shallow clone
Elijah Newren <[email protected]>
| Newsgroups | org.kernel.vger.git |
|---|---|
| Message-ID | <CABPp-BHWz_cugSO0EezqjHhDGok-xGuVQqWLqf=jMUvVM-Vpyw@mail.gmail.com> |
On Fri, Aug 21, 2026 at 10:36 AM Elijah Newren <[email protected]> wrote: > > On Fri, Aug 21, 2026 at 6:17 AM Patrick Steinhardt <[email protected]> wrote: > > > > On Fri, Aug 21, 2026 at 06:55:51AM +0000, Elijah Newren via GitGitGadget wrote: > > > From: Elijah Newren <[email protected]> > > > > > > When pushing from a shallow clone, even if we only have made a small > > > one-line change to a tiny file, we often push the entire toplevel tree > > > of files. For large repositories, this could be gigabytes instead of > > > kilobytes. > > > > Oh yeah, that issue. It's a common foot gun indeed, and the common > > advice here is to never clone with "--depth=1", but always with > > "--depth=2" so that there is at least one non-grafted commit available > > on the client so that they can indeed perform proper negotiation with a > > server. But over the years I had to explain this again and again, so it > > is clear that this common knowledge might only be commonly known to > > people who have spent way too much time in the Git codebase. > > I don't think --depth=2 actually helps here. What enables real > negotiation is push.negotiate, not the extra commit, and > push.negotiate works just as well at --depth=1. > > Without push.negotiate, send-pack's only negatives come from the refs > the server advertised filtered by what we actually have. In the > foot-gun scenario -- clone shallow, server advances, then push, using > depth of 2 just walks one commit further to the graft and then > re-sends the whole tree anyway. Running the four combinations (server > advanced after clone, optimization disabled) in a small test repo: > > depth=1, push.negotiate=false: Enumerating objects: 205 > depth=2, push.negotiate=false: Enumerating objects: 208 > depth=1, push.negotiate=true: Enumerating objects: 4 > depth=2, push.negotiate=true: Enumerating objects: 4 > > --depth=2 without negotiation is if anything a hair worse, while > negotiation fixes it regardless of depth (the negotiator offers the > shallow graft commit itself as a "have", and the server ACKs it). > > --depth=2 can in rare cases help, but only in the lucky/accidental > case where some advertised ref happens to point at the extra commit > you now have. I guess I should add that --depth=2 is not really "luck" for some users, but may be guaranteed by their workflow: - customers of forges - assuming those forges (make refs for merge/pull requests AND advertise those refs from receive-pack) OR (keep a branch pointing at the tip of the {pull,merge} request) - assuming those users never push directly to their main branch (instead only updating it via merge requests or pull requests) - assuming those users don't use squash merges or rebases on their merge request/pull requests, but do actual merges If all the conditions above are met, forges should have a ref pointing to the tip of the now-merged {merge,pull} request, which will never change since it was merged, and thus a --depth=2 clone will pick up such a commit and have some common history it discovers. GitHub includes refs/pull/ in receive.hiderefs, and users often delete branches upon merge, so neither half of that second condition holds for us and this wouldn't help our customers. Further, even if we did change the ref advertisement, we have a number of big repositories who don't satisfy the other conditions (e.g. some customers make heavy use of squash merges), so it still wouldn't help them.