From: "Elijah Newren via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Patrick Steinhardt <ps@pks.im>, Elijah Newren <newren@gmail.com>,
Derrick Stolee <stolee@gmail.com>,
Elijah Newren <newren@gmail.com>,
Elijah Newren <newren@gmail.com>
Subject: [PATCH v3 6/6] send-pack: advise splitting incomplete shallow pushes
Date: Sun, 06 Sep 2026 07:25:00 +0000 [thread overview]
Message-ID: <ae821ce0784286486fe76117b90bce78610ea37f.1788679500.git.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2208.v3.git.1788679500.gitgitgadget@gmail.com>
From: Elijah Newren <newren@gmail.com>
When several refs share a pack, an omitted shallow boundary reached from
one ref can exclude an object needed by another. Pushing each ref
separately recomputes the pack and avoids that interaction.
When such a multi-ref push fails after excluding a boundary, suggest
separate pushes. Gate the message on advice.pushShallowBoundary.
Assisted-by: Claude Opus 4.8
Signed-off-by: Elijah Newren <newren@gmail.com>
---
Documentation/config/advice.adoc | 5 +++++
advice.c | 1 +
advice.h | 1 +
send-pack.c | 26 ++++++++++++++++++++++----
t/t5538-push-shallow.sh | 31 +++++++++++++++++++++++++++++++
5 files changed, 60 insertions(+), 4 deletions(-)
diff --git a/Documentation/config/advice.adoc b/Documentation/config/advice.adoc
index 81f80a9274..6bb6955246 100644
--- a/Documentation/config/advice.adoc
+++ b/Documentation/config/advice.adoc
@@ -99,6 +99,11 @@ all advice messages.
a configured remote but looks like a `<remote>/<branch>` ref,
suggesting that the remote and branch be given as separate
arguments.
+ pushShallowBoundary::
+ Shown when a push from a shallow clone is rejected because
+ the remote could not unpack the pack, hinting that a shallow
+ boundary may have omitted objects and suggesting the refs be
+ pushed one at a time.
pushUnqualifiedRefname::
Shown when linkgit:git-push[1] gives up trying to
guess based on the source and destination refs what
diff --git a/advice.c b/advice.c
index 63bf8b0c5f..3701672048 100644
--- a/advice.c
+++ b/advice.c
@@ -70,6 +70,7 @@ static struct {
[ADVICE_PUSH_NON_FF_MATCHING] = { "pushNonFFMatching" },
[ADVICE_PUSH_REF_NEEDS_UPDATE] = { "pushRefNeedsUpdate" },
[ADVICE_PUSH_REPO_LOOKS_LIKE_REF] = { "pushRepoLooksLikeRef" },
+ [ADVICE_PUSH_SHALLOW_BOUNDARY] = { "pushShallowBoundary" },
[ADVICE_PUSH_UNQUALIFIED_REF_NAME] = { "pushUnqualifiedRefName" },
[ADVICE_PUSH_UPDATE_REJECTED] = { "pushUpdateRejected" },
[ADVICE_PUSH_UPDATE_REJECTED_ALIAS] = { "pushNonFastForward" }, /* backwards compatibility */
diff --git a/advice.h b/advice.h
index 66f6cd6a77..b2e281baa5 100644
--- a/advice.h
+++ b/advice.h
@@ -37,6 +37,7 @@ enum advice_type {
ADVICE_PUSH_NON_FF_MATCHING,
ADVICE_PUSH_REF_NEEDS_UPDATE,
ADVICE_PUSH_REPO_LOOKS_LIKE_REF,
+ ADVICE_PUSH_SHALLOW_BOUNDARY,
ADVICE_PUSH_UNQUALIFIED_REF_NAME,
ADVICE_PUSH_UPDATE_REJECTED,
ADVICE_PUSH_UPDATE_REJECTED_ALIAS,
diff --git a/send-pack.c b/send-pack.c
index 8a7cedf65a..4fa17810a7 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -1,4 +1,5 @@
#include "git-compat-util.h"
+#include "advice.h"
#include "config.h"
#include "commit.h"
#include "date.h"
@@ -161,7 +162,8 @@ static int append_reachable_shallow_grafts(struct repository *r,
static int pack_objects(struct repository *r,
int fd, struct ref *refs, struct oid_array *advertised,
struct oid_array *negotiated,
- struct send_pack_args *args)
+ struct send_pack_args *args,
+ int *excluded_boundary)
{
struct odb_generate_pack_options opts = ODB_GENERATE_PACK_OPTIONS_INIT;
struct odb_pack_generator *generator;
@@ -191,7 +193,8 @@ static int pack_objects(struct repository *r,
/* Exclude reachable shallow boundaries from the pack. */
if (is_repository_shallow(r) &&
get_exclude_boundary_mode(r) == EXCLUDE_BOUNDARY_YES)
- append_reachable_shallow_grafts(r, refs, advertised,
+ *excluded_boundary = append_reachable_shallow_grafts(
+ r, refs, advertised,
negotiated, args,
&opts.haves);
@@ -607,6 +610,8 @@ int send_pack(struct repository *r,
int push_options_supported = 0;
int object_format_supported = 0;
unsigned cmds_sent = 0;
+ int excluded_boundary = 0;
+ int pack_contributing_refs = 0;
int ret;
struct async demux;
char *push_cert_nonce = NULL;
@@ -742,8 +747,10 @@ int send_pack(struct repository *r,
default:
continue;
}
- if (!ref->deletion)
+ if (!ref->deletion) {
need_pack_data = 1;
+ pack_contributing_refs++;
+ }
if (args->dry_run || !status_report)
ref->status = REF_STATUS_OK;
@@ -832,7 +839,8 @@ int send_pack(struct repository *r,
PACKET_READ_DIE_ON_ERR_PACKET);
if (need_pack_data && cmds_sent) {
- if (pack_objects(r, out, remote_refs, extra_have, &commons, args) < 0) {
+ if (pack_objects(r, out, remote_refs, extra_have, &commons, args,
+ &excluded_boundary) < 0) {
if (args->stateless_rpc)
close(out);
if (git_connection_is_socket(conn))
@@ -878,6 +886,16 @@ int send_pack(struct repository *r,
}
}
+ /*
+ * Per-ref pushes prevent one ref's boundary from excluding objects
+ * needed by another.
+ */
+ if (ret < 0 && excluded_boundary && pack_contributing_refs > 1)
+ advise_if_enabled(ADVICE_PUSH_SHALLOW_BOUNDARY,
+ _("A shallow boundary may have excluded objects needed by another ref.\n"
+ "Try pushing the refs one at a time, e.g.:\n"
+ " git push <remote> <ref>"));
+
if (ret < 0)
goto out;
diff --git a/t/t5538-push-shallow.sh b/t/t5538-push-shallow.sh
index e52f3e50e2..f2a84eb227 100755
--- a/t/t5538-push-shallow.sh
+++ b/t/t5538-push-shallow.sh
@@ -343,4 +343,35 @@ test_expect_success 'push to a shallowUpdate receiver rejects a rootless snapsho
git --git-dir=seed-receiver.git rev-parse --verify seeded
'
+# Splitting a multi-ref push recomputes the pack and avoids exclusions from
+# one ref stripping objects needed by another.
+test_expect_success 'incomplete multi-ref shallow push advises pushing refs separately' '
+ git init hint-origin &&
+ git -C hint-origin checkout -b A &&
+ test_commit -C hint-origin --no-tag has-shared sh shared &&
+ test_commit -C hint-origin --no-tag A1 &&
+ git -C hint-origin switch --orphan B &&
+ test_commit -C hint-origin --no-tag B0 &&
+ test_commit -C hint-origin --no-tag B1 &&
+
+ # Strict checking rejects the incomplete pack before connectivity.
+ git init --bare hint-receiver.git &&
+ git --git-dir=hint-receiver.git config receive.fsckObjects true &&
+ git -C hint-origin push "file://$(pwd)/hint-receiver.git" \
+ B:refs/heads/B B:refs/heads/A &&
+
+ git clone --depth=1 --no-single-branch \
+ "file://$(pwd)/hint-origin" hint-client &&
+
+ git -C hint-client checkout A &&
+ test_commit -C hint-client --no-tag cX &&
+ git -C hint-client checkout -b topic B &&
+ test_commit -C hint-client --no-tag reintroduce sh shared &&
+
+ test_must_fail git -C hint-client \
+ -c push.shallowExcludeBoundary=true \
+ push --force "file://$(pwd)/hint-receiver.git" A topic 2>err &&
+ test_grep "shallow boundary may have excluded objects" err
+'
+
test_done
--
gitgitgadget
prev parent reply other threads:[~2026-09-06 7:25 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-21 6:55 [PATCH] send-pack: avoid sending the whole tree when pushing from a shallow clone Elijah Newren via GitGitGadget
2026-08-21 13:17 ` Patrick Steinhardt
2026-08-21 17:36 ` Elijah Newren
2026-08-21 18:21 ` Elijah Newren
2026-08-24 5:30 ` Patrick Steinhardt
2026-08-25 5:00 ` Elijah Newren
2026-09-02 19:05 ` Derrick Stolee
2026-09-02 20:57 ` Elijah Newren
2026-08-25 19:06 ` [PATCH v2] " Elijah Newren via GitGitGadget
2026-09-02 18:23 ` Derrick Stolee
2026-09-03 9:22 ` Elijah Newren
2026-09-06 7:24 ` [PATCH v3 0/6] " Elijah Newren via GitGitGadget
2026-09-06 7:24 ` [PATCH v3 1/6] unpack-objects: distinguish missing objects from type mismatches Elijah Newren via GitGitGadget
2026-09-06 7:24 ` [PATCH v3 2/6] receive-pack: avoid repeating connectivity errors Elijah Newren via GitGitGadget
2026-09-06 7:24 ` [PATCH v3 3/6] shallow: reject missing boundaries without disconnecting Elijah Newren via GitGitGadget
2026-09-06 7:24 ` [PATCH v3 4/6] send-pack: optionally omit shallow boundaries Elijah Newren via GitGitGadget
2026-09-06 7:24 ` [PATCH v3 5/6] send-pack: default to excluding " Elijah Newren via GitGitGadget
2026-09-06 7:25 ` Elijah Newren via GitGitGadget [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=ae821ce0784286486fe76117b90bce78610ea37f.1788679500.git.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=git@vger.kernel.org \
--cc=newren@gmail.com \
--cc=ps@pks.im \
--cc=stolee@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox