Skip to content

Commit 9c1e657

Browse files
jonathantanmygitster
authored andcommitted
fetch: teach independent negotiation (no packfile)
Currently, the packfile negotiation step within a Git fetch cannot be done independent of sending the packfile, even though there is at least one application wherein this is useful. Therefore, make it possible for this negotiation step to be done independently. A subsequent commit will use this for one such application - push negotiation. This feature is for protocol v2 only. (An implementation for protocol v0 would require a separate implementation in the fetch, transport, and transport helper code.) In the protocol, the main hindrance towards independent negotiation is that the server can unilaterally decide to send the packfile. This is solved by a "wait-for-done" argument: the server will then wait for the client to say "done". In practice, the client will never say it; instead it will cease requests once it is satisfied. In the client, the main change lies in the transport and transport helper code. fetch_refs_via_pack() performs everything needed - protocol version and capability checks, and the negotiation itself. 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. 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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 6871d0c commit 9c1e657

File tree

11 files changed

+300
-17
lines changed

11 files changed

+300
-17
lines changed

Documentation/technical/protocol-v2.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,14 @@ explained below.
346346
client should download from all given URIs. Currently, the
347347
protocols supported are "http" and "https".
348348

349+
If the 'wait-for-done' feature is advertised, the following argument
350+
can be included in the client's request.
351+
352+
wait-for-done
353+
Indicates to the server that it should never send "ready", but
354+
should wait for the client to say "done" before sending the
355+
packfile.
356+
349357
The response of `fetch` is broken into a number of sections separated by
350358
delimiter packets (0001), with each section beginning with its section
351359
header. Most sections are sent only when the packfile is sent.

builtin/fetch.c

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ static struct string_list server_options = STRING_LIST_INIT_DUP;
8282
static struct string_list negotiation_tip = STRING_LIST_INIT_NODUP;
8383
static int fetch_write_commit_graph = -1;
8484
static int stdin_refspecs = 0;
85+
static int negotiate_only;
8586

8687
static int git_fetch_config(const char *k, const char *v, void *cb)
8788
{
@@ -202,6 +203,8 @@ static struct option builtin_fetch_options[] = {
202203
TRANSPORT_FAMILY_IPV6),
203204
OPT_STRING_LIST(0, "negotiation-tip", &negotiation_tip, N_("revision"),
204205
N_("report that we have only objects reachable from this object")),
206+
OPT_BOOL(0, "negotiate-only", &negotiate_only,
207+
N_("do not fetch a packfile; instead, print ancestors of negotiation tips")),
205208
OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
206209
OPT_BOOL(0, "auto-maintenance", &enable_auto_gc,
207210
N_("run 'maintenance --auto' after fetching")),
@@ -1986,7 +1989,29 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
19861989
}
19871990
}
19881991

