Merge branch 'ds/fetch-disable-force-notice'
authorJunio C Hamano <gitster@pobox.com>
Tue, 9 Jul 2019 22:25:46 +0000 (15:25 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 9 Jul 2019 22:25:46 +0000 (15:25 -0700)
"git fetch" and "git pull" reports when a fetch results in
non-fast-forward updates to let the user notice unusual situation.
The commands learned "--no-shown-forced-updates" option to disable
this safety feature.

* ds/fetch-disable-force-notice:
  pull: add --[no-]show-forced-updates passthrough
  fetch: warn about forced updates in branch listing
  fetch: add --[no-]show-forced-updates argument

1  2 
Documentation/config/advice.txt
Documentation/fetch-options.txt
advice.c
advice.h
builtin/fetch.c

@@@ -4,6 -4,10 +4,10 @@@ advice.*:
        can tell Git that you do not need help by setting these to 'false':
  +
  --
+       fetchShowForcedUpdates::
+               Advice shown when linkgit:git-fetch[1] takes a long time
+               to calculate forced updates after ref updates, or to warn
+               that the check is disabled.
        pushUpdateRejected::
                Set this variable to 'false' if you want to disable
                'pushNonFFCurrent',
                we can still suggest that the user push to either
                refs/heads/* or refs/tags/* based on the type of the
                source object.
 +      statusAheadBehind::
 +              Shown when linkgit:git-status[1] computes the ahead/behind
 +              counts for a local ref compared to its remote tracking ref,
 +              and that calculation takes longer than expected. Will not
 +              appear if `status.aheadBehind` is false or the option
 +              `--no-ahead-behind` is given.
        statusHints::
                Show directions on how to proceed from the current
                state in the output of linkgit:git-status[1], in
                the template shown when writing commit messages in
                linkgit:git-commit[1], and in the help message shown
 -              by linkgit:git-checkout[1] when switching branch.
 +              by linkgit:git-switch[1] or
 +              linkgit:git-checkout[1] when switching branch.
        statusUoption::
                Advise to consider using the `-u` option to linkgit:git-status[1]
                when the command takes more than 2 seconds to enumerate untracked
                your information is guessed from the system username and
                domain name.
        detachedHead::
 -              Advice shown when you used linkgit:git-checkout[1] to
 -              move to the detach HEAD state, to instruct how to create
 -              a local branch after the fact.
 +              Advice shown when you used
 +              linkgit:git-switch[1] or linkgit:git-checkout[1]
 +              to move to the detach HEAD state, to instruct how to
 +              create a local branch after the fact.
        checkoutAmbiguousRemoteBranchName::
                Advice shown when the argument to
 -              linkgit:git-checkout[1] ambiguously resolves to a
 +              linkgit:git-checkout[1] and linkgit:git-switch[1]
 +              ambiguously resolves to a
                remote tracking branch on more than one remote in
                situations where an unambiguous argument would have
                otherwise caused a remote-tracking branch to be
@@@ -88,10 -88,6 +88,10 @@@ ifndef::git-pull[
        Allow several <repository> and <group> arguments to be
        specified. No <refspec>s may be specified.
  
 +--[no-]auto-gc::
 +      Run `git gc --auto` at the end to perform garbage collection
 +      if needed. This is enabled by default.
 +
  -p::
  --prune::
        Before fetching, remove any remote-tracking references that no
@@@ -225,6 -221,19 +225,19 @@@ endif::git-pull[
        When multiple `--server-option=<option>` are given, they are all
        sent to the other side in the order listed on the command line.
  
+ --show-forced-updates::
+       By default, git checks if a branch is force-updated during
+       fetch. This can be disabled through fetch.showForcedUpdates, but
+       the --show-forced-updates option guarantees this check occurs.
+       See linkgit:git-config[1].
+ --no-show-forced-updates::
+       By default, git checks if a branch is force-updated during
+       fetch. Pass --no-show-forced-updates or set fetch.showForcedUpdates
+       to false to skip this check for performance reasons. If used during
+       'git-pull' the --ff-only option will still check for forced updates
+       before attempting a fast-forward update. See linkgit:git-config[1].
  -4::
  --ipv4::
        Use IPv4 addresses only, ignoring IPv6 addresses.
diff --combined advice.c
+++ b/advice.c
@@@ -3,6 -3,7 +3,7 @@@
  #include "color.h"
  #include "help.h"
  
+ int advice_fetch_show_forced_updates = 1;
  int advice_push_update_rejected = 1;
  int advice_push_non_ff_current = 1;
  int advice_push_non_ff_matching = 1;
@@@ -12,7 -13,6 +13,7 @@@ int advice_push_needs_force = 1
  int advice_push_unqualified_ref_name = 1;
  int advice_status_hints = 1;
  int advice_status_u_option = 1;
 +int advice_status_ahead_behind_warning = 1;
  int advice_commit_before_merge = 1;
  int advice_reset_quiet_warning = 1;
  int advice_resolve_conflict = 1;
@@@ -60,6 -60,7 +61,7 @@@ static struct 
        const char *name;
        int *preference;
  } advice_config[] = {
+       { "fetchShowForcedUpdates", &advice_fetch_show_forced_updates },
        { "pushUpdateRejected", &advice_push_update_rejected },
        { "pushNonFFCurrent", &advice_push_non_ff_current },
        { "pushNonFFMatching", &advice_push_non_ff_matching },
@@@ -69,7 -70,6 +71,7 @@@
        { "pushUnqualifiedRefName", &advice_push_unqualified_ref_name },
        { "statusHints", &advice_status_hints },
        { "statusUoption", &advice_status_u_option },
 +      { "statusAheadBehindWarning", &advice_status_ahead_behind_warning },
        { "commitBeforeMerge", &advice_commit_before_merge },
        { "resetQuiet", &advice_reset_quiet_warning },
        { "resolveConflict", &advice_resolve_conflict },
@@@ -195,22 -195,13 +197,22 @@@ void NORETURN die_conclude_merge(void
  void detach_advice(const char *new_name)
  {
        const char *fmt =
 -      _("Note: checking out '%s'.\n\n"
 +      _("Note: switching to '%s'.\n"
 +      "\n"
        "You are in 'detached HEAD' state. You can look around, make experimental\n"
        "changes and commit them, and you can discard any commits you make in this\n"
 -      "state without impacting any branches by performing another checkout.\n\n"
 +      "state without impacting any branches by switching back to a branch.\n"
 +      "\n"
        "If you want to create a new branch to retain commits you create, you may\n"
 -      "do so (now or later) by using -b with the checkout command again. Example:\n\n"
 -      "  git checkout -b <new-branch-name>\n\n");
 +      "do so (now or later) by using -c with the switch command. Example:\n"
 +      "\n"
 +      "  git switch -c <new-branch-name>\n"
 +      "\n"
 +      "Or undo this operation with:\n"
 +      "\n"
 +      "  git switch -\n"
 +      "\n"
 +      "Turn off this advice by setting config variable advice.detachedHead to false\n\n");
  
        fprintf(stderr, fmt, new_name);
  }
diff --combined advice.h
+++ b/advice.h
@@@ -3,6 -3,7 +3,7 @@@
  
  #include "git-compat-util.h"
  
+ extern int advice_fetch_show_forced_updates;
  extern int advice_push_update_rejected;
  extern int advice_push_non_ff_current;
  extern int advice_push_non_ff_matching;
@@@ -12,7 -13,6 +13,7 @@@ extern int advice_push_needs_force
  extern int advice_push_unqualified_ref_name;
  extern int advice_status_hints;
  extern int advice_status_u_option;
 +extern int advice_status_ahead_behind_warning;
  extern int advice_commit_before_merge;
  extern int advice_reset_quiet_warning;
  extern int advice_resolve_conflict;
diff --combined builtin/fetch.c
@@@ -24,6 -24,8 +24,8 @@@
  #include "list-objects-filter-options.h"
  #include "commit-reach.h"
  
+ #define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000)
  static const char * const builtin_fetch_usage[] = {
        N_("git fetch [<options>] [<repository> [<refspec>...]]"),
        N_("git fetch [<options>] <group>"),
@@@ -39,6 -41,8 +41,8 @@@ enum 
  };
  
  static int fetch_prune_config = -1; /* unspecified */
+ static int fetch_show_forced_updates = 1;
+ static uint64_t forced_updates_ms = 0;
  static int prune = -1; /* unspecified */
  #define PRUNE_BY_DEFAULT 0 /* do we prune by default? */
  
@@@ -48,7 -52,6 +52,7 @@@ static int prune_tags = -1; /* unspecif
  
  static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity, deepen_relative;
  static int progress = -1;
 +static int enable_auto_gc = 1;
  static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen;
  static int max_children = 1;
  static enum transport_family family;
@@@ -80,6 -83,11 +84,11 @@@ static int git_fetch_config(const char 
                return 0;
        }
  
+       if (!strcmp(k, "fetch.showforcedupdates")) {
+               fetch_show_forced_updates = git_config_bool(k, v);
+               return 0;
+       }
        if (!strcmp(k, "submodule.recurse")) {
                int r = git_config_bool(k, v) ?
                        RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF;
@@@ -170,8 -178,8 +179,10 @@@ static struct option builtin_fetch_opti
        OPT_STRING_LIST(0, "negotiation-tip", &negotiation_tip, N_("revision"),
                        N_("report that we have only objects reachable from this object")),
        OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
 +      OPT_BOOL(0, "auto-gc", &enable_auto_gc,
 +               N_("run 'gc --auto' after fetching")),
+       OPT_BOOL(0, "show-forced-updates", &fetch_show_forced_updates,
+                N_("check for forced-updates on all updated branches")),
        OPT_END()
  };
  
@@@ -242,7 -250,6 +253,7 @@@ static int will_fetch(struct ref **head
  struct refname_hash_entry {
        struct hashmap_entry ent; /* must be the first member */
        struct object_id oid;
 +      int ignore;
        char refname[FLEX_ARRAY];
  };
  
