Skip to content

Commit a518291

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 4aaf2c8 commit a518291

2 files changed

Lines changed: 136 additions & 3 deletions

File tree

‎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
@@ -20,6 +20,7 @@
2020
#include "setup.h"
2121
#include "trace2.h"
2222
#include "path.h"
23+
#include "json-parser.h"
2324

2425
static void setup_enlistment_directory(int argc, const char **argv,
2526
const char * const *usagestr,
@@ -364,6 +365,85 @@ static int set_config(const char *fmt, ...)
364365
return res;
365366
}
366367

368+
/* Find N for which .CacheServers[N].GlobalDefault == true */
369+
static int get_cache_server_index(struct json_iterator *it)
370+
{
371+
const char *p;
372+
char *q;
373+
long l;
374+
375+
if (it->type == JSON_TRUE &&
376+
skip_iprefix(it->key.buf, ".CacheServers[", &p) &&
377+
(l = strtol(p, &q, 10)) >= 0 && p != q &&
378+
!strcasecmp(q, "].GlobalDefault")) {
379+
*(long *)it->fn_data = l;
380+
return 1;
381+
}
382+
383+
return 0;
384+
}
385+
386+
struct cache_server_url_data {
387+
char *key, *url;
388+
};
389+
390+
/* Get .CacheServers[N].Url */
391+
static int get_cache_server_url(struct json_iterator *it)
392+
{
393+
struct cache_server_url_data *data = it->fn_data;
394+
395+
if (it->type == JSON_STRING &&
396+
!strcasecmp(data->key, it->key.buf)) {
397+
data->url = strbuf_detach(&it->string_value, NULL);
398+
return 1;
399+
}
400+
401+
return 0;
402+
}
403+
404+
/*
405+
* If `cache_server_url` is `NULL`, print the list to `stdout`.
406+
*
407+
* Since `gvfs-helper` requires a Git directory, this _must_ be run in
408+
* a worktree.
409+
*/
410+
static int supports_gvfs_protocol(const char *url, char **cache_server_url)
411+
{
412+
struct child_process cp = CHILD_PROCESS_INIT;
413+
struct strbuf out = STRBUF_INIT;
414+
415+
cp.git_cmd = 1;
416+
strvec_pushl(&cp.args, "-c", "http.version=HTTP/1.1",
417+
"gvfs-helper", "--remote", url, "config", NULL);
418+
if (!pipe_command(&cp, NULL, 0, &out, 512, NULL, 0)) {
419+
long l = 0;
420+
struct json_iterator it =
421+
JSON_ITERATOR_INIT(out.buf, get_cache_server_index, &l);
422+
struct cache_server_url_data data = { .url = NULL };
423+
424+
if (iterate_json(&it) < 0) {
425+
reset_iterator(&it);
426+
strbuf_release(&out);
427+
return error("JSON parse error");
428+
}
429+
data.key = xstrfmt(".CacheServers[%ld].Url", l);
430+
it.fn = get_cache_server_url;
431+
it.fn_data = &data;
432+
if (iterate_json(&it) < 0) {
433+
reset_iterator(&it);
434+
strbuf_release(&out);
435+
return error("JSON parse error");
436+
}
437+
*cache_server_url = data.url;
438+
free(data.key);
439+
reset_iterator(&it);
440+
strbuf_release(&out);
441+
return 1;
442+
}
443+
strbuf_release(&out);
444+
return 0; /* error out quietly */
445+
}
446+
367447
static char *remote_default_branch(const char *url)
368448
{
369449
struct child_process cp = CHILD_PROCESS_INIT;
@@ -464,6 +544,8 @@ static int cmd_clone(int argc, const char **argv)
464544
char *branch_to_free = NULL;
465545
int full_clone = 0, single_branch = 0, show_progress = isatty(2);
466546
int src = 1, tags = 1, maintenance = 1;
547+
const char *cache_server_url = NULL;
548+
char *default_cache_server_url = NULL;
467549
struct option clone_options[] = {
468550
OPT_STRING('b', "branch", &branch, N_("<branch>"),
469551
N_("branch to checkout after clone")),
@@ -478,6 +560,9 @@ static int cmd_clone(int argc, const char **argv)
478560
N_("specify if tags should be fetched during clone")),
479561
OPT_BOOL(0, "maintenance", &maintenance,
480562
N_("specify if background maintenance should be enabled")),
563+
OPT_STRING(0, "cache-server-url", &cache_server_url,
564+
N_("<url>"),
565+
N_("the url or friendly name of the cache server")),
481566
OPT_END(),
482567
};
483568
const char * const clone_usage[] = {
@@ -489,6 +574,7 @@ static int cmd_clone(int argc, const char **argv)
489574
char *enlistment = NULL, *dir = NULL;
490575
struct strbuf buf = STRBUF_INIT;
491576
int res;
577+
int gvfs_protocol;
492578

493579
argc = parse_options(argc, argv, NULL, clone_options, clone_usage, 0);
494580

@@ -554,9 +640,7 @@ static int cmd_clone(int argc, const char **argv)
554640
set_config("remote.origin.fetch="
555641
"+refs/heads/%s:refs/remotes/origin/%s",
556642
single_branch ? branch : "*",
557-
single_branch ? branch : "*") ||
558-
set_config("remote.origin.promisor=true") ||
559-
set_config("remote.origin.partialCloneFilter=blob:none")) {
643+
single_branch ? branch : "*")) {
560644
res = error(_("could not configure remote in '%s'"), dir);
561645
goto cleanup;
562646
}
@@ -566,6 +650,41 @@ static int cmd_clone(int argc, const char **argv)
566650
goto cleanup;
567651
}
568652

