Skip to content

Commit de6450b

Browse files
dschoderrickstolee
andcommitted
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. We should not be putting the .scalarCache inside the enlistment as a sibling to the 'src' directory, even in "unattended" mode, as it would negate any benefit of a shared object cache because each enlistment absolutely does not share any objects with others. Hence, we move the shared object cache in this case to a level above the enlistment in the unattended mode, so at least there is some hope that it can be reused. This is also critical to the --no-src option, since the shared object cache cannot be located within the Git repository. Co-authored-by: Derrick Stolee <derrickstolee@github.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
1 parent 86e4bf4 commit de6450b

File tree

3 files changed

+212
-6
lines changed

3 files changed

+212
-6
lines changed

Documentation/scalar.adoc

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ SYNOPSIS
99
--------
1010
[verse]
1111
scalar clone [--single-branch] [--branch <main-branch>] [--full-clone]
12-
[--[no-]src] [--[no-]tags] [--[no-]maintenance] <url> [<enlistment>]
12+
[--[no-]src] [--[no-]tags] [--[no-]maintenance]
13+
[--[no-]src] [--local-cache-path <path>] [--cache-server-url <url>]
14+
<url> [<enlistment>]
1315
scalar list
1416
scalar register [--[no-]maintenance] [<enlistment>]
1517
scalar unregister [<enlistment>]
@@ -107,6 +109,17 @@ cloning. If the HEAD at the remote did not point at any branch when
107109
background maintenance feature. Use the `--no-maintenance` to skip
108110
this configuration.
109111

112+
--local-cache-path <path>::
113+
Override the path to the local cache root directory; Pre-fetched objects
114+
are stored into a repository-dependent subdirectory of that path.
115+
+
116+
The default is `<drive>:\.scalarCache` on Windows (on the same drive as the
117+
clone), and `~/.scalarCache` on macOS.
118+
119+
--cache-server-url <url>::
120+
Retrieve missing objects from the specified remote, which is expected to
121+
understand the GVFS protocol.
122+
110123
List
111124
~~~~
112125

diagnose.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ int create_diagnostics_archive(struct repository *r,
186186
struct strvec archiver_args = STRVEC_INIT;
187187
char **argv_copy = NULL;
188188
int stdout_fd = -1, archiver_fd = -1;
189-
char *cache_server_url = NULL;
189+
char *cache_server_url = NULL, *shared_cache = NULL;
190190
struct strbuf buf = STRBUF_INIT;
191191
int res;
192192
struct archive_dir archive_dirs[] = {
@@ -224,8 +224,10 @@ int create_diagnostics_archive(struct repository *r,
224224
strbuf_addf(&buf, "Repository root: %s\n", r->worktree);
225225

226226
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");
227+
repo_config_get_string(r, "gvfs.sharedCache", &shared_cache);
228+
strbuf_addf(&buf, "Cache Server: %s\nLocal Cache: %s\n\n",
229+
cache_server_url ? cache_server_url : "None",
230+
shared_cache ? shared_cache : "None");
229231

230232
get_disk_info(&buf);
231233
write_or_die(stdout_fd, buf.buf, buf.len);
@@ -285,6 +287,7 @@ int create_diagnostics_archive(struct repository *r,
285287
strvec_clear(&archiver_args);
286288
strbuf_release(&buf);
287289
free(cache_server_url);
290+
free(shared_cache);
288291

289292
return res;
290293
}

scalar.c

Lines changed: 192 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,29 @@
77
#include "git-compat-util.h"
88
#include "abspath.h"
99
#include "gettext.h"
10+
#include "hex.h"
1011
#include "parse-options.h"
1112
#include "config.h"
13+
#include "environment.h"
1214
#include "run-command.h"
1315
#include "simple-ipc.h"
1416
#include "fsmonitor-ipc.h"
1517
#include "fsmonitor-settings.h"
1618
#include "refs.h"
1719
#include "dir.h"
20+
#include "object-file.h"
1821
#include "packfile.h"
1922
#include "help.h"
2023
#include "setup.h"
24+
#include "wrapper.h"
2125
#include "trace2.h"
2226
#include "path.h"
2327
#include "json-parser.h"
28+
#include "path.h"
29+
30+
static int is_unattended(void) {
31+
return git_env_bool("Scalar_UNATTENDED", 0);
32+
}
2433

2534
static void setup_enlistment_directory(int argc, const char **argv,
2635
const char * const *usagestr,
@@ -107,6 +116,19 @@ static int run_git(const char *arg, ...)
107116
return res;
108117
}
109118

119+
static const char *ensure_absolute_path(const char *path, char **absolute)
120+
{
121+
struct strbuf buf = STRBUF_INIT;
122+
123+
if (is_absolute_path(path))
124+
return path;
125+
126+
strbuf_realpath_forgiving(&buf, path, 1);
127+
free(*absolute);
128+
*absolute = strbuf_detach(&buf, NULL);
129+
return *absolute;
130+
}
131+
110132
struct scalar_config {
111133
const char *key;
112134
const char *value;
@@ -444,6 +466,92 @@ static int supports_gvfs_protocol(const char *url, char **cache_server_url)
444466
return 0; /* error out quietly */
445467
}
446468

469+
static char *default_cache_root(const char *root)
470+
{
471+
const char *env;
472+
473+
if (is_unattended()) {
474+
struct strbuf path = STRBUF_INIT;
475+
strbuf_addstr(&path, root);
476+
strip_last_path_component(&path);
477+
strbuf_addstr(&path, "/.scalarCache");
478+
return strbuf_detach(&path, NULL);
479+
}
480+
481+
#ifdef WIN32
482+
(void)env;
483+
return xstrfmt("%.*s.scalarCache", offset_1st_component(root), root);
484+
#elif defined(__APPLE__)
485+
if ((env = getenv("HOME")) && *env)
486+
return xstrfmt("%s/.scalarCache", env);
487+
return NULL;
488+
#else
489+
if ((env = getenv("XDG_CACHE_HOME")) && *env)
490+
return xstrfmt("%s/scalar", env);
491+
if ((env = getenv("HOME")) && *env)
492+
return xstrfmt("%s/.cache/scalar", env);
493+
return NULL;
494+
#endif
495+
}
496+
497+
static int get_repository_id(struct json_iterator *it)
498+
{
499+
if (it->type == JSON_STRING &&
500+
!strcasecmp(".repository.id", it->key.buf)) {
501+
*(char **)it->fn_data = strbuf_detach(&it->string_value, NULL);
502+
return 1;
503+
}
504+
505+
return 0;
506+
}
507+
508+
/* Needs to run this in a worktree; gvfs-helper requires a Git repository */
509+
static char *get_cache_key(const char *url)
510+
{
511+
struct child_process cp = CHILD_PROCESS_INIT;
512+
struct strbuf out = STRBUF_INIT;
513+
char *cache_key = NULL;
514+
515+
cp.git_cmd = 1;
516+
strvec_pushl(&cp.args, "gvfs-helper", "--remote", url,
517+
"endpoint", "vsts/info", NULL);
518+
if (!pipe_command(&cp, NULL, 0, &out, 512, NULL, 0)) {
519+
char *id = NULL;
520+
struct json_iterator it =
521+
JSON_ITERATOR_INIT(out.buf, get_repository_id, &id);
522+
523+
if (iterate_json(&it) < 0)
524+
warning("JSON parse error (%s)", out.buf);
525+
else if (id)
526+
cache_key = xstrfmt("id_%s", id);
527+
free(id);
528+
}
529+
530+
if (!cache_key) {
531+
struct strbuf downcased = STRBUF_INIT;
532+
int hash_algo_index = hash_algo_by_name("sha1");
533+
const struct git_hash_algo *hash_algo = hash_algo_index < 0 ?
534+
the_hash_algo : &hash_algos[hash_algo_index];
535+
struct git_hash_ctx ctx;
536+
unsigned char hash[GIT_MAX_RAWSZ];
537+
538+
strbuf_addstr(&downcased, url);
539+
strbuf_tolower(&downcased);
540+
541+
hash_algo->init_fn(&ctx);
542+
hash_algo->update_fn(&ctx, downcased.buf, downcased.len);
543+
hash_algo->final_fn(hash, &ctx);
544+
545+
strbuf_release(&downcased);
546+
547+
cache_key = xstrfmt("url_%s",
548+
hash_to_hex_algop(hash, hash_algo));
549+
}
550+
551+
strbuf_release(&out);
552+
return cache_key;
553+
}
554+
447555
static char *remote_default_branch(const char *url)
448556
{
449557
struct child_process cp = CHILD_PROCESS_INIT;
@@ -538,14 +646,52 @@ void load_builtin_commands(const char *prefix UNUSED,
538646
die("not implemented");
539647
}
540648

649+
static int init_shared_object_cache(const char *url,
650+
const char *local_cache_root)
651+
{
652+
struct strbuf buf = STRBUF_INIT;
653+
int res = 0;
654+
char *cache_key = NULL, *shared_cache_path = NULL, *alternates = NULL;
655+
656+
if (!(cache_key = get_cache_key(url))) {
657+
res = error(_("could not determine cache key for '%s'"), url);
658+
goto cleanup;
659+
}
660+
661+
shared_cache_path = xstrfmt("%s/%s", local_cache_root, cache_key);
662+
if (set_config("gvfs.sharedCache=%s", shared_cache_path)) {
663+
res = error(_("could not configure shared cache"));
664+
goto cleanup;
665+
}
666+
667+
strbuf_addf(&buf, "%s/pack", shared_cache_path);
668+
switch (safe_create_leading_directories(the_repository, buf.buf)) {
669+
case SCLD_OK: case SCLD_EXISTS:
670+
break; /* okay */
671+
default:
672+
res = error_errno(_("could not initialize '%s'"), buf.buf);
673+
goto cleanup;
674+
}
675+
676+
alternates = repo_git_path(the_repository, "objects/info/alternates");
677+
write_file(alternates, "%s\n", shared_cache_path);
678+
679+
cleanup:
680+
strbuf_release(&buf);
681+
free(shared_cache_path);
682+
free(cache_key);
683+
free(alternates);
684+
return res;
685+
}
686+
541687
static int cmd_clone(int argc, const char **argv)
542688
{
543689
const char *branch = NULL;
544690
char *branch_to_free = NULL;
545691
int full_clone = 0, single_branch = 0, show_progress = isatty(2);
546692
int src = 1, tags = 1, maintenance = 1;
547-
const char *cache_server_url = NULL;
548-
char *default_cache_server_url = NULL;
693+
const char *cache_server_url = NULL, *local_cache_root = NULL;
694+
char *default_cache_server_url = NULL, *local_cache_root_abs = NULL;
549695
struct option clone_options[] = {
550696
OPT_STRING('b', "branch", &branch, N_("<branch>"),
551697
N_("branch to checkout after clone")),
@@ -563,6 +709,9 @@ static int cmd_clone(int argc, const char **argv)
563709
OPT_STRING(0, "cache-server-url", &cache_server_url,
564710
N_("<url>"),
565711
N_("the url or friendly name of the cache server")),
712+
OPT_STRING(0, "local-cache-path", &local_cache_root,
713+
N_("<path>"),
714+
N_("override the path for the local Scalar cache")),
566715
OPT_END(),
567716
};
568717
const char * const clone_usage[] = {
@@ -604,11 +753,23 @@ static int cmd_clone(int argc, const char **argv)
604753
if (is_directory(enlistment))
605754
die(_("directory '%s' exists already"), enlistment);
606755

756+
ensure_absolute_path(enlistment, &enlistment);
757+
607758
if (src)
608759
dir = xstrfmt("%s/src", enlistment);
609760
else
610761
dir = xstrdup(enlistment);
611762

763+
if (!local_cache_root)
764+
local_cache_root = local_cache_root_abs =
765+
default_cache_root(enlistment);
766+
else
767+
local_cache_root = ensure_absolute_path(local_cache_root,
768+
&local_cache_root_abs);
769+
770+
if (!local_cache_root)
771+
die(_("could not determine local cache root"));
772+
612773
strbuf_reset(&buf);
613774
if (branch)
614775
strbuf_addf(&buf, "init.defaultBranch=%s", branch);
@@ -628,8 +789,28 @@ static int cmd_clone(int argc, const char **argv)
628789

629790
setup_git_directory();
630791

792+
repo_config(the_repository, git_default_config, NULL);
793+
794+
/*
795+
* This `dir_inside_of()` call relies on git_config() having parsed the
796+
* newly-initialized repository config's `core.ignoreCase` value.
797+
*/
798+
if (dir_inside_of(local_cache_root, dir) >= 0) {
799+
struct strbuf path = STRBUF_INIT;
800+
801+
strbuf_addstr(&path, enlistment);
802+
if (chdir("../..") < 0 ||
803+
remove_dir_recursively(&path, 0) < 0)
804+
die(_("'--local-cache-path' cannot be inside the src "
805+
"folder;\nCould not remove '%s'"), enlistment);
806+
807+
die(_("'--local-cache-path' cannot be inside the src folder"));
808+
}
809+
631810
/* common-main already logs `argv` */
632811
trace2_def_repo(the_repository);
812+
trace2_data_intmax("scalar", the_repository, "unattended",
813+
is_unattended());
633814

634815
if (!branch && !(branch = branch_to_free = remote_default_branch(url))) {
635816
res = error(_("failed to get default branch for '%s'"), url);
@@ -659,6 +840,8 @@ static int cmd_clone(int argc, const char **argv)
659840
supports_gvfs_protocol(url, &default_cache_server_url);
660841

661842
if (gvfs_protocol) {
843+
if ((res = init_shared_object_cache(url, local_cache_root)))
844+
goto cleanup;
662845
if (!cache_server_url)
663846
cache_server_url = default_cache_server_url;
664847
if (set_config("core.useGVFSHelper=true") ||
@@ -737,6 +920,7 @@ static int cmd_clone(int argc, const char **argv)
737920
free(dir);
738921
strbuf_release(&buf);
739922
free(default_cache_server_url);
923+
free(local_cache_root_abs);
740924
return res;
741925
}
742926

@@ -1171,6 +1355,12 @@ int cmd_main(int argc, const char **argv)
11711355
struct strbuf scalar_usage = STRBUF_INIT;
11721356
int i;
11731357

1358+
if (is_unattended()) {
1359+
setenv("GIT_ASKPASS", "", 0);
1360+
setenv("GIT_TERMINAL_PROMPT", "false", 0);
1361+
git_config_push_parameter("credential.interactive=false");
1362+
}
1363+
11741364
while (argc > 1 && *argv[1] == '-') {
11751365
if (!strcmp(argv[1], "-C")) {
11761366
if (argc < 3)

0 commit comments

Comments
 (0)