Skip to content

Commit 8b39eb9

Browse files
committed
fetch: add --must-have option for negotiation
Add a --must-have option to git fetch that specifies ref patterns whose tips should always be sent as "have" commits during negotiation, regardless of what the negotiation algorithm selects. Each value is either an exact ref name (e.g. refs/heads/release) or a glob pattern (e.g. refs/heads/release/*). The pattern syntax is the same as for --negotiation-tip. This is useful when certain references are important for negotiation efficiency but might be skipped by the negotiation algorithm or excluded by --negotiation-tip. Unlike --negotiation-tip which restricts the have set, --must-have is additive: the negotiation algorithm still runs and advertises its own selected commits, but the refs matching --must-have are sent unconditionally on top of those. If --negotiation-tip is used, the have set is first restricted by that option and then increased to include the tips specified by --must-have. Due to the comparision with --negotiation-tip, a previously untranslated warning around --negotiation-tip is converted into a translatable string with a swap for which option that is relevant. Getting this functionality to work requires moving these options through the transport API layer. Signed-off-by: Derrick Stolee <stolee@gmail.com>
1 parent 540a568 commit 8b39eb9

7 files changed

Lines changed: 205 additions & 9 deletions

File tree

Documentation/fetch-options.adoc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,24 @@ See also the `fetch.negotiationAlgorithm` and `push.negotiate`
6969
configuration variables documented in linkgit:git-config[1], and the
7070
`--negotiate-only` option below.
7171

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

builtin/fetch.c

Lines changed: 10 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_tip = STRING_LIST_INIT_NODUP;
102+
static struct string_list must_have = STRING_LIST_INIT_NODUP;
102103

103104
struct fetch_config {
104105
enum display_format display_format;
@@ -1599,7 +1600,13 @@ static struct transport *prepare_transport(struct remote *remote, int deepen,
15991600
if (transport->smart_options)
16001601
add_negotiation_tips(transport->smart_options);
16011602
else
1602-
warning("ignoring --negotiation-tip because the protocol does not support it");
1603+
warning(_("ignoring %s because the protocol does not support it"), "--negotiation-tip");
1604+
}
1605+
if (must_have.nr) {
1606+
if (transport->smart_options)
1607+
transport->smart_options->must_have = &must_have;
1608+
else
1609+
warning(_("ignoring %s because the protocol does not support it"), "--must-have");
16031610
}
16041611
return transport;
16051612
}
@@ -2567,6 +2574,8 @@ int cmd_fetch(int argc,
25672574
OPT_IPVERSION(&family),
25682575
OPT_STRING_LIST(0, "negotiation-tip", &negotiation_tip, N_("revision"),
25692576
N_("report that we have only objects reachable from this object")),
2577+
OPT_STRING_LIST(0, "must-have", &must_have, N_("revision"),
2578+
N_("ensure this ref is always sent as a negotiation have")),
25702579
OPT_BOOL(0, "negotiate-only", &negotiate_only,
25712580
N_("do not fetch a packfile; instead, print ancestors of negotiation tips")),
25722581
OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),

fetch-pack.c

Lines changed: 86 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,40 @@ 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+
oidset_insert(set, ref->oid);
340+
return 0;
341+
}
342+
343+
static void resolve_must_have(const struct string_list *must_have,
344+
struct oidset *result)
345+
{
346+
struct string_list_item *item;
347+
348+
if (!must_have || !must_have->nr)
349+
return;
350+
351+
for_each_string_list_item(item, must_have) {
352+
if (!has_glob_specials(item->string)) {
353+
struct object_id oid;
354+
if (repo_get_oid(the_repository, item->string, &oid))
355+
continue;
356+
if (!odb_has_object(the_repository->objects, &oid, 0))
357+
continue;
358+
oidset_insert(result, &oid);
359+
} else {
360+
struct refs_for_each_ref_options opts = {
361+
.pattern = item->string,
362+
};
363+
refs_for_each_ref_ext(
364+
get_main_ref_store(the_repository),
365+
add_oid_to_oidset, result, &opts);
366+
}
367+
}
368+
}
369+
335370
static int find_common(struct fetch_negotiator *negotiator,
336371
struct fetch_pack_args *args,
337372
int fd[2], struct object_id *result_oid,
@@ -347,6 +382,7 @@ static int find_common(struct fetch_negotiator *negotiator,
347382
struct strbuf req_buf = STRBUF_INIT;
348383
size_t state_len = 0;
349384
struct packet_reader reader;
385+
struct oidset must_have_oids = OIDSET_INIT;
350386

351387
if (args->stateless_rpc && multi_ack == 1)
352388
die(_("the option '%s' requires '%s'"), "--stateless-rpc", "multi_ack_detailed");
@@ -474,7 +510,24 @@ static int find_common(struct fetch_negotiator *negotiator,
474510
trace2_region_enter("fetch-pack", "negotiation_v0_v1", the_repository);
475511
flushes = 0;
476512
retval = -1;
513+
514+
/* Send unconditional haves from --must-have */
515+
resolve_must_have(args->must_have, &must_have_oids);
516+
if (oidset_size(&must_have_oids)) {
517+
struct oidset_iter iter;
518+
oidset_iter_init(&must_have_oids, &iter);
519+
520+
while ((oid = oidset_iter_next(&iter))) {
521+
packet_buf_write(&req_buf, "have %s\n",
522+
oid_to_hex(oid));
523+
print_verbose(args, "have %s", oid_to_hex(oid));
524+
}
525+
}
526+
477527
while ((oid = negotiator->next(negotiator))) {
528+
/* avoid duplicate oids from --must-have */
529+
if (oidset_contains(&must_have_oids, oid))
530+
continue;
478531
packet_buf_write(&req_buf, "have %s\n", oid_to_hex(oid));
479532
print_verbose(args, "have %s", oid_to_hex(oid));
480533
in_vain++;
@@ -584,6 +637,7 @@ static int find_common(struct fetch_negotiator *negotiator,
584637
flushes++;
585638
}
586639
strbuf_release(&req_buf);
640+
oidset_clear(&must_have_oids);
587641

588642
if (!got_ready || !no_done)
589643
consume_shallow_list(args, &reader);
@@ -1305,12 +1359,25 @@ static void add_common(struct strbuf *req_buf, struct oidset *common)
13051359

13061360
static int add_haves(struct fetch_negotiator *negotiator,
13071361
struct strbuf *req_buf,
1308-
int *haves_to_send)
1362+
int *haves_to_send,
1363+
struct oidset *must_have_oids)
13091364
{
13101365
int haves_added = 0;
13111366
const struct object_id *oid;
13121367

1368+
/* Send unconditional haves from --must-have */
1369+
if (must_have_oids) {
1370+
struct oidset_iter iter;
1371+
oidset_iter_init(must_have_oids, &iter);
1372+
1373+
while ((oid = oidset_iter_next(&iter)))
1374+
packet_buf_write(req_buf, "have %s\n",
1375+
oid_to_hex(oid));
1376+
}
1377+
13131378
while ((oid = negotiator->next(negotiator))) {
1379+
if (must_have_oids && oidset_contains(must_have_oids, oid))
1380+
continue;
13141381
packet_buf_write(req_buf, "have %s\n", oid_to_hex(oid));
13151382
if (++haves_added >= *haves_to_send)
13161383
break;
@@ -1358,7 +1425,8 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
13581425
struct fetch_pack_args *args,
13591426
const struct ref *wants, struct oidset *common,
13601427
int *haves_to_send, int *in_vain,
1361-
int sideband_all, int seen_ack)
1428+
int sideband_all, int seen_ack,
1429+
struct oidset *must_have_oids)
13621430
{
13631431
int haves_added;
13641432
int done_sent = 0;
@@ -1413,7 +1481,8 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
14131481
/* Add all of the common commits we've found in previous rounds */
14141482
add_common(&req_buf, common);
14151483

1416-
haves_added = add_haves(negotiator, &req_buf, haves_to_send);
1484+
haves_added = add_haves(negotiator, &req_buf, haves_to_send,
1485+
must_have_oids);
14171486
*in_vain += haves_added;
14181487
trace2_data_intmax("negotiation_v2", the_repository, "haves_added", haves_added);
14191488
trace2_data_intmax("negotiation_v2", the_repository, "in_vain", *in_vain);
@@ -1657,6 +1726,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
16571726
struct ref *ref = copy_ref_list(orig_ref);
16581727
enum fetch_state state = FETCH_CHECK_LOCAL;
16591728
struct oidset common = OIDSET_INIT;
1729+
struct oidset must_have_oids = OIDSET_INIT;
16601730
struct packet_reader reader;
16611731
int in_vain = 0, negotiation_started = 0;
16621732
int negotiation_round = 0;
@@ -1708,6 +1778,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
17081778
reader.me = "fetch-pack";
17091779
}
17101780

1781+
resolve_must_have(args->must_have, &must_have_oids);
1782+
17111783
while (state != FETCH_DONE) {
17121784
switch (state) {
17131785
case FETCH_CHECK_LOCAL:
@@ -1747,7 +1819,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
17471819
&common,
17481820
&haves_to_send, &in_vain,
17491821
reader.use_sideband,
1750-
seen_ack)) {
1822+
seen_ack,
1823+
&must_have_oids)) {
17511824
trace2_region_leave_printf("negotiation_v2", "round",
17521825
the_repository, "%d",
17531826
negotiation_round);
@@ -1883,6 +1956,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
18831956
negotiator->release(negotiator);
18841957

18851958
oidset_clear(&common);
1959+
oidset_clear(&must_have_oids);
18861960
return ref;
18871961
}
18881962