@@@ -291,11 -298,6 +302,11 @@@ static int refname_hash_exists(struct h
        return !!hashmap_get_from_hash(map, strhash(refname), refname);
  }
  
 +static void clear_item(struct refname_hash_entry *item)
 +{
 +      item->ignore = 1;
 +}
 +
  static void find_non_local_tags(const struct ref *refs,
                                struct ref **head,
                                struct ref ***tail)
                            !will_fetch(head, ref->old_oid.hash) &&
                            !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) &&
                            !will_fetch(head, item->oid.hash))
 -                              oidclr(&item->oid);
 +                              clear_item(item);
                        item = NULL;
                        continue;
                }
                if (item &&
                    !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) &&
                    !will_fetch(head, item->oid.hash))
 -                      oidclr(&item->oid);
 +                      clear_item(item);
  
                item = NULL;
  
        if (item &&
            !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) &&
            !will_fetch(head, item->oid.hash))
 -              oidclr(&item->oid);
 +              clear_item(item);
  
        /*
         * For all the tags in the remote_refs_list,
         */
        for_each_string_list_item(remote_ref_item, &remote_refs_list) {
                const char *refname = remote_ref_item->string;
 +              struct ref *rm;
  
                item = hashmap_get_from_hash(&remote_refs, strhash(refname), refname);
                if (!item)
                        BUG("unseen remote ref?");
  
                /* Unless we have already decided to ignore this item... */
 -              if (!is_null_oid(&item->oid)) {
 -                      struct ref *rm = alloc_ref(item->refname);
 -                      rm->peer_ref = alloc_ref(item->refname);
 -                      oidcpy(&rm->old_oid, &item->oid);
 -                      **tail = rm;
 -                      *tail = &rm->next;
 -              }
 +              if (item->ignore)
 +                      continue;
 +
 +              rm = alloc_ref(item->refname);
 +              rm->peer_ref = alloc_ref(item->refname);
 +              oidcpy(&rm->old_oid, &item->oid);
 +              **tail = rm;
 +              *tail = &rm->next;
        }
        hashmap_free(&remote_refs, 1);
        string_list_clear(&remote_refs_list, 0);
