Merge branch 'ab/checkout-default-remote'
authorJunio C Hamano <gitster@pobox.com>
Thu, 2 Aug 2018 22:30:41 +0000 (15:30 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 2 Aug 2018 22:30:41 +0000 (15:30 -0700)
"git checkout" and "git worktree add" learned to honor
checkout.defaultRemote when auto-vivifying a local branch out of a
remote tracking branch in a repository with multiple remotes that
have tracking branches that share the same names.

* ab/checkout-default-remote:
checkout & worktree: introduce checkout.defaultRemote
checkout: add advice for ambiguous "checkout <branch>"
builtin/checkout.c: use "ret" variable for return
checkout: pass the "num_matches" up to callers
checkout.c: change "unique" member to "num_matches"
checkout.c: introduce an *_INIT macro
checkout.h: wrap the arguments to unique_tracking_name()
checkout tests: index should be clean after dwim checkout

1  2 
Documentation/config.txt
advice.c
advice.h
builtin/checkout.c
diff --combined Documentation/config.txt
index 3fdc6f0327fe8051bbf42c154fe0e56b06bfe88c,aef2769211acf4c7d5776d7ed44327efa8e471c5..44df580a81c3076a89483c34f07b40192894c7fa
@@@ -344,6 -344,16 +344,16 @@@ advice.*:
                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.
+       checkoutAmbiguousRemoteBranchName::
+               Advice shown when the argument to
+               linkgit:git-checkout[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
+               checked out. See the `checkout.defaultRemote`
+               configuration variable for how to set a given remote
+               to used by default in some situations where this
+               advice would be printed.
        amWorkDir::
                Advice that shows the location of the patch file when
                linkgit:git-am[1] fails to apply it.
                Advice on what to do when you've accidentally added one
                git repo inside of another.
        ignoredHook::
 -              Advice shown if an hook is ignored because the hook is not
 +              Advice shown if a hook is ignored because the hook is not
                set as executable.
        waitingForEditor::
                Print a message to the terminal whenever Git is waiting for
@@@ -390,19 -400,16 +400,19 @@@ core.hideDotFiles:
        default mode is 'dotGitOnly'.
  
  core.ignoreCase::
 -      If true, this option enables various workarounds to enable
 +      Internal variable which enables various workarounds to enable
        Git to work better on filesystems that are not case sensitive,
 -      like FAT. For example, if a directory listing finds
 -      "makefile" when Git expects "Makefile", Git will assume
 +      like APFS, HFS+, FAT, NTFS, etc. For example, if a directory listing
 +      finds "makefile" when Git expects "Makefile", Git will assume
        it is really the same file, and continue to remember it as
        "Makefile".
  +
  The default is false, except linkgit:git-clone[1] or linkgit:git-init[1]
  will probe and set core.ignoreCase true if appropriate when the repository
  is created.
 ++
 +Git relies on the proper configuration of this variable for your operating
 +and file system. Modifying this value may result in unexpected behavior.
  
  core.precomposeUnicode::
        This option is only used by Mac OS implementation of Git.
@@@ -907,12 -914,9 +917,12 @@@ core.notesRef:
  This setting defaults to "refs/notes/commits", and it can be overridden by
  the `GIT_NOTES_REF` environment variable.  See linkgit:git-notes[1].
  
 -core.commitGraph::
 -      Enable git commit graph feature. Allows reading from the
 -      commit-graph file.
 +gc.commitGraph::
 +      If true, then gc will rewrite the commit-graph file when
 +      linkgit:git-gc[1] is run. When using linkgit:git-gc[1]
 +      '--auto' the commit-graph will be updated if housekeeping is
 +      required. Default is false. See linkgit:git-commit-graph[1]
 +      for details.
  
  core.sparseCheckout::
        Enable "sparse checkout" feature. See section "Sparse checkout" in
@@@ -1104,6 -1108,22 +1114,22 @@@ browser.<tool>.path:
        browse HTML help (see `-w` option in linkgit:git-help[1]) or a
        working repository in gitweb (see linkgit:git-instaweb[1]).
  
+ checkout.defaultRemote::
+       When you run 'git checkout <something>' and only have one
+       remote, it may implicitly fall back on checking out and
+       tracking e.g. 'origin/<something>'. This stops working as soon
+       as you have more than one remote with a '<something>'
+       reference. This setting allows for setting the name of a
+       preferred remote that should always win when it comes to
+       disambiguation. The typical use-case is to set this to
+       `origin`.
+ +
+ Currently this is used by linkgit:git-checkout[1] when 'git checkout
+ <something>' will checkout the '<something>' branch on another remote,
+ and by linkgit:git-worktree[1] when 'git worktree add' refers to a
+ remote branch. This setting might be used for other checkout-like
+ commands or functionality in the future.
  clean.requireForce::
        A boolean to make git-clean do nothing unless given -f,
        -i or -n.   Defaults to true.
@@@ -1152,11 -1172,6 +1178,11 @@@ diff.colorMoved:
        true the default color mode will be used. When set to false,
        moved lines are not colored.
  
 +diff.colorMovedWS::
 +      When moved lines are colored using e.g. the `diff.colorMoved` setting,
 +      this option controls the `<mode>` how spaces are treated
 +      for details of valid modes see '--color-moved-ws' in linkgit:git-diff[1].
 +
  color.diff.<slot>::
        Use customized color for diff colorization.  `<slot>` specifies
        which part of the patch to use the specified color, and is one
  color.decorate.<slot>::
        Use customized color for 'git log --decorate' output.  `<slot>` is one
        of `branch`, `remoteBranch`, `tag`, `stash` or `HEAD` for local
 -      branches, remote-tracking branches, tags, stash and HEAD, respectively.
 +      branches, remote-tracking branches, tags, stash and HEAD, respectively
 +      and `grafted` for grafted commits.
  
  color.grep::
        When set to `always`, always highlight matches.  When `false` (or
@@@ -1193,10 -1207,8 +1219,10 @@@ color.grep.<slot>:
        filename prefix (when not using `-h`)
  `function`;;
        function name lines (when using `-p`)
 -`linenumber`;;
 +`lineNumber`;;
        line number prefix (when using `-n`)
 +`column`;;
 +      column number prefix (when using `--column`)
  `match`;;
        matching text (same as setting `matchContext` and `matchSelected`)
  `matchContext`;;
@@@ -1811,9 -1823,6 +1837,9 @@@ gitweb.snapshot:
  grep.lineNumber::
        If set to true, enable `-n` option by default.
  
 +grep.column::
 +      If set to true, enable the `--column` option by default.
 +
  grep.patternType::
        Set the default matching behavior. Using a value of 'basic', 'extended',
        'fixed', or 'perl' will enable the `--basic-regexp`, `--extended-regexp`,
@@@ -3344,13 -3353,12 +3370,13 @@@ submodule.<name>.ignore:
  submodule.<name>.active::
        Boolean value indicating if the submodule is of interest to git
        commands.  This config option takes precedence over the
 -      submodule.active config option.
 +      submodule.active config option. See linkgit:gitsubmodules[7] for
 +      details.
  
  submodule.active::
        A repeated field which contains a pathspec used to match against a
        submodule's path to determine if the submodule is of interest to git
 -      commands.
 +      commands. See linkgit:gitsubmodules[7] for details.
  
  submodule.recurse::
        Specifies if commands recurse into submodules by default. This
@@@ -3497,13 -3505,6 +3523,13 @@@ Note that this configuration variable i
  repository-level config (this is a safety measure against fetching from
  untrusted repositories).
  
 +uploadpack.allowRefInWant::
 +      If this option is set, `upload-pack` will support the `ref-in-want`
 +      feature of the protocol version 2 `fetch` command.  This feature
 +      is intended for the benefit of load-balanced servers which may
 +      not have the same view of what OIDs their refs point to due to
 +      replication delay.
 +
  url.<base>.insteadOf::
        Any URL that starts with this value will be rewritten to
        start, instead, with <base>. In cases where some site serves a
diff --combined advice.c
index 52aa85bdfd9e9054c24445f259125bc4b81be038,75e7dede90a6fe0ae201217f9cc6d2b619480296..3561cd64e9dab0a5b0c52d117253f37a5926f9c7
+++ b/advice.c
@@@ -1,7 -1,6 +1,7 @@@
  #include "cache.h"
  #include "config.h"
  #include "color.h"
 +#include "help.h"
  
  int advice_push_update_rejected = 1;
  int advice_push_non_ff_current = 1;
@@@ -17,12 -16,12 +17,13 @@@ int advice_implicit_identity = 1
  int advice_detached_head = 1;
  int advice_set_upstream_failure = 1;
  int advice_object_name_warning = 1;
 +int advice_amworkdir = 1;
  int advice_rm_hints = 1;
  int advice_add_embedded_repo = 1;
  int advice_ignored_hook = 1;
  int advice_waiting_for_editor = 1;
  int advice_graft_file_deprecated = 1;
+ int advice_checkout_ambiguous_remote_branch_name = 1;
  
  static int advice_use_color = -1;
  static char advice_colors[][COLOR_MAXLEN] = {
@@@ -55,29 -54,29 +56,30 @@@ static struct 
        const char *name;
        int *preference;
  } advice_config[] = {
 -      { "pushupdaterejected", &advice_push_update_rejected },
 -      { "pushnonffcurrent", &advice_push_non_ff_current },
 -      { "pushnonffmatching", &advice_push_non_ff_matching },
 -      { "pushalreadyexists", &advice_push_already_exists },
 -      { "pushfetchfirst", &advice_push_fetch_first },
 -      { "pushneedsforce", &advice_push_needs_force },
 -      { "statushints", &advice_status_hints },
 -      { "statusuoption", &advice_status_u_option },
 -      { "commitbeforemerge", &advice_commit_before_merge },
 -      { "resolveconflict", &advice_resolve_conflict },
 -      { "implicitidentity", &advice_implicit_identity },
 -      { "detachedhead", &advice_detached_head },
 -      { "setupstreamfailure", &advice_set_upstream_failure },
 -      { "objectnamewarning", &advice_object_name_warning },
 -      { "rmhints", &advice_rm_hints },
 -      { "addembeddedrepo", &advice_add_embedded_repo },
 -      { "ignoredhook", &advice_ignored_hook },
 -      { "waitingforeditor", &advice_waiting_for_editor },
 -      { "graftfiledeprecated", &advice_graft_file_deprecated },
 -      { "checkoutambiguousremotebranchname", &advice_checkout_ambiguous_remote_branch_name },
 +      { "pushUpdateRejected", &advice_push_update_rejected },
 +      { "pushNonFFCurrent", &advice_push_non_ff_current },
 +      { "pushNonFFMatching", &advice_push_non_ff_matching },
 +      { "pushAlreadyExists", &advice_push_already_exists },
 +      { "pushFetchFirst", &advice_push_fetch_first },
 +      { "pushNeedsForce", &advice_push_needs_force },
 +      { "statusHints", &advice_status_hints },
 +      { "statusUoption", &advice_status_u_option },
 +      { "commitBeforeMerge", &advice_commit_before_merge },
 +      { "resolveConflict", &advice_resolve_conflict },
 +      { "implicitIdentity", &advice_implicit_identity },
 +      { "detachedHead", &advice_detached_head },
 +      { "setupStreamFailure", &advice_set_upstream_failure },
 +      { "objectNameWarning", &advice_object_name_warning },
 +      { "amWorkDir", &advice_amworkdir },
 +      { "rmHints", &advice_rm_hints },
 +      { "addEmbeddedRepo", &advice_add_embedded_repo },
 +      { "ignoredHook", &advice_ignored_hook },
 +      { "waitingForEditor", &advice_waiting_for_editor },
 +      { "graftFileDeprecated", &advice_graft_file_deprecated },
++      { "checkoutAmbiguousRemoteBranchName", &advice_checkout_ambiguous_remote_branch_name },
  
        /* make this an alias for backward compatibility */
 -      { "pushnonfastforward", &advice_push_update_rejected }
 +      { "pushNonFastForward", &advice_push_update_rejected }
  };
  
  void advise(const char *advice, ...)
@@@ -125,7 -124,7 +127,7 @@@ int git_default_advice_config(const cha
                return 0;
  
        for (i = 0; i < ARRAY_SIZE(advice_config); i++) {
 -              if (strcmp(k, advice_config[i].name))
 +              if (strcasecmp(k, advice_config[i].name))
                        continue;
                *advice_config[i].preference = git_config_bool(var, value);
                return 0;
        return 0;
  }
  
 +void list_config_advices(struct string_list *list, const char *prefix)
 +{
 +      int i;
 +
 +      for (i = 0; i < ARRAY_SIZE(advice_config); i++)
 +              list_config_item(list, prefix, advice_config[i].name);
 +}
 +
  int error_resolve_conflict(const char *me)
  {
        if (!strcmp(me, "cherry-pick"))
diff --combined advice.h
index 7e9377864f8fca1051ce3fd27f3def62b8d234eb,4d11d51d433da3a95134e6b374ae08f91a9dae7b..ab24df0fd0d0c739f6f58bb2650bb4162ef4c7f2
+++ b/advice.h
@@@ -17,12 -17,12 +17,13 @@@ extern int advice_implicit_identity
  extern int advice_detached_head;
  extern int advice_set_upstream_failure;
  extern int advice_object_name_warning;
 +extern int advice_amworkdir;
  extern int advice_rm_hints;
  extern int advice_add_embedded_repo;
  extern int advice_ignored_hook;
  extern int advice_waiting_for_editor;
  extern int advice_graft_file_deprecated;
+ extern int advice_checkout_ambiguous_remote_branch_name;
  
  int git_default_advice_config(const char *var, const char *value);
  __attribute__((format (printf, 1, 2)))
diff --combined builtin/checkout.c
index 28627650cd66bb38a432397b2e6398b412cbf073,5b357e922a59cf5058c20d07d2ced73a4ad555e4..2de10d28c76e7616748ef37def3b6e4f43a4e7a7
@@@ -4,7 -4,6 +4,7 @@@
  #include "lockfile.h"
  #include "parse-options.h"
  #include "refs.h"
 +#include "object-store.h"
  #include "commit.h"
  #include "tree.h"
  #include "tree-walk.h"
@@@ -23,6 -22,7 +23,7 @@@
  #include "resolve-undo.h"
  #include "submodule-config.h"
  #include "submodule.h"
+ #include "advice.h"
  
  static const char * const checkout_usage[] = {
        N_("git checkout [<options>] <branch>"),
@@@ -879,7 -879,8 +880,8 @@@ static int parse_branchname_arg(int arg
                                int dwim_new_local_branch_ok,
                                struct branch_info *new_branch_info,
                                struct checkout_opts *opts,
-                               struct object_id *rev)
+                               struct object_id *rev,
+                               int *dwim_remotes_matched)
  {
        struct tree **source_tree = &opts->source_tree;
        const char **new_branch = &opts->new_branch;
         *   (b) If <something> is _not_ a commit, either "--" is present
         *       or <something> is not a path, no -t or -b was given, and
         *       and there is a tracking branch whose name is <something>
-        *       in one and only one remote, then this is a short-hand to
-        *       fork local <something> from that remote-tracking branch.
+        *       in one and only one remote (or if the branch exists on the
+        *       remote named in checkout.defaultRemote), then this is a
+        *       short-hand to fork local <something> from that
+        *       remote-tracking branch.
         *
         *   (c) Otherwise, if "--" is present, treat it like case (1).
         *
                        recover_with_dwim = 0;
  
                if (recover_with_dwim) {
-                       const char *remote = unique_tracking_name(arg, rev);
+                       const char *remote = unique_tracking_name(arg, rev,
+                                                                 dwim_remotes_matched);
                        if (remote) {
                                *new_branch = arg;
                                arg = remote;
@@@ -1110,6 -1114,7 +1115,7 @@@ int cmd_checkout(int argc, const char *
        struct branch_info new_branch_info;
        char *conflict_style = NULL;
        int dwim_new_local_branch = 1;
+       int dwim_remotes_matched = 0;
        struct option options[] = {
                OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
                OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
                OPT_SET_INT('t', "track",  &opts.track, N_("set upstream info for new branch"),
                        BRANCH_TRACK_EXPLICIT),
                OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
 -              OPT_SET_INT('2', "ours", &opts.writeout_stage, N_("checkout our version for unmerged files"),
 -                          2),
 -              OPT_SET_INT('3', "theirs", &opts.writeout_stage, N_("checkout their version for unmerged files"),
 -                          3),
 +              OPT_SET_INT_F('2', "ours", &opts.writeout_stage,
 +                            N_("checkout our version for unmerged files"),
 +                            2, PARSE_OPT_NONEG),
 +              OPT_SET_INT_F('3', "theirs", &opts.writeout_stage,
 +                            N_("checkout their version for unmerged files"),
 +                            3, PARSE_OPT_NONEG),
                OPT__FORCE(&opts.force, N_("force checkout (throw away local modifications)"),
                           PARSE_OPT_NOCOMPLETE),
                OPT_BOOL('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")),
                        opts.track == BRANCH_TRACK_UNSPECIFIED &&
                        !opts.new_branch;
                int n = parse_branchname_arg(argc, argv, dwim_ok,
-                                            &new_branch_info, &opts, &rev);
+                                            &new_branch_info, &opts, &rev,
+                                            &dwim_remotes_matched);
                argv += n;
                argc -= n;
        }
        }
  
        UNLEAK(opts);
-       if (opts.patch_mode || opts.pathspec.nr)
-               return checkout_paths(&opts, new_branch_info.name);
-       else
+       if (opts.patch_mode || opts.pathspec.nr) {
+               int ret = checkout_paths(&opts, new_branch_info.name);
+               if (ret && dwim_remotes_matched > 1 &&
+                   advice_checkout_ambiguous_remote_branch_name)
+                       advise(_("'%s' matched more than one remote tracking branch.\n"
+                                "We found %d remotes with a reference that matched. So we fell back\n"
+                                "on trying to resolve the argument as a path, but failed there too!\n"
+                                "\n"
+                                "If you meant to check out a remote tracking branch on, e.g. 'origin',\n"
+                                "you can do so by fully qualifying the name with the --track option:\n"
+                                "\n"
+                                "    git checkout --track origin/<name>\n"
+                                "\n"
+                                "If you'd like to always have checkouts of an ambiguous <name> prefer\n"
+                                "one remote, e.g. the 'origin' remote, consider setting\n"
+                                "checkout.defaultRemote=origin in your config."),
+                              argv[0],
+                              dwim_remotes_matched);
+               return ret;
+       } else {
                return checkout_branch(&opts, &new_branch_info);
+       }
  }