From: Taylor Blau <ttaylorr@openai.com>
To: git@vger.kernel.org
Cc: "Junio C Hamano" <gitster@pobox.com>, "Jeff King" <peff@peff.net>,
"Patrick Steinhardt" <ps@pks.im>,
"Elijah Newren" <newren@gmail.com>,
"Jonathan Tan" <jonathantanmy@fastmail.com>,
"René Scharfe" <l.s.r@web.de>,
"Derrick Stolee" <stolee@gmail.com>,
"Ted Nyman" <tnyman@openai.com>,
"Daniel Reynaud" <dreynaud@openai.com>,
"Aaron Friel" <friel@openai.com>
Subject: [PATCH v3 0/2] negotiator: parse commits before queueing
Date: Mon, 26 Oct 2026 18:49:28 -0500 [thread overview]
Message-ID: <cover.1793058562.git.ttaylorr@openai.com> (raw)
In-Reply-To: <dc3dfa3647c42da9170bf3289f4e8a4a8a55c8e2.1792441003.git.me@ttaylorr.com>
This is a reroll of my patches to fix a pair of bugs in the default and
skipping negotiators.
This version fixes the SEEN bookkeeping issue Junio pointed out in v2,
and adds a separate fix for the same mistake in the default negotiator.
The original problem is that the skipping negotiator can queue unparsed
parents with a date of zero. Old ref tips then sort ahead of recent
ancestors, so negotiation can end before we offer a recent common
ancestor. As in v2, the fix parses commits in rev_list_push(), where
their dates are needed to order the queue, and removes the later parse
from get_rev().
But v2 set SEEN before attempting that parse. If parsing failed, another
child sharing the missing parent would look for a queue entry that did
not exist and trigger BUG("missing parent in priority queue"). Both
rev_list_push() implementations set SEEN only after parsing succeeds:
- The first patch fixes the existing bug in the default negotiator.
Marking a missing ancestor SEEN can cause mark_common() to decrement
non_common_revs for a commit that was never queued, ending
negotiation while other commits remain.
- The second patch is an updated version of the previous round which
applies the same fix to the skipping negotiator. Relative to the
earlier round, it also adds a test where two tips share a missing
parent and fetching retrieves that parent without hitting the BUG().
Thanks to Junio and Patrick for their review. The range-diff against v2
is below.
Thanks in advance for your review!
Taylor Blau (2):
negotiator/default.c: mark commits seen only after parsing
negotiator/skipping.c: parse parents before queueing
negotiator/default.c | 3 +-
negotiator/skipping.c | 9 +++-
t/t5552-skipping-fetch-negotiator.sh | 63 ++++++++++++++++++++++++++++
t/t5702-protocol-v2.sh | 31 ++++++++++++++
4 files changed, 102 insertions(+), 4 deletions(-)
Range-diff against v2:
-: ----------- > 1: 5a6af916635 negotiator/default.c: mark commits seen only after parsing
1: c1674625a65 ! 2: 7365a2ab180 negotiator/skipping.c: parse parents before queueing
@@ Commit message
then waits behind old ref tips. With enough old refs, negotiation can
end before we offer that recent common ancestor.
- Parse each unseen parent before adding it to the queue, so the queue
- uses its actual date. If parsing fails, leave the parent out of the
- queue.
+ Parse commits in rev_list_push() before adding them to the queue, so
+ the queue uses their actual dates. If parsing fails, leave the commit
+ out of the queue. Move the parsing from get_rev() to the insertion
+ point, where the date is needed to order the queue.
- Since the sole caller of `prio_queue_put()` ensures that commits are
- fully parsed before pushing them onto the queue, we can avoid a noop
- call to `repo_parse_commit()` within `get_rev()`.
+ Set SEEN only after parsing succeeds, following the default negotiator
+ after the preceding fix. push_parent() assumes that a commit marked
+ SEEN but not POPPED has an entry in the queue. Marking an unparseable
+ parent SEEN would violate that assumption and trigger a BUG() if another
+ child tried to push the same parent.
- Note that unlike the default negotiator, the skipping negotiator's
- version of `rev_list_push()` returns a pointer to an 'entry' struct. As
- a consequence, callers must be prepared to handle a NULL return value.
- The caller in `push_parent()` gains a NULL-ness check, and the remaining
- two callers continue to ignore the return value from `rev_list_push()`.
+ Unlike the default negotiator, the skipping negotiator's version of
+ rev_list_push() returns a pointer to an entry struct. Check for NULL in
+ push_parent() before using that entry. The other two callers continue
+ to ignore the return value.
+
+ Add tests for the ordering of recent ancestors relative to old ref tips
+ and for two tips sharing a missing parent. In the latter case, fetching
+ from a server that has the parent should retrieve it without triggering
+ the missing-entry BUG().
Signed-off-by: Taylor Blau <ttaylorr@openai.com>
## negotiator/skipping.c ##
-@@ negotiator/skipping.c: static struct entry *rev_list_push(struct data *data, struct commit *commit, int
+@@ negotiator/skipping.c: static int compare(const void *a_, const void *b_, void *data UNUSED)
+ static struct entry *rev_list_push(struct data *data, struct commit *commit, int mark)
+ {
struct entry *entry;
- commit->object.flags |= mark | SEEN;
-
++
+ if (repo_parse_commit(the_repository, commit))
+ return NULL;
+
+ commit->object.flags |= mark | SEEN;
+
CALLOC_ARRAY(entry, 1);
- entry->commit = commit;
- prio_queue_put(&data->rev_list, entry);
@@ negotiator/skipping.c: static void mark_common(struct data *data, struct commit *seen_commit)
* entry has the correct flags and ttl.
*
@@ t/t5552-skipping-fetch-negotiator.sh: test_expect_success 'do not send "have" wi
+ sed -n "s/.*fetch> have //p" trace | head -n 2 >actual &&
+ test_cmp expect actual
+'
++
++test_expect_success 'missing parent shared by two tips is not treated as queued' '
++ rm -rf server client trace &&
++ git init server &&
++ test_commit -C server base &&
++ parent=$(git -C server rev-parse HEAD) &&
++ test_commit -C server to_fetch &&
++
++ git init client &&
++ tree=$(git -C client mktree </dev/null) &&
++ # Write the tips directly, bypassing the parent lookup in commit-tree.
++ # Both tips name a parent that only the server has.
++ for i in 1 2
++ do
++ cat >commit <<-EOF_COMMIT &&
++ tree $tree
++ parent $parent
++ author A U Thor <author@example.com> 1000000000 +0000
++ committer C O Mitter <committer@example.com> 1000000000 +0000
++
++ tip $i
++ EOF_COMMIT
++ tip=$(git -C client hash-object -w -t commit --stdin <commit) &&
++ git -C client update-ref refs/heads/tip-$i "$tip" || return 1
++ done &&
++
++ test_config -C client fetch.negotiationalgorithm skipping &&
++ trace_fetch client "$(pwd)/server" \
++ --negotiation-tip=tip-1 --negotiation-tip=tip-2 &&
++ git -C client cat-file -e "$parent"
++'
+
test_done
base-commit: c51408889c3b372814bc20b9431c3b74d0d268eb
--
2.56.0.187.g9e9ae442333
next prev parent reply other threads:[~2026-10-26 23:49 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-19 20:17 [PATCH] negotiator/skipping.c: parse parents before queueing Taylor Blau
2026-10-21 6:49 ` Patrick Steinhardt
2026-10-21 19:02 ` Junio C Hamano
2026-10-22 20:38 ` Taylor Blau
2026-10-22 21:46 ` Junio C Hamano
2026-10-22 22:49 ` Taylor Blau
2026-10-22 21:21 ` [PATCH v2] " Taylor Blau
2026-10-22 22:34 ` Junio C Hamano
2026-10-22 22:48 ` Taylor Blau
2026-10-23 14:30 ` Junio C Hamano
2026-10-24 0:03 ` Taylor Blau
2026-10-24 2:06 ` Junio C Hamano
2026-10-26 23:49 ` Taylor Blau [this message]
2026-10-26 23:49 ` [PATCH v3 1/2] negotiator/default.c: mark commits seen only after parsing Taylor Blau
2026-10-26 23:49 ` [PATCH v3 2/2] negotiator/skipping.c: parse parents before queueing Taylor Blau
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=cover.1793058562.git.ttaylorr@openai.com \
--to=ttaylorr@openai.com \
--cc=dreynaud@openai.com \
--cc=friel@openai.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=jonathantanmy@fastmail.com \
--cc=l.s.r@web.de \
--cc=newren@gmail.com \
--cc=peff@peff.net \
--cc=ps@pks.im \
--cc=stolee@gmail.com \
--cc=tnyman@openai.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