diff --git a/cache.h b/cache.h index da1f02f99b241a..ebec1cf5e1be97 100644 --- a/cache.h +++ b/cache.h @@ -897,7 +897,7 @@ extern int protect_ntfs; extern const char *core_fsmonitor; extern int core_use_gvfs_helper; extern const char *gvfs_cache_server_url; -extern const char *gvfs_shared_cache_pathname; +extern struct strbuf gvfs_shared_cache_pathname; int core_apply_sparse_checkout; int core_sparse_checkout_cone; diff --git a/config.c b/config.c index f459e3b87a7104..7dae94e45ce431 100644 --- a/config.c +++ b/config.c @@ -1513,19 +1513,17 @@ static int git_default_gvfs_config(const char *var, const char *value) } if (!strcmp(var, "gvfs.sharedcache") && value && *value) { - struct strbuf buf = STRBUF_INIT; - strbuf_addstr(&buf, value); - if (strbuf_normalize_path(&buf) < 0) { + strbuf_setlen(&gvfs_shared_cache_pathname, 0); + strbuf_addstr(&gvfs_shared_cache_pathname, value); + if (strbuf_normalize_path(&gvfs_shared_cache_pathname) < 0) { /* * Pretend it wasn't set. This will cause us to * fallback to ".git/objects" effectively. */ - strbuf_release(&buf); + strbuf_release(&gvfs_shared_cache_pathname); return 0; } - strbuf_trim_trailing_dir_sep(&buf); - - gvfs_shared_cache_pathname = strbuf_detach(&buf, NULL); + strbuf_trim_trailing_dir_sep(&gvfs_shared_cache_pathname); return 0; } diff --git a/environment.c b/environment.c index 5e9b0f74429387..a07692aaed1a5b 100644 --- a/environment.c +++ b/environment.c @@ -90,7 +90,7 @@ int protect_ntfs = PROTECT_NTFS_DEFAULT; const char *core_fsmonitor; int core_use_gvfs_helper; const char *gvfs_cache_server_url; -const char *gvfs_shared_cache_pathname; +struct strbuf gvfs_shared_cache_pathname = STRBUF_INIT; /* * The character that begins a commented line in user-editable file diff --git a/fetch-object.c b/fetch-object.c index a2861d1b41f404..486bdb0dcf86a2 100644 --- a/fetch-object.c +++ b/fetch-object.c @@ -31,10 +31,10 @@ void fetch_objects(const char *remote_name, const struct object_id *oids, int i; if (core_use_gvfs_helper) { - enum ghc__created ghc = GHC__CREATED__NOTHING; + enum gh_client__created ghc = GHC__CREATED__NOTHING; - ghc__queue_oid_array(oids, oid_nr); - ghc__drain_queue(&ghc); + gh_client__queue_oid_array(oids, oid_nr); + gh_client__drain_queue(&ghc); return; } diff --git a/gvfs-helper-client.c b/gvfs-helper-client.c index f295b04227faed..1564610aae594b 100644 --- a/gvfs-helper-client.c +++ b/gvfs-helper-client.c @@ -1,16 +1,9 @@ #include "cache.h" -#include "commit.h" #include "argv-array.h" #include "trace2.h" -#include "progress.h" #include "oidset.h" -#include "revision.h" -#include "list-objects.h" -#include "list-objects-filter.h" -#include "list-objects-filter-options.h" #include "object.h" #include "object-store.h" -#include "bisect.h" #include "gvfs-helper-client.h" #include "sub-process.h" #include "sigchain.h" @@ -18,22 +11,22 @@ #include "quote.h" #include "packfile.h" -static struct oidset ghc__oidset_queued = OIDSET_INIT; -static unsigned long ghc__oidset_count; -static int ghc__includes_immediate; +static struct oidset gh_client__oidset_queued = OIDSET_INIT; +static unsigned long gh_client__oidset_count; +static int gh_client__includes_immediate; -struct ghs__process { +struct gh_server__process { struct subprocess_entry subprocess; /* must be first */ unsigned int supported_capabilities; }; -static int ghs__subprocess_map_initialized; -static struct hashmap ghs__subprocess_map; -static struct object_directory *ghs__chosen_odb; +static int gh_server__subprocess_map_initialized; +static struct hashmap gh_server__subprocess_map; +static struct object_directory *gh_client__chosen_odb; #define CAP_GET (1u<<1) -static int ghc__start_fn(struct subprocess_entry *subprocess) +static int gh_client__start_fn(struct subprocess_entry *subprocess) { static int versions[] = {1, 0}; static struct subprocess_capability capabilities[] = { @@ -41,7 +34,7 @@ static int ghc__start_fn(struct subprocess_entry *subprocess) { NULL, 0 } }; - struct ghs__process *entry = (struct ghs__process *)subprocess; + struct gh_server__process *entry = (struct gh_server__process *)subprocess; return subprocess_handshake(subprocess, "gvfs-helper", versions, NULL, capabilities, @@ -56,7 +49,7 @@ static int ghc__start_fn(struct subprocess_entry *subprocess) * * */ -static int ghc__get__send_command(struct child_process *process) +static int gh_client__get__send_command(struct child_process *process) { struct oidset_iter iter; struct object_id *oid; @@ -71,7 +64,7 @@ static int ghc__get__send_command(struct child_process *process) if (err) return err; - oidset_iter_init(&ghc__oidset_queued, &iter); + oidset_iter_init(&gh_client__oidset_queued, &iter); while ((oid = oidset_iter_next(&iter))) { err = packet_write_fmt_gently(process->in, "%s\n", oid_to_hex(oid)); @@ -95,23 +88,24 @@ static int ghc__get__send_command(struct child_process *process) * In particular, I don't see a need to try to search for the response * value in from our list of alternates. */ -static void ghc__verify_odb_line(const char *line) +static void gh_client__verify_odb_line(const char *line) { const char *v1_odb_path; if (!skip_prefix(line, "odb ", &v1_odb_path)) BUG("verify_odb_line: invalid line '%s'", line); - if (!ghs__chosen_odb || strcmp(v1_odb_path, ghs__chosen_odb->path)) + if (!gh_client__chosen_odb || + strcmp(v1_odb_path, gh_client__chosen_odb->path)) BUG("verify_odb_line: unexpeced odb path '%s' vs '%s'", - v1_odb_path, ghs__chosen_odb->path); + v1_odb_path, gh_client__chosen_odb->path); } /* * Update the loose object cache to include the newly created * object. */ -static void ghc__update_loose_cache(const char *line) +static void gh_client__update_loose_cache(const char *line) { const char *v1_oid; struct object_id oid; @@ -119,13 +113,16 @@ static void ghc__update_loose_cache(const char *line) if (!skip_prefix(line, "loose ", &v1_oid)) BUG("update_loose_cache: invalid line '%s'", line); - odb_loose_cache_add_new_oid(ghs__chosen_odb, &oid); + if (get_oid_hex(v1_oid, &oid)) + BUG("update_loose_cache: invalid line '%s'", line); + + odb_loose_cache_add_new_oid(gh_client__chosen_odb, &oid); } /* * Update the packed-git list to include the newly created packfile. */ -static void ghc__update_packed_git(const char *line) +static void gh_client__update_packed_git(const char *line) { struct strbuf path = STRBUF_INIT; const char *v1_filename; @@ -138,9 +135,10 @@ static void ghc__update_packed_git(const char *line) /* * ODB[0] is the local .git/objects. All others are alternates. */ - is_local = (ghs__chosen_odb == the_repository->objects->odb); + is_local = (gh_client__chosen_odb == the_repository->objects->odb); - strbuf_addf(&path, "%s/pack/%s", ghs__chosen_odb->path, v1_filename); + strbuf_addf(&path, "%s/pack/%s", + gh_client__chosen_odb->path, v1_filename); strbuf_strip_suffix(&path, ".pack"); strbuf_addstr(&path, ".idx"); @@ -181,11 +179,12 @@ static void ghc__update_packed_git(const char *line) * grouped with a queued request for a blob. The tree-walk *might* be * able to continue and let the 404 blob be handled later. */ -static int ghc__get__receive_response(struct child_process *process, - enum ghc__created *p_ghc, - int *p_nr_loose, int *p_nr_packfile) +static int gh_client__get__receive_response( + struct child_process *process, + enum gh_client__created *p_ghc, + int *p_nr_loose, int *p_nr_packfile) { - enum ghc__created ghc = GHC__CREATED__NOTHING; + enum gh_client__created ghc = GHC__CREATED__NOTHING; const char *v1; char *line; int len; @@ -201,17 +200,17 @@ static int ghc__get__receive_response(struct child_process *process, break; if (starts_with(line, "odb")) { - ghc__verify_odb_line(line); + gh_client__verify_odb_line(line); } else if (starts_with(line, "packfile")) { - ghc__update_packed_git(line); + gh_client__update_packed_git(line); ghc |= GHC__CREATED__PACKFILE; *p_nr_packfile += 1; } else if (starts_with(line, "loose")) { - ghc__update_loose_cache(line); + gh_client__update_loose_cache(line); ghc |= GHC__CREATED__LOOSE; *p_nr_loose += 1; } @@ -231,34 +230,38 @@ static int ghc__get__receive_response(struct child_process *process, return err; } -static void ghc__choose_odb(void) +/* + * Select the preferred ODB for fetching missing objects. + * This should be the alternate with the same directory + * name as set in `gvfs.sharedCache`. + * + * Fallback to .git/objects if necessary. + */ +static void gh_client__choose_odb(void) { struct object_directory *odb; - if (ghs__chosen_odb) + if (gh_client__chosen_odb) return; + gh_client__chosen_odb = the_repository->objects->odb; + prepare_alt_odb(the_repository); - if (gvfs_shared_cache_pathname && *gvfs_shared_cache_pathname) { - for (odb = the_repository->objects->odb; odb; odb = odb->next) { - if (!strcmp(odb->path, gvfs_shared_cache_pathname)) { - ghs__chosen_odb = odb; - return; - } + if (!gvfs_shared_cache_pathname.len) + return; + + for (odb = the_repository->objects->odb->next; odb; odb = odb->next) { + if (!strcmp(odb->path, gvfs_shared_cache_pathname.buf)) { + gh_client__chosen_odb = odb; + return; } } - - /* - * Use .git/objects if "gvfs.sharedcache" not set or set to an - * unknown pathname. - */ - ghs__chosen_odb = the_repository->objects->odb; } -static int ghc__get(enum ghc__created *p_ghc) +static int gh_client__get(enum gh_client__created *p_ghc) { - struct ghs__process *entry; + struct gh_server__process *entry; struct child_process *process; struct argv_array argv = ARGV_ARRAY_INIT; struct strbuf quoted = STRBUF_INIT; @@ -268,7 +271,7 @@ static int ghc__get(enum ghc__created *p_ghc) trace2_region_enter("gh-client", "get", the_repository); - ghc__choose_odb(); + gh_client__choose_odb(); /* * TODO decide what defaults we want. @@ -276,27 +279,28 @@ static int ghc__get(enum ghc__created *p_ghc) argv_array_push(&argv, "gvfs-helper"); argv_array_push(&argv, "--fallback"); argv_array_push(&argv, "--cache-server=trust"); - argv_array_pushf(&argv, "--shared-cache=%s", ghs__chosen_odb->path); + argv_array_pushf(&argv, "--shared-cache=%s", + gh_client__chosen_odb->path); argv_array_push(&argv, "server"); sq_quote_argv_pretty("ed, argv.argv); - if (!ghs__subprocess_map_initialized) { - ghs__subprocess_map_initialized = 1; - hashmap_init(&ghs__subprocess_map, + if (!gh_server__subprocess_map_initialized) { + gh_server__subprocess_map_initialized = 1; + hashmap_init(&gh_server__subprocess_map, (hashmap_cmp_fn)cmd2process_cmp, NULL, 0); entry = NULL; } else - entry = (struct ghs__process *)subprocess_find_entry( - &ghs__subprocess_map, quoted.buf); + entry = (struct gh_server__process *)subprocess_find_entry( + &gh_server__subprocess_map, quoted.buf); if (!entry) { entry = xmalloc(sizeof(*entry)); entry->supported_capabilities = 0; err = subprocess_start_argv( - &ghs__subprocess_map, &entry->subprocess, 1, - &argv, ghc__start_fn); + &gh_server__subprocess_map, &entry->subprocess, 1, + &argv, gh_client__start_fn); if (err) { free(entry); goto leave_region; @@ -307,7 +311,7 @@ static int ghc__get(enum ghc__created *p_ghc) if (!(CAP_GET & entry->supported_capabilities)) { error("gvfs-helper: does not support GET"); - subprocess_stop(&ghs__subprocess_map, + subprocess_stop(&gh_server__subprocess_map, (struct subprocess_entry *)entry); free(entry); err = -1; @@ -316,15 +320,15 @@ static int ghc__get(enum ghc__created *p_ghc) sigchain_push(SIGPIPE, SIG_IGN); - err = ghc__get__send_command(process); + err = gh_client__get__send_command(process); if (!err) - err = ghc__get__receive_response(process, p_ghc, + err = gh_client__get__receive_response(process, p_ghc, &nr_loose, &nr_packfile); sigchain_pop(SIGPIPE); if (err) { - subprocess_stop(&ghs__subprocess_map, + subprocess_stop(&gh_server__subprocess_map, (struct subprocess_entry *)entry); free(entry); } @@ -334,10 +338,10 @@ static int ghc__get(enum ghc__created *p_ghc) strbuf_release("ed); trace2_data_intmax("gh-client", the_repository, - "get/immediate", ghc__includes_immediate); + "get/immediate", gh_client__includes_immediate); trace2_data_intmax("gh-client", the_repository, - "get/nr_objects", ghc__oidset_count); + "get/nr_objects", gh_client__oidset_count); if (nr_loose) trace2_data_intmax("gh-client", the_repository, @@ -353,22 +357,22 @@ static int ghc__get(enum ghc__created *p_ghc) trace2_region_leave("gh-client", "get", the_repository); - oidset_clear(&ghc__oidset_queued); - ghc__oidset_count = 0; - ghc__includes_immediate = 0; + oidset_clear(&gh_client__oidset_queued); + gh_client__oidset_count = 0; + gh_client__includes_immediate = 0; return err; } -void ghc__queue_oid(const struct object_id *oid) +void gh_client__queue_oid(const struct object_id *oid) { // TODO consider removing this trace2. it is useful for interactive // TODO debugging, but may generate way too much noise for a data // TODO event. - trace2_printf("ghc__queue_oid: %s", oid_to_hex(oid)); + trace2_printf("gh_client__queue_oid: %s", oid_to_hex(oid)); - if (!oidset_insert(&ghc__oidset_queued, oid)) - ghc__oidset_count++; + if (!oidset_insert(&gh_client__oidset_queued, oid)) + gh_client__oidset_count++; } /* @@ -376,35 +380,36 @@ void ghc__queue_oid(const struct object_id *oid) * rather than the component parts, but fetch_objects() uses * this model (because of the call in sha1-file.c). */ -void ghc__queue_oid_array(const struct object_id *oids, int oid_nr) +void gh_client__queue_oid_array(const struct object_id *oids, int oid_nr) { int k; for (k = 0; k < oid_nr; k++) - ghc__queue_oid(&oids[k]); + gh_client__queue_oid(&oids[k]); } -int ghc__drain_queue(enum ghc__created *p_ghc) +int gh_client__drain_queue(enum gh_client__created *p_ghc) { *p_ghc = GHC__CREATED__NOTHING; - if (!ghc__oidset_count) + if (!gh_client__oidset_count) return 0; - return ghc__get(p_ghc); + return gh_client__get(p_ghc); } -int ghc__get_immediate(const struct object_id *oid, enum ghc__created *p_ghc) +int gh_client__get_immediate(const struct object_id *oid, + enum gh_client__created *p_ghc) { - ghc__includes_immediate = 1; + gh_client__includes_immediate = 1; // TODO consider removing this trace2. it is useful for interactive // TODO debugging, but may generate way too much noise for a data // TODO event. - trace2_printf("ghc__get_immediate: %s", oid_to_hex(oid)); + trace2_printf("gh_client__get_immediate: %s", oid_to_hex(oid)); - if (!oidset_insert(&ghc__oidset_queued, oid)) - ghc__oidset_count++; + if (!oidset_insert(&gh_client__oidset_queued, oid)) + gh_client__oidset_count++; - return ghc__drain_queue(p_ghc); + return gh_client__drain_queue(p_ghc); } diff --git a/gvfs-helper-client.h b/gvfs-helper-client.h index 61f026d1faa233..5f5e824bb0771a 100644 --- a/gvfs-helper-client.h +++ b/gvfs-helper-client.h @@ -4,7 +4,7 @@ struct repository; struct commit; -enum ghc__created { +enum gh_client__created { /* * The _get_ operation did not create anything. If doesn't * matter if `gvfs-helper` had errors or not -- just that @@ -13,7 +13,7 @@ enum ghc__created { GHC__CREATED__NOTHING = 0, /* - * The _get_operation created one or more packfiles. + * The _get_ operation created one or more packfiles. */ GHC__CREATED__PACKFILE = 1<<1, @@ -40,7 +40,8 @@ enum ghc__created { * It is undefined whether the requested OID will be loose or * in a packfile. */ -int ghc__get_immediate(const struct object_id *oid, enum ghc__created *p_ghc); +int gh_client__get_immediate(const struct object_id *oid, + enum gh_client__created *p_ghc); /* * Queue this OID for a future fetch using `gvfs-helper service`. @@ -53,9 +54,9 @@ int ghc__get_immediate(const struct object_id *oid, enum ghc__created *p_ghc); * Callers should not rely on the queued object being on disk until * the queue has been drained. */ -void ghc__queue_oid(const struct object_id *oid); -void ghc__queue_oid_array(const struct object_id *oids, int oid_nr); +void gh_client__queue_oid(const struct object_id *oid); +void gh_client__queue_oid_array(const struct object_id *oids, int oid_nr); -int ghc__drain_queue(enum ghc__created *p_ghc); +int gh_client__drain_queue(enum gh_client__created *p_ghc); #endif /* GVFS_HELPER_CLIENT_H */ diff --git a/gvfs-helper.c b/gvfs-helper.c index 37726bfef99c21..eec3fcee99aa8d 100644 --- a/gvfs-helper.c +++ b/gvfs-helper.c @@ -81,10 +81,11 @@ // // Fetch 1 or more objects. If a cache-server is configured, // try it first. Optionally fallback to the main Git server. +// // Create 1 or more loose objects and/or packfiles in the -// requested shared-cache directory (given on the command -// line and which is reported at the beginning of the -// response). +// shared-cache ODB. (The pathname of the selected ODB is +// reported at the beginning of the response; this should +// match the pathname given on the command line). // // git> get // git> @@ -404,10 +405,16 @@ static void gh__response_status__set_from_slot( strbuf_addf(&status->error_message, "%s (curl)", curl_easy_strerror(status->curl_code)); status->ec = GH__ERROR_CODE__CURL_ERROR; + + trace2_data_string("gvfs-helper", NULL, + "error/curl", status->error_message.buf); } else { strbuf_addf(&status->error_message, "HTTP %ld Unexpected", status->response_code); status->ec = GH__ERROR_CODE__HTTP_UNEXPECTED_CODE; + + trace2_data_string("gvfs-helper", NULL, + "error/http", status->error_message.buf); } if (status->ec != GH__ERROR_CODE__OK) @@ -632,26 +639,88 @@ static int option_parse_cache_server_mode(const struct option *opt, } /* - * Let command line args override "gvfs.sharedcache" config setting. + * Let command line args override "gvfs.sharedcache" config setting + * and override the value set by git_default_config(). * - * It would be nice to move this to parse-options.c as an - * OPTION_PATHNAME handler. And maybe have flags for exists() - * and is_directory(). + * The command line is parsed *AFTER* the config is loaded, so + * prepared_alt_odb() has already been called any default or inherited + * shared-cache has already been set. + * + * We have a chance to override it here. */ static int option_parse_shared_cache_directory(const struct option *opt, const char *arg, int unset) { + struct strbuf buf_arg = STRBUF_INIT; + if (unset) /* should not happen */ return error(_("missing value for switch '%s'"), opt->long_name); - if (!is_directory(arg)) - return error(_("value for switch '%s' is not a directory: '%s'"), - opt->long_name, arg); + strbuf_addstr(&buf_arg, arg); + if (strbuf_normalize_path(&buf_arg) < 0) { + /* + * Pretend command line wasn't given. Use whatever + * settings we already have from the config. + */ + strbuf_release(&buf_arg); + return 0; + } + strbuf_trim_trailing_dir_sep(&buf_arg); - gvfs_shared_cache_pathname = arg; + if (!strbuf_cmp(&buf_arg, &gvfs_shared_cache_pathname)) { + /* + * The command line argument matches what we got from + * the config, so we're already setup correctly. (And + * we have already verified that the directory exists + * on disk.) + */ + strbuf_release(&buf_arg); + return 0; + } - return 0; + else if (!gvfs_shared_cache_pathname.len) { + /* + * A shared-cache was requested and we did not inherit one. + * Try it, but let alt_odb_usabe() secretly disable it if + * it cannot create the directory on disk. + */ + strbuf_addbuf(&gvfs_shared_cache_pathname, &buf_arg); + + add_to_alternates_memory(buf_arg.buf); + + strbuf_release(&buf_arg); + return 0; + } + + else { + /* + * The requested shared-cache is different from the one + * we inherited. Replace the inherited value with this + * one, but smartly fallback if necessary. + */ + struct strbuf buf_prev = STRBUF_INIT; + + strbuf_addbuf(&buf_prev, &gvfs_shared_cache_pathname); + + strbuf_setlen(&gvfs_shared_cache_pathname, 0); + strbuf_addbuf(&gvfs_shared_cache_pathname, &buf_arg); + + add_to_alternates_memory(buf_arg.buf); + + /* + * alt_odb_usabe() releases gvfs_shared_cache_pathname + * if it cannot create the directory on disk, so fallback + * to the previous choice when it fails. + */ + if (!gvfs_shared_cache_pathname.len) + strbuf_addbuf(&gvfs_shared_cache_pathname, + &buf_prev); + + strbuf_release(&buf_arg); + strbuf_release(&buf_prev); + return 0; + } } /* @@ -949,24 +1018,20 @@ static void approve_cache_server_creds(void) } /* - * Select the ODB directory where we will write objects that we - * download. If was given on the command line or define in the - * config, use the local ODB (in ".git/objects"). + * Get the pathname to the ODB where we write objects that we download. */ static void select_odb(void) { - const char *odb_path = NULL; + prepare_alt_odb(the_repository); strbuf_init(&gh__global.buf_odb_path, 0); - if (gvfs_shared_cache_pathname && *gvfs_shared_cache_pathname) - odb_path = gvfs_shared_cache_pathname; - else { - prepare_alt_odb(the_repository); - odb_path = the_repository->objects->odb->path; - } - - strbuf_addstr(&gh__global.buf_odb_path, odb_path); + if (gvfs_shared_cache_pathname.len) + strbuf_addbuf(&gh__global.buf_odb_path, + &gvfs_shared_cache_pathname); + else + strbuf_addstr(&gh__global.buf_odb_path, + the_repository->objects->odb->path); } /* @@ -1981,6 +2046,7 @@ static enum gh__error_code do_server_subprocess_get(void) goto cleanup; } + ec = status.ec; err = 0; if (ec == GH__ERROR_CODE__OK) err = packet_write_fmt_gently(1, "ok\n"); diff --git a/sha1-file.c b/sha1-file.c index ca90b059fcfa2a..43375b25f14d4e 100644 --- a/sha1-file.c +++ b/sha1-file.c @@ -443,6 +443,8 @@ const char *loose_object_path(struct repository *r, struct strbuf *buf, return odb_loose_path(r->objects->odb, buf, oid); } +static int gvfs_matched_shared_cache_to_alternate; + /* * Return non-zero iff the path is usable as an alternate object database. */ @@ -452,6 +454,52 @@ static int alt_odb_usable(struct raw_object_store *o, { struct object_directory *odb; + if (!strbuf_cmp(path, &gvfs_shared_cache_pathname)) { + /* + * `gvfs.sharedCache` is the preferred alternate that we + * will use with `gvfs-helper.exe` to dynamically fetch + * missing objects. It is set during git_default_config(). + * + * Make sure the directory exists on disk before we let the + * stock code discredit it. + */ + struct strbuf buf_pack_foo = STRBUF_INIT; + enum scld_error scld; + + /* + * Force create the "" and "/pack" directories, if + * not present on disk. Append an extra bogus directory to + * get safe_create_leading_directories() to see "/pack" + * as a leading directory of something deeper (which it + * won't create). + */ + strbuf_addf(&buf_pack_foo, "%s/pack/foo", path->buf); + + scld = safe_create_leading_directories(buf_pack_foo.buf); + if (scld != SCLD_OK && scld != SCLD_EXISTS) { + error_errno(_("could not create shared-cache ODB '%s'"), + gvfs_shared_cache_pathname.buf); + + strbuf_release(&buf_pack_foo); + + /* + * Pretend no shared-cache was requested and + * effectively fallback to ".git/objects" for + * fetching missing objects. + */ + strbuf_release(&gvfs_shared_cache_pathname); + return 0; + } + + /* + * We know that there is an alternate (either from + * .git/objects/info/alternates or from a memory-only + * entry) associated with the shared-cache directory. + */ + gvfs_matched_shared_cache_to_alternate++; + strbuf_release(&buf_pack_foo); + } + /* Detect cases where alternate disappeared */ if (!is_directory(path->buf)) { error(_("object directory %s does not exist; " @@ -867,6 +915,33 @@ void prepare_alt_odb(struct repository *r) link_alt_odb_entries(r, r->objects->alternate_db, PATH_SEP, NULL, 0); read_info_alternates(r, r->objects->odb->path, 0); + + if (gvfs_shared_cache_pathname.len && + !gvfs_matched_shared_cache_to_alternate) { + /* + * There is no entry in .git/objects/info/alternates for + * the requested shared-cache directory. Therefore, the + * odb-list does not contain this directory. + * + * Force this directory into the odb-list as an in-memory + * alternate. Implicitly create the directory on disk, if + * necessary. + * + * See GIT_ALTERNATE_OBJECT_DIRECTORIES for another example + * of this kind of usage. + * + * Note: This has the net-effect of allowing Git to treat + * `gvfs.sharedCache` as an unofficial alternate. This + * usage should be discouraged for compatbility reasons + * with other tools in the overall Git ecosystem (that + * won't know about this trick). It would be much better + * for us to update .git/objects/info/alternates instead. + * The code here is considered a backstop. + */ + link_alt_odb_entries(r, gvfs_shared_cache_pathname.buf, + '\n', NULL, 0); + } + r->objects->loaded_alternates = 1; } @@ -1606,12 +1681,12 @@ int oid_object_info_extended(struct repository *r, const struct object_id *oid, return 0; if (core_use_gvfs_helper && !tried_gvfs_helper) { - enum ghc__created ghc; + enum gh_client__created ghc; if (flags & OBJECT_INFO_SKIP_FETCH_OBJECT) return -1; - ghc__get_immediate(real, &ghc); + gh_client__get_immediate(real, &ghc); tried_gvfs_helper = 1; /*