Skip to content

Commit d460267

Browse files
chriscoolgitster
authored andcommitted
Add 'promisor-remote' capability to protocol v2
When a server S knows that some objects from a repository are available from a promisor remote X, S might want to suggest to a client C cloning or fetching the repo from S that C may use X directly instead of S for these objects. Note that this could happen both in the case S itself doesn't have the objects and borrows them from X, and in the case S has the objects but knows that X is better connected to the world (e.g., it is in a $LARGEINTERNETCOMPANY datacenter with petabit/s backbone connections) than S. Implementation of the latter case, which would require S to omit in its response the objects available on X, is left for future improvement though. Then C might or might not, want to get the objects from X. If S and C can agree on C using X directly, S can then omit objects that can be obtained from X when answering C's request. To allow S and C to agree and let each other know about C using X or not, let's introduce a new "promisor-remote" capability in the protocol v2, as well as a few new configuration variables: - "promisor.advertise" on the server side, and: - "promisor.acceptFromServer" on the client side. By default, or if "promisor.advertise" is set to 'false', a server S will not advertise the "promisor-remote" capability. If S doesn't advertise the "promisor-remote" capability, then a client C replying to S shouldn't advertise the "promisor-remote" capability either. If "promisor.advertise" is set to 'true', S will advertise its promisor remotes with a string like: promisor-remote=<pr-info>[;<pr-info>]... where each <pr-info> element contains information about a single promisor remote in the form: name=<pr-name>[,url=<pr-url>] where <pr-name> is the urlencoded name of a promisor remote and <pr-url> is the urlencoded URL of the promisor remote named <pr-name>. For now, the URL is passed in addition to the name. In the future, it might be possible to pass other information like a filter-spec that the client may use when cloning from S, or a token that the client may use when retrieving objects from X. It is C's responsibility to arrange how it can reach X though, so pieces of information that are usually outside Git's concern, like proxy configuration, must not be distributed over this protocol. It might also be possible in the future for "promisor.advertise" to have other values. For example a value like "onlyName" could prevent S from advertising URLs, which could help in case C should use a different URL for X than the URL S is using. (The URL S is using might be an internal one on the server side for example.) By default or if "promisor.acceptFromServer" is set to "None", C will not accept to use the promisor remotes that might have been advertised by S. In this case, C will not advertise any "promisor-remote" capability in its reply to S. If "promisor.acceptFromServer" is set to "All" and S advertised some promisor remotes, then on the contrary, C will accept to use all the promisor remotes that S advertised and C will reply with a string like: promisor-remote=<pr-name>[;<pr-name>]... where the <pr-name> elements are the urlencoded names of all the promisor remotes S advertised. In a following commit, other values for "promisor.acceptFromServer" will be implemented, so that C will be able to decide the promisor remotes it accepts depending on the name and URL it received from S. So even if that name and URL information is not used much right now, it will be needed soon. Helped-by: Taylor Blau <[email protected]> Helped-by: Patrick Steinhardt <[email protected]> Signed-off-by: Christian Couder <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 0394451 commit d460267

File tree

9 files changed

+584
-1
lines changed

9 files changed

+584
-1
lines changed

Documentation/config/promisor.adoc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
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. Default is "none", which means no promisor remote
16+
advertised by a server will be accepted. By accepting a
17+
promisor remote, the client agrees that the server might omit
18+
objects that are lazily fetchable from this promisor remote
19+
from its responses to "fetch" and "clone" requests from the
20+
client. See linkgit:gitprotocol-v2[5].

Documentation/gitprotocol-v2.adoc

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 to control what they
823+
advertise or accept respectively. See the documentation of these
824+
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: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include "strvec.h"
1212
#include "packfile.h"
1313
#include "environment.h"
14+
#include "url.h"
15+
#include "version.h"
1416

1517
struct promisor_remote_config {
1618
struct promisor_remote *promisors;
@@ -221,6 +223,18 @@ int repo_has_promisor_remote(struct repository *r)
221223
return !!repo_promisor_remote_find(r, NULL);
222224
}
223225

