Skip to content

Commit f2c23f8

Browse files
committed
Merge branch 'cc/promisor-remote-capability' into jch
The v2 protocol learned to allow the server to advertise possible promisor remotes, and the client to respond with what promissor remotes it uses, so that the server side can omit objects that the client can lazily obtain from these other promissor remotes. Comments? * cc/promisor-remote-capability: promisor-remote: check advertised name or URL Add 'promisor-remote' capability to protocol v2 strbuf: refactor strbuf_trim_trailing_ch() version: refactor strbuf_sanitize()
2 parents 32c67e7 + bc0c4e1 commit f2c23f8

File tree

12 files changed

+620
-16
lines changed

12 files changed

+620
-16
lines changed

Documentation/config/promisor.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,30 @@
11
promisor.quiet::
22
If set to "true" assume `--quiet` when fetching additional
33
objects for a partial clone.
4+
5+
promisor.advertise::
6+
If set to "true", a server will use the "promisor-remote"
7+
capability, see linkgit:gitprotocol-v2[5], to advertise the
8+
promisor remotes it is using, if it uses some. Default is
9+
"false", which means the "promisor-remote" capability is not
10+
advertised.
11+
12+
promisor.acceptFromServer::
13+
If set to "all", a client will accept all the promisor remotes
14+
a server might advertise using the "promisor-remote"
15+
capability. If set to "knownName" the client will accept
16+
promisor remotes which are already configured on the client
17+
and have the same name as those advertised by the client. This
18+
is not very secure, but could be used in a corporate setup
19+
where servers and clients are trusted to not switch name and
20+
URLs. If set to "knownUrl", the client will accept promisor
21+
remotes which have both the same name and the same URL
22+
configured on the client as the name and URL advertised by the
23+
server. This is more secure than "all" or "knownUrl", so it
24+
should be used if possible instead of those options. Default
25+
is "none", which means no promisor remote advertised by a
26+
server will be accepted. By accepting a promisor remote, the
27+
client agrees that the server might omit objects that are
28+
lazily fetchable from this promisor remote from its responses
29+
to "fetch" and "clone" requests from the client. See
30+
linkgit:gitprotocol-v2[5].

Documentation/gitprotocol-v2.txt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,60 @@ retrieving the header from a bundle at the indicated URI, and thus
781781
save themselves and the server(s) the request(s) needed to inspect the
782782
headers of that bundle or bundles.
783783

784+
promisor-remote=<pr-infos>
785+
~~~~~~~~~~~~~~~~~~~~~~~~~~
786+
787+
The server may advertise some promisor remotes it is using or knows
788+
about to a client which may want to use them as its promisor remotes,
789+
instead of this repository. In this case <pr-infos> should be of the
790+
form:
791+
792+
pr-infos = pr-info | pr-infos ";" pr-info
793+
794+
pr-info = "name=" pr-name | "name=" pr-name "," "url=" pr-url
795+
796+
where `pr-name` is the urlencoded name of a promisor remote, and
797+
`pr-url` the urlencoded URL of that promisor remote.
798+
799+
In this case, if the client decides to use one or more promisor
800+
remotes the server advertised, it can reply with
801+
"promisor-remote=<pr-names>" where <pr-names> should be of the form:
802+
803+
pr-names = pr-name | pr-names ";" pr-name
804+
805+
where `pr-name` is the urlencoded name of a promisor remote the server
806+
advertised and the client accepts.
807+
808+
Note that, everywhere in this document, `pr-name` MUST be a valid
809+
remote name, and the ';' and ',' characters MUST be encoded if they
810+
appear in `pr-name` or `pr-url`.
811+
812+
If the server doesn't know any promisor remote that could be good for
813+
a client to use, or prefers a client not to use any promisor remote it
814+
uses or knows about, it shouldn't advertise the "promisor-remote"
815+
capability at all.
816+
817+
In this case, or if the client doesn't want to use any promisor remote
818+
the server advertised, the client shouldn't advertise the
819+
"promisor-remote" capability at all in its reply.
820+
821+
The "promisor.advertise" and "promisor.acceptFromServer" configuration
822+
options can be used on the server and client side respectively to
823+
control what they advertise or accept respectively. See the
824+
documentation of these configuration options for more information.
825+
826+
Note that in the future it would be nice if the "promisor-remote"
827+
protocol capability could be used by the server, when responding to
828+
`git fetch` or `git clone`, to advertise better-connected remotes that
829+
the client can use as promisor remotes, instead of this repository, so
830+
that the client can lazily fetch objects from these other
831+
better-connected remotes. This would require the server to omit in its
832+
response the objects available on the better-connected remotes that
833+
the client has accepted. This hasn't been implemented yet though. So
834+
for now this "promisor-remote" capability is useful only when the
835+
server advertises some promisor remotes it already uses to borrow
836+
objects from.
837+
784838
GIT
785839
---
786840
Part of the linkgit:git[1] suite

