Skip to content

Commit ae81ef3

Browse files
committed
fetch: add --negotiation-include option for negotiation
Add a new --negotiation-include option to 'git fetch', which ensures that certain ref tips are always sent as 'have' lines during fetch negotiation, regardless of what the negotiation algorithm selects. This is useful when the repository has a large number of references, so the normal negotiation algorithm truncates the list. This is especially important in repositories with long parallel commit histories. For example, a repo could have a 'dev' branch for development and a 'release' branch for released versions. If the 'dev' branch isn't selected for negotiation, then it's not a big deal because there are many in-progress development branches with a shared history. However, if 'release' is not selected for negotiation, then the server may think that this is the first time the client has asked for that reference, causing a full download of its parallel commit history (and any extra data that may be unique to that branch). This is based on a real example where certain fetches would grow to 60+ GB when a release branch updated. This option is a complement to --negotiation-restrict, which reduces the negotiation ref set to a specific list. In the earlier example, using --negotiation-restrict to focus the negotiation to 'dev' and 'release' would avoid those problematic downloads, but would still not allow advertising potentially-relevant user brances. In this way, the 'include' version solves the problem I mention while allowing negotiation to pick other references opportunistically. The two options can also be combined to allow the best of both worlds. The argument may be an exact ref name or a glob pattern. Non-existent refs are silently ignored. This behavior is also updated in the ref matching logic for the related --negotiation-restrict option to match. The implementation outputs the requested objects as haves before the negotiation algorithm kicks in and performs a priority-queue walk from the tip commits. In order to avoid duplicates, we mark the requested objects as COMMON so they (and their descendants) are not output by the negotiator. The negotiator still outputs at least one have before a round is flushed, when the server could ACK to stop the negotiation. Also add --negotiation-include to 'git pull' passthrough options. Signed-off-by: Derrick Stolee <stolee@gmail.com>
1 parent d2f48b7 commit ae81ef3

8 files changed

Lines changed: 227 additions & 9 deletions

File tree

Documentation/fetch-options.adoc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,25 @@ See also the `fetch.negotiationAlgorithm` and `push.negotiate`
7373
configuration variables documented in linkgit:git-config[1], and the
7474
`--negotiate-only` option below.
7575

76+
`--negotiation-include=<revision>`::
77+
Ensure that the given ref tip is always sent as a "have" line
78+
during fetch negotiation, regardless of what the negotiation
79+
algorithm selects. This is useful to guarantee that common
80+
history reachable from specific refs is always considered, even
81+
when `--negotiation-restrict` restricts the set of tips or when
82+
the negotiation algorithm would otherwise skip them.
83+
+
84+
This option may be specified more than once; if so, each ref is sent
85+
unconditionally.
86+
+
87+
The argument may be an exact ref name (e.g. `refs/heads/release`) or a
88+
glob pattern (e.g. `refs/heads/release/{asterisk}`). The pattern syntax
89+
is the same as for `--negotiation-restrict`.
90+
+
91+
If `--negotiation-restrict` is used, the have set is first restricted by
92+
that option and then increased to include the tips specified by
93+
`--negotiation-include`.
94+
7695
`--negotiate-only`::
7796
Do not fetch anything from the server, and instead print the
7897
ancestors of the provided `--negotiation-tip=` arguments,

builtin/fetch.c

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ static struct transport *gsecondary;
9999
static struct refspec refmap = REFSPEC_INIT_FETCH;
100100
static struct string_list server_options = STRING_LIST_INIT_DUP;
101101
static struct string_list negotiation_restrict = STRING_LIST_INIT_NODUP;
102+
static struct string_list negotiation_include = STRING_LIST_INIT_NODUP;
102103