226+
int repo_has_accepted_promisor_remote(struct repository *r)
227+
{
228+
struct promisor_remote *p;
229+
230+
promisor_remote_init(r);
231+
232+
for (p = r->promisor_remote_config->promisors; p; p = p->next)
233+
if (p->accepted)
234+
return 1;
235+
return 0;
236+
}
237+
224238
static int remove_fetched_oids(struct repository *repo,
225239
struct object_id **oids,
226240
int oid_nr, int to_free)
@@ -292,3 +306,183 @@ void promisor_remote_get_direct(struct repository *repo,
292306
if (to_free)
293307
free(remaining_oids);
294308
}
309+
310+
static int allow_unsanitized(char ch)
311+
{
312+
if (ch == ',' || ch == ';' || ch == '%')
313+
return 0;
314+
return ch > 32 && ch < 127;
315+
}
316+
317+
static void promisor_info_vecs(struct repository *repo,
318+
struct strvec *names,
319+
struct strvec *urls)
320+
{
321+
struct promisor_remote *r;
322+
323+
promisor_remote_init(repo);
324+
325+
for (r = repo->promisor_remote_config->promisors; r; r = r->next) {
326+
char *url;
327+
char *url_key = xstrfmt("remote.%s.url", r->name);
328+
329+
strvec_push(names, r->name);
330+
strvec_push(urls, git_config_get_string(url_key, &url) ? NULL : url);
331+
332+
free(url);
333+
free(url_key);
334+
}
335+
}
336+
337+
char *promisor_remote_info(struct repository *repo)
338+
{
339+
struct strbuf sb = STRBUF_INIT;
340+
int advertise_promisors = 0;
341+
struct strvec names = STRVEC_INIT;
342+
struct strvec urls = STRVEC_INIT;
343+
344+
git_config_get_bool("promisor.advertise", &advertise_promisors);
345+
346+
if (!advertise_promisors)
347+
return NULL;
348+
349+
promisor_info_vecs(repo, &names, &urls);
350+
351+
if (!names.nr)
352+
return NULL;
353+
354+
for (size_t i = 0; i < names.nr; i++) {
355+
if (i)
356+
strbuf_addch(&sb, ';');
357+
strbuf_addstr(&sb, "name=");
358+
strbuf_addstr_urlencode(&sb, names.v[i], allow_unsanitized);
359+
if (urls.v[i]) {
360+
strbuf_addstr(&sb, ",url=");
361+
strbuf_addstr_urlencode(&sb, urls.v[i], allow_unsanitized);
362+
}
363+
}
364+
365+
strvec_clear(&names);
366+
strvec_clear(&urls);
367+
368+
return strbuf_detach(&sb, NULL);
369+
}
370+
371+
enum accept_promisor {
372+
ACCEPT_NONE = 0,
373+
ACCEPT_ALL
374+
};
375+
376+
static int should_accept_remote(enum accept_promisor accept,
377+
const char *remote_name UNUSED,
378+
const char *remote_url UNUSED)
379+
{
380+
if (accept == ACCEPT_ALL)
381+
return 1;
382+
383+
BUG("Unhandled 'enum accept_promisor' value '%d'", accept);
384+
}
385+
386+
static void filter_promisor_remote(struct strvec *accepted, const char *info)
387+
{
388+
struct strbuf **remotes;
389+
const char *accept_str;
390+
enum accept_promisor accept = ACCEPT_NONE;
391+
392+
if (!git_config_get_string_tmp("promisor.acceptfromserver", &accept_str)) {
393+
if (!*accept_str || !strcasecmp("None", accept_str))
394+
accept = ACCEPT_NONE;
395+
else if (!strcasecmp("All", accept_str))
396+
accept = ACCEPT_ALL;
397+
else
398+
warning(_("unknown '%s' value for '%s' config option"),
399+
accept_str, "promisor.acceptfromserver");
400+
}
401+
402+
if (accept == ACCEPT_NONE)
403+
return;
404+
405+
/* Parse remote info received */
406+
407+
remotes = strbuf_split_str(info, ';', 0);
408+
409+
for (size_t i = 0; remotes[i]; i++) {
410+
struct strbuf **elems;
411+
const char *remote_name = NULL;
412+
const char *remote_url = NULL;
413+
char *decoded_name = NULL;
414+
char *decoded_url = NULL;
415+
416+
strbuf_strip_suffix(remotes[i], ";");
417+
elems = strbuf_split(remotes[i], ',');
418+
419+
for (size_t j = 0; elems[j]; j++) {
420+
int res;
421+
strbuf_strip_suffix(elems[j], ",");
422+
res = skip_prefix(elems[j]->buf, "name=", &remote_name) ||
423+
skip_prefix(elems[j]->buf, "url=", &remote_url);
424+
if (!res)
425+
warning(_("unknown element '%s' from remote info"),
426+
elems[j]->buf);
427+
}
428+
429+
if (remote_name)
430+
decoded_name = url_percent_decode(remote_name);
431+
if (remote_url)
432+
decoded_url = url_percent_decode(remote_url);
433+
434+
if (decoded_name && should_accept_remote(accept, decoded_name, decoded_url))
435+
strvec_push(accepted, decoded_name);
436+
437+
strbuf_list_free(elems);
438+
free(decoded_name);
439+
free(decoded_url);
440+
}
441+
442+
strbuf_list_free(remotes);
443+
}
444+
445+
char *promisor_remote_reply(const char *info)
446+
{
447+
struct strvec accepted = STRVEC_INIT;
448+
struct strbuf reply = STRBUF_INIT;
449+
450+
filter_promisor_remote(&accepted, info);
451+
452+
if (!accepted.nr)
453+
return NULL;
454+
455+
for (size_t i = 0; i < accepted.nr; i++) {
456+
if (i)
457+
strbuf_addch(&reply, ';');
458+
strbuf_addstr_urlencode(&reply, accepted.v[i], allow_unsanitized);
459+
}
460+
461+
strvec_clear(&accepted);
462+
463+
return strbuf_detach(&reply, NULL);
464+
}
465+
466+
void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes)
467+
{
468+
struct strbuf **accepted_remotes = strbuf_split_str(remotes, ';', 0);
469+
470+
for (size_t i = 0; accepted_remotes[i]; i++) {
471+
struct promisor_remote *p;
472+
char *decoded_remote;
473+
474+
strbuf_strip_suffix(accepted_remotes[i], ";");
475+
decoded_remote = url_percent_decode(accepted_remotes[i]->buf);
476+
477+
p = repo_promisor_remote_find(r, decoded_remote);
478+
if (p)
479+
p->accepted = 1;
480+
else
481+
warning(_("accepted promisor remote '%s' not found"),
482+
decoded_remote);
483+
484+
free(decoded_remote);
485+
}
486+
487+
strbuf_list_free(accepted_remotes);
488+
}