1989-
if (remote) {
1992+
if (negotiate_only) {
1993+
struct oidset acked_commits = OIDSET_INIT;
1994+
struct oidset_iter iter;
1995+
const struct object_id *oid;
1996+
1997+
if (!remote)
1998+
die(_("must supply remote when using --negotiate-only"));
1999+
gtransport = prepare_transport(remote, 1);
2000+
if (gtransport->smart_options) {
2001+
gtransport->smart_options->acked_commits = &acked_commits;
2002+
} else {
2003+
warning(_("Protocol does not support --negotiate-only, exiting."));
2004+
return 1;
2005+
}
2006+
if (server_options.nr)
2007+
gtransport->server_options = &server_options;
2008+
result = transport_fetch_refs(gtransport, NULL);
2009+
2010+
oidset_iter_init(&acked_commits, &iter);
2011+
while ((oid = oidset_iter_next(&iter)))
2012+
printf("%s\n", oid_to_hex(oid));
2013+
oidset_clear(&acked_commits);
2014+
} else if (remote) {
19902015
if (filter_options.choice || has_promisor_remote())
19912016
fetch_one_setup_partial(remote);
19922017
result = fetch_one(remote, argc, argv, prune_tags_ok, stdin_refspecs);

fetch-pack.c

Lines changed: 107 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#include "fetch-negotiator.h"
2424
#include "fsck.h"
2525
#include "shallow.h"
26+
#include "commit-reach.h"
27+
#include "commit-graph.h"
2628

2729
static int transfer_unpack_limit = -1;
2830
static int fetch_unpack_limit = -1;
@@ -45,6 +47,8 @@ static struct string_list uri_protocols = STRING_LIST_INIT_DUP;
4547
/* Remember to update object flag allocation in object.h */
4648
#define COMPLETE (1U << 0)
4749
#define ALTERNATE (1U << 1)
50+
#define COMMON (1U << 6)
51+
#define REACH_SCRATCH (1U << 7)
4852

4953
/*
5054
* After sending this many "have"s if we do not get any new ACK , we
@@ -1523,10 +1527,10 @@ enum fetch_state {
15231527
FETCH_DONE,
15241528
};
15251529

1526-
static void do_check_stateless_delimiter(const struct fetch_pack_args *args,
1530+
static void do_check_stateless_delimiter(int stateless_rpc,
15271531
struct packet_reader *reader)
15281532
{
1529-
check_stateless_delimiter(args->stateless_rpc, reader,
1533+
check_stateless_delimiter(stateless_rpc, reader,
15301534
_("git fetch-pack: expected response end packet"));
15311535
}
15321536

@@ -1622,7 +1626,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
16221626
*/
16231627
state = FETCH_GET_PACK;
16241628
} else {
1625-
do_check_stateless_delimiter(args, &reader);
1629+
do_check_stateless_delimiter(args->stateless_rpc, &reader);
16261630
state = FETCH_SEND_REQUEST;
16271631
}
16281632
break;
@@ -1645,7 +1649,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
16451649
packfile_uris.nr ? &index_pack_args : NULL,
16461650
sought, nr_sought, &fsck_options.gitmodules_found))
16471651
die(_("git fetch-pack: fetch failed."));
1648-
do_check_stateless_delimiter(args, &reader);
1652+
do_check_stateless_delimiter(args->stateless_rpc, &reader);
16491653

16501654
state = FETCH_DONE;
16511655
break;
@@ -1962,6 +1966,105 @@ struct ref *fetch_pack(struct fetch_pack_args *args,
19621966
return ref_cpy;
19631967
}
19641968

