fetch-pack: support negotiation tip whitelist

During negotiation, fetch-pack eventually reports as "have" lines all
commits reachable from all refs. Allow the user to restrict the commits
sent in this way by providing a whitelist of tips; only the tips
themselves and their ancestors will be sent.

Both globs and single objects are supported.

This feature is only supported for protocols that support connect or
stateless-connect (such as HTTP with protocol v2).

This will speed up negotiation when the repository has multiple
relatively independent branches (for example, when a repository
interacts with multiple repositories, such as with linux-next [1] and
torvalds/linux [2]), and the user knows which local branch is likely to
have commits in common with the upstream branch they are fetching.

[1] https://kernel.googlesource.com/pub/scm/linux/kernel/git/next/linux-next/
[2] https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux/

Signed-off-by: Jonathan Tan <jonathantanmy@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Jonathan Tan 2018-07-02 15:39:44 -07:00 committed by Junio C Hamano
parent ec06283844
commit 3390e42adb
8 changed files with 176 additions and 2 deletions

View File

@ -42,6 +42,22 @@ the current repository has the same history as the source repository.
.git/shallow. This option updates .git/shallow and accept such
refs.
--negotiation-tip=<commit|glob>::
By default, Git will report, to the server, commits reachable
from all local refs to find common commits in an attempt to
reduce the size of the to-be-received packfile. If specified,
Git will only report commits reachable from the given tips.
This is useful to speed up fetches when the user knows which
local ref is likely to have commits in common with the
upstream ref being fetched.
+
This option may be specified more than once; if so, Git will report
commits reachable from any of the given commits.
+
The argument to this option may be a glob on ref names, a ref, or the (possibly
abbreviated) SHA-1 of a commit. Specifying a glob is equivalent to specifying
this option multiple times, one for each matching ref name.
ifndef::git-pull[]
--dry-run::
Show what would be done, without making any changes.

View File

@ -63,6 +63,7 @@ static int shown_url = 0;
static struct refspec refmap = REFSPEC_INIT_FETCH;
static struct list_objects_filter_options filter_options;
static struct string_list server_options = STRING_LIST_INIT_DUP;
static struct string_list negotiation_tip = STRING_LIST_INIT_NODUP;
static int git_fetch_config(const char *k, const char *v, void *cb)
{
@ -174,6 +175,8 @@ static struct option builtin_fetch_options[] = {
TRANSPORT_FAMILY_IPV4),
OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
TRANSPORT_FAMILY_IPV6),
OPT_STRING_LIST(0, "negotiation-tip", &negotiation_tip, N_("revision"),
N_("report that we have only objects reachable from this object")),
OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
OPT_END()
};
@ -1049,6 +1052,40 @@ static void set_option(struct transport *transport, const char *name, const char
name, transport->url);
}
static int add_oid(const char *refname, const struct object_id *oid, int flags,
void *cb_data)
{
struct oid_array *oids = cb_data;
oid_array_append(oids, oid);
return 0;
}
static void add_negotiation_tips(struct git_transport_options *smart_options)
{
struct oid_array *oids = xcalloc(1, sizeof(*oids));
int i;
for (i = 0; i < negotiation_tip.nr; i++) {
const char *s = negotiation_tip.items[i].string;
int old_nr;
if (!has_glob_specials(s)) {
struct object_id oid;
if (get_oid(s, &oid))
die("%s is not a valid object", s);
oid_array_append(oids, &oid);
continue;
}
old_nr = oids->nr;
for_each_glob_ref(add_oid, s, oids);
if (old_nr == oids->nr)
warning("Ignoring --negotiation-tip=%s because it does not match any refs",
s);
}
smart_options->negotiation_tips = oids;
}
static struct transport *prepare_transport(struct remote *remote, int deepen)
{
struct transport *transport;
@ -1075,6 +1112,12 @@ static struct transport *prepare_transport(struct remote *remote, int deepen)
filter_options.filter_spec);
set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
}
if (negotiation_tip.nr) {
if (transport->smart_options)
add_negotiation_tips(transport->smart_options);
else
warning("Ignoring --negotiation-tip because the protocol does not support it.");
}
return transport;
}

View File

