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.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.