From 84d8b491fdbbd9981bc4222fac902afe9f7cb4e2 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Wed, 19 Aug 2026 11:07:03 -0400 Subject: [PATCH] scalar: add --[no-]prefetch option When using the GVFS protocol with 'scalar clone', the first 'git fetch' uses the prefetch endpoint to download commits and trees so history operations are usable immediately after cloning. Some users want to optimize for the initial usability of the repository, and they don't need the full history available right away. They are prepared to wait for future fetches (perhaps in the background) doing that work for them. Add a new --no-prefetch option that skips the initial prefetch. This is implemented by using '-c core.gvfs=X' arguments in the underlying fetch operation to temporarily avoid the prefetch operation for that subcommand only. It's important that this does not actually stop prefetches forever, though that can be adjusted by flipping the appropriate bit in the core.gvfs config option. Signed-off-by: Derrick Stolee --- Documentation/scalar.adoc | 16 ++++++++++++- scalar.c | 48 ++++++++++++++++++++++++++++++++------- t/t9210-scalar.sh | 29 +++++++++++++++++++++++ 3 files changed, 84 insertions(+), 9 deletions(-) diff --git a/Documentation/scalar.adoc b/Documentation/scalar.adoc index e08f22607f0267..966373cac6fcdb 100644 --- a/Documentation/scalar.adoc +++ b/Documentation/scalar.adoc @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] scalar clone [--single-branch] [--branch ] [--full-clone] - [--[no-]src] [--[no-]tags] [--[no-]maintenance] + [--[no-]src] [--[no-]tags] [--[no-]maintenance] [--[no-]prefetch] [--cache-server-url ] [--[verb]-cache-server-url ] [--local-cache-path ] [] scalar list @@ -110,6 +110,20 @@ cloning. If the HEAD at the remote did not point at any branch when background maintenance feature. Use the `--no-maintenance` to skip this configuration. +--prefetch:: +--no-prefetch:: + By default, when cloning with the GVFS Protocol, `scalar clone` + issues a `/gvfs/prefetch` request to hydrate the local object cache + with the commits and trees that back the checked-out branch. Use + `--no-prefetch` to skip that request during the clone so that the + initial worktree becomes ready as quickly as possible. ++ +This only affects the initial fetch performed by `scalar clone`: the +prefetched data is still downloaded by the next `git fetch` (including the +background maintenance `prefetch` task), so the object cache is populated +shortly afterwards. This option has no effect when the GVFS Protocol is not +in use. + --local-cache-path :: Override the path to the local cache root directory; Pre-fetched objects are stored into a repository-dependent subdirectory of that path. diff --git a/scalar.c b/scalar.c index 017ce4a23a16ef..742e4e3436ac9f 100644 --- a/scalar.c +++ b/scalar.c @@ -25,9 +25,19 @@ #include "trace2.h" #include "path.h" #include "json-parser.h" +#include "gvfs.h" #include "remote.h" #include "path.h" +/* + * The `core.gvfs` bitmask that `scalar clone` configures for enlistments + * that use the GVFS Protocol (historically the value `150`). See gvfs.h + * for the meaning of the individual bits. + */ +#define SCALAR_GVFS_MODE (GVFS_BLOCK_COMMANDS | GVFS_MISSING_OK | \ + GVFS_FETCH_SKIP_REACHABILITY_AND_UPLOADPACK | \ + GVFS_PREFETCH_DURING_FETCH) + static int is_unattended(void) { return git_env_bool("Scalar_UNATTENDED", 0); } @@ -780,7 +790,7 @@ static int cmd_clone(int argc, const char **argv) const char *branch = NULL; char *branch_to_free = NULL; int full_clone = 0, single_branch = 0, show_progress = isatty(2); - int src = 1, tags = 1, maintenance = 1; + int src = 1, tags = 1, maintenance = 1, prefetch = 1; const char *cache_server_url = NULL, *local_cache_root = NULL; char *default_cache_server_url = NULL, *local_cache_root_abs = NULL; const char *prefetch_server = NULL, *get_server = NULL, *post_server = NULL; @@ -801,6 +811,9 @@ static int cmd_clone(int argc, const char **argv) N_("specify if tags should be fetched during clone")), OPT_BOOL(0, "maintenance", &maintenance, N_("specify if background maintenance should be enabled")), + OPT_BOOL(0, "prefetch", &prefetch, + N_("specify if commits and trees should be prefetched " + "during clone when using the GVFS Protocol")), OPT_BOOL(0, "gvfs-protocol", &gvfs_protocol, N_("force enable (or disable) the GVFS Protocol")), OPT_STRING(0, "cache-server-url", &cache_server_url, @@ -826,7 +839,8 @@ static int cmd_clone(int argc, const char **argv) }; const char * const clone_usage[] = { N_("scalar clone [--single-branch] [--branch ] [--full-clone]\n" - "\t[--[no-]src] [--[no-]tags] [--[no-]maintenance] [--ref-format ]\n" + "\t[--[no-]src] [--[no-]tags] [--[no-]maintenance] [--[no-]prefetch]\n" + "\t[--ref-format ]\n" "\t[--cache-server-url ] [--[verb]-cache-server-url ]\n" "\t[--local-cache-path ] []"), NULL @@ -977,7 +991,7 @@ static int cmd_clone(int argc, const char **argv) if (!cache_server_url) cache_server_url = default_cache_server_url; if (set_config("core.useGVFSHelper=true") || - set_config("core.gvfs=150") || + set_config("core.gvfs=%d", SCALAR_GVFS_MODE) || set_config("http.%s.version=HTTP/1.1", url)) { res = error(_("could not turn on GVFS helper")); goto cleanup; @@ -1034,11 +1048,29 @@ static int cmd_clone(int argc, const char **argv) if (set_recommended_config(0)) return error(_("could not configure '%s'"), dir); - if ((res = run_git("fetch", "--quiet", - show_progress ? "--progress" : "--no-progress", - "origin", - (tags ? NULL : "--no-tags"), - NULL))) { + strvec_clear(&init_argv); + /* + * When cloning with the GVFS Protocol, the `core.gvfs` value set + * above enables the GVFS_PREFETCH_DURING_FETCH bit, so the `git fetch` + * below issues a `/gvfs/prefetch` request to hydrate the local object + * cache. With `--no-prefetch`, skip that request for this initial + * fetch only (by clearing that bit for this invocation) so the + * worktree becomes ready sooner. The persisted `core.gvfs` value is + * left untouched, so subsequent fetches -- including background + * maintenance -- still prefetch as usual. + */ + if (gvfs_protocol && !prefetch) { + strvec_push(&init_argv, "-c"); + strvec_pushf(&init_argv, "core.gvfs=%d", + SCALAR_GVFS_MODE & ~GVFS_PREFETCH_DURING_FETCH); + } + strvec_pushl(&init_argv, "fetch", "--quiet", + show_progress ? "--progress" : "--no-progress", + "origin", NULL); + if (!tags) + strvec_push(&init_argv, "--no-tags"); + + if ((res = run_git_argv(&init_argv))) { if (gvfs_protocol) { res = error(_("failed to prefetch commits and trees")); goto cleanup; diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh index 75105baa9ec4e5..f190423912ad7d 100755 --- a/t/t9210-scalar.sh +++ b/t/t9210-scalar.sh @@ -461,6 +461,35 @@ test_expect_success '`scalar clone` with GVFS-enabled server' ' ) ' +test_expect_success '`scalar clone --no-prefetch` skips the initial prefetch' ' + git config --global core.askPass true && + + # A normal GVFS-enabled clone issues a "/gvfs/prefetch" request, + # which shows up in the trace as a "prefetch/since" data event. + GIT_TRACE2_EVENT="$(pwd)/with-prefetch-trace" scalar \ + -c credential.interactive=true \ + clone --gvfs-protocol --single-branch \ + -- http://$ORIGIN_HOST_PORT/ with-prefetch && + grep "prefetch/since" with-prefetch-trace && + + # ... but "--no-prefetch" skips that request during the clone. + GIT_TRACE2_EVENT="$(pwd)/no-prefetch-trace" scalar \ + -c credential.interactive=true \ + clone --no-prefetch --gvfs-protocol --single-branch \ + -- http://$ORIGIN_HOST_PORT/ no-prefetch && + ! grep "prefetch/since" no-prefetch-trace && + + : the persisted core.gvfs still enables prefetch during fetch && + echo 150 >expect && + git -C no-prefetch/src config core.gvfs >actual && + test_cmp expect actual && + + : and a subsequent git fetch performs the deferred prefetch && + GIT_TRACE2_EVENT="$(pwd)/fetch-trace" \ + git -C no-prefetch/src fetch origin && + grep "prefetch/since" fetch-trace +' + test_expect_success '`scalar clone` with GVFS-enabled server; local cache path' ' : the fake cache server requires fake authentication && git config --global core.askPass true &&