@ -213,6 +213,22 @@ static int next_flush(int stateless_rpc, int count)
return count;
}
static void mark_tips(struct fetch_negotiator *negotiator,
const struct oid_array *negotiation_tips)
{
int i;
if (!negotiation_tips) {
for_each_ref(rev_list_insert_ref_oid, negotiator);
return;
}
for (i = 0; i < negotiation_tips->nr; i++)
rev_list_insert_ref(negotiator, NULL,
&negotiation_tips->oid[i]);
return;
}
static int find_common(struct fetch_negotiator *negotiator,
struct fetch_pack_args *args,
int fd[2], struct object_id *result_oid,
@ -230,7 +246,7 @@ static int find_common(struct fetch_negotiator *negotiator,
if (args->stateless_rpc && multi_ack == 1)
die(_("--stateless-rpc requires multi_ack_detailed"));
for_each_ref(rev_list_insert_ref_oid, negotiator);
mark_tips(negotiator, args->negotiation_tips);
for_each_cached_alternate(negotiator, insert_one_alternate_object);
fetching = 0;
@ -1295,7 +1311,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
else
state = FETCH_SEND_REQUEST;
for_each_ref(rev_list_insert_ref_oid, &negotiator);
mark_tips(&negotiator, args->negotiation_tips);
for_each_cached_alternate(&negotiator,
insert_one_alternate_object);
break;

View File

@ -16,6 +16,13 @@ struct fetch_pack_args {
const struct string_list *deepen_not;
struct list_objects_filter_options filter_options;
const struct string_list *server_options;
/*
* If not NULL, during packfile negotiation, fetch-pack will send "have"
* lines only with these tips and their ancestors.
*/
const struct oid_array *negotiation_tips;
unsigned deepen_relative:1;
unsigned quiet:1;
unsigned keep_pack:1;

View File

@ -865,4 +865,82 @@ test_expect_success C_LOCALE_OUTPUT 'fetch compact output' '
test_cmp expect actual
'
setup_negotiation_tip () {
SERVER="$1"
URL="$2"
USE_PROTOCOL_V2="$3"
rm -rf "$SERVER" client trace &&
git init "$SERVER" &&
test_commit -C "$SERVER" alpha_1 &&
test_commit -C "$SERVER" alpha_2 &&
git -C "$SERVER" checkout --orphan beta &&
test_commit -C "$SERVER" beta_1 &&
test_commit -C "$SERVER" beta_2 &&
git clone "$URL" client &&
if test "$USE_PROTOCOL_V2" -eq 1
then
git -C "$SERVER" config protocol.version 2 &&
git -C client config protocol.version 2
fi &&
test_commit -C "$SERVER" beta_s &&
git -C "$SERVER" checkout master &&
test_commit -C "$SERVER" alpha_s &&
git -C "$SERVER" tag -d alpha_1 alpha_2 beta_1 beta_2
}
check_negotiation_tip () {
# Ensure that {alpha,beta}_1 are sent as "have", but not {alpha_beta}_2
ALPHA_1=$(git -C client rev-parse alpha_1) &&
grep "fetch> have $ALPHA_1" trace &&
BETA_1=$(git -C client rev-parse beta_1) &&
grep "fetch> have $BETA_1" trace &&
ALPHA_2=$(git -C client rev-parse alpha_2) &&
! grep "fetch> have $ALPHA_2" trace &&
BETA_2=$(git -C client rev-parse beta_2) &&
! grep "fetch> have $BETA_2" trace
}
test_expect_success '--negotiation-tip limits "have" lines sent' '
setup_negotiation_tip server server 0 &&
GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \
--negotiation-tip=alpha_1 --negotiation-tip=beta_1 \
origin alpha_s beta_s &&
check_negotiation_tip
'
test_expect_success '--negotiation-tip understands globs' '
setup_negotiation_tip server server 0 &&
GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \
--negotiation-tip=*_1 \
origin alpha_s beta_s &&
check_negotiation_tip
'
test_expect_success '--negotiation-tip understands abbreviated SHA-1' '
setup_negotiation_tip server server 0 &&
GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \
--negotiation-tip=$(git -C client rev-parse --short alpha_1) \
--negotiation-tip=$(git -C client rev-parse --short beta_1) \
origin alpha_s beta_s &&
check_negotiation_tip
'
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
test_expect_success '--negotiation-tip limits "have" lines sent with HTTP protocol v2' '
setup_negotiation_tip "$HTTPD_DOCUMENT_ROOT_PATH/server" \
"$HTTPD_URL/smart/server" 1 &&
GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \
--negotiation-tip=alpha_1 --negotiation-tip=beta_1 \
origin alpha_s beta_s &&
check_negotiation_tip
'
stop_httpd
test_done

View File

@ -684,6 +684,9 @@ static int fetch(struct transport *transport,
transport, "filter",
data->transport_options.filter_options.filter_spec);
if (data->transport_options.negotiation_tips)
warning("Ignoring --negotiation-tip because the protocol does not support it.");
if (data->fetch)
return fetch_with_fetch(transport, nr_heads, to_fetch);

View File

@ -318,6 +318,7 @@ static int fetch_refs_via_pack(struct transport *transport,
args.filter_options = data->options.filter_options;
args.stateless_rpc = transport->stateless_rpc;
args.server_options = transport->server_options;
args.negotiation_tips = data->options.negotiation_tips;
if (!data->got_remote_heads)
refs_tmp = get_refs_via_connect(transport, 0, NULL);

View File

@ -25,6 +25,16 @@ struct git_transport_options {
const char *receivepack;
struct push_cas_option *cas;
struct list_objects_filter_options filter_options;
/*
* This is only used during fetch. See the documentation of
* negotiation_tips in struct fetch_pack_args.
*
* This field is only supported by transports that support connect or
* stateless_connect. Set this field directly instead of using
* transport_set_option().
*/
struct oid_array *negotiation_tips;
};
enum transport_family {