Merge branch 'nd/command-list'
authorJunio C Hamano <gitster@pobox.com>
Fri, 1 Jun 2018 06:06:37 +0000 (15:06 +0900)
committerJunio C Hamano <gitster@pobox.com>
Fri, 1 Jun 2018 06:06:37 +0000 (15:06 +0900)
The list of commands with their various attributes were spread
across a few places in the build procedure, but it now is getting a
bit more consolidated to allow more automation.

* nd/command-list:
completion: allow to customize the completable command list
completion: add and use --list-cmds=alias
completion: add and use --list-cmds=nohelpers
Move declaration for alias.c to alias.h
completion: reduce completable command list
completion: let git provide the completable command list
command-list.txt: documentation and guide line
help: use command-list.txt for the source of guides
help: add "-a --verbose" to list all commands with synopsis
git: support --list-cmds=list-<category>
completion: implement and use --list-cmds=main,others
git --list-cmds: collect command list in a string_list
git.c: convert --list-* to --list-cmds=*
Remove common-cmds.h
help: use command-list.h for common command list
generate-cmds.sh: export all commands to command-list.h
generate-cmds.sh: factor out synopsis extract code

17 files changed:
1  2 
.gitignore
Documentation/config.txt
Documentation/git.txt
Documentation/gitattributes.txt
Makefile
builtin/help.c
builtin/merge.c
cache.h
command-list.txt
connect.c
contrib/completion/git-completion.bash
git.c
help.c
pager.c
sequencer.c
shell.c
t/t9902-completion.sh
diff --combined .gitignore
index b2a1ae4a1d6293004b10d14c33bb64fd9d14fe8b,0836083992f55820af35046008f627a1fec0c8f8..388cc4beee54faf9f06e9cfae75e7a256d368739
@@@ -3,7 -3,6 +3,7 @@@
  /GIT-LDFLAGS
  /GIT-PREFIX
  /GIT-PERL-DEFINES
 +/GIT-PERL-HEADER
  /GIT-PYTHON-VARS
  /GIT-SCRIPT-DEFINES
  /GIT-USER-AGENT
@@@ -35,7 -34,6 +35,7 @@@
  /git-clone
  /git-column
  /git-commit
 +/git-commit-graph
  /git-commit-tree
  /git-config
  /git-count-objects
  /git-rm
  /git-send-email
  /git-send-pack
 +/git-serve
  /git-sh-i18n
  /git-sh-i18n--envsubst
  /git-sh-setup
  /gitweb/gitweb.cgi
  /gitweb/static/gitweb.js
  /gitweb/static/gitweb.min.*
- /common-cmds.h
+ /command-list.h
  *.tar.gz
  *.dsc
  *.deb
diff --combined Documentation/config.txt
index 7d8383433ce9995c1950705fb9b345eff1bd652d,9e81dcf867d8323d35a534859aa74bd7acc2eb83..ab641bf5a9984b7ab2dfea787a0db5704f79a448
@@@ -530,12 -530,6 +530,12 @@@ core.autocrlf:
        This variable can be set to 'input',
        in which case no output conversion is performed.
  
 +core.checkRoundtripEncoding::
 +      A comma and/or whitespace separated list of encodings that Git
 +      performs UTF-8 round trip checks on if they are used in an
 +      `working-tree-encoding` attribute (see linkgit:gitattributes[5]).
 +      The default value is `SHIFT-JIS`.
 +
  core.symlinks::
        If false, symbolic links are checked out as small plain files that
        contain the link text. linkgit:git-update-index[1] and
@@@ -904,10 -898,6 +904,10 @@@ 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.
 +
  core.sparseCheckout::
        Enable "sparse checkout" feature. See section "Sparse checkout" in
        linkgit:git-read-tree[1] for more information.
@@@ -1068,10 -1058,6 +1068,10 @@@ branch.<name>.rebase:
        "git pull" is run. See "pull.rebase" for doing this in a non
        branch-specific manner.
  +
 +When `merges`, pass the `--rebase-merges` option to 'git rebase'
 +so that the local merge commits are included in the rebase (see
 +linkgit:git-rebase[1] for details).
 ++
  When preserve, also pass `--preserve-merges` along to 'git rebase'
  so that locally committed merge commits will not be flattened
  by running 'git pull'.
@@@ -1102,16 -1088,6 +1102,16 @@@ clean.requireForce:
        A boolean to make git-clean do nothing unless given -f,
        -i or -n.   Defaults to true.
  
 +color.advice::
 +      A boolean to enable/disable color in hints (e.g. when a push
 +      failed, see `advice.*` for a list).  May be set to `always`,
 +      `false` (or `never`) or `auto` (or `true`), in which case colors
 +      are used only when the error output goes to a terminal. If
 +      unset, then the value of `color.ui` is used (`auto` by default).
 +
 +color.advice.hint::
 +      Use customized color for hints.
 +
  color.branch::
        A boolean to enable/disable color in the output of
        linkgit:git-branch[1]. May be set to `always`,
@@@ -1214,15 -1190,6 +1214,15 @@@ color.pager:
        A boolean to enable/disable colored output when the pager is in
        use (default is true).
  
 +color.push::
 +      A boolean to enable/disable color in push errors. May be set to
 +      `always`, `false` (or `never`) or `auto` (or `true`), in which
 +      case colors are used only when the error output goes to a terminal.
 +      If unset, then the value of `color.ui` is used (`auto` by default).
 +
 +color.push.error::
 +      Use customized color for push errors.
 +
  color.showBranch::
        A boolean to enable/disable color in the output of
        linkgit:git-show-branch[1]. May be set to `always`,
@@@ -1251,42 -1218,6 +1251,42 @@@ 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
 +      case colors are used only when the error output goes to a terminal.
 +      If unset, then the value of `color.ui` is used (`auto` by default).
 +
 +color.transport.rejected::
 +      Use customized color when a push was rejected.
 +
  color.ui::
        This variable determines the default value for variables such
        as `color.diff` and `color.grep` that control the use of color
@@@ -1412,6 -1343,14 +1412,14 @@@ credential.<url>.*:
  credentialCache.ignoreSIGHUP::
        Tell git-credential-cache--daemon to ignore SIGHUP, instead of quitting.
  
+ completion.commands::
+       This is only used by git-completion.bash to add or remove
+       commands from the list of completed commands. Normally only
+       porcelain commands and a few select others are completed. You
+       can add more commands, separated by space, in this
+       variable. Prefixing the command with '-' will remove it from
+       the existing list.
  include::diff-config.txt[]
  
  difftool.<tool>.path::
@@@ -1627,18 -1566,6 +1635,18 @@@ gc.autoDetach:
        Make `git gc --auto` return immediately and run in background
        if the system supports it. Default is true.
  
 +gc.bigPackThreshold::
 +      If non-zero, all packs larger than this limit are kept when
 +      `git gc` is run. This is very similar to `--keep-base-pack`
 +      except that all packs that meet the threshold are kept, not
 +      just the base pack. Defaults to zero. Common unit suffixes of
 +      'k', 'm', or 'g' are supported.
 ++
 +Note that if the number of kept packs is more than gc.autoPackLimit,
 +this configuration variable is ignored, all packs except the base pack
 +will be repacked. After this the number of packs should go below
 +gc.autoPackLimit and gc.bigPackThreshold should be respected again.
 +
  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
@@@ -2503,7 -2430,6 +2511,7 @@@ pack.window:
  pack.depth::
        The maximum delta depth used by linkgit:git-pack-objects[1] when no
        maximum depth is given on the command line. Defaults to 50.
 +      Maximum value is 4095.
  
  pack.windowMemory::
        The maximum size of memory that is consumed by each thread
@@@ -2540,8 -2466,7 +2548,8 @@@ pack.deltaCacheLimit:
        The maximum size of a delta, that is cached in
        linkgit:git-pack-objects[1]. This cache is used to speed up the
        writing object phase by not having to recompute the final delta
 -      result once the best match for all objects is found. Defaults to 1000.
 +      result once the best match for all objects is found.
 +      Defaults to 1000. Maximum value is 65535.
  
  pack.threads::
        Specifies the number of threads to spawn when searching for best
@@@ -2700,10 -2625,6 +2708,10 @@@ pull.rebase:
        pull" is run. See "branch.<name>.rebase" for setting this on a
        per-branch basis.
  +
 +When `merges`, pass the `--rebase-merges` option to 'git rebase'
 +so that the local merge commits are included in the rebase (see
 +linkgit:git-rebase[1] for details).
 ++
  When preserve, also pass `--preserve-merges` along to 'git rebase'
  so that locally committed merge commits will not be flattened
  by running 'git pull'.
@@@ -3206,18 -3127,6 +3214,18 @@@ status.displayCommentPrefix:
        behavior of linkgit:git-status[1] in Git 1.8.4 and previous.
        Defaults to false.
  
 +status.renameLimit::
 +      The number of files to consider when performing rename detection
 +      in linkgit:git-status[1] and linkgit:git-commit[1]. Defaults to
 +      the value of diff.renameLimit.
 +
 +status.renames::
 +      Whether and how Git detects renames in linkgit:git-status[1] and
 +      linkgit:git-commit[1] .  If set to "false", rename detection is
 +      disabled. If set to "true", basic rename detection is enabled.
 +      If set to "copies" or "copy", Git will detect copies, as well.
 +      Defaults to the value of diff.renames.
 +
  status.showStash::
        If set to true, linkgit:git-status[1] will display the number of
        entries currently stashed away.
diff --combined Documentation/git.txt
index c662f41c1dce8ee69113dbb35298bd5c97e831b7,6f7eddf847bd1218ad62fad3563dc18a2448625d..dba7f0c18e33e7e26ca100992236e39b3d5b8a91
@@@ -11,7 -11,7 +11,7 @@@ SYNOPSI
  [verse]
  'git' [--version] [--help] [-C <path>] [-c <name>=<value>]
      [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
 -    [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
 +    [-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare]
      [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
      [--super-prefix=<path>]
      <command> [<args>]
@@@ -103,7 -103,6 +103,7 @@@ foo.bar= ...`) sets `foo.bar` to the em
        configuration options (see the "Configuration Mechanism" section
        below).
  
 +-P::
  --no-pager::
        Do not pipe Git output into a pager.
  
        Do not perform optional operations that require locks. This is
        equivalent to setting the `GIT_OPTIONAL_LOCKS` to `0`.
  
+ --list-cmds=group[,group...]::
+       List commands by group. This is an internal/experimental
+       option and may change or be removed in the future. Supported
+       groups are: builtins, parseopt (builtin commands that use
+       parse-options), main (all commands in libexec directory),
+       others (all other commands in `$PATH` that have git- prefix),
+       list-<category> (see categories in command-list.txt),
+       nohelpers (exclude helper commands), alias and config
+       (retrieve command list from config variable completion.commands)
  GIT COMMANDS
  ------------
  
index b72936a885c9772d60c7105cb9e5ba2e27968fcc,083c2f380d218bce30d494813f24e420b331a8a8..92010b062e08678fcfb0a143f75bbd4b07bfcf4b
@@@ -3,7 -3,7 +3,7 @@@ gitattributes(5
  
  NAME
  ----
- gitattributes - defining attributes per path
+ gitattributes - Defining attributes per path
  
  SYNOPSIS
  --------
@@@ -279,94 -279,6 +279,94 @@@ few exceptions.  Even though..
    catch potential problems early, safety triggers.
  
  
 +`working-tree-encoding`
 +^^^^^^^^^^^^^^^^^^^^^^^
 +
 +Git recognizes files encoded in ASCII or one of its supersets (e.g.
 +UTF-8, ISO-8859-1, ...) as text files. Files encoded in certain other
 +encodings (e.g. UTF-16) are interpreted as binary and consequently
 +built-in Git text processing tools (e.g. 'git diff') as well as most Git
 +web front ends do not visualize the contents of these files by default.
 +
 +In these cases you can tell Git the encoding of a file in the working
 +directory with the `working-tree-encoding` attribute. If a file with this
 +attribute is added to Git, then Git reencodes the content from the
 +specified encoding to UTF-8. Finally, Git stores the UTF-8 encoded
 +content in its internal data structure (called "the index"). On checkout
 +the content is reencoded back to the specified encoding.
 +
 +Please note that using the `working-tree-encoding` attribute may have a
 +number of pitfalls:
 +
 +- Alternative Git implementations (e.g. JGit or libgit2) and older Git
 +  versions (as of March 2018) do not support the `working-tree-encoding`
 +  attribute. If you decide to use the `working-tree-encoding` attribute
 +  in your repository, then it is strongly recommended to ensure that all
 +  clients working with the repository support it.
 +
 +  For example, Microsoft Visual Studio resources files (`*.rc`) or
 +  PowerShell script files (`*.ps1`) are sometimes encoded in UTF-16.
 +  If you declare `*.ps1` as files as UTF-16 and you add `foo.ps1` with
 +  a `working-tree-encoding` enabled Git client, then `foo.ps1` will be
 +  stored as UTF-8 internally. A client without `working-tree-encoding`
 +  support will checkout `foo.ps1` as UTF-8 encoded file. This will
 +  typically cause trouble for the users of this file.
 +
 +  If a Git client, that does not support the `working-tree-encoding`
 +  attribute, adds a new file `bar.ps1`, then `bar.ps1` will be
 +  stored "as-is" internally (in this example probably as UTF-16).
 +  A client with `working-tree-encoding` support will interpret the
 +  internal contents as UTF-8 and try to convert it to UTF-16 on checkout.
 +  That operation will fail and cause an error.
 +
 +- Reencoding content to non-UTF encodings can cause errors as the
 +  conversion might not be UTF-8 round trip safe. If you suspect your
 +  encoding to not be round trip safe, then add it to
 +  `core.checkRoundtripEncoding` to make Git check the round trip
 +  encoding (see linkgit:git-config[1]). SHIFT-JIS (Japanese character
 +  set) is known to have round trip issues with UTF-8 and is checked by
 +  default.
 +
 +- Reencoding content requires resources that might slow down certain
 +  Git operations (e.g 'git checkout' or 'git add').
 +
 +Use the `working-tree-encoding` attribute only if you cannot store a file
 +in UTF-8 encoding and if you want Git to be able to process the content
 +as text.
 +
 +As an example, use the following attributes if your '*.ps1' files are
 +UTF-16 encoded with byte order mark (BOM) and you want Git to perform
 +automatic line ending conversion based on your platform.
 +
 +------------------------
 +*.ps1         text working-tree-encoding=UTF-16
 +------------------------
 +
 +Use the following attributes if your '*.ps1' files are UTF-16 little
 +endian encoded without BOM and you want Git to use Windows line endings
 +in the working directory. Please note, it is highly recommended to
 +explicitly define the line endings with `eol` if the `working-tree-encoding`
 +attribute is used to avoid ambiguity.
 +
 +------------------------
 +*.ps1         text working-tree-encoding=UTF-16LE eol=CRLF
 +------------------------
 +
 +You can get a list of all available encodings on your platform with the
 +following command:
 +
 +------------------------
 +iconv --list
 +------------------------
 +
 +If you do not know the encoding of a file, then you can use the `file`
 +command to guess the encoding:
 +
 +------------------------
 +file foo.ps1
 +------------------------
 +
 +
  `ident`
  ^^^^^^^
  
@@@ -1229,8 -1141,8 +1229,8 @@@ to
  ------------
  
  
 -EXAMPLE
 --------
 +EXAMPLES
 +--------
  
  If you have these three `gitattributes` file:
  
diff --combined Makefile
index 4bca65383a784dca7da0092adb6c2eb754fa83cf,1efb751e4621934e55c8805bb40e7d34ec0735ca..1d27f36365ae2485e3706548b2b0299436902cc6
+++ b/Makefile
@@@ -441,49 -441,6 +441,49 @@@ all:
  #
  # When cross-compiling, define HOST_CPU as the canonical name of the CPU on
  # which the built Git will run (for instance "x86_64").
 +#
 +# Define RUNTIME_PREFIX to configure Git to resolve its ancillary tooling and
 +# support files relative to the location of the runtime binary, rather than
 +# hard-coding them into the binary. Git installations built with RUNTIME_PREFIX
 +# can be moved to arbitrary filesystem locations. RUNTIME_PREFIX also causes
 +# Perl scripts to use a modified entry point header allowing them to resolve
 +# support files at runtime.
 +#
 +# When using RUNTIME_PREFIX, define HAVE_BSD_KERN_PROC_SYSCTL if your platform
 +# supports the KERN_PROC BSD sysctl function.
 +#
 +# When using RUNTIME_PREFIX, define PROCFS_EXECUTABLE_PATH if your platform
 +# mounts a "procfs" filesystem capable of resolving the path of the current
 +# executable. If defined, this must be the canonical path for the "procfs"
 +# current executable path.
 +#
 +# When using RUNTIME_PREFIX, define HAVE_NS_GET_EXECUTABLE_PATH if your platform
 +# supports calling _NSGetExecutablePath to retrieve the path of the running
 +# executable.
 +#
 +# When using RUNTIME_PREFIX, define HAVE_WPGMPTR if your platform offers
 +# the global variable _wpgmptr containing the absolute path of the current
 +# executable (this is the case on Windows).
 +#
 +# Define DEVELOPER to enable more compiler warnings. Compiler version
 +# and family are auto detected, but could be overridden by defining
 +# COMPILER_FEATURES (see config.mak.dev)
 +#
 +# When DEVELOPER is set, DEVOPTS can be used to control compiler
 +# options.  This variable contains keywords separated by
 +# whitespace. The following keywords are are recognized:
 +#
 +#    no-error:
 +#
 +#        suppresses the -Werror that implicitly comes with
 +#        DEVELOPER=1. Useful for getting the full set of errors
 +#        without immediately dying, or for logging them.
 +#
 +#    extra-all:
 +#
 +#        The DEVELOPER mode enables -Wextra with a few exceptions. By
 +#        setting this flag the exceptions are removed, and all of
 +#        -Wextra is used.
  
  GIT-VERSION-FILE: FORCE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
  # CFLAGS and LDFLAGS are for the users to override from the command line.
  
  CFLAGS = -g -O2 -Wall
 -DEVELOPER_CFLAGS = -Werror \
 -      -Wdeclaration-after-statement \
 -      -Wno-format-zero-length \
 -      -Wold-style-definition \
 -      -Woverflow \
 -      -Wpointer-arith \
 -      -Wstrict-prototypes \
 -      -Wunused \
 -      -Wvla
  LDFLAGS =
  ALL_CFLAGS = $(CPPFLAGS) $(CFLAGS)
  ALL_LDFLAGS = $(LDFLAGS)
@@@ -512,8 -478,6 +512,8 @@@ ARFLAGS = rc
  #   mandir
  #   infodir
  #   htmldir
 +#   localedir
 +#   perllibdir
  # This can help installing the suite in a relocatable way.
  
  prefix = $(HOME)
@@@ -538,9 -502,7 +538,9 @@@ bindir_relative = $(patsubst $(prefix)/
  mandir_relative = $(patsubst $(prefix)/%,%,$(mandir))
  infodir_relative = $(patsubst $(prefix)/%,%,$(infodir))
  gitexecdir_relative = $(patsubst $(prefix)/%,%,$(gitexecdir))
 +localedir_relative = $(patsubst $(prefix)/%,%,$(localedir))
  htmldir_relative = $(patsubst $(prefix)/%,%,$(htmldir))
 +perllibdir_relative = $(patsubst $(prefix)/%,%,$(perllibdir))
  
  export prefix bindir sharedir sysconfdir gitwebdir perllibdir localedir
  
@@@ -690,6 -652,7 +690,6 @@@ PROGRAM_OBJS += imap-send.
  PROGRAM_OBJS += sh-i18n--envsubst.o
  PROGRAM_OBJS += shell.o
  PROGRAM_OBJS += show-index.o
 -PROGRAM_OBJS += upload-pack.o
  PROGRAM_OBJS += remote-testsvn.o
  
  # Binary suffix, set to .exe for Windows builds
@@@ -738,7 -701,6 +738,7 @@@ TEST_PROGRAMS_NEED_X += test-dump-untra
  TEST_PROGRAMS_NEED_X += test-fake-ssh
  TEST_PROGRAMS_NEED_X += test-line-buffer
  TEST_PROGRAMS_NEED_X += test-parse-options
 +TEST_PROGRAMS_NEED_X += test-pkt-line
  TEST_PROGRAMS_NEED_X += test-svn-fe
  TEST_PROGRAMS_NEED_X += test-tool
  
@@@ -795,7 -757,7 +795,7 @@@ LIB_FILE = libgit.
  XDIFF_LIB = xdiff/lib.a
  VCSSVN_LIB = vcs-svn/lib.a
  
- GENERATED_H += common-cmds.h
+ GENERATED_H += command-list.h
  
  LIB_H = $(shell $(FIND) . \
        -name .git -prune -o \
@@@ -821,13 -783,11 +821,13 @@@ LIB_OBJS += branch.
  LIB_OBJS += bulk-checkin.o
  LIB_OBJS += bundle.o
  LIB_OBJS += cache-tree.o
 +LIB_OBJS += chdir-notify.o
  LIB_OBJS += checkout.o
  LIB_OBJS += color.o
  LIB_OBJS += column.o
  LIB_OBJS += combine-diff.o
  LIB_OBJS += commit.o
 +LIB_OBJS += commit-graph.o
  LIB_OBJS += compat/obstack.o
  LIB_OBJS += compat/terminal.o
  LIB_OBJS += config.o
@@@ -858,7 -818,7 +858,7 @@@ LIB_OBJS += ewah/bitmap.
  LIB_OBJS += ewah/ewah_bitmap.o
  LIB_OBJS += ewah/ewah_io.o
  LIB_OBJS += ewah/ewah_rlw.o
 -LIB_OBJS += exec_cmd.o
 +LIB_OBJS += exec-cmd.o
  LIB_OBJS += fetch-object.o
  LIB_OBJS += fetch-pack.o
  LIB_OBJS += fsck.o
@@@ -881,11 -841,9 +881,11 @@@ LIB_OBJS += list-objects-filter-options
  LIB_OBJS += ll-merge.o
  LIB_OBJS += lockfile.o
  LIB_OBJS += log-tree.o
 +LIB_OBJS += ls-refs.o
  LIB_OBJS += mailinfo.o
  LIB_OBJS += mailmap.o
  LIB_OBJS += match-trees.o
 +LIB_OBJS += mem-pool.o
  LIB_OBJS += merge.o
  LIB_OBJS += merge-blobs.o
  LIB_OBJS += merge-recursive.o
@@@ -928,10 -886,9 +928,10 @@@ LIB_OBJS += refs/files-backend.
  LIB_OBJS += refs/iterator.o
  LIB_OBJS += refs/packed-backend.o
  LIB_OBJS += refs/ref-cache.o
 +LIB_OBJS += refspec.o
  LIB_OBJS += ref-filter.o
  LIB_OBJS += remote.o
 -LIB_OBJS += replace_object.o
 +LIB_OBJS += replace-object.o
  LIB_OBJS += repository.o
  LIB_OBJS += rerere.o
  LIB_OBJS += resolve-undo.o
@@@ -939,13 -896,12 +939,13 @@@ LIB_OBJS += revision.
  LIB_OBJS += run-command.o
  LIB_OBJS += send-pack.o
  LIB_OBJS += sequencer.o
 +LIB_OBJS += serve.o
  LIB_OBJS += server-info.o
  LIB_OBJS += setup.o
  LIB_OBJS += sha1-array.o
  LIB_OBJS += sha1-lookup.o
 -LIB_OBJS += sha1_file.o
 -LIB_OBJS += sha1_name.o
 +LIB_OBJS += sha1-file.o
 +LIB_OBJS += sha1-name.o
  LIB_OBJS += shallow.o
  LIB_OBJS += sideband.o
  LIB_OBJS += sigchain.o
@@@ -968,7 -924,6 +968,7 @@@ LIB_OBJS += tree-diff.
  LIB_OBJS += tree.o
  LIB_OBJS += tree-walk.o
  LIB_OBJS += unpack-trees.o
 +LIB_OBJS += upload-pack.o
  LIB_OBJS += url.o
  LIB_OBJS += urlmatch.o
  LIB_OBJS += usage.o
@@@ -981,7 -936,7 +981,7 @@@ LIB_OBJS += walker.
  LIB_OBJS += wildmatch.o
  LIB_OBJS += worktree.o
  LIB_OBJS += wrapper.o
 -LIB_OBJS += write_or_die.o
 +LIB_OBJS += write-or-die.o
  LIB_OBJS += ws.o
  LIB_OBJS += wt-status.o
  LIB_OBJS += xdiff-interface.o
@@@ -1008,7 -963,6 +1008,7 @@@ BUILTIN_OBJS += builtin/clone.
  BUILTIN_OBJS += builtin/column.o
  BUILTIN_OBJS += builtin/commit-tree.o
  BUILTIN_OBJS += builtin/commit.o
 +BUILTIN_OBJS += builtin/commit-graph.o
  BUILTIN_OBJS += builtin/config.o
  BUILTIN_OBJS += builtin/count-objects.o
  BUILTIN_OBJS += builtin/credential.o
@@@ -1074,7 -1028,6 +1074,7 @@@ BUILTIN_OBJS += builtin/rev-parse.
  BUILTIN_OBJS += builtin/revert.o
  BUILTIN_OBJS += builtin/rm.o
  BUILTIN_OBJS += builtin/send-pack.o
 +BUILTIN_OBJS += builtin/serve.o
  BUILTIN_OBJS += builtin/shortlog.o
  BUILTIN_OBJS += builtin/show-branch.o
  BUILTIN_OBJS += builtin/show-ref.o
@@@ -1088,7 -1041,6 +1088,7 @@@ BUILTIN_OBJS += builtin/update-index.
  BUILTIN_OBJS += builtin/update-ref.o
  BUILTIN_OBJS += builtin/update-server-info.o
  BUILTIN_OBJS += builtin/upload-archive.o
 +BUILTIN_OBJS += builtin/upload-pack.o
  BUILTIN_OBJS += builtin/var.o
  BUILTIN_OBJS += builtin/verify-commit.o
  BUILTIN_OBJS += builtin/verify-pack.o
@@@ -1110,7 -1062,7 +1110,7 @@@ include config.mak.unam
  -include config.mak
  
  ifdef DEVELOPER
 -CFLAGS += $(DEVELOPER_CFLAGS)
 +include config.mak.dev
  endif
  
  comma := ,
@@@ -1711,27 -1663,10 +1711,27 @@@ ifdef HAVE_BSD_SYSCT
        BASIC_CFLAGS += -DHAVE_BSD_SYSCTL
  endif
  
 +ifdef HAVE_BSD_KERN_PROC_SYSCTL
 +      BASIC_CFLAGS += -DHAVE_BSD_KERN_PROC_SYSCTL
 +endif
 +
  ifdef HAVE_GETDELIM
        BASIC_CFLAGS += -DHAVE_GETDELIM
  endif
  
 +ifneq ($(PROCFS_EXECUTABLE_PATH),)
 +      procfs_executable_path_SQ = $(subst ','\'',$(PROCFS_EXECUTABLE_PATH))
 +      BASIC_CFLAGS += '-DPROCFS_EXECUTABLE_PATH="$(procfs_executable_path_SQ)"'
 +endif
 +
 +ifdef HAVE_NS_GET_EXECUTABLE_PATH
 +      BASIC_CFLAGS += -DHAVE_NS_GET_EXECUTABLE_PATH
 +endif
 +
 +ifdef HAVE_WPGMPTR
 +      BASIC_CFLAGS += -DHAVE_WPGMPTR
 +endif
 +
  ifeq ($(TCLTK_PATH),)
  NO_TCLTK = NoThanks
  endif
@@@ -1816,13 -1751,11 +1816,13 @@@ mandir_relative_SQ = $(subst ','\'',$(m
  infodir_relative_SQ = $(subst ','\'',$(infodir_relative))
  perllibdir_SQ = $(subst ','\'',$(perllibdir))
  localedir_SQ = $(subst ','\'',$(localedir))
 +localedir_relative_SQ = $(subst ','\'',$(localedir_relative))
  gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
  gitexecdir_relative_SQ = $(subst ','\'',$(gitexecdir_relative))
  template_dir_SQ = $(subst ','\'',$(template_dir))
  htmldir_relative_SQ = $(subst ','\'',$(htmldir_relative))
  prefix_SQ = $(subst ','\'',$(prefix))
 +perllibdir_relative_SQ = $(subst ','\'',$(perllibdir_relative))
  gitwebdir_SQ = $(subst ','\'',$(gitwebdir))
  
  SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
@@@ -1833,31 -1766,6 +1833,31 @@@ TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_
  DIFF_SQ = $(subst ','\'',$(DIFF))
  PERLLIB_EXTRA_SQ = $(subst ','\'',$(PERLLIB_EXTRA))
  
 +# RUNTIME_PREFIX's resolution logic requires resource paths to be expressed
 +# relative to each other and share an installation path.
 +#
 +# This is a dependency in:
 +# - Git's binary RUNTIME_PREFIX logic in (see "exec_cmd.c").
 +# - The runtime prefix Perl header (see
 +#   "perl/header_templates/runtime_prefix.template.pl").
 +ifdef RUNTIME_PREFIX
 +
 +ifneq ($(filter /%,$(firstword $(gitexecdir_relative))),)
 +$(error RUNTIME_PREFIX requires a relative gitexecdir, not: $(gitexecdir))
 +endif
 +
 +ifneq ($(filter /%,$(firstword $(localedir_relative))),)
 +$(error RUNTIME_PREFIX requires a relative localedir, not: $(localedir))
 +endif
 +
 +ifndef NO_PERL
 +ifneq ($(filter /%,$(firstword $(perllibdir_relative))),)
 +$(error RUNTIME_PREFIX requires a relative perllibdir, not: $(perllibdir))
 +endif
 +endif
 +
 +endif
 +
  # We must filter out any object files from $(GITLIBS),
  # as it is typically used like:
  #
@@@ -2006,9 -1914,9 +2006,9 @@@ git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
                $(filter %.o,$^) $(LIBS)
  
- help.sp help.s help.o: common-cmds.h
+ help.sp help.s help.o: command-list.h
  
- builtin/help.sp builtin/help.s builtin/help.o: common-cmds.h GIT-PREFIX
+ builtin/help.sp builtin/help.s builtin/help.o: command-list.h GIT-PREFIX
  builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \
        '-DGIT_HTML_PATH="$(htmldir_relative_SQ)"' \
        '-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \
@@@ -2027,9 -1935,9 +2027,9 @@@ $(BUILT_INS): git$
        ln -s $< $@ 2>/dev/null || \
        cp $< $@
  
- common-cmds.h: generate-cmdlist.sh command-list.txt
+ command-list.h: generate-cmdlist.sh command-list.txt
  
- common-cmds.h: $(wildcard Documentation/git-*.txt)
+ command-list.h: $(wildcard Documentation/git*.txt)
        $(QUIET_GEN)$(SHELL_PATH) ./generate-cmdlist.sh command-list.txt >$@+ && mv $@+ $@
  
  SCRIPT_DEFINES = $(SHELL_PATH_SQ):$(DIFF_SQ):$(GIT_VERSION):\
@@@ -2078,44 -1986,27 +2078,44 @@@ git.res: git.rc GIT-VERSION-FIL
  # This makes sure we depend on the NO_PERL setting itself.
  $(SCRIPT_PERL_GEN): GIT-BUILD-OPTIONS
  
 -ifndef NO_PERL
 -$(SCRIPT_PERL_GEN):
 +# Used for substitution in Perl modules. Disabled when using RUNTIME_PREFIX
 +# since the locale directory is injected.
 +perl_localedir_SQ = $(localedir_SQ)
  
 +ifndef NO_PERL
 +PERL_HEADER_TEMPLATE = perl/header_templates/fixed_prefix.template.pl
  PERL_DEFINES = $(PERL_PATH_SQ):$(PERLLIB_EXTRA_SQ):$(perllibdir_SQ)
 -$(SCRIPT_PERL_GEN): % : %.perl GIT-PERL-DEFINES GIT-VERSION-FILE
 +
 +PERL_DEFINES := $(PERL_PATH_SQ) $(PERLLIB_EXTRA_SQ) $(perllibdir_SQ)
 +PERL_DEFINES += $(RUNTIME_PREFIX)
 +
 +# Support Perl runtime prefix. In this mode, a different header is installed
 +# into Perl scripts.
 +ifdef RUNTIME_PREFIX
 +
 +PERL_HEADER_TEMPLATE = perl/header_templates/runtime_prefix.template.pl
 +
 +# Don't export a fixed $(localedir) path; it will be resolved by the Perl header
 +# at runtime.
 +perl_localedir_SQ =
 +
 +endif
 +
 +PERL_DEFINES += $(gitexecdir) $(perllibdir) $(localedir)
 +
 +$(SCRIPT_PERL_GEN): % : %.perl GIT-PERL-DEFINES GIT-PERL-HEADER GIT-VERSION-FILE
        $(QUIET_GEN)$(RM) $@ $@+ && \
 -      INSTLIBDIR='$(perllibdir_SQ)' && \
 -      INSTLIBDIR_EXTRA='$(PERLLIB_EXTRA_SQ)' && \
 -      INSTLIBDIR="$$INSTLIBDIR$${INSTLIBDIR_EXTRA:+:$$INSTLIBDIR_EXTRA}" && \
        sed -e '1{' \
            -e '        s|#!.*perl|#!$(PERL_PATH_SQ)|' \
 -          -e '        h' \
 -          -e '        s=.*=use lib (split(/$(pathsep)/, $$ENV{GITPERLLIB} || "'"$$INSTLIBDIR"'"));=' \
 -          -e '        H' \
 -          -e '        x' \
 +          -e '        rGIT-PERL-HEADER' \
 +          -e '        G' \
            -e '}' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            $< >$@+ && \
        chmod +x $@+ && \
        mv $@+ $@
  
 +PERL_DEFINES := $(subst $(space),:,$(PERL_DEFINES))
  GIT-PERL-DEFINES: FORCE
        @FLAGS='$(PERL_DEFINES)'; \
            if test x"$$FLAGS" != x"`cat $@ 2>/dev/null`" ; then \
                echo "$$FLAGS" >$@; \
            fi
  
 +GIT-PERL-HEADER: $(PERL_HEADER_TEMPLATE) GIT-PERL-DEFINES Makefile
 +      $(QUIET_GEN)$(RM) $@ && \
 +      INSTLIBDIR='$(perllibdir_SQ)' && \
 +      INSTLIBDIR_EXTRA='$(PERLLIB_EXTRA_SQ)' && \
 +      INSTLIBDIR="$$INSTLIBDIR$${INSTLIBDIR_EXTRA:+:$$INSTLIBDIR_EXTRA}" && \
 +      sed -e 's=@@PATHSEP@@=$(pathsep)=g' \
 +          -e "s=@@INSTLIBDIR@@=$$INSTLIBDIR=g" \
 +          -e 's=@@PERLLIBDIR_REL@@=$(perllibdir_relative_SQ)=g' \
 +          -e 's=@@GITEXECDIR_REL@@=$(gitexecdir_relative_SQ)=g' \
 +          -e 's=@@LOCALEDIR_REL@@=$(localedir_relative_SQ)=g' \
 +          $< >$@+ && \
 +      mv $@+ $@
 +
 +.PHONY: perllibdir
 +perllibdir:
 +      @echo '$(perllibdir_SQ)'
  
  .PHONY: gitweb
  gitweb:
  # Dependencies on header files, for platforms that do not support
  # the gcc -MMD option.
  #
- # Dependencies on automatically generated headers such as common-cmds.h
+ # Dependencies on automatically generated headers such as command-list.h
  # should _not_ be included here, since they are necessary even when
  # building an object for the first time.
  
  $(OBJECTS): $(LIB_H)
  endif
  
 -exec_cmd.sp exec_cmd.s exec_cmd.o: GIT-PREFIX
 -exec_cmd.sp exec_cmd.s exec_cmd.o: EXTRA_CPPFLAGS = \
 +exec-cmd.sp exec-cmd.s exec-cmd.o: GIT-PREFIX
 +exec-cmd.sp exec-cmd.s exec-cmd.o: EXTRA_CPPFLAGS = \
        '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \
 +      '-DGIT_LOCALE_PATH="$(localedir_relative_SQ)"' \
        '-DBINDIR="$(bindir_relative_SQ)"' \
 -      '-DPREFIX="$(prefix_SQ)"'
 +      '-DFALLBACK_RUNTIME_PREFIX="$(prefix_SQ)"'
  
  builtin/init-db.sp builtin/init-db.s builtin/init-db.o: GIT-PREFIX
  builtin/init-db.sp builtin/init-db.s builtin/init-db.o: EXTRA_CPPFLAGS = \
@@@ -2301,7 -2175,7 +2301,7 @@@ attr.sp attr.s attr.o: EXTRA_CPPFLAGS 
  
  gettext.sp gettext.s gettext.o: GIT-PREFIX
  gettext.sp gettext.s gettext.o: EXTRA_CPPFLAGS = \
 -      -DGIT_LOCALE_PATH='"$(localedir_SQ)"'
 +      -DGIT_LOCALE_PATH='"$(localedir_relative_SQ)"'
  
  http-push.sp http.sp http-walker.sp remote-curl.sp imap-send.sp: SPARSE_FLAGS += \
        -DCURL_DISABLE_TYPECHECK
@@@ -2461,7 -2335,7 +2461,7 @@@ endi
  
  perl/build/lib/%.pm: perl/%.pm
        $(QUIET_GEN)mkdir -p $(dir $@) && \
 -      sed -e 's|@@LOCALEDIR@@|$(localedir_SQ)|g' \
 +      sed -e 's|@@LOCALEDIR@@|$(perl_localedir_SQ)|g' \
            -e 's|@@NO_PERL_CPAN_FALLBACKS@@|$(NO_PERL_CPAN_FALLBACKS_SQ)|g' \
        < $< > $@
  
@@@ -2653,7 -2527,7 +2653,7 @@@ sparse: $(SP_OBJ
  style:
        git clang-format --style file --diff --extensions c,h
  
- check: common-cmds.h
+ check: command-list.h
        @if sparse; \
        then \
                echo >&2 "Use 'make sparse' instead"; \
@@@ -2901,7 -2775,7 +2901,7 @@@ clean: profile-clean coverage-clea
        $(RM) $(TEST_PROGRAMS) $(NO_INSTALL)
        $(RM) -r bin-wrappers $(dep_dirs)
        $(RM) -r po/build/
-       $(RM) *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope*
+       $(RM) *.pyc *.pyo */*.pyc */*.pyo command-list.h $(ETAGS_TARGET) tags cscope*
        $(RM) -r $(GIT_TARNAME) .doc-tmp-dir
        $(RM) $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz
        $(RM) $(htmldocs).tar.gz $(manpages).tar.gz
@@@ -2919,7 -2793,7 +2919,7 @@@ ifndef NO_TCLT
  endif
        $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-LDFLAGS GIT-BUILD-OPTIONS
        $(RM) GIT-USER-AGENT GIT-PREFIX
 -      $(RM) GIT-SCRIPT-DEFINES GIT-PERL-DEFINES GIT-PYTHON-VARS
 +      $(RM) GIT-SCRIPT-DEFINES GIT-PERL-DEFINES GIT-PERL-HEADER GIT-PYTHON-VARS
  
  .PHONY: all install profile-clean clean strip
  .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell
diff --combined builtin/help.c
index 2d5107142926d46230d17715211338c1aa171e69,6b4b3df90d9cd80c2a4bc1827d8ffb58fe7a2bb6..58e0a5507f10365b43eaa2698da2c8bba18b12ed
@@@ -4,11 -4,12 +4,12 @@@
  #include "cache.h"
  #include "config.h"
  #include "builtin.h"
 -#include "exec_cmd.h"
 +#include "exec-cmd.h"
  #include "parse-options.h"
  #include "run-command.h"
  #include "column.h"
  #include "help.h"
+ #include "alias.h"
  
  #ifndef DEFAULT_HELP_FORMAT
  #define DEFAULT_HELP_FORMAT "man"
@@@ -36,6 -37,7 +37,7 @@@ static const char *html_path
  
  static int show_all = 0;
  static int show_guides = 0;
+ static int verbose;
  static unsigned int colopts;
  static enum help_format help_format = HELP_FORMAT_NONE;
  static int exclude_guides;
@@@ -48,6 -50,7 +50,7 @@@ static struct option builtin_help_optio
                        HELP_FORMAT_WEB),
        OPT_SET_INT('i', "info", &help_format, N_("show info page"),
                        HELP_FORMAT_INFO),
+       OPT__VERBOSE(&verbose, N_("print command description")),
        OPT_END(),
  };
  
@@@ -400,38 -403,6 +403,6 @@@ static void show_html_page(const char *
        open_html(page_path.buf);
  }
  
- static struct {
-       const char *name;
-       const char *help;
- } common_guides[] = {
-       { "attributes", N_("Defining attributes per path") },
-       { "everyday", N_("Everyday Git With 20 Commands Or So") },
-       { "glossary", N_("A Git glossary") },
-       { "ignore", N_("Specifies intentionally untracked files to ignore") },
-       { "modules", N_("Defining submodule properties") },
-       { "revisions", N_("Specifying revisions and ranges for Git") },
-       { "tutorial", N_("A tutorial introduction to Git (for version 1.5.1 or newer)") },
-       { "workflows", N_("An overview of recommended workflows with Git") },
- };
- static void list_common_guides_help(void)
- {
-       int i, longest = 0;
-       for (i = 0; i < ARRAY_SIZE(common_guides); i++) {
-               if (longest < strlen(common_guides[i].name))
-                       longest = strlen(common_guides[i].name);
-       }
-       puts(_("The common Git guides are:\n"));
-       for (i = 0; i < ARRAY_SIZE(common_guides); i++) {
-               printf("   %s   ", common_guides[i].name);
-               mput_char(' ', longest - strlen(common_guides[i].name));
-               puts(_(common_guides[i].help));
-       }
-       putchar('\n');
- }
  static const char *check_git_cmd(const char* cmd)
  {
        char *alias;
@@@ -463,6 -434,11 +434,11 @@@ int cmd_help(int argc, const char **arg
  
        if (show_all) {
                git_config(git_help_config, NULL);
+               if (verbose) {
+                       setup_pager();
+                       list_all_cmds_help();
+                       return 0;
+               }
                printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
                load_command_list("git-", &main_cmds, &other_cmds);
                list_commands(colopts, &main_cmds, &other_cmds);
diff --combined builtin/merge.c
index d85f99b7817d7b536d2749ee38f2d7c434286478,e3681cd8501ba526a0934225dacb601f54e8d427..6d7bbe8c9f34827c97f23e9beaffb19708db016c
@@@ -14,7 -14,6 +14,7 @@@
  #include "run-command.h"
  #include "diff.h"
  #include "refs.h"
 +#include "refspec.h"
  #include "commit.h"
  #include "diffcore.h"
  #include "revision.h"
@@@ -35,6 -34,7 +35,7 @@@
  #include "string-list.h"
  #include "packfile.h"
  #include "tag.h"
+ #include "alias.h"
  
  #define DEFAULT_TWOHEAD (1<<0)
  #define DEFAULT_OCTOPUS (1<<1)
@@@ -281,7 -281,7 +282,7 @@@ out
        return rc;
  }
  
 -static void read_empty(unsigned const char *sha1, int verbose)
 +static void read_empty(const struct object_id *oid, int verbose)
  {
        int i = 0;
        const char *args[7];
                args[i++] = "-v";
        args[i++] = "-m";
        args[i++] = "-u";
 -      args[i++] = EMPTY_TREE_SHA1_HEX;
 -      args[i++] = sha1_to_hex(sha1);
 +      args[i++] = empty_tree_oid_hex();
 +      args[i++] = oid_to_hex(oid);
        args[i] = NULL;
  
        if (run_command_v_opt(args, RUN_GIT_CMD))
                die(_("read-tree failed"));
  }
  
 -static void reset_hard(unsigned const char *sha1, int verbose)
 +static void reset_hard(const struct object_id *oid, int verbose)
  {
        int i = 0;
        const char *args[6];
                args[i++] = "-v";
        args[i++] = "--reset";
        args[i++] = "-u";
 -      args[i++] = sha1_to_hex(sha1);
 +      args[i++] = oid_to_hex(oid);
        args[i] = NULL;
  
        if (run_command_v_opt(args, RUN_GIT_CMD))
@@@ -325,7 -325,7 +326,7 @@@ static void restore_state(const struct 
        if (is_null_oid(stash))
                return;
  
 -      reset_hard(head->hash, 1);
 +      reset_hard(head, 1);
  
        args[2] = oid_to_hex(stash);
  
@@@ -648,7 -648,7 +649,7 @@@ static int try_merge_strategy(const cha
                              struct commit_list *remoteheads,
                              struct commit *head)
  {
 -      static struct lock_file lock;
 +      struct lock_file lock = LOCK_INIT;
        const char *head_arg = "HEAD";
  
        hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
@@@ -806,7 -806,7 +807,7 @@@ static int merge_trivial(struct commit 
  {
        struct object_id result_tree, result_commit;
        struct commit_list *parents, **pptr = &parents;
 -      static struct lock_file lock;
 +      struct lock_file lock = LOCK_INIT;
  
        hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
        refresh_cache(REFRESH_QUIET);
@@@ -1298,7 -1298,7 +1299,7 @@@ int cmd_merge(int argc, const char **ar
                if (remoteheads->next)
                        die(_("Can merge only exactly one commit into empty head"));
                remote_head_oid = &remoteheads->item->object.oid;
 -              read_empty(remote_head_oid->hash, 0);
 +              read_empty(remote_head_oid, 0);
                update_ref("initial pull", "HEAD", remote_head_oid, NULL, 0,
                           UPDATE_REFS_DIE_ON_ERR);
                goto done;
diff --combined cache.h
index 08a48263b52b621aa9f1db1a5f5abab39c31e36a,111116ea1362e8b80c1c2d1c99bbcf898dc86e66..89a107a7f79175c600eb2a9689982912541ae4e7
+++ b/cache.h
@@@ -324,7 -324,7 +324,7 @@@ struct index_state 
                 drop_cache_tree : 1;
        struct hashmap name_hash;
        struct hashmap dir_hash;
 -      unsigned char sha1[20];
 +      struct object_id oid;
        struct untracked_cache *untracked;
        uint64_t fsmonitor_last_update;
        struct ewah_bitmap *fsmonitor_dirty;
@@@ -373,13 -373,6 +373,13 @@@ extern void free_name_hash(struct index
  #define read_blob_data_from_cache(path, sz) read_blob_data_from_index(&the_index, (path), (sz))
  #endif
  
 +#define TYPE_BITS 3
 +
 +/*
 + * Values in this enum (except those outside the 3 bit range) are part
 + * of pack file format. See Documentation/technical/pack-format.txt
 + * for more information.
 + */
  enum object_type {
        OBJ_BAD = -1,
        OBJ_NONE = 0,
@@@ -435,7 -428,6 +435,7 @@@ static inline enum object_type object_t
  #define GIT_ICASE_PATHSPECS_ENVIRONMENT "GIT_ICASE_PATHSPECS"
  #define GIT_QUARANTINE_ENVIRONMENT "GIT_QUARANTINE_PATH"
  #define GIT_OPTIONAL_LOCKS_ENVIRONMENT "GIT_OPTIONAL_LOCKS"
 +#define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT "GIT_TEXTDOMAINDIR"
  
  /*
   * Environment variable used in handshaking the wire protocol.
@@@ -485,7 -477,7 +485,7 @@@ extern const char *get_git_common_dir(v
  extern char *get_object_directory(void);
  extern char *get_index_file(void);
  extern char *get_graft_file(void);
 -extern int set_git_dir(const char *path);
 +extern void set_git_dir(const char *path);
  extern int get_common_dir_noenv(struct strbuf *sb, const char *gitdir);
  extern int get_common_dir(struct strbuf *sb, const char *gitdir);
  extern const char *get_git_namespace(void);
@@@ -642,7 -634,7 +642,7 @@@ extern int unmerged_index(const struct 
   */
  extern int index_has_changes(struct strbuf *sb);
  
 -extern int verify_path(const char *path);
 +extern int verify_path(const char *path, unsigned mode);
  extern int strcmp_offset(const char *s1, const char *s2, size_t *first_change);
  extern int index_dir_exists(struct index_state *istate, const char *name, int namelen);
  extern void adjust_dirname_case(struct index_state *istate, char *name);
@@@ -813,7 -805,6 +813,7 @@@ extern char *git_replace_ref_base
  
  extern int fsync_object_files;
  extern int core_preload_index;
 +extern int core_commit_graph;
  extern int core_apply_sparse_checkout;
  extern int precomposed_unicode;
  extern int protect_hfs;
@@@ -1017,10 -1008,21 +1017,10 @@@ static inline void oidclr(struct object
        memset(oid->hash, 0, GIT_MAX_RAWSZ);
  }
  
 -
 -#define EMPTY_TREE_SHA1_HEX \
 -      "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
 -#define EMPTY_TREE_SHA1_BIN_LITERAL \
 -       "\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60" \
 -       "\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04"
 -extern const struct object_id empty_tree_oid;
 -#define EMPTY_TREE_SHA1_BIN (empty_tree_oid.hash)
 -
 -#define EMPTY_BLOB_SHA1_HEX \
 -      "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
 -#define EMPTY_BLOB_SHA1_BIN_LITERAL \
 -      "\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b" \
 -      "\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91"
 -extern const struct object_id empty_blob_oid;
 +static inline void oidread(struct object_id *oid, const unsigned char *hash)
 +{
 +      memcpy(oid->hash, hash, the_hash_algo->rawsz);
 +}
  
  static inline int is_empty_blob_sha1(const unsigned char *sha1)
  {
@@@ -1042,9 -1044,6 +1042,9 @@@ static inline int is_empty_tree_oid(con
        return !oidcmp(oid, the_hash_algo->empty_tree);
  }
  
 +const char *empty_tree_oid_hex(void);
 +const char *empty_blob_oid_hex(void);
 +
  /* set default permissions by passing mode arguments to open(2) */
  int git_mkstemps_mode(char *pattern, int suffix_len, int mode);
  int git_mkstemp_mode(char *pattern, int mode);
@@@ -1160,15 -1159,7 +1160,15 @@@ int normalize_path_copy(char *dst, cons
  int longest_ancestor_length(const char *path, struct string_list *prefixes);
  char *strip_path_suffix(const char *path, const char *suffix);
  int daemon_avoid_alias(const char *path);
 -extern int is_ntfs_dotgit(const char *name);
 +
 +/*
 + * These functions match their is_hfs_dotgit() counterparts; see utf8.h for
 + * details.
 + */
 +int is_ntfs_dotgit(const char *name);
 +int is_ntfs_dotgitmodules(const char *name);
 +int is_ntfs_dotgitignore(const char *name);
 +int is_ntfs_dotgitattributes(const char *name);
  
  /*
   * Returns true iff "str" could be confused as a command-line option when
@@@ -1200,8 -1191,27 +1200,8 @@@ static inline void *read_object_file(co
        return read_object_file_extended(oid, type, size, 1);
  }
  
 -/*
 - * This internal function is only declared here for the benefit of
 - * lookup_replace_object().  Please do not call it directly.
 - */
 -extern const struct object_id *do_lookup_replace_object(const struct object_id *oid);
 -
 -/*
 - * If object sha1 should be replaced, return the replacement object's
 - * name (replaced recursively, if necessary).  The return value is
 - * either sha1 or a pointer to a permanently-allocated value.  When
 - * object replacement is suppressed, always return sha1.
 - */
 -static inline const struct object_id *lookup_replace_object(const struct object_id *oid)
 -{
 -      if (!check_replace_refs)
 -              return oid;
 -      return do_lookup_replace_object(oid);
 -}
 -
  /* Read and unpack an object file into memory, write memory to an object file */
 -extern int oid_object_info(const struct object_id *, unsigned long *);
 +int oid_object_info(struct repository *r, const struct object_id *, unsigned long *);
  
  extern int hash_object_file(const void *buf, unsigned long len,
                            const char *type, struct object_id *oid);
@@@ -1260,7 -1270,7 +1260,7 @@@ extern int has_object_file_with_flags(c
   * with the specified name.  This function does not respect replace
   * references.
   */
 -extern int has_loose_object_nonlocal(const unsigned char *sha1);
 +extern int has_loose_object_nonlocal(const struct object_id *oid);
  
  extern void assert_oid_type(const struct object_id *oid, enum object_type expect);
  
@@@ -1291,6 -1301,7 +1291,6 @@@ static inline int hex2chr(const char *s
  #define FALLBACK_DEFAULT_ABBREV 7
  
  struct object_context {
 -      unsigned char tree[20];
        unsigned mode;
        /*
         * symlink_path is only used by get_tree_entry_follow_symlinks,
@@@ -1557,6 -1568,7 +1557,6 @@@ struct pack_window 
  
  struct pack_entry {
        off_t offset;
 -      unsigned char sha1[20];
        struct packed_git *p;
  };
  
@@@ -1680,10 -1692,7 +1680,10 @@@ struct object_info 
  #define OBJECT_INFO_QUICK 8
  /* Do not check loose object */
  #define OBJECT_INFO_IGNORE_LOOSE 16
 -extern int oid_object_info_extended(const struct object_id *, struct object_info *, unsigned flags);
 +
 +int oid_object_info_extended(struct repository *r,
 +                           const struct object_id *,
 +                           struct object_info *, unsigned flags);
  
  /*
   * Set this to 0 to prevent sha1_object_info_extended() from fetching missing
@@@ -1826,11 -1835,6 +1826,6 @@@ extern int ws_blank_line(const char *li
  void overlay_tree_on_index(struct index_state *istate,
                           const char *tree_name, const char *prefix);
  
- char *alias_lookup(const char *alias);
- int split_cmdline(char *cmdline, const char ***argv);
- /* Takes a negative value returned by split_cmdline */
- const char *split_cmdline_strerror(int cmdline_errno);
  /* setup.c */
  struct startup_info {
        int have_repository;
diff --combined command-list.txt
index 835c5890be93abc1852dd0e1e19dbb627fee6041,e505a1e34c857de3b2596276451e8cc52e970fba..e1c26c1bb7e618f6f372d9a568e7cab75612d2db
@@@ -1,23 -1,58 +1,58 @@@
- # common commands are grouped by themes
- # these groups are output by 'git help' in the order declared here.
- # map each common command in the command list to one of these groups.
- ### common groups (do not change this line)
- init         start a working area (see also: git help tutorial)
- worktree     work on the current change (see also: git help everyday)
- info         examine the history and state (see also: git help revisions)
- history      grow, mark and tweak your common history
- remote       collaborate (see also: git help workflows)
- ### command list (do not change this line)
- # command name                          category [deprecated] [common]
+ # Command classification list
+ # ---------------------------
+ # All supported commands, builtin or external, must be described in
+ # here. This info is used to list commands in various places. Each
+ # command is on one line followed by one or more attributes.
+ #
+ # The first attribute group is mandatory and indicates the command
+ # type. This group includes:
+ #
+ #   mainporcelain
+ #   ancillarymanipulators
+ #   ancillaryinterrogators
+ #   foreignscminterface
+ #   plumbingmanipulators
+ #   plumbinginterrogators
+ #   synchingrepositories
+ #   synchelpers
+ #   purehelpers
+ #
+ # The type names are self explanatory. But if you want to see what
+ # command belongs to what group to get a better picture, have a look
+ # at "git" man page, "GIT COMMANDS" section.
+ #
+ # Commands of type mainporcelain can also optionally have one of these
+ # attributes:
+ #
+ #   init
+ #   worktree
+ #   info
+ #   history
+ #   remote
+ #
+ # These commands are considered "common" and will show up in "git
+ # help" output in groups. Uncommon porcelain commands must not
+ # specify any of these attributes.
+ #
+ # "complete" attribute is used to mark that the command should be
+ # completable by git-completion.bash. Note that by default,
+ # mainporcelain commands are completable so you don't need this
+ # attribute.
+ #
+ # As part of the Git man page list, the man(5/7) guides are also
+ # specified here, which can only have "guide" attribute and nothing
+ # else.
+ #
+ ### command list (do not change this line, also do not change alignment)
+ # command name                          category [category] [category]
  git-add                                 mainporcelain           worktree
  git-am                                  mainporcelain
  git-annotate                            ancillaryinterrogators
- git-apply                               plumbingmanipulators
+ git-apply                               plumbingmanipulators            complete
  git-archimport                          foreignscminterface
  git-archive                             mainporcelain
  git-bisect                              mainporcelain           info
- git-blame                               ancillaryinterrogators
+ git-blame                               ancillaryinterrogators          complete
  git-branch                              mainporcelain           history
  git-bundle                              mainporcelain
  git-cat-file                            plumbinginterrogators
@@@ -27,16 -62,15 +62,16 @@@ git-check-mailma
  git-checkout                            mainporcelain           history
  git-checkout-index                      plumbingmanipulators
  git-check-ref-format                    purehelpers
- git-cherry                              ancillaryinterrogators
+ git-cherry                              ancillaryinterrogators          complete
  git-cherry-pick                         mainporcelain
  git-citool                              mainporcelain
  git-clean                               mainporcelain
  git-clone                               mainporcelain           init
  git-column                              purehelpers
  git-commit                              mainporcelain           history
 +git-commit-graph                        plumbingmanipulators
  git-commit-tree                         plumbingmanipulators
- git-config                              ancillarymanipulators
+ git-config                              ancillarymanipulators           complete
  git-count-objects                       ancillaryinterrogators
  git-credential                          purehelpers
  git-credential-cache                    purehelpers
@@@ -50,7 -84,7 +85,7 @@@ git-dif
  git-diff-files                          plumbinginterrogators
  git-diff-index                          plumbinginterrogators
  git-diff-tree                           plumbinginterrogators
- git-difftool                            ancillaryinterrogators
+ git-difftool                            ancillaryinterrogators          complete
  git-fast-export                         ancillarymanipulators
  git-fast-import                         ancillarymanipulators
  git-fetch                               mainporcelain           remote
@@@ -59,20 -93,20 +94,20 @@@ git-filter-branc
  git-fmt-merge-msg                       purehelpers
  git-for-each-ref                        plumbinginterrogators
  git-format-patch                        mainporcelain
- git-fsck                                ancillaryinterrogators
+ git-fsck                                ancillaryinterrogators          complete
  git-gc                                  mainporcelain
  git-get-tar-commit-id                   ancillaryinterrogators
  git-grep                                mainporcelain           info
  git-gui                                 mainporcelain
  git-hash-object                         plumbingmanipulators
- git-help                                ancillaryinterrogators
+ git-help                                ancillaryinterrogators          complete
  git-http-backend                        synchingrepositories
  git-http-fetch                          synchelpers
  git-http-push                           synchelpers
  git-imap-send                           foreignscminterface
  git-index-pack                          plumbingmanipulators
  git-init                                mainporcelain           init
- git-instaweb                            ancillaryinterrogators
+ git-instaweb                            ancillaryinterrogators          complete
  git-interpret-trailers                  purehelpers
  gitk                                    mainporcelain
  git-log                                 mainporcelain           info
@@@ -86,7 -120,7 +121,7 @@@ git-merge-bas
  git-merge-file                          plumbingmanipulators
  git-merge-index                         plumbingmanipulators
  git-merge-one-file                      purehelpers
- git-mergetool                           ancillarymanipulators
+ git-mergetool                           ancillarymanipulators           complete
  git-merge-tree                          ancillaryinterrogators
  git-mktag                               plumbingmanipulators
  git-mktree                              plumbingmanipulators
@@@ -107,28 -141,29 +142,29 @@@ git-quiltimpor
  git-read-tree                           plumbingmanipulators
  git-rebase                              mainporcelain           history
  git-receive-pack                        synchelpers
- git-reflog                              ancillarymanipulators
- git-remote                              ancillarymanipulators
- git-repack                              ancillarymanipulators
- git-replace                             ancillarymanipulators
- git-request-pull                        foreignscminterface
+ git-reflog                              ancillarymanipulators           complete
+ git-remote                              ancillarymanipulators           complete
+ git-repack                              ancillarymanipulators           complete
+ git-replace                             ancillarymanipulators           complete
+ git-request-pull                        foreignscminterface             complete
  git-rerere                              ancillaryinterrogators
  git-reset                               mainporcelain           worktree
  git-revert                              mainporcelain
  git-rev-list                            plumbinginterrogators
  git-rev-parse                           ancillaryinterrogators
  git-rm                                  mainporcelain           worktree
- git-send-email                          foreignscminterface
+ git-send-email                          foreignscminterface             complete
  git-send-pack                           synchingrepositories
  git-shell                               synchelpers
  git-shortlog                            mainporcelain
  git-show                                mainporcelain           info
- git-show-branch                         ancillaryinterrogators
+ git-show-branch                         ancillaryinterrogators          complete
  git-show-index                          plumbinginterrogators
  git-show-ref                            plumbinginterrogators
  git-sh-i18n                             purehelpers
  git-sh-setup                            purehelpers
  git-stash                               mainporcelain
+ git-stage                                                               complete
  git-status                              mainporcelain           info
  git-stripspace                          purehelpers
  git-submodule                           mainporcelain
@@@ -147,6 -182,22 +183,22 @@@ git-verify-commi
  git-verify-pack                         plumbinginterrogators
  git-verify-tag                          ancillaryinterrogators
  gitweb                                  ancillaryinterrogators
- git-whatchanged                         ancillaryinterrogators
+ git-whatchanged                         ancillaryinterrogators          complete
  git-worktree                            mainporcelain
  git-write-tree                          plumbingmanipulators
+ gitattributes                           guide
+ gitcli                                  guide
+ gitcore-tutorial                        guide
+ gitcvs-migration                        guide
+ gitdiffcore                             guide
+ giteveryday                             guide
+ gitglossary                             guide
+ githooks                                guide
+ gitignore                               guide
+ gitmodules                              guide
+ gitnamespaces                           guide
+ gitrepository-layout                    guide
+ gitrevisions                            guide
+ gittutorial-2                           guide
+ gittutorial                             guide
+ gitworkflows                            guide
diff --combined connect.c
index 31aa9c843311b4e0b01e7378a85709083b071311,ff078d28dcb0c128afd59bab6e93d442f9304b13..968e91b18c09e5ac814328e0f833e2d4aa91cf2c
+++ b/connect.c
  #include "sha1-array.h"
  #include "transport.h"
  #include "strbuf.h"
 +#include "version.h"
  #include "protocol.h"
+ #include "alias.h"
  
 -static char *server_capabilities;
 +static char *server_capabilities_v1;
 +static struct argv_array server_capabilities_v2 = ARGV_ARRAY_INIT;
  static const char *parse_feature_value(const char *, const char *, int *);
  
  static int check_ref(const char *name, unsigned int flags)
@@@ -48,14 -47,8 +49,14 @@@ int check_ref_type(const struct ref *re
        return check_ref(ref->name, flags);
  }
  
 -static void die_initial_contact(int unexpected)
 +static NORETURN void die_initial_contact(int unexpected)
  {
 +      /*
 +       * A hang-up after seeing some response from the other end
 +       * means that it is unexpected, as we know the other end is
 +       * willing to talk to us.  A hang-up before seeing any
 +       * response does not necessarily mean an ACL problem, though.
 +       */
        if (unexpected)
                die(_("The remote end hung up upon initial contact"));
        else
                      "and the repository exists."));
  }
  
 +/* Checks if the server supports the capability 'c' */
 +int server_supports_v2(const char *c, int die_on_error)
 +{
 +      int i;
 +
 +      for (i = 0; i < server_capabilities_v2.argc; i++) {
 +              const char *out;
 +              if (skip_prefix(server_capabilities_v2.argv[i], c, &out) &&
 +                  (!*out || *out == '='))
 +                      return 1;
 +      }
 +
 +      if (die_on_error)
 +              die("server doesn't support '%s'", c);
 +
 +      return 0;
 +}
 +
 +int server_supports_feature(const char *c, const char *feature,
 +                          int die_on_error)
 +{
 +      int i;
 +
 +      for (i = 0; i < server_capabilities_v2.argc; i++) {
 +              const char *out;
 +              if (skip_prefix(server_capabilities_v2.argv[i], c, &out) &&
 +                  (!*out || *(out++) == '=')) {
 +                      if (parse_feature_request(out, feature))
 +                              return 1;
 +                      else
 +                              break;
 +              }
 +      }
 +
 +      if (die_on_error)
 +              die("server doesn't support feature '%s'", feature);
 +
 +      return 0;
 +}
 +
 +static void process_capabilities_v2(struct packet_reader *reader)
 +{
 +      while (packet_reader_read(reader) == PACKET_READ_NORMAL)
 +              argv_array_push(&server_capabilities_v2, reader->line);
 +
 +      if (reader->status != PACKET_READ_FLUSH)
 +              die("expected flush after capabilities");
 +}
 +
 +enum protocol_version discover_version(struct packet_reader *reader)
 +{
 +      enum protocol_version version = protocol_unknown_version;
 +
 +      /*
 +       * Peek the first line of the server's response to
 +       * determine the protocol version the server is speaking.
 +       */
 +      switch (packet_reader_peek(reader)) {
 +      case PACKET_READ_EOF:
 +              die_initial_contact(0);
 +      case PACKET_READ_FLUSH:
 +      case PACKET_READ_DELIM:
 +              version = protocol_v0;
 +              break;
 +      case PACKET_READ_NORMAL:
 +              version = determine_protocol_version_client(reader->line);
 +              break;
 +      }
 +
 +      switch (version) {
 +      case protocol_v2:
 +              process_capabilities_v2(reader);
 +              break;
 +      case protocol_v1:
 +              /* Read the peeked version line */
 +              packet_reader_read(reader);
 +              break;
 +      case protocol_v0:
 +              break;
 +      case protocol_unknown_version:
 +              BUG("unknown protocol version");
 +      }
 +
 +      return version;
 +}
 +
  static void parse_one_symref_info(struct string_list *symref, const char *val, int len)
  {
        char *sym, *target;
@@@ -179,7 -86,7 +180,7 @@@ reject
  static void annotate_refs_with_symref_info(struct ref *ref)
  {
        struct string_list symref = STRING_LIST_INIT_DUP;
 -      const char *feature_list = server_capabilities;
 +      const char *feature_list = server_capabilities_v1;
  
        while (feature_list) {
                int len;
        string_list_clear(&symref, 0);
  }
  
 -/*
 - * Read one line of a server's ref advertisement into packet_buffer.
 - */
 -static int read_remote_ref(int in, char **src_buf, size_t *src_len,
 -                         int *responded)
 -{
 -      int len = packet_read(in, src_buf, src_len,
 -                            packet_buffer, sizeof(packet_buffer),
 -                            PACKET_READ_GENTLE_ON_EOF |
 -                            PACKET_READ_CHOMP_NEWLINE);
 -      const char *arg;
 -      if (len < 0)
 -              die_initial_contact(*responded);
 -      if (len > 4 && skip_prefix(packet_buffer, "ERR ", &arg))
 -              die("remote error: %s", arg);
 -
 -      *responded = 1;
 -
 -      return len;
 -}
 -
 -#define EXPECTING_PROTOCOL_VERSION 0
 -#define EXPECTING_FIRST_REF 1
 -#define EXPECTING_REF 2
 -#define EXPECTING_SHALLOW 3
 -
 -/* Returns 1 if packet_buffer is a protocol version pkt-line, 0 otherwise. */
 -static int process_protocol_version(void)
 +static void process_capabilities(const char *line, int *len)
  {
 -      switch (determine_protocol_version_client(packet_buffer)) {
 -      case protocol_v1:
 -              return 1;
 -      case protocol_v0:
 -              return 0;
 -      default:
 -              die("server is speaking an unknown protocol");
 -      }
 -}
 -
 -static void process_capabilities(int *len)
 -{
 -      int nul_location = strlen(packet_buffer);
 +      int nul_location = strlen(line);
        if (nul_location == *len)
                return;
 -      server_capabilities = xstrdup(packet_buffer + nul_location + 1);
 +      server_capabilities_v1 = xstrdup(line + nul_location + 1);
        *len = nul_location;
  }
  
 -static int process_dummy_ref(void)
 +static int process_dummy_ref(const char *line)
  {
        struct object_id oid;
        const char *name;
  
 -      if (parse_oid_hex(packet_buffer, &oid, &name))
 +      if (parse_oid_hex(line, &oid, &name))
                return 0;
        if (*name != ' ')
                return 0;
        return !oidcmp(&null_oid, &oid) && !strcmp(name, "capabilities^{}");
  }
  
 -static void check_no_capabilities(int len)
 +static void check_no_capabilities(const char *line, int len)
  {
 -      if (strlen(packet_buffer) != len)
 +      if (strlen(line) != len)
                warning("Ignoring capabilities after first line '%s'",
 -                      packet_buffer + strlen(packet_buffer));
 +                      line + strlen(line));
  }
  
 -static int process_ref(int len, struct ref ***list, unsigned int flags,
 -                     struct oid_array *extra_have)
 +static int process_ref(const char *line, int len, struct ref ***list,
 +                     unsigned int flags, struct oid_array *extra_have)
  {
        struct object_id old_oid;
        const char *name;
  
 -      if (parse_oid_hex(packet_buffer, &old_oid, &name))
 +      if (parse_oid_hex(line, &old_oid, &name))
                return 0;
        if (*name != ' ')
                return 0;
                **list = ref;
                *list = &ref->next;
        }
 -      check_no_capabilities(len);
 +      check_no_capabilities(line, len);
        return 1;
  }
  
 -static int process_shallow(int len, struct oid_array *shallow_points)
 +static int process_shallow(const char *line, int len,
 +                         struct oid_array *shallow_points)
  {
        const char *arg;
        struct object_id old_oid;
  
 -      if (!skip_prefix(packet_buffer, "shallow ", &arg))
 +      if (!skip_prefix(line, "shallow ", &arg))
                return 0;
  
        if (get_oid_hex(arg, &old_oid))
        if (!shallow_points)
                die("repository on the other end cannot be shallow");
        oid_array_append(shallow_points, &old_oid);
 -      check_no_capabilities(len);
 +      check_no_capabilities(line, len);
        return 1;
  }
  
 +enum get_remote_heads_state {
 +      EXPECTING_FIRST_REF = 0,
 +      EXPECTING_REF,
 +      EXPECTING_SHALLOW,
 +      EXPECTING_DONE,
 +};
 +
  /*
   * Read all the refs from the other end
   */
 -struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
 +struct ref **get_remote_heads(struct packet_reader *reader,
                              struct ref **list, unsigned int flags,
                              struct oid_array *extra_have,
                              struct oid_array *shallow_points)
  {
        struct ref **orig_list = list;
 -
 -      /*
 -       * A hang-up after seeing some response from the other end
 -       * means that it is unexpected, as we know the other end is
 -       * willing to talk to us.  A hang-up before seeing any
 -       * response does not necessarily mean an ACL problem, though.
 -       */
 -      int responded = 0;
 -      int len;
 -      int state = EXPECTING_PROTOCOL_VERSION;
 +      int len = 0;
 +      enum get_remote_heads_state state = EXPECTING_FIRST_REF;
 +      const char *arg;
  
        *list = NULL;
  
 -      while ((len = read_remote_ref(in, &src_buf, &src_len, &responded))) {
 +      while (state != EXPECTING_DONE) {
 +              switch (packet_reader_read(reader)) {
 +              case PACKET_READ_EOF:
 +                      die_initial_contact(1);
 +              case PACKET_READ_NORMAL:
 +                      len = reader->pktlen;
 +                      if (len > 4 && skip_prefix(reader->line, "ERR ", &arg))
 +                              die("remote error: %s", arg);
 +                      break;
 +              case PACKET_READ_FLUSH:
 +                      state = EXPECTING_DONE;
 +                      break;
 +              case PACKET_READ_DELIM:
 +                      die("invalid packet");
 +              }
 +
                switch (state) {
 -              case EXPECTING_PROTOCOL_VERSION:
 -                      if (process_protocol_version()) {
 -                              state = EXPECTING_FIRST_REF;
 -                              break;
 -                      }
 -                      state = EXPECTING_FIRST_REF;
 -                      /* fallthrough */
                case EXPECTING_FIRST_REF:
 -                      process_capabilities(&len);
 -                      if (process_dummy_ref()) {
 +                      process_capabilities(reader->line, &len);
 +                      if (process_dummy_ref(reader->line)) {
                                state = EXPECTING_SHALLOW;
                                break;
                        }
                        state = EXPECTING_REF;
                        /* fallthrough */
                case EXPECTING_REF:
 -                      if (process_ref(len, &list, flags, extra_have))
 +                      if (process_ref(reader->line, len, &list, flags, extra_have))
                                break;
                        state = EXPECTING_SHALLOW;
                        /* fallthrough */
                case EXPECTING_SHALLOW:
 -                      if (process_shallow(len, shallow_points))
 +                      if (process_shallow(reader->line, len, shallow_points))
                                break;
 -                      die("protocol error: unexpected '%s'", packet_buffer);
 -              default:
 -                      die("unexpected state %d", state);
 +                      die("protocol error: unexpected '%s'", reader->line);
 +              case EXPECTING_DONE:
 +                      break;
                }
        }
  
        return list;
  }
  
 +/* Returns 1 when a valid ref has been added to `list`, 0 otherwise */
 +static int process_ref_v2(const char *line, struct ref ***list)
 +{
 +      int ret = 1;
 +      int i = 0;
 +      struct object_id old_oid;
 +      struct ref *ref;
 +      struct string_list line_sections = STRING_LIST_INIT_DUP;
 +      const char *end;
 +
 +      /*
 +       * Ref lines have a number of fields which are space deliminated.  The
 +       * first field is the OID of the ref.  The second field is the ref
 +       * name.  Subsequent fields (symref-target and peeled) are optional and
 +       * don't have a particular order.
 +       */
 +      if (string_list_split(&line_sections, line, ' ', -1) < 2) {
 +              ret = 0;
 +              goto out;
 +      }
 +
 +      if (parse_oid_hex(line_sections.items[i++].string, &old_oid, &end) ||
 +          *end) {
 +              ret = 0;
 +              goto out;
 +      }
 +
 +      ref = alloc_ref(line_sections.items[i++].string);
 +
 +      oidcpy(&ref->old_oid, &old_oid);
 +      **list = ref;
 +      *list = &ref->next;
 +
 +      for (; i < line_sections.nr; i++) {
 +              const char *arg = line_sections.items[i].string;
 +              if (skip_prefix(arg, "symref-target:", &arg))
 +                      ref->symref = xstrdup(arg);
 +
 +              if (skip_prefix(arg, "peeled:", &arg)) {
 +                      struct object_id peeled_oid;
 +                      char *peeled_name;
 +                      struct ref *peeled;
 +                      if (parse_oid_hex(arg, &peeled_oid, &end) || *end) {
 +                              ret = 0;
 +                              goto out;
 +                      }
 +
 +                      peeled_name = xstrfmt("%s^{}", ref->name);
 +                      peeled = alloc_ref(peeled_name);
 +
 +                      oidcpy(&peeled->old_oid, &peeled_oid);
 +                      **list = peeled;
 +                      *list = &peeled->next;
 +
 +                      free(peeled_name);
 +              }
 +      }
 +
 +out:
 +      string_list_clear(&line_sections, 0);
 +      return ret;
 +}
 +
 +struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
 +                           struct ref **list, int for_push,
 +                           const struct argv_array *ref_prefixes,
 +                           const struct string_list *server_options)
 +{
 +      int i;
 +      *list = NULL;
 +
 +      if (server_supports_v2("ls-refs", 1))
 +              packet_write_fmt(fd_out, "command=ls-refs\n");
 +
 +      if (server_supports_v2("agent", 0))
 +              packet_write_fmt(fd_out, "agent=%s", git_user_agent_sanitized());
 +
 +      if (server_options && server_options->nr &&
 +          server_supports_v2("server-option", 1))
 +              for (i = 0; i < server_options->nr; i++)
 +                      packet_write_fmt(fd_out, "server-option=%s",
 +                                       server_options->items[i].string);
 +
 +      packet_delim(fd_out);
 +      /* When pushing we don't want to request the peeled tags */
 +      if (!for_push)
 +              packet_write_fmt(fd_out, "peel\n");
 +      packet_write_fmt(fd_out, "symrefs\n");
 +      for (i = 0; ref_prefixes && i < ref_prefixes->argc; i++) {
 +              packet_write_fmt(fd_out, "ref-prefix %s\n",
 +                               ref_prefixes->argv[i]);
 +      }
 +      packet_flush(fd_out);
 +
 +      /* Process response from server */
 +      while (packet_reader_read(reader) == PACKET_READ_NORMAL) {
 +              if (!process_ref_v2(reader->line, &list))
 +                      die("invalid ls-refs response: %s", reader->line);
 +      }
 +
 +      if (reader->status != PACKET_READ_FLUSH)
 +              die("expected flush after ref listing");
 +
 +      return list;
 +}
 +
  static const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp)
  {
        int len;
@@@ -493,7 -324,7 +494,7 @@@ int parse_feature_request(const char *f
  
  const char *server_feature_value(const char *feature, int *len)
  {
 -      return parse_feature_value(server_capabilities, feature, len);
 +      return parse_feature_value(server_capabilities_v1, feature, len);
  }
  
  int server_supports(const char *feature)
@@@ -1042,7 -873,6 +1043,7 @@@ static enum ssh_variant determine_ssh_v
   */
  static struct child_process *git_connect_git(int fd[2], char *hostandport,
                                             const char *path, const char *prog,
 +                                           enum protocol_version version,
                                             int flags)
  {
        struct child_process *conn;
                    target_host, 0);
  
        /* If using a new version put that stuff here after a second null byte */
 -      if (get_protocol_version_config() > 0) {
 +      if (version > 0) {
                strbuf_addch(&request, '\0');
                strbuf_addf(&request, "version=%d%c",
 -                          get_protocol_version_config(), '\0');
 +                          version, '\0');
        }
  
        packet_write(fd[1], request.buf, request.len);
   */
  static void push_ssh_options(struct argv_array *args, struct argv_array *env,
                             enum ssh_variant variant, const char *port,
 -                           int flags)
 +                           enum protocol_version version, int flags)
  {
        if (variant == VARIANT_SSH &&
 -          get_protocol_version_config() > 0) {
 +          version > 0) {
                argv_array_push(args, "-o");
                argv_array_push(args, "SendEnv=" GIT_PROTOCOL_ENVIRONMENT);
                argv_array_pushf(env, GIT_PROTOCOL_ENVIRONMENT "=version=%d",
 -                               get_protocol_version_config());
 +                               version);
        }
  
        if (flags & CONNECT_IPV4) {
  
  /* Prepare a child_process for use by Git's SSH-tunneled transport. */
  static void fill_ssh_args(struct child_process *conn, const char *ssh_host,
 -                        const char *port, int flags)
 +                        const char *port, enum protocol_version version,
 +                        int flags)
  {
        const char *ssh;
        enum ssh_variant variant;
                argv_array_push(&detect.args, ssh);
                argv_array_push(&detect.args, "-G");
                push_ssh_options(&detect.args, &detect.env_array,
 -                               VARIANT_SSH, port, flags);
 +                               VARIANT_SSH, port, version, flags);
                argv_array_push(&detect.args, ssh_host);
  
                variant = run_command(&detect) ? VARIANT_SIMPLE : VARIANT_SSH;
        }
  
        argv_array_push(&conn->args, ssh);
 -      push_ssh_options(&conn->args, &conn->env_array, variant, port, flags);
 +      push_ssh_options(&conn->args, &conn->env_array, variant, port, version, flags);
        argv_array_push(&conn->args, ssh_host);
  }
  
@@@ -1223,15 -1052,6 +1224,15 @@@ struct child_process *git_connect(int f
        char *hostandport, *path;
        struct child_process *conn;
        enum protocol protocol;
 +      enum protocol_version version = get_protocol_version_config();
 +
 +      /*
 +       * NEEDSWORK: If we are trying to use protocol v2 and we are planning
 +       * to perform a push, then fallback to v0 since the client doesn't know
 +       * how to push yet using v2.
 +       */
 +      if (version == protocol_v2 && !strcmp("git-receive-pack", prog))
 +              version = protocol_v0;
  
        /* Without this we cannot rely on waitpid() to tell
         * what happened to our children.
                printf("Diag: path=%s\n", path ? path : "NULL");
                conn = NULL;
        } else if (protocol == PROTO_GIT) {
 -              conn = git_connect_git(fd, hostandport, path, prog, flags);
 +              conn = git_connect_git(fd, hostandport, path, prog, version, flags);
        } else {
                struct strbuf cmd = STRBUF_INIT;
                const char *const *var;
                                strbuf_release(&cmd);
                                return NULL;
                        }
 -                      fill_ssh_args(conn, ssh_host, port, flags);
 +                      fill_ssh_args(conn, ssh_host, port, version, flags);
                } else {
                        transport_check_allowed("file");
 -                      if (get_protocol_version_config() > 0) {
 +                      if (version > 0) {
                                argv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT "=version=%d",
 -                                               get_protocol_version_config());
 +                                               version);
                        }
                }
                argv_array_push(&conn->args, cmd.buf);
index 1491b7239be11cd04c257aabc42944569b806f70,e5b2ccbdd2208069f25f2ce9505a2f88a8c8b5ff..12814e9bbf6be5ff0d1608c71554c2b4bb14c87d
@@@ -94,70 -94,6 +94,70 @@@ __git (
                ${__git_dir:+--git-dir="$__git_dir"} "$@" 2>/dev/null
  }
  
 +# Removes backslash escaping, single quotes and double quotes from a word,
 +# stores the result in the variable $dequoted_word.
 +# 1: The word to dequote.
 +__git_dequote ()
 +{
 +      local rest="$1" len ch
 +
 +      dequoted_word=""
 +
 +      while test -n "$rest"; do
 +              len=${#dequoted_word}
 +              dequoted_word="$dequoted_word${rest%%[\\\'\"]*}"
 +              rest="${rest:$((${#dequoted_word}-$len))}"
 +
 +              case "${rest:0:1}" in
 +              \\)
 +                      ch="${rest:1:1}"
 +                      case "$ch" in
 +                      $'\n')
 +                              ;;
 +                      *)
 +                              dequoted_word="$dequoted_word$ch"
 +                              ;;
 +                      esac
 +                      rest="${rest:2}"
 +                      ;;
 +              \')
 +                      rest="${rest:1}"
 +                      len=${#dequoted_word}
 +                      dequoted_word="$dequoted_word${rest%%\'*}"
 +                      rest="${rest:$((${#dequoted_word}-$len+1))}"
 +                      ;;
 +              \")
 +                      rest="${rest:1}"
 +                      while test -n "$rest" ; do
 +                              len=${#dequoted_word}
 +                              dequoted_word="$dequoted_word${rest%%[\\\"]*}"
 +                              rest="${rest:$((${#dequoted_word}-$len))}"
 +                              case "${rest:0:1}" in
 +                              \\)
 +                                      ch="${rest:1:1}"
 +                                      case "$ch" in
 +                                      \"|\\|\$|\`)
 +                                              dequoted_word="$dequoted_word$ch"
 +                                              ;;
 +                                      $'\n')
 +                                              ;;
 +                                      *)
 +                                              dequoted_word="$dequoted_word\\$ch"
 +                                              ;;
 +                                      esac
 +                                      rest="${rest:2}"
 +                                      ;;
 +                              \")
 +                                      rest="${rest:1}"
 +                                      break
 +                                      ;;
 +                              esac
 +                      done
 +                      ;;
 +              esac
 +      done
 +}
 +
  # The following function is based on code from:
  #
  #   bash_completion - programmable completion functions for bash 3.2+
@@@ -348,11 -284,7 +348,11 @@@ __gitcomp (
  
  # Clear the variables caching builtins' options when (re-)sourcing
  # the completion script.
 -unset $(set |sed -ne 's/^\(__gitcomp_builtin_[a-zA-Z0-9_][a-zA-Z0-9_]*\)=.*/\1/p') 2>/dev/null
 +if [[ -n ${ZSH_VERSION-} ]]; then
 +      unset $(set |sed -ne 's/^\(__gitcomp_builtin_[a-zA-Z0-9_][a-zA-Z0-9_]*\)=.*/\1/p') 2>/dev/null
 +else
 +      unset $(compgen -v __gitcomp_builtin_)
 +fi
  
  # This function is equivalent to
  #
@@@ -410,24 -342,6 +410,24 @@@ __gitcomp_nl (
        __gitcomp_nl_append "$@"
  }
  
 +# Fills the COMPREPLY array with prefiltered paths without any additional
 +# processing.
 +# Callers must take care of providing only paths that match the current path
 +# to be completed and adding any prefix path components, if necessary.
 +# 1: List of newline-separated matching paths, complete with all prefix
 +#    path componens.
 +__gitcomp_file_direct ()
 +{
 +      local IFS=$'\n'
 +
 +      COMPREPLY=($1)
 +
 +      # use a hack to enable file mode in bash < 4
 +      compopt -o filenames +o nospace 2>/dev/null ||
 +      compgen -f /non-existing-dir/ >/dev/null ||
 +      true
 +}
 +
  # Generates completion reply with compgen from newline-separated possible
  # completion filenames.
  # It accepts 1 to 3 arguments:
@@@ -447,8 -361,7 +447,8 @@@ __gitcomp_file (
  
        # use a hack to enable file mode in bash < 4
        compopt -o filenames +o nospace 2>/dev/null ||
 -      compgen -f /non-existing-dir/ > /dev/null
 +      compgen -f /non-existing-dir/ >/dev/null ||
 +      true
  }
  
  # Execute 'git ls-files', unless the --committable option is specified, in
  __git_ls_files_helper ()
  {
        if [ "$2" == "--committable" ]; then
 -              __git -C "$1" diff-index --name-only --relative HEAD
 +              __git -C "$1" -c core.quotePath=false diff-index \
 +                      --name-only --relative HEAD -- "${3//\\/\\\\}*"
        else
                # NOTE: $2 is not quoted in order to support multiple options
 -              __git -C "$1" ls-files --exclude-standard $2
 +              __git -C "$1" -c core.quotePath=false ls-files \
 +                      --exclude-standard $2 -- "${3//\\/\\\\}*"
        fi
  }
  
  #    If provided, only files within the specified directory are listed.
  #    Sub directories are never recursed.  Path must have a trailing
  #    slash.
 +# 3: List only paths matching this path component (optional).
  __git_index_files ()
  {
 -      local root="${2-.}" file
 +      local root="$2" match="$3"
  
 -      __git_ls_files_helper "$root" "$1" |
 -      while read -r file; do
 -              case "$file" in
 -              ?*/*) echo "${file%%/*}" ;;
 -              *) echo "$file" ;;
 -              esac
 -      done | sort | uniq
 +      __git_ls_files_helper "$root" "$1" "$match" |
 +      awk -F / -v pfx="${2//\\/\\\\}" '{
 +              paths[$1] = 1
 +      }
 +      END {
 +              for (p in paths) {
 +                      if (substr(p, 1, 1) != "\"") {
 +                              # No special characters, easy!
 +                              print pfx p
 +                              continue
 +                      }
 +
 +                      # The path is quoted.
 +                      p = dequote(p)
 +                      if (p == "")
 +                              continue
 +
 +                      # Even when a directory name itself does not contain
 +                      # any special characters, it will still be quoted if
 +                      # any of its (stripped) trailing path components do.
 +                      # Because of this we may have seen the same direcory
 +                      # both quoted and unquoted.
 +                      if (p in paths)
 +                              # We have seen the same directory unquoted,
 +                              # skip it.
 +                              continue
 +                      else
 +                              print pfx p
 +              }
 +      }
 +      function dequote(p,    bs_idx, out, esc, esc_idx, dec) {
 +              # Skip opening double quote.
 +              p = substr(p, 2)
 +
 +              # Interpret backslash escape sequences.
 +              while ((bs_idx = index(p, "\\")) != 0) {
 +                      out = out substr(p, 1, bs_idx - 1)
 +                      esc = substr(p, bs_idx + 1, 1)
 +                      p = substr(p, bs_idx + 2)
 +
 +                      if ((esc_idx = index("abtvfr\"\\", esc)) != 0) {
 +                              # C-style one-character escape sequence.
 +                              out = out substr("\a\b\t\v\f\r\"\\",
 +                                               esc_idx, 1)
 +                      } else if (esc == "n") {
 +                              # Uh-oh, a newline character.
 +                              # We cant reliably put a pathname
 +                              # containing a newline into COMPREPLY,
 +                              # and the newline would create a mess.
 +                              # Skip this path.
 +                              return ""
 +                      } else {
 +                              # Must be a \nnn octal value, then.
 +                              dec = esc             * 64 + \
 +                                    substr(p, 1, 1) * 8  + \
 +                                    substr(p, 2, 1)
 +                              out = out sprintf("%c", dec)
 +                              p = substr(p, 3)
 +                      }
 +              }
 +              # Drop closing double quote, if there is one.
 +              # (There isnt any if this is a directory, as it was
 +              # already stripped with the trailing path components.)
 +              if (substr(p, length(p), 1) == "\"")
 +                      out = out substr(p, 1, length(p) - 1)
 +              else
 +                      out = out p
 +
 +              return out
 +      }'
 +}
 +
 +# __git_complete_index_file requires 1 argument:
 +# 1: the options to pass to ls-file
 +#
 +# The exception is --committable, which finds the files appropriate commit.
 +__git_complete_index_file ()
 +{
 +      local dequoted_word pfx="" cur_
 +
 +      __git_dequote "$cur"
 +
 +      case "$dequoted_word" in
 +      ?*/*)
 +              pfx="${dequoted_word%/*}/"
 +              cur_="${dequoted_word##*/}"
 +              ;;
 +      *)
 +              cur_="$dequoted_word"
 +      esac
 +
 +      __gitcomp_file_direct "$(__git_index_files "$1" "$pfx" "$cur_")"
  }
  
  # Lists branches from the local repository.
@@@ -889,6 -714,26 +889,6 @@@ __git_complete_revlist_file (
        esac
  }
  
 -
 -# __git_complete_index_file requires 1 argument:
 -# 1: the options to pass to ls-file
 -#
 -# The exception is --committable, which finds the files appropriate commit.
 -__git_complete_index_file ()
 -{
 -      local pfx="" cur_="$cur"
 -
 -      case "$cur_" in
 -      ?*/*)
 -              pfx="${cur_%/*}"
 -              cur_="${cur_##*/}"
 -              pfx="${pfx}/"
 -              ;;
 -      esac
 -
 -      __gitcomp_file "$(__git_index_files "$1" ${pfx:+"$pfx"})" "$pfx" "$cur_"
 -}
 -
  __git_complete_file ()
  {
        __git_complete_revlist_file
@@@ -989,127 -834,11 +989,11 @@@ __git_complete_strategy (
        return 1
  }
  
- __git_commands () {
-       if test -n "${GIT_TESTING_COMMAND_COMPLETION:-}"
-       then
-               printf "%s" "${GIT_TESTING_COMMAND_COMPLETION}"
-       else
-               git help -a|egrep '^  [a-zA-Z0-9]'
-       fi
- }
- __git_list_all_commands ()
- {
-       local i IFS=" "$'\n'
-       for i in $(__git_commands)
-       do
-               case $i in
-               *--*)             : helper pattern;;
-               *) echo $i;;
-               esac
-       done
- }
  __git_all_commands=
  __git_compute_all_commands ()
  {
        test -n "$__git_all_commands" ||
-       __git_all_commands=$(__git_list_all_commands)
- }
- __git_list_porcelain_commands ()
- {
-       local i IFS=" "$'\n'
-       __git_compute_all_commands
-       for i in $__git_all_commands
-       do
-               case $i in
-               *--*)             : helper pattern;;
-               applymbox)        : ask gittus;;
-               applypatch)       : ask gittus;;
-               archimport)       : import;;
-               cat-file)         : plumbing;;
-               check-attr)       : plumbing;;
-               check-ignore)     : plumbing;;
-               check-mailmap)    : plumbing;;
-               check-ref-format) : plumbing;;
-               checkout-index)   : plumbing;;
-               column)           : internal helper;;
-               commit-graph)     : plumbing;;
-               commit-tree)      : plumbing;;
-               count-objects)    : infrequent;;
-               credential)       : credentials;;
-               credential-*)     : credentials helper;;
-               cvsexportcommit)  : export;;
-               cvsimport)        : import;;
-               cvsserver)        : daemon;;
-               daemon)           : daemon;;
-               diff-files)       : plumbing;;
-               diff-index)       : plumbing;;
-               diff-tree)        : plumbing;;
-               fast-import)      : import;;
-               fast-export)      : export;;
-               fsck-objects)     : plumbing;;
-               fetch-pack)       : plumbing;;
-               fmt-merge-msg)    : plumbing;;
-               for-each-ref)     : plumbing;;
-               hash-object)      : plumbing;;
-               http-*)           : transport;;
-               index-pack)       : plumbing;;
-               init-db)          : deprecated;;
-               local-fetch)      : plumbing;;
-               ls-files)         : plumbing;;
-               ls-remote)        : plumbing;;
-               ls-tree)          : plumbing;;
-               mailinfo)         : plumbing;;
-               mailsplit)        : plumbing;;
-               merge-*)          : plumbing;;
-               mktree)           : plumbing;;
-               mktag)            : plumbing;;
-               pack-objects)     : plumbing;;
-               pack-redundant)   : plumbing;;
-               pack-refs)        : plumbing;;
-               parse-remote)     : plumbing;;
-               patch-id)         : plumbing;;
-               prune)            : plumbing;;
-               prune-packed)     : plumbing;;
-               quiltimport)      : import;;
-               read-tree)        : plumbing;;
-               receive-pack)     : plumbing;;
-               remote-*)         : transport;;
-               rerere)           : plumbing;;
-               rev-list)         : plumbing;;
-               rev-parse)        : plumbing;;
-               runstatus)        : plumbing;;
-               sh-setup)         : internal;;
-               shell)            : daemon;;
-               show-ref)         : plumbing;;
-               send-pack)        : plumbing;;
-               show-index)       : plumbing;;
-               ssh-*)            : transport;;
-               stripspace)       : plumbing;;
-               symbolic-ref)     : plumbing;;
-               unpack-file)      : plumbing;;
-               unpack-objects)   : plumbing;;
-               update-index)     : plumbing;;
-               update-ref)       : plumbing;;
-               update-server-info) : daemon;;
-               upload-archive)   : plumbing;;
-               upload-pack)      : plumbing;;
-               write-tree)       : plumbing;;
-               var)              : infrequent;;
-               verify-pack)      : infrequent;;
-               verify-tag)       : plumbing;;
-               *) echo $i;;
-               esac
-       done
- }
- __git_porcelain_commands=
- __git_compute_porcelain_commands ()
- {
-       test -n "$__git_porcelain_commands" ||
-       __git_porcelain_commands=$(__git_list_porcelain_commands)
+       __git_all_commands=$(git --list-cmds=main,others,alias,nohelpers)
  }
  
  # Lists all set config variables starting with the given section prefix,
@@@ -1127,11 -856,6 +1011,6 @@@ __git_pretty_aliases (
        __git_get_config_variables "pretty"
  }
  
- __git_aliases ()
- {
-       __git_get_config_variables "alias"
- }
  # __git_aliased_command requires 1 argument
  __git_aliased_command ()
  {
@@@ -1739,13 -1463,12 +1618,12 @@@ _git_help (
                return
                ;;
        esac
-       __git_compute_all_commands
-       __gitcomp "$__git_all_commands $(__git_aliases)
-               attributes cli core-tutorial cvs-migration
-               diffcore everyday gitk glossary hooks ignore modules
-               namespaces repository-layout revisions tutorial tutorial-2
-               workflows
-               "
+       if test -n "$GIT_TESTING_ALL_COMMAND_LIST"
+       then
+               __gitcomp "$GIT_TESTING_ALL_COMMAND_LIST $(git --list-cmds=alias,list-guide) gitk"
+       else
+               __gitcomp "$(git --list-cmds=main,nohelpers,alias,list-guide) gitk"
+       fi
  }
  
  _git_init ()
@@@ -2105,7 -1828,7 +1983,7 @@@ _git_rebase (
        --*)
                __gitcomp "
                        --onto --merge --strategy --interactive
 -                      --preserve-merges --stat --no-stat
 +                      --rebase-merges --preserve-merges --stat --no-stat
                        --committer-date-is-author-date --ignore-date
                        --ignore-whitespace --whitespace=
                        --autosquash --no-autosquash
@@@ -2276,7 -1999,7 +2154,7 @@@ _git_config (
                return
                ;;
        branch.*.rebase)
 -              __gitcomp "false true preserve interactive"
 +              __gitcomp "false true merges preserve interactive"
                return
                ;;
        remote.pushdefault)
                __gitcomp "$__git_log_date_formats"
                return
                ;;
 -      sendemail.aliasesfiletype)
 +      sendemail.aliasfiletype)
                __gitcomp "mutt mailrc pine elm gnus"
                return
                ;;
                core.bigFileThreshold
                core.checkStat
                core.commentChar
 +              core.commitGraph
                core.compression
                core.createObject
                core.deltaBaseCacheLimit
@@@ -2931,21 -2653,13 +2809,21 @@@ _git_show_branch (
  _git_stash ()
  {
        local save_opts='--all --keep-index --no-keep-index --quiet --patch --include-untracked'
 -      local subcommands='push save list show apply clear drop pop create branch'
 -      local subcommand="$(__git_find_on_cmdline "$subcommands")"
 +      local subcommands='push list show apply clear drop pop create branch'
 +      local subcommand="$(__git_find_on_cmdline "$subcommands save")"
 +      if [ -n "$(__git_find_on_cmdline "-p")" ]; then
 +              subcommand="push"
 +      fi
        if [ -z "$subcommand" ]; then
                case "$cur" in
                --*)
                        __gitcomp "$save_opts"
                        ;;
 +              sa*)
 +                      if [ -z "$(__git_find_on_cmdline "$save_opts")" ]; then
 +                              __gitcomp "save"
 +                      fi
 +                      ;;
                *)
                        if [ -z "$(__git_find_on_cmdline "$save_opts")" ]; then
                                __gitcomp "$subcommands"
@@@ -3214,7 -2928,7 +3092,7 @@@ __git_complete_common () 
  __git_cmds_with_parseopt_helper=
  __git_support_parseopt_helper () {
        test -n "$__git_cmds_with_parseopt_helper" ||
-               __git_cmds_with_parseopt_helper="$(__git --list-parseopt-builtins)"
+               __git_cmds_with_parseopt_helper="$(__git --list-cmds=parseopt)"
  
        case " $__git_cmds_with_parseopt_helper " in
        *" $1 "*)
  __git_complete_command () {
        local command="$1"
        local completion_func="_git_${command//-/_}"
 -      if declare -f $completion_func >/dev/null 2>/dev/null; then
 +      if ! declare -f $completion_func >/dev/null 2>/dev/null &&
 +              declare -f _completion_loader >/dev/null 2>/dev/null
 +      then
 +              _completion_loader "git-$command"
 +      fi
 +      if declare -f $completion_func >/dev/null 2>/dev/null
 +      then
                $completion_func
                return 0
 -      elif __git_support_parseopt_helper "$command"; then
 +      elif __git_support_parseopt_helper "$command"
 +      then
                __git_complete_common "$command"
                return 0
        else
@@@ -3300,8 -3007,14 +3178,14 @@@ __git_main (
                        --help
                        "
                        ;;
-               *)     __git_compute_porcelain_commands
-                      __gitcomp "$__git_porcelain_commands $(__git_aliases)" ;;
+               *)
+                       if test -n "$GIT_TESTING_PORCELAIN_COMMAND_LIST"
+                       then
+                               __gitcomp "$GIT_TESTING_PORCELAIN_COMMAND_LIST"
+                       else
+                               __gitcomp "$(git --list-cmds=list-mainporcelain,others,nohelpers,alias,list-complete,config)"
+                       fi
+                       ;;
                esac
                return
        fi
@@@ -3388,15 -3101,6 +3272,15 @@@ if [[ -n ${ZSH_VERSION-} ]]; the
                compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0
        }
  
 +      __gitcomp_file_direct ()
 +      {
 +              emulate -L zsh
 +
 +              local IFS=$'\n'
 +              compset -P '*[=:]'
 +              compadd -Q -f -- ${=1} && _ret=0
 +      }
 +
        __gitcomp_file ()
        {
                emulate -L zsh
diff --combined git.c
index 5771d62a328d69692291674c7ed0f9050efac3ca,447dac0e71e6f3dc6680dcabc31210c6a967b814..c2f48d53dd4aaba0752aa5f2e6633242d320b248
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -1,8 -1,9 +1,9 @@@
  #include "builtin.h"
  #include "config.h"
 -#include "exec_cmd.h"
 +#include "exec-cmd.h"
  #include "help.h"
  #include "run-command.h"
+ #include "alias.h"
  
  #define RUN_SETUP             (1<<0)
  #define RUN_SETUP_GENTLY      (1<<1)
@@@ -25,7 -26,7 +26,7 @@@ struct cmd_struct 
  const char git_usage_string[] =
        N_("git [--version] [--help] [-C <path>] [-c <name>=<value>]\n"
           "           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n"
 -         "           [-p | --paginate | --no-pager] [--no-replace-objects] [--bare]\n"
 +         "           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]\n"
           "           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n"
           "           <command> [<args>]");
  
@@@ -36,7 -37,66 +37,66 @@@ const char git_more_info_string[] 
  
  static int use_pager = -1;
  
- static void list_builtins(unsigned int exclude_option, char sep);
+ static void list_builtins(struct string_list *list, unsigned int exclude_option);
+ static void exclude_helpers_from_list(struct string_list *list)
+ {
+       int i = 0;
+       while (i < list->nr) {
+               if (strstr(list->items[i].string, "--"))
+                       unsorted_string_list_delete_item(list, i, 0);
+               else
+                       i++;
+       }
+ }
+ static int match_token(const char *spec, int len, const char *token)
+ {
+       int token_len = strlen(token);
+       return len == token_len && !strncmp(spec, token, token_len);
+ }
+ static int list_cmds(const char *spec)
+ {
+       struct string_list list = STRING_LIST_INIT_DUP;
+       int i;
+       while (*spec) {
+               const char *sep = strchrnul(spec, ',');
+               int len = sep - spec;
+               if (match_token(spec, len, "builtins"))
+                       list_builtins(&list, 0);
+               else if (match_token(spec, len, "main"))
+                       list_all_main_cmds(&list);
+               else if (match_token(spec, len, "others"))
+                       list_all_other_cmds(&list);
+               else if (match_token(spec, len, "nohelpers"))
+                       exclude_helpers_from_list(&list);
+               else if (match_token(spec, len, "alias"))
+                       list_aliases(&list);
+               else if (match_token(spec, len, "config"))
+                       list_cmds_by_config(&list);
+               else if (len > 5 && !strncmp(spec, "list-", 5)) {
+                       struct strbuf sb = STRBUF_INIT;
+                       strbuf_add(&sb, spec + 5, len - 5);
+                       list_cmds_by_category(&list, sb.buf);
+                       strbuf_release(&sb);
+               }
+               else
+                       die(_("unsupported command listing type '%s'"), spec);
+               spec += len;
+               if (*spec == ',')
+                       spec++;
+       }
+       for (i = 0; i < list.nr; i++)
+               puts(list.items[i].string);
+       string_list_clear(&list, 0);
+       return 0;
+ }
  
  static void commit_pager_choice(void) {
        switch (use_pager) {
@@@ -83,7 -143,7 +143,7 @@@ static int handle_options(const char **
                 */
                if (skip_prefix(cmd, "--exec-path", &cmd)) {
                        if (*cmd == '=')
 -                              git_set_argv_exec_path(cmd + 1);
 +                              git_set_exec_path(cmd + 1);
                        else {
                                puts(git_exec_path());
                                exit(0);
                        exit(0);
                } else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) {
                        use_pager = 1;
 -              } else if (!strcmp(cmd, "--no-pager")) {
 +              } else if (!strcmp(cmd, "-P") || !strcmp(cmd, "--no-pager")) {
                        use_pager = 0;
                        if (envchanged)
                                *envchanged = 1;
                        }
                        (*argv)++;
                        (*argc)--;
-               } else if (!strcmp(cmd, "--list-builtins")) {
-                       list_builtins(0, '\n');
-                       exit(0);
-               } else if (!strcmp(cmd, "--list-parseopt-builtins")) {
-                       list_builtins(NO_PARSEOPT, ' ');
-                       exit(0);
+               } else if (skip_prefix(cmd, "--list-cmds=", &cmd)) {
+                       if (!strcmp(cmd, "parseopt")) {
+                               struct string_list list = STRING_LIST_INIT_DUP;
+                               int i;
+                               list_builtins(&list, NO_PARSEOPT);
+                               for (i = 0; i < list.nr; i++)
+                                       printf("%s ", list.items[i].string);
+                               string_list_clear(&list, 0);
+                               exit(0);
+                       } else {
+                               exit(list_cmds(cmd));
+                       }
                } else {
                        fprintf(stderr, _("unknown option: %s\n"), cmd);
                        usage(git_usage_string);
@@@ -392,7 -459,6 +459,7 @@@ static struct cmd_struct commands[] = 
        { "clone", cmd_clone },
        { "column", cmd_column, RUN_SETUP_GENTLY },
        { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
 +      { "commit-graph", cmd_commit_graph, RUN_SETUP },
        { "commit-tree", cmd_commit_tree, RUN_SETUP | NO_PARSEOPT },
        { "config", cmd_config, RUN_SETUP_GENTLY | DELAY_PAGER_CONFIG },
        { "count-objects", cmd_count_objects, RUN_SETUP },
        { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
        { "rm", cmd_rm, RUN_SETUP },
        { "send-pack", cmd_send_pack, RUN_SETUP },
 +      { "serve", cmd_serve, RUN_SETUP },
        { "shortlog", cmd_shortlog, RUN_SETUP_GENTLY | USE_PAGER },
        { "show", cmd_show, RUN_SETUP },
        { "show-branch", cmd_show_branch, RUN_SETUP },
        { "update-server-info", cmd_update_server_info, RUN_SETUP },
        { "upload-archive", cmd_upload_archive, NO_PARSEOPT },
        { "upload-archive--writer", cmd_upload_archive_writer, NO_PARSEOPT },
 +      { "upload-pack", cmd_upload_pack },
        { "var", cmd_var, RUN_SETUP_GENTLY | NO_PARSEOPT },
        { "verify-commit", cmd_verify_commit, RUN_SETUP },
        { "verify-pack", cmd_verify_pack },
@@@ -511,14 -575,14 +578,14 @@@ int is_builtin(const char *s
        return !!get_builtin(s);
  }
  
- static void list_builtins(unsigned int exclude_option, char sep)
+ static void list_builtins(struct string_list *out, unsigned int exclude_option)
  {
        int i;
        for (i = 0; i < ARRAY_SIZE(commands); i++) {
                if (exclude_option &&
                    (commands[i].option & exclude_option))
                        continue;
-               printf("%s%c", commands[i].cmd, sep);
+               string_list_append(out, commands[i].cmd);
        }
  }
  
diff --combined help.c
index a4feef2ffe90ae853c220b9368642c4edb503e5e,abf87205b2ea2f13925348fa1a42ac0aebbb1ac4..dd35fcc133094e4dc89898463868e89162637076
--- 1/help.c
--- 2/help.c
+++ b/help.c
  #include "cache.h"
  #include "config.h"
  #include "builtin.h"
 -#include "exec_cmd.h"
 +#include "exec-cmd.h"
  #include "run-command.h"
  #include "levenshtein.h"
  #include "help.h"
- #include "common-cmds.h"
+ #include "command-list.h"
  #include "string-list.h"
  #include "column.h"
  #include "version.h"
  #include "refs.h"
  #include "parse-options.h"
  
+ struct category_description {
+       uint32_t category;
+       const char *desc;
+ };
+ static uint32_t common_mask =
+       CAT_init | CAT_worktree | CAT_info |
+       CAT_history | CAT_remote;
+ static struct category_description common_categories[] = {
+       { CAT_init, N_("start a working area (see also: git help tutorial)") },
+       { CAT_worktree, N_("work on the current change (see also: git help everyday)") },
+       { CAT_info, N_("examine the history and state (see also: git help revisions)") },
+       { CAT_history, N_("grow, mark and tweak your common history") },
+       { CAT_remote, N_("collaborate (see also: git help workflows)") },
+       { 0, NULL }
+ };
+ static struct category_description main_categories[] = {
+       { CAT_mainporcelain, N_("Main Porcelain Commands") },
+       { CAT_ancillarymanipulators, N_("Ancillary Commands / Manipulators") },
+       { CAT_ancillaryinterrogators, N_("Ancillary Commands / Interrogators") },
+       { CAT_foreignscminterface, N_("Interacting with Others") },
+       { CAT_plumbingmanipulators, N_("Low-level Commands / Manipulators") },
+       { CAT_plumbinginterrogators, N_("Low-level Commands / Interrogators") },
+       { CAT_synchingrepositories, N_("Low-level Commands / Synching Repositories") },
+       { CAT_purehelpers, N_("Low-level Commands / Internal Helpers") },
+       { 0, NULL }
+ };
+ static const char *drop_prefix(const char *name, uint32_t category)
+ {
+       const char *new_name;
+       if (skip_prefix(name, "git-", &new_name))
+               return new_name;
+       if (category == CAT_guide && skip_prefix(name, "git", &new_name))
+               return new_name;
+       return name;
+ }
+ static void extract_cmds(struct cmdname_help **p_cmds, uint32_t mask)
+ {
+       int i, nr = 0;
+       struct cmdname_help *cmds;
+       if (ARRAY_SIZE(command_list) == 0)
+               BUG("empty command_list[] is a sign of broken generate-cmdlist.sh");
+       ALLOC_ARRAY(cmds, ARRAY_SIZE(command_list) + 1);
+       for (i = 0; i < ARRAY_SIZE(command_list); i++) {
+               const struct cmdname_help *cmd = command_list + i;
+               if (!(cmd->category & mask))
+                       continue;
+               cmds[nr] = *cmd;
+               cmds[nr].name = drop_prefix(cmd->name, cmd->category);
+               nr++;
+       }
+       cmds[nr].name = NULL;
+       *p_cmds = cmds;
+ }
+ static void print_command_list(const struct cmdname_help *cmds,
+                              uint32_t mask, int longest)
+ {
+       int i;
+       for (i = 0; cmds[i].name; i++) {
+               if (cmds[i].category & mask) {
+                       printf("   %s   ", cmds[i].name);
+                       mput_char(' ', longest - strlen(cmds[i].name));
+                       puts(_(cmds[i].help));
+               }
+       }
+ }
+ static int cmd_name_cmp(const void *elem1, const void *elem2)
+ {
+       const struct cmdname_help *e1 = elem1;
+       const struct cmdname_help *e2 = elem2;
+       return strcmp(e1->name, e2->name);
+ }
+ static void print_cmd_by_category(const struct category_description *catdesc)
+ {
+       struct cmdname_help *cmds;
+       int longest = 0;
+       int i, nr = 0;
+       uint32_t mask = 0;
+       for (i = 0; catdesc[i].desc; i++)
+               mask |= catdesc[i].category;
+       extract_cmds(&cmds, mask);
+       for (i = 0; cmds[i].name; i++, nr++) {
+               if (longest < strlen(cmds[i].name))
+                       longest = strlen(cmds[i].name);
+       }
+       QSORT(cmds, nr, cmd_name_cmp);
+       for (i = 0; catdesc[i].desc; i++) {
+               uint32_t mask = catdesc[i].category;
+               const char *desc = catdesc[i].desc;
+               printf("\n%s\n", _(desc));
+               print_command_list(cmds, mask, longest);
+       }
+       free(cmds);
+ }
  void add_cmdname(struct cmdnames *cmds, const char *name, int len)
  {
        struct cmdname *ent;
@@@ -190,44 -304,116 +304,116 @@@ void list_commands(unsigned int colopts
        }
  }
  
static int cmd_group_cmp(const void *elem1, const void *elem2)
void list_common_cmds_help(void)
  {
-       const struct cmdname_help *e1 = elem1;
-       const struct cmdname_help *e2 = elem2;
+       puts(_("These are common Git commands used in various situations:"));
+       print_cmd_by_category(common_categories);
+ }
  
-       if (e1->group < e2->group)
-               return -1;
-       if (e1->group > e2->group)
-               return 1;
-       return strcmp(e1->name, e2->name);
+ void list_all_main_cmds(struct string_list *list)
+ {
+       struct cmdnames main_cmds, other_cmds;
+       int i;
+       memset(&main_cmds, 0, sizeof(main_cmds));
+       memset(&other_cmds, 0, sizeof(other_cmds));
+       load_command_list("git-", &main_cmds, &other_cmds);
+       for (i = 0; i < main_cmds.cnt; i++)
+               string_list_append(list, main_cmds.names[i]->name);
+       clean_cmdnames(&main_cmds);
+       clean_cmdnames(&other_cmds);
  }
  
- void list_common_cmds_help(void)
+ void list_all_other_cmds(struct string_list *list)
  {
-       int i, longest = 0;
-       int current_grp = -1;
+       struct cmdnames main_cmds, other_cmds;
+       int i;
  
-       for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
-               if (longest < strlen(common_cmds[i].name))
-                       longest = strlen(common_cmds[i].name);
-       }
+       memset(&main_cmds, 0, sizeof(main_cmds));
+       memset(&other_cmds, 0, sizeof(other_cmds));
+       load_command_list("git-", &main_cmds, &other_cmds);
  
-       QSORT(common_cmds, ARRAY_SIZE(common_cmds), cmd_group_cmp);
+       for (i = 0; i < other_cmds.cnt; i++)
+               string_list_append(list, other_cmds.names[i]->name);
  
-       puts(_("These are common Git commands used in various situations:"));
+       clean_cmdnames(&main_cmds);
+       clean_cmdnames(&other_cmds);
+ }
+ void list_cmds_by_category(struct string_list *list,
+                          const char *cat)
+ {
+       int i, n = ARRAY_SIZE(command_list);
+       uint32_t cat_id = 0;
  
-       for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
-               if (common_cmds[i].group != current_grp) {
-                       printf("\n%s\n", _(common_cmd_groups[common_cmds[i].group]));
-                       current_grp = common_cmds[i].group;
+       for (i = 0; category_names[i]; i++) {
+               if (!strcmp(cat, category_names[i])) {
+                       cat_id = 1UL << i;
+                       break;
                }
+       }
+       if (!cat_id)
+               die(_("unsupported command listing type '%s'"), cat);
+       for (i = 0; i < n; i++) {
+               struct cmdname_help *cmd = command_list + i;
+               if (!(cmd->category & cat_id))
+                       continue;
+               string_list_append(list, drop_prefix(cmd->name, cmd->category));
+       }
+ }
+ void list_cmds_by_config(struct string_list *list)
+ {
+       const char *cmd_list;
+       /*
+        * There's no actual repository setup at this point (and even
+        * if there is, we don't really care; only global config
+        * matters). If we accidentally set up a repository, it's ok
+        * too since the caller (git --list-cmds=) should exit shortly
+        * anyway.
+        */
+       if (git_config_get_string_const("completion.commands", &cmd_list))
+               return;
+       string_list_sort(list);
+       string_list_remove_duplicates(list, 0);
+       while (*cmd_list) {
+               struct strbuf sb = STRBUF_INIT;
+               const char *p = strchrnul(cmd_list, ' ');
  
-               printf("   %s   ", common_cmds[i].name);
-               mput_char(' ', longest - strlen(common_cmds[i].name));
-               puts(_(common_cmds[i].help));
+               strbuf_add(&sb, cmd_list, p - cmd_list);
+               if (*cmd_list == '-')
+                       string_list_remove(list, cmd_list + 1, 0);
+               else
+                       string_list_insert(list, sb.buf);
+               strbuf_release(&sb);
+               while (*p == ' ')
+                       p++;
+               cmd_list = p;
        }
  }
  
+ void list_common_guides_help(void)
+ {
+       struct category_description catdesc[] = {
+               { CAT_guide, N_("The common Git guides are:") },
+               { 0, NULL }
+       };
+       print_cmd_by_category(catdesc);
+       putchar('\n');
+ }
+ void list_all_cmds_help(void)
+ {
+       print_cmd_by_category(main_categories);
+ }
  int is_in_cmdlist(struct cmdnames *c, const char *s)
  {
        int i;
@@@ -285,6 -471,7 +471,7 @@@ const char *help_unknown_cmd(const cha
  {
        int i, n, best_similarity = 0;
        struct cmdnames main_cmds, other_cmds;
+       struct cmdname_help *common_cmds;
  
        memset(&main_cmds, 0, sizeof(main_cmds));
        memset(&other_cmds, 0, sizeof(other_cmds));
        QSORT(main_cmds.names, main_cmds.cnt, cmdname_compare);
        uniq(&main_cmds);
  
+       extract_cmds(&common_cmds, common_mask);
        /* This abuses cmdname->len for levenshtein distance */
        for (i = 0, n = 0; i < main_cmds.cnt; i++) {
                int cmp = 0; /* avoid compiler stupidity */
                        die(_(bad_interpreter_advice), cmd, cmd);
  
                /* Does the candidate appear in common_cmds list? */
-               while (n < ARRAY_SIZE(common_cmds) &&
+               while (common_cmds[n].name &&
                       (cmp = strcmp(common_cmds[n].name, candidate)) < 0)
                        n++;
-               if ((n < ARRAY_SIZE(common_cmds)) && !cmp) {
+               if (common_cmds[n].name && !cmp) {
                        /* Yes, this is one of the common commands */
                        n++; /* use the entry from common_cmds[] */
                        if (starts_with(candidate, cmd)) {
                main_cmds.names[i]->len =
                        levenshtein(cmd, candidate, 0, 2, 1, 3) + 1;
        }
+       FREE_AND_NULL(common_cmds);
  
        QSORT(main_cmds.names, main_cmds.cnt, levenshtein_compare);
  
diff --combined pager.c
index 226828f178a0c1a876710326634e83288863af3d,1f4688fa030756c403568441719ab383a86b4c00..a768797fcfcc44de4dbe4d983a45ade8c81b2e1c
+++ b/pager.c
@@@ -2,6 -2,7 +2,7 @@@
  #include "config.h"
  #include "run-command.h"
  #include "sigchain.h"
+ #include "alias.h"
  
  #ifndef DEFAULT_PAGER
  #define DEFAULT_PAGER "less"
@@@ -109,15 -110,10 +110,15 @@@ void setup_pager(void
                return;
  
        /*
 -       * force computing the width of the terminal before we redirect
 -       * the standard output to the pager.
 +       * After we redirect standard output, we won't be able to use an ioctl
 +       * to get the terminal size. Let's grab it now, and then set $COLUMNS
 +       * to communicate it to any sub-processes.
         */
 -      (void) term_columns();
 +      {
 +              char buf[64];
 +              xsnprintf(buf, sizeof(buf), "%d", term_columns());
 +              setenv("COLUMNS", buf, 0);
 +      }
  
        setenv("GIT_PAGER_IN_USE", "true", 1);
  
diff --combined sequencer.c
index 72b4d8ecae3b07e2259cf05c4a8f4acdb46eae0a,1288a36ebd6eeca2529313333f311dcff3dee017..560fc9b67d480069328d379421c606d800e82135
@@@ -7,7 -7,7 +7,7 @@@
  #include "sequencer.h"
  #include "tag.h"
  #include "run-command.h"
 -#include "exec_cmd.h"
 +#include "exec-cmd.h"
  #include "utf8.h"
  #include "cache-tree.h"
  #include "diff.h"
  #include "hashmap.h"
  #include "notes-utils.h"
  #include "sigchain.h"
 +#include "unpack-trees.h"
 +#include "worktree.h"
 +#include "oidmap.h"
 +#include "oidset.h"
+ #include "alias.h"
  
  #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
  
@@@ -78,6 -75,13 +79,6 @@@ static GIT_PATH_FUNC(rebase_path_messag
   * previous commit and from the first squash/fixup commit are written
   * to it. The commit message for each subsequent squash/fixup commit
   * is appended to the file as it is processed.
 - *
 - * The first line of the file is of the form
 - *     # This is a combination of $count commits.
 - * where $count is the number of commits whose messages have been
 - * written to the file so far (including the initial "pick" commit).
 - * Each time that a commit message is processed, this line is read and
 - * updated. It is deleted just before the combined commit is made.
   */
  static GIT_PATH_FUNC(rebase_path_squash_msg, "rebase-merge/message-squash")
  /*
   * commit without opening the editor.)
   */
  static GIT_PATH_FUNC(rebase_path_fixup_msg, "rebase-merge/message-fixup")
 +/*
 + * This file contains the list fixup/squash commands that have been
 + * accumulated into message-fixup or message-squash so far.
 + */
 +static GIT_PATH_FUNC(rebase_path_current_fixups, "rebase-merge/current-fixups")
  /*
   * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
   * GIT_AUTHOR_DATE that will be used for the commit that is currently
@@@ -122,19 -121,6 +123,19 @@@ static GIT_PATH_FUNC(rebase_path_stoppe
  static GIT_PATH_FUNC(rebase_path_rewritten_list, "rebase-merge/rewritten-list")
  static GIT_PATH_FUNC(rebase_path_rewritten_pending,
        "rebase-merge/rewritten-pending")
 +
 +/*
 + * The path of the file containig the OID of the "squash onto" commit, i.e.
 + * the dummy commit used for `reset [new root]`.
 + */
 +static GIT_PATH_FUNC(rebase_path_squash_onto, "rebase-merge/squash-onto")
 +
 +/*
 + * The path of the file listing refs that need to be deleted after the rebase
 + * finishes. This is used by the `label` command to record the need for cleanup.
 + */
 +static GIT_PATH_FUNC(rebase_path_refs_to_delete, "rebase-merge/refs-to-delete")
 +
  /*
   * The following files are written by git-rebase just after parsing the
   * command-line (and are only consumed, not modified, by the sequencer).
  static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt")
  static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head")
  static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose")
 +static GIT_PATH_FUNC(rebase_path_signoff, "rebase-merge/signoff")
  static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name")
  static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto")
  static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash")
@@@ -260,35 -245,18 +261,35 @@@ static const char *gpg_sign_opt_quoted(
  
  int sequencer_remove_state(struct replay_opts *opts)
  {
 -      struct strbuf dir = STRBUF_INIT;
 +      struct strbuf buf = STRBUF_INIT;
        int i;
  
 +      if (is_rebase_i(opts) &&
 +          strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {
 +              char *p = buf.buf;
 +              while (*p) {
 +                      char *eol = strchr(p, '\n');
 +                      if (eol)
 +                              *eol = '\0';
 +                      if (delete_ref("(rebase -i) cleanup", p, NULL, 0) < 0)
 +                              warning(_("could not delete '%s'"), p);
 +                      if (!eol)
 +                              break;
 +                      p = eol + 1;
 +              }
 +      }
 +
        free(opts->gpg_sign);
        free(opts->strategy);
        for (i = 0; i < opts->xopts_nr; i++)
                free(opts->xopts[i]);
        free(opts->xopts);
 +      strbuf_release(&opts->current_fixups);
  
 -      strbuf_addstr(&dir, get_dir(opts));
 -      remove_dir_recursively(&dir, 0);
 -      strbuf_release(&dir);
 +      strbuf_reset(&buf);
 +      strbuf_addstr(&buf, get_dir(opts));
 +      remove_dir_recursively(&buf, 0);
 +      strbuf_release(&buf);
  
        return 0;
  }
@@@ -378,14 -346,12 +379,14 @@@ static int write_message(const void *bu
        if (msg_fd < 0)
                return error_errno(_("could not lock '%s'"), filename);
        if (write_in_full(msg_fd, buf, len) < 0) {
 +              error_errno(_("could not write to '%s'"), filename);
                rollback_lock_file(&msg_file);
 -              return error_errno(_("could not write to '%s'"), filename);
 +              return -1;
        }
        if (append_eol && write(msg_fd, "\n", 1) < 0) {
 +              error_errno(_("could not write eol to '%s'"), filename);
                rollback_lock_file(&msg_file);
 -              return error_errno(_("could not write eol to '%s'"), filename);
 +              return -1;
        }
        if (commit_lock_file(&msg_file) < 0)
                return error(_("failed to finalize '%s'"), filename);
@@@ -475,8 -441,7 +476,8 @@@ static int fast_forward_to(const struc
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
            ref_transaction_update(transaction, "HEAD",
 -                                 to, unborn ? &null_oid : from,
 +                                 to, unborn && !is_rebase_i(opts) ?
 +                                 &null_oid : from,
                                   0, sb.buf, &err) ||
            ref_transaction_commit(transaction, &err)) {
                ref_transaction_free(transaction);
@@@ -535,8 -500,8 +536,8 @@@ static int do_recursive_merge(struct co
        o.show_rename_progress = 1;
  
        head_tree = parse_tree_indirect(head);
 -      next_tree = next ? next->tree : empty_tree();
 -      base_tree = base ? base->tree : empty_tree();
 +      next_tree = next ? get_commit_tree(next) : empty_tree();
 +      base_tree = base ? get_commit_tree(base) : empty_tree();
  
        for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++)
                parse_merge_opt(&o, *xopt);
        return !clean;
  }
  
 +static struct object_id *get_cache_tree_oid(void)
 +{
 +      if (!active_cache_tree)
 +              active_cache_tree = cache_tree();
 +
 +      if (!cache_tree_fully_valid(active_cache_tree))
 +              if (cache_tree_update(&the_index, 0)) {
 +                      error(_("unable to update cache tree"));
 +                      return NULL;
 +              }
 +
 +      return &active_cache_tree->oid;
 +}
 +
  static int is_index_unchanged(void)
  {
 -      struct object_id head_oid;
 +      struct object_id head_oid, *cache_tree_oid;
        struct commit *head_commit;
  
        if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
        if (parse_commit(head_commit))
                return -1;
  
 -      if (!active_cache_tree)
 -              active_cache_tree = cache_tree();
 -
 -      if (!cache_tree_fully_valid(active_cache_tree))
 -              if (cache_tree_update(&the_index, 0))
 -                      return error(_("unable to update cache tree"));
 +      if (!(cache_tree_oid = get_cache_tree_oid()))
 +              return -1;
  
 -      return !oidcmp(&active_cache_tree->oid,
 -                     &head_commit->tree->object.oid);
 +      return !oidcmp(cache_tree_oid, get_commit_tree_oid(head_commit));
  }
  
  static int write_author_script(const char *message)
@@@ -698,52 -654,6 +699,52 @@@ static char *get_author(const char *mes
        return NULL;
  }
  
 +/* Read author-script and return an ident line (author <email> timestamp) */
 +static const char *read_author_ident(struct strbuf *buf)
 +{
 +      const char *keys[] = {
 +              "GIT_AUTHOR_NAME=", "GIT_AUTHOR_EMAIL=", "GIT_AUTHOR_DATE="
 +      };
 +      char *in, *out, *eol;
 +      int i = 0, len;
 +
 +      if (strbuf_read_file(buf, rebase_path_author_script(), 256) <= 0)
 +              return NULL;
 +
 +      /* dequote values and construct ident line in-place */
 +      for (in = out = buf->buf; i < 3 && in - buf->buf < buf->len; i++) {
 +              if (!skip_prefix(in, keys[i], (const char **)&in)) {
 +                      warning("could not parse '%s' (looking for '%s'",
 +                              rebase_path_author_script(), keys[i]);
 +                      return NULL;
 +              }
 +
 +              eol = strchrnul(in, '\n');
 +              *eol = '\0';
 +              sq_dequote(in);
 +              len = strlen(in);
 +
 +              if (i > 0) /* separate values by spaces */
 +                      *(out++) = ' ';
 +              if (i == 1) /* email needs to be surrounded by <...> */
 +                      *(out++) = '<';
 +              memmove(out, in, len);
 +              out += len;
 +              if (i == 1) /* email needs to be surrounded by <...> */
 +                      *(out++) = '>';
 +              in = eol + 1;
 +      }
 +
 +      if (i < 3) {
 +              warning("could not parse '%s' (looking for '%s')",
 +                      rebase_path_author_script(), keys[i]);
 +              return NULL;
 +      }
 +
 +      buf->len = out - buf->buf;
 +      return buf->buf;
 +}
 +
  static const char staged_changes_advice[] =
  N_("you have staged changes in your working tree\n"
  "If these changes are meant to be squashed into the previous commit, run:\n"
  #define AMEND_MSG   (1<<2)
  #define CLEANUP_MSG (1<<3)
  #define VERIFY_MSG  (1<<4)
 +#define CREATE_ROOT_COMMIT (1<<5)
  
  /*
   * If we are cherry-pick, and if the merge did not result in
@@@ -783,40 -692,6 +784,40 @@@ static int run_git_commit(const char *d
        struct child_process cmd = CHILD_PROCESS_INIT;
        const char *value;
  
 +      if (flags & CREATE_ROOT_COMMIT) {
 +              struct strbuf msg = STRBUF_INIT, script = STRBUF_INIT;
 +              const char *author = is_rebase_i(opts) ?
 +                      read_author_ident(&script) : NULL;
 +              struct object_id root_commit, *cache_tree_oid;
 +              int res = 0;
 +
 +              if (!defmsg)
 +                      BUG("root commit without message");
 +
 +              if (!(cache_tree_oid = get_cache_tree_oid()))
 +                      res = -1;
 +
 +              if (!res)
 +                      res = strbuf_read_file(&msg, defmsg, 0);
 +
 +              if (res <= 0)
 +                      res = error_errno(_("could not read '%s'"), defmsg);
 +              else
 +                      res = commit_tree(msg.buf, msg.len, cache_tree_oid,
 +                                        NULL, &root_commit, author,
 +                                        opts->gpg_sign);
 +
 +              strbuf_release(&msg);
 +              strbuf_release(&script);
 +              if (!res) {
 +                      update_ref(NULL, "CHERRY_PICK_HEAD", &root_commit, NULL,
 +                                 REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR);
 +                      res = update_ref(NULL, "HEAD", &root_commit, NULL, 0,
 +                                       UPDATE_REFS_MSG_ON_ERR);
 +              }
 +              return res < 0 ? error(_("writing root commit")) : 0;
 +      }
 +
        cmd.git_cmd = 1;
  
        if (is_rebase_i(opts)) {
                argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
        if (defmsg)
                argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
 +      else if (!(flags & EDIT_MSG))
 +              argv_array_pushl(&cmd.args, "-C", "HEAD", NULL);
        if ((flags & CLEANUP_MSG))
                argv_array_push(&cmd.args, "--cleanup=strip");
        if ((flags & EDIT_MSG))
@@@ -1246,8 -1119,8 +1247,8 @@@ static int try_to_commit(struct strbuf 
        }
  
        if (!(flags & ALLOW_EMPTY) && !oidcmp(current_head ?
 -                                            &current_head->tree->object.oid :
 -                                            &empty_tree_oid, &tree)) {
 +                                            get_commit_tree_oid(current_head) :
 +                                            the_hash_algo->empty_tree, &tree)) {
                res = 1; /* run 'git commit' to display error message */
                goto out;
        }
                goto out;
        }
  
 +      reset_ident_date();
 +
        if (commit_tree_extended(msg->buf, msg->len, &tree, parents,
                                 oid, author, opts->gpg_sign, extra)) {
                res = error(_("failed to write commit object"));
@@@ -1307,8 -1178,7 +1308,8 @@@ static int do_commit(const char *msg_fi
  {
        int res = 1;
  
 -      if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG)) {
 +      if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG) &&
 +          !(flags & CREATE_ROOT_COMMIT)) {
                struct object_id oid;
                struct strbuf sb = STRBUF_INIT;
  
@@@ -1347,12 -1217,12 +1348,12 @@@ static int is_original_commit_empty(str
                if (parse_commit(parent))
                        return error(_("could not parse parent commit %s"),
                                oid_to_hex(&parent->object.oid));
 -              ptree_oid = &parent->tree->object.oid;
 +              ptree_oid = get_commit_tree_oid(parent);
        } else {
                ptree_oid = the_hash_algo->empty_tree; /* commit is root */
        }
  
 -      return !oidcmp(ptree_oid, &commit->tree->object.oid);
 +      return !oidcmp(ptree_oid, get_commit_tree_oid(commit));
  }
  
  /*
@@@ -1408,9 -1278,6 +1409,9 @@@ enum todo_command 
        TODO_SQUASH,
        /* commands that do something else than handling a single commit */
        TODO_EXEC,
 +      TODO_LABEL,
 +      TODO_RESET,
 +      TODO_MERGE,
        /* commands that do nothing but are counted for reporting progress */
        TODO_NOOP,
        TODO_DROP,
@@@ -1429,9 -1296,6 +1430,9 @@@ static struct 
        { 'f', "fixup" },
        { 's', "squash" },
        { 'x', "exec" },
 +      { 'l', "label" },
 +      { 't', "reset" },
 +      { 'm', "merge" },
        { 0,   "noop" },
        { 'd', "drop" },
        { 0,   NULL }
@@@ -1461,43 -1325,38 +1462,43 @@@ static int is_fixup(enum todo_command c
        return command == TODO_FIXUP || command == TODO_SQUASH;
  }
  
 +/* Does this command create a (non-merge) commit? */
 +static int is_pick_or_similar(enum todo_command command)
 +{
 +      switch (command) {
 +      case TODO_PICK:
 +      case TODO_REVERT:
 +      case TODO_EDIT:
 +      case TODO_REWORD:
 +      case TODO_FIXUP:
 +      case TODO_SQUASH:
 +              return 1;
 +      default:
 +              return 0;
 +      }
 +}
 +
  static int update_squash_messages(enum todo_command command,
                struct commit *commit, struct replay_opts *opts)
  {
        struct strbuf buf = STRBUF_INIT;
 -      int count, res;
 +      int res;
        const char *message, *body;
  
 -      if (file_exists(rebase_path_squash_msg())) {
 +      if (opts->current_fixup_count > 0) {
                struct strbuf header = STRBUF_INIT;
 -              char *eol, *p;
 +              char *eol;
  
 -              if (strbuf_read_file(&buf, rebase_path_squash_msg(), 2048) <= 0)
 +              if (strbuf_read_file(&buf, rebase_path_squash_msg(), 9) <= 0)
                        return error(_("could not read '%s'"),
                                rebase_path_squash_msg());
  
 -              p = buf.buf + 1;
 -              eol = strchrnul(buf.buf, '\n');
 -              if (buf.buf[0] != comment_line_char ||
 -                  (p += strcspn(p, "0123456789\n")) == eol)
 -                      return error(_("unexpected 1st line of squash message:"
 -                                     "\n\n\t%.*s"),
 -                                   (int)(eol - buf.buf), buf.buf);
 -              count = strtol(p, NULL, 10);
 -
 -              if (count < 1)
 -                      return error(_("invalid 1st line of squash message:\n"
 -                                     "\n\t%.*s"),
 -                                   (int)(eol - buf.buf), buf.buf);
 +              eol = buf.buf[0] != comment_line_char ?
 +                      buf.buf : strchrnul(buf.buf, '\n');
  
                strbuf_addf(&header, "%c ", comment_line_char);
 -              strbuf_addf(&header,
 -                          _("This is a combination of %d commits."), ++count);
 +              strbuf_addf(&header, _("This is a combination of %d commits."),
 +                          opts->current_fixup_count + 2);
                strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
                strbuf_release(&header);
        } else {
                                     rebase_path_fixup_msg());
                }
  
 -              count = 2;
                strbuf_addf(&buf, "%c ", comment_line_char);
 -              strbuf_addf(&buf, _("This is a combination of %d commits."),
 -                          count);
 +              strbuf_addf(&buf, _("This is a combination of %d commits."), 2);
                strbuf_addf(&buf, "\n%c ", comment_line_char);
                strbuf_addstr(&buf, _("This is the 1st commit message:"));
                strbuf_addstr(&buf, "\n\n");
        if (command == TODO_SQUASH) {
                unlink(rebase_path_fixup_msg());
                strbuf_addf(&buf, "\n%c ", comment_line_char);
 -              strbuf_addf(&buf, _("This is the commit message #%d:"), count);
 +              strbuf_addf(&buf, _("This is the commit message #%d:"),
 +                          ++opts->current_fixup_count);
                strbuf_addstr(&buf, "\n\n");
                strbuf_addstr(&buf, body);
        } else if (command == TODO_FIXUP) {
                strbuf_addf(&buf, "\n%c ", comment_line_char);
                strbuf_addf(&buf, _("The commit message #%d will be skipped:"),
 -                          count);
 +                          ++opts->current_fixup_count);
                strbuf_addstr(&buf, "\n\n");
                strbuf_add_commented_lines(&buf, body, strlen(body));
        } else
  
        res = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0);
        strbuf_release(&buf);
 +
 +      if (!res) {
 +              strbuf_addf(&opts->current_fixups, "%s%s %s",
 +                          opts->current_fixups.len ? "\n" : "",
 +                          command_to_string(command),
 +                          oid_to_hex(&commit->object.oid));
 +              res = write_message(opts->current_fixups.buf,
 +                                  opts->current_fixups.len,
 +                                  rebase_path_current_fixups(), 0);
 +      }
 +
        return res;
  }
  
@@@ -1630,16 -1479,9 +1631,16 @@@ static int do_pick_commit(enum todo_com
                        return error(_("your index file is unmerged."));
        } else {
                unborn = get_oid("HEAD", &head);
 -              if (unborn)
 +              /* Do we want to generate a root commit? */
 +              if (is_pick_or_similar(command) && opts->have_squash_onto &&
 +                  !oidcmp(&head, &opts->squash_onto)) {
 +                      if (is_fixup(command))
 +                              return error(_("cannot fixup root commit"));
 +                      flags |= CREATE_ROOT_COMMIT;
 +                      unborn = 1;
 +              } else if (unborn)
                        oidcpy(&head, the_hash_algo->empty_tree);
 -              if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD",
 +              if (index_differs_from(unborn ? empty_tree_oid_hex() : "HEAD",
                                       NULL, 0))
                        return error_dirty_index(opts);
        }
                }
        }
  
 -      if (opts->signoff)
 +      if (opts->signoff && !is_fixup(command))
                append_signoff(&msgbuf, 0, 0);
  
        if (is_rebase_i(opts) && write_author_script(msg.message) < 0)
@@@ -1834,9 -1676,6 +1835,9 @@@ fast_forward_edit
        if (!res && final_fixup) {
                unlink(rebase_path_fixup_msg());
                unlink(rebase_path_squash_msg());
 +              unlink(rebase_path_current_fixups());
 +              strbuf_reset(&opts->current_fixups);
 +              opts->current_fixup_count = 0;
        }
  
  leave:
@@@ -1884,14 -1723,9 +1885,14 @@@ static int read_and_refresh_cache(struc
        return 0;
  }
  
 +enum todo_item_flags {
 +      TODO_EDIT_MERGE_MSG = 1
 +};
 +
  struct todo_item {
        enum todo_command command;
        struct commit *commit;
 +      unsigned int flags;
        const char *arg;
        int arg_len;
        size_t offset_in_buf;
@@@ -1926,8 -1760,6 +1927,8 @@@ static int parse_insn_line(struct todo_
        char *end_of_object_name;
        int i, saved, status, padding;
  
 +      item->flags = 0;
 +
        /* left-trim */
        bol += strspn(bol, " \t");
  
                return error(_("missing arguments for %s"),
                             command_to_string(item->command));
  
 -      if (item->command == TODO_EXEC) {
 +      if (item->command == TODO_EXEC || item->command == TODO_LABEL ||
 +          item->command == TODO_RESET) {
                item->commit = NULL;
                item->arg = bol;
                item->arg_len = (int)(eol - bol);
                return 0;
        }
  
 +      if (item->command == TODO_MERGE) {
 +              if (skip_prefix(bol, "-C", &bol))
 +                      bol += strspn(bol, " \t");
 +              else if (skip_prefix(bol, "-c", &bol)) {
 +                      bol += strspn(bol, " \t");
 +                      item->flags |= TODO_EDIT_MERGE_MSG;
 +              } else {
 +                      item->flags |= TODO_EDIT_MERGE_MSG;
 +                      item->commit = NULL;
 +                      item->arg = bol;
 +                      item->arg_len = (int)(eol - bol);
 +                      return 0;
 +              }
 +      }
 +
        end_of_object_name = (char *) bol + strcspn(bol, " \t\n");
        saved = *end_of_object_name;
        *end_of_object_name = '\0';
@@@ -2053,23 -1869,6 +2054,23 @@@ static int count_commands(struct todo_l
        return count;
  }
  
 +static int get_item_line_offset(struct todo_list *todo_list, int index)
 +{
 +      return index < todo_list->nr ?
 +              todo_list->items[index].offset_in_buf : todo_list->buf.len;
 +}
 +
 +static const char *get_item_line(struct todo_list *todo_list, int index)
 +{
 +      return todo_list->buf.buf + get_item_line_offset(todo_list, index);
 +}
 +
 +static int get_item_line_length(struct todo_list *todo_list, int index)
 +{
 +      return get_item_line_offset(todo_list, index + 1)
 +              -  get_item_line_offset(todo_list, index);
 +}
 +
  static ssize_t strbuf_read_file_or_whine(struct strbuf *sb, const char *path)
  {
        int fd;
@@@ -2245,30 -2044,9 +2246,30 @@@ static int read_populate_opts(struct re
                if (file_exists(rebase_path_verbose()))
                        opts->verbose = 1;
  
 +              if (file_exists(rebase_path_signoff())) {
 +                      opts->allow_ff = 0;
 +                      opts->signoff = 1;
 +              }
 +
                read_strategy_opts(opts, &buf);
                strbuf_release(&buf);
  
 +              if (read_oneliner(&opts->current_fixups,
 +                                rebase_path_current_fixups(), 1)) {
 +                      const char *p = opts->current_fixups.buf;
 +                      opts->current_fixup_count = 1;
 +                      while ((p = strchr(p, '\n'))) {
 +                              opts->current_fixup_count++;
 +                              p++;
 +                      }
 +              }
 +
 +              if (read_oneliner(&buf, rebase_path_squash_onto(), 0)) {
 +                      if (get_oid_hex(buf.buf, &opts->squash_onto) < 0)
 +                              return error(_("unusable squash-onto"));
 +                      opts->have_squash_onto = 1;
 +              }
 +
                return 0;
        }
  
@@@ -2342,9 -2120,9 +2343,9 @@@ static int save_head(const char *head
        written = write_in_full(fd, buf.buf, buf.len);
        strbuf_release(&buf);
        if (written < 0) {
 +              error_errno(_("could not write to '%s'"), git_path_head_file());
                rollback_lock_file(&head_lock);
 -              return error_errno(_("could not write to '%s'"),
 -                                 git_path_head_file());
 +              return -1;
        }
        if (commit_lock_file(&head_lock) < 0)
                return error(_("failed to finalize '%s'"), git_path_head_file());
@@@ -2465,27 -2243,29 +2466,27 @@@ static int save_todo(struct todo_list *
        fd = hold_lock_file_for_update(&todo_lock, todo_path, 0);
        if (fd < 0)
                return error_errno(_("could not lock '%s'"), todo_path);
 -      offset = next < todo_list->nr ?
 -              todo_list->items[next].offset_in_buf : todo_list->buf.len;
 +      offset = get_item_line_offset(todo_list, next);
        if (write_in_full(fd, todo_list->buf.buf + offset,
                        todo_list->buf.len - offset) < 0)
                return error_errno(_("could not write to '%s'"), todo_path);
        if (commit_lock_file(&todo_lock) < 0)
                return error(_("failed to finalize '%s'"), todo_path);
  
 -      if (is_rebase_i(opts)) {
 -              const char *done_path = rebase_path_done();
 -              int fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);
 -              int prev_offset = !next ? 0 :
 -                      todo_list->items[next - 1].offset_in_buf;
 +      if (is_rebase_i(opts) && next > 0) {
 +              const char *done = rebase_path_done();
 +              int fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666);
 +              int ret = 0;
  
 -              if (fd >= 0 && offset > prev_offset &&
 -                  write_in_full(fd, todo_list->buf.buf + prev_offset,
 -                                offset - prev_offset) < 0) {
 -                      close(fd);
 -                      return error_errno(_("could not write to '%s'"),
 -                                         done_path);
 -              }
 -              if (fd >= 0)
 -                      close(fd);
 +              if (fd < 0)
 +                      return 0;
 +              if (write_in_full(fd, get_item_line(todo_list, next - 1),
 +                                get_item_line_length(todo_list, next - 1))
 +                  < 0)
 +                      ret = error_errno(_("could not write to '%s'"), done);
 +              if (close(fd) < 0)
 +                      ret = error_errno(_("failed to finalize '%s'"), done);
 +              return ret;
        }
        return 0;
  }
@@@ -2613,9 -2393,10 +2614,9 @@@ static int error_with_patch(struct comm
  static int error_failed_squash(struct commit *commit,
        struct replay_opts *opts, int subject_len, const char *subject)
  {
 -      if (rename(rebase_path_squash_msg(), rebase_path_message()))
 -              return error(_("could not rename '%s' to '%s'"),
 +      if (copy_file(rebase_path_message(), rebase_path_squash_msg(), 0666))
 +              return error(_("could not copy '%s' to '%s'"),
                        rebase_path_squash_msg(), rebase_path_message());
 -      unlink(rebase_path_fixup_msg());
        unlink(git_path_merge_msg());
        if (copy_file(git_path_merge_msg(), rebase_path_message(), 0666))
                return error(_("could not copy '%s' to '%s'"),
@@@ -2668,377 -2449,6 +2669,377 @@@ static int do_exec(const char *command_
        return status;
  }
  
 +static int safe_append(const char *filename, const char *fmt, ...)
 +{
 +      va_list ap;
 +      struct lock_file lock = LOCK_INIT;
 +      int fd = hold_lock_file_for_update(&lock, filename,
 +                                         LOCK_REPORT_ON_ERROR);
 +      struct strbuf buf = STRBUF_INIT;
 +
 +      if (fd < 0)
 +              return -1;
 +
 +      if (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT) {
 +              error_errno(_("could not read '%s'"), filename);
 +              rollback_lock_file(&lock);
 +              return -1;
 +      }
 +      strbuf_complete(&buf, '\n');
 +      va_start(ap, fmt);
 +      strbuf_vaddf(&buf, fmt, ap);
 +      va_end(ap);
 +
 +      if (write_in_full(fd, buf.buf, buf.len) < 0) {
 +              error_errno(_("could not write to '%s'"), filename);
 +              strbuf_release(&buf);
 +              rollback_lock_file(&lock);
 +              return -1;
 +      }
 +      if (commit_lock_file(&lock) < 0) {
 +              strbuf_release(&buf);
 +              rollback_lock_file(&lock);
 +              return error(_("failed to finalize '%s'"), filename);
 +      }
 +
 +      strbuf_release(&buf);
 +      return 0;
 +}
 +
 +static int do_label(const char *name, int len)
 +{
 +      struct ref_store *refs = get_main_ref_store(the_repository);
 +      struct ref_transaction *transaction;
 +      struct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;
 +      struct strbuf msg = STRBUF_INIT;
 +      int ret = 0;
 +      struct object_id head_oid;
 +
 +      if (len == 1 && *name == '#')
 +              return error("Illegal label name: '%.*s'", len, name);
 +
 +      strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
 +      strbuf_addf(&msg, "rebase -i (label) '%.*s'", len, name);
 +
 +      transaction = ref_store_transaction_begin(refs, &err);
 +      if (!transaction) {
 +              error("%s", err.buf);
 +              ret = -1;
 +      } else if (get_oid("HEAD", &head_oid)) {
 +              error(_("could not read HEAD"));
 +              ret = -1;
 +      } else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,
 +                                        NULL, 0, msg.buf, &err) < 0 ||
 +                 ref_transaction_commit(transaction, &err)) {
 +              error("%s", err.buf);
 +              ret = -1;
 +      }
 +      ref_transaction_free(transaction);
 +      strbuf_release(&err);
 +      strbuf_release(&msg);
 +
 +      if (!ret)
 +              ret = safe_append(rebase_path_refs_to_delete(),
 +                                "%s\n", ref_name.buf);
 +      strbuf_release(&ref_name);
 +
 +      return ret;
 +}
 +
 +static const char *reflog_message(struct replay_opts *opts,
 +      const char *sub_action, const char *fmt, ...);
 +
 +static int do_reset(const char *name, int len, struct replay_opts *opts)
 +{
 +      struct strbuf ref_name = STRBUF_INIT;
 +      struct object_id oid;
 +      struct lock_file lock = LOCK_INIT;
 +      struct tree_desc desc;
 +      struct tree *tree;
 +      struct unpack_trees_options unpack_tree_opts;
 +      int ret = 0, i;
 +
 +      if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)
 +              return -1;
 +
 +      if (len == 10 && !strncmp("[new root]", name, len)) {
 +              if (!opts->have_squash_onto) {
 +                      const char *hex;
 +                      if (commit_tree("", 0, the_hash_algo->empty_tree,
 +                                      NULL, &opts->squash_onto,
 +                                      NULL, NULL))
 +                              return error(_("writing fake root commit"));
 +                      opts->have_squash_onto = 1;
 +                      hex = oid_to_hex(&opts->squash_onto);
 +                      if (write_message(hex, strlen(hex),
 +                                        rebase_path_squash_onto(), 0))
 +                              return error(_("writing squash-onto"));
 +              }
 +              oidcpy(&oid, &opts->squash_onto);
 +      } else {
 +              /* Determine the length of the label */
 +              for (i = 0; i < len; i++)
 +                      if (isspace(name[i]))
 +                              len = i;
 +
 +              strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
 +              if (get_oid(ref_name.buf, &oid) &&
 +                  get_oid(ref_name.buf + strlen("refs/rewritten/"), &oid)) {
 +                      error(_("could not read '%s'"), ref_name.buf);
 +                      rollback_lock_file(&lock);
 +                      strbuf_release(&ref_name);
 +                      return -1;
 +              }
 +      }
 +
 +      memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
 +      setup_unpack_trees_porcelain(&unpack_tree_opts, "reset");
 +      unpack_tree_opts.head_idx = 1;
 +      unpack_tree_opts.src_index = &the_index;
 +      unpack_tree_opts.dst_index = &the_index;
 +      unpack_tree_opts.fn = oneway_merge;
 +      unpack_tree_opts.merge = 1;
 +      unpack_tree_opts.update = 1;
 +
 +      if (read_cache_unmerged()) {
 +              rollback_lock_file(&lock);
 +              strbuf_release(&ref_name);
 +              return error_resolve_conflict(_(action_name(opts)));
 +      }
 +
 +      if (!fill_tree_descriptor(&desc, &oid)) {
 +              error(_("failed to find tree of %s"), oid_to_hex(&oid));
 +              rollback_lock_file(&lock);
 +              free((void *)desc.buffer);
 +              strbuf_release(&ref_name);
 +              return -1;
 +      }
 +
 +      if (unpack_trees(1, &desc, &unpack_tree_opts)) {
 +              rollback_lock_file(&lock);
 +              free((void *)desc.buffer);
 +              strbuf_release(&ref_name);
 +              return -1;
 +      }
 +
 +      tree = parse_tree_indirect(&oid);
 +      prime_cache_tree(&the_index, tree);
 +
 +      if (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)
 +              ret = error(_("could not write index"));
 +      free((void *)desc.buffer);
 +
 +      if (!ret)
 +              ret = update_ref(reflog_message(opts, "reset", "'%.*s'",
 +                                              len, name), "HEAD", &oid,
 +                               NULL, 0, UPDATE_REFS_MSG_ON_ERR);
 +
 +      strbuf_release(&ref_name);
 +      return ret;
 +}
 +
 +static int do_merge(struct commit *commit, const char *arg, int arg_len,
 +                  int flags, struct replay_opts *opts)
 +{
 +      int run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?
 +              EDIT_MSG | VERIFY_MSG : 0;
 +      struct strbuf ref_name = STRBUF_INIT;
 +      struct commit *head_commit, *merge_commit, *i;
 +      struct commit_list *bases, *j, *reversed = NULL;
 +      struct merge_options o;
 +      int merge_arg_len, oneline_offset, can_fast_forward, ret;
 +      static struct lock_file lock;
 +      const char *p;
 +
 +      if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) {
 +              ret = -1;
 +              goto leave_merge;
 +      }
 +
 +      head_commit = lookup_commit_reference_by_name("HEAD");
 +      if (!head_commit) {
 +              ret = error(_("cannot merge without a current revision"));
 +              goto leave_merge;
 +      }
 +
 +      oneline_offset = arg_len;
 +      merge_arg_len = strcspn(arg, " \t\n");
 +      p = arg + merge_arg_len;
 +      p += strspn(p, " \t\n");
 +      if (*p == '#' && (!p[1] || isspace(p[1]))) {
 +              p += 1 + strspn(p + 1, " \t\n");
 +              oneline_offset = p - arg;
 +      } else if (p - arg < arg_len)
 +              BUG("octopus merges are not supported yet: '%s'", p);
 +
 +      strbuf_addf(&ref_name, "refs/rewritten/%.*s", merge_arg_len, arg);
 +      merge_commit = lookup_commit_reference_by_name(ref_name.buf);
 +      if (!merge_commit) {
 +              /* fall back to non-rewritten ref or commit */
 +              strbuf_splice(&ref_name, 0, strlen("refs/rewritten/"), "", 0);
 +              merge_commit = lookup_commit_reference_by_name(ref_name.buf);
 +      }
 +
 +      if (!merge_commit) {
 +              ret = error(_("could not resolve '%s'"), ref_name.buf);
 +              goto leave_merge;
 +      }
 +
 +      if (opts->have_squash_onto &&
 +          !oidcmp(&head_commit->object.oid, &opts->squash_onto)) {
 +              /*
 +               * When the user tells us to "merge" something into a
 +               * "[new root]", let's simply fast-forward to the merge head.
 +               */
 +              rollback_lock_file(&lock);
 +              ret = fast_forward_to(&merge_commit->object.oid,
 +                                     &head_commit->object.oid, 0, opts);
 +              goto leave_merge;
 +      }
 +
 +      if (commit) {
 +              const char *message = get_commit_buffer(commit, NULL);
 +              const char *body;
 +              int len;
 +
 +              if (!message) {
 +                      ret = error(_("could not get commit message of '%s'"),
 +                                  oid_to_hex(&commit->object.oid));
 +                      goto leave_merge;
 +              }
 +              write_author_script(message);
 +              find_commit_subject(message, &body);
 +              len = strlen(body);
 +              ret = write_message(body, len, git_path_merge_msg(), 0);
 +              unuse_commit_buffer(commit, message);
 +              if (ret) {
 +                      error_errno(_("could not write '%s'"),
 +                                  git_path_merge_msg());
 +                      goto leave_merge;
 +              }
 +      } else {
 +              struct strbuf buf = STRBUF_INIT;
 +              int len;
 +
 +              strbuf_addf(&buf, "author %s", git_author_info(0));
 +              write_author_script(buf.buf);
 +              strbuf_reset(&buf);
 +
 +              if (oneline_offset < arg_len) {
 +                      p = arg + oneline_offset;
 +                      len = arg_len - oneline_offset;
 +              } else {
 +                      strbuf_addf(&buf, "Merge branch '%.*s'",
 +                                  merge_arg_len, arg);
 +                      p = buf.buf;
 +                      len = buf.len;
 +              }
 +
 +              ret = write_message(p, len, git_path_merge_msg(), 0);
 +              strbuf_release(&buf);
 +              if (ret) {
 +                      error_errno(_("could not write '%s'"),
 +                                  git_path_merge_msg());
 +                      goto leave_merge;
 +              }
 +      }
 +
 +      /*
 +       * If HEAD is not identical to the first parent of the original merge
 +       * commit, we cannot fast-forward.
 +       */
 +      can_fast_forward = opts->allow_ff && commit && commit->parents &&
 +              !oidcmp(&commit->parents->item->object.oid,
 +                      &head_commit->object.oid);
 +
 +      /*
 +       * If the merge head is different from the original one, we cannot
 +       * fast-forward.
 +       */
 +      if (can_fast_forward) {
 +              struct commit_list *second_parent = commit->parents->next;
 +
 +              if (second_parent && !second_parent->next &&
 +                  oidcmp(&merge_commit->object.oid,
 +                         &second_parent->item->object.oid))
 +                      can_fast_forward = 0;
 +      }
 +
 +      if (can_fast_forward && commit->parents->next &&
 +          !commit->parents->next->next &&
 +          !oidcmp(&commit->parents->next->item->object.oid,
 +                  &merge_commit->object.oid)) {
 +              rollback_lock_file(&lock);
 +              ret = fast_forward_to(&commit->object.oid,
 +                                    &head_commit->object.oid, 0, opts);
 +              goto leave_merge;
 +      }
 +
 +      write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,
 +                    git_path_merge_head(), 0);
 +      write_message("no-ff", 5, git_path_merge_mode(), 0);
 +
 +      bases = get_merge_bases(head_commit, merge_commit);
 +      if (bases && !oidcmp(&merge_commit->object.oid,
 +                           &bases->item->object.oid)) {
 +              ret = 0;
 +              /* skip merging an ancestor of HEAD */
 +              goto leave_merge;
 +      }
 +
 +      for (j = bases; j; j = j->next)
 +              commit_list_insert(j->item, &reversed);
 +      free_commit_list(bases);
 +
 +      read_cache();
 +      init_merge_options(&o);
 +      o.branch1 = "HEAD";
 +      o.branch2 = ref_name.buf;
 +      o.buffer_output = 2;
 +
 +      ret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);
 +      if (ret <= 0)
 +              fputs(o.obuf.buf, stdout);
 +      strbuf_release(&o.obuf);
 +      if (ret < 0) {
 +              error(_("could not even attempt to merge '%.*s'"),
 +                    merge_arg_len, arg);
 +              goto leave_merge;
 +      }
 +      /*
 +       * The return value of merge_recursive() is 1 on clean, and 0 on
 +       * unclean merge.
 +       *
 +       * Let's reverse that, so that do_merge() returns 0 upon success and
 +       * 1 upon failed merge (keeping the return value -1 for the cases where
 +       * we will want to reschedule the `merge` command).
 +       */
 +      ret = !ret;
 +
 +      if (active_cache_changed &&
 +          write_locked_index(&the_index, &lock, COMMIT_LOCK)) {
 +              ret = error(_("merge: Unable to write new index file"));
 +              goto leave_merge;
 +      }
 +
 +      rollback_lock_file(&lock);
 +      if (ret)
 +              rerere(opts->allow_rerere_auto);
 +      else
 +              /*
 +               * In case of problems, we now want to return a positive
 +               * value (a negative one would indicate that the `merge`
 +               * command needs to be rescheduled).
 +               */
 +              ret = !!run_git_commit(git_path_merge_msg(), opts,
 +                                   run_commit_flags);
 +
 +leave_merge:
 +      strbuf_release(&ref_name);
 +      rollback_lock_file(&lock);
 +      return ret;
 +}
 +
  static int is_final_fixup(struct todo_list *todo_list)
  {
        int i = todo_list->current;
@@@ -3129,20 -2539,9 +3130,20 @@@ static const char *reflog_message(struc
        return buf.buf;
  }
  
 +static const char rescheduled_advice[] =
 +N_("Could not execute the todo command\n"
 +"\n"
 +"    %.*s"
 +"\n"
 +"It has been rescheduled; To edit the command before continuing, please\n"
 +"edit the todo list first:\n"
 +"\n"
 +"    git rebase --edit-todo\n"
 +"    git rebase --continue\n");
 +
  static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
  {
 -      int res = 0;
 +      int res = 0, reschedule = 0;
  
        setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
        if (opts->allow_ff)
                                        opts, is_final_fixup(todo_list));
                        if (is_rebase_i(opts) && res < 0) {
                                /* Reschedule */
 +                              advise(_(rescheduled_advice),
 +                                     get_item_line_length(todo_list,
 +                                                          todo_list->current),
 +                                     get_item_line(todo_list,
 +                                                   todo_list->current));
                                todo_list->current--;
                                if (save_todo(todo_list, opts))
                                        return -1;
                                        intend_to_amend();
                                return error_failed_squash(item->commit, opts,
                                        item->arg_len, item->arg);
 -                      } else if (res && is_rebase_i(opts))
 +                      } else if (res && is_rebase_i(opts) && item->commit)
                                return res | error_with_patch(item->commit,
                                        item->arg, item->arg_len, opts, res,
                                        item->command == TODO_REWORD);
                                /* `current` will be incremented below */
                                todo_list->current = -1;
                        }
 +              } else if (item->command == TODO_LABEL) {
 +                      if ((res = do_label(item->arg, item->arg_len)))
 +                              reschedule = 1;
 +              } else if (item->command == TODO_RESET) {
 +                      if ((res = do_reset(item->arg, item->arg_len, opts)))
 +                              reschedule = 1;
 +              } else if (item->command == TODO_MERGE) {
 +                      if ((res = do_merge(item->commit,
 +                                          item->arg, item->arg_len,
 +                                          item->flags, opts)) < 0)
 +                              reschedule = 1;
 +                      else if (item->commit)
 +                              record_in_rewritten(&item->commit->object.oid,
 +                                                  peek_command(todo_list, 1));
 +                      if (res > 0)
 +                              /* failed with merge conflicts */
 +                              return error_with_patch(item->commit,
 +                                                      item->arg,
 +                                                      item->arg_len, opts,
 +                                                      res, 0);
                } else if (!is_noop(item->command))
                        return error(_("unknown command %d"), item->command);
  
 +              if (reschedule) {
 +                      advise(_(rescheduled_advice),
 +                             get_item_line_length(todo_list,
 +                                                  todo_list->current),
 +                             get_item_line(todo_list, todo_list->current));
 +                      todo_list->current--;
 +                      if (save_todo(todo_list, opts))
 +                              return -1;
 +                      if (item->commit)
 +                              return error_with_patch(item->commit,
 +                                                      item->arg,
 +                                                      item->arg_len, opts,
 +                                                      res, 0);
 +              }
 +
                todo_list->current++;
                if (res)
                        return res;
@@@ -3403,16 -2762,19 +3404,16 @@@ static int continue_single_pick(void
        return run_command_v_opt(argv, RUN_GIT_CMD);
  }
  
 -static int commit_staged_changes(struct replay_opts *opts)
 +static int commit_staged_changes(struct replay_opts *opts,
 +                               struct todo_list *todo_list)
  {
        unsigned int flags = ALLOW_EMPTY | EDIT_MSG;
 +      unsigned int final_fixup = 0, is_clean;
  
        if (has_unstaged_changes(1))
                return error(_("cannot rebase: You have unstaged changes."));
 -      if (!has_uncommitted_changes(0)) {
 -              const char *cherry_pick_head = git_path_cherry_pick_head();
  
 -              if (file_exists(cherry_pick_head) && unlink(cherry_pick_head))
 -                      return error(_("could not remove CHERRY_PICK_HEAD"));
 -              return 0;
 -      }
 +      is_clean = !has_uncommitted_changes(0);
  
        if (file_exists(rebase_path_amend())) {
                struct strbuf rev = STRBUF_INIT;
                if (get_oid_hex(rev.buf, &to_amend))
                        return error(_("invalid contents: '%s'"),
                                rebase_path_amend());
 -              if (oidcmp(&head, &to_amend))
 +              if (!is_clean && oidcmp(&head, &to_amend))
                        return error(_("\nYou have uncommitted changes in your "
                                       "working tree. Please, commit them\n"
                                       "first and then run 'git rebase "
                                       "--continue' again."));
 +              /*
 +               * When skipping a failed fixup/squash, we need to edit the
 +               * commit message, the current fixup list and count, and if it
 +               * was the last fixup/squash in the chain, we need to clean up
 +               * the commit message and if there was a squash, let the user
 +               * edit it.
 +               */
 +              if (is_clean && !oidcmp(&head, &to_amend) &&
 +                  opts->current_fixup_count > 0 &&
 +                  file_exists(rebase_path_stopped_sha())) {
 +                      const char *p = opts->current_fixups.buf;
 +                      int len = opts->current_fixups.len;
 +
 +                      opts->current_fixup_count--;
 +                      if (!len)
 +                              BUG("Incorrect current_fixups:\n%s", p);
 +                      while (len && p[len - 1] != '\n')
 +                              len--;
 +                      strbuf_setlen(&opts->current_fixups, len);
 +                      if (write_message(p, len, rebase_path_current_fixups(),
 +                                        0) < 0)
 +                              return error(_("could not write file: '%s'"),
 +                                           rebase_path_current_fixups());
 +
 +                      /*
 +                       * If a fixup/squash in a fixup/squash chain failed, the
 +                       * commit message is already correct, no need to commit
 +                       * it again.
 +                       *
 +                       * Only if it is the final command in the fixup/squash
 +                       * chain, and only if the chain is longer than a single
 +                       * fixup/squash command (which was just skipped), do we
 +                       * actually need to re-commit with a cleaned up commit
 +                       * message.
 +                       */
 +                      if (opts->current_fixup_count > 0 &&
 +                          !is_fixup(peek_command(todo_list, 0))) {
 +                              final_fixup = 1;
 +                              /*
 +                               * If there was not a single "squash" in the
 +                               * chain, we only need to clean up the commit
 +                               * message, no need to bother the user with
 +                               * opening the commit message in the editor.
 +                               */
 +                              if (!starts_with(p, "squash ") &&
 +                                  !strstr(p, "\nsquash "))
 +                                      flags = (flags & ~EDIT_MSG) | CLEANUP_MSG;
 +                      } else if (is_fixup(peek_command(todo_list, 0))) {
 +                              /*
 +                               * We need to update the squash message to skip
 +                               * the latest commit message.
 +                               */
 +                              struct commit *commit;
 +                              const char *path = rebase_path_squash_msg();
 +
 +                              if (parse_head(&commit) ||
 +                                  !(p = get_commit_buffer(commit, NULL)) ||
 +                                  write_message(p, strlen(p), path, 0)) {
 +                                      unuse_commit_buffer(commit, p);
 +                                      return error(_("could not write file: "
 +                                                     "'%s'"), path);
 +                              }
 +                              unuse_commit_buffer(commit, p);
 +                      }
 +              }
  
                strbuf_release(&rev);
                flags |= AMEND_MSG;
        }
  
 -      if (run_git_commit(rebase_path_message(), opts, flags))
 +      if (is_clean) {
 +              const char *cherry_pick_head = git_path_cherry_pick_head();
 +
 +              if (file_exists(cherry_pick_head) && unlink(cherry_pick_head))
 +                      return error(_("could not remove CHERRY_PICK_HEAD"));
 +              if (!final_fixup)
 +                      return 0;
 +      }
 +
 +      if (run_git_commit(final_fixup ? NULL : rebase_path_message(),
 +                         opts, flags))
                return error(_("could not commit staged changes."));
        unlink(rebase_path_amend());
 +      if (final_fixup) {
 +              unlink(rebase_path_fixup_msg());
 +              unlink(rebase_path_squash_msg());
 +      }
 +      if (opts->current_fixup_count > 0) {
 +              /*
 +               * Whether final fixup or not, we just cleaned up the commit
 +               * message...
 +               */
 +              unlink(rebase_path_current_fixups());
 +              strbuf_reset(&opts->current_fixups);
 +              opts->current_fixup_count = 0;
 +      }
        return 0;
  }
  
@@@ -3537,16 -2811,14 +3538,16 @@@ int sequencer_continue(struct replay_op
        if (read_and_refresh_cache(opts))
                return -1;
  
 +      if (read_populate_opts(opts))
 +              return -1;
        if (is_rebase_i(opts)) {
 -              if (commit_staged_changes(opts))
 +              if ((res = read_populate_todo(&todo_list, opts)))
 +                      goto release_todo_list;
 +              if (commit_staged_changes(opts, &todo_list))
                        return -1;
        } else if (!file_exists(get_todo_path(opts)))
                return continue_single_pick();
 -      if (read_populate_opts(opts))
 -              return -1;
 -      if ((res = read_populate_todo(&todo_list, opts)))
 +      else if ((res = read_populate_todo(&todo_list, opts)))
                goto release_todo_list;
  
        if (!is_rebase_i(opts)) {
@@@ -3605,8 -2877,7 +3606,8 @@@ int sequencer_pick_revisions(struct rep
  
                if (!get_oid(name, &oid)) {
                        if (!lookup_commit_reference_gently(&oid, 1)) {
 -                              enum object_type type = oid_object_info(&oid,
 +                              enum object_type type = oid_object_info(the_repository,
 +                                                                      &oid,
                                                                        NULL);
                                return error(_("%s: can't cherry-pick a %s"),
                                        name, type_name(type));
@@@ -3717,348 -2988,6 +3718,348 @@@ void append_signoff(struct strbuf *msgb
        strbuf_release(&sob);
  }
  
 +struct labels_entry {
 +      struct hashmap_entry entry;
 +      char label[FLEX_ARRAY];
 +};
 +
 +static int labels_cmp(const void *fndata, const struct labels_entry *a,
 +                    const struct labels_entry *b, const void *key)
 +{
 +      return key ? strcmp(a->label, key) : strcmp(a->label, b->label);
 +}
 +
 +struct string_entry {
 +      struct oidmap_entry entry;
 +      char string[FLEX_ARRAY];
 +};
 +
 +struct label_state {
 +      struct oidmap commit2label;
 +      struct hashmap labels;
 +      struct strbuf buf;
 +};
 +
 +static const char *label_oid(struct object_id *oid, const char *label,
 +                           struct label_state *state)
 +{
 +      struct labels_entry *labels_entry;
 +      struct string_entry *string_entry;
 +      struct object_id dummy;
 +      size_t len;
 +      int i;
 +
 +      string_entry = oidmap_get(&state->commit2label, oid);
 +      if (string_entry)
 +              return string_entry->string;
 +
 +      /*
 +       * For "uninteresting" commits, i.e. commits that are not to be
 +       * rebased, and which can therefore not be labeled, we use a unique
 +       * abbreviation of the commit name. This is slightly more complicated
 +       * than calling find_unique_abbrev() because we also need to make
 +       * sure that the abbreviation does not conflict with any other
 +       * label.
 +       *
 +       * We disallow "interesting" commits to be labeled by a string that
 +       * is a valid full-length hash, to ensure that we always can find an
 +       * abbreviation for any uninteresting commit's names that does not
 +       * clash with any other label.
 +       */
 +      if (!label) {
 +              char *p;
 +
 +              strbuf_reset(&state->buf);
 +              strbuf_grow(&state->buf, GIT_SHA1_HEXSZ);
 +              label = p = state->buf.buf;
 +
 +              find_unique_abbrev_r(p, oid, default_abbrev);
 +
 +              /*
 +               * We may need to extend the abbreviated hash so that there is
 +               * no conflicting label.
 +               */
 +              if (hashmap_get_from_hash(&state->labels, strihash(p), p)) {
 +                      size_t i = strlen(p) + 1;
 +
 +                      oid_to_hex_r(p, oid);
 +                      for (; i < GIT_SHA1_HEXSZ; i++) {
 +                              char save = p[i];
 +                              p[i] = '\0';
 +                              if (!hashmap_get_from_hash(&state->labels,
 +                                                         strihash(p), p))
 +                                      break;
 +                              p[i] = save;
 +                      }
 +              }
 +      } else if (((len = strlen(label)) == GIT_SHA1_RAWSZ &&
 +                  !get_oid_hex(label, &dummy)) ||
 +                 (len == 1 && *label == '#') ||
 +                 hashmap_get_from_hash(&state->labels,
 +                                       strihash(label), label)) {
 +              /*
 +               * If the label already exists, or if the label is a valid full
 +               * OID, or the label is a '#' (which we use as a separator
 +               * between merge heads and oneline), we append a dash and a
 +               * number to make it unique.
 +               */
 +              struct strbuf *buf = &state->buf;
 +
 +              strbuf_reset(buf);
 +              strbuf_add(buf, label, len);
 +
 +              for (i = 2; ; i++) {
 +                      strbuf_setlen(buf, len);
 +                      strbuf_addf(buf, "-%d", i);
 +                      if (!hashmap_get_from_hash(&state->labels,
 +                                                 strihash(buf->buf),
 +                                                 buf->buf))
 +                              break;
 +              }
 +
 +              label = buf->buf;
 +      }
 +
 +      FLEX_ALLOC_STR(labels_entry, label, label);
 +      hashmap_entry_init(labels_entry, strihash(label));
 +      hashmap_add(&state->labels, labels_entry);
 +
 +      FLEX_ALLOC_STR(string_entry, string, label);
 +      oidcpy(&string_entry->entry.oid, oid);
 +      oidmap_put(&state->commit2label, string_entry);
 +
 +      return string_entry->string;
 +}
 +
 +static int make_script_with_merges(struct pretty_print_context *pp,
 +                                 struct rev_info *revs, FILE *out,
 +                                 unsigned flags)
 +{
 +      int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
 +      int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
 +      struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
 +      struct strbuf label = STRBUF_INIT;
 +      struct commit_list *commits = NULL, **tail = &commits, *iter;
 +      struct commit_list *tips = NULL, **tips_tail = &tips;
 +      struct commit *commit;
 +      struct oidmap commit2todo = OIDMAP_INIT;
 +      struct string_entry *entry;
 +      struct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,
 +              shown = OIDSET_INIT;
 +      struct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };
 +
 +      int abbr = flags & TODO_LIST_ABBREVIATE_CMDS;
 +      const char *cmd_pick = abbr ? "p" : "pick",
 +              *cmd_label = abbr ? "l" : "label",
 +              *cmd_reset = abbr ? "t" : "reset",
 +              *cmd_merge = abbr ? "m" : "merge";
 +
 +      oidmap_init(&commit2todo, 0);
 +      oidmap_init(&state.commit2label, 0);
 +      hashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0);
 +      strbuf_init(&state.buf, 32);
 +
 +      if (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {
 +              struct object_id *oid = &revs->cmdline.rev[0].item->oid;
 +              FLEX_ALLOC_STR(entry, string, "onto");
 +              oidcpy(&entry->entry.oid, oid);
 +              oidmap_put(&state.commit2label, entry);
 +      }
 +
 +      /*
 +       * First phase:
 +       * - get onelines for all commits
 +       * - gather all branch tips (i.e. 2nd or later parents of merges)
 +       * - label all branch tips
 +       */
 +      while ((commit = get_revision(revs))) {
 +              struct commit_list *to_merge;
 +              int is_octopus;
 +              const char *p1, *p2;
 +              struct object_id *oid;
 +              int is_empty;
 +
 +              tail = &commit_list_insert(commit, tail)->next;
 +              oidset_insert(&interesting, &commit->object.oid);
 +
 +              is_empty = is_original_commit_empty(commit);
 +              if (!is_empty && (commit->object.flags & PATCHSAME))
 +                      continue;
 +
 +              strbuf_reset(&oneline);
 +              pretty_print_commit(pp, commit, &oneline);
 +
 +              to_merge = commit->parents ? commit->parents->next : NULL;
 +              if (!to_merge) {
 +                      /* non-merge commit: easy case */
 +                      strbuf_reset(&buf);
 +                      if (!keep_empty && is_empty)
 +                              strbuf_addf(&buf, "%c ", comment_line_char);
 +                      strbuf_addf(&buf, "%s %s %s", cmd_pick,
 +                                  oid_to_hex(&commit->object.oid),
 +                                  oneline.buf);
 +
 +                      FLEX_ALLOC_STR(entry, string, buf.buf);
 +                      oidcpy(&entry->entry.oid, &commit->object.oid);
 +                      oidmap_put(&commit2todo, entry);
 +
 +                      continue;
 +              }
 +
 +              is_octopus = to_merge && to_merge->next;
 +
 +              if (is_octopus)
 +                      BUG("Octopus merges not yet supported");
 +
 +              /* Create a label */
 +              strbuf_reset(&label);
 +              if (skip_prefix(oneline.buf, "Merge ", &p1) &&
 +                  (p1 = strchr(p1, '\'')) &&
 +                  (p2 = strchr(++p1, '\'')))
 +                      strbuf_add(&label, p1, p2 - p1);
 +              else if (skip_prefix(oneline.buf, "Merge pull request ",
 +                                   &p1) &&
 +                       (p1 = strstr(p1, " from ")))
 +                      strbuf_addstr(&label, p1 + strlen(" from "));
 +              else
 +                      strbuf_addbuf(&label, &oneline);
 +
 +              for (p1 = label.buf; *p1; p1++)
 +                      if (isspace(*p1))
 +                              *(char *)p1 = '-';
 +
 +              strbuf_reset(&buf);
 +              strbuf_addf(&buf, "%s -C %s",
 +                          cmd_merge, oid_to_hex(&commit->object.oid));
 +
 +              /* label the tip of merged branch */
 +              oid = &to_merge->item->object.oid;
 +              strbuf_addch(&buf, ' ');
 +
 +              if (!oidset_contains(&interesting, oid))
 +                      strbuf_addstr(&buf, label_oid(oid, NULL, &state));
 +              else {
 +                      tips_tail = &commit_list_insert(to_merge->item,
 +                                                      tips_tail)->next;
 +
 +                      strbuf_addstr(&buf, label_oid(oid, label.buf, &state));
 +              }
 +              strbuf_addf(&buf, " # %s", oneline.buf);
 +
 +              FLEX_ALLOC_STR(entry, string, buf.buf);
 +              oidcpy(&entry->entry.oid, &commit->object.oid);
 +              oidmap_put(&commit2todo, entry);
 +      }
 +
 +      /*
 +       * Second phase:
 +       * - label branch points
 +       * - add HEAD to the branch tips
 +       */
 +      for (iter = commits; iter; iter = iter->next) {
 +              struct commit_list *parent = iter->item->parents;
 +              for (; parent; parent = parent->next) {
 +                      struct object_id *oid = &parent->item->object.oid;
 +                      if (!oidset_contains(&interesting, oid))
 +                              continue;
 +                      if (!oidset_contains(&child_seen, oid))
 +                              oidset_insert(&child_seen, oid);
 +                      else
 +                              label_oid(oid, "branch-point", &state);
 +              }
 +
 +              /* Add HEAD as implict "tip of branch" */
 +              if (!iter->next)
 +                      tips_tail = &commit_list_insert(iter->item,
 +                                                      tips_tail)->next;
 +      }
 +
 +      /*
 +       * Third phase: output the todo list. This is a bit tricky, as we
 +       * want to avoid jumping back and forth between revisions. To
 +       * accomplish that goal, we walk backwards from the branch tips,
 +       * gathering commits not yet shown, reversing the list on the fly,
 +       * then outputting that list (labeling revisions as needed).
 +       */
 +      fprintf(out, "%s onto\n", cmd_label);
 +      for (iter = tips; iter; iter = iter->next) {
 +              struct commit_list *list = NULL, *iter2;
 +
 +              commit = iter->item;
 +              if (oidset_contains(&shown, &commit->object.oid))
 +                      continue;
 +              entry = oidmap_get(&state.commit2label, &commit->object.oid);
 +
 +              if (entry)
 +                      fprintf(out, "\n# Branch %s\n", entry->string);
 +              else
 +                      fprintf(out, "\n");
 +
 +              while (oidset_contains(&interesting, &commit->object.oid) &&
 +                     !oidset_contains(&shown, &commit->object.oid)) {
 +                      commit_list_insert(commit, &list);
 +                      if (!commit->parents) {
 +                              commit = NULL;
 +                              break;
 +                      }
 +                      commit = commit->parents->item;
 +              }
 +
 +              if (!commit)
 +                      fprintf(out, "%s %s\n", cmd_reset,
 +                              rebase_cousins ? "onto" : "[new root]");
 +              else {
 +                      const char *to = NULL;
 +
 +                      entry = oidmap_get(&state.commit2label,
 +                                         &commit->object.oid);
 +                      if (entry)
 +                              to = entry->string;
 +                      else if (!rebase_cousins)
 +                              to = label_oid(&commit->object.oid, NULL,
 +                                             &state);
 +
 +                      if (!to || !strcmp(to, "onto"))
 +                              fprintf(out, "%s onto\n", cmd_reset);
 +                      else {
 +                              strbuf_reset(&oneline);
 +                              pretty_print_commit(pp, commit, &oneline);
 +                              fprintf(out, "%s %s # %s\n",
 +                                      cmd_reset, to, oneline.buf);
 +                      }
 +              }
 +
 +              for (iter2 = list; iter2; iter2 = iter2->next) {
 +                      struct object_id *oid = &iter2->item->object.oid;
 +                      entry = oidmap_get(&commit2todo, oid);
 +                      /* only show if not already upstream */
 +                      if (entry)
 +                              fprintf(out, "%s\n", entry->string);
 +                      entry = oidmap_get(&state.commit2label, oid);
 +                      if (entry)
 +                              fprintf(out, "%s %s\n",
 +                                      cmd_label, entry->string);
 +                      oidset_insert(&shown, oid);
 +              }
 +
 +              free_commit_list(list);
 +      }
 +
 +      free_commit_list(commits);
 +      free_commit_list(tips);
 +
 +      strbuf_release(&label);
 +      strbuf_release(&oneline);
 +      strbuf_release(&buf);
 +
 +      oidmap_free(&commit2todo, 1);
 +      oidmap_free(&state.commit2label, 1);
 +      hashmap_free(&state.labels, 1);
 +      strbuf_release(&state.buf);
 +
 +      return 0;
 +}
 +
  int sequencer_make_script(FILE *out, int argc, const char **argv,
                          unsigned flags)
  {
        struct commit *commit;
        int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
        const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
 +      int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
  
        init_revisions(&revs, NULL);
        revs.verbose_header = 1;
 -      revs.max_parents = 1;
 -      revs.cherry_pick = 1;
 +      if (!rebase_merges)
 +              revs.max_parents = 1;
 +      revs.cherry_mark = 1;
        revs.limited = 1;
        revs.reverse = 1;
        revs.right_only = 1;
        if (prepare_revision_walk(&revs) < 0)
                return error(_("make_script: error preparing revisions"));
  
 +      if (rebase_merges)
 +              return make_script_with_merges(&pp, &revs, out, flags);
 +
        while ((commit = get_revision(&revs))) {
 +              int is_empty  = is_original_commit_empty(commit);
 +
 +              if (!is_empty && (commit->object.flags & PATCHSAME))
 +                      continue;
                strbuf_reset(&buf);
 -              if (!keep_empty && is_original_commit_empty(commit))
 +              if (!keep_empty && is_empty)
                        strbuf_addf(&buf, "%c ", comment_line_char);
                strbuf_addf(&buf, "%s %s ", insn,
                            oid_to_hex(&commit->object.oid));
@@@ -4195,16 -3115,8 +4196,16 @@@ int transform_todos(unsigned flags
                                          short_commit_name(item->commit) :
                                          oid_to_hex(&item->commit->object.oid);
  
 +                      if (item->command == TODO_MERGE) {
 +                              if (item->flags & TODO_EDIT_MERGE_MSG)
 +                                      strbuf_addstr(&buf, " -c");
 +                              else
 +                                      strbuf_addstr(&buf, " -C");
 +                      }
 +
                        strbuf_addf(&buf, " %s", oid);
                }
 +
                /* add all the rest */
                if (!item->arg_len)
                        strbuf_addch(&buf, '\n');
@@@ -4384,7 -3296,8 +4385,7 @@@ int skip_unnecessary_picks(void
                oid = &item->commit->object.oid;
        }
        if (i > 0) {
 -              int offset = i < todo_list.nr ?
 -                      todo_list.items[i].offset_in_buf : todo_list.buf.len;
 +              int offset = get_item_line_offset(&todo_list, i);
                const char *done_path = rebase_path_done();
  
                fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);
@@@ -4479,7 -3392,7 +4480,7 @@@ int rearrange_squash(void
                struct subject2item_entry *entry;
  
                next[i] = tail[i] = -1;
 -              if (item->command >= TODO_EXEC) {
 +              if (!item->commit || item->command == TODO_DROP) {
                        subjects[i] = NULL;
                        continue;
                }
                                continue;
  
                        while (cur >= 0) {
 -                              int offset = todo_list.items[cur].offset_in_buf;
 -                              int end_offset = cur + 1 < todo_list.nr ?
 -                                      todo_list.items[cur + 1].offset_in_buf :
 -                                      todo_list.buf.len;
 -                              char *bol = todo_list.buf.buf + offset;
 -                              char *eol = todo_list.buf.buf + end_offset;
 +                              const char *bol =
 +                                      get_item_line(&todo_list, cur);
 +                              const char *eol =
 +                                      get_item_line(&todo_list, cur + 1);
  
                                /* replace 'pick', by 'fixup' or 'squash' */
                                command = todo_list.items[cur].command;
diff --combined shell.c
index 0200d10796c43d6ea1249c314ee158f2b57a481f,3ce77b8e3465c594a5ae07b003e786c948627289..40084a30130ef844899bc1f3321285afdda82607
+++ b/shell.c
@@@ -1,8 -1,9 +1,9 @@@
  #include "cache.h"
  #include "quote.h"
 -#include "exec_cmd.h"
 +#include "exec-cmd.h"
  #include "strbuf.h"
  #include "run-command.h"
+ #include "alias.h"
  
  #define COMMAND_DIR "git-shell-commands"
  #define HELP_COMMAND COMMAND_DIR "/help"
diff --combined t/t9902-completion.sh
index 1b6d27525454a1d14da91f53d45e39a659e48e36,5863b1acac67534f5e86f8401c70b97112fd7978..36deb0b123cf501d4acd2c9ec26ff3acef21bc87
@@@ -13,7 -13,7 +13,7 @@@ complete (
        return 0
  }
  
- # Be careful when updating this list:
+ # Be careful when updating these lists:
  #
  # (1) The build tree may have build artifact from different branch, or
  #     the user's $PATH may have a random executable that may begin
@@@ -30,7 -30,8 +30,8 @@@
  #     completion for "git <TAB>", and a plumbing is excluded.  "add",
  #     "filter-branch" and "ls-files" are listed for this.
  
- GIT_TESTING_COMMAND_COMPLETION='add checkout check-attr filter-branch ls-files'
+ GIT_TESTING_ALL_COMMAND_LIST='add checkout check-attr filter-branch ls-files'
+ GIT_TESTING_PORCELAIN_COMMAND_LIST='add checkout filter-branch'
  
  . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash"
  
@@@ -84,11 -85,10 +85,11 @@@ test_completion (
        then
                printf '%s\n' "$2" >expected
        else
 -              sed -e 's/Z$//' >expected
 +              sed -e 's/Z$//' |sort >expected
        fi &&
        run_completion "$1" &&
 -      test_cmp expected out
 +      sort out >out_sorted &&
 +      test_cmp expected out_sorted
  }
  
  # Test __gitcomp.
@@@ -401,46 -401,6 +402,46 @@@ test_expect_success '__gitdir - remote 
        test_cmp expected "$actual"
  '
  
 +
 +test_expect_success '__git_dequote - plain unquoted word' '
 +      __git_dequote unquoted-word &&
 +      verbose test unquoted-word = "$dequoted_word"
 +'
 +
 +# input:    b\a\c\k\'\\\"s\l\a\s\h\es
 +# expected: back'\"slashes
 +test_expect_success '__git_dequote - backslash escaped' '
 +      __git_dequote "b\a\c\k\\'\''\\\\\\\"s\l\a\s\h\es" &&
 +      verbose test "back'\''\\\"slashes" = "$dequoted_word"
 +'
 +
 +# input:    sin'gle\' '"quo'ted
 +# expected: single\ "quoted
 +test_expect_success '__git_dequote - single quoted' '
 +      __git_dequote "'"sin'gle\\\\' '\\\"quo'ted"'" &&
 +      verbose test '\''single\ "quoted'\'' = "$dequoted_word"
 +'
 +
 +# input:    dou"ble\\" "\"\quot"ed
 +# expected: double\ "\quoted
 +test_expect_success '__git_dequote - double quoted' '
 +      __git_dequote '\''dou"ble\\" "\"\quot"ed'\'' &&
 +      verbose test '\''double\ "\quoted'\'' = "$dequoted_word"
 +'
 +
 +# input: 'open single quote
 +test_expect_success '__git_dequote - open single quote' '
 +      __git_dequote "'\''open single quote" &&
 +      verbose test "open single quote" = "$dequoted_word"
 +'
 +
 +# input: "open double quote
 +test_expect_success '__git_dequote - open double quote' '
 +      __git_dequote "\"open double quote" &&
 +      verbose test "open double quote" = "$dequoted_word"
 +'
 +
 +
  test_expect_success '__gitcomp_direct - puts everything into COMPREPLY as-is' '
        sed -e "s/Z$//g" >expected <<-EOF &&
        with-trailing-space Z
@@@ -1209,124 -1169,6 +1210,124 @@@ test_expect_success 'teardown after re
        git remote remove other
  '
  
 +
 +test_path_completion ()
 +{
 +      test $# = 2 || error "bug in the test script: not 2 parameters to test_path_completion"
 +
 +      local cur="$1" expected="$2"
 +      echo "$expected" >expected &&
 +      (
 +              # In the following tests calling this function we only
 +              # care about how __git_complete_index_file() deals with
 +              # unusual characters in path names.  By requesting only
 +              # untracked files we dont have to bother adding any
 +              # paths to the index in those tests.
 +              __git_complete_index_file --others &&
 +              print_comp
 +      ) &&
 +      test_cmp expected out
 +}
 +
 +test_expect_success 'setup for path completion tests' '
 +      mkdir simple-dir \
 +            "spaces in dir" \
 +            árvíztűrő &&
 +      touch simple-dir/simple-file \
 +            "spaces in dir/spaces in file" \
 +            "árvíztűrő/Сайн яваарай" &&
 +      if test_have_prereq !MINGW &&
 +         mkdir BS\\dir \
 +               '$'separators\034in\035dir'' &&
 +         touch BS\\dir/DQ\"file \
 +               '$'separators\034in\035dir/sep\036in\037file''
 +      then
 +              test_set_prereq FUNNYNAMES
 +      else
 +              rm -rf BS\\dir '$'separators\034in\035dir''
 +      fi
 +'
 +
 +test_expect_success '__git_complete_index_file - simple' '
 +      test_path_completion simple simple-dir &&  # Bash is supposed to
 +                                                 # add the trailing /.
 +      test_path_completion simple-dir/simple simple-dir/simple-file
 +'
 +
 +test_expect_success \
 +    '__git_complete_index_file - escaped characters on cmdline' '
 +      test_path_completion spac "spaces in dir" &&  # Bash will turn this
 +                                                    # into "spaces\ in\ dir"
 +      test_path_completion "spaces\\ i" \
 +                           "spaces in dir" &&
 +      test_path_completion "spaces\\ in\\ dir/s" \
 +                           "spaces in dir/spaces in file" &&
 +      test_path_completion "spaces\\ in\\ dir/spaces\\ i" \
 +                           "spaces in dir/spaces in file"
 +'
 +
 +test_expect_success \
 +    '__git_complete_index_file - quoted characters on cmdline' '
 +      # Testing with an opening but without a corresponding closing
 +      # double quote is important.
 +      test_path_completion \"spac "spaces in dir" &&
 +      test_path_completion "\"spaces i" \
 +                           "spaces in dir" &&
 +      test_path_completion "\"spaces in dir/s" \
 +                           "spaces in dir/spaces in file" &&
 +      test_path_completion "\"spaces in dir/spaces i" \
 +                           "spaces in dir/spaces in file"
 +'
 +
 +test_expect_success '__git_complete_index_file - UTF-8 in ls-files output' '
 +      test_path_completion á árvíztűrő &&
 +      test_path_completion árvíztűrő/С "árvíztűrő/Сайн яваарай"
 +'
 +
 +test_expect_success FUNNYNAMES \
 +    '__git_complete_index_file - C-style escapes in ls-files output' '
 +      test_path_completion BS \
 +                           BS\\dir &&
 +      test_path_completion BS\\\\d \
 +                           BS\\dir &&
 +      test_path_completion BS\\\\dir/DQ \
 +                           BS\\dir/DQ\"file &&
 +      test_path_completion BS\\\\dir/DQ\\\"f \
 +                           BS\\dir/DQ\"file
 +'
 +
 +test_expect_success FUNNYNAMES \
 +    '__git_complete_index_file - \nnn-escaped characters in ls-files output' '
 +      test_path_completion sep '$'separators\034in\035dir'' &&
 +      test_path_completion '$'separators\034i'' \
 +                           '$'separators\034in\035dir'' &&
 +      test_path_completion '$'separators\034in\035dir/sep'' \
 +                           '$'separators\034in\035dir/sep\036in\037file'' &&
 +      test_path_completion '$'separators\034in\035dir/sep\036i'' \
 +                           '$'separators\034in\035dir/sep\036in\037file''
 +'
 +
 +test_expect_success FUNNYNAMES \
 +    '__git_complete_index_file - removing repeated quoted path components' '
 +      test_when_finished rm -r repeated-quoted &&
 +      mkdir repeated-quoted &&      # A directory whose name in itself
 +                                    # would not be quoted ...
 +      >repeated-quoted/0-file &&
 +      >repeated-quoted/1\"file &&   # ... but here the file makes the
 +                                    # dirname quoted ...
 +      >repeated-quoted/2-file &&
 +      >repeated-quoted/3\"file &&   # ... and here, too.
 +
 +      # Still, we shold only list the directory name only once.
 +      test_path_completion repeated repeated-quoted
 +'
 +
 +test_expect_success 'teardown after path completion tests' '
 +      rm -rf simple-dir "spaces in dir" árvíztűrő \
 +             BS\\dir '$'separators\034in\035dir''
 +'
 +
 +
  test_expect_success '__git_get_config_variables' '
        cat >expect <<-EOF &&
        name-1
@@@ -1350,17 -1192,6 +1351,6 @@@ test_expect_success '__git_pretty_alias
        test_cmp expect actual
  '
  
- test_expect_success '__git_aliases' '
-       cat >expect <<-EOF &&
-       ci
-       co
-       EOF
-       test_config alias.ci commit &&
-       test_config alias.co checkout &&
-       __git_aliases >actual &&
-       test_cmp expect actual
- '
  test_expect_success 'basic' '
        run_completion "git " &&
        # built-in
@@@ -1524,7 -1355,6 +1514,7 @@@ test_expect_success 'complete files' 
  
        echo "expected" > .gitignore &&
        echo "out" >> .gitignore &&
 +      echo "out_sorted" >> .gitignore &&
  
        git add .gitignore &&
        test_completion "git commit " ".gitignore" &&
@@@ -1670,13 -1500,6 +1660,6 @@@ test_expect_success 'sourcing the compl
        verbose test -z "$__git_all_commands"
  '
  
- test_expect_success 'sourcing the completion script clears cached porcelain commands' '
-       __git_compute_porcelain_commands &&
-       verbose test -n "$__git_porcelain_commands" &&
-       . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
-       verbose test -z "$__git_porcelain_commands"
- '
  test_expect_success !GETTEXT_POISON 'sourcing the completion script clears cached merge strategies' '
        __git_compute_merge_strategies &&
        verbose test -n "$__git_merge_strategies" &&