Skip to content

Commit 18d5475

Browse files
derrickstoleegitster
authored andcommitted
fetch: add --negotiation-require option for negotiation
Add a new --negotiation-require 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 'require' 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. Also add --negotiation-require to 'git pull' passthrough options. Signed-off-by: Derrick Stolee <stolee@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 203275d commit 18d5475

File tree

8 files changed

+206
-8
lines changed

8 files changed

+206
-8
lines changed

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-require=<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-require`.
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: 10 additions & 0 deletions
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_tip = STRING_LIST_INIT_NODUP;
102+
static struct string_list negotiation_require = STRING_LIST_INIT_NODUP;
102103

103104
struct fetch_config {
104105
enum display_format display_format;
@@ -1615,6 +1616,13 @@ static struct transport *prepare_transport(struct remote *remote, int deepen,
16151616
strbuf_release(&config_name);
16161617
}
16171618
}
1619+
if (negotiation_require.nr) {
1620+
if (transport->smart_options)
1621+
transport->smart_options->negotiation_require = &negotiation_require;
1622+
else
1623+
warning(_("ignoring %s because the protocol does not support it"),
1624+
"--negotiation-require");
1625+
}
16181626
return transport;
16191627
}
16201628

@@ -2583,6 +2591,8 @@ int cmd_fetch(int argc,
25832591
N_("report that we have only objects reachable from this object")),
25842592
OPT_STRING_LIST(0, "negotiation-restrict", &negotiation_tip, N_("revision"),
25852593
N_("report that we have only objects reachable from this object")),
2594+
OPT_STRING_LIST(0, "negotiation-require", &negotiation_require, N_("revision"),
2595+
N_("ensure this ref is always sent as a negotiation have")),
25862596
OPT_BOOL(0, "negotiate-only", &negotiate_only,
25872597
N_("do not fetch a packfile; instead, print ancestors of negotiation tips")),
25882598
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-require", &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: 90 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,41 @@ 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+
oidset_insert(set, ref->oid);
341+
return 0;
342+
}
343+
344+
static void resolve_negotiation_require(const struct string_list *negotiation_require,
345+
struct oidset *result)
346+
{
347+
struct string_list_item *item;
348+
349+
if (!negotiation_require || !negotiation_require->nr)
350+
return;
351+
352+
for_each_string_list_item(item, negotiation_require) {
353+
if (!has_glob_specials(item->string)) {
354+
struct object_id oid;
355+
if (repo_get_oid(the_repository, item->string, &oid))
356+
continue;
357+
if (!odb_has_object(the_repository->objects, &oid, 0))
358+
continue;
359+
oidset_insert(result, &oid);
360+
} else {
361+
struct refs_for_each_ref_options opts = {
362+
.pattern = item->string,
363+
};
364+
refs_for_each_ref_ext(
365+
get_main_ref_store(the_repository),
366+
add_oid_to_oidset, result, &opts);
367+
}
368+
}
369+
}
370+
335371
static int find_common(struct fetch_negotiator *negotiator,
336372
struct fetch_pack_args *args,
337373
int fd[2], struct object_id *result_oid,
@@ -347,6 +383,7 @@ static int find_common(struct fetch_negotiator *negotiator,
347383
struct strbuf req_buf = STRBUF_INIT;
348384
size_t state_len = 0;
349385
struct packet_reader reader;
386+
struct oidset negotiation_require_oids = OIDSET_INIT;
350387

351388
if (args->stateless_rpc && multi_ack == 1)
352389
die(_("the option '%s' requires '%s'"), "--stateless-rpc", "multi_ack_detailed");
@@ -474,7 +511,25 @@ static int find_common(struct fetch_negotiator *negotiator,
474511
trace2_region_enter("fetch-pack", "negotiation_v0_v1", the_repository);
475512
flushes = 0;
476513
retval = -1;
514+
515+
/* Send unconditional haves from --negotiation-require */
516+
resolve_negotiation_require(args->negotiation_require,
517+
&negotiation_require_oids);
518+
if (oidset_size(&negotiation_require_oids)) {
519+
struct oidset_iter iter;
520+
oidset_iter_init(&negotiation_require_oids, &iter);
521+
522+
while ((oid = oidset_iter_next(&iter))) {
523+
packet_buf_write(&req_buf, "have %s\n",
524+
oid_to_hex(oid));
525+
print_verbose(args, "have %s", oid_to_hex(oid));
526+
}
527+
}
528+
477529
while ((oid = negotiator->next(negotiator))) {
530+
/* avoid duplicate oids from --negotiation-require */
531+
if (oidset_contains(&negotiation_require_oids, oid))
532+
continue;
478533
packet_buf_write(&req_buf, "have %s\n", oid_to_hex(oid));
479534
print_verbose(args, "have %s", oid_to_hex(oid));
480535
in_vain++;
@@ -584,6 +639,7 @@ static int find_common(struct fetch_negotiator *negotiator,
584639
flushes++;
585640
}
586641
strbuf_release(&req_buf);
642+
oidset_clear(&negotiation_require_oids);
587643

588644
if (!got_ready || !no_done)
589645
consume_shallow_list(args, &reader);
@@ -1305,12 +1361,26 @@ static void add_common(struct strbuf *req_buf, struct oidset *common)
13051361

13061362
static int add_haves(struct fetch_negotiator *negotiator,
13071363
struct strbuf *req_buf,
1308-
int *haves_to_send)
1364+
int *haves_to_send,
1365+
struct oidset *negotiation_require_oids)
13091366
{
13101367
int haves_added = 0;
13111368
const struct object_id *oid;
13121369

1370+
/* Send unconditional haves from --negotiation-require */
1371+
if (negotiation_require_oids) {
1372+
struct oidset_iter iter;
1373+
oidset_iter_init(negotiation_require_oids, &iter);
1374+
1375+
while ((oid = oidset_iter_next(&iter)))
1376+
packet_buf_write(req_buf, "have %s\n",
1377+
oid_to_hex(oid));
1378+
}
1379+
13131380
while ((oid = negotiator->next(negotiator))) {
1381+
if (negotiation_require_oids &&
1382+
oidset_contains(negotiation_require_oids, oid))
1383+
continue;
13141384
packet_buf_write(req_buf, "have %s\n", oid_to_hex(oid));
13151385
if (++haves_added >= *haves_to_send)
13161386
break;
@@ -1358,7 +1428,8 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
13581428
struct fetch_pack_args *args,
13591429
const struct ref *wants, struct oidset *common,
13601430
int *haves_to_send, int *in_vain,
1361-
int sideband_all, int seen_ack)
1431+
int sideband_all, int seen_ack,
1432+
struct oidset *negotiation_require_oids)
13621433
{
13631434
int haves_added;
13641435
int done_sent = 0;
@@ -1413,7 +1484,8 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
14131484
/* Add all of the common commits we've found in previous rounds */
14141485
add_common(&req_buf, common);
14151486

1416-
haves_added = add_haves(negotiator, &req_buf, haves_to_send);
1487+
haves_added = add_haves(negotiator, &req_buf, haves_to_send,
1488+
negotiation_require_oids);
14171489
*in_vain += haves_added;
14181490
trace2_data_intmax("negotiation_v2", the_repository, "haves_added", haves_added);
14191491
trace2_data_intmax("negotiation_v2", the_repository, "in_vain", *in_vain);
@@ -1657,6 +1729,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
16571729
struct ref *ref = copy_ref_list(orig_ref);
16581730
enum fetch_state state = FETCH_CHECK_LOCAL;
16591731
struct oidset common = OIDSET_INIT;
1732+
struct oidset negotiation_require_oids = OIDSET_INIT;
16601733
struct packet_reader reader;
16611734
int in_vain = 0, negotiation_started = 0;
16621735
int negotiation_round = 0;
@@ -1729,6 +1802,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
17291802
state = FETCH_SEND_REQUEST;
17301803

17311804
mark_tips(negotiator, args->negotiation_restrict_tips);
1805+
resolve_negotiation_require(args->negotiation_require,
1806+
&negotiation_require_oids);
17321807
for_each_cached_alternate(negotiator,
17331808
insert_one_alternate_object);
17341809
break;
@@ -1747,7 +1822,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
17471822
&common,
17481823
&haves_to_send, &in_vain,
17491824
reader.use_sideband,
1750-
seen_ack)) {
1825+
seen_ack,
1826+
&negotiation_require_oids)) {
17511827
trace2_region_leave_printf("negotiation_v2", "round",
17521828
the_repository, "%d",
17531829
negotiation_round);
@@ -1883,6 +1959,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
18831959
negotiator->release(negotiator);
18841960

18851961
oidset_clear(&common);
1962+
oidset_clear(&negotiation_require_oids);
18861963
return ref;
18871964
}
18881965

@@ -2181,12 +2258,14 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,
21812258
const struct string_list *server_options,
21822259
int stateless_rpc,
21832260
int fd[],
2184-
struct oidset *acked_commits)
2261+
struct oidset *acked_commits,
2262+
const struct string_list *negotiation_require)
21852263
{
21862264
struct fetch_negotiator negotiator;
21872265
struct packet_reader reader;
21882266
struct object_array nt_object_array = OBJECT_ARRAY_INIT;
21892267
struct strbuf req_buf = STRBUF_INIT;
2268+
struct oidset negotiation_require_oids = OIDSET_INIT;
21902269
int haves_to_send = INITIAL_FLUSH;
21912270
int in_vain = 0;
21922271
int seen_ack = 0;
@@ -2197,6 +2276,9 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,
21972276
fetch_negotiator_init(the_repository, &negotiator);
21982277
mark_tips(&negotiator, negotiation_restrict_tips);
21992278

2279+
resolve_negotiation_require(negotiation_require,
2280+
&negotiation_require_oids);
2281+
22002282
packet_reader_init(&reader, fd[0], NULL, 0,
22012283
PACKET_READ_CHOMP_NEWLINE |
22022284
PACKET_READ_DIE_ON_ERR_PACKET);
@@ -2221,7 +2303,8 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,
22212303

22222304
packet_buf_write(&req_buf, "wait-for-done");
22232305

2224-
haves_added = add_haves(&negotiator, &req_buf, &haves_to_send);
2306+
haves_added = add_haves(&negotiator, &req_buf, &haves_to_send,
2307+
&negotiation_require_oids);
22252308
in_vain += haves_added;
22262309
if (!haves_added || (seen_ack && in_vain >= MAX_IN_VAIN))
22272310
last_iteration = 1;
@@ -2273,6 +2356,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,
22732356

22742357
clear_common_flag(acked_commits);
22752358
object_array_clear(&nt_object_array);
2359+
oidset_clear(&negotiation_require_oids);
22762360
negotiator.release(&negotiator);
22772361
strbuf_release(&req_buf);
22782362
}

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_require;
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_require);
97105

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

t/t5510-fetch.sh

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1507,6 +1507,72 @@ test_expect_success 'CLI --negotiation-restrict overrides remote config' '
15071507
test_grep ! "fetch> have $BETA_1" trace
15081508
'
15091509

1510+
test_expect_success '--negotiation-require includes configured refs as haves' '
1511+
test_when_finished rm -f trace &&
1512+
setup_negotiation_tip server server 0 &&
1513+
1514+
GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \
1515+
--negotiation-restrict=alpha_1 \
1516+
--negotiation-require=refs/tags/beta_1 \
1517+
origin alpha_s beta_s &&
1518+
1519+
ALPHA_1=$(git -C client rev-parse alpha_1) &&
1520+
test_grep "fetch> have $ALPHA_1" trace &&
1521+
BETA_1=$(git -C client rev-parse beta_1) &&
1522+
test_grep "fetch> have $BETA_1" trace
1523+
'
1524+
1525+
test_expect_success '--negotiation-require works with glob patterns' '
1526+
test_when_finished rm -f trace &&
1527+
setup_negotiation_tip server server 0 &&
1528+
1529+
GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \
1530+
--negotiation-restrict=alpha_1 \
1531+
--negotiation-require="refs/tags/beta_*" \
1532+
origin alpha_s beta_s &&
1533+
1534+
BETA_1=$(git -C client rev-parse beta_1) &&
1535+
test_grep "fetch> have $BETA_1" trace &&
1536+
BETA_2=$(git -C client rev-parse beta_2) &&
1537+
test_grep "fetch> have $BETA_2" trace
1538+
'
1539+
1540+
test_expect_success '--negotiation-require is additive with negotiation' '
1541+
test_when_finished rm -f trace &&
1542+
setup_negotiation_tip server server 0 &&
1543+
1544+
GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \
1545+
--negotiation-require=refs/tags/beta_1 \
1546+
origin alpha_s beta_s &&
1547+
1548+
BETA_1=$(git -C client rev-parse beta_1) &&
1549+
test_grep "fetch> have $BETA_1" trace
1550+
'
1551+
1552+
test_expect_success '--negotiation-require ignores non-existent refs silently' '
1553+
setup_negotiation_tip server server 0 &&
1554+
1555+
git -C client fetch --quiet \
1556+
--negotiation-restrict=alpha_1 \
1557+
--negotiation-require=refs/tags/nonexistent \
1558+
origin alpha_s beta_s 2>err &&
1559+
test_must_be_empty err
1560+
'
1561+
1562+
test_expect_success '--negotiation-require avoids duplicates with negotiator' '
1563+
test_when_finished rm -f trace &&
1564+
setup_negotiation_tip server server 0 &&
1565+
1566+
ALPHA_1=$(git -C client rev-parse alpha_1) &&
1567+
GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \
1568+
--negotiation-restrict=alpha_1 \
1569+
--negotiation-require=refs/tags/alpha_1 \
1570+
origin alpha_s beta_s &&
1571+
1572+
test_grep "fetch> have $ALPHA_1" trace >matches &&
1573+
test_line_count = 1 matches
1574+
'
1575+
15101576
test_expect_success SYMLINKS 'clone does not get confused by a D/F conflict' '
15111577
git init df-conflict &&
15121578
(

0 commit comments

Comments
 (0)