103104
struct fetch_config {
104105
enum display_format display_format;
@@ -1547,10 +1548,14 @@ static void add_negotiation_restrict_tips(struct git_transport_options *smart_op
15471548
int old_nr;
15481549
if (!has_glob_specials(s)) {
15491550
struct object_id oid;
1551+
1552+
/* Ignore missing reference. */
15501553
if (repo_get_oid(the_repository, s, &oid))
1551-
die(_("%s is not a valid object"), s);
1554+
continue;
1555+
/* Fail on missing object pointed by ref. */
15521556
if (!odb_has_object(the_repository->objects, &oid, 0))
15531557
die(_("the object %s does not exist"), s);
1558+
15541559
oid_array_append(oids, &oid);
15551560
continue;
15561561
}
@@ -1615,6 +1620,13 @@ static struct transport *prepare_transport(struct remote *remote, int deepen,
16151620
strbuf_release(&config_name);
16161621
}
16171622
}
1623+
if (negotiation_include.nr) {
1624+
if (transport->smart_options)
1625+
transport->smart_options->negotiation_include = &negotiation_include;
1626+
else
1627+
warning(_("ignoring %s because the protocol does not support it"),
1628+
"--negotiation-include");
1629+
}
16181630
return transport;
16191631
}
16201632

@@ -2582,6 +2594,8 @@ int cmd_fetch(int argc,
25822594
OPT_STRING_LIST(0, "negotiation-restrict", &negotiation_restrict, N_("revision"),
25832595
N_("report that we have only objects reachable from this object")),
25842596
OPT_ALIAS(0, "negotiation-tip", "negotiation-restrict"),
2597+
OPT_STRING_LIST(0, "negotiation-include", &negotiation_include, N_("revision"),
2598+
N_("ensure this ref is always sent as a negotiation have")),
25852599
OPT_BOOL(0, "negotiate-only", &negotiate_only,
25862600
N_("do not fetch a packfile; instead, print ancestors of negotiation tips")),
25872601
OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),

builtin/pull.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,9 @@ int cmd_pull(int argc,
10021002
OPT_PASSTHRU_ARGV(0, "negotiation-restrict", &opt_fetch, N_("revision"),
10031003
N_("report that we have only objects reachable from this object"),
10041004
0),
1005+
OPT_PASSTHRU_ARGV(0, "negotiation-include", &opt_fetch, N_("revision"),
1006+
N_("ensure this ref is always sent as a negotiation have"),
1007+
0),
10051008
OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates,
10061009
N_("check for forced-updates on all updated branches")),
10071010
OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL,

fetch-pack.c

Lines changed: 106 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "oidset.h"
2626
#include "packfile.h"
2727
#include "odb.h"
28+
#include "object-name.h"
2829
#include "path.h"
2930
#include "connected.h"
3031
#include "fetch-negotiator.h"
@@ -332,6 +333,48 @@ static void send_filter(struct fetch_pack_args *args,
332333
}
333334
}
334335

