Skip to content

Commit c38c305

Browse files
committed
scalar: do initialize gvfs.sharedCache
This finalizes the port of the `QueryVstsInfo()` function: we already taught `gvfs-helper` to access the `vsts/info` endpoint on demand, we implemented proper JSON parsing, and now it is time to hook it all up. To that end, we also provide a default local cache root directory. It works the same way as the .NET version of Scalar: it uses C:\scalarCache on Windows, ~/.scalarCache/ on macOS and ~/.cache/scalar on Linux Modified to include call to is_unattended() that was removed from a previous commit. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
1 parent 05df3fd commit c38c305

File tree

3 files changed

+200
-6
lines changed

3 files changed

+200
-6
lines changed

Documentation/scalar.txt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ scalar - A tool for managing large Git repositories
88
SYNOPSIS
99
--------
1010
[verse]
11-
scalar clone [--single-branch] [--branch <main-branch>] [--full-clone] <url> [<enlistment>]
11+
scalar clone [--single-branch] [--branch <main-branch>] [--full-clone]
12+
[--local-cache-path <path>] [--cache-server-url <url>]
13+
<url> [<enlistment>]
1214
scalar list
1315
scalar register [<enlistment>]
1416
scalar unregister [<enlistment>]
@@ -84,6 +86,17 @@ cloning. If the HEAD at the remote did not point at any branch when
8486
A sparse-checkout is initialized by default. This behavior can be
8587
turned off via `--full-clone`.
8688

89+
--local-cache-path <path>::
90+
Override the path to the local cache root directory; Pre-fetched objects
91+
are stored into a repository-dependent subdirectory of that path.
92+
+
93+
The default is `<drive>:\.scalarCache` on Windows (on the same drive as the
94+
clone), and `~/.scalarCache` on macOS.
95+
96+
--cache-server-url <url>::
97+
Retrieve missing objects from the specified remote, which is expected to
98+
understand the GVFS protocol.
99+
87100
List
88101
~~~~
89102

diagnose.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ int create_diagnostics_archive(struct strbuf *zip_path, enum diagnose_mode mode)
173173
struct strvec archiver_args = STRVEC_INIT;
174174
char **argv_copy = NULL;
175175
int stdout_fd = -1, archiver_fd = -1;
176-
char *cache_server_url = NULL;
176+
char *cache_server_url = NULL, *shared_cache = NULL;
177177
struct strbuf buf = STRBUF_INIT;
178178
int res, i;
179179
struct archive_dir archive_dirs[] = {
@@ -211,8 +211,10 @@ int create_diagnostics_archive(struct strbuf *zip_path, enum diagnose_mode mode)
211211
strbuf_addf(&buf, "Repository root: %s\n", the_repository->worktree);
212212

213213
git_config_get_string("gvfs.cache-server", &cache_server_url);
214-
strbuf_addf(&buf, "Cache Server: %s\n\n",
215-
cache_server_url ? cache_server_url : "None");
214+
git_config_get_string("gvfs.sharedCache", &shared_cache);
215+
strbuf_addf(&buf, "Cache Server: %s\nLocal Cache: %s\n\n",
216+
cache_server_url ? cache_server_url : "None",
217+
shared_cache ? shared_cache : "None");
216218

217219
get_disk_info(&buf);
218220
write_or_die(stdout_fd, buf.buf, buf.len);
@@ -272,6 +274,7 @@ int create_diagnostics_archive(struct strbuf *zip_path, enum diagnose_mode mode)
272274
strvec_clear(&archiver_args);
273275
strbuf_release(&buf);
274276
free(cache_server_url);
277+
free(shared_cache);
275278

276279
return res;
277280
}

scalar.c

Lines changed: 180 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
#include "help.h"
1717
#include "json-parser.h"
1818

19+
static int is_unattended(void) {
20+
return git_env_bool("Scalar_UNATTENDED", 0);
21+
}
22+
1923
static void setup_enlistment_directory(int argc, const char **argv,
2024
const char * const *usagestr,
2125
const struct option *options,
@@ -95,6 +99,19 @@ static int run_git(const char *arg, ...)
9599
return res;
96100
}
97101

102+
static const char *ensure_absolute_path(const char *path, char **absolute)
103+
{
104+
struct strbuf buf = STRBUF_INIT;
105+
106+
if (is_absolute_path(path))
107+
return path;
108+
109+
strbuf_realpath_forgiving(&buf, path, 1);
110+
free(*absolute);
111+
*absolute = strbuf_detach(&buf, NULL);
112+
return *absolute;
113+
}
114+
98115
struct scalar_config {
99116
const char *key;
100117
const char *value;
@@ -397,6 +414,87 @@ static int supports_gvfs_protocol(const char *url, char **cache_server_url)
397414
return 0; /* error out quietly */
398415
}
399416

