git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v3 3/6] shallow: reject missing boundaries without disconnecting

From
Elijah Newren via GitGitGadget <gitgitgadget@gmail.com>
Date
Sep 6, 2026, 07:24 UTC
Message-ID
<fc21ecf8327722ed02b656a85e76b4a60371597b.1788679500.git.gitgitgadget@gmail.com>
In-Reply-To
<pull.2208.v3.git.1788679500.gitgitgadget@gmail.com>
From: Elijah Newren <newren@gmail.com>

An incomplete shallow push can refer to a boundary commit the receiver does not have. remove_nonexistent_theirs_shallow() drops that graft, so paint_down() does not recognize it as a boundary and dies when parsing the missing commit. The client then sees only that the remote hung up.

Treat an absent commit as the end of that traversal path rather than aborting receive-pack. This lets paint_down() process the remaining commits, after which the connectivity check rejects each affected ref with "missing necessary objects". A present commit that cannot be parsed still indicates corruption and remains fatal.

Assisted-by: Claude Opus 4.8 & GPT-5.6 Sol
Signed-off-by: Elijah Newren <newren@gmail.com>
---
 shallow.c               | 16 +++++++++++---
 t/t5538-push-shallow.sh | 46 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 59 insertions(+), 3 deletions(-)
diff --git a/shallow.c b/shallow.c
index 8e244a5669..c6f7437022 100644
--- a/shallow.c
+++ b/shallow.c
@@ -659,9 +659,19 @@ static void paint_down(struct paint_info *info, const struct object_id *oid,
 		if (c->object.flags & BOTTOM)
 			continue;
 
-		if (repo_parse_commit(the_repository, c))
-			die("unable to parse commit %s",
-			    oid_to_hex(&c->object.oid));
+		if (repo_parse_commit_gently(the_repository, c, 1)) {
+			/*
+			 * remove_nonexistent_theirs_shallow() may have
+			 * dropped a missing boundary, leaving it unmarked
+			 * as BOTTOM. Let the connectivity check reject a
+			 * missing commit, but still die on a corrupt one.
+			 */
+			if (odb_has_object(the_repository->objects,
+					   &c->object.oid, 0))
+				die("unable to parse commit %s",
+				    oid_to_hex(&c->object.oid));
+			continue;
+		}
 
 		for (p = c->parents; p; p = p->next) {
 			if (p->item->object.flags & SEEN)
diff --git a/t/t5538-push-shallow.sh b/t/t5538-push-shallow.sh
index afab456b32..10ca7833d8 100755
--- a/t/t5538-push-shallow.sh
+++ b/t/t5538-push-shallow.sh
@@ -164,4 +164,50 @@ test_expect_success 'push new commit from shallow clone has good deltas' '
 	test_region pack-objects path-walk config-push.txt
 '
 
+test_expect_success 'incomplete shallow push rejects without disconnecting' '
+	git init raw-origin &&
+	git -C raw-origin checkout -b A &&
+	test_commit -C raw-origin --no-tag has-shared sh shared &&
+	test_commit -C raw-origin --no-tag A1 &&
+	A1=$(git -C raw-origin rev-parse HEAD) &&
+	git -C raw-origin switch --orphan B &&
+	test_commit -C raw-origin --no-tag B0 &&
+	test_commit -C raw-origin --no-tag B1 &&
+	B1=$(git -C raw-origin rev-parse HEAD) &&
+
+	git init --bare raw-receiver.git &&
+	git -C raw-receiver.git config receive.fsckObjects false &&
+	git -C raw-origin push ../raw-receiver.git \
+		B:refs/heads/B B:refs/heads/A &&
+
+	git -C raw-origin checkout A &&
+	test_commit -C raw-origin --no-tag cX &&
+	cX=$(git -C raw-origin rev-parse HEAD) &&
+	git -C raw-origin checkout -b topic B &&
+	test_commit -C raw-origin --no-tag reintroduce sh shared &&
+	topic=$(git -C raw-origin rev-parse HEAD) &&
+
+	# Declare A1 and B1 as shallow, but omit them and their objects from
+	# the pack. This mimics an incomplete shallow push without relying on
+	# send-pack to create one.
+	{
+		printf "shallow %s\nshallow %s\n" "$A1" "$B1" |
+		packetize &&
+		printf "%s %s refs/heads/A\0report-status object-format=%s\n" \
+			"$B1" "$cX" "$(test_oid algo)" |
+		packetize_raw &&
+		printf "%s %s refs/heads/topic\n" "$ZERO_OID" "$topic" |
+		packetize &&
+		printf 0000 &&
+		printf "%s\n%s\n^%s\n^%s\n" "$cX" "$topic" "$A1" "$B1" |
+		git -C raw-origin pack-objects --stdout --revs
+	} >input &&
+
+	git receive-pack raw-receiver.git <input >out 2>err &&
+	depacketize <out >out.raw &&
+	test_grep "ng refs/heads/A missing necessary objects" out.raw &&
+	test_grep "ng refs/heads/topic missing necessary objects" out.raw &&
+	test_grep ! "unable to parse commit" err
+'
+
 test_done
-- 
gitgitgadget
Previous: Elijah Newren via GitGitGadgetNext: Elijah Newren via GitGitGadget
Message 15 of 18 in “send-pack: avoid sending the whole tree when pushing from a shallow clone”
  1. send-pack: avoid sending the whole tree when pushing from a shallow cloneElijah Newren via GitGitGadget, Aug 21, 2026
  2. Patrick SteinhardtAug 21, 2026
  3. Elijah NewrenAug 21, 2026
  4. Elijah NewrenAug 21, 2026
  5. Patrick SteinhardtAug 24, 2026
  6. Elijah NewrenAug 25, 2026
  7. Derrick StoleeSep 2, 2026
  8. Elijah NewrenSep 2, 2026
  9. send-pack: avoid sending the whole tree when pushing from a shallow cloneElijah Newren via GitGitGadget, Aug 25, 2026
  10. Derrick StoleeSep 2, 2026
  11. Elijah NewrenSep 3, 2026
  12. 0/6 send-pack: avoid sending the whole tree when pushing from a shallow cloneElijah Newren via GitGitGadget, Sep 6, 2026
  13. 1/6 unpack-objects: distinguish missing objects from type mismatchesElijah Newren via GitGitGadget, Sep 6, 2026
  14. 2/6 receive-pack: avoid repeating connectivity errorsElijah Newren via GitGitGadget, Sep 6, 2026
  15. 3/6 shallow: reject missing boundaries without disconnectingElijah Newren via GitGitGadget, Sep 6, 2026
  16. 4/6 send-pack: optionally omit shallow boundariesElijah Newren via GitGitGadget, Sep 6, 2026
  17. 5/6 send-pack: default to excluding shallow boundariesElijah Newren via GitGitGadget, Sep 6, 2026
  18. 6/6 send-pack: advise splitting incomplete shallow pushesElijah Newren via GitGitGadget, Sep 6, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.