diff --git a/Documentation/scalar.adoc b/Documentation/scalar.adoc index e08f22607f0267..f7a33e1245b310 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,22 @@ 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. The tip commit + and the trees needed for the initial checkout are still downloaded + through the `/gvfs/objects` endpoint. ++ +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..b55311847423bf 100644 --- a/scalar.c +++ b/scalar.c @@ -25,9 +25,21 @@ #include "trace2.h" #include "path.h" #include "json-parser.h" +#include "gvfs.h" +#include "gvfs-helper-client.h" +#include "object-name.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 +792,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 +813,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 +841,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 +993,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 +1050,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; @@ -1066,6 +1100,26 @@ static int cmd_clone(int argc, const char **argv) strbuf_reset(&buf); strbuf_addf(&buf, "origin/%s", branch); + if (gvfs_protocol && !prefetch) { + struct object_id checkout_oid; + enum gh_client__created ghc; + + /* + * A commit requested via the GVFS objects POST endpoint + * includes the trees needed to check it out. + */ + repo_config(the_repository, git_default_config, NULL); + if (repo_get_oid(the_repository, buf.buf, &checkout_oid)) { + res = error(_("could not resolve '%s'"), buf.buf); + goto cleanup; + } + gh_client__queue_oid(&checkout_oid); + if (gh_client__drain_queue(&ghc)) { + res = error(_("failed to download trees for '%s'"), + buf.buf); + goto cleanup; + } + } res = run_git("checkout", "-f", "-t", buf.buf, NULL); if (res) goto cleanup; diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh index 75105baa9ec4e5..4378ad0730c9e9 100755 --- a/t/t9210-scalar.sh +++ b/t/t9210-scalar.sh @@ -461,6 +461,42 @@ 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 && + tip=$(git rev-parse HEAD) && + + # 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 while + # fetching the tip commit and its trees through the objects POST + # endpoint before checkout. + GIT_TRACE2_EVENT="$(pwd)/no-prefetch-trace" \ + GIT_TRACE2_PERF="$(pwd)/no-prefetch-perf" scalar \ + -c credential.interactive=true \ + clone --no-prefetch --gvfs-protocol --single-branch \ + -- http://$ORIGIN_HOST_PORT/ no-prefetch && + ! grep "prefetch/since" no-prefetch-trace && + grep "gh_client__queue_oid: $tip" no-prefetch-perf && + test_trace2_data gh-client objects/post/nr_objects 1 \ + 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 &&