[PATCH v3 5/6] send-pack: default to excluding shallow boundaries
- From
Elijah Newren via GitGitGadget <gitgitgadget@gmail.com>
- Date
- Sep 6, 2026, 07:24 UTC
- Message-ID
- <afa44c6d2262dda7d04ba243fdd47563d997561d.1788679500.git.gitgitgadget@gmail.com>
- In-Reply-To
- <pull.2208.v3.git.1788679500.gitgitgadget@gmail.com>
From: Elijah Newren <newren@gmail.com>
Sending a shallow boundary is almost always wasted work. We got the shallow boundary from somewhere, and most likely that is the server we are pushing to. If the receiver has the boundary, omitting it avoids transferring and recompressing its entire tree. If the receiver lacks both it and its history, the push is rejected either way, but omission reaches that answer without first sending the tree.
Make push.shallowExcludeBoundary default to true. This also covers cases where push negotiation is disabled, unavailable, or fails to find the boundary, so users do not need special configuration to avoid unexpectedly huge pushes.
The practical compatibility cost is the rare use of push to seed a new shallow root. That already requires receive.shallowUpdate on the server; it now also requires push.shallowExcludeBoundary=false on the client so the receiver gets the boundary snapshot.
Two other edge cases instead fail faster with the new default:
(A) A receiver has the boundary's parents but not the boundary itself.
This likely means the user is pushing to the wrong receiver, where
a quick rejection is preferable to a slow accidental success. (B) In a multi-ref push, one ref's shallow boundary can exclude objects
needed by another ref. This may reject more refs than necessary,
but retrying the refs separately avoids the problem; the next
patch advises users to do so.Neither case justifies making every ordinary shallow push send the boundary's potentially enormous tree.
Signed-off-by: Elijah Newren <newren@gmail.com> --- Documentation/config/push.adoc | 10 +++++----- send-pack.c | 2 +- t/t5538-push-shallow.sh | 10 ++++++---- 3 files changed, 12 insertions(+), 10 deletions(-)
diff --git a/Documentation/config/push.adoc b/Documentation/config/push.adoc index 0ad55965e8..a08ec04c21 100644 --- a/Documentation/config/push.adoc +++ b/Documentation/config/push.adoc @@ -147,12 +147,12 @@ This will result in only b (a and c are cleared). If the push reaches such a boundary, refuse it rather than choosing whether to send or omit it. `true`;; - Omit the boundary objects (fast). If the receiver does not have - them, the push is rejected. + (the default) Omit the boundary objects (fast). If the receiver + does not have them, the push is rejected. `false`;; - (the default) Send the boundary objects, retaining the historical - behavior. This can send the boundary's entire tree, which may be - very large. This is only needed when pushing to a receiver that + Send the boundary objects, retaining the historical behavior. + This can send the boundary's entire tree, which may be very + large. This is only needed when pushing to a receiver that accepts new shallow roots (i.e. one with `receive.shallowUpdate` enabled), which is very rare. -- diff --git a/send-pack.c b/send-pack.c index 386ea8b9a2..8a7cedf65a 100644 --- a/send-pack.c +++ b/send-pack.c @@ -70,7 +70,7 @@ static enum exclude_boundary_mode get_exclude_boundary_mode(struct repository *r const char *value; if (repo_config_get_string_tmp(r, "push.shallowexcludeboundary", &value)) - return EXCLUDE_BOUNDARY_NONE; + return EXCLUDE_BOUNDARY_YES; switch (git_parse_maybe_bool(value)) { case 1: diff --git a/t/t5538-push-shallow.sh b/t/t5538-push-shallow.sh index 67db51e60e..e52f3e50e2 100755 --- a/t/t5538-push-shallow.sh +++ b/t/t5538-push-shallow.sh @@ -64,7 +64,8 @@ EOF test_expect_success 'push from shallow clone, with grafted roots' ' ( cd shallow2 && - test_must_fail git push ../.git +main:refs/remotes/shallow2/main 2>err && + test_must_fail git -c push.shallowExcludeBoundary=false \ + push ../.git +main:refs/remotes/shallow2/main 2>err && test_grep "shallow2/main.*shallow update not allowed" err ) && test_must_fail git rev-parse shallow2/main && @@ -75,7 +76,8 @@ test_expect_success 'add new shallow root with receive.updateshallow on' ' test_config receive.shallowupdate true && ( cd shallow2 && - git push ../.git +main:refs/remotes/shallow2/main + git -c push.shallowExcludeBoundary=false \ + push ../.git +main:refs/remotes/shallow2/main ) && git log --format=%s shallow2/main >actual && git fsck && @@ -90,7 +92,8 @@ test_expect_success 'push from shallow to shallow' ' ( cd shallow && git --git-dir=../shallow2/.git config receive.shallowupdate true && - git push ../shallow2/.git +main:refs/remotes/shallow/main && + git -c push.shallowExcludeBoundary=false \ + push ../shallow2/.git +main:refs/remotes/shallow/main && git --git-dir=../shallow2/.git config receive.shallowupdate false ) && ( @@ -227,7 +230,6 @@ test_expect_success 'shallow boundary exclusion avoids sending the full tree' ' git -C adv-client checkout -b topic && test_commit --no-tag -C adv-client new && GIT_PROGRESS_DELAY=0 git -C adv-client \ - -c push.shallowExcludeBoundary=true \ push --progress origin topic 2>err && # Only the new commit, its tree, and the new blob are sent; sending
-- gitgitgadget