Skip to content

Commit 0f178d7

Browse files
dschoderrickstolee
andcommitted
scalar clone: support GVFS-enabled remote repositories
With this change, we come a big step closer to feature parity with Scalar: this allows cloning from Azure Repos (which do not support partial clones at time of writing). We use the just-implemented JSON parser to parse the response we got from the `gvfs/config` endpoint; Please note that this response might, or might not, contain information about a cache server. The presence or absence of said cache server, however, has nothing to do with the ability to speak the GVFS protocol (but the presence of the `gvfs/config` endpoint does that). An alternative considered during the development of this patch was to perform simple string matching instead of parsing the JSON-formatted data; However, this would have been fragile, as the response contains free-form text (e.g. the repository's description) which might contain parts that would confuse a simple string matcher (but not a proper JSON parser). Note: we need to limit the re-try logic in `git clone` to handle only the non-GVFS case: the call to `set_config()` to un-set the partial clone settings would otherwise fail because those settings would not exist in the GVFS protocol case. This will at least give us a clearer reason why such a fetch fails. The way the `gvfs-helper` command operates is apparently incompatible with HTTP/2, that's why we need to enforce HTTP/1.1 in Scalar clones accessing Azure Repos. Co-authored-by: Derrick Stolee <dstolee@microsoft.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
1 parent 7c496d4 commit 0f178d7

File tree

2 files changed

+136
-3
lines changed

2 files changed

+136
-3
lines changed

diagnose.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "parse-options.h"
1313
#include "repository.h"
1414
#include "write-or-die.h"
15+
#include "config.h"
1516

1617
struct archive_dir {
1718
const char *path;
@@ -185,6 +186,7 @@ int create_diagnostics_archive(struct repository *r,
185186
struct strvec archiver_args = STRVEC_INIT;
186187
char **argv_copy = NULL;
187188
int stdout_fd = -1, archiver_fd = -1;
189+
char *cache_server_url = NULL;
188190
struct strbuf buf = STRBUF_INIT;
189191
int res;
190192
struct archive_dir archive_dirs[] = {
@@ -220,6 +222,11 @@ int create_diagnostics_archive(struct repository *r,
220222
get_version_info(&buf, 1);
221223

222224
strbuf_addf(&buf, "Repository root: %s\n", r->worktree);
225+
226+
repo_config_get_string(r, "gvfs.cache-server", &cache_server_url);
227+
strbuf_addf(&buf, "Cache Server: %s\n\n",
228+
cache_server_url ? cache_server_url : "None");
229+
223230
get_disk_info(&buf);
224231
write_or_die(stdout_fd, buf.buf, buf.len);
225232
strvec_pushf(&archiver_args,
@@ -277,6 +284,7 @@ int create_diagnostics_archive(struct repository *r,
277284
free(argv_copy);
278285
strvec_clear(&archiver_args);
279286
strbuf_release(&buf);
287+
free(cache_server_url);
280288

281289
return res;
282290
}

scalar.c

Lines changed: 128 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "help.h"
2020
#include "setup.h"
2121
#include "trace2.h"
22+
#include "json-parser.h"
2223

2324
static void setup_enlistment_directory(int argc, const char **argv,
2425
const char * const *usagestr,
@@ -355,6 +356,85 @@ static int set_config(const char *fmt, ...)
355356
return res;
356357
}
357358

359+
/* Find N for which .CacheServers[N].GlobalDefault == true */
360+
static int get_cache_server_index(struct json_iterator *it)
361+
{
362+
const char *p;
363+
char *q;
364+
long l;
365+
366+
if (it->type == JSON_TRUE &&
367+
skip_iprefix(it->key.buf, ".CacheServers[", &p) &&
368+
(l = strtol(p, &q, 10)) >= 0 && p != q &&
369+
!strcasecmp(q, "].GlobalDefault")) {
370+
*(long *)it->fn_data = l;
371+
return 1;
372+
}
373+
374+
return 0;
375+
}
376+
377+
struct cache_server_url_data {
378+
char *key, *url;
379+
};
380+
381+
/* Get .CacheServers[N].Url */
382+
static int get_cache_server_url(struct json_iterator *it)
383+
{
384+
struct cache_server_url_data *data = it->fn_data;
385+
386+
if (it->type == JSON_STRING &&
387+
!strcasecmp(data->key, it->key.buf)) {
388+
data->url = strbuf_detach(&it->string_value, NULL);
389+
return 1;
390+
}
391+
392+
return 0;
393+
}
394+
395+
/*
396+
* If `cache_server_url` is `NULL`, print the list to `stdout`.
397+
*
398+
* Since `gvfs-helper` requires a Git directory, this _must_ be run in
399+
* a worktree.
400+
*/
401+
static int supports_gvfs_protocol(const char *url, char **cache_server_url)
402+
{
403+
struct child_process cp = CHILD_PROCESS_INIT;
404+
struct strbuf out = STRBUF_INIT;
405+
406+
cp.git_cmd = 1;
407+
strvec_pushl(&cp.args, "-c", "http.version=HTTP/1.1",
408+
"gvfs-helper", "--remote", url, "config", NULL);
409+
if (!pipe_command(&cp, NULL, 0, &out, 512, NULL, 0)) {
410+
long l = 0;
411+
struct json_iterator it =
412+
JSON_ITERATOR_INIT(out.buf, get_cache_server_index, &l);
413+
struct cache_server_url_data data = { .url = NULL };
414+
415+
if (iterate_json(&it) < 0) {
416+
reset_iterator(&it);
417+
strbuf_release(&out);
418+
return error("JSON parse error");
419+
}
420+
data.key = xstrfmt(".CacheServers[%ld].Url", l);
421+
it.fn = get_cache_server_url;
422+
it.fn_data = &data;
423+
if (iterate_json(&it) < 0) {
424+
reset_iterator(&it);
425+
strbuf_release(&out);
426+
return error("JSON parse error");
427+
}
428+
*cache_server_url = data.url;
429+
free(data.key);
430+
reset_iterator(&it);
431+
strbuf_release(&out);
432+
return 1;
433+
}
434+
strbuf_release(&out);
435+
return 0; /* error out quietly */
436+
}
437+
358438
static char *remote_default_branch(const char *url)
359439
{
360440
struct child_process cp = CHILD_PROCESS_INIT;
@@ -455,6 +535,8 @@ static int cmd_clone(int argc, const char **argv)
455535
char *branch_to_free = NULL;
456536
int full_clone = 0, single_branch = 0, show_progress = isatty(2);
457537
int src = 1, tags = 1, maintenance = 1;
538+
const char *cache_server_url = NULL;
539+
char *default_cache_server_url = NULL;
458540
struct option clone_options[] = {
459541
OPT_STRING('b', "branch", &branch, N_("<branch>"),
460542
N_("branch to checkout after clone")),
@@ -469,6 +551,9 @@ static int cmd_clone(int argc, const char **argv)
469551
N_("specify if tags should be fetched during clone")),
470552
OPT_BOOL(0, "maintenance", &maintenance,
471553
N_("specify if background maintenance should be enabled")),
554+
OPT_STRING(0, "cache-server-url", &cache_server_url,
555+
N_("<url>"),
556+
N_("the url or friendly name of the cache server")),
472557
OPT_END(),
473558
};
474559
const char * const clone_usage[] = {
@@ -480,6 +565,7 @@ static int cmd_clone(int argc, const char **argv)
480565
char *enlistment = NULL, *dir = NULL;
481566
struct strbuf buf = STRBUF_INIT;
482567
int res;
568+
int gvfs_protocol;
483569

484570
argc = parse_options(argc, argv, NULL, clone_options, clone_usage, 0);
485571

@@ -545,9 +631,7 @@ static int cmd_clone(int argc, const char **argv)
545631
set_config("remote.origin.fetch="
546632
"+refs/heads/%s:refs/remotes/origin/%s",
547633
single_branch ? branch : "*",
548-
single_branch ? branch : "*") ||
549-
set_config("remote.origin.promisor=true") ||
550-
set_config("remote.origin.partialCloneFilter=blob:none")) {
634+
single_branch ? branch : "*")) {
551635
res = error(_("could not configure remote in '%s'"), dir);
552636
goto cleanup;
553637
}
@@ -557,6 +641,41 @@ static int cmd_clone(int argc, const char **argv)
557641
goto cleanup;
558642
}
559643

644+
if (set_config("credential.https://dev.azure.com.useHttpPath=true")) {
645+
res = error(_("could not configure credential.useHttpPath"));
646+
goto cleanup;
647+
}
648+
649+
gvfs_protocol = cache_server_url ||
650+
supports_gvfs_protocol(url, &default_cache_server_url);
651+
652+
if (gvfs_protocol) {
653+
if (!cache_server_url)
654+
cache_server_url = default_cache_server_url;
655+
if (set_config("core.useGVFSHelper=true") ||
656+
set_config("core.gvfs=150") ||
657+
set_config("http.version=HTTP/1.1")) {
658+
res = error(_("could not turn on GVFS helper"));
659+
goto cleanup;
660+
}
661+
if (cache_server_url &&
662+
set_config("gvfs.cache-server=%s", cache_server_url)) {
663+
res = error(_("could not configure cache server"));
664+
goto cleanup;
665+
}
666+
if (cache_server_url)
667+
fprintf(stderr, "Cache server URL: %s\n",
668+
cache_server_url);
669+
} else {
670+
if (set_config("core.useGVFSHelper=false") ||
671+
set_config("remote.origin.promisor=true") ||
672+
set_config("remote.origin.partialCloneFilter=blob:none")) {
673+
res = error(_("could not configure partial clone in "
674+
"'%s'"), dir);
675+
goto cleanup;
676+
}
677+
}
678+
560679
if (!full_clone &&
561680
(res = run_git("sparse-checkout", "init", "--cone", NULL)))
562681
goto cleanup;
@@ -569,6 +688,11 @@ static int cmd_clone(int argc, const char **argv)
569688
"origin",
570689
(tags ? NULL : "--no-tags"),
571690
NULL))) {
691+
if (gvfs_protocol) {
692+
res = error(_("failed to prefetch commits and trees"));
693+
goto cleanup;
694+
}
695+
572696
warning(_("partial clone failed; attempting full clone"));
573697

574698
if (set_config("remote.origin.promisor") ||
@@ -603,6 +727,7 @@ static int cmd_clone(int argc, const char **argv)
603727
free(enlistment);
604728
free(dir);
605729
strbuf_release(&buf);
730+
free(default_cache_server_url);
606731
return res;
607732
}
608733

0 commit comments

Comments
 (0)