promisor-remote.h

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ struct object_id;
99
* Promisor remote linked list
1010
*
1111
* Information in its fields come from remote.XXX config entries or
12-
* from extensions.partialclone.
12+
* from extensions.partialclone, except for 'accepted' which comes
13+
* from protocol v2 capabilities exchange.
1314
*/
1415
struct promisor_remote {
1516
struct promisor_remote *next;
1617
char *partial_clone_filter;
18+
unsigned int accepted : 1;
1719
const char name[FLEX_ARRAY];
1820
};
1921

@@ -32,4 +34,37 @@ void promisor_remote_get_direct(struct repository *repo,
3234
const struct object_id *oids,
3335
int oid_nr);
3436

37+
/*
38+
* Prepare a "promisor-remote" advertisement by a server.
39+
* Check the value of "promisor.advertise" and maybe the configured
40+
* promisor remotes, if any, to prepare information to send in an
41+
* advertisement.
42+
* Return value is NULL if no promisor remote advertisement should be
43+
* made. Otherwise it contains the names and urls of the advertised
44+
* promisor remotes separated by ';'. See gitprotocol-v2(5).
45+
*/
46+
char *promisor_remote_info(struct repository *repo);
47+
48+
/*
49+
* Prepare a reply to a "promisor-remote" advertisement from a server.
50+
* Check the value of "promisor.acceptfromserver" and maybe the
51+
* configured promisor remotes, if any, to prepare the reply.
52+
* Return value is NULL if no promisor remote from the server
53+
* is accepted. Otherwise it contains the names of the accepted promisor
54+
* remotes separated by ';'. See gitprotocol-v2(5).
55+
*/
56+
char *promisor_remote_reply(const char *info);
57+
58+
/*
59+
* Set the 'accepted' flag for some promisor remotes. Useful on the
60+
* server side when some promisor remotes have been accepted by the
61+
* client.
62+
*/
63+
void mark_promisor_remotes_as_accepted(struct repository *repo, const char *remotes);
64+
65+
/*
66+
* Has any promisor remote been accepted by the client?
67+
*/
68+
int repo_has_accepted_promisor_remote(struct repository *r);
69+
3570
#endif /* PROMISOR_REMOTE_H */

0 commit comments

Comments
 (0)