1969+
static int add_to_object_array(const struct object_id *oid, void *data)
1970+
{
1971+
struct object_array *a = data;
1972+
1973+
add_object_array(lookup_object(the_repository, oid), "", a);
1974+
return 0;
1975+
}
1976+
1977+
static void clear_common_flag(struct oidset *s)
1978+
{
1979+
struct oidset_iter iter;
1980+
const struct object_id *oid;
1981+
oidset_iter_init(s, &iter);
1982+
1983+
while ((oid = oidset_iter_next(&iter))) {
1984+
struct object *obj = lookup_object(the_repository, oid);
1985+
obj->flags &= ~COMMON;
1986+
}
1987+
}
1988+
1989+
void negotiate_using_fetch(const struct oid_array *negotiation_tips,
1990+
const struct string_list *server_options,
1991+
int stateless_rpc,
1992+
int fd[],
1993+
struct oidset *acked_commits)
1994+
{
1995+
struct fetch_negotiator negotiator;
1996+
struct packet_reader reader;
1997+
struct object_array nt_object_array = OBJECT_ARRAY_INIT;
1998+
struct strbuf req_buf = STRBUF_INIT;
1999+
int haves_to_send = INITIAL_FLUSH;
2000+
int in_vain = 0;
2001+
int seen_ack = 0;
2002+
int last_iteration = 0;
2003+
timestamp_t min_generation = GENERATION_NUMBER_INFINITY;
2004+
2005+
fetch_negotiator_init(the_repository, &negotiator);
2006+
mark_tips(&negotiator, negotiation_tips);
2007+
2008+
packet_reader_init(&reader, fd[0], NULL, 0,
2009+
PACKET_READ_CHOMP_NEWLINE |
2010+
PACKET_READ_DIE_ON_ERR_PACKET);
2011+
2012+
oid_array_for_each((struct oid_array *) negotiation_tips,
2013+
add_to_object_array,
2014+
&nt_object_array);
2015+
2016+
while (!last_iteration) {
2017+
int haves_added;
2018+
struct object_id common_oid;
2019+
int received_ready = 0;
2020+
2021+
strbuf_reset(&req_buf);
2022+
write_fetch_command_and_capabilities(&req_buf, server_options);
2023+
2024+
packet_buf_write(&req_buf, "wait-for-done");
2025+
2026+
haves_added = add_haves(&negotiator, &req_buf, &haves_to_send);
2027+
in_vain += haves_added;
2028+
if (!haves_added || (seen_ack && in_vain >= MAX_IN_VAIN))
2029+
last_iteration = 1;
2030+
2031+
/* Send request */
2032+
packet_buf_flush(&req_buf);
2033+
if (write_in_full(fd[1], req_buf.buf, req_buf.len) < 0)
2034+
die_errno(_("unable to write request to remote"));
2035+
2036+
/* Process ACKs/NAKs */
2037+
process_section_header(&reader, "acknowledgments", 0);
2038+
while (process_ack(&negotiator, &reader, &common_oid,
2039+
&received_ready)) {
2040+
struct commit *commit = lookup_commit(the_repository,
2041+
&common_oid);
2042+
if (commit) {
2043+
timestamp_t generation;
2044+
2045+
parse_commit_or_die(commit);
2046+
commit->object.flags |= COMMON;
2047+
generation = commit_graph_generation(commit);
2048+
if (generation < min_generation)
2049+
min_generation = generation;
2050+
}
2051+
in_vain = 0;
2052+
seen_ack = 1;
2053+
oidset_insert(acked_commits, &common_oid);
2054+
}
2055+
if (received_ready)
2056+
die(_("unexpected 'ready' from remote"));
2057+
else
2058+
do_check_stateless_delimiter(stateless_rpc, &reader);
2059+
if (can_all_from_reach_with_flag(&nt_object_array, COMMON,
2060+
REACH_SCRATCH, 0,
2061+
min_generation))
2062+
last_iteration = 1;
2063+
}
2064+
clear_common_flag(acked_commits);
2065+
strbuf_release(&req_buf);
2066+
}
2067+
19652068
int report_unmatched_refs(struct ref **sought, int nr_sought)
19662069
{
19672070
int i, ret = 0;

fetch-pack.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "run-command.h"
66
#include "protocol.h"
77
#include "list-objects-filter-options.h"
8+
#include "oidset.h"
89

910
struct oid_array;
1011

@@ -81,6 +82,19 @@ struct ref *fetch_pack(struct fetch_pack_args *args,
8182
struct string_list *pack_lockfiles,
8283
enum protocol_version version);
8384

85+
/*
86+
* Execute the --negotiate-only mode of "git fetch", adding all known common
87+
* commits to acked_commits.
88+
*
89+
* In the capability advertisement that has happened prior to invoking this
90+
* function, the "wait-for-done" capability must be present.
91+
*/
92+
void negotiate_using_fetch(const struct oid_array *negotiation_tips,
93+
const struct string_list *server_options,
94+
int stateless_rpc,
95+
int fd[],
96+
struct oidset *acked_commits);
97+
8498
/*
8599
* Print an appropriate error message for each sought ref that wasn't
86100
* matched. Return 0 if all sought refs were matched, otherwise 1.

object.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ struct object_array {
6060
/*
6161
* object flag allocation:
6262
* revision.h: 0---------10 15 23------26
63-
* fetch-pack.c: 01
63+
* fetch-pack.c: 01 67
6464
* negotiator/default.c: 2--5
6565
* walker.c: 0-2
6666
* upload-pack.c: 4 11-----14 16-----19

t/t5701-git-serve.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ test_expect_success 'test capability advertisement' '
1616
version 2
1717
agent=git/$(git version | cut -d" " -f3)
1818
ls-refs=unborn
19-
fetch=shallow
19+
fetch=shallow wait-for-done
2020
server-option
2121
object-format=$(test_oid algo)
2222
0000

t/t5702-protocol-v2.sh

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,49 @@ test_expect_success 'deepen-relative' '
585585
test_cmp expected actual
586586
'
587587

588+
setup_negotiate_only () {
589+
SERVER="$1"
590+
URI="$2"
591+
592+
rm -rf "$SERVER" client
593+
594+
git init "$SERVER"
595+
test_commit -C "$SERVER" one
596+
test_commit -C "$SERVER" two
597+
598+
git clone "$URI" client
599+
test_commit -C client three
600+
}
601+
602+
test_expect_success 'file:// --negotiate-only' '
603+
SERVER="server" &&
604+
URI="file://$(pwd)/server" &&
605+
606+
setup_negotiate_only "$SERVER" "$URI" &&
607+
608+
git -c protocol.version=2 -C client fetch \
609+
--no-tags \
610+
--negotiate-only \
611+
--negotiation-tip=$(git -C client rev-parse HEAD) \
612+
origin >out &&
613+
COMMON=$(git -C "$SERVER" rev-parse two) &&
614+
grep "$COMMON" out
615+
'
616+
617+
test_expect_success 'file:// --negotiate-only with protocol v0' '
618+
SERVER="server" &&
619+
URI="file://$(pwd)/server" &&
620+
621+
setup_negotiate_only "$SERVER" "$URI" &&
622+
623+
test_must_fail git -c protocol.version=0 -C client fetch \
624+
--no-tags \
625+
--negotiate-only \
626+
--negotiation-tip=$(git -C client rev-parse HEAD) \
627+
origin 2>err &&
628+
test_i18ngrep "negotiate-only requires protocol v2" err
629+
'
630+
588631
# Test protocol v2 with 'http://' transport
589632
#
590633
. "$TEST_DIRECTORY"/lib-httpd.sh
@@ -1035,6 +1078,52 @@ test_expect_success 'packfile-uri with transfer.fsckobjects fails when .gitmodul
10351078
test_i18ngrep "disallowed submodule name" err
10361079
'
10371080

1081+
test_expect_success 'http:// --negotiate-only' '
1082+
SERVER="$HTTPD_DOCUMENT_ROOT_PATH/server" &&
1083+
URI="$HTTPD_URL/smart/server" &&
1084+
1085+
setup_negotiate_only "$SERVER" "$URI" &&
1086+
1087+
git -c protocol.version=2 -C client fetch \
1088+
--no-tags \
1089+
--negotiate-only \
1090+
--negotiation-tip=$(git -C client rev-parse HEAD) \
1091+
origin >out &&
1092+
COMMON=$(git -C "$SERVER" rev-parse two) &&
1093+
grep "$COMMON" out
1094+
'
1095+
1096+
test_expect_success 'http:// --negotiate-only without wait-for-done support' '
1097+
SERVER="server" &&
1098+
URI="$HTTPD_URL/one_time_perl/server" &&
1099+
1100+
setup_negotiate_only "$SERVER" "$URI" &&
1101+
1102+
echo "s/ wait-for-done/ xxxx-xxx-xxxx/" \
1103+
>"$HTTPD_ROOT_PATH/one-time-perl" &&
1104+
1105+
test_must_fail git -c protocol.version=2 -C client fetch \
1106+
--no-tags \
1107+
--negotiate-only \
1108+
--negotiation-tip=$(git -C client rev-parse HEAD) \
1109+
origin 2>err &&
1110+
test_i18ngrep "server does not support wait-for-done" err
1111+
'
1112+
1113+
test_expect_success 'http:// --negotiate-only with protocol v0' '
1114+
SERVER="$HTTPD_DOCUMENT_ROOT_PATH/server" &&
1115+
URI="$HTTPD_URL/smart/server" &&
1116+
1117+
setup_negotiate_only "$SERVER" "$URI" &&
1118+
1119+
test_must_fail git -c protocol.version=0 -C client fetch \
1120+
--no-tags \
1121+
--negotiate-only \
1122+
--negotiation-tip=$(git -C client rev-parse HEAD) \
1123+
origin 2>err &&
1124+
test_i18ngrep "negotiate-only requires protocol v2" err
1125+
'
1126+
10381127
# DO NOT add non-httpd-specific tests here, because the last part of this
10391128
# test script is only executed when httpd is available and enabled.
10401129

transport-helper.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,16 @@ static int fetch(struct transport *transport,
684684
return transport->vtable->fetch(transport, nr_heads, to_fetch);
685685
}
686686

687+
/*
688+
* If we reach here, then the server, the client, and/or the transport
689+
* helper does not support protocol v2. --negotiate-only requires
690+
* protocol v2.
691+
*/
692+
if (data->transport_options.acked_commits) {
693+
warning(_("--negotiate-only requires protocol v2"));
694+
return -1;
695+
}
696+
687697
if (!data->get_refs_list_called)
688698
get_refs_list_using_list(transport, 0);
689699

0 commit comments

Comments
 (0)