@@ -2181,12 +2255,14 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,
21812255
const struct string_list *server_options,
21822256
int stateless_rpc,
21832257
int fd[],
2184-
struct oidset *acked_commits)
2258+
struct oidset *acked_commits,
2259+
const struct string_list *must_have)
21852260
{
21862261
struct fetch_negotiator negotiator;
21872262
struct packet_reader reader;
21882263
struct object_array nt_object_array = OBJECT_ARRAY_INIT;
21892264
struct strbuf req_buf = STRBUF_INIT;
2265+
struct oidset must_have_oids = OIDSET_INIT;
21902266
int haves_to_send = INITIAL_FLUSH;
21912267
int in_vain = 0;
21922268
int seen_ack = 0;
@@ -2205,6 +2281,8 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,
22052281
add_to_object_array,
22062282
&nt_object_array);
22072283

2284+
resolve_must_have(must_have, &must_have_oids);
2285+
22082286
trace2_region_enter("fetch-pack", "negotiate_using_fetch", the_repository);
22092287
while (!last_iteration) {
22102288
int haves_added;
@@ -2221,7 +2299,8 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,
22212299

22222300
packet_buf_write(&req_buf, "wait-for-done");
22232301

2224-
haves_added = add_haves(&negotiator, &req_buf, &haves_to_send);
2302+
haves_added = add_haves(&negotiator, &req_buf, &haves_to_send,
2303+
&must_have_oids);
22252304
in_vain += haves_added;
22262305
if (!haves_added || (seen_ack && in_vain >= MAX_IN_VAIN))
22272306
last_iteration = 1;
@@ -2273,6 +2352,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,
22732352

22742353
clear_common_flag(acked_commits);
22752354
object_array_clear(&nt_object_array);
2355+
oidset_clear(&must_have_oids);
22762356
negotiator.release(&negotiator);
22772357
strbuf_release(&req_buf);
22782358
}

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_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 *must_have;
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_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 *must_have);
97105

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