@@@ -710,6 -710,7 +721,7 @@@ static int update_local_ref(struct ref 
        enum object_type type;
        struct branch *current_branch = branch_get(NULL);
        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)
                return r;
        }
  
-       if (in_merge_bases(current, updated)) {
+       if (fetch_show_forced_updates) {
+               uint64_t t_before = getnanotime();
+               fast_forward = in_merge_bases(current, updated);
+               forced_updates_ms += (getnanotime() - t_before) / 1000000;
+       } else {
+               fast_forward = 1;
+       }
+       if (fast_forward) {
                struct strbuf quickref = STRBUF_INIT;
                int r;
                strbuf_add_unique_abbrev(&quickref, &current->object.oid, DEFAULT_ABBREV);
                strbuf_addstr(&quickref, "..");
                strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV);
@@@ -982,6 -992,17 +1003,17 @@@ static int store_updated_refs(const cha
                      " 'git remote prune %s' to remove any old, conflicting "
                      "branches"), remote_name);
  
+       if (advice_fetch_show_forced_updates) {
+               if (!fetch_show_forced_updates) {
+                       warning(_("Fetch normally indicates which branches had a forced update, but that check has been disabled."));
+                       warning(_("To re-enable, use '--show-forced-updates' flag or run 'git config fetch.showForcedUpdates true'."));
+               } else if (forced_updates_ms > FORCED_UPDATES_DELAY_WARNING_IN_MS) {
+                       warning(_("It took %.2f seconds to check forced updates. You can use '--no-show-forced-updates'\n"),
+                               forced_updates_ms / 1000.0);
+                       warning(_("or run 'git config fetch.showForcedUpdates false' to avoid this check.\n"));
+               }
+       }
   abort:
        strbuf_release(&note);
        free(url);
