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
|
`promisor.acceptFromServer` config variable is not set to "None". If
|
||||||
set to "None", this config variable has no effect. See
|
set to "None", this config variable has no effect. See
|
||||||
linkgit:gitprotocol-v2[5].
|
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
|
This is incompatible with `--recurse-submodules=(yes|on-demand)` and takes
|
||||||
precedence over the `fetch.output` config option.
|
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[]
|
ifndef::git-pull[]
|
||||||
`--write-fetch-head`::
|
`--write-fetch-head`::
|
||||||
`--no-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
|
Use the partial clone feature and request that the server sends
|
||||||
a subset of reachable objects according to a given object filter.
|
a subset of reachable objects according to a given object filter.
|
||||||
When using `--filter`, the supplied _<filter-spec>_ is used for
|
When using `--filter`, the supplied _<filter-spec>_ is used for
|
||||||
the partial clone filter. For example, `--filter=blob:none` will
|
the partial clone filter.
|
||||||
filter out all blobs (file contents) until needed by Git. Also,
|
+
|
||||||
`--filter=blob:limit=<size>` will filter out all blobs of size
|
If `--filter=auto` is used the filter specification is determined
|
||||||
at least _<size>_. For more details on filter specifications, see
|
automatically through the 'promisor-remote' protocol (see
|
||||||
the `--filter` option in linkgit:git-rev-list[1].
|
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-filter-submodules`::
|
||||||
Also apply the partial clone filter to any submodules in the repository.
|
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
|
After these mandatory fields, the server MAY advertise the following
|
||||||
optional fields in any order:
|
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
|
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").
|
is compatible with their needs (e.g., checking if both use
|
||||||
It corresponds to the "remote.<name>.partialCloneFilter" config setting.
|
"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
|
`token`:: An authentication token that clients can use when
|
||||||
connecting to the remote. It corresponds to the "remote.<name>.token"
|
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
|
above. Clients MUST ignore fields they don't recognize to allow for
|
||||||
future protocol extensions.
|
future protocol extensions.
|
||||||
|
|
||||||
For now, the client can only use information transmitted through these
|
The client can use information transmitted through these fields to
|
||||||
fields to decide if it accepts the advertised promisor remote. In the
|
decide if it accepts the advertised promisor remote. Also, the client
|
||||||
future that information might be used for other purposes though.
|
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.
|
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"
|
On the server side, the "promisor.advertise" and "promisor.sendFields"
|
||||||
configuration options can be used to control what it advertises. On
|
configuration options can be used to control what it advertises. On
|
||||||
the client side, the "promisor.acceptFromServer" configuration option
|
the client side, the "promisor.acceptFromServer" configuration option
|
||||||
can be used to control what it accepts. See the documentation of these
|
can be used to control what it accepts, and the "promisor.storeFields"
|
||||||
configuration options for more information.
|
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"
|
Note that in the future it would be nice if the "promisor-remote"
|
||||||
protocol capability could be used by the server, when responding to
|
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-example-decorate
|
||||||
CLAR_TEST_SUITES += u-hash
|
CLAR_TEST_SUITES += u-hash
|
||||||
CLAR_TEST_SUITES += u-hashmap
|
CLAR_TEST_SUITES += u-hashmap
|
||||||
|
CLAR_TEST_SUITES += u-list-objects-filter-options
|
||||||
CLAR_TEST_SUITES += u-mem-pool
|
CLAR_TEST_SUITES += u-mem-pool
|
||||||
CLAR_TEST_SUITES += u-oid-array
|
CLAR_TEST_SUITES += u-oid-array
|
||||||
CLAR_TEST_SUITES += u-oidmap
|
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 struct string_list option_optional_reference = STRING_LIST_INIT_NODUP;
|
||||||
static int max_jobs = -1;
|
static int max_jobs = -1;
|
||||||
static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP;
|
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 config_filter_submodules = -1; /* unspecified */
|
||||||
static int option_remote_submodules;
|
static int option_remote_submodules;
|
||||||
|
|
||||||
@ -634,7 +633,9 @@ static int git_sparse_checkout_init(const char *repo)
|
|||||||
return result;
|
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)
|
enum ref_storage_format ref_storage_format)
|
||||||
{
|
{
|
||||||
struct object_id oid;
|
struct object_id oid;
|
||||||
@ -723,9 +724,9 @@ static int checkout(int submodule_progress, int filter_submodules,
|
|||||||
strvec_pushf(&cmd.args, "--ref-format=%s",
|
strvec_pushf(&cmd.args, "--ref-format=%s",
|
||||||
ref_storage_format_to_name(ref_storage_format));
|
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",
|
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)
|
if (option_single_branch >= 0)
|
||||||
strvec_push(&cmd.args, option_single_branch ?
|
strvec_push(&cmd.args, option_single_branch ?
|
||||||
@ -903,6 +904,7 @@ int cmd_clone(int argc,
|
|||||||
enum transport_family family = TRANSPORT_FAMILY_ALL;
|
enum transport_family family = TRANSPORT_FAMILY_ALL;
|
||||||
struct string_list option_config = STRING_LIST_INIT_DUP;
|
struct string_list option_config = STRING_LIST_INIT_DUP;
|
||||||
int option_dissociate = 0;
|
int option_dissociate = 0;
|
||||||
|
struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
|
||||||
int option_filter_submodules = -1; /* unspecified */
|
int option_filter_submodules = -1; /* unspecified */
|
||||||
struct string_list server_options = STRING_LIST_INIT_NODUP;
|
struct string_list server_options = STRING_LIST_INIT_NODUP;
|
||||||
const char *bundle_uri = NULL;
|
const char *bundle_uri = NULL;
|
||||||
@ -999,6 +1001,8 @@ int cmd_clone(int argc,
|
|||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
filter_options.allow_auto_filter = 1;
|
||||||
|
|
||||||
packet_trace_identity("clone");
|
packet_trace_identity("clone");
|
||||||
|
|
||||||
repo_config(the_repository, git_clone_config, NULL);
|
repo_config(the_repository, git_clone_config, NULL);
|
||||||
@ -1625,9 +1629,13 @@ int cmd_clone(int argc,
|
|||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
junk_mode = JUNK_LEAVE_REPO;
|
junk_mode = JUNK_LEAVE_REPO;
|
||||||
err = checkout(submodule_progress, filter_submodules,
|
err = checkout(submodule_progress,
|
||||||
|
&filter_options,
|
||||||
|
filter_submodules,
|
||||||
ref_storage_format);
|
ref_storage_format);
|
||||||
|
|
||||||
|
list_objects_filter_release(&filter_options);
|
||||||
|
|
||||||
string_list_clear(&option_not, 0);
|
string_list_clear(&option_not, 0);
|
||||||
string_list_clear(&option_config, 0);
|
string_list_clear(&option_config, 0);
|
||||||
string_list_clear(&server_options, 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 *gtransport;
|
||||||
static struct transport *gsecondary;
|
static struct transport *gsecondary;
|
||||||
static struct refspec refmap = REFSPEC_INIT_FETCH;
|
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 server_options = STRING_LIST_INIT_DUP;
|
||||||
static struct string_list negotiation_tip = STRING_LIST_INIT_NODUP;
|
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;
|
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;
|
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");
|
set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes");
|
||||||
if (refetch)
|
if (refetch)
|
||||||
set_option(transport, TRANS_OPT_REFETCH, "yes");
|
set_option(transport, TRANS_OPT_REFETCH, "yes");
|
||||||
if (filter_options.choice) {
|
if (filter_options->choice) {
|
||||||
const char *spec =
|
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_LIST_OBJECTS_FILTER, spec);
|
||||||
set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
|
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_transaction *transaction,
|
||||||
struct ref *ref_map,
|
struct ref *ref_map,
|
||||||
struct fetch_head *fetch_head,
|
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;
|
int retcode, cannot_reuse;
|
||||||
|
|
||||||
@ -1507,7 +1508,7 @@ static int backfill_tags(struct display_state *display_state,
|
|||||||
cannot_reuse = transport->cannot_reuse ||
|
cannot_reuse = transport->cannot_reuse ||
|
||||||
deepen_since || deepen_not.nr;
|
deepen_since || deepen_not.nr;
|
||||||
if (cannot_reuse) {
|
if (cannot_reuse) {
|
||||||
gsecondary = prepare_transport(transport->remote, 0);
|
gsecondary = prepare_transport(transport->remote, 0, filter_options);
|
||||||
transport = gsecondary;
|
transport = gsecondary;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1713,7 +1714,8 @@ out:
|
|||||||
|
|
||||||
static int do_fetch(struct transport *transport,
|
static int do_fetch(struct transport *transport,
|
||||||
struct refspec *rs,
|
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_transaction *transaction = NULL;
|
||||||
struct ref *ref_map = NULL;
|
struct ref *ref_map = NULL;
|
||||||
@ -1873,7 +1875,7 @@ static int do_fetch(struct transport *transport,
|
|||||||
* the transaction and don't commit anything.
|
* the transaction and don't commit anything.
|
||||||
*/
|
*/
|
||||||
if (backfill_tags(&display_state, transport, transaction, tags_ref_map,
|
if (backfill_tags(&display_state, transport, transaction, tags_ref_map,
|
||||||
&fetch_head, config))
|
&fetch_head, config, filter_options))
|
||||||
retcode = 1;
|
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
|
* Fetching from the promisor remote should use the given filter-spec
|
||||||
* or inherit the default filter-spec from the config.
|
* 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
|
* Explicit --no-filter argument overrides everything, regardless
|
||||||
* of any prior partial clones and fetches.
|
* of any prior partial clones and fetches.
|
||||||
*/
|
*/
|
||||||
if (filter_options.no_filter)
|
if (filter_options->no_filter)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If no prior partial clone/fetch and the current fetch DID NOT
|
* If no prior partial clone/fetch and the current fetch DID NOT
|
||||||
* request a partial-fetch, do a normal fetch.
|
* 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;
|
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
|
* filter-spec as the default for subsequent fetches to this
|
||||||
* remote if there is currently no default filter-spec.
|
* remote if there is currently no default filter-spec.
|
||||||
*/
|
*/
|
||||||
if (filter_options.choice) {
|
if (filter_options->choice) {
|
||||||
partial_clone_register(remote->name, &filter_options);
|
partial_clone_register(remote->name, filter_options);
|
||||||
return;
|
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
|
* explicitly given filter-spec or inherit the filter-spec from
|
||||||
* the config.
|
* the config.
|
||||||
*/
|
*/
|
||||||
if (!filter_options.choice)
|
if (!filter_options->choice)
|
||||||
partial_clone_get_default_filter_spec(&filter_options, remote->name);
|
partial_clone_get_default_filter_spec(filter_options, remote->name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int fetch_one(struct remote *remote, int argc, const char **argv,
|
static int fetch_one(struct remote *remote, int argc, const char **argv,
|
||||||
int prune_tags_ok, int use_stdin_refspecs,
|
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;
|
struct refspec rs = REFSPEC_INIT_FETCH;
|
||||||
int i;
|
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"
|
die(_("no remote repository specified; please specify either a URL or a\n"
|
||||||
"remote name from which new revisions should be fetched"));
|
"remote name from which new revisions should be fetched"));
|
||||||
|
|
||||||
gtransport = prepare_transport(remote, 1);
|
gtransport = prepare_transport(remote, 1, filter_options);
|
||||||
|
|
||||||
if (prune < 0) {
|
if (prune < 0) {
|
||||||
/* no command line request */
|
/* 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);
|
sigchain_push_common(unlock_pack_on_signal);
|
||||||
atexit(unlock_pack_atexit);
|
atexit(unlock_pack_atexit);
|
||||||
sigchain_push(SIGPIPE, SIG_IGN);
|
sigchain_push(SIGPIPE, SIG_IGN);
|
||||||
exit_code = do_fetch(gtransport, &rs, config);
|
exit_code = do_fetch(gtransport, &rs, config, filter_options);
|
||||||
sigchain_pop(SIGPIPE);
|
sigchain_pop(SIGPIPE);
|
||||||
refspec_clear(&rs);
|
refspec_clear(&rs);
|
||||||
transport_disconnect(gtransport);
|
transport_disconnect(gtransport);
|
||||||
@ -2329,6 +2333,7 @@ int cmd_fetch(int argc,
|
|||||||
const char *submodule_prefix = "";
|
const char *submodule_prefix = "";
|
||||||
const char *bundle_uri;
|
const char *bundle_uri;
|
||||||
struct string_list list = STRING_LIST_INIT_DUP;
|
struct string_list list = STRING_LIST_INIT_DUP;
|
||||||
|
struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
|
||||||
struct remote *remote = NULL;
|
struct remote *remote = NULL;
|
||||||
int all = -1, multiple = 0;
|
int all = -1, multiple = 0;
|
||||||
int result = 0;
|
int result = 0;
|
||||||
@ -2434,6 +2439,8 @@ int cmd_fetch(int argc,
|
|||||||
OPT_END()
|
OPT_END()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
filter_options.allow_auto_filter = 1;
|
||||||
|
|
||||||
packet_trace_identity("fetch");
|
packet_trace_identity("fetch");
|
||||||
|
|
||||||
/* Record the command line for the reflog */
|
/* Record the command line for the reflog */
|
||||||
@ -2594,7 +2601,7 @@ int cmd_fetch(int argc,
|
|||||||
trace2_region_enter("fetch", "negotiate-only", the_repository);
|
trace2_region_enter("fetch", "negotiate-only", the_repository);
|
||||||
if (!remote)
|
if (!remote)
|
||||||
die(_("must supply remote when using --negotiate-only"));
|
die(_("must supply remote when using --negotiate-only"));
|
||||||
gtransport = prepare_transport(remote, 1);
|
gtransport = prepare_transport(remote, 1, &filter_options);
|
||||||
if (gtransport->smart_options) {
|
if (gtransport->smart_options) {
|
||||||
gtransport->smart_options->acked_commits = &acked_commits;
|
gtransport->smart_options->acked_commits = &acked_commits;
|
||||||
} else {
|
} else {
|
||||||
@ -2616,12 +2623,12 @@ int cmd_fetch(int argc,
|
|||||||
} else if (remote) {
|
} else if (remote) {
|
||||||
if (filter_options.choice || repo_has_promisor_remote(the_repository)) {
|
if (filter_options.choice || repo_has_promisor_remote(the_repository)) {
|
||||||
trace2_region_enter("fetch", "setup-partial", 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_leave("fetch", "setup-partial", the_repository);
|
||||||
}
|
}
|
||||||
trace2_region_enter("fetch", "fetch-one", the_repository);
|
trace2_region_enter("fetch", "fetch-one", the_repository);
|
||||||
result = fetch_one(remote, argc, argv, prune_tags_ok, stdin_refspecs,
|
result = fetch_one(remote, argc, argv, prune_tags_ok, stdin_refspecs,
|
||||||
&config);
|
&config, &filter_options);
|
||||||
trace2_region_leave("fetch", "fetch-one", the_repository);
|
trace2_region_leave("fetch", "fetch-one", the_repository);
|
||||||
} else {
|
} else {
|
||||||
int max_children = max_jobs;
|
int max_children = max_jobs;
|
||||||
@ -2727,5 +2734,6 @@ int cmd_fetch(int argc,
|
|||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
string_list_clear(&list, 0);
|
string_list_clear(&list, 0);
|
||||||
|
list_objects_filter_release(&filter_options);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
20
fetch-pack.c
20
fetch-pack.c
@ -35,6 +35,7 @@
|
|||||||
#include "sigchain.h"
|
#include "sigchain.h"
|
||||||
#include "mergesort.h"
|
#include "mergesort.h"
|
||||||
#include "prio-queue.h"
|
#include "prio-queue.h"
|
||||||
|
#include "promisor-remote.h"
|
||||||
|
|
||||||
static int transfer_unpack_limit = -1;
|
static int transfer_unpack_limit = -1;
|
||||||
static int fetch_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;
|
struct string_list packfile_uris = STRING_LIST_INIT_DUP;
|
||||||
int i;
|
int i;
|
||||||
struct strvec index_pack_args = STRVEC_INIT;
|
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;
|
negotiator = &negotiator_alloc;
|
||||||
if (args->refetch)
|
if (args->refetch)
|
||||||
|
|||||||
@ -20,6 +20,8 @@ const char *list_object_filter_config_name(enum list_objects_filter_choice c)
|
|||||||
case LOFC_DISABLED:
|
case LOFC_DISABLED:
|
||||||
/* we have no name for "no filter at all" */
|
/* we have no name for "no filter at all" */
|
||||||
break;
|
break;
|
||||||
|
case LOFC_AUTO:
|
||||||
|
return "auto";
|
||||||
case LOFC_BLOB_NONE:
|
case LOFC_BLOB_NONE:
|
||||||
return "blob:none";
|
return "blob:none";
|
||||||
case LOFC_BLOB_LIMIT:
|
case LOFC_BLOB_LIMIT:
|
||||||
@ -52,7 +54,17 @@ int gently_parse_list_objects_filter(
|
|||||||
if (filter_options->choice)
|
if (filter_options->choice)
|
||||||
BUG("filter_options already populated");
|
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;
|
filter_options->choice = LOFC_BLOB_NONE;
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
@ -146,10 +158,20 @@ static int parse_combine_subfilter(
|
|||||||
|
|
||||||
decoded = url_percent_decode(subspec->buf);
|
decoded = url_percent_decode(subspec->buf);
|
||||||
|
|
||||||
result = has_reserved_character(subspec, errbuf) ||
|
result = has_reserved_character(subspec, errbuf);
|
||||||
gently_parse_list_objects_filter(
|
if (result)
|
||||||
&filter_options->sub[new_index], decoded, errbuf);
|
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);
|
free(decoded);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -208,6 +230,41 @@ static void filter_spec_append_urlencode(
|
|||||||
filter->filter_spec.buf + orig_len);
|
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
|
* Changes filter_options into an equivalent LOFC_COMBINE filter options
|
||||||
* instance. Does not do anything if filter_options is already LOFC_COMBINE.
|
* instance. Does not do anything if filter_options is already LOFC_COMBINE.
|
||||||
@ -263,6 +320,9 @@ void parse_list_objects_filter(
|
|||||||
} else {
|
} else {
|
||||||
struct list_objects_filter_options *sub;
|
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
|
* Make filter_options an LOFC_COMBINE spec so we can trivially
|
||||||
* add subspecs to it.
|
* add subspecs to it.
|
||||||
@ -277,6 +337,9 @@ void parse_list_objects_filter(
|
|||||||
if (gently_parse_list_objects_filter(sub, arg, &errbuf))
|
if (gently_parse_list_objects_filter(sub, arg, &errbuf))
|
||||||
die("%s", errbuf.buf);
|
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, '+');
|
strbuf_addch(&filter_options->filter_spec, '+');
|
||||||
filter_spec_append_urlencode(filter_options, arg);
|
filter_spec_append_urlencode(filter_options, arg);
|
||||||
}
|
}
|
||||||
@ -317,6 +380,7 @@ void list_objects_filter_release(
|
|||||||
struct list_objects_filter_options *filter_options)
|
struct list_objects_filter_options *filter_options)
|
||||||
{
|
{
|
||||||
size_t sub;
|
size_t sub;
|
||||||
|
unsigned int allow_auto_filter = filter_options->allow_auto_filter;
|
||||||
|
|
||||||
if (!filter_options)
|
if (!filter_options)
|
||||||
return;
|
return;
|
||||||
@ -326,6 +390,7 @@ void list_objects_filter_release(
|
|||||||
list_objects_filter_release(&filter_options->sub[sub]);
|
list_objects_filter_release(&filter_options->sub[sub]);
|
||||||
free(filter_options->sub);
|
free(filter_options->sub);
|
||||||
list_objects_filter_init(filter_options);
|
list_objects_filter_init(filter_options);
|
||||||
|
filter_options->allow_auto_filter = allow_auto_filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
void partial_clone_register(
|
void partial_clone_register(
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#include "strbuf.h"
|
#include "strbuf.h"
|
||||||
|
|
||||||
struct option;
|
struct option;
|
||||||
|
struct string_list;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The list of defined filters for list-objects.
|
* The list of defined filters for list-objects.
|
||||||
@ -18,6 +19,7 @@ enum list_objects_filter_choice {
|
|||||||
LOFC_SPARSE_OID,
|
LOFC_SPARSE_OID,
|
||||||
LOFC_OBJECT_TYPE,
|
LOFC_OBJECT_TYPE,
|
||||||
LOFC_COMBINE,
|
LOFC_COMBINE,
|
||||||
|
LOFC_AUTO,
|
||||||
LOFC__COUNT /* must be last */
|
LOFC__COUNT /* must be last */
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -50,6 +52,11 @@ struct list_objects_filter_options {
|
|||||||
*/
|
*/
|
||||||
unsigned int no_filter : 1;
|
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
|
* BEGIN choice-specific parsed values from within the filter-spec. Only
|
||||||
* some values will be defined for any given choice.
|
* some values will be defined for any given choice.
|
||||||
@ -162,4 +169,22 @@ void list_objects_filter_copy(
|
|||||||
struct list_objects_filter_options *dest,
|
struct list_objects_filter_options *dest,
|
||||||
const struct list_objects_filter_options *src);
|
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 */
|
#endif /* LIST_OBJECTS_FILTER_OPTIONS_H */
|
||||||
|
|||||||
@ -745,6 +745,13 @@ static void filter_combine__init(
|
|||||||
filter->finalize_omits_fn = filter_combine__finalize_omits;
|
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)(
|
typedef void (*filter_init_fn)(
|
||||||
struct list_objects_filter_options *filter_options,
|
struct list_objects_filter_options *filter_options,
|
||||||
struct filter *filter);
|
struct filter *filter);
|
||||||
@ -760,6 +767,7 @@ static filter_init_fn s_filters[] = {
|
|||||||
filter_sparse_oid__init,
|
filter_sparse_oid__init,
|
||||||
filter_object_type__init,
|
filter_object_type__init,
|
||||||
filter_combine__init,
|
filter_combine__init,
|
||||||
|
filter_auto__init,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct filter *list_objects_filter__init(
|
struct filter *list_objects_filter__init(
|
||||||
|
|||||||
@ -193,6 +193,7 @@ void promisor_remote_clear(struct promisor_remote_config *config)
|
|||||||
while (config->promisors) {
|
while (config->promisors) {
|
||||||
struct promisor_remote *r = config->promisors;
|
struct promisor_remote *r = config->promisors;
|
||||||
free(r->partial_clone_filter);
|
free(r->partial_clone_filter);
|
||||||
|
free(r->advertised_filter);
|
||||||
config->promisors = config->promisors->next;
|
config->promisors = config->promisors->next;
|
||||||
free(r);
|
free(r);
|
||||||
}
|
}
|
||||||
@ -375,18 +376,24 @@ static char *fields_from_config(struct string_list *fields_list, const char *con
|
|||||||
return fields;
|
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_sent(void)
|
||||||
{
|
{
|
||||||
static struct string_list fields_list = STRING_LIST_INIT_NODUP;
|
static struct string_list fields_list = STRING_LIST_INIT_NODUP;
|
||||||
static int initialized;
|
static int initialized;
|
||||||
|
|
||||||
if (!initialized) {
|
return initialize_fields_list(&fields_list, &initialized, "promisor.sendFields");
|
||||||
fields_list.cmp = strcasecmp;
|
|
||||||
fields_from_config(&fields_list, "promisor.sendFields");
|
|
||||||
initialized = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return &fields_list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct string_list *fields_checked(void)
|
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 struct string_list fields_list = STRING_LIST_INIT_NODUP;
|
||||||
static int initialized;
|
static int initialized;
|
||||||
|
|
||||||
if (!initialized) {
|
return initialize_fields_list(&fields_list, &initialized, "promisor.checkFields");
|
||||||
fields_list.cmp = strcasecmp;
|
}
|
||||||
fields_from_config(&fields_list, "promisor.checkFields");
|
|
||||||
initialized = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
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,
|
static void filter_promisor_remote(struct repository *repo,
|
||||||
struct strvec *accepted,
|
struct strvec *accepted,
|
||||||
const char *info)
|
const char *info)
|
||||||
@ -700,7 +835,10 @@ static void filter_promisor_remote(struct repository *repo,
|
|||||||
enum accept_promisor accept = ACCEPT_NONE;
|
enum accept_promisor accept = ACCEPT_NONE;
|
||||||
struct string_list config_info = STRING_LIST_INIT_NODUP;
|
struct string_list config_info = STRING_LIST_INIT_NODUP;
|
||||||
struct string_list remote_info = STRING_LIST_INIT_DUP;
|
struct string_list remote_info = STRING_LIST_INIT_DUP;
|
||||||
|
struct store_info *store_info = NULL;
|
||||||
struct string_list_item *item;
|
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 (!repo_config_get_string_tmp(the_repository, "promisor.acceptfromserver", &accept_str)) {
|
||||||
if (!*accept_str || !strcasecmp("None", 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);
|
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);
|
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_free(advertised);
|
||||||
}
|
}
|
||||||
|
|
||||||
promisor_info_list_clear(&config_info);
|
promisor_info_list_clear(&config_info);
|
||||||
string_list_clear(&remote_info, 0);
|
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)
|
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);
|
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 {
|
||||||
struct promisor_remote *next;
|
struct promisor_remote *next;
|
||||||
char *partial_clone_filter;
|
char *partial_clone_filter;
|
||||||
|
char *advertised_filter;
|
||||||
unsigned int accepted : 1;
|
unsigned int accepted : 1;
|
||||||
const char name[FLEX_ARRAY];
|
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);
|
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 */
|
#endif /* PROMISOR_REMOTE_H */
|
||||||
|
|||||||
@ -4,6 +4,7 @@ clar_test_suites = [
|
|||||||
'unit-tests/u-example-decorate.c',
|
'unit-tests/u-example-decorate.c',
|
||||||
'unit-tests/u-hash.c',
|
'unit-tests/u-hash.c',
|
||||||
'unit-tests/u-hashmap.c',
|
'unit-tests/u-hashmap.c',
|
||||||
|
'unit-tests/u-list-objects-filter-options.c',
|
||||||
'unit-tests/u-mem-pool.c',
|
'unit-tests/u-mem-pool.c',
|
||||||
'unit-tests/u-oid-array.c',
|
'unit-tests/u-oid-array.c',
|
||||||
'unit-tests/u-oidmap.c',
|
'unit-tests/u-oidmap.c',
|
||||||
|
|||||||
@ -360,6 +360,115 @@ test_expect_success "clone with promisor.checkFields" '
|
|||||||
check_missing_objects server 1 "$oid"
|
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" '
|
test_expect_success "clone with promisor.advertise set to 'true' but don't delete the client" '
|
||||||
git -C server config promisor.advertise true &&
|
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));
|
struct git_transport_data *data = xcalloc(1, sizeof(*data));
|
||||||
list_objects_filter_init(&data->options.filter_options);
|
list_objects_filter_init(&data->options.filter_options);
|
||||||
|
data->options.filter_options.allow_auto_filter = 1;
|
||||||
ret->data = data;
|
ret->data = data;
|
||||||
ret->vtable = &builtin_smart_vtable;
|
ret->vtable = &builtin_smart_vtable;
|
||||||
ret->smart_options = &(data->options);
|
ret->smart_options = &(data->options);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user