mirror of
https://github.com/git/git.git
synced 2026-01-11 13:23:12 +09:00
Merge branch 'cc/lop-filter-auto' into seen
"auto filter" logic for large-object promisor remote. Comments? * cc/lop-filter-auto: fetch-pack: wire up and enable auto filter logic promisor-remote: keep advertised filter in memory list-objects-filter-options: implement auto filter resolution list-objects-filter-options: support 'auto' mode for --filter doc: fetch: document `--filter=<filter-spec>` option fetch: make filter_options local to cmd_fetch() clone: make filter_options local to cmd_clone() promisor-remote: allow a client to store fields promisor-remote: refactor initialising field lists
This commit is contained in:
commit
285db44532
@ -89,3 +89,36 @@ variable. The fields are checked only if the
|
||||
`promisor.acceptFromServer` config variable is not set to "None". If
|
||||
set to "None", this config variable has no effect. See
|
||||
linkgit:gitprotocol-v2[5].
|
||||
|
||||
promisor.storeFields::
|
||||
A comma or space separated list of additional remote related
|
||||
field names. If a client accepts an advertised remote, the
|
||||
client will store the values associated with these field names
|
||||
taken from the remote advertisement into its configuration,
|
||||
and then reload its remote configuration. Currently,
|
||||
"partialCloneFilter" and "token" are the only supported field
|
||||
names.
|
||||
+
|
||||
For example if a server advertises "partialCloneFilter=blob:limit=20k"
|
||||
for remote "foo", and that remote is accepted, then "blob:limit=20k"
|
||||
will be stored for the "remote.foo.partialCloneFilter" configuration
|
||||
variable.
|
||||
+
|
||||
If the new field value from an advertised remote is the same as the
|
||||
existing field value for that remote on the client side, then no
|
||||
change is made to the client configuration though.
|
||||
+
|
||||
When a new value is stored, a message is printed to standard error to
|
||||
let users know about this.
|
||||
+
|
||||
Note that for security reasons, if the remote is not already
|
||||
configured on the client side, nothing will be stored for that
|
||||
remote. In any case, no new remote will be created and no URL will be
|
||||
stored.
|
||||
+
|
||||
Before storing a partial clone filter, it's parsed to check it's
|
||||
valid. If it's not, a warning is emitted and it's not stored.
|
||||
+
|
||||
Before storing a token, a check is performed to ensure it contains no
|
||||
control character. If the check fails, a warning is emitted and it's
|
||||
not stored.
|
||||
|
||||
@ -88,6 +88,25 @@ linkgit:git-config[1].
|
||||
This is incompatible with `--recurse-submodules=(yes|on-demand)` and takes
|
||||
precedence over the `fetch.output` config option.
|
||||
|
||||
--filter=<filter-spec>::
|
||||
Use the partial clone feature and request that the server sends
|
||||
a subset of reachable objects according to a given object filter.
|
||||
When using `--filter`, the supplied _<filter-spec>_ is used for
|
||||
the partial fetch.
|
||||
+
|
||||
If `--filter=auto` is used, the filter specification is determined
|
||||
automatically by combining the filter specifications advertised by
|
||||
the server for the promisor remotes that the client accepts (see
|
||||
linkgit:gitprotocol-v2[5] and the `promisor.acceptFromServer`
|
||||
configuration option in linkgit:git-config[1]).
|
||||
+
|
||||
For details on all other available filter specifications, see the
|
||||
`--filter=<filter-spec>` option in linkgit:git-rev-list[1].
|
||||
+
|
||||
For example, `--filter=blob:none` will filter out all blobs (file
|
||||
contents) until needed by Git. Also, `--filter=blob:limit=<size>` will
|
||||
filter out all blobs of size at least _<size>_.
|
||||
|
||||
ifndef::git-pull[]
|
||||
`--write-fetch-head`::
|
||||
`--no-write-fetch-head`::
|
||||
|
||||
@ -187,11 +187,26 @@ objects from the source repository into a pack in the cloned repository.
|
||||
Use the partial clone feature and request that the server sends
|
||||
a subset of reachable objects according to a given object filter.
|
||||
When using `--filter`, the supplied _<filter-spec>_ is used for
|
||||
the partial clone filter. For example, `--filter=blob:none` will
|
||||
filter out all blobs (file contents) until needed by Git. Also,
|
||||
`--filter=blob:limit=<size>` will filter out all blobs of size
|
||||
at least _<size>_. For more details on filter specifications, see
|
||||
the `--filter` option in linkgit:git-rev-list[1].
|
||||
the partial clone filter.
|
||||
+
|
||||
If `--filter=auto` is used the filter specification is determined
|
||||
automatically through the 'promisor-remote' protocol (see
|
||||
linkgit:gitprotocol-v2[5]) by combining the filter specifications
|
||||
advertised by the server for the promisor remotes that the client
|
||||
accepts (see the `promisor.acceptFromServer` configuration option in
|
||||
linkgit:git-config[1]). This allows the server to suggest the optimal
|
||||
filter for the available promisor remotes.
|
||||
+
|
||||
As with other filter specifications, the "auto" value is persisted in
|
||||
the configuration. This ensures that future fetches will continue to
|
||||
adapt to the server's current recommendation.
|
||||
+
|
||||
For details on all other available filter specifications, see the
|
||||
`--filter=<filter-spec>` option in linkgit:git-rev-list[1].
|
||||
+
|
||||
For example, `--filter=blob:none` will filter out all blobs (file
|
||||
contents) until needed by Git. Also, `--filter=blob:limit=<size>` will
|
||||
filter out all blobs of size at least _<size>_.
|
||||
|
||||
`--also-filter-submodules`::
|
||||
Also apply the partial clone filter to any submodules in the repository.
|
||||
|
||||
@ -812,10 +812,15 @@ MUST appear first in each pr-fields, in that order.
|
||||
After these mandatory fields, the server MAY advertise the following
|
||||
optional fields in any order:
|
||||
|
||||
`partialCloneFilter`:: The filter specification used by the remote.
|
||||
`partialCloneFilter`:: The filter specification for the remote. It
|
||||
corresponds to the "remote.<name>.partialCloneFilter" config setting.
|
||||
Clients can use this to determine if the remote's filtering strategy
|
||||
is compatible with their needs (e.g., checking if both use "blob:none").
|
||||
It corresponds to the "remote.<name>.partialCloneFilter" config setting.
|
||||
is compatible with their needs (e.g., checking if both use
|
||||
"blob:none"). Additionally they can use this through the
|
||||
`--filter=auto` option in linkgit:git-clone[1]. With that option, the
|
||||
filter specification of the clone will be automatically computed by
|
||||
combining the filter specifications of the promisor remotes the client
|
||||
accepts.
|
||||
|
||||
`token`:: An authentication token that clients can use when
|
||||
connecting to the remote. It corresponds to the "remote.<name>.token"
|
||||
@ -826,9 +831,11 @@ are case-sensitive and MUST be transmitted exactly as specified
|
||||
above. Clients MUST ignore fields they don't recognize to allow for
|
||||
future protocol extensions.
|
||||
|
||||
For now, the client can only use information transmitted through these
|
||||
fields to decide if it accepts the advertised promisor remote. In the
|
||||
future that information might be used for other purposes though.
|
||||
The client can use information transmitted through these fields to
|
||||
decide if it accepts the advertised promisor remote. Also, the client
|
||||
can be configured to store the values of these fields or use them
|
||||
to automatically configure the repository (see "promisor.storeFields"
|
||||
in linkgit:git-config[1] and `--filter=auto` in linkgit:git-clone[1]).
|
||||
|
||||
Field values MUST be urlencoded.
|
||||
|
||||
@ -856,8 +863,9 @@ the server advertised, the client shouldn't advertise the
|
||||
On the server side, the "promisor.advertise" and "promisor.sendFields"
|
||||
configuration options can be used to control what it advertises. On
|
||||
the client side, the "promisor.acceptFromServer" configuration option
|
||||
can be used to control what it accepts. See the documentation of these
|
||||
configuration options for more information.
|
||||
can be used to control what it accepts, and the "promisor.storeFields"
|
||||
option, to control what it stores. See the documentation of these
|
||||
configuration options in linkgit:git-config[1] for more information.
|
||||
|
||||
Note that in the future it would be nice if the "promisor-remote"
|
||||
protocol capability could be used by the server, when responding to
|
||||
|
||||
1
Makefile
1
Makefile
@ -1517,6 +1517,7 @@ CLAR_TEST_SUITES += u-dir
|
||||
CLAR_TEST_SUITES += u-example-decorate
|
||||
CLAR_TEST_SUITES += u-hash
|
||||
CLAR_TEST_SUITES += u-hashmap
|
||||
CLAR_TEST_SUITES += u-list-objects-filter-options
|
||||
CLAR_TEST_SUITES += u-mem-pool
|
||||
CLAR_TEST_SUITES += u-oid-array
|
||||
CLAR_TEST_SUITES += u-oidmap
|
||||
|
||||
@ -77,7 +77,6 @@ static struct string_list option_required_reference = STRING_LIST_INIT_NODUP;
|
||||
static struct string_list option_optional_reference = STRING_LIST_INIT_NODUP;
|
||||
static int max_jobs = -1;
|
||||
static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP;
|
||||
static struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
|
||||
static int config_filter_submodules = -1; /* unspecified */
|
||||
static int option_remote_submodules;
|
||||
|
||||
@ -634,7 +633,9 @@ static int git_sparse_checkout_init(const char *repo)
|
||||
return result;
|
||||
}
|
||||
|
||||
static int checkout(int submodule_progress, int filter_submodules,
|
||||
static int checkout(int submodule_progress,
|
||||
struct list_objects_filter_options *filter_options,
|
||||
int filter_submodules,
|
||||
enum ref_storage_format ref_storage_format)
|
||||
{
|
||||
struct object_id oid;
|
||||
@ -723,9 +724,9 @@ static int checkout(int submodule_progress, int filter_submodules,
|
||||
strvec_pushf(&cmd.args, "--ref-format=%s",
|
||||
ref_storage_format_to_name(ref_storage_format));
|
||||
|
||||
if (filter_submodules && filter_options.choice)
|
||||
if (filter_submodules && filter_options->choice)
|
||||
strvec_pushf(&cmd.args, "--filter=%s",
|
||||
expand_list_objects_filter_spec(&filter_options));
|
||||
expand_list_objects_filter_spec(filter_options));
|
||||
|
||||
if (option_single_branch >= 0)
|
||||
strvec_push(&cmd.args, option_single_branch ?
|
||||
@ -903,6 +904,7 @@ int cmd_clone(int argc,
|
||||
enum transport_family family = TRANSPORT_FAMILY_ALL;
|
||||
struct string_list option_config = STRING_LIST_INIT_DUP;
|
||||
int option_dissociate = 0;
|
||||
struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
|
||||
int option_filter_submodules = -1; /* unspecified */
|
||||
struct string_list server_options = STRING_LIST_INIT_NODUP;
|
||||
const char *bundle_uri = NULL;
|
||||
@ -999,6 +1001,8 @@ int cmd_clone(int argc,
|
||||
NULL
|
||||
};
|
||||
|
||||
filter_options.allow_auto_filter = 1;
|
||||
|
||||
packet_trace_identity("clone");
|
||||
|
||||
repo_config(the_repository, git_clone_config, NULL);
|
||||
@ -1625,9 +1629,13 @@ int cmd_clone(int argc,
|
||||
return 1;
|
||||
|
||||
junk_mode = JUNK_LEAVE_REPO;
|
||||
err = checkout(submodule_progress, filter_submodules,
|
||||
err = checkout(submodule_progress,
|
||||
&filter_options,
|
||||
filter_submodules,
|
||||
ref_storage_format);
|
||||
|
||||
list_objects_filter_release(&filter_options);
|
||||
|
||||
string_list_clear(&option_not, 0);
|
||||
string_list_clear(&option_config, 0);
|
||||
string_list_clear(&server_options, 0);
|
||||
|
||||
@ -97,7 +97,6 @@ static struct strbuf default_rla = STRBUF_INIT;
|
||||
static struct transport *gtransport;
|
||||
static struct transport *gsecondary;
|
||||
static struct refspec refmap = REFSPEC_INIT_FETCH;
|
||||
static struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
|
||||
static struct string_list server_options = STRING_LIST_INIT_DUP;
|
||||
static struct string_list negotiation_tip = STRING_LIST_INIT_NODUP;
|
||||
|
||||
@ -1449,7 +1448,8 @@ static void add_negotiation_tips(struct git_transport_options *smart_options)
|
||||
smart_options->negotiation_tips = oids;
|
||||
}
|
||||
|
||||
static struct transport *prepare_transport(struct remote *remote, int deepen)
|
||||
static struct transport *prepare_transport(struct remote *remote, int deepen,
|
||||
struct list_objects_filter_options *filter_options)
|
||||
{
|
||||
struct transport *transport;
|
||||
|
||||
@ -1473,9 +1473,9 @@ static struct transport *prepare_transport(struct remote *remote, int deepen)
|
||||
set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes");
|
||||
if (refetch)
|
||||
set_option(transport, TRANS_OPT_REFETCH, "yes");
|
||||
if (filter_options.choice) {
|
||||
if (filter_options->choice) {
|
||||
const char *spec =
|
||||
expand_list_objects_filter_spec(&filter_options);
|
||||
expand_list_objects_filter_spec(filter_options);
|
||||
set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, spec);
|
||||
set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
|
||||
}
|
||||
@ -1493,7 +1493,8 @@ static int backfill_tags(struct display_state *display_state,
|
||||
struct ref_transaction *transaction,
|
||||
struct ref *ref_map,
|
||||
struct fetch_head *fetch_head,
|
||||
const struct fetch_config *config)
|
||||
const struct fetch_config *config,
|
||||
struct list_objects_filter_options *filter_options)
|
||||
{
|
||||
int retcode, cannot_reuse;
|
||||
|
||||
@ -1507,7 +1508,7 @@ static int backfill_tags(struct display_state *display_state,
|
||||
cannot_reuse = transport->cannot_reuse ||
|
||||
deepen_since || deepen_not.nr;
|
||||
if (cannot_reuse) {
|
||||
gsecondary = prepare_transport(transport->remote, 0);
|
||||
gsecondary = prepare_transport(transport->remote, 0, filter_options);
|
||||
transport = gsecondary;
|
||||
}
|
||||
|
||||
@ -1713,7 +1714,8 @@ out:
|
||||
|
||||
static int do_fetch(struct transport *transport,
|
||||
struct refspec *rs,
|
||||
const struct fetch_config *config)
|
||||
const struct fetch_config *config,
|
||||
struct list_objects_filter_options *filter_options)
|
||||
{
|
||||
struct ref_transaction *transaction = NULL;
|
||||
struct ref *ref_map = NULL;
|
||||
@ -1873,7 +1875,7 @@ static int do_fetch(struct transport *transport,
|
||||
* the transaction and don't commit anything.
|
||||
*/
|
||||
if (backfill_tags(&display_state, transport, transaction, tags_ref_map,
|
||||
&fetch_head, config))
|
||||
&fetch_head, config, filter_options))
|
||||
retcode = 1;
|
||||
}
|
||||
|
||||
@ -2198,20 +2200,21 @@ static int fetch_multiple(struct string_list *list, int max_children,
|
||||
* Fetching from the promisor remote should use the given filter-spec
|
||||
* or inherit the default filter-spec from the config.
|
||||
*/
|
||||
static inline void fetch_one_setup_partial(struct remote *remote)
|
||||
static inline void fetch_one_setup_partial(struct remote *remote,
|
||||
struct list_objects_filter_options *filter_options)
|
||||
{
|
||||
/*
|
||||
* Explicit --no-filter argument overrides everything, regardless
|
||||
* of any prior partial clones and fetches.
|
||||
*/
|
||||
if (filter_options.no_filter)
|
||||
if (filter_options->no_filter)
|
||||
return;
|
||||
|
||||
/*
|
||||
* If no prior partial clone/fetch and the current fetch DID NOT
|
||||
* request a partial-fetch, do a normal fetch.
|
||||
*/
|
||||
if (!repo_has_promisor_remote(the_repository) && !filter_options.choice)
|
||||
if (!repo_has_promisor_remote(the_repository) && !filter_options->choice)
|
||||
return;
|
||||
|
||||
/*
|
||||
@ -2220,8 +2223,8 @@ static inline void fetch_one_setup_partial(struct remote *remote)
|
||||
* filter-spec as the default for subsequent fetches to this
|
||||
* remote if there is currently no default filter-spec.
|
||||
*/
|
||||
if (filter_options.choice) {
|
||||
partial_clone_register(remote->name, &filter_options);
|
||||
if (filter_options->choice) {
|
||||
partial_clone_register(remote->name, filter_options);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2230,14 +2233,15 @@ static inline void fetch_one_setup_partial(struct remote *remote)
|
||||
* explicitly given filter-spec or inherit the filter-spec from
|
||||
* the config.
|
||||
*/
|
||||
if (!filter_options.choice)
|
||||
partial_clone_get_default_filter_spec(&filter_options, remote->name);
|
||||
if (!filter_options->choice)
|
||||
partial_clone_get_default_filter_spec(filter_options, remote->name);
|
||||
return;
|
||||
}
|
||||
|
||||
static int fetch_one(struct remote *remote, int argc, const char **argv,
|
||||
int prune_tags_ok, int use_stdin_refspecs,
|
||||
const struct fetch_config *config)
|
||||
const struct fetch_config *config,
|
||||
struct list_objects_filter_options *filter_options)
|
||||
{
|
||||
struct refspec rs = REFSPEC_INIT_FETCH;
|
||||
int i;
|
||||
@ -2249,7 +2253,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
|
||||
die(_("no remote repository specified; please specify either a URL or a\n"
|
||||
"remote name from which new revisions should be fetched"));
|
||||
|
||||
gtransport = prepare_transport(remote, 1);
|
||||
gtransport = prepare_transport(remote, 1, filter_options);
|
||||
|
||||
if (prune < 0) {
|
||||
/* no command line request */
|
||||
@ -2304,7 +2308,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
|
||||
sigchain_push_common(unlock_pack_on_signal);
|
||||
atexit(unlock_pack_atexit);
|
||||
sigchain_push(SIGPIPE, SIG_IGN);
|
||||
exit_code = do_fetch(gtransport, &rs, config);
|
||||
exit_code = do_fetch(gtransport, &rs, config, filter_options);
|
||||
sigchain_pop(SIGPIPE);
|
||||
refspec_clear(&rs);
|
||||
transport_disconnect(gtransport);
|
||||
@ -2329,6 +2333,7 @@ int cmd_fetch(int argc,
|
||||
const char *submodule_prefix = "";
|
||||
const char *bundle_uri;
|
||||
struct string_list list = STRING_LIST_INIT_DUP;
|
||||
struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
|
||||
struct remote *remote = NULL;
|
||||
int all = -1, multiple = 0;
|
||||
int result = 0;
|
||||
@ -2434,6 +2439,8 @@ int cmd_fetch(int argc,
|
||||
OPT_END()
|
||||
};
|
||||
|
||||
filter_options.allow_auto_filter = 1;
|
||||
|
||||
packet_trace_identity("fetch");
|
||||
|
||||
/* Record the command line for the reflog */
|
||||
@ -2594,7 +2601,7 @@ int cmd_fetch(int argc,
|
||||
trace2_region_enter("fetch", "negotiate-only", the_repository);
|
||||
if (!remote)
|
||||
die(_("must supply remote when using --negotiate-only"));
|
||||
gtransport = prepare_transport(remote, 1);
|
||||
gtransport = prepare_transport(remote, 1, &filter_options);
|
||||
if (gtransport->smart_options) {
|
||||
gtransport->smart_options->acked_commits = &acked_commits;
|
||||
} else {
|
||||
@ -2616,12 +2623,12 @@ int cmd_fetch(int argc,
|
||||
} else if (remote) {
|
||||
if (filter_options.choice || repo_has_promisor_remote(the_repository)) {
|
||||
trace2_region_enter("fetch", "setup-partial", the_repository);
|
||||
fetch_one_setup_partial(remote);
|
||||
fetch_one_setup_partial(remote, &filter_options);
|
||||
trace2_region_leave("fetch", "setup-partial", the_repository);
|
||||
}
|
||||
trace2_region_enter("fetch", "fetch-one", the_repository);
|
||||
result = fetch_one(remote, argc, argv, prune_tags_ok, stdin_refspecs,
|
||||
&config);
|
||||
&config, &filter_options);
|
||||
trace2_region_leave("fetch", "fetch-one", the_repository);
|
||||
} else {
|
||||
int max_children = max_jobs;
|
||||
@ -2727,5 +2734,6 @@ int cmd_fetch(int argc,
|
||||
|
||||
cleanup:
|
||||
string_list_clear(&list, 0);
|
||||
list_objects_filter_release(&filter_options);
|
||||
return result;
|
||||
}
|
||||
|
||||
20
fetch-pack.c
20
fetch-pack.c
@ -35,6 +35,7 @@
|
||||
#include "sigchain.h"
|
||||
#include "mergesort.h"
|
||||
#include "prio-queue.h"
|
||||
#include "promisor-remote.h"
|
||||
|
||||
static int transfer_unpack_limit = -1;
|
||||
static int fetch_unpack_limit = -1;
|
||||
@ -1661,6 +1662,25 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
|
||||
struct string_list packfile_uris = STRING_LIST_INIT_DUP;
|
||||
int i;
|
||||
struct strvec index_pack_args = STRVEC_INIT;
|
||||
const char *promisor_remote_config;
|
||||
|
||||
if (server_feature_v2("promisor-remote", &promisor_remote_config)) {
|
||||
char *remote_name = promisor_remote_reply(promisor_remote_config);
|
||||
free(remote_name);
|
||||
}
|
||||
|
||||
if (args->filter_options.choice == LOFC_AUTO) {
|
||||
struct strbuf errbuf = STRBUF_INIT;
|
||||
char *constructed_filter = promisor_remote_construct_filter(r);
|
||||
|
||||
list_objects_filter_resolve_auto(&args->filter_options,
|
||||
constructed_filter, &errbuf);
|
||||
if (errbuf.len > 0)
|
||||
die(_("couldn't resolve 'auto' filter: %s"), errbuf.buf);
|
||||
|
||||
free(constructed_filter);
|
||||
strbuf_release(&errbuf);
|
||||
}
|
||||
|
||||
negotiator = &negotiator_alloc;
|
||||
if (args->refetch)
|
||||
|
||||
@ -20,6 +20,8 @@ const char *list_object_filter_config_name(enum list_objects_filter_choice c)
|
||||
case LOFC_DISABLED:
|
||||
/* we have no name for "no filter at all" */
|
||||
break;
|
||||
case LOFC_AUTO:
|
||||
return "auto";
|
||||
case LOFC_BLOB_NONE:
|
||||
return "blob:none";
|
||||
case LOFC_BLOB_LIMIT:
|
||||
@ -52,7 +54,17 @@ int gently_parse_list_objects_filter(
|
||||
if (filter_options->choice)
|
||||
BUG("filter_options already populated");
|
||||
|
||||
if (!strcmp(arg, "blob:none")) {
|
||||
if (!strcmp(arg, "auto")) {
|
||||
if (!filter_options->allow_auto_filter) {
|
||||
strbuf_addstr(
|
||||
errbuf,
|
||||
_("'auto' filter not supported by this command"));
|
||||
return 1;
|
||||
}
|
||||
filter_options->choice = LOFC_AUTO;
|
||||
return 0;
|
||||
|
||||
} else if (!strcmp(arg, "blob:none")) {
|
||||
filter_options->choice = LOFC_BLOB_NONE;
|
||||
return 0;
|
||||
|
||||
@ -146,10 +158,20 @@ static int parse_combine_subfilter(
|
||||
|
||||
decoded = url_percent_decode(subspec->buf);
|
||||
|
||||
result = has_reserved_character(subspec, errbuf) ||
|
||||
gently_parse_list_objects_filter(
|
||||
&filter_options->sub[new_index], decoded, errbuf);
|
||||
result = has_reserved_character(subspec, errbuf);
|
||||
if (result)
|
||||
goto cleanup;
|
||||
|
||||
result = gently_parse_list_objects_filter(
|
||||
&filter_options->sub[new_index], decoded, errbuf);
|
||||
if (result)
|
||||
goto cleanup;
|
||||
|
||||
result = (filter_options->sub[new_index].choice == LOFC_AUTO);
|
||||
if (result)
|
||||
strbuf_addstr(errbuf, _("an 'auto' filter cannot be combined"));
|
||||
|
||||
cleanup:
|
||||
free(decoded);
|
||||
return result;
|
||||
}
|
||||
@ -208,6 +230,41 @@ static void filter_spec_append_urlencode(
|
||||
filter->filter_spec.buf + orig_len);
|
||||
}
|
||||
|
||||
char *list_objects_filter_combine(const struct string_list *specs)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
|
||||
if (!specs->nr)
|
||||
return NULL;
|
||||
|
||||
if (specs->nr == 1)
|
||||
return xstrdup(specs->items[0].string);
|
||||
|
||||
strbuf_addstr(&buf, "combine:");
|
||||
|
||||
for (size_t i = 0; i < specs->nr; i++) {
|
||||
const char *spec = specs->items[i].string;
|
||||
if (i > 0)
|
||||
strbuf_addch(&buf, '+');
|
||||
|
||||
strbuf_addstr_urlencode(&buf, spec, allow_unencoded);
|
||||
}
|
||||
|
||||
return strbuf_detach(&buf, NULL);
|
||||
}
|
||||
|
||||
void list_objects_filter_resolve_auto(struct list_objects_filter_options *filter_options,
|
||||
char *new_filter, struct strbuf *errbuf)
|
||||
{
|
||||
if (filter_options->choice != LOFC_AUTO)
|
||||
return;
|
||||
|
||||
list_objects_filter_release(filter_options);
|
||||
|
||||
if (new_filter)
|
||||
gently_parse_list_objects_filter(filter_options, new_filter, errbuf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Changes filter_options into an equivalent LOFC_COMBINE filter options
|
||||
* instance. Does not do anything if filter_options is already LOFC_COMBINE.
|
||||
@ -263,6 +320,9 @@ void parse_list_objects_filter(
|
||||
} else {
|
||||
struct list_objects_filter_options *sub;
|
||||
|
||||
if (filter_options->choice == LOFC_AUTO)
|
||||
die(_("an 'auto' filter is incompatible with any other filter"));
|
||||
|
||||
/*
|
||||
* Make filter_options an LOFC_COMBINE spec so we can trivially
|
||||
* add subspecs to it.
|
||||
@ -277,6 +337,9 @@ void parse_list_objects_filter(
|
||||
if (gently_parse_list_objects_filter(sub, arg, &errbuf))
|
||||
die("%s", errbuf.buf);
|
||||
|
||||
if (sub->choice == LOFC_AUTO)
|
||||
die(_("an 'auto' filter is incompatible with any other filter"));
|
||||
|
||||
strbuf_addch(&filter_options->filter_spec, '+');
|
||||
filter_spec_append_urlencode(filter_options, arg);
|
||||
}
|
||||
@ -317,6 +380,7 @@ void list_objects_filter_release(
|
||||
struct list_objects_filter_options *filter_options)
|
||||
{
|
||||
size_t sub;
|
||||
unsigned int allow_auto_filter = filter_options->allow_auto_filter;
|
||||
|
||||
if (!filter_options)
|
||||
return;
|
||||
@ -326,6 +390,7 @@ void list_objects_filter_release(
|
||||
list_objects_filter_release(&filter_options->sub[sub]);
|
||||
free(filter_options->sub);
|
||||
list_objects_filter_init(filter_options);
|
||||
filter_options->allow_auto_filter = allow_auto_filter;
|
||||
}
|
||||
|
||||
void partial_clone_register(
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include "strbuf.h"
|
||||
|
||||
struct option;
|
||||
struct string_list;
|
||||
|
||||
/*
|
||||
* The list of defined filters for list-objects.
|
||||
@ -18,6 +19,7 @@ enum list_objects_filter_choice {
|
||||
LOFC_SPARSE_OID,
|
||||
LOFC_OBJECT_TYPE,
|
||||
LOFC_COMBINE,
|
||||
LOFC_AUTO,
|
||||
LOFC__COUNT /* must be last */
|
||||
};
|
||||
|
||||
@ -50,6 +52,11 @@ struct list_objects_filter_options {
|
||||
*/
|
||||
unsigned int no_filter : 1;
|
||||
|
||||
/*
|
||||
* Is LOFC_AUTO a valid option?
|
||||
*/
|
||||
unsigned int allow_auto_filter : 1;
|
||||
|
||||
/*
|
||||
* BEGIN choice-specific parsed values from within the filter-spec. Only
|
||||
* some values will be defined for any given choice.
|
||||
@ -162,4 +169,22 @@ void list_objects_filter_copy(
|
||||
struct list_objects_filter_options *dest,
|
||||
const struct list_objects_filter_options *src);
|
||||
|
||||
/*
|
||||
* Combine the filter specs in 'specs' into a combined filter string
|
||||
* like "combine:<spec1>+<spec2>", where <spec1>, <spec2>, etc are
|
||||
* properly urlencoded. If 'specs' contains no element, NULL is
|
||||
* returned. If 'specs' contains a single element, a copy of that
|
||||
* element is returned.
|
||||
*/
|
||||
char *list_objects_filter_combine(const struct string_list *specs);
|
||||
|
||||
/*
|
||||
* Check if 'filter_options' are an 'auto' filter, and if that's the
|
||||
* case populate it with the filter specified by 'new_filter'.
|
||||
*/
|
||||
void list_objects_filter_resolve_auto(
|
||||
struct list_objects_filter_options *filter_options,
|
||||
char *new_filter,
|
||||
struct strbuf *errbuf);
|
||||
|
||||
#endif /* LIST_OBJECTS_FILTER_OPTIONS_H */
|
||||
|
||||
@ -745,6 +745,13 @@ static void filter_combine__init(
|
||||
filter->finalize_omits_fn = filter_combine__finalize_omits;
|
||||
}
|
||||
|
||||
static void filter_auto__init(
|
||||
struct list_objects_filter_options *filter_options UNUSED,
|
||||
struct filter *filter UNUSED)
|
||||
{
|
||||
BUG("LOFC_AUTO should have been resolved before initializing the filter");
|
||||
}
|
||||
|
||||
typedef void (*filter_init_fn)(
|
||||
struct list_objects_filter_options *filter_options,
|
||||
struct filter *filter);
|
||||
@ -760,6 +767,7 @@ static filter_init_fn s_filters[] = {
|
||||
filter_sparse_oid__init,
|
||||
filter_object_type__init,
|
||||
filter_combine__init,
|
||||
filter_auto__init,
|
||||
};
|
||||
|
||||
struct filter *list_objects_filter__init(
|
||||
|
||||
@ -193,6 +193,7 @@ void promisor_remote_clear(struct promisor_remote_config *config)
|
||||
while (config->promisors) {
|
||||
struct promisor_remote *r = config->promisors;
|
||||
free(r->partial_clone_filter);
|
||||
free(r->advertised_filter);
|
||||
config->promisors = config->promisors->next;
|
||||
free(r);
|
||||
}
|
||||
@ -375,18 +376,24 @@ static char *fields_from_config(struct string_list *fields_list, const char *con
|
||||
return fields;
|
||||
}
|
||||
|
||||
static struct string_list *initialize_fields_list(struct string_list *fields_list, int *initialized,
|
||||
const char *config_key)
|
||||
{
|
||||
if (!*initialized) {
|
||||
fields_list->cmp = strcasecmp;
|
||||
fields_from_config(fields_list, config_key);
|
||||
*initialized = 1;
|
||||
}
|
||||
|
||||
return fields_list;
|
||||
}
|
||||
|
||||
static struct string_list *fields_sent(void)
|
||||
{
|
||||
static struct string_list fields_list = STRING_LIST_INIT_NODUP;
|
||||
static int initialized;
|
||||
|
||||
if (!initialized) {
|
||||
fields_list.cmp = strcasecmp;
|
||||
fields_from_config(&fields_list, "promisor.sendFields");
|
||||
initialized = 1;
|
||||
}
|
||||
|
||||
return &fields_list;
|
||||
return initialize_fields_list(&fields_list, &initialized, "promisor.sendFields");
|
||||
}
|
||||
|
||||
static struct string_list *fields_checked(void)
|
||||
@ -394,13 +401,15 @@ static struct string_list *fields_checked(void)
|
||||
static struct string_list fields_list = STRING_LIST_INIT_NODUP;
|
||||
static int initialized;
|
||||
|
||||
if (!initialized) {
|
||||
fields_list.cmp = strcasecmp;
|
||||
fields_from_config(&fields_list, "promisor.checkFields");
|
||||
initialized = 1;
|
||||
}
|
||||
return initialize_fields_list(&fields_list, &initialized, "promisor.checkFields");
|
||||
}
|
||||
|
||||
return &fields_list;
|
||||
static struct string_list *fields_stored(void)
|
||||
{
|
||||
static struct string_list fields_list = STRING_LIST_INIT_NODUP;
|
||||
static int initialized;
|
||||
|
||||
return initialize_fields_list(&fields_list, &initialized, "promisor.storeFields");
|
||||
}
|
||||
|
||||
/*
|
||||
@ -692,6 +701,132 @@ static struct promisor_info *parse_one_advertised_remote(const char *remote_info
|
||||
return info;
|
||||
}
|
||||
|
||||
static bool store_one_field(struct repository *repo, const char *remote_name,
|
||||
const char *field_name, const char *field_key,
|
||||
const char *advertised, const char *current)
|
||||
{
|
||||
if (advertised && (!current || strcmp(current, advertised))) {
|
||||
char *key = xstrfmt("remote.%s.%s", remote_name, field_key);
|
||||
|
||||
fprintf(stderr, _("Storing new %s from server for remote '%s'.\n"
|
||||
" '%s' -> '%s'\n"),
|
||||
field_name, remote_name,
|
||||
current ? current : "",
|
||||
advertised);
|
||||
|
||||
repo_config_set_worktree_gently(repo, key, advertised);
|
||||
free(key);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check that a filter is valid by parsing it */
|
||||
static bool valid_filter(const char *filter, const char *remote_name)
|
||||
{
|
||||
struct list_objects_filter_options filter_opts = LIST_OBJECTS_FILTER_INIT;
|
||||
struct strbuf err = STRBUF_INIT;
|
||||
int res = gently_parse_list_objects_filter(&filter_opts, filter, &err);
|
||||
|
||||
if (res)
|
||||
warning(_("invalid filter '%s' for remote '%s' "
|
||||
"will not be stored: %s"),
|
||||
filter, remote_name, err.buf);
|
||||
|
||||
list_objects_filter_release(&filter_opts);
|
||||
strbuf_release(&err);
|
||||
|
||||
return !res;
|
||||
}
|
||||
|
||||
/* Check that a token doesn't contain any control character */
|
||||
static bool valid_token(const char *token, const char *remote_name)
|
||||
{
|
||||
const char *c = token;
|
||||
|
||||
for (; *c; c++)
|
||||
if (iscntrl(*c)) {
|
||||
warning(_("invalid token '%s' for remote '%s' "
|
||||
"will not be stored"),
|
||||
token, remote_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct store_info {
|
||||
struct repository *repo;
|
||||
struct string_list config_info;
|
||||
bool store_filter;
|
||||
bool store_token;
|
||||
};
|
||||
|
||||
static struct store_info *new_store_info(struct repository *repo)
|
||||
{
|
||||
struct string_list *fields_to_store = fields_stored();
|
||||
struct store_info *s = xmalloc(sizeof(*s));
|
||||
|
||||
s->repo = repo;
|
||||
|
||||
string_list_init_nodup(&s->config_info);
|
||||
promisor_config_info_list(repo, &s->config_info, fields_to_store);
|
||||
string_list_sort(&s->config_info);
|
||||
|
||||
s->store_filter = !!string_list_lookup(fields_to_store, promisor_field_filter);
|
||||
s->store_token = !!string_list_lookup(fields_to_store, promisor_field_token);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static void free_store_info(struct store_info *s)
|
||||
{
|
||||
if (s) {
|
||||
promisor_info_list_clear(&s->config_info);
|
||||
free(s);
|
||||
}
|
||||
}
|
||||
|
||||
static bool promisor_store_advertised_fields(struct promisor_info *advertised,
|
||||
struct store_info *store_info)
|
||||
{
|
||||
struct promisor_info *p;
|
||||
struct string_list_item *item;
|
||||
const char *remote_name = advertised->name;
|
||||
bool reload_config = false;
|
||||
|
||||
if (!(store_info->store_filter || store_info->store_token))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Get existing config info for the advertised promisor
|
||||
* remote. This ensures the remote is already configured on
|
||||
* the client side.
|
||||
*/
|
||||
item = string_list_lookup(&store_info->config_info, remote_name);
|
||||
|
||||
if (!item)
|
||||
return false;
|
||||
|
||||
p = item->util;
|
||||
|
||||
if (store_info->store_filter && advertised->filter &&
|
||||
valid_filter(advertised->filter, remote_name))
|
||||
reload_config |= store_one_field(store_info->repo, remote_name,
|
||||
"filter", promisor_field_filter,
|
||||
advertised->filter, p->filter);
|
||||
|
||||
if (store_info->store_token && advertised->token &&
|
||||
valid_token(advertised->token, remote_name))
|
||||
reload_config |= store_one_field(store_info->repo, remote_name,
|
||||
"token", promisor_field_token,
|
||||
advertised->token, p->token);
|
||||
|
||||
return reload_config;
|
||||
}
|
||||
|
||||
static void filter_promisor_remote(struct repository *repo,
|
||||
struct strvec *accepted,
|
||||
const char *info)
|
||||
@ -700,7 +835,10 @@ static void filter_promisor_remote(struct repository *repo,
|
||||
enum accept_promisor accept = ACCEPT_NONE;
|
||||
struct string_list config_info = STRING_LIST_INIT_NODUP;
|
||||
struct string_list remote_info = STRING_LIST_INIT_DUP;
|
||||
struct store_info *store_info = NULL;
|
||||
struct string_list_item *item;
|
||||
bool reload_config = false;
|
||||
struct string_list captured_filters = STRING_LIST_INIT_DUP;
|
||||
|
||||
if (!repo_config_get_string_tmp(the_repository, "promisor.acceptfromserver", &accept_str)) {
|
||||
if (!*accept_str || !strcasecmp("None", accept_str))
|
||||
@ -736,14 +874,50 @@ static void filter_promisor_remote(struct repository *repo,
|
||||
string_list_sort(&config_info);
|
||||
}
|
||||
|
||||
if (should_accept_remote(accept, advertised, &config_info))
|
||||
if (should_accept_remote(accept, advertised, &config_info)) {
|
||||
if (!store_info)
|
||||
store_info = new_store_info(repo);
|
||||
if (promisor_store_advertised_fields(advertised, store_info))
|
||||
reload_config = true;
|
||||
|
||||
strvec_push(accepted, advertised->name);
|
||||
|
||||
/* Capture advertised filters for accepted remotes */
|
||||
if (advertised->filter) {
|
||||
struct string_list_item *i;
|
||||
i = string_list_append(&captured_filters, advertised->name);
|
||||
i->util = xstrdup(advertised->filter);
|
||||
}
|
||||
}
|
||||
|
||||
promisor_info_free(advertised);
|
||||
}
|
||||
|
||||
promisor_info_list_clear(&config_info);
|
||||
string_list_clear(&remote_info, 0);
|
||||
free_store_info(store_info);
|
||||
|
||||
if (reload_config)
|
||||
repo_promisor_remote_reinit(repo);
|
||||
|
||||
/* Apply captured filters to the stable repo state */
|
||||
for_each_string_list_item(item, &captured_filters) {
|
||||
struct promisor_remote *r = repo_promisor_remote_find(repo, item->string);
|
||||
if (r) {
|
||||
free(r->advertised_filter);
|
||||
r->advertised_filter = item->util;
|
||||
item->util = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
string_list_clear(&captured_filters, 1);
|
||||
|
||||
/* Mark the remotes as accepted in the repository state */
|
||||
for (size_t i = 0; i < accepted->nr; i++) {
|
||||
struct promisor_remote *r = repo_promisor_remote_find(repo, accepted->v[i]);
|
||||
if (r)
|
||||
r->accepted = 1;
|
||||
}
|
||||
}
|
||||
|
||||
char *promisor_remote_reply(const char *info)
|
||||
@ -789,3 +963,23 @@ void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes
|
||||
|
||||
string_list_clear(&accepted_remotes, 0);
|
||||
}
|
||||
|
||||
char *promisor_remote_construct_filter(struct repository *repo)
|
||||
{
|
||||
struct string_list advertised_filters = STRING_LIST_INIT_NODUP;
|
||||
struct promisor_remote *r;
|
||||
char *result;
|
||||
|
||||
promisor_remote_init(repo);
|
||||
|
||||
for (r = repo->promisor_remote_config->promisors; r; r = r->next) {
|
||||
if (r->accepted && r->advertised_filter)
|
||||
string_list_append(&advertised_filters, r->advertised_filter);
|
||||
}
|
||||
|
||||
result = list_objects_filter_combine(&advertised_filters);
|
||||
|
||||
string_list_clear(&advertised_filters, 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ struct object_id;
|
||||
struct promisor_remote {
|
||||
struct promisor_remote *next;
|
||||
char *partial_clone_filter;
|
||||
char *advertised_filter;
|
||||
unsigned int accepted : 1;
|
||||
const char name[FLEX_ARRAY];
|
||||
};
|
||||
@ -67,4 +68,9 @@ void mark_promisor_remotes_as_accepted(struct repository *repo, const char *remo
|
||||
*/
|
||||
int repo_has_accepted_promisor_remote(struct repository *r);
|
||||
|
||||
/*
|
||||
* Use the filters from the accepted remotes to create a filter.
|
||||
*/
|
||||
char *promisor_remote_construct_filter(struct repository *repo);
|
||||
|
||||
#endif /* PROMISOR_REMOTE_H */
|
||||
|
||||
@ -4,6 +4,7 @@ clar_test_suites = [
|
||||
'unit-tests/u-example-decorate.c',
|
||||
'unit-tests/u-hash.c',
|
||||
'unit-tests/u-hashmap.c',
|
||||
'unit-tests/u-list-objects-filter-options.c',
|
||||
'unit-tests/u-mem-pool.c',
|
||||
'unit-tests/u-oid-array.c',
|
||||
'unit-tests/u-oidmap.c',
|
||||
|
||||
@ -360,6 +360,115 @@ test_expect_success "clone with promisor.checkFields" '
|
||||
check_missing_objects server 1 "$oid"
|
||||
'
|
||||
|
||||
test_expect_success "clone with promisor.storeFields=partialCloneFilter" '
|
||||
git -C server config promisor.advertise true &&
|
||||
test_when_finished "rm -rf client" &&
|
||||
|
||||
git -C server remote add otherLop "https://invalid.invalid" &&
|
||||
git -C server config remote.otherLop.token "fooBar" &&
|
||||
git -C server config remote.otherLop.stuff "baz" &&
|
||||
git -C server config remote.otherLop.partialCloneFilter "blob:limit=10k" &&
|
||||
test_when_finished "git -C server remote remove otherLop" &&
|
||||
|
||||
git -C server config remote.lop.token "fooXXX" &&
|
||||
git -C server config remote.lop.partialCloneFilter "blob:limit=8k" &&
|
||||
|
||||
test_config -C server promisor.sendFields "partialCloneFilter, token" &&
|
||||
test_when_finished "rm trace" &&
|
||||
|
||||
# Clone from server to create a client
|
||||
GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \
|
||||
-c remote.lop.promisor=true \
|
||||
-c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
|
||||
-c remote.lop.url="file://$(pwd)/lop" \
|
||||
-c remote.lop.token="fooYYY" \
|
||||
-c remote.lop.partialCloneFilter="blob:none" \
|
||||
-c promisor.acceptfromserver=All \
|
||||
-c promisor.storeFields=partialcloneFilter \
|
||||
--no-local --filter="blob:limit=5k" server client 2>err &&
|
||||
|
||||
# Check that the filter from the server is stored
|
||||
echo "blob:limit=8k" >expected &&
|
||||
git -C client config remote.lop.partialCloneFilter >actual &&
|
||||
test_cmp expected actual &&
|
||||
|
||||
# Check that user is notified when the filter is stored
|
||||
test_grep "Storing new filter from server for remote '\''lop'\''" err &&
|
||||
test_grep "'\''blob:none'\'' -> '\''blob:limit=8k'\''" err &&
|
||||
|
||||
# Check that the token from the server is NOT stored
|
||||
echo "fooYYY" >expected &&
|
||||
git -C client config remote.lop.token >actual &&
|
||||
test_cmp expected actual &&
|
||||
test_grep ! "Storing new token from server" err &&
|
||||
|
||||
# Check that the filter for an unknown remote is NOT stored
|
||||
test_must_fail git -C client config remote.otherLop.partialCloneFilter >actual &&
|
||||
|
||||
# Check that the largest object is still missing on the server
|
||||
check_missing_objects server 1 "$oid"
|
||||
'
|
||||
|
||||
test_expect_success "clone and fetch with --filter=auto" '
|
||||
git -C server config promisor.advertise true &&
|
||||
test_when_finished "rm -rf client trace" &&
|
||||
|
||||
git -C server config remote.lop.partialCloneFilter "blob:limit=9500" &&
|
||||
test_config -C server promisor.sendFields "partialCloneFilter" &&
|
||||
|
||||
GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \
|
||||
-c remote.lop.promisor=true \
|
||||
-c remote.lop.url="file://$(pwd)/lop" \
|
||||
-c promisor.acceptfromserver=All \
|
||||
--no-local --filter=auto server client 2>err &&
|
||||
|
||||
test_grep "filter blob:limit=9500" trace &&
|
||||
test_grep ! "filter auto" trace &&
|
||||
|
||||
# Verify "auto" is persisted in config
|
||||
echo auto >expected &&
|
||||
git -C client config remote.origin.partialCloneFilter >actual &&
|
||||
test_cmp expected actual &&
|
||||
|
||||
# Check that the largest object is still missing on the server
|
||||
check_missing_objects server 1 "$oid" &&
|
||||
|
||||
# Now change the filter on the server
|
||||
git -C server config remote.lop.partialCloneFilter "blob:limit=5678" &&
|
||||
|
||||
# Get a new commit on the server to ensure "git fetch" actually runs fetch-pack
|
||||
test_commit -C template new-commit &&
|
||||
git -C template push --all "$(pwd)/server" &&
|
||||
|
||||
# Perform a fetch WITH --filter=auto
|
||||
rm -rf trace &&
|
||||
GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch --filter=auto &&
|
||||
|
||||
# Verify that the new filter was used
|
||||
test_grep "filter blob:limit=5678" trace &&
|
||||
|
||||
# Check that the largest object is still missing on the server
|
||||
check_missing_objects server 1 "$oid" &&
|
||||
|
||||
# Change the filter on the server again
|
||||
git -C server config remote.lop.partialCloneFilter "blob:limit=5432" &&
|
||||
|
||||
# Get yet a new commit on the server to ensure fetch-pack runs
|
||||
test_commit -C template yet-a-new-commit &&
|
||||
git -C template push --all "$(pwd)/server" &&
|
||||
|
||||
# Perform a fetch WITHOUT --filter=auto
|
||||
# Relies on "auto" being persisted in the client config
|
||||
rm -rf trace &&
|
||||
GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch &&
|
||||
|
||||
# Verify that the new filter was used
|
||||
test_grep "filter blob:limit=5432" trace &&
|
||||
|
||||
# Check that the largest object is still missing on the server
|
||||
check_missing_objects server 1 "$oid"
|
||||
'
|
||||
|
||||
test_expect_success "clone with promisor.advertise set to 'true' but don't delete the client" '
|
||||
git -C server config promisor.advertise true &&
|
||||
|
||||
|
||||
86
t/unit-tests/u-list-objects-filter-options.c
Normal file
86
t/unit-tests/u-list-objects-filter-options.c
Normal file
@ -0,0 +1,86 @@
|
||||
#include "unit-test.h"
|
||||
#include "list-objects-filter-options.h"
|
||||
#include "strbuf.h"
|
||||
#include "string-list.h"
|
||||
|
||||
/* Helper to test gently_parse_list_objects_filter() */
|
||||
static void check_gentle_parse(const char *filter_spec,
|
||||
int expect_success,
|
||||
int allow_auto,
|
||||
enum list_objects_filter_choice expected_choice)
|
||||
{
|
||||
struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
|
||||
struct strbuf errbuf = STRBUF_INIT;
|
||||
int ret;
|
||||
|
||||
filter_options.allow_auto_filter = allow_auto;
|
||||
|
||||
ret = gently_parse_list_objects_filter(&filter_options, filter_spec, &errbuf);
|
||||
|
||||
if (expect_success) {
|
||||
cl_assert_equal_i(ret, 0);
|
||||
cl_assert_equal_i(expected_choice, filter_options.choice);
|
||||
cl_assert_equal_i(errbuf.len, 0);
|
||||
} else {
|
||||
cl_assert(ret != 0);
|
||||
cl_assert(errbuf.len > 0);
|
||||
}
|
||||
|
||||
strbuf_release(&errbuf);
|
||||
list_objects_filter_release(&filter_options);
|
||||
}
|
||||
|
||||
void test_list_objects_filter_options__regular_filters(void)
|
||||
{
|
||||
check_gentle_parse("blob:none", 1, 0, LOFC_BLOB_NONE);
|
||||
check_gentle_parse("blob:none", 1, 1, LOFC_BLOB_NONE);
|
||||
check_gentle_parse("blob:limit=5k", 1, 0, LOFC_BLOB_LIMIT);
|
||||
check_gentle_parse("blob:limit=5k", 1, 1, LOFC_BLOB_LIMIT);
|
||||
check_gentle_parse("combine:blob:none+tree:0", 1, 0, LOFC_COMBINE);
|
||||
check_gentle_parse("combine:blob:none+tree:0", 1, 1, LOFC_COMBINE);
|
||||
}
|
||||
|
||||
void test_list_objects_filter_options__auto_allowed(void)
|
||||
{
|
||||
check_gentle_parse("auto", 1, 1, LOFC_AUTO);
|
||||
check_gentle_parse("auto", 0, 0, 0);
|
||||
}
|
||||
|
||||
void test_list_objects_filter_options__combine_auto_fails(void)
|
||||
{
|
||||
check_gentle_parse("combine:auto+blob:none", 0, 1, 0);
|
||||
check_gentle_parse("combine:blob:none+auto", 0, 1, 0);
|
||||
check_gentle_parse("combine:auto+auto", 0, 1, 0);
|
||||
}
|
||||
|
||||
/* Helper to test list_objects_filter_combine() */
|
||||
static void check_combine(const char **specs, size_t nr, const char *expected)
|
||||
{
|
||||
struct string_list spec_list = STRING_LIST_INIT_NODUP;
|
||||
char *actual;
|
||||
|
||||
for (size_t i = 0; i < nr; i++)
|
||||
string_list_append(&spec_list, specs[i]);
|
||||
|
||||
actual = list_objects_filter_combine(&spec_list);
|
||||
|
||||
cl_assert_equal_s(actual, expected);
|
||||
|
||||
free(actual);
|
||||
string_list_clear(&spec_list, 0);
|
||||
}
|
||||
|
||||
void test_list_objects_filter_options__combine_helper(void)
|
||||
{
|
||||
const char *empty[] = { NULL };
|
||||
const char *one[] = { "blob:none" };
|
||||
const char *two[] = { "blob:none", "tree:0" };
|
||||
const char *complex[] = { "blob:limit=1k", "object:type=tag" };
|
||||
const char *needs_encoding[] = { "blob:none", "combine:tree:0+blob:limit=1k" };
|
||||
|
||||
check_combine(empty, 0, NULL);
|
||||
check_combine(one, 1, "blob:none");
|
||||
check_combine(two, 2, "combine:blob:none+tree:0");
|
||||
check_combine(complex, 2, "combine:blob:limit=1k+object:type=tag");
|
||||
check_combine(needs_encoding, 2, "combine:blob:none+combine:tree:0%2bblob:limit=1k");
|
||||
}
|
||||
@ -1219,6 +1219,7 @@ struct transport *transport_get(struct remote *remote, const char *url)
|
||||
*/
|
||||
struct git_transport_data *data = xcalloc(1, sizeof(*data));
|
||||
list_objects_filter_init(&data->options.filter_options);
|
||||
data->options.filter_options.allow_auto_filter = 1;
|
||||
ret->data = data;
|
||||
ret->vtable = &builtin_smart_vtable;
|
||||
ret->smart_options = &(data->options);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user