Merge branch 'cc/promisor-remote-capability'
The "promisor-remote" capability mechanism has been updated to allow the "partialCloneFilter" settings and the "token" value to be communicated from the server side. * cc/promisor-remote-capability: promisor-remote: use string_list_split() in mark_remotes_as_accepted() promisor-remote: allow a client to check fields promisor-remote: use string_list_split() in filter_promisor_remote() promisor-remote: refactor how we parse advertised fields promisor-remote: use string constants for 'name' and 'url' too promisor-remote: allow a server to advertise more fields promisor-remote: refactor to get rid of 'struct strvec'main
commit
2be606a3bd
|
@ -9,6 +9,28 @@ promisor.advertise::
|
||||||
"false", which means the "promisor-remote" capability is not
|
"false", which means the "promisor-remote" capability is not
|
||||||
advertised.
|
advertised.
|
||||||
|
|
||||||
|
promisor.sendFields::
|
||||||
|
A comma or space separated list of additional remote related
|
||||||
|
field names. A server sends these field names and the
|
||||||
|
associated field values from its configuration when
|
||||||
|
advertising its promisor remotes using the "promisor-remote"
|
||||||
|
capability, see linkgit:gitprotocol-v2[5]. Currently, only the
|
||||||
|
"partialCloneFilter" and "token" field names are supported.
|
||||||
|
+
|
||||||
|
`partialCloneFilter`:: contains the partial clone filter
|
||||||
|
used for the remote.
|
||||||
|
+
|
||||||
|
`token`:: contains an authentication token for the remote.
|
||||||
|
+
|
||||||
|
When a field name is part of this list and a corresponding
|
||||||
|
"remote.foo.<field-name>" config variable is set on the server to a
|
||||||
|
non-empty value, then the field name and value are sent when
|
||||||
|
advertising the promisor remote "foo".
|
||||||
|
+
|
||||||
|
This list has no effect unless the "promisor.advertise" config
|
||||||
|
variable is set to "true", and the "name" and "url" fields are always
|
||||||
|
advertised regardless of this setting.
|
||||||
|
|
||||||
promisor.acceptFromServer::
|
promisor.acceptFromServer::
|
||||||
If set to "all", a client will accept all the promisor remotes
|
If set to "all", a client will accept all the promisor remotes
|
||||||
a server might advertise using the "promisor-remote"
|
a server might advertise using the "promisor-remote"
|
||||||
|
@ -28,3 +50,42 @@ promisor.acceptFromServer::
|
||||||
lazily fetchable from this promisor remote from its responses
|
lazily fetchable from this promisor remote from its responses
|
||||||
to "fetch" and "clone" requests from the client. Name and URL
|
to "fetch" and "clone" requests from the client. Name and URL
|
||||||
comparisons are case sensitive. See linkgit:gitprotocol-v2[5].
|
comparisons are case sensitive. See linkgit:gitprotocol-v2[5].
|
||||||
|
|
||||||
|
promisor.checkFields::
|
||||||
|
A comma or space separated list of additional remote related
|
||||||
|
field names. A client checks if the values of these fields
|
||||||
|
transmitted by a server correspond to the values of these
|
||||||
|
fields in its own configuration before accepting a promisor
|
||||||
|
remote. Currently, "partialCloneFilter" and "token" are the
|
||||||
|
only supported field names.
|
||||||
|
+
|
||||||
|
If one of these field names (e.g., "token") is being checked for an
|
||||||
|
advertised promisor remote (e.g., "foo"), three conditions must be met
|
||||||
|
for the check of this specific field to pass:
|
||||||
|
+
|
||||||
|
1. The corresponding local configuration (e.g., `remote.foo.token`)
|
||||||
|
must be set.
|
||||||
|
2. The server must advertise the "token" field for remote "foo".
|
||||||
|
3. The value of the locally configured `remote.foo.token` must exactly
|
||||||
|
match the value advertised by the server for the "token" field.
|
||||||
|
+
|
||||||
|
If any of these conditions is not met for any field name listed in
|
||||||
|
`promisor.checkFields`, the advertised remote "foo" is rejected.
|
||||||
|
+
|
||||||
|
For the "partialCloneFilter" field, this allows the client to ensure
|
||||||
|
that the server's filter matches what it expects locally, preventing
|
||||||
|
inconsistencies in filtering behavior. For the "token" field, this can
|
||||||
|
be used to verify that authentication credentials match expected
|
||||||
|
values.
|
||||||
|
+
|
||||||
|
Field values are compared case-sensitively.
|
||||||
|
+
|
||||||
|
The "name" and "url" fields are always checked according to the
|
||||||
|
`promisor.acceptFromServer` policy, independently of this setting.
|
||||||
|
+
|
||||||
|
The field names and values should be passed by the server through the
|
||||||
|
"promisor-remote" capability by using the `promisor.sendFields` config
|
||||||
|
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].
|
||||||
|
|
|
@ -785,33 +785,64 @@ retrieving the header from a bundle at the indicated URI, and thus
|
||||||
save themselves and the server(s) the request(s) needed to inspect the
|
save themselves and the server(s) the request(s) needed to inspect the
|
||||||
headers of that bundle or bundles.
|
headers of that bundle or bundles.
|
||||||
|
|
||||||
promisor-remote=<pr-infos>
|
promisor-remote=<pr-info>
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
The server may advertise some promisor remotes it is using or knows
|
The server may advertise some promisor remotes it is using or knows
|
||||||
about to a client which may want to use them as its promisor remotes,
|
about to a client which may want to use them as its promisor remotes,
|
||||||
instead of this repository. In this case <pr-infos> should be of the
|
instead of this repository. In this case <pr-info> should be of the
|
||||||
form:
|
form:
|
||||||
|
|
||||||
pr-infos = pr-info | pr-infos ";" pr-info
|
pr-info = pr-fields | pr-info ";" pr-fields
|
||||||
|
|
||||||
pr-info = "name=" pr-name | "name=" pr-name "," "url=" pr-url
|
pr-fields = pr-field | pr-fields "," pr-field
|
||||||
|
|
||||||
where `pr-name` is the urlencoded name of a promisor remote, and
|
pr-field = field-name "=" field-value
|
||||||
`pr-url` the urlencoded URL of that promisor remote.
|
|
||||||
|
|
||||||
In this case, if the client decides to use one or more promisor
|
where all the `field-name` and `field-value` in a given `pr-fields`
|
||||||
remotes the server advertised, it can reply with
|
are field names and values related to a single promisor remote. A
|
||||||
"promisor-remote=<pr-names>" where <pr-names> should be of the form:
|
given `field-name` MUST NOT appear more than once in given
|
||||||
|
`pr-fields`.
|
||||||
|
|
||||||
|
The server MUST advertise at least the "name" and "url" field names
|
||||||
|
along with the associated field values, which are the name of a valid
|
||||||
|
remote and its URL, in each `pr-fields`. The "name" and "url" fields
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
|
||||||
|
`token`:: An authentication token that clients can use when
|
||||||
|
connecting to the remote. It corresponds to the "remote.<name>.token"
|
||||||
|
config setting.
|
||||||
|
|
||||||
|
No other fields are defined by the protocol at this time. Field names
|
||||||
|
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.
|
||||||
|
|
||||||
|
Field values MUST be urlencoded.
|
||||||
|
|
||||||
|
If the client decides to use one or more promisor remotes the server
|
||||||
|
advertised, it can reply with "promisor-remote=<pr-names>" where
|
||||||
|
<pr-names> should be of the form:
|
||||||
|
|
||||||
pr-names = pr-name | pr-names ";" pr-name
|
pr-names = pr-name | pr-names ";" pr-name
|
||||||
|
|
||||||
where `pr-name` is the urlencoded name of a promisor remote the server
|
where `pr-name` is the urlencoded name of a promisor remote the server
|
||||||
advertised and the client accepts.
|
advertised and the client accepts.
|
||||||
|
|
||||||
Note that, everywhere in this document, `pr-name` MUST be a valid
|
Note that, everywhere in this document, the ';' and ',' characters
|
||||||
remote name, and the ';' and ',' characters MUST be encoded if they
|
MUST be encoded if they appear in `pr-name` or `field-value`.
|
||||||
appear in `pr-name` or `pr-url`.
|
|
||||||
|
|
||||||
If the server doesn't know any promisor remote that could be good for
|
If the server doesn't know any promisor remote that could be good for
|
||||||
a client to use, or prefers a client not to use any promisor remote it
|
a client to use, or prefers a client not to use any promisor remote it
|
||||||
|
@ -822,9 +853,10 @@ In this case, or if the client doesn't want to use any promisor remote
|
||||||
the server advertised, the client shouldn't advertise the
|
the server advertised, the client shouldn't advertise the
|
||||||
"promisor-remote" capability at all in its reply.
|
"promisor-remote" capability at all in its reply.
|
||||||
|
|
||||||
The "promisor.advertise" and "promisor.acceptFromServer" configuration
|
On the server side, the "promisor.advertise" and "promisor.sendFields"
|
||||||
options can be used on the server and client side to control what they
|
configuration options can be used to control what it advertises. On
|
||||||
advertise or accept respectively. See the documentation of these
|
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.
|
configuration options 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"
|
||||||
|
|
|
@ -314,9 +314,162 @@ static int allow_unsanitized(char ch)
|
||||||
return ch > 32 && ch < 127;
|
return ch > 32 && ch < 127;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void promisor_info_vecs(struct repository *repo,
|
/*
|
||||||
struct strvec *names,
|
* All the fields used in "promisor-remote" protocol capability,
|
||||||
struct strvec *urls)
|
* including the mandatory "name" and "url" ones.
|
||||||
|
*/
|
||||||
|
static const char promisor_field_name[] = "name";
|
||||||
|
static const char promisor_field_url[] = "url";
|
||||||
|
static const char promisor_field_filter[] = "partialCloneFilter";
|
||||||
|
static const char promisor_field_token[] = "token";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* List of optional field names that can be used in the
|
||||||
|
* "promisor-remote" protocol capability (others must be
|
||||||
|
* ignored). Each field should correspond to a configurable property
|
||||||
|
* of a remote that can be relevant for the client.
|
||||||
|
*/
|
||||||
|
static const char *known_fields[] = {
|
||||||
|
promisor_field_filter, /* Filter used for partial clone */
|
||||||
|
promisor_field_token, /* Authentication token for the remote */
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if 'field' is in the list of the known field names for the
|
||||||
|
* "promisor-remote" protocol capability.
|
||||||
|
*/
|
||||||
|
static int is_known_field(const char *field)
|
||||||
|
{
|
||||||
|
const char **p;
|
||||||
|
|
||||||
|
for (p = known_fields; *p; p++)
|
||||||
|
if (!strcasecmp(*p, field))
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_valid_field(struct string_list_item *item, void *cb_data)
|
||||||
|
{
|
||||||
|
const char *field = item->string;
|
||||||
|
const char *config_key = (const char *)cb_data;
|
||||||
|
|
||||||
|
if (!is_known_field(field)) {
|
||||||
|
warning(_("unsupported field '%s' in '%s' config"), field, config_key);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *fields_from_config(struct string_list *fields_list, const char *config_key)
|
||||||
|
{
|
||||||
|
char *fields = NULL;
|
||||||
|
|
||||||
|
if (!repo_config_get_string(the_repository, config_key, &fields) && *fields) {
|
||||||
|
string_list_split_in_place_f(fields_list, fields, ",", -1,
|
||||||
|
STRING_LIST_SPLIT_TRIM |
|
||||||
|
STRING_LIST_SPLIT_NONEMPTY);
|
||||||
|
filter_string_list(fields_list, 0, is_valid_field, (void *)config_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 &fields_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Struct for promisor remotes involved in the "promisor-remote"
|
||||||
|
* protocol capability.
|
||||||
|
*
|
||||||
|
* Except for "name", each <member> in this struct and its <value>
|
||||||
|
* should correspond (either on the client side or on the server side)
|
||||||
|
* to a "remote.<name>.<member>" config variable set to <value> where
|
||||||
|
* "<name>" is a promisor remote name.
|
||||||
|
*/
|
||||||
|
struct promisor_info {
|
||||||
|
const char *name;
|
||||||
|
const char *url;
|
||||||
|
const char *filter;
|
||||||
|
const char *token;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void promisor_info_free(struct promisor_info *p)
|
||||||
|
{
|
||||||
|
free((char *)p->name);
|
||||||
|
free((char *)p->url);
|
||||||
|
free((char *)p->filter);
|
||||||
|
free((char *)p->token);
|
||||||
|
free(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void promisor_info_list_clear(struct string_list *list)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < list->nr; i++)
|
||||||
|
promisor_info_free(list->items[i].util);
|
||||||
|
string_list_clear(list, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_one_field(struct promisor_info *p,
|
||||||
|
const char *field, const char *value)
|
||||||
|
{
|
||||||
|
if (!strcasecmp(field, promisor_field_filter))
|
||||||
|
p->filter = xstrdup(value);
|
||||||
|
else if (!strcasecmp(field, promisor_field_token))
|
||||||
|
p->token = xstrdup(value);
|
||||||
|
else
|
||||||
|
BUG("invalid field '%s'", field);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_fields(struct promisor_info *p,
|
||||||
|
struct string_list *field_names)
|
||||||
|
{
|
||||||
|
struct string_list_item *item;
|
||||||
|
|
||||||
|
for_each_string_list_item(item, field_names) {
|
||||||
|
char *key = xstrfmt("remote.%s.%s", p->name, item->string);
|
||||||
|
const char *val;
|
||||||
|
if (!repo_config_get_string_tmp(the_repository, key, &val) && *val)
|
||||||
|
set_one_field(p, item->string, val);
|
||||||
|
free(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Populate 'list' with promisor remote information from the config.
|
||||||
|
* The 'util' pointer of each list item will hold a 'struct
|
||||||
|
* promisor_info'. Except "name" and "url", only members of that
|
||||||
|
* struct specified by the 'field_names' list are set (using values
|
||||||
|
* from the configuration).
|
||||||
|
*/
|
||||||
|
static void promisor_config_info_list(struct repository *repo,
|
||||||
|
struct string_list *list,
|
||||||
|
struct string_list *field_names)
|
||||||
{
|
{
|
||||||
struct promisor_remote *r;
|
struct promisor_remote *r;
|
||||||
|
|
||||||
|
@ -328,8 +481,17 @@ static void promisor_info_vecs(struct repository *repo,
|
||||||
|
|
||||||
/* Only add remotes with a non empty URL */
|
/* Only add remotes with a non empty URL */
|
||||||
if (!repo_config_get_string_tmp(the_repository, url_key, &url) && *url) {
|
if (!repo_config_get_string_tmp(the_repository, url_key, &url) && *url) {
|
||||||
strvec_push(names, r->name);
|
struct promisor_info *new_info = xcalloc(1, sizeof(*new_info));
|
||||||
strvec_push(urls, url);
|
struct string_list_item *item;
|
||||||
|
|
||||||
|
new_info->name = xstrdup(r->name);
|
||||||
|
new_info->url = xstrdup(url);
|
||||||
|
|
||||||
|
if (field_names)
|
||||||
|
set_fields(new_info, field_names);
|
||||||
|
|
||||||
|
item = string_list_append(list, new_info->name);
|
||||||
|
item->util = new_info;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(url_key);
|
free(url_key);
|
||||||
|
@ -340,47 +502,45 @@ char *promisor_remote_info(struct repository *repo)
|
||||||
{
|
{
|
||||||
struct strbuf sb = STRBUF_INIT;
|
struct strbuf sb = STRBUF_INIT;
|
||||||
int advertise_promisors = 0;
|
int advertise_promisors = 0;
|
||||||
struct strvec names = STRVEC_INIT;
|
struct string_list config_info = STRING_LIST_INIT_NODUP;
|
||||||
struct strvec urls = STRVEC_INIT;
|
struct string_list_item *item;
|
||||||
|
|
||||||
repo_config_get_bool(the_repository, "promisor.advertise", &advertise_promisors);
|
repo_config_get_bool(the_repository, "promisor.advertise", &advertise_promisors);
|
||||||
|
|
||||||
if (!advertise_promisors)
|
if (!advertise_promisors)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
promisor_info_vecs(repo, &names, &urls);
|
promisor_config_info_list(repo, &config_info, fields_sent());
|
||||||
|
|
||||||
if (!names.nr)
|
if (!config_info.nr)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
for (size_t i = 0; i < names.nr; i++) {
|
for_each_string_list_item(item, &config_info) {
|
||||||
if (i)
|
struct promisor_info *p = item->util;
|
||||||
|
|
||||||
|
if (item != config_info.items)
|
||||||
strbuf_addch(&sb, ';');
|
strbuf_addch(&sb, ';');
|
||||||
strbuf_addstr(&sb, "name=");
|
|
||||||
strbuf_addstr_urlencode(&sb, names.v[i], allow_unsanitized);
|
strbuf_addf(&sb, "%s=", promisor_field_name);
|
||||||
strbuf_addstr(&sb, ",url=");
|
strbuf_addstr_urlencode(&sb, p->name, allow_unsanitized);
|
||||||
strbuf_addstr_urlencode(&sb, urls.v[i], allow_unsanitized);
|
strbuf_addf(&sb, ",%s=", promisor_field_url);
|
||||||
|
strbuf_addstr_urlencode(&sb, p->url, allow_unsanitized);
|
||||||
|
|
||||||
|
if (p->filter) {
|
||||||
|
strbuf_addf(&sb, ",%s=", promisor_field_filter);
|
||||||
|
strbuf_addstr_urlencode(&sb, p->filter, allow_unsanitized);
|
||||||
|
}
|
||||||
|
if (p->token) {
|
||||||
|
strbuf_addf(&sb, ",%s=", promisor_field_token);
|
||||||
|
strbuf_addstr_urlencode(&sb, p->token, allow_unsanitized);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
strvec_clear(&names);
|
promisor_info_list_clear(&config_info);
|
||||||
strvec_clear(&urls);
|
|
||||||
|
|
||||||
return strbuf_detach(&sb, NULL);
|
return strbuf_detach(&sb, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Find first index of 'nicks' where there is 'nick'. 'nick' is
|
|
||||||
* compared case sensitively to the strings in 'nicks'. If not found
|
|
||||||
* 'nicks->nr' is returned.
|
|
||||||
*/
|
|
||||||
static size_t remote_nick_find(struct strvec *nicks, const char *nick)
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < nicks->nr; i++)
|
|
||||||
if (!strcmp(nicks->v[i], nick))
|
|
||||||
return i;
|
|
||||||
return nicks->nr;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum accept_promisor {
|
enum accept_promisor {
|
||||||
ACCEPT_NONE = 0,
|
ACCEPT_NONE = 0,
|
||||||
ACCEPT_KNOWN_URL,
|
ACCEPT_KNOWN_URL,
|
||||||
|
@ -388,23 +548,84 @@ enum accept_promisor {
|
||||||
ACCEPT_ALL
|
ACCEPT_ALL
|
||||||
};
|
};
|
||||||
|
|
||||||
static int should_accept_remote(enum accept_promisor accept,
|
static int match_field_against_config(const char *field, const char *value,
|
||||||
const char *remote_name, const char *remote_url,
|
struct promisor_info *config_info)
|
||||||
struct strvec *names, struct strvec *urls)
|
|
||||||
{
|
{
|
||||||
size_t i;
|
if (config_info->filter && !strcasecmp(field, promisor_field_filter))
|
||||||
|
return !strcmp(config_info->filter, value);
|
||||||
|
else if (config_info->token && !strcasecmp(field, promisor_field_token))
|
||||||
|
return !strcmp(config_info->token, value);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int all_fields_match(struct promisor_info *advertised,
|
||||||
|
struct string_list *config_info,
|
||||||
|
int in_list)
|
||||||
|
{
|
||||||
|
struct string_list *fields = fields_checked();
|
||||||
|
struct string_list_item *item_checked;
|
||||||
|
|
||||||
|
for_each_string_list_item(item_checked, fields) {
|
||||||
|
int match = 0;
|
||||||
|
const char *field = item_checked->string;
|
||||||
|
const char *value = NULL;
|
||||||
|
struct string_list_item *item;
|
||||||
|
|
||||||
|
if (!strcasecmp(field, promisor_field_filter))
|
||||||
|
value = advertised->filter;
|
||||||
|
else if (!strcasecmp(field, promisor_field_token))
|
||||||
|
value = advertised->token;
|
||||||
|
|
||||||
|
if (!value)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (in_list) {
|
||||||
|
for_each_string_list_item(item, config_info) {
|
||||||
|
struct promisor_info *p = item->util;
|
||||||
|
if (match_field_against_config(field, value, p)) {
|
||||||
|
match = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item = string_list_lookup(config_info, advertised->name);
|
||||||
|
if (item) {
|
||||||
|
struct promisor_info *p = item->util;
|
||||||
|
match = match_field_against_config(field, value, p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!match)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int should_accept_remote(enum accept_promisor accept,
|
||||||
|
struct promisor_info *advertised,
|
||||||
|
struct string_list *config_info)
|
||||||
|
{
|
||||||
|
struct promisor_info *p;
|
||||||
|
struct string_list_item *item;
|
||||||
|
const char *remote_name = advertised->name;
|
||||||
|
const char *remote_url = advertised->url;
|
||||||
|
|
||||||
if (accept == ACCEPT_ALL)
|
if (accept == ACCEPT_ALL)
|
||||||
return 1;
|
return all_fields_match(advertised, config_info, 1);
|
||||||
|
|
||||||
i = remote_nick_find(names, remote_name);
|
/* Get config info for that promisor remote */
|
||||||
|
item = string_list_lookup(config_info, remote_name);
|
||||||
|
|
||||||
if (i >= names->nr)
|
if (!item)
|
||||||
/* We don't know about that remote */
|
/* We don't know about that remote */
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
p = item->util;
|
||||||
|
|
||||||
if (accept == ACCEPT_KNOWN_NAME)
|
if (accept == ACCEPT_KNOWN_NAME)
|
||||||
return 1;
|
return all_fields_match(advertised, config_info, 0);
|
||||||
|
|
||||||
if (accept != ACCEPT_KNOWN_URL)
|
if (accept != ACCEPT_KNOWN_URL)
|
||||||
BUG("Unhandled 'enum accept_promisor' value '%d'", accept);
|
BUG("Unhandled 'enum accept_promisor' value '%d'", accept);
|
||||||
|
@ -414,24 +635,72 @@ static int should_accept_remote(enum accept_promisor accept,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strcmp(urls->v[i], remote_url))
|
if (!strcmp(p->url, remote_url))
|
||||||
return 1;
|
return all_fields_match(advertised, config_info, 0);
|
||||||
|
|
||||||
warning(_("known remote named '%s' but with URL '%s' instead of '%s'"),
|
warning(_("known remote named '%s' but with URL '%s' instead of '%s'"),
|
||||||
remote_name, urls->v[i], remote_url);
|
remote_name, p->url, remote_url);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int skip_field_name_prefix(const char *elem, const char *field_name, const char **value)
|
||||||
|
{
|
||||||
|
const char *p;
|
||||||
|
if (!skip_prefix(elem, field_name, &p) || *p != '=')
|
||||||
|
return 0;
|
||||||
|
*value = p + 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct promisor_info *parse_one_advertised_remote(const char *remote_info)
|
||||||
|
{
|
||||||
|
struct promisor_info *info = xcalloc(1, sizeof(*info));
|
||||||
|
struct string_list elem_list = STRING_LIST_INIT_DUP;
|
||||||
|
struct string_list_item *item;
|
||||||
|
|
||||||
|
string_list_split(&elem_list, remote_info, ",", -1);
|
||||||
|
|
||||||
|
for_each_string_list_item(item, &elem_list) {
|
||||||
|
const char *elem = item->string;
|
||||||
|
const char *p = strchr(elem, '=');
|
||||||
|
|
||||||
|
if (!p) {
|
||||||
|
warning(_("invalid element '%s' from remote info"), elem);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skip_field_name_prefix(elem, promisor_field_name, &p))
|
||||||
|
info->name = url_percent_decode(p);
|
||||||
|
else if (skip_field_name_prefix(elem, promisor_field_url, &p))
|
||||||
|
info->url = url_percent_decode(p);
|
||||||
|
else if (skip_field_name_prefix(elem, promisor_field_filter, &p))
|
||||||
|
info->filter = url_percent_decode(p);
|
||||||
|
else if (skip_field_name_prefix(elem, promisor_field_token, &p))
|
||||||
|
info->token = url_percent_decode(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
string_list_clear(&elem_list, 0);
|
||||||
|
|
||||||
|
if (!info->name || !info->url) {
|
||||||
|
warning(_("server advertised a promisor remote without a name or URL: %s"),
|
||||||
|
remote_info);
|
||||||
|
promisor_info_free(info);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
struct strbuf **remotes;
|
|
||||||
const char *accept_str;
|
const char *accept_str;
|
||||||
enum accept_promisor accept = ACCEPT_NONE;
|
enum accept_promisor accept = ACCEPT_NONE;
|
||||||
struct strvec names = STRVEC_INIT;
|
struct string_list config_info = STRING_LIST_INIT_NODUP;
|
||||||
struct strvec urls = STRVEC_INIT;
|
struct string_list remote_info = STRING_LIST_INIT_DUP;
|
||||||
|
struct string_list_item *item;
|
||||||
|
|
||||||
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))
|
||||||
|
@ -450,49 +719,31 @@ static void filter_promisor_remote(struct repository *repo,
|
||||||
if (accept == ACCEPT_NONE)
|
if (accept == ACCEPT_NONE)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (accept != ACCEPT_ALL)
|
|
||||||
promisor_info_vecs(repo, &names, &urls);
|
|
||||||
|
|
||||||
/* Parse remote info received */
|
/* Parse remote info received */
|
||||||
|
|
||||||
remotes = strbuf_split_str(info, ';', 0);
|
string_list_split(&remote_info, info, ";", -1);
|
||||||
|
|
||||||
for (size_t i = 0; remotes[i]; i++) {
|
for_each_string_list_item(item, &remote_info) {
|
||||||
struct strbuf **elems;
|
struct promisor_info *advertised;
|
||||||
const char *remote_name = NULL;
|
|
||||||
const char *remote_url = NULL;
|
|
||||||
char *decoded_name = NULL;
|
|
||||||
char *decoded_url = NULL;
|
|
||||||
|
|
||||||
strbuf_strip_suffix(remotes[i], ";");
|
advertised = parse_one_advertised_remote(item->string);
|
||||||
elems = strbuf_split(remotes[i], ',');
|
|
||||||
|
|
||||||
for (size_t j = 0; elems[j]; j++) {
|
if (!advertised)
|
||||||
int res;
|
continue;
|
||||||
strbuf_strip_suffix(elems[j], ",");
|
|
||||||
res = skip_prefix(elems[j]->buf, "name=", &remote_name) ||
|
if (!config_info.nr) {
|
||||||
skip_prefix(elems[j]->buf, "url=", &remote_url);
|
promisor_config_info_list(repo, &config_info, fields_checked());
|
||||||
if (!res)
|
string_list_sort(&config_info);
|
||||||
warning(_("unknown element '%s' from remote info"),
|
|
||||||
elems[j]->buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remote_name)
|
if (should_accept_remote(accept, advertised, &config_info))
|
||||||
decoded_name = url_percent_decode(remote_name);
|
strvec_push(accepted, advertised->name);
|
||||||
if (remote_url)
|
|
||||||
decoded_url = url_percent_decode(remote_url);
|
|
||||||
|
|
||||||
if (decoded_name && should_accept_remote(accept, decoded_name, decoded_url, &names, &urls))
|
promisor_info_free(advertised);
|
||||||
strvec_push(accepted, decoded_name);
|
|
||||||
|
|
||||||
strbuf_list_free(elems);
|
|
||||||
free(decoded_name);
|
|
||||||
free(decoded_url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
strvec_clear(&names);
|
promisor_info_list_clear(&config_info);
|
||||||
strvec_clear(&urls);
|
string_list_clear(&remote_info, 0);
|
||||||
strbuf_list_free(remotes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char *promisor_remote_reply(const char *info)
|
char *promisor_remote_reply(const char *info)
|
||||||
|
@ -518,16 +769,15 @@ char *promisor_remote_reply(const char *info)
|
||||||
|
|
||||||
void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes)
|
void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes)
|
||||||
{
|
{
|
||||||
struct strbuf **accepted_remotes = strbuf_split_str(remotes, ';', 0);
|
struct string_list accepted_remotes = STRING_LIST_INIT_DUP;
|
||||||
|
struct string_list_item *item;
|
||||||
|
|
||||||
for (size_t i = 0; accepted_remotes[i]; i++) {
|
string_list_split(&accepted_remotes, remotes, ";", -1);
|
||||||
struct promisor_remote *p;
|
|
||||||
char *decoded_remote;
|
|
||||||
|
|
||||||
strbuf_strip_suffix(accepted_remotes[i], ";");
|
for_each_string_list_item(item, &accepted_remotes) {
|
||||||
decoded_remote = url_percent_decode(accepted_remotes[i]->buf);
|
char *decoded_remote = url_percent_decode(item->string);
|
||||||
|
struct promisor_remote *p = repo_promisor_remote_find(r, decoded_remote);
|
||||||
|
|
||||||
p = repo_promisor_remote_find(r, decoded_remote);
|
|
||||||
if (p)
|
if (p)
|
||||||
p->accepted = 1;
|
p->accepted = 1;
|
||||||
else
|
else
|
||||||
|
@ -537,5 +787,5 @@ void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes
|
||||||
free(decoded_remote);
|
free(decoded_remote);
|
||||||
}
|
}
|
||||||
|
|
||||||
strbuf_list_free(accepted_remotes);
|
string_list_clear(&accepted_remotes, 0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -295,6 +295,71 @@ test_expect_success "clone with 'KnownUrl' and empty url, so not advertised" '
|
||||||
check_missing_objects server 1 "$oid"
|
check_missing_objects server 1 "$oid"
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success "clone with promisor.sendFields" '
|
||||||
|
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" &&
|
||||||
|
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 promisor.acceptfromserver=All \
|
||||||
|
--no-local --filter="blob:limit=5k" server client &&
|
||||||
|
|
||||||
|
# Check that fields are properly transmitted
|
||||||
|
ENCODED_URL=$(echo "file://$(pwd)/lop" | sed -e "s/ /%20/g") &&
|
||||||
|
PR1="name=lop,url=$ENCODED_URL,partialCloneFilter=blob:none" &&
|
||||||
|
PR2="name=otherLop,url=https://invalid.invalid,partialCloneFilter=blob:limit=10k,token=fooBar" &&
|
||||||
|
test_grep "clone< promisor-remote=$PR1;$PR2" trace &&
|
||||||
|
test_grep "clone> promisor-remote=lop;otherLop" trace &&
|
||||||
|
|
||||||
|
# Check that the largest object is still missing on the server
|
||||||
|
check_missing_objects server 1 "$oid"
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "clone with promisor.checkFields" '
|
||||||
|
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" &&
|
||||||
|
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.partialCloneFilter="blob:none" \
|
||||||
|
-c promisor.acceptfromserver=All \
|
||||||
|
-c promisor.checkFields=partialcloneFilter \
|
||||||
|
--no-local --filter="blob:limit=5k" server client &&
|
||||||
|
|
||||||
|
# Check that fields are properly transmitted
|
||||||
|
ENCODED_URL=$(echo "file://$(pwd)/lop" | sed -e "s/ /%20/g") &&
|
||||||
|
PR1="name=lop,url=$ENCODED_URL,partialCloneFilter=blob:none" &&
|
||||||
|
PR2="name=otherLop,url=https://invalid.invalid,partialCloneFilter=blob:limit=10k,token=fooBar" &&
|
||||||
|
test_grep "clone< promisor-remote=$PR1;$PR2" trace &&
|
||||||
|
test_grep "clone> promisor-remote=lop" trace &&
|
||||||
|
test_grep ! "clone> promisor-remote=lop;otherLop" 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 &&
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue