Merge branch 'bp/checkout-new-branch-optim'
authorJunio C Hamano <gitster@pobox.com>
Mon, 17 Sep 2018 20:53:48 +0000 (13:53 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 17 Sep 2018 20:53:48 +0000 (13:53 -0700)
"git checkout -b newbranch [HEAD]" should not have to do as much as
checking out a commit different from HEAD. An attempt is made to
optimize this special case.

* bp/checkout-new-branch-optim:
checkout: optimize "git checkout -b <new_branch>"

1  2 
Documentation/config.txt
builtin/checkout.c
diff --combined Documentation/config.txt
index eb66a119753726b0260acd456f5728351dc05ba3,1a2deeaf20811f23fb346e52cf99b4bdbedf9bf0..69a27eb688167e0c27f6f62bf14c5d5218cc20a8
@@@ -344,16 -344,6 +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.
@@@ -462,20 -452,10 +462,20 @@@ core.untrackedCache:
        See linkgit:git-update-index[1]. `keep` by default.
  
  core.checkStat::
 -      Determines which stat fields to match between the index
 -      and work tree. The user can set this to 'default' or
 -      'minimal'. Default (or explicitly 'default'), is to check
 -      all fields, including the sub-second part of mtime and ctime.
 +      When missing or is set to `default`, many fields in the stat
 +      structure are checked to detect if a file has been modified
 +      since Git looked at it.  When this configuration variable is
 +      set to `minimal`, sub-second part of mtime and ctime, the
 +      uid and gid of the owner of the file, the inode number (and
 +      the device number, if Git was compiled to use it), are
 +      excluded from the check among these fields, leaving only the
 +      whole-second part of mtime (and ctime, if `core.trustCtime`
 +      is set) and the filesize to be checked.
 ++
 +There are implementations of Git that do not leave usable values in
 +some fields (e.g. JGit); by excluding these fields from the
 +comparison, the `minimal` mode may help interoperability when the
 +same repository is used by these other systems at the same time.
  
  core.quotePath::
        Commands that output paths (e.g. 'ls-files', 'diff'), will
@@@ -928,14 -908,8 +928,14 @@@ This setting defaults to "refs/notes/co
  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.
 +      If true, then git will read the commit-graph file (if it exists)
 +      to parse the graph structure of commits. Defaults to false. See
 +      linkgit:git-commit-graph[1] for more information.
 +
 +core.useReplaceRefs::
 +      If set to `false`, behave as if the `--no-replace-objects`
 +      option was given on the command line. See linkgit:git[1] and
 +      linkgit:git-replace[1] for more information.
  
  core.sparseCheckout::
        Enable "sparse checkout" feature. See section "Sparse checkout" in
@@@ -1003,28 -977,23 +1003,28 @@@ apply.whitespace:
        Tells 'git apply' how to handle whitespaces, in the same way
        as the `--whitespace` option. See linkgit:git-apply[1].
  
 -blame.showRoot::
 -      Do not treat root commits as boundaries in linkgit:git-blame[1].
 -      This option defaults to false.
 -
  blame.blankBoundary::
        Show blank commit object name for boundary commits in
        linkgit:git-blame[1]. This option defaults to false.
  
 -blame.showEmail::
 -      Show the author email instead of author name in linkgit:git-blame[1].
 -      This option defaults to false.
 +blame.coloring::
 +      This determines the coloring scheme to be applied to blame
 +      output. It can be 'repeatedLines', 'highlightRecent',
 +      or 'none' which is the default.
  
  blame.date::
        Specifies the format used to output dates in linkgit:git-blame[1].
        If unset the iso format is used. For supported values,
        see the discussion of the `--date` option at linkgit:git-log[1].
  
 +blame.showEmail::
 +      Show the author email instead of author name in linkgit:git-blame[1].
 +      This option defaults to false.
 +
 +blame.showRoot::
 +      Do not treat root commits as boundaries in linkgit:git-blame[1].
 +      This option defaults to false.
 +
  branch.autoSetupMerge::
        Tells 'git branch' and 'git checkout' to set up new branches
        so that linkgit:git-pull[1] will appropriately merge from the
@@@ -1052,12 -1021,6 +1052,12 @@@ branch.autoSetupRebase:
        branch to track another branch.
        This option defaults to never.
  
 +branch.sort::
 +      This variable controls the sort ordering of branches when displayed by
 +      linkgit:git-branch[1]. Without the "--sort=<value>" option provided, the
 +      value of this variable will be used as the default.
 +      See linkgit:git-for-each-ref[1] field names for valid values.
 +
  branch.<name>.remote::
        When on branch <name>, it tells 'git fetch' and 'git push'
        which remote to fetch from/push to.  The remote to push to
@@@ -1138,22 -1101,14 +1138,30 @@@ 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.
 +
+ checkout.optimizeNewBranch
+       Optimizes the performance of "git checkout -b <new_branch>" when
+       using sparse-checkout.  When set to true, git will not update the
+       repo based on the current sparse-checkout settings.  This means it
+       will not update the skip-worktree bit in the index nor add/remove
+       files in the working directory to reflect the current sparse checkout
+       settings nor will it show the local changes.
  clean.requireForce::
        A boolean to make git-clean do nothing unless given -f,
        -i or -n.   Defaults to true.
@@@ -1168,28 -1123,6 +1176,28 @@@ color.advice:
  color.advice.hint::
        Use customized color for hints.
  
 +color.blame.highlightRecent::
 +      This can be used to color the metadata of a blame line depending
 +      on age of the line.
 ++
 +This setting should be set to a comma-separated list of color and date settings,
 +starting and ending with a color, the dates should be set from oldest to newest.
 +The metadata will be colored given the colors if the the line was introduced
 +before the given timestamp, overwriting older timestamped colors.
 ++
 +Instead of an absolute timestamp relative timestamps work as well, e.g.
 +2.weeks.ago is valid to address anything older than 2 weeks.
 ++
 +It defaults to 'blue,12 month ago,white,1 month ago,red', which colors
 +everything older than one year blue, recent changes between one month and
 +one year old are kept white, and lines introduced within the last month are
 +colored red.
 +
 +color.blame.repeatedLines::
 +      Use the customized color for the part of git-blame output that
 +      is repeated meta information per line (such as commit id,
 +      author name, date and timezone). Defaults to cyan.
 +
  color.branch::
        A boolean to enable/disable color in the output of
        linkgit:git-branch[1]. May be set to `always`,
@@@ -1217,6 -1150,13 +1225,6 @@@ This does not affect linkgit:git-format
  'git-diff-{asterisk}' plumbing commands.  Can be overridden on the
  command line with the `--color[=<when>]` option.
  
 -diff.colorMoved::
 -      If set to either a valid `<mode>` or a true value, moved lines
 -      in a diff are colored differently, for details of valid modes
 -      see '--color-moved' in linkgit:git-diff[1]. If simply set to
 -      true the default color mode will be used. When set to false,
 -      moved lines are not colored.
 -
  color.diff.<slot>::
        Use customized color for diff colorization.  `<slot>` specifies
        which part of the patch to use the specified color, and is one
        (highlighting whitespace errors), `oldMoved` (deleted lines),
        `newMoved` (added lines), `oldMovedDimmed`, `oldMovedAlternative`,
        `oldMovedAlternativeDimmed`, `newMovedDimmed`, `newMovedAlternative`
 -      and `newMovedAlternativeDimmed` (See the '<mode>'
 -      setting of '--color-moved' in linkgit:git-diff[1] for details).
 +      `newMovedAlternativeDimmed` (See the '<mode>'
 +      setting of '--color-moved' in linkgit:git-diff[1] for details),
 +      `contextDimmed`, `oldDimmed`, `newDimmed`, `contextBold`,
 +      `oldBold`, and `newBold` (see linkgit:git-range-diff[1] for details).
  
  color.decorate.<slot>::
        Use customized color for 'git log --decorate' output.  `<slot>` is one
@@@ -1299,18 -1237,6 +1307,18 @@@ color.push:
  color.push.error::
        Use customized color for push errors.
  
 +color.remote::
 +      If set, keywords at the start of the line are highlighted. The
 +      keywords are "error", "warning", "hint" and "success", and are
 +      matched case-insensitively. May be set to `always`, `false` (or
 +      `never`) or `auto` (or `true`). If unset, then the value of
 +      `color.ui` is used (`auto` by default).
 +
 +color.remote.<slot>::
 +      Use customized color for each remote keyword. `<slot>` may be
 +      `hint`, `warning`, `success` or `error` which match the
 +      corresponding keyword.
 +
  color.showBranch::
        A boolean to enable/disable color in the output of
        linkgit:git-show-branch[1]. May be set to `always`,
@@@ -1339,6 -1265,33 +1347,6 @@@ color.status.<slot>:
        status short-format), or
        `unmerged` (files which have unmerged changes).
  
 -color.blame.repeatedLines::
 -      Use the customized color for the part of git-blame output that
 -      is repeated meta information per line (such as commit id,
 -      author name, date and timezone). Defaults to cyan.
 -
 -color.blame.highlightRecent::
 -      This can be used to color the metadata of a blame line depending
 -      on age of the line.
 -+
 -This setting should be set to a comma-separated list of color and date settings,
 -starting and ending with a color, the dates should be set from oldest to newest.
 -The metadata will be colored given the colors if the the line was introduced
 -before the given timestamp, overwriting older timestamped colors.
 -+
 -Instead of an absolute timestamp relative timestamps work as well, e.g.
 -2.weeks.ago is valid to address anything older than 2 weeks.
 -+
 -It defaults to 'blue,12 month ago,white,1 month ago,red', which colors
 -everything older than one year blue, recent changes between one month and
 -one year old are kept white, and lines introduced within the last month are
 -colored red.
 -
 -blame.coloring::
 -      This determines the coloring scheme to be applied to blame
 -      output. It can be 'repeatedLines', 'highlightRecent',
 -      or 'none' which is the default.
 -
  color.transport::
        A boolean to enable/disable color when pushes are rejected. May be
        set to `always`, `false` (or `never`) or `auto` (or `true`), in which
@@@ -1518,19 -1471,10 +1526,19 @@@ fetch.recurseSubmodules:
  
  fetch.fsckObjects::
        If it is set to true, git-fetch-pack will check all fetched
 -      objects. It will abort in the case of a malformed object or a
 -      broken link. The result of an abort are only dangling objects.
 -      Defaults to false. If not set, the value of `transfer.fsckObjects`
 -      is used instead.
 +      objects. See `transfer.fsckObjects` for what's
 +      checked. Defaults to false. If not set, the value of
 +      `transfer.fsckObjects` is used instead.
 +
 +fetch.fsck.<msg-id>::
 +      Acts like `fsck.<msg-id>`, but is used by
 +      linkgit:git-fetch-pack[1] instead of linkgit:git-fsck[1]. See
 +      the `fsck.<msg-id>` documentation for details.
 +
 +fetch.fsck.skipList::
 +      Acts like `fsck.skipList`, but is used by
 +      linkgit:git-fetch-pack[1] instead of linkgit:git-fsck[1]. See
 +      the `fsck.skipList` documentation for details.
  
  fetch.unpackLimit::
        If the number of objects fetched over the Git native
@@@ -1561,18 -1505,6 +1569,18 @@@ fetch.output:
        `full` and `compact`. Default value is `full`. See section
        OUTPUT in linkgit:git-fetch[1] for detail.
  
 +fetch.negotiationAlgorithm::
 +      Control how information about the commits in the local repository is
 +      sent when negotiating the contents of the packfile to be sent by the
 +      server. Set to "skipping" to use an algorithm that skips commits in an
 +      effort to converge faster, but may result in a larger-than-necessary
 +      packfile; The default is "default" which instructs Git to use the default algorithm
 +      that never skips commits (unless the server has acknowledged it or one
 +      of its descendants).
 +      Unknown values will cause 'git fetch' to error out.
 ++
 +See also the `--negotiation-tip` option for linkgit:git-fetch[1].
 +
  format.attach::
        Enable multipart/mixed attachments as the default for
        'format-patch'.  The value can also be a double quoted string
@@@ -1672,42 -1604,15 +1680,42 @@@ filter.<driver>.smudge:
        linkgit:gitattributes[5] for details.
  
  fsck.<msg-id>::
 -      Allows overriding the message type (error, warn or ignore) of a
 -      specific message ID such as `missingEmail`.
 -+
 -For convenience, fsck prefixes the error/warning with the message ID,
 -e.g.  "missingEmail: invalid author/committer line - missing email" means
 -that setting `fsck.missingEmail = ignore` will hide that issue.
 -+
 -This feature is intended to support working with legacy repositories
 -which cannot be repaired without disruptive changes.
 +      During fsck git may find issues with legacy data which
 +      wouldn't be generated by current versions of git, and which
 +      wouldn't be sent over the wire if `transfer.fsckObjects` was
 +      set. This feature is intended to support working with legacy
 +      repositories containing such data.
 ++
 +Setting `fsck.<msg-id>` will be picked up by linkgit:git-fsck[1], but
 +to accept pushes of such data set `receive.fsck.<msg-id>` instead, or
 +to clone or fetch it set `fetch.fsck.<msg-id>`.
 ++
 +The rest of the documentation discusses `fsck.*` for brevity, but the
 +same applies for the corresponding `receive.fsck.*` and
 +`fetch.<msg-id>.*`. variables.
 ++
 +Unlike variables like `color.ui` and `core.editor` the
 +`receive.fsck.<msg-id>` and `fetch.fsck.<msg-id>` variables will not
 +fall back on the `fsck.<msg-id>` configuration if they aren't set. To
 +uniformly configure the same fsck settings in different circumstances
 +all three of them they must all set to the same values.
 ++
 +When `fsck.<msg-id>` is set, errors can be switched to warnings and
 +vice versa by configuring the `fsck.<msg-id>` setting where the
 +`<msg-id>` is the fsck message ID and the value is one of `error`,
 +`warn` or `ignore`. For convenience, fsck prefixes the error/warning
 +with the message ID, e.g. "missingEmail: invalid author/committer line
 +- missing email" means that setting `fsck.missingEmail = ignore` will
 +hide that issue.
 ++
 +In general, it is better to enumerate existing objects with problems
 +with `fsck.skipList`, instead of listing the kind of breakages these
 +problematic objects share to be ignored, as doing the latter will
 +allow new instances of the same breakages go unnoticed.
 ++
 +Setting an unknown `fsck.<msg-id>` value will cause fsck to die, but
 +doing the same for `receive.fsck.<msg-id>` and `fetch.fsck.<msg-id>`
 +will only cause git to warn.
  
  fsck.skipList::
        The path to a sorted list of object names (i.e. one SHA-1 per
        should be accepted despite early commits containing errors that
        can be safely ignored such as invalid committer email addresses.
        Note: corrupt objects cannot be skipped with this setting.
 ++
 +Like `fsck.<msg-id>` this variable has corresponding
 +`receive.fsck.skipList` and `fetch.fsck.skipList` variants.
 ++
 +Unlike variables like `color.ui` and `core.editor` the
 +`receive.fsck.skipList` and `fetch.fsck.skipList` variables will not
 +fall back on the `fsck.skipList` configuration if they aren't set. To
 +uniformly configure the same fsck settings in different circumstances
 +all three of them they must all set to the same values.
  
  gc.aggressiveDepth::
        The depth parameter used in the delta compression
@@@ -1765,13 -1661,6 +1773,13 @@@ this configuration variable is ignored
  will be repacked. After this the number of packs should go below
  gc.autoPackLimit and gc.bigPackThreshold should be respected again.
  
 +gc.writeCommitGraph::
 +      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.
 +
  gc.logExpiry::
        If the file gc.log exists, then `git gc --auto` won't run
        unless that file is more than 'gc.logExpiry' old.  Default is
@@@ -1955,16 -1844,6 +1963,16 @@@ gpg.program:
        signed, and the program is expected to send the result to its
        standard output.
  
 +gpg.format::
 +      Specifies which key format to use when signing with `--gpg-sign`.
 +      Default is "openpgp" and another possible value is "x509".
 +
 +gpg.<format>.program::
 +      Use this to customize the program used for the signing format you
 +      chose. (see `gpg.program` and `gpg.format`) `gpg.program` can still
 +      be used as a legacy synonym for `gpg.openpgp.program`. The default
 +      value for `gpg.x509.program` is "gpgsm".
 +
  gui.commitMsgWidth::
        Defines how wide the commit message window is in the
        linkgit:git-gui[1]. "75" is the default.
@@@ -3018,21 -2897,32 +3026,21 @@@ receive.certNonceSlop:
  
  receive.fsckObjects::
        If it is set to true, git-receive-pack will check all received
 -      objects. It will abort in the case of a malformed object or a
 -      broken link. The result of an abort are only dangling objects.
 -      Defaults to false. If not set, the value of `transfer.fsckObjects`
 -      is used instead.
 +      objects. See `transfer.fsckObjects` for what's checked.
 +      Defaults to false. If not set, the value of
 +      `transfer.fsckObjects` is used instead.
  
  receive.fsck.<msg-id>::
 -      When `receive.fsckObjects` is set to true, errors can be switched
 -      to warnings and vice versa by configuring the `receive.fsck.<msg-id>`
 -      setting where the `<msg-id>` is the fsck message ID and the value
 -      is one of `error`, `warn` or `ignore`. For convenience, fsck prefixes
 -      the error/warning with the message ID, e.g. "missingEmail: invalid
 -      author/committer line - missing email" means that setting
 -      `receive.fsck.missingEmail = ignore` will hide that issue.
 -+
 -This feature is intended to support working with legacy repositories
 -which would not pass pushing when `receive.fsckObjects = true`, allowing
 -the host to accept repositories with certain known issues but still catch
 -other issues.
 +      Acts like `fsck.<msg-id>`, but is used by
 +      linkgit:git-receive-pack[1] instead of
 +      linkgit:git-fsck[1]. See the `fsck.<msg-id>` documentation for
 +      details.
  
  receive.fsck.skipList::
 -      The path to a sorted list of object names (i.e. one SHA-1 per
 -      line) that are known to be broken in a non-fatal way and should
 -      be ignored. This feature is useful when an established project
 -      should be accepted despite early commits containing errors that
 -      can be safely ignored such as invalid committer email addresses.
 -      Note: corrupt objects cannot be skipped with this setting.
 +      Acts like `fsck.skipList`, but is used by
 +      linkgit:git-receive-pack[1] instead of
 +      linkgit:git-fsck[1]. See the `fsck.skipList` documentation for
 +      details.
  
  receive.keepAlive::
        After receiving the pack from the client, `receive-pack` may
@@@ -3507,40 -3397,6 +3515,40 @@@ transfer.fsckObjects:
        When `fetch.fsckObjects` or `receive.fsckObjects` are
        not set, the value of this variable is used instead.
        Defaults to false.
 ++
 +When set, the fetch or receive will abort in the case of a malformed
 +object or a link to a nonexistent object. In addition, various other
 +issues are checked for, including legacy issues (see `fsck.<msg-id>`),
 +and potential security issues like the existence of a `.GIT` directory
 +or a malicious `.gitmodules` file (see the release notes for v2.2.1
 +and v2.17.1 for details). Other sanity and security checks may be
 +added in future releases.
 ++
 +On the receiving side, failing fsckObjects will make those objects
 +unreachable, see "QUARANTINE ENVIRONMENT" in
 +linkgit:git-receive-pack[1]. On the fetch side, malformed objects will
 +instead be left unreferenced in the repository.
 ++
 +Due to the non-quarantine nature of the `fetch.fsckObjects`
 +implementation it can not be relied upon to leave the object store
 +clean like `receive.fsckObjects` can.
 ++
 +As objects are unpacked they're written to the object store, so there
 +can be cases where malicious objects get introduced even though the
 +"fetch" failed, only to have a subsequent "fetch" succeed because only
 +new incoming objects are checked, not those that have already been
 +written to the object store. That difference in behavior should not be
 +relied upon. In the future, such objects may be quarantined for
 +"fetch" as well.
 ++
 +For now, the paranoid need to find some way to emulate the quarantine
 +environment if they'd like the same protection as "push". E.g. in the
 +case of an internal mirror do the mirroring in two steps, one to fetch
 +the untrusted objects, and then do a second "push" (which will use the
 +quarantine) to another internal repo, and have internal clients
 +consume this pushed-to repository, or embargo internal fetches and
 +only allow them once a full "fsck" has run (and no new fetches have
 +happened in the meantime).
  
  transfer.hideRefs::
        String(s) `receive-pack` and `upload-pack` use to decide which
diff --combined builtin/checkout.c
index 29ef50013dccbd118093af0b4dc08eb907953cc2,21bac3a561dfa253d54f35f052c83e132396b598..67a83fb95bb5a11acef857bdcd4e07535bb3f073
  #include "resolve-undo.h"
  #include "submodule-config.h"
  #include "submodule.h"
 +#include "advice.h"
  
+ static int checkout_optimize_new_branch;
  static const char * const checkout_usage[] = {
        N_("git checkout [<options>] <branch>"),
        N_("git checkout [<options>] [<branch>] -- <file>..."),
@@@ -42,6 -43,10 +44,10 @@@ struct checkout_opts 
        int ignore_skipworktree;
        int ignore_other_worktrees;
        int show_progress;
+       /*
+        * If new checkout options are added, skip_merge_working_tree
+        * should be updated accordingly.
+        */
  
        const char *new_branch;
        const char *new_branch_force;
@@@ -79,7 -84,7 +85,7 @@@ static int update_some(const struct obj
                return READ_TREE_RECURSIVE;
  
        len = base->len + strlen(pathname);
 -      ce = xcalloc(1, cache_entry_size(len));
 +      ce = make_empty_cache_entry(&the_index, len);
        oidcpy(&ce->oid, oid);
        memcpy(ce->name, base->buf, base->len);
        memcpy(ce->name + base->len, pathname, len - base->len);
                if (ce->ce_mode == old->ce_mode &&
                    !oidcmp(&ce->oid, &old->oid)) {
                        old->ce_flags |= CE_UPDATE;
 -                      free(ce);
 +                      discard_cache_entry(ce);
                        return 0;
                }
        }
@@@ -232,11 -237,11 +238,11 @@@ static int checkout_merged(int pos, con
        if (write_object_file(result_buf.ptr, result_buf.size, blob_type, &oid))
                die(_("Unable to add merge result for '%s'"), path);
        free(result_buf.ptr);
 -      ce = make_cache_entry(mode, oid.hash, path, 2, 0);
 +      ce = make_transient_cache_entry(mode, &oid, path, 2);
        if (!ce)
                die(_("make_cache_entry failed for path '%s'"), path);
        status = checkout_entry(ce, state, NULL);
 -      free(ce);
 +      discard_cache_entry(ce);
        return status;
  }
  
@@@ -318,7 -323,7 +324,7 @@@ static int checkout_paths(const struct 
                 * match_pathspec() for _all_ entries when
                 * opts->source_tree != NULL.
                 */
 -              if (ce_path_match(ce, &opts->pathspec, ps_matched))
 +              if (ce_path_match(&the_index, ce, &opts->pathspec, ps_matched))
                        ce->ce_flags |= CE_MATCHED;
        }
  
                die(_("unable to write new index file"));
  
        read_ref_full("HEAD", 0, &rev, NULL);
 -      head = lookup_commit_reference_gently(&rev, 1);
 +      head = lookup_commit_reference_gently(the_repository, &rev, 1);
  
        errs |= post_checkout_hook(head, head, 0);
        return errs;
@@@ -472,6 -477,98 +478,98 @@@ static void setup_branch_path(struct br
        branch->path = strbuf_detach(&buf, NULL);
  }
  
+ /*
+  * Skip merging the trees, updating the index and working directory if and
+  * only if we are creating a new branch via "git checkout -b <new_branch>."
+  */
+ static int skip_merge_working_tree(const struct checkout_opts *opts,
+       const struct branch_info *old_branch_info,
+       const struct branch_info *new_branch_info)
+ {
+       /*
+        * Do the merge if sparse checkout is on and the user has not opted in
+        * to the optimized behavior
+        */
+       if (core_apply_sparse_checkout && !checkout_optimize_new_branch)
+               return 0;
+       /*
+        * We must do the merge if we are actually moving to a new commit.
+        */
+       if (!old_branch_info->commit || !new_branch_info->commit ||
+               oidcmp(&old_branch_info->commit->object.oid, &new_branch_info->commit->object.oid))
+               return 0;
+       /*
+        * opts->patch_mode cannot be used with switching branches so is
+        * not tested here
+        */
+       /*
+        * opts->quiet only impacts output so doesn't require a merge
+        */
+       /*
+        * Honor the explicit request for a three-way merge or to throw away
+        * local changes
+        */
+       if (opts->merge || opts->force)
+               return 0;
+       /*
+        * --detach is documented as "updating the index and the files in the
+        * working tree" but this optimization skips those steps so fall through
+        * to the regular code path.
+        */
+       if (opts->force_detach)
+               return 0;
+       /*
+        * opts->writeout_stage cannot be used with switching branches so is
+        * not tested here
+        */
+       /*
+        * Honor the explicit ignore requests
+        */
+       if (!opts->overwrite_ignore || opts->ignore_skipworktree ||
+               opts->ignore_other_worktrees)
+               return 0;
+       /*
+        * opts->show_progress only impacts output so doesn't require a merge
+        */
+       /*
+        * If we aren't creating a new branch any changes or updates will
+        * happen in the existing branch.  Since that could only be updating
+        * the index and working directory, we don't want to skip those steps
+        * or we've defeated any purpose in running the command.
+        */
+       if (!opts->new_branch)
+               return 0;
+       /*
+        * new_branch_force is defined to "create/reset and checkout a branch"
+        * so needs to go through the merge to do the reset
+        */
+       if (opts->new_branch_force)
+               return 0;
+       /*
+        * A new orphaned branch requrires the index and the working tree to be
+        * adjusted to <start_point>
+        */
+       if (opts->new_orphan_branch)
+               return 0;
+       /*
+        * Remaining variables are not checkout options but used to track state
+        */
+       return 1;
+ }
  static int merge_working_tree(const struct checkout_opts *opts,
                              struct branch_info *old_branch_info,
                              struct branch_info *new_branch_info,
@@@ -831,7 -928,7 +929,7 @@@ static int switch_branches(const struc
        memset(&old_branch_info, 0, sizeof(old_branch_info));
        old_branch_info.path = path_to_free = resolve_refdup("HEAD", 0, &rev, &flag);
        if (old_branch_info.path)
 -              old_branch_info.commit = lookup_commit_reference_gently(&rev, 1);
 +              old_branch_info.commit = lookup_commit_reference_gently(the_repository, &rev, 1);
        if (!(flag & REF_ISSYMREF))
                old_branch_info.path = NULL;
  
                parse_commit_or_die(new_branch_info->commit);
        }
  
-       ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
-       if (ret) {
-               free(path_to_free);
-               return ret;
+       /* optimize the "checkout -b <new_branch> path */
+       if (skip_merge_working_tree(opts, &old_branch_info, new_branch_info)) {
+               if (!checkout_optimize_new_branch && !opts->quiet) {
+                       if (read_cache_preload(NULL) < 0)
+                               return error(_("index file corrupt"));
+                       show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
+               }
+       } else {
+               ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
+               if (ret) {
+                       free(path_to_free);
+                       return ret;
+               }
        }
  
        if (!opts->quiet && !old_branch_info.path && old_branch_info.commit && new_branch_info->commit != old_branch_info.commit)
  
  static int git_checkout_config(const char *var, const char *value, void *cb)
  {
+       if (!strcmp(var, "checkout.optimizenewbranch")) {
+               checkout_optimize_new_branch = git_config_bool(var, value);
+               return 0;
+       }
        if (!strcmp(var, "diff.ignoresubmodules")) {
                struct checkout_opts *opts = cb;
                handle_ignore_submodules_arg(&opts->diff_options, value);
@@@ -880,8 -991,7 +992,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;
        else
                new_branch_info->path = NULL; /* not an existing branch */
  
 -      new_branch_info->commit = lookup_commit_reference_gently(rev, 1);
 +      new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
        if (!new_branch_info->commit) {
                /* not a commit */
                *source_tree = parse_tree_indirect(rev);
@@@ -1115,7 -1222,6 +1227,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"),
        if (opts.track != BRANCH_TRACK_UNSPECIFIED && !opts.new_branch) {
                const char *argv0 = argv[0];
                if (!argc || !strcmp(argv0, "--"))
 -                      die (_("--track needs a branch name"));
 +                      die(_("--track needs a branch name"));
                skip_prefix(argv0, "refs/", &argv0);
                skip_prefix(argv0, "remotes/", &argv0);
                argv0 = strchr(argv0, '/');
                if (!argv0 || !argv0[1])
 -                      die (_("Missing branch name; try -b"));
 +                      die(_("missing branch name; try -b"));
                opts.new_branch = argv0 + 1;
        }
  
                        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);
 +      }
  }