653+
if (set_config("credential.https://dev.azure.com.useHttpPath=true")) {
654+
res = error(_("could not configure credential.useHttpPath"));
655+
goto cleanup;
656+
}
657+
658+
gvfs_protocol = cache_server_url ||
659+
supports_gvfs_protocol(url, &default_cache_server_url);
660+
661+
if (gvfs_protocol) {
662+
if (!cache_server_url)
663+
cache_server_url = default_cache_server_url;
664+
if (set_config("core.useGVFSHelper=true") ||
665+
set_config("core.gvfs=150") ||
666+
set_config("http.version=HTTP/1.1")) {
667+
res = error(_("could not turn on GVFS helper"));
668+
goto cleanup;
669+
}
670+
if (cache_server_url &&
671+
set_config("gvfs.cache-server=%s", cache_server_url)) {
672+
res = error(_("could not configure cache server"));
673+
goto cleanup;
674+
}
675+
if (cache_server_url)
676+
fprintf(stderr, "Cache server URL: %s\n",
677+
cache_server_url);
678+
} else {
679+
if (set_config("core.useGVFSHelper=false") ||
680+
set_config("remote.origin.promisor=true") ||
681+
set_config("remote.origin.partialCloneFilter=blob:none")) {
682+
res = error(_("could not configure partial clone in "
683+
"'%s'"), dir);
684+
goto cleanup;
685+
}
686+
}
687+
569688
if (!full_clone &&
570689
(res = run_git("sparse-checkout", "init", "--cone", NULL)))
571690
goto cleanup;
@@ -578,6 +697,11 @@ static int cmd_clone(int argc, const char **argv)
578697
"origin",
579698
(tags ? NULL : "--no-tags"),
580699
NULL))) {
700+
if (gvfs_protocol) {
701+
res = error(_("failed to prefetch commits and trees"));
702+
goto cleanup;
703+
}
704+
581705
warning(_("partial clone failed; attempting full clone"));
582706

583707
if (set_config("remote.origin.promisor") ||
@@ -612,6 +736,7 @@ static int cmd_clone(int argc, const char **argv)
612736
free(enlistment);
613737
free(dir);
614738
strbuf_release(&buf);
739+
free(default_cache_server_url);
615740
return res;
616741
}
617742

0 commit comments

Comments
 (0)