Skip to content

Commit e00dfdb

Browse files
committed
fetch-pack: respect fetch.haveRefs
The previous change documented and loaded the fetch.haveRefs multi-valued config, but did not change any behavior. Now, let's update the behavior for fetch negotiation. This is a config-only option right now, unlike the --negotiation-tips command-line options. For this reason, the values are read on-demand from the repo_settings struct instead of passed down via options. If we wish to reflect this option in the command line as well, then that plumbing would be easy to refactor. One interesting aspect is how this relates to --negotiation-tips arguments. The --negotation-tips arguments _restricts_ the set of haves while the fetch.haveRefs configs _increases_ the set of haves. I chose to have these options done in that order. This helps create tests by enforcing that the negotiation strategy restricts the set of haves before the config adds them back in. This provides some simulation of the intended use case where the haves were restricted due to an overwhelming number of refs instead of by --negotiation-tips. The set of fetch.haveRefs are output entirely before considering a batch for flushing. This should hopefully not be a problem as users _should_ select a small core set of references. While these are output, we add them to an oidset to avoid duplicates. During my development, I found that if I try to increment the counters while outputting these ref tips, then I could get into an infinite loop where the client and server go back and forth forever. This happened when the fetch.haveRefs included the entire set of references that could be selected, which is covered in the tests. Signed-off-by: Derrick Stolee <stolee@gmail.com>
1 parent 37fe8bd commit e00dfdb

File tree

6 files changed

+191
-6
lines changed

6 files changed

+191
-6
lines changed

Documentation/config/fetch.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ Each value is either an exact ref name (e.g. `refs/heads/release`) or a
9292
glob pattern (e.g. `refs/heads/release/*`). The pattern syntax is the same
9393
as for `--negotiation-tip`.
9494
+
95+
If `--negotiation-tip` is used, then the have set is first restricted by
96+
that option and then increased to include the tips specified by the
97+
`fetch.haveRefs` config values.
98+
+
9599
This option is additive with the normal negotiation process: the
96100
negotiation algorithm still runs and advertises its own selected commits,
97101
but the refs matching `fetch.haveRefs` are sent unconditionally on top of

Documentation/fetch-options.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ this option multiple times, one for each matching ref name.
6767
+
6868
See also the `fetch.negotiationAlgorithm` and `push.negotiate`
6969
configuration variables documented in linkgit:git-config[1], and the
70-
`--negotiate-only` option below.
70+
`--negotiate-only` option below. Also, these restrictions do not apply
71+
to any `fetch.haveRefs` config values.
7172

7273
`--negotiate-only`::
7374
Do not fetch anything from the server, and instead print the

builtin/fetch.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "promisor-remote.h"
3939
#include "commit-graph.h"
4040
#include "shallow.h"
41+
#include "repo-settings.h"
4142
#include "trace.h"
4243
#include "trace2.h"
4344
#include "bundle-uri.h"

fetch-pack.c

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
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"
32+
#include "repo-settings.h"
3133
#include "fsck.h"
3234
#include "shallow.h"
3335
#include "commit-reach.h"
@@ -332,6 +334,40 @@ static void send_filter(struct fetch_pack_args *args,
332334
}
333335
}
334336