336+
static int add_oid_to_oidset(const struct reference *ref, void *cb_data)
337+
{
338+
struct oidset *set = cb_data;
339+
if (!odb_has_object(the_repository->objects, ref->oid, 0))
340+
die(_("the object %s does not exist"), oid_to_hex(ref->oid));
341+
oidset_insert(set, ref->oid);
342+
return 0;
343+
}
344+
345+
static void resolve_negotiation_include(const struct string_list *negotiation_include,
346+
struct oidset *result)
347+
{
348+
struct string_list_item *item;
349+
350+
if (!negotiation_include || !negotiation_include->nr)
351+
return;
352+
353+
for_each_string_list_item(item, negotiation_include) {
354+
if (!has_glob_specials(item->string)) {
355+
struct object_id oid;
356+
357+
/* Ignore missing reference. */
358+
if (repo_get_oid(the_repository, item->string, &oid))
359+
continue;
360+
361+
/* Fail on missing object pointed by ref. */
362+
if (!odb_has_object(the_repository->objects, &oid, 0))
363+
die(_("the object %s does not exist"),
364+
item->string);
365+
366+
oidset_insert(result, &oid);
367+
} else {
368+
struct refs_for_each_ref_options opts = {
369+
.pattern = item->string,
370+
};
371+
refs_for_each_ref_ext(
372+
get_main_ref_store(the_repository),
373+
add_oid_to_oidset, result, &opts);
374+
}
375+
}
376+
}
377+
335378
static int find_common(struct fetch_negotiator *negotiator,
336379
struct fetch_pack_args *args,
337380
int fd[2], struct object_id *result_oid,
@@ -347,6 +390,7 @@ static int find_common(struct fetch_negotiator *negotiator,
347390
struct strbuf req_buf = STRBUF_INIT;
348391
size_t state_len = 0;
349392
struct packet_reader reader;
393+
struct oidset negotiation_include_oids = OIDSET_INIT;
350394

351395
if (args->stateless_rpc && multi_ack == 1)
352396
die(_("the option '%s' requires '%s'"), "--stateless-rpc", "multi_ack_detailed");
@@ -474,6 +518,33 @@ static int find_common(struct fetch_negotiator *negotiator,
474518
trace2_region_enter("fetch-pack", "negotiation_v0_v1", the_repository);
475519
flushes = 0;
476520
retval = -1;
521+
522+
/* Send unconditional haves from --negotiation-include */
523+
resolve_negotiation_include(args->negotiation_include,
524+
&negotiation_include_oids);
525+
if (oidset_size(&negotiation_include_oids)) {
526+
struct oidset_iter iter;
527+
oidset_iter_init(&negotiation_include_oids, &iter);
528+
529+
while ((oid = oidset_iter_next(&iter))) {
530+
struct commit *commit;
531+
packet_buf_write(&req_buf, "have %s\n",
532+
oid_to_hex(oid));
533+
print_verbose(args, "have %s", oid_to_hex(oid));
534+
count++;
535+
536+
/*
537+
* If this is a commit, then mark as COMMON to
538+
* avoid the negotiator also outputting it as
539+
* a have.
540+
*/
541+
commit = lookup_commit(the_repository, oid);
542+
if (commit &&
543+
!repo_parse_commit(the_repository, commit))
544+
commit->object.flags |= COMMON;
545+
}
546+
}
547+
477548
while ((oid = negotiator->next(negotiator))) {
478549
packet_buf_write(&req_buf, "have %s\n", oid_to_hex(oid));
479550
print_verbose(args, "have %s", oid_to_hex(oid));
@@ -584,6 +655,7 @@ static int find_common(struct fetch_negotiator *negotiator,
584655
flushes++;
585656
}
586657
strbuf_release(&req_buf);
658+
oidset_clear(&negotiation_include_oids);
587659

588660
if (!got_ready || !no_done)
589661
consume_shallow_list(args, &reader);
@@ -1305,12 +1377,26 @@ static void add_common(struct strbuf *req_buf, struct oidset *common)
13051377

13061378
static int add_haves(struct fetch_negotiator *negotiator,
13071379
struct strbuf *req_buf,
1308-
int *haves_to_send)
1380+
int *haves_to_send,
1381+
struct oidset *negotiation_include_oids)
13091382
{
13101383
int haves_added = 0;
13111384
const struct object_id *oid;
13121385

1386+
/* Send unconditional haves from --negotiation-include */
1387+
if (negotiation_include_oids) {
1388+
struct oidset_iter iter;
1389+
oidset_iter_init(negotiation_include_oids, &iter);
1390+
1391+
while ((oid = oidset_iter_next(&iter)))
1392+
packet_buf_write(req_buf, "have %s\n",
1393+
oid_to_hex(oid));
1394+
}
1395+
13131396
while ((oid = negotiator->next(negotiator))) {
1397+
if (negotiation_include_oids &&
1398+
oidset_contains(negotiation_include_oids, oid))
1399+
continue;
13141400
packet_buf_write(req_buf, "have %s\n", oid_to_hex(oid));
13151401
if (++haves_added >= *haves_to_send)
13161402
break;
@@ -1358,7 +1444,8 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
13581444
struct fetch_pack_args *args,
13591445
const struct ref *wants, struct oidset *common,
13601446
int *haves_to_send, int *in_vain,
1361-
int sideband_all, int seen_ack)
1447+
int sideband_all, int seen_ack,
1448+
struct oidset *negotiation_include_oids)
13621449
{
13631450
int haves_added;
13641451
int done_sent = 0;
@@ -1413,7 +1500,8 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
14131500
/* Add all of the common commits we've found in previous rounds */
14141501
add_common(&req_buf, common);
14151502

1416-
haves_added = add_haves(negotiator, &req_buf, haves_to_send);
1503+
haves_added = add_haves(negotiator, &req_buf, haves_to_send,
1504+
negotiation_include_oids);
14171505
*in_vain += haves_added;
14181506
trace2_data_intmax("negotiation_v2", the_repository, "haves_added", haves_added);
14191507
trace2_data_intmax("negotiation_v2", the_repository, "in_vain", *in_vain);
@@ -1657,6 +1745,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
16571745
struct ref *ref = copy_ref_list(orig_ref);
16581746
enum fetch_state state = FETCH_CHECK_LOCAL;
16591747
struct oidset common = OIDSET_INIT;
1748+
struct oidset negotiation_include_oids = OIDSET_INIT;
16601749
struct packet_reader reader;
16611750
int in_vain = 0, negotiation_started = 0;
16621751
int negotiation_round = 0;
@@ -1729,6 +1818,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
17291818
state = FETCH_SEND_REQUEST;
17301819

17311820
mark_tips(negotiator, args->negotiation_restrict_tips);
1821+
resolve_negotiation_include(args->negotiation_include,
1822+
&negotiation_include_oids);
17321823
for_each_cached_alternate(negotiator,
17331824
insert_one_alternate_object);
17341825
break;
@@ -1747,7 +1838,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
17471838
&common,
17481839
&haves_to_send, &in_vain,
17491840
reader.use_sideband,
1750-
seen_ack)) {
1841+
seen_ack,
1842+
&negotiation_include_oids)) {
17511843
trace2_region_leave_printf("negotiation_v2", "round",
17521844
the_repository, "%d",
17531845
negotiation_round);
@@ -1883,6 +1975,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
18831975
negotiator->release(negotiator);
18841976

18851977
oidset_clear(&common);
1978+
oidset_clear(&negotiation_include_oids);
18861979
return ref;
18871980
}
18881981

@@ -2181,12 +2274,14 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,
21812274
const struct string_list *server_options,
21822275
int stateless_rpc,
21832276
int fd[],
2184-
struct oidset *acked_commits)
2277+
struct oidset *acked_commits,
2278+
const struct string_list *negotiation_include)
21852279
{
21862280
struct fetch_negotiator negotiator;
21872281
struct packet_reader reader;
21882282
struct object_array nt_object_array = OBJECT_ARRAY_INIT;
21892283
struct strbuf req_buf = STRBUF_INIT;
2284+
struct oidset negotiation_include_oids = OIDSET_INIT;
21902285
int haves_to_send = INITIAL_FLUSH;
21912286
int in_vain = 0;
21922287
int seen_ack = 0;
@@ -2197,6 +2292,9 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,
21972292
fetch_negotiator_init(the_repository, &negotiator);
21982293
mark_tips(&negotiator, negotiation_restrict_tips);
21992294

2295+
resolve_negotiation_include(negotiation_include,
2296+
&negotiation_include_oids);
2297+
22002298
packet_reader_init(&reader, fd[0], NULL, 0,
22012299
PACKET_READ_CHOMP_NEWLINE |
22022300
PACKET_READ_DIE_ON_ERR_PACKET);
@@ -2221,7 +2319,8 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,
22212319

22222320
packet_buf_write(&req_buf, "wait-for-done");
22232321

2224-
haves_added = add_haves(&negotiator, &req_buf, &haves_to_send);
2322+
haves_added = add_haves(&negotiator, &req_buf, &haves_to_send,
2323+
&negotiation_include_oids);
22252324
in_vain += haves_added;
22262325
if (!haves_added || (seen_ack && in_vain >= MAX_IN_VAIN))
22272326
last_iteration = 1;
@@ -2273,6 +2372,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,
22732372

22742373
clear_common_flag(acked_commits);
22752374
object_array_clear(&nt_object_array);
2375+
oidset_clear(&negotiation_include_oids);
22762376
negotiator.release(&negotiator);
22772377
strbuf_release(&req_buf);
22782378
}

fetch-pack.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ struct fetch_pack_args {
2323
*/
2424
const struct oid_array *negotiation_restrict_tips;
2525

26+
/*
27+
* If non-empty, ref patterns whose tips should always be sent
28+
* as "have" lines during negotiation, regardless of what the
29+
* negotiation algorithm selects.
30+
*/
31+
const struct string_list *negotiation_include;
32+
2633
unsigned deepen_relative:1;
2734
unsigned quiet:1;
2835
unsigned keep_pack:1;
@@ -93,7 +100,8 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,
93100
const struct string_list *server_options,
94101
int stateless_rpc,
95102
int fd[],
96-
struct oidset *acked_commits);
103+
struct oidset *acked_commits,
104+
const struct string_list *negotiation_include);
97105

98106
/*
99107
* Print an appropriate error message for each sought ref that wasn't

0 commit comments

Comments
 (0)