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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.