337+
static int add_oid_to_oidset(const struct reference *ref, void *cb_data)
338+
{
339+
struct oidset *set = cb_data;
340+
oidset_insert(set, ref->oid);
341+
return 0;
342+
}
343+
344+
static void resolve_have_refs(struct string_list *have_refs,
345+
struct oidset *result)
346+
{
347+
struct string_list_item *item;
348+
349+
if (!have_refs)
350+
return;
351+
352+
for_each_string_list_item(item, have_refs) {
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 have_refs_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,26 @@ 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 fetch.haveRefs */
516+
prepare_repo_settings(the_repository);
517+
resolve_have_refs(the_repository->settings.fetch_have_refs,
518+
&have_refs_oids);
519+
if (oidset_size(&have_refs_oids)) {
520+
struct oidset_iter iter;
521+
oidset_iter_init(&have_refs_oids, &iter);
522+
523+
while ((oid = oidset_iter_next(&iter))) {
524+
packet_buf_write(&req_buf, "have %s\n",
525+
oid_to_hex(oid));
526+
print_verbose(args, "have %s", oid_to_hex(oid));
527+
}
528+
}
529+
477530
while ((oid = negotiator->next(negotiator))) {
531+
/* avoid duplicate oids as fetch.haveRefs. */
532+
if (oidset_contains(&have_refs_oids, oid))
533+
continue;
478534
packet_buf_write(&req_buf, "have %s\n", oid_to_hex(oid));
479535
print_verbose(args, "have %s", oid_to_hex(oid));
480536
in_vain++;
@@ -584,6 +640,7 @@ static int find_common(struct fetch_negotiator *negotiator,
584640
flushes++;
585641
}
586642
strbuf_release(&req_buf);
643+
oidset_clear(&have_refs_oids);
587644

588645
if (!got_ready || !no_done)
589646
consume_shallow_list(args, &reader);
@@ -1305,12 +1362,25 @@ static void add_common(struct strbuf *req_buf, struct oidset *common)
13051362

13061363
static int add_haves(struct fetch_negotiator *negotiator,
13071364
struct strbuf *req_buf,
1308-
int *haves_to_send)
1365+
int *haves_to_send,
1366+
struct oidset *have_refs_oids)
13091367
{
13101368
int haves_added = 0;
13111369
const struct object_id *oid;
13121370

1371+
/* Send unconditional haves from fetch.haveRefs */
1372+
if (have_refs_oids) {
1373+
struct oidset_iter iter;
1374+
oidset_iter_init(have_refs_oids, &iter);
1375+
1376+
while ((oid = oidset_iter_next(&iter)))
1377+
packet_buf_write(req_buf, "have %s\n",
1378+
oid_to_hex(oid));
1379+
}
1380+
13131381
while ((oid = negotiator->next(negotiator))) {
1382+
if (have_refs_oids && oidset_contains(have_refs_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 *have_refs_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+
have_refs_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 have_refs_oids = OIDSET_INIT;
16601733
struct packet_reader reader;
16611734
int in_vain = 0, negotiation_started = 0;
16621735
int negotiation_round = 0;
@@ -1708,6 +1781,10 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
17081781
reader.me = "fetch-pack";
17091782
}
17101783

1784+
prepare_repo_settings(the_repository);
1785+
resolve_have_refs(the_repository->settings.fetch_have_refs,
1786+
&have_refs_oids);
1787+
17111788
while (state != FETCH_DONE) {
17121789
switch (state) {
17131790
case FETCH_CHECK_LOCAL:
@@ -1747,7 +1824,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
17471824
&common,
17481825
&haves_to_send, &in_vain,
17491826
reader.use_sideband,
1750-
seen_ack)) {
1827+
seen_ack,
1828+
&have_refs_oids)) {
17511829
trace2_region_leave_printf("negotiation_v2", "round",
17521830
the_repository, "%d",
17531831
negotiation_round);
@@ -1883,6 +1961,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
18831961
negotiator->release(negotiator);
18841962

18851963
oidset_clear(&common);
1964+
oidset_clear(&have_refs_oids);
18861965
return ref;
18871966
}
18881967

@@ -2187,6 +2266,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,
21872266
struct packet_reader reader;
21882267
struct object_array nt_object_array = OBJECT_ARRAY_INIT;
21892268
struct strbuf req_buf = STRBUF_INIT;
2269+
struct oidset have_refs_oids = OIDSET_INIT;
21902270
int haves_to_send = INITIAL_FLUSH;
21912271
int in_vain = 0;
21922272
int seen_ack = 0;
@@ -2205,6 +2285,10 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,
22052285
add_to_object_array,
22062286
&nt_object_array);
22072287

2288+
prepare_repo_settings(the_repository);
2289+
resolve_have_refs(the_repository->settings.fetch_have_refs,
2290+
&have_refs_oids);
2291+
22082292
trace2_region_enter("fetch-pack", "negotiate_using_fetch", the_repository);
22092293
while (!last_iteration) {
22102294
int haves_added;
@@ -2221,7 +2305,8 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,
22212305

22222306
packet_buf_write(&req_buf, "wait-for-done");
22232307

2224-
haves_added = add_haves(&negotiator, &req_buf, &haves_to_send);
2308+
haves_added = add_haves(&negotiator, &req_buf, &haves_to_send,
2309+
&have_refs_oids);
22252310
in_vain += haves_added;
22262311
if (!haves_added || (seen_ack && in_vain >= MAX_IN_VAIN))
22272312
last_iteration = 1;
@@ -2273,6 +2358,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,
22732358

22742359
clear_common_flag(acked_commits);
22752360
object_array_clear(&nt_object_array);
2361+
oidset_clear(&have_refs_oids);
22762362
negotiator.release(&negotiator);
22772363
strbuf_release(&req_buf);
22782364
}

t/t5510-fetch.sh

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

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

t/t5516-fetch-push.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,21 @@ test_expect_success 'push with negotiation does not attempt to fetch submodules'
254254
! grep "Fetching submodule" err
255255
'
256256

257+
test_expect_success 'push with negotiation and fetch.haveRefs' '
258+
test_when_finished rm -rf haverefs &&
259+
mk_empty haverefs &&
260+
git push haverefs $the_first_commit:refs/remotes/origin/first_commit &&
261+
test_commit -C haverefs unrelated_commit &&
262+
git -C haverefs config receive.hideRefs refs/remotes/origin/first_commit &&
263+
test_when_finished "rm event" &&
264+
GIT_TRACE2_EVENT="$(pwd)/event" \
265+
git -c protocol.version=2 -c push.negotiate=1 \
266+
-c fetch.haveRefs=refs/heads/main \
267+
push haverefs refs/heads/main:refs/remotes/origin/main &&
268+
test_grep \"key\":\"total_rounds\" event &&
269+
grep_wrote 2 event # 1 commit, 1 tree
270+
'
271+
257272
test_expect_success 'push without wildcard' '
258273
mk_empty testrepo &&
259274

0 commit comments

Comments
 (0)