connect.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "protocol.h"
2323
#include "alias.h"
2424
#include "bundle-uri.h"
25+
#include "promisor-remote.h"
2526

2627
static char *server_capabilities_v1;
2728
static struct strvec server_capabilities_v2 = STRVEC_INIT;
@@ -487,6 +488,7 @@ void check_stateless_delimiter(int stateless_rpc,
487488
static void send_capabilities(int fd_out, struct packet_reader *reader)
488489
{
489490
const char *hash_name;
491+
const char *promisor_remote_info;
490492

491493
if (server_supports_v2("agent"))
492494
packet_write_fmt(fd_out, "agent=%s", git_user_agent_sanitized());
@@ -500,6 +502,13 @@ static void send_capabilities(int fd_out, struct packet_reader *reader)
500502
} else {
501503
reader->hash_algo = &hash_algos[GIT_HASH_SHA1];
502504
}
505+
if (server_feature_v2("promisor-remote", &promisor_remote_info)) {
506+
char *reply = promisor_remote_reply(promisor_remote_info);
507+
if (reply) {
508+
packet_write_fmt(fd_out, "promisor-remote=%s", reply);
509+
free(reply);
510+
}
511+
}
503512
}
504513

505514
int get_remote_bundle_uri(int fd_out, struct packet_reader *reader,

promisor-remote.c

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "strvec.h"
1212
#include "packfile.h"
1313
#include "environment.h"
14+
#include "url.h"
1415

1516
struct promisor_remote_config {
1617
struct promisor_remote *promisors;
@@ -219,6 +220,18 @@ int repo_has_promisor_remote(struct repository *r)
219220
return !!repo_promisor_remote_find(r, NULL);
220221
}
221222

223+
int repo_has_accepted_promisor_remote(struct repository *r)
224+
{
225+
struct promisor_remote *p;
226+
227+
promisor_remote_init(r);
228+
229+
for (p = r->promisor_remote_config->promisors; p; p = p->next)
230+
if (p->accepted)
231+
return 1;
232+
return 0;
233+
}
234+
222235
static int remove_fetched_oids(struct repository *repo,
223236
struct object_id **oids,
224237
int oid_nr, int to_free)
@@ -290,3 +303,234 @@ void promisor_remote_get_direct(struct repository *repo,
290303
if (to_free)
291304
free(remaining_oids);
292305
}
306+
307+
static int allow_unsanitized(char ch)
308+
{
309+
if (ch == ',' || ch == ';' || ch == '%')
310+
return 0;
311+
return ch > 32 && ch < 127;
312+
}
313+
314+
static void promisor_info_vecs(struct repository *repo,
315+
struct strvec *names,
316+
struct strvec *urls)
317+
{
318+
struct promisor_remote *r;
319+
320+
promisor_remote_init(repo);
321+
322+
for (r = repo->promisor_remote_config->promisors; r; r = r->next) {
323+
char *url;
324+
char *url_key = xstrfmt("remote.%s.url", r->name);
325+
326+
strvec_push(names, r->name);
327+
strvec_push(urls, git_config_get_string(url_key, &url) ? NULL : url);
328+
329+
free(url);
330+
free(url_key);
331+
}
332+
}
333+
334+
char *promisor_remote_info(struct repository *repo)
335+
{
336+
struct strbuf sb = STRBUF_INIT;
337+
int advertise_promisors = 0;
338+
struct strvec names = STRVEC_INIT;
339+
struct strvec urls = STRVEC_INIT;
340+
341+
git_config_get_bool("promisor.advertise", &advertise_promisors);
342+
343+
if (!advertise_promisors)
344+
return NULL;
345+
346+
promisor_info_vecs(repo, &names, &urls);
347+
348+
if (!names.nr)
349+
return NULL;
350+
351+
for (size_t i = 0; i < names.nr; i++) {
352+
if (i)
353+
strbuf_addch(&sb, ';');
354+
strbuf_addstr(&sb, "name=");
355+
strbuf_addstr_urlencode(&sb, names.v[i], allow_unsanitized);
356+
if (urls.v[i]) {
357+
strbuf_addstr(&sb, ",url=");
358+
strbuf_addstr_urlencode(&sb, urls.v[i], allow_unsanitized);
359+
}
360+
}
361+
362+
strbuf_sanitize(&sb);
363+
364+
strvec_clear(&names);
365+
strvec_clear(&urls);
366+
367+
return strbuf_detach(&sb, NULL);
368+
}
369+
370+
/*
371+
* Find first index of 'vec' where there is 'val'. 'val' is compared
372+
* case insensively to the strings in 'vec'. If not found 'vec->nr' is
373+
* returned.
374+
*/
375+
static size_t strvec_find_index(struct strvec *vec, const char *val)
376+
{
377+
for (size_t i = 0; i < vec->nr; i++)
378+
if (!strcasecmp(vec->v[i], val))
379+
return i;
380+
return vec->nr;
381+
}
382+
383+
enum accept_promisor {
384+
ACCEPT_NONE = 0,
385+
ACCEPT_KNOWN_URL,
386+
ACCEPT_KNOWN_NAME,
387+
ACCEPT_ALL
388+
};
389+
390+
static int should_accept_remote(enum accept_promisor accept,
391+
const char *remote_name, const char *remote_url,
392+
struct strvec *names, struct strvec *urls)
393+
{
394+
size_t i;
395+
396+
if (accept == ACCEPT_ALL)
397+
return 1;
398+
399+
i = strvec_find_index(names, remote_name);
400+
401+
if (i >= names->nr)
402+
/* We don't know about that remote */
403+
return 0;
404+
405+
if (accept == ACCEPT_KNOWN_NAME)
406+
return 1;
407+
408+
if (accept != ACCEPT_KNOWN_URL)
409+
BUG("Unhandled 'enum accept_promisor' value '%d'", accept);
410+
411+
if (!strcasecmp(urls->v[i], remote_url))
412+
return 1;
413+
414+
warning(_("known remote named '%s' but with url '%s' instead of '%s'"),
415+
remote_name, urls->v[i], remote_url);
416+
417+
return 0;
418+
}
419+
420+
static void filter_promisor_remote(struct repository *repo,
421+
struct strvec *accepted,
422+
const char *info)
423+
{
424+
struct strbuf **remotes;
425+
char *accept_str;
426+
enum accept_promisor accept = ACCEPT_NONE;
427+
struct strvec names = STRVEC_INIT;
428+
struct strvec urls = STRVEC_INIT;
429+
430+
if (!git_config_get_string("promisor.acceptfromserver", &accept_str)) {
431+
if (!accept_str || !*accept_str || !strcasecmp("None", accept_str))
432+
accept = ACCEPT_NONE;
433+
else if (!strcasecmp("KnownUrl", accept_str))
434+
accept = ACCEPT_KNOWN_URL;
435+
else if (!strcasecmp("KnownName", accept_str))
436+
accept = ACCEPT_KNOWN_NAME;
437+
else if (!strcasecmp("All", accept_str))
438+
accept = ACCEPT_ALL;
439+
else
440+
warning(_("unknown '%s' value for '%s' config option"),
441+
accept_str, "promisor.acceptfromserver");
442+
}
443+
444+
if (accept == ACCEPT_NONE)
445+
return;
446+
447+
if (accept != ACCEPT_ALL)
448+
promisor_info_vecs(repo, &names, &urls);
449+
450+
/* Parse remote info received */
451+
452+
remotes = strbuf_split_str(info, ';', 0);
453+
454+
for (size_t i = 0; remotes[i]; i++) {
455+
struct strbuf **elems;
456+
const char *remote_name = NULL;
457+
const char *remote_url = NULL;
458+
char *decoded_name = NULL;
459+
char *decoded_url = NULL;
460+
461+
strbuf_trim_trailing_ch(remotes[i], ';');
462+
elems = strbuf_split_str(remotes[i]->buf, ',', 0);
463+
464+
for (size_t j = 0; elems[j]; j++) {
465+
int res;
466+
strbuf_trim_trailing_ch(elems[j], ',');
467+
res = skip_prefix(elems[j]->buf, "name=", &remote_name) ||
468+
skip_prefix(elems[j]->buf, "url=", &remote_url);
469+
if (!res)
470+
warning(_("unknown element '%s' from remote info"),
471+
elems[j]->buf);
472+
}
473+
474+
if (remote_name)
475+
decoded_name = url_percent_decode(remote_name);
476+
if (remote_url)
477+
decoded_url = url_percent_decode(remote_url);
478+
479+
if (decoded_name && should_accept_remote(accept, decoded_name, decoded_url, &names, &urls))
480+
strvec_push(accepted, decoded_name);
481+
482+
strbuf_list_free(elems);
483+
free(decoded_name);
484+
free(decoded_url);
485+
}
486+
487+
free(accept_str);
488+
strvec_clear(&names);
489+
strvec_clear(&urls);
490+
strbuf_list_free(remotes);
491+
}
492+
493+
char *promisor_remote_reply(const char *info)
494+
{
495+
struct strvec accepted = STRVEC_INIT;
496+
struct strbuf reply = STRBUF_INIT;
497+
498+
filter_promisor_remote(the_repository, &accepted, info);
499+
500+
if (!accepted.nr)
501+
return NULL;
502+
503+
for (size_t i = 0; i < accepted.nr; i++) {
504+
if (i)
505+
strbuf_addch(&reply, ';');
506+
strbuf_addstr_urlencode(&reply, accepted.v[i], allow_unsanitized);
507+
}
508+
509+
strvec_clear(&accepted);
510+
511+
return strbuf_detach(&reply, NULL);
512+
}
513+
514+
void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes)
515+
{
516+
struct strbuf **accepted_remotes = strbuf_split_str(remotes, ';', 0);
517+
518+
for (size_t i = 0; accepted_remotes[i]; i++) {
519+
struct promisor_remote *p;
520+
char *decoded_remote;
521+
522+
strbuf_trim_trailing_ch(accepted_remotes[i], ';');
523+
decoded_remote = url_percent_decode(accepted_remotes[i]->buf);
524+
525+
p = repo_promisor_remote_find(r, decoded_remote);
526+
if (p)
527+
p->accepted = 1;
528+
else
529+
warning(_("accepted promisor remote '%s' not found"),
530+
decoded_remote);
531+
532+
free(decoded_remote);
533+
}
534+
535+
strbuf_list_free(accepted_remotes);
536+
}

0 commit comments

Comments
 (0)