Thanks for the review, Junio and Stolee. The first patch in version 1 has been merged to master, I believe, so here are the remaining 5 patches. As Stolee pointed out, some tests in version 1 fail when GIT_TEST_PROTOCOL_VERSION is set to 0. I have corrected that, but there is one test that still fails in t5601. That seems to also fail in "master". I'll see if I can fix it and send a separate patch for that. Jonathan Tan (5): fetch-pack: refactor process_acks() fetch-pack: refactor add_haves() fetch-pack: refactor command and capability write fetch: teach independent negotiation (no packfile) send-pack: support push negotiation Documentation/config/push.txt | 7 + Documentation/technical/protocol-v2.txt | 8 + builtin/fetch.c | 27 ++- fetch-pack.c | 246 ++++++++++++++++-------- fetch-pack.h | 14 ++ object.h | 2 +- send-pack.c | 61 +++++- t/t5516-fetch-push.sh | 35 ++++ t/t5701-git-serve.sh | 2 +- t/t5702-protocol-v2.sh | 89 +++++++++ transport-helper.c | 10 + transport.c | 30 ++- transport.h | 6 + upload-pack.c | 18 +- 14 files changed, 455 insertions(+), 100 deletions(-) Range-diff against v1: -: ---------- > 1: 8102570374 fetch-pack: refactor process_acks() -: ---------- > 2: 57c3451b2e fetch-pack: refactor add_haves() -: ---------- > 3: 6871d0cec6 fetch-pack: refactor command and capability write 1: 3ebe4ada28 ! 4: 1de34a6dce fetch: teach independent negotiation (no packfile) @@ Commit message There are 2 code paths that do not go through fetch_refs_via_pack() that needed to be individually excluded: the bundle transport (excluded through requiring smart_options, which the bundle transport doesn't - support) and transport helpers that do not support takeover. - Fortunately, none of these support protocol v2. + support) and transport helpers that do not support takeover. If or when + we support independent negotiation for protocol v0, we will need to + modify these 2 code paths to support it. But for now, report failure if + independent negotiation is requested in these cases. Signed-off-by: Jonathan Tan <jonathantanmy@xxxxxxxxxx> Signed-off-by: Junio C Hamano <gitster@xxxxxxxxx> @@ builtin/fetch.c: int cmd_fetch(int argc, const char **argv, const char *prefix) + const struct object_id *oid; + + if (!remote) -+ die(_("Must supply remote when using --negotiate-only")); ++ die(_("must supply remote when using --negotiate-only")); + gtransport = prepare_transport(remote, 1); + if (gtransport->smart_options) { + gtransport->smart_options->acked_commits = &acked_commits; @@ fetch-pack.c #include "fsck.h" #include "shallow.h" +#include "commit-reach.h" ++#include "commit-graph.h" static int transfer_unpack_limit = -1; static int fetch_unpack_limit = -1; @@ fetch-pack.c: struct ref *fetch_pack(struct fetch_pack_args *args, +{ + struct object_array *a = data; + -+ add_object_array(parse_object(the_repository, oid), "", a); ++ add_object_array(lookup_object(the_repository, oid), "", a); + return 0; +} + ++static void clear_common_flag(struct oidset *s) ++{ ++ struct oidset_iter iter; ++ const struct object_id *oid; ++ oidset_iter_init(s, &iter); ++ ++ while ((oid = oidset_iter_next(&iter))) { ++ struct object *obj = lookup_object(the_repository, oid); ++ obj->flags &= ~COMMON; ++ } ++} ++ +void negotiate_using_fetch(const struct oid_array *negotiation_tips, + const struct string_list *server_options, + int stateless_rpc, @@ fetch-pack.c: struct ref *fetch_pack(struct fetch_pack_args *args, + int in_vain = 0; + int seen_ack = 0; + int last_iteration = 0; ++ timestamp_t min_generation = GENERATION_NUMBER_INFINITY; + + fetch_negotiator_init(the_repository, &negotiator); + mark_tips(&negotiator, negotiation_tips); @@ fetch-pack.c: struct ref *fetch_pack(struct fetch_pack_args *args, + &received_ready)) { + struct commit *commit = lookup_commit(the_repository, + &common_oid); -+ if (commit) ++ if (commit) { ++ timestamp_t generation; ++ ++ parse_commit_or_die(commit); + commit->object.flags |= COMMON; ++ generation = commit_graph_generation(commit); ++ if (generation < min_generation) ++ min_generation = generation; ++ } + in_vain = 0; + seen_ack = 1; + oidset_insert(acked_commits, &common_oid); @@ fetch-pack.c: struct ref *fetch_pack(struct fetch_pack_args *args, + do_check_stateless_delimiter(stateless_rpc, &reader); + if (can_all_from_reach_with_flag(&nt_object_array, COMMON, + REACH_SCRATCH, 0, -+ GENERATION_NUMBER_ZERO)) ++ min_generation)) + last_iteration = 1; + } ++ clear_common_flag(acked_commits); + strbuf_release(&req_buf); +} + @@ fetch-pack.h: struct ref *fetch_pack(struct fetch_pack_args *args, +/* + * Execute the --negotiate-only mode of "git fetch", adding all known common + * commits to acked_commits. ++ * ++ * In the capability advertisement that has happened prior to invoking this ++ * function, the "wait-for-done" capability must be present. + */ +void negotiate_using_fetch(const struct oid_array *negotiation_tips, + const struct string_list *server_options, @@ t/t5702-protocol-v2.sh: test_expect_success 'deepen-relative' ' + + setup_negotiate_only "$SERVER" "$URI" && + -+ git -C client fetch \ ++ git -c protocol.version=2 -C client fetch \ + --no-tags \ + --negotiate-only \ + --negotiation-tip=$(git -C client rev-parse HEAD) \ @@ t/t5702-protocol-v2.sh: test_expect_success 'packfile-uri with transfer.fsckobje + + setup_negotiate_only "$SERVER" "$URI" && + -+ git -C client fetch \ ++ git -c protocol.version=2 -C client fetch \ + --no-tags \ + --negotiate-only \ + --negotiation-tip=$(git -C client rev-parse HEAD) \ @@ t/t5702-protocol-v2.sh: test_expect_success 'packfile-uri with transfer.fsckobje + echo "s/ wait-for-done/ xxxx-xxx-xxxx/" \ + >"$HTTPD_ROOT_PATH/one-time-perl" && + -+ test_must_fail git -C client fetch \ ++ test_must_fail git -c protocol.version=2 -C client fetch \ + --no-tags \ + --negotiate-only \ + --negotiation-tip=$(git -C client rev-parse HEAD) \ @@ transport.h: struct git_transport_options { struct oid_array *negotiation_tips; + + /* -+ * If set, whenever transport_fetch_refs() is called, add known common -+ * commits to this oidset instead of fetching any packfiles. ++ * If allocated, whenever transport_fetch_refs() is called, add known ++ * common commits to this oidset instead of fetching any packfiles. + */ + struct oidset *acked_commits; }; 2: bd55d6ba36 ! 5: d38a8b7d66 send-pack: support push negotiation @@ t/t5516-fetch-push.sh: test_expect_success 'fetch with pushInsteadOf (should not + git push testrepo $the_first_commit:refs/remotes/origin/first_commit && + git -C testrepo config receive.hideRefs refs/remotes/origin/first_commit && + echo now pushing without negotiation && -+ GIT_TRACE2_EVENT="$(pwd)/event" git push testrepo refs/heads/main:refs/remotes/origin/main && ++ GIT_TRACE2_EVENT="$(pwd)/event" git -c protocol.version=2 push testrepo refs/heads/main:refs/remotes/origin/main && + grep_wrote 5 event && # 2 commits, 2 trees, 1 blob + + # Same commands, but with negotiation @@ t/t5516-fetch-push.sh: test_expect_success 'fetch with pushInsteadOf (should not + mk_empty testrepo && + git push testrepo $the_first_commit:refs/remotes/origin/first_commit && + git -C testrepo config receive.hideRefs refs/remotes/origin/first_commit && -+ GIT_TRACE2_EVENT="$(pwd)/event" git -c push.negotiate=1 push testrepo refs/heads/main:refs/remotes/origin/main && ++ GIT_TRACE2_EVENT="$(pwd)/event" git -c protocol.version=2 -c push.negotiate=1 push testrepo refs/heads/main:refs/remotes/origin/main && + grep_wrote 2 event # 1 commit, 1 tree +' + @@ t/t5516-fetch-push.sh: test_expect_success 'fetch with pushInsteadOf (should not + mk_empty testrepo && + git push testrepo $the_first_commit:refs/remotes/origin/first_commit && + git -C testrepo config receive.hideRefs refs/remotes/origin/first_commit && -+ GIT_TRACE_PACKET="$(pwd)/trace" GIT_TEST_PROTOCOL_VERSION=0 GIT_TRACE2_EVENT="$(pwd)/event" \ ++ GIT_TEST_PROTOCOL_VERSION=0 GIT_TRACE2_EVENT="$(pwd)/event" \ + git -c push.negotiate=1 push testrepo refs/heads/main:refs/remotes/origin/main 2>err && + grep_wrote 5 event && # 2 commits, 2 trees, 1 blob + test_i18ngrep "push negotiation failed" err -- 2.31.1.527.g47e6f16901-goog