@@@ -1435,7 -1456,7 +1467,7 @@@ static int fetch_multiple(struct string
                        return errcode;
        }
  
 -      argv_array_pushl(&argv, "fetch", "--append", NULL);
 +      argv_array_pushl(&argv, "fetch", "--append", "--no-auto-gc", NULL);
        add_options_to_argv(&argv);
  
        for (i = 0; i < list->nr; i++) {
@@@ -1683,15 -1704,13 +1715,15 @@@ int cmd_fetch(int argc, const char **ar
  
        string_list_clear(&list, 0);
  
 -      close_all_packs(the_repository->objects);
 +      close_object_store(the_repository->objects);
  
 -      argv_array_pushl(&argv_gc_auto, "gc", "--auto", NULL);
 -      if (verbosity < 0)
 -              argv_array_push(&argv_gc_auto, "--quiet");
 -      run_command_v_opt(argv_gc_auto.argv, RUN_GIT_CMD);
 -      argv_array_clear(&argv_gc_auto);
 +      if (enable_auto_gc) {
 +              argv_array_pushl(&argv_gc_auto, "gc", "--auto", NULL);
 +              if (verbosity < 0)
 +                      argv_array_push(&argv_gc_auto, "--quiet");
 +              run_command_v_opt(argv_gc_auto.argv, RUN_GIT_CMD);
 +              argv_array_clear(&argv_gc_auto);
 +      }
  
        return result;
  }