]> git.ipfire.org Git - thirdparty/git.git/blobdiff - builtin/fetch.c
Merge branch 'rc/negotiate-only-typofix'
[thirdparty/git.git] / builtin / fetch.c
index c480db32ce31e27a773daa3fb4e965cb36e9172d..55d2568b188832ef705feb63d6a2a72ae6a4aea3 100644 (file)
@@ -28,6 +28,7 @@
 #include "promisor-remote.h"
 #include "commit-graph.h"
 #include "shallow.h"
+#include "worktree.h"
 
 #define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000)
 
@@ -75,6 +76,7 @@ static struct transport *gtransport;
 static struct transport *gsecondary;
 static const char *submodule_prefix = "";
 static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
+static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;
 static int recurse_submodules_default = RECURSE_SUBMODULES_ON_DEMAND;
 static int shown_url = 0;
 static struct refspec refmap = REFSPEC_INIT_FETCH;
@@ -166,7 +168,7 @@ static struct option builtin_fetch_options[] = {
                 N_("prune remote-tracking branches no longer on remote")),
        OPT_BOOL('P', "prune-tags", &prune_tags,
                 N_("prune local tags no longer on remote and clobber changed tags")),
-       OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules, N_("on-demand"),
+       OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules_cli, N_("on-demand"),
                    N_("control recursive fetching of submodules"),
                    PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules),
        OPT_BOOL(0, "dry-run", &dry_run,
@@ -222,17 +224,22 @@ static struct option builtin_fetch_options[] = {
        OPT_END()
 };
 
-static void unlock_pack(void)
+static void unlock_pack(unsigned int flags)
 {
        if (gtransport)
-               transport_unlock_pack(gtransport);
+               transport_unlock_pack(gtransport, flags);
        if (gsecondary)
-               transport_unlock_pack(gsecondary);
+               transport_unlock_pack(gsecondary, flags);
+}
+
+static void unlock_pack_atexit(void)
+{
+       unlock_pack(0);
 }
 
 static void unlock_pack_on_signal(int signo)
 {
-       unlock_pack();
+       unlock_pack(TRANSPORT_UNLOCK_PACK_IN_SIGNAL_HANDLER);
        sigchain_pop(signo);
        raise(signo);
 }
@@ -552,7 +559,7 @@ static struct ref *get_ref_map(struct remote *remote,
                for (i = 0; i < fetch_refspec->nr; i++)
                        get_fetch_map(ref_map, &fetch_refspec->items[i], &oref_tail, 1);
        } else if (refmap.nr) {
-               die("--refmap option is only meaningful with command-line refspec(s).");
+               die("--refmap option is only meaningful with command-line refspec(s)");
        } else {
                /* Use the defaults */
                struct branch *branch = branch_get(NULL);
@@ -583,7 +590,7 @@ static struct ref *get_ref_map(struct remote *remote,
                } else if (!prefetch) {
                        ref_map = get_remote_ref(remote_refs, "HEAD");
                        if (!ref_map)
-                               die(_("Couldn't find remote ref HEAD"));
+                               die(_("couldn't find remote ref HEAD"));
                        ref_map->fetch_head_status = FETCH_HEAD_MERGE;
                        tail = &ref_map->next;
                }
@@ -712,7 +719,7 @@ static void adjust_refcol_width(const struct ref *ref)
        int max, rlen, llen, len;
 
        /* uptodate lines are only shown on high verbosity level */
-       if (!verbosity && oideq(&ref->peer_ref->old_oid, &ref->old_oid))
+       if (verbosity <= 0 && oideq(&ref->peer_ref->old_oid, &ref->old_oid))
                return;
 
        max    = term_columns();
@@ -748,6 +755,9 @@ static void prepare_format_display(struct ref *ref_map)
        struct ref *rm;
        const char *format = "full";
 
+       if (verbosity < 0)
+               return;
+
        git_config_get_string_tmp("fetch.output", &format);
        if (!strcasecmp(format, "full"))
                compact_format = 0;
@@ -827,7 +837,12 @@ static void format_display(struct strbuf *display, char code,
                           const char *remote, const char *local,
                           int summary_width)
 {
-       int width = (summary_width + strlen(summary) - gettext_width(summary));
+       int width;
+
+       if (verbosity < 0)
+               return;
+
+       width = (summary_width + strlen(summary) - gettext_width(summary));
 
        strbuf_addf(display, "%c %-*s ", code, width, summary);
        if (!compact_format)
@@ -840,19 +855,16 @@ static void format_display(struct strbuf *display, char code,
 
 static int update_local_ref(struct ref *ref,
                            struct ref_transaction *transaction,
-                           const char *remote,
-                           const struct ref *remote_ref,
-                           struct strbuf *display,
-                           int summary_width)
+                           const char *remote, const struct ref *remote_ref,
+                           struct strbuf *display, int summary_width,
+                           struct worktree **worktrees)
 {
        struct commit *current = NULL, *updated;
-       enum object_type type;
-       struct branch *current_branch = branch_get(NULL);
+       const struct worktree *wt;
        const char *pretty_ref = prettify_refname(ref->name);
        int fast_forward = 0;
 
-       type = oid_object_info(the_repository, &ref->new_oid, NULL);
-       if (type < 0)
+       if (!repo_has_object_file(the_repository, &ref->new_oid))
                die(_("object %s not found"), oid_to_hex(&ref->new_oid));
 
        if (oideq(&ref->old_oid, &ref->new_oid)) {
@@ -862,16 +874,17 @@ static int update_local_ref(struct ref *ref,
                return 0;
        }
 
-       if (current_branch &&
-           !strcmp(ref->name, current_branch->name) &&
-           !(update_head_ok || is_bare_repository()) &&
-           !is_null_oid(&ref->old_oid)) {
+       if (!update_head_ok &&
+           (wt = find_shared_symref(worktrees, "HEAD", ref->name)) &&
+           !wt->is_bare && !is_null_oid(&ref->old_oid)) {
                /*
                 * If this is the head, and it's not okay to update
                 * the head, and the old value of the head isn't empty...
                 */
                format_display(display, '!', _("[rejected]"),
-                              _("can't fetch in current branch"),
+                              wt->is_current ?
+                                      _("can't fetch in current branch") :
+                                      _("checked out in another worktree"),
                               remote, pretty_ref, summary_width);
                return 1;
        }
@@ -964,7 +977,7 @@ static int update_local_ref(struct ref *ref,
        }
 }
 
-static int iterate_ref_map(void *cb_data, struct object_id *oid)
+static const struct object_id *iterate_ref_map(void *cb_data)
 {
        struct ref **rm = cb_data;
        struct ref *ref = *rm;
@@ -972,10 +985,9 @@ static int iterate_ref_map(void *cb_data, struct object_id *oid)
        while (ref && ref->status == REF_STATUS_REJECT_SHALLOW)
                ref = ref->next;
        if (!ref)
-               return -1; /* end of the list */
+               return NULL;
        *rm = ref->next;
-       oidcpy(oid, &ref->old_oid);
-       return 0;
+       return &ref->old_oid;
 }
 
 struct fetch_head {
@@ -990,7 +1002,7 @@ static int open_fetch_head(struct fetch_head *fetch_head)
        if (write_fetch_head) {
                fetch_head->fp = fopen(filename, "a");
                if (!fetch_head->fp)
-                       return error_errno(_("cannot open %s"), filename);
+                       return error_errno(_("cannot open '%s'"), filename);
                strbuf_init(&fetch_head->buf, 0);
        } else {
                fetch_head->fp = NULL;
@@ -1062,19 +1074,19 @@ static void close_fetch_head(struct fetch_head *fetch_head)
 }
 
 static const char warn_show_forced_updates[] =
-N_("Fetch normally indicates which branches had a forced update,\n"
-   "but that check has been disabled. To re-enable, use '--show-forced-updates'\n"
-   "flag or run 'git config fetch.showForcedUpdates true'.");
+N_("fetch normally indicates which branches had a forced update,\n"
+   "but that check has been disabled; to re-enable, use '--show-forced-updates'\n"
+   "flag or run 'git config fetch.showForcedUpdates true'");
 static const char warn_time_show_forced_updates[] =
-N_("It took %.2f seconds to check forced updates. You can use\n"
+N_("it took %.2f seconds to check forced updates; you can use\n"
    "'--no-show-forced-updates' or run 'git config fetch.showForcedUpdates false'\n"
-   " to avoid this check.\n");
+   "to avoid this check\n");
 
 static int store_updated_refs(const char *raw_url, const char *remote_name,
-                             int connectivity_checked, struct ref *ref_map)
+                             int connectivity_checked, struct ref *ref_map,
+                             struct worktree **worktrees)
 {
        struct fetch_head fetch_head;
-       struct commit *commit;
        int url_len, i, rc = 0;
        struct strbuf note = STRBUF_INIT, err = STRBUF_INIT;
        struct ref_transaction *transaction = NULL;
@@ -1122,20 +1134,33 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
             want_status <= FETCH_HEAD_IGNORE;
             want_status++) {
                for (rm = ref_map; rm; rm = rm->next) {
+                       struct commit *commit = NULL;
                        struct ref *ref = NULL;
 
                        if (rm->status == REF_STATUS_REJECT_SHALLOW) {
                                if (want_status == FETCH_HEAD_MERGE)
-                                       warning(_("reject %s because shallow roots are not allowed to be updated"),
+                                       warning(_("rejected %s because shallow roots are not allowed to be updated"),
                                                rm->peer_ref ? rm->peer_ref->name : rm->name);
                                continue;
                        }
 
-                       commit = lookup_commit_reference_gently(the_repository,
-                                                               &rm->old_oid,
-                                                               1);
-                       if (!commit)
-                               rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE;
+                       /*
+                        * References in "refs/tags/" are often going to point
+                        * to annotated tags, which are not part of the
+                        * commit-graph. We thus only try to look up refs in
+                        * the graph which are not in that namespace to not
+                        * regress performance in repositories with many
+                        * annotated tags.
+                        */
+                       if (!starts_with(rm->name, "refs/tags/"))
+                               commit = lookup_commit_in_graph(the_repository, &rm->old_oid);
+                       if (!commit) {
+                               commit = lookup_commit_reference_gently(the_repository,
+                                                                       &rm->old_oid,
+                                                                       1);
+                               if (!commit)
+                                       rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE;
+                       }
 
                        if (rm->fetch_head_status != want_status)
                                continue;
@@ -1188,7 +1213,8 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                        strbuf_reset(&note);
                        if (ref) {
                                rc |= update_local_ref(ref, transaction, what,
-                                                      rm, &note, summary_width);
+                                                      rm, &note, summary_width,
+                                                      worktrees);
                                free(ref);
                        } else if (write_fetch_head || dry_run) {
                                /*
@@ -1202,13 +1228,12 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                                               "FETCH_HEAD", summary_width);
                        }
                        if (note.len) {
-                               if (verbosity >= 0 && !shown_url) {
+                               if (!shown_url) {
                                        fprintf(stderr, _("From %.*s\n"),
                                                        url_len, url);
                                        shown_url = 1;
                                }
-                               if (verbosity >= 0)
-                                       fprintf(stderr, " %s\n", note.buf);
+                               fprintf(stderr, " %s\n", note.buf);
                        }
                }
        }
@@ -1229,7 +1254,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                      " 'git remote prune %s' to remove any old, conflicting "
                      "branches"), remote_name);
 
-       if (advice_fetch_show_forced_updates) {
+       if (advice_enabled(ADVICE_FETCH_SHOW_FORCED_UPDATES)) {
                if (!fetch_show_forced_updates) {
                        warning(_(warn_show_forced_updates));
                } else if (forced_updates_ms > FORCED_UPDATES_DELAY_WARNING_IN_MS) {
@@ -1282,37 +1307,35 @@ static int check_exist_and_connected(struct ref *ref_map)
        return check_connected(iterate_ref_map, &rm, &opt);
 }
 
-static int fetch_refs(struct transport *transport, struct ref *ref_map)
+static int fetch_and_consume_refs(struct transport *transport,
+                                 struct ref *ref_map,
+                                 struct worktree **worktrees)
 {
-       int ret = check_exist_and_connected(ref_map);
+       int connectivity_checked = 1;
+       int ret;
+
+       /*
+        * We don't need to perform a fetch in case we can already satisfy all
+        * refs.
+        */
+       ret = check_exist_and_connected(ref_map);
        if (ret) {
                trace2_region_enter("fetch", "fetch_refs", the_repository);
                ret = transport_fetch_refs(transport, ref_map);
                trace2_region_leave("fetch", "fetch_refs", the_repository);
+               if (ret)
+                       goto out;
+               connectivity_checked = transport->smart_options ?
+                       transport->smart_options->connectivity_checked : 0;
        }
-       if (!ret)
-               /*
-                * Keep the new pack's ".keep" file around to allow the caller
-                * time to update refs to reference the new objects.
-                */
-               return 0;
-       transport_unlock_pack(transport);
-       return ret;
-}
 
-/* Update local refs based on the ref values fetched from a remote */
-static int consume_refs(struct transport *transport, struct ref *ref_map)
-{
-       int connectivity_checked = transport->smart_options
-               ? transport->smart_options->connectivity_checked : 0;
-       int ret;
        trace2_region_enter("fetch", "consume_refs", the_repository);
-       ret = store_updated_refs(transport->url,
-                                transport->remote->name,
-                                connectivity_checked,
-                                ref_map);
-       transport_unlock_pack(transport);
+       ret = store_updated_refs(transport->url, transport->remote->name,
+                                connectivity_checked, ref_map, worktrees);
        trace2_region_leave("fetch", "consume_refs", the_repository);
+
+out:
+       transport_unlock_pack(transport, 0);
        return ret;
 }
 
@@ -1371,18 +1394,18 @@ static int prune_refs(struct refspec *rs, struct ref *ref_map,
        return result;
 }
 
-static void check_not_current_branch(struct ref *ref_map)
+static void check_not_current_branch(struct ref *ref_map,
+                                    struct worktree **worktrees)
 {
-       struct branch *current_branch = branch_get(NULL);
-
-       if (is_bare_repository() || !current_branch)
-               return;
-
+       const struct worktree *wt;
        for (; ref_map; ref_map = ref_map->next)
-               if (ref_map->peer_ref && !strcmp(current_branch->refname,
-                                       ref_map->peer_ref->name))
-                       die(_("Refusing to fetch into current branch %s "
-                           "of non-bare repository"), current_branch->refname);
+               if (ref_map->peer_ref &&
+                   (wt = find_shared_symref(worktrees, "HEAD",
+                                            ref_map->peer_ref->name)) &&
+                   !wt->is_bare)
+                       die(_("refusing to fetch into branch '%s' "
+                             "checked out at '%s'"),
+                           ref_map->peer_ref->name, wt->path);
 }
 
 static int truncate_fetch_head(void)
@@ -1391,7 +1414,7 @@ static int truncate_fetch_head(void)
        FILE *fp = fopen_for_writing(filename);
 
        if (!fp)
-               return error_errno(_("cannot open %s"), filename);
+               return error_errno(_("cannot open '%s'"), filename);
        fclose(fp);
        return 0;
 }
@@ -1400,10 +1423,10 @@ static void set_option(struct transport *transport, const char *name, const char
 {
        int r = transport_set_option(transport, name, value);
        if (r < 0)
-               die(_("Option \"%s\" value \"%s\" is not valid for %s"),
+               die(_("option \"%s\" value \"%s\" is not valid for %s"),
                    name, value, transport->url);
        if (r > 0)
-               warning(_("Option \"%s\" is ignored for %s\n"),
+               warning(_("option \"%s\" is ignored for %s\n"),
                        name, transport->url);
 }
 
@@ -1428,14 +1451,16 @@ static void add_negotiation_tips(struct git_transport_options *smart_options)
                if (!has_glob_specials(s)) {
                        struct object_id oid;
                        if (get_oid(s, &oid))
-                               die("%s is not a valid object", s);
+                               die(_("%s is not a valid object"), s);
+                       if (!has_object(the_repository, &oid, 0))
+                               die(_("the object %s does not exist"), s);
                        oid_array_append(oids, &oid);
                        continue;
                }
                old_nr = oids->nr;
                for_each_glob_ref(add_oid, s, oids);
                if (old_nr == oids->nr)
-                       warning("Ignoring --negotiation-tip=%s because it does not match any refs",
+                       warning("ignoring --negotiation-tip=%s because it does not match any refs",
                                s);
        }
        smart_options->negotiation_tips = oids;
@@ -1473,12 +1498,13 @@ static struct transport *prepare_transport(struct remote *remote, int deepen)
                if (transport->smart_options)
                        add_negotiation_tips(transport->smart_options);
                else
-                       warning("Ignoring --negotiation-tip because the protocol does not support it.");
+                       warning("ignoring --negotiation-tip because the protocol does not support it");
        }
        return transport;
 }
 
-static void backfill_tags(struct transport *transport, struct ref *ref_map)
+static void backfill_tags(struct transport *transport, struct ref *ref_map,
+                         struct worktree **worktrees)
 {
        int cannot_reuse;
 
@@ -1499,8 +1525,7 @@ static void backfill_tags(struct transport *transport, struct ref *ref_map)
        transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
        transport_set_option(transport, TRANS_OPT_DEPTH, "0");
        transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL);
-       if (!fetch_refs(transport, ref_map))
-               consume_refs(transport, ref_map);
+       fetch_and_consume_refs(transport, ref_map, worktrees);
 
        if (gsecondary) {
                transport_disconnect(gsecondary);
@@ -1518,6 +1543,7 @@ static int do_fetch(struct transport *transport,
        struct transport_ls_refs_options transport_ls_refs_options =
                TRANSPORT_LS_REFS_OPTIONS_INIT;
        int must_list_refs = 1;
+       struct worktree **worktrees = get_worktrees();
 
        if (tags == TAGS_DEFAULT) {
                if (transport->remote->fetch_tags == 2)
@@ -1573,7 +1599,7 @@ static int do_fetch(struct transport *transport,
        ref_map = get_ref_map(transport->remote, remote_refs, rs,
                              tags, &autotags);
        if (!update_head_ok)
-               check_not_current_branch(ref_map);
+               check_not_current_branch(ref_map, worktrees);
 
        if (tags == TAGS_DEFAULT && autotags)
                transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
@@ -1591,7 +1617,7 @@ static int do_fetch(struct transport *transport,
                                   transport->url);
                }
        }
-       if (fetch_refs(transport, ref_map) || consume_refs(transport, ref_map)) {
+       if (fetch_and_consume_refs(transport, ref_map, worktrees)) {
                free_refs(ref_map);
                retcode = 1;
                goto cleanup;
@@ -1623,6 +1649,16 @@ static int do_fetch(struct transport *transport,
                        }
                }
                if (source_ref) {
+                       if (!branch) {
+                               const char *shortname = source_ref->name;
+                               skip_prefix(shortname, "refs/heads/", &shortname);
+
+                               warning(_("could not set upstream of HEAD to '%s' from '%s' when "
+                                         "it does not point to any branch."),
+                                       shortname, transport->remote->name);
+                               goto skip;
+                       }
+
                        if (!strcmp(source_ref->name, "HEAD") ||
                            starts_with(source_ref->name, "refs/heads/"))
                                install_branch_config(0,
@@ -1636,11 +1672,11 @@ static int do_fetch(struct transport *transport,
                        else
                                warning(_("unknown branch type"));
                } else {
-                       warning(_("no source branch found.\n"
-                               "you need to specify exactly one branch with the --set-upstream option."));
+                       warning(_("no source branch found;\n"
+                                 "you need to specify exactly one branch with the --set-upstream option"));
                }
        }
- skip:
+skip:
        free_refs(ref_map);
 
        /* if neither --no-tags nor --tags was specified, do automated tag
@@ -1650,11 +1686,12 @@ static int do_fetch(struct transport *transport,
                ref_map = NULL;
                find_non_local_tags(remote_refs, &ref_map, &tail);
                if (ref_map)
-                       backfill_tags(transport, ref_map);
+                       backfill_tags(transport, ref_map, worktrees);
                free_refs(ref_map);
        }
 
- cleanup:
+cleanup:
+       free_worktrees(worktrees);
        return retcode;
 }
 
@@ -1775,7 +1812,7 @@ static int fetch_failed_to_start(struct strbuf *out, void *cb, void *task_cb)
        struct parallel_fetch_state *state = cb;
        const char *remote = task_cb;
 
-       state->result = error(_("Could not fetch %s"), remote);
+       state->result = error(_("could not fetch %s"), remote);
 
        return 0;
 }
@@ -1830,7 +1867,7 @@ static int fetch_multiple(struct string_list *list, int max_children)
                        if (verbosity >= 0)
                                printf(_("Fetching %s\n"), name);
                        if (run_command_v_opt(argv.v, RUN_GIT_CMD)) {
-                               error(_("Could not fetch %s"), name);
+                               error(_("could not fetch %s"), name);
                                result = 1;
                        }
                        strvec_pop(&argv);
@@ -1891,8 +1928,8 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
        int remote_via_config = remote_is_configured(remote, 0);
 
        if (!remote)
-               die(_("No remote repository specified.  Please, specify either a URL or a\n"
-                   "remote name from which new revisions should be fetched."));
+               die(_("no remote repository specified; please specify either a URL or a\n"
+                     "remote name from which new revisions should be fetched"));
 
        gtransport = prepare_transport(remote, 1);
 
@@ -1927,7 +1964,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
                if (!strcmp(argv[i], "tag")) {
                        i++;
                        if (i >= argc)
-                               die(_("You need to specify a tag name."));
+                               die(_("you need to specify a tag name"));
 
                        refspec_appendf(&rs, "refs/tags/%s:refs/tags/%s",
                                        argv[i], argv[i]);
@@ -1947,7 +1984,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
                gtransport->server_options = &server_options;
 
        sigchain_push_common(unlock_pack_on_signal);
-       atexit(unlock_pack);
+       atexit(unlock_pack_atexit);
        sigchain_push(SIGPIPE, SIG_IGN);
        exit_code = do_fetch(gtransport, &rs);
        sigchain_pop(SIGPIPE);
@@ -1978,9 +2015,33 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
        }
 
        git_config(git_fetch_config, NULL);
+       prepare_repo_settings(the_repository);
+       the_repository->settings.command_requires_full_index = 0;
 
        argc = parse_options(argc, argv, prefix,
                             builtin_fetch_options, builtin_fetch_usage, 0);
+
+       if (recurse_submodules_cli != RECURSE_SUBMODULES_DEFAULT)
+               recurse_submodules = recurse_submodules_cli;
+
+       if (negotiate_only) {
+               switch (recurse_submodules_cli) {
+               case RECURSE_SUBMODULES_OFF:
+               case RECURSE_SUBMODULES_DEFAULT:
+                       /*
+                        * --negotiate-only should never recurse into
+                        * submodules. Skip it by setting recurse_submodules to
+                        * RECURSE_SUBMODULES_OFF.
+                        */
+                       recurse_submodules = RECURSE_SUBMODULES_OFF;
+                       break;
+
+               default:
+                       die(_("options '%s' and '%s' cannot be used together"),
+                           "--negotiate-only", "--recurse-submodules");
+               }
+       }
+
        if (recurse_submodules != RECURSE_SUBMODULES_OFF) {
                int *sfjc = submodule_fetch_jobs_config == -1
                            ? &submodule_fetch_jobs_config : NULL;
@@ -1995,14 +2056,14 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
 
        if (deepen_relative) {
                if (deepen_relative < 0)
-                       die(_("Negative depth in --deepen is not supported"));
+                       die(_("negative depth in --deepen is not supported"));
                if (depth)
-                       die(_("--deepen and --depth are mutually exclusive"));
+                       die(_("options '%s' and '%s' cannot be used together"), "--deepen", "--depth");
                depth = xstrfmt("%d", deepen_relative);
        }
        if (unshallow) {
                if (depth)
-                       die(_("--depth and --unshallow cannot be used together"));
+                       die(_("options '%s' and '%s' cannot be used together"), "--depth", "--unshallow");
                else if (!is_repository_shallow(the_repository))
                        die(_("--unshallow on a complete repository does not make sense"));
                else
@@ -2032,14 +2093,15 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
                /* All arguments are assumed to be remotes or groups */
                for (i = 0; i < argc; i++)
                        if (!add_remote_or_group(argv[i], &list))
-                               die(_("No such remote or remote group: %s"), argv[i]);
+                               die(_("no such remote or remote group: %s"),
+                                   argv[i]);
        } else {
                /* Single remote or group */
                (void) add_remote_or_group(argv[0], &list);
                if (list.nr > 1) {
                        /* More than one remote */
                        if (argc > 1)
-                               die(_("Fetching a group and specifying refspecs does not make sense"));
+                               die(_("fetching a group and specifying refspecs does not make sense"));
                } else {
                        /* Zero or one remotes */
                        remote = remote_get(argv[0]);
@@ -2060,8 +2122,9 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
                if (gtransport->smart_options) {
                        gtransport->smart_options->acked_commits = &acked_commits;
                } else {
-                       warning(_("Protocol does not support --negotiate-only, exiting."));
-                       return 1;
+                       warning(_("protocol does not support --negotiate-only, exiting"));
+                       result = 1;
+                       goto cleanup;
                }
                if (server_options.nr)
                        gtransport->server_options = &server_options;
@@ -2117,7 +2180,16 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
                strvec_clear(&options);
        }
 
-       string_list_clear(&list, 0);
+       /*
+        * Skip irrelevant tasks because we know objects were not
+        * fetched.
+        *
+        * NEEDSWORK: as a future optimization, we can return early
+        * whenever objects were not fetched e.g. if we already have all
+        * of them.
+        */
+       if (negotiate_only)
+               goto cleanup;
 
        prepare_repo_settings(the_repository);
        if (fetch_write_commit_graph > 0 ||
@@ -2133,10 +2205,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
                                             NULL);
        }
 
-       close_object_store(the_repository->objects);
-
        if (enable_auto_gc)
                run_auto_maintenance(verbosity < 0);
 
+ cleanup:
+       string_list_clear(&list, 0);
        return result;
 }