t/t5510-fetch.sh

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1728,6 +1728,79 @@ test_expect_success REFFILES "HEAD is updated even with conflicts" '
17281728
)
17291729
'
17301730

1731+
test_expect_success '--must-have includes configured refs as haves' '
1732+
test_when_finished rm -f trace &&
1733+
setup_negotiation_tip server server 0 &&
1734+
1735+
# With --negotiation-tip restricting tips, only alpha_1 is
1736+
# normally sent. --must-have should also include beta_1.
1737+
GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \
1738+
--negotiation-tip=alpha_1 \
1739+
--must-have=refs/tags/beta_1 \
1740+
origin alpha_s beta_s &&
1741+
1742+
ALPHA_1=$(git -C client rev-parse alpha_1) &&
1743+
test_grep "fetch> have $ALPHA_1" trace &&
1744+
BETA_1=$(git -C client rev-parse beta_1) &&
1745+
test_grep "fetch> have $BETA_1" trace
1746+
'
1747+
1748+
test_expect_success '--must-have works with glob patterns' '
1749+
test_when_finished rm -f trace &&
1750+
setup_negotiation_tip server server 0 &&
1751+
1752+
GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \
1753+
--negotiation-tip=alpha_1 \
1754+
--must-have="refs/tags/beta_*" \
1755+
origin alpha_s beta_s &&
1756+
1757+
BETA_1=$(git -C client rev-parse beta_1) &&
1758+
test_grep "fetch> have $BETA_1" trace &&
1759+
BETA_2=$(git -C client rev-parse beta_2) &&
1760+
test_grep "fetch> have $BETA_2" trace
1761+
'
1762+
1763+
test_expect_success '--must-have is additive with negotiation' '
1764+
test_when_finished rm -f trace &&
1765+
setup_negotiation_tip server server 0 &&
1766+
1767+
# Without --negotiation-tip, all local refs are used as tips.
1768+
# --must-have should add its refs unconditionally on top.
1769+
GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \
1770+
--must-have=refs/tags/beta_1 \
1771+
origin alpha_s beta_s &&
1772+
1773+
BETA_1=$(git -C client rev-parse beta_1) &&
1774+
test_grep "fetch> have $BETA_1" trace
1775+
'
1776+
1777+
test_expect_success '--must-have ignores non-existent refs silently' '
1778+
setup_negotiation_tip server server 0 &&
1779+
1780+
git -C client fetch --quiet \
1781+
--negotiation-tip=alpha_1 \
1782+
--must-have=refs/tags/nonexistent \
1783+
origin alpha_s beta_s 2>err &&
1784+
test_must_be_empty err
1785+
'
1786+
1787+
test_expect_success '--must-have avoids duplicates with negotiator' '
1788+
test_when_finished rm -f trace &&
1789+
setup_negotiation_tip server server 0 &&
1790+
1791+
# Configure a ref that will also be a negotiation tip.
1792+
# fetch should still complete successfully.
1793+
ALPHA_1=$(git -C client rev-parse alpha_1) &&
1794+
GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \
1795+
--negotiation-tip=alpha_1 \
1796+
--must-have=refs/tags/alpha_1 \
1797+
origin alpha_s beta_s &&
1798+
1799+
# alpha_1 should appear as a have
1800+
test_grep "fetch> have $ALPHA_1" trace >matches &&
1801+
test_line_count = 1 matches
1802+
'
1803+
17311804
. "$TEST_DIRECTORY"/lib-httpd.sh
17321805
start_httpd
17331806

transport.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,7 @@ static int fetch_refs_via_pack(struct transport *transport,
464464
args.stateless_rpc = transport->stateless_rpc;
465465
args.server_options = transport->server_options;
466466
args.negotiation_tips = data->options.negotiation_tips;
467+
args.must_have = data->options.must_have;
467468
args.reject_shallow_remote = transport->smart_options->reject_shallow;
468469

469470
if (!data->finished_handshake) {
@@ -495,7 +496,8 @@ static int fetch_refs_via_pack(struct transport *transport,
495496
transport->server_options,
496497
transport->stateless_rpc,
497498
data->fd,
498-
data->options.acked_commits);
499+
data->options.acked_commits,
500+
data->options.must_have);
499501
ret = 0;
500502
}
501503
goto cleanup;

0 commit comments

Comments
 (0)