417+
static char *default_cache_root(const char *root)
418+
{
419+
const char *env;
420+
421+
if (is_unattended())
422+
return xstrfmt("%s/.scalarCache", root);
423+
424+
#ifdef WIN32
425+
(void)env;
426+
return xstrfmt("%.*s.scalarCache", offset_1st_component(root), root);
427+
#elif defined(__APPLE__)
428+
if ((env = getenv("HOME")) && *env)
429+
return xstrfmt("%s/.scalarCache", env);
430+
return NULL;
431+
#else
432+
if ((env = getenv("XDG_CACHE_HOME")) && *env)
433+
return xstrfmt("%s/scalar", env);
434+
if ((env = getenv("HOME")) && *env)
435+
return xstrfmt("%s/.cache/scalar", env);
436+
return NULL;
437+
#endif
438+
}
439+
440+
static int get_repository_id(struct json_iterator *it)
441+
{
442+
if (it->type == JSON_STRING &&
443+
!strcasecmp(".repository.id", it->key.buf)) {
444+
*(char **)it->fn_data = strbuf_detach(&it->string_value, NULL);
445+
return 1;
446+
}
447+
448+
return 0;
449+
}
450+
451+
/* Needs to run this in a worktree; gvfs-helper requires a Git repository */
452+
static char *get_cache_key(const char *url)
453+
{
454+
struct child_process cp = CHILD_PROCESS_INIT;
455+
struct strbuf out = STRBUF_INIT;
456+
char *cache_key = NULL;
457+
458+
cp.git_cmd = 1;
459+
strvec_pushl(&cp.args, "gvfs-helper", "--remote", url,
460+
"endpoint", "vsts/info", NULL);
461+
if (!pipe_command(&cp, NULL, 0, &out, 512, NULL, 0)) {
462+
char *id = NULL;
463+
struct json_iterator it =
464+
JSON_ITERATOR_INIT(out.buf, get_repository_id, &id);
465+
466+
if (iterate_json(&it) < 0)
467+
warning("JSON parse error (%s)", out.buf);
468+
else if (id)
469+
cache_key = xstrfmt("id_%s", id);
470+
free(id);
471+
}
472+
473+
if (!cache_key) {
474+
struct strbuf downcased = STRBUF_INIT;
475+
int hash_algo_index = hash_algo_by_name("sha1");
476+
const struct git_hash_algo *hash_algo = hash_algo_index < 0 ?
477+
the_hash_algo : &hash_algos[hash_algo_index];
478+
git_hash_ctx ctx;
479+
unsigned char hash[GIT_MAX_RAWSZ];
480+
481+
strbuf_addstr(&downcased, url);
482+
strbuf_tolower(&downcased);
483+
484+
hash_algo->init_fn(&ctx);
485+
hash_algo->update_fn(&ctx, downcased.buf, downcased.len);
486+
hash_algo->final_fn(hash, &ctx);
487+
488+
strbuf_release(&downcased);
489+
490+
cache_key = xstrfmt("url_%s",
491+
hash_to_hex_algop(hash, hash_algo));
492+
}
493+
494+
strbuf_release(&out);
495+
return cache_key;
496+
}
497+
400498
static char *remote_default_branch(const char *url)
401499
{
402500
struct child_process cp = CHILD_PROCESS_INIT;
@@ -494,12 +592,48 @@ void load_builtin_commands(const char *prefix, struct cmdnames *cmds)
494592
die("not implemented");
495593
}
496594

595+
static int init_shared_object_cache(const char *url,
596+
const char *local_cache_root)
597+
{
598+
struct strbuf buf = STRBUF_INIT;
599+
int res = 0;
600+
char *cache_key = NULL, *shared_cache_path = NULL;
601+
602+
if (!(cache_key = get_cache_key(url))) {
603+
res = error(_("could not determine cache key for '%s'"), url);
604+
goto cleanup;
605+
}
606+
607+
shared_cache_path = xstrfmt("%s/%s", local_cache_root, cache_key);
608+
if (set_config("gvfs.sharedCache=%s", shared_cache_path)) {
609+
res = error(_("could not configure shared cache"));
610+
goto cleanup;
611+
}
612+
613+
strbuf_addf(&buf, "%s/pack", shared_cache_path);
614+
switch (safe_create_leading_directories(buf.buf)) {
615+
case SCLD_OK: case SCLD_EXISTS:
616+
break; /* okay */
617+
default:
618+
res = error_errno(_("could not initialize '%s'"), buf.buf);
619+
goto cleanup;
620+
}
621+
622+
write_file(git_path("objects/info/alternates"),"%s\n", shared_cache_path);
623+
624+
cleanup:
625+
strbuf_release(&buf);
626+
free(shared_cache_path);
627+
free(cache_key);
628+
return res;
629+
}
630+
497631
static int cmd_clone(int argc, const char **argv)
498632
{
499633
const char *branch = NULL;
500634
int full_clone = 0, single_branch = 0;
501-
const char *cache_server_url = NULL;
502-
char *default_cache_server_url = NULL;
635+
const char *cache_server_url = NULL, *local_cache_root = NULL;
636+
char *default_cache_server_url = NULL, *local_cache_root_abs = NULL;
503637
struct option clone_options[] = {
504638
OPT_STRING('b', "branch", &branch, N_("<branch>"),
505639
N_("branch to checkout after clone")),
@@ -511,6 +645,9 @@ static int cmd_clone(int argc, const char **argv)
511645
OPT_STRING(0, "cache-server-url", &cache_server_url,
512646
N_("<url>"),
513647
N_("the url or friendly name of the cache server")),
648+
OPT_STRING(0, "local-cache-path", &local_cache_root,
649+
N_("<path>"),
650+
N_("override the path for the local Scalar cache")),
514651
OPT_END(),
515652
};
516653
const char * const clone_usage[] = {
@@ -551,8 +688,20 @@ static int cmd_clone(int argc, const char **argv)
551688
if (is_directory(enlistment))
552689
die(_("directory '%s' exists already"), enlistment);
553690

691+
ensure_absolute_path(enlistment, &enlistment);
692+
554693
dir = xstrfmt("%s/src", enlistment);
555694

695+
if (!local_cache_root)
696+
local_cache_root = local_cache_root_abs =
697+
default_cache_root(enlistment);
698+
else
699+
local_cache_root = ensure_absolute_path(local_cache_root,
700+
&local_cache_root_abs);
701+
702+
if (!local_cache_root)
703+
die(_("could not determine local cache root"));
704+
556705
strbuf_reset(&buf);
557706
if (branch)
558707
strbuf_addf(&buf, "init.defaultBranch=%s", branch);
@@ -572,8 +721,28 @@ static int cmd_clone(int argc, const char **argv)
572721

573722
setup_git_directory();
574723

724+
git_config(git_default_config, NULL);
725+
726+
/*
727+
* This `dir_inside_of()` call relies on git_config() having parsed the
728+
* newly-initialized repository config's `core.ignoreCase` value.
729+
*/
730+
if (dir_inside_of(local_cache_root, dir) >= 0) {
731+
struct strbuf path = STRBUF_INIT;
732+
733+
strbuf_addstr(&path, enlistment);
734+
if (chdir("../..") < 0 ||
735+
remove_dir_recursively(&path, 0) < 0)
736+
die(_("'--local-cache-path' cannot be inside the src "
737+
"folder;\nCould not remove '%s'"), enlistment);
738+
739+
die(_("'--local-cache-path' cannot be inside the src folder"));
740+
}
741+
575742
/* common-main already logs `argv` */
576743
trace2_def_repo(the_repository);
744+
trace2_data_intmax("scalar", the_repository, "unattended",
745+
is_unattended());
577746

578747
if (!branch && !(branch = remote_default_branch(url))) {
579748
res = error(_("failed to get default branch for '%s'"), url);
@@ -598,6 +767,8 @@ static int cmd_clone(int argc, const char **argv)
598767
supports_gvfs_protocol(url, &default_cache_server_url);
599768

600769
if (gvfs_protocol) {
770+
if ((res = init_shared_object_cache(url, local_cache_root)))
771+
goto cleanup;
601772
if (!cache_server_url)
602773
cache_server_url = default_cache_server_url;
603774
if (set_config("core.useGVFSHelper=true") ||
@@ -668,6 +839,7 @@ static int cmd_clone(int argc, const char **argv)
668839
free(dir);
669840
strbuf_release(&buf);
670841
free(default_cache_server_url);
842+
free(local_cache_root_abs);
671843
return res;
672844
}
673845

@@ -1026,6 +1198,12 @@ int cmd_main(int argc, const char **argv)
10261198
struct strbuf scalar_usage = STRBUF_INIT;
10271199
int i;
10281200

1201+
if (is_unattended()) {
1202+
setenv("GIT_ASKPASS", "", 0);
1203+
setenv("GIT_TERMINAL_PROMPT", "false", 0);
1204+
git_config_push_parameter("credential.interactive=never");
1205+
}
1206+
10291207
while (argc > 1 && *argv[1] == '-') {
10301208
if (!strcmp(argv[1], "-C")) {
10311209
if (argc < 3)

0 commit comments

Comments
 (0)