Merge branch 'nd/command-list' into nd/complete-config-vars
authorJunio C Hamano <gitster@pobox.com>
Tue, 29 May 2018 05:51:14 +0000 (14:51 +0900)
committerJunio C Hamano <gitster@pobox.com>
Tue, 29 May 2018 05:51:14 +0000 (14:51 +0900)
* 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

15 files changed:
1  2 
.gitignore
Documentation/config.txt
Documentation/git.txt
Documentation/gitattributes.txt
Makefile
builtin/help.c
cache.h
command-list.txt
connect.c
contrib/completion/git-completion.bash
git.c
help.c
pager.c
sequencer.c
shell.c
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 84e2891aed156e367a38692ee34998c8e06261d3,9e81dcf867d8323d35a534859aa74bd7acc2eb83..a6cd53b59c2818b7a75eb8ab92e8a73b5ddf6df5
@@@ -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,15 -1218,6 +1251,15 @@@ color.status.<slot>:
        status short-format), or
        `unmerged` (files which have unmerged changes).
  
 +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
@@@ -1385,6 -1343,14 +1385,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::
@@@ -1600,18 -1566,6 +1608,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
@@@ -2476,7 -2430,6 +2484,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
@@@ -2513,8 -2466,7 +2521,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
@@@ -2673,10 -2625,6 +2681,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'.
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 ad880d1fc57212fc6b47aeea792a58129b61238e,1efb751e4621934e55c8805bb40e7d34ec0735ca..bf795129f15054d663d483a8898024765aa10c6a
+++ 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
@@@ -930,7 -888,7 +930,7 @@@ LIB_OBJS += refs/packed-backend.
  LIB_OBJS += refs/ref-cache.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
@@@ -938,13 -896,12 +938,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
@@@ -967,7 -924,6 +967,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
@@@ -980,7 -936,7 +980,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
@@@ -1007,7 -963,6 +1007,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
@@@ -1073,7 -1028,6 +1073,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
@@@ -1087,7 -1041,6 +1087,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
@@@ -1109,7 -1062,7 +1109,7 @@@ include config.mak.unam
  -include config.mak
  
  ifdef DEVELOPER
 -CFLAGS += $(DEVELOPER_CFLAGS)
 +include config.mak.dev
  endif
  
  comma := ,
@@@ -1710,27 -1663,10 +1710,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
@@@ -1815,13 -1751,11 +1815,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))
@@@ -1832,31 -1766,6 +1832,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:
  #
@@@ -2005,9 -1914,9 +2005,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)"' \
@@@ -2026,9 -1935,9 +2026,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):\
@@@ -2077,44 -1986,27 +2077,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 = \
@@@ -2300,7 -2175,7 +2300,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
@@@ -2460,7 -2335,7 +2460,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' \
        < $< > $@
  
@@@ -2652,7 -2527,7 +2652,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"; \
@@@ -2900,7 -2775,7 +2900,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
@@@ -2918,7 -2793,7 +2918,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 cache.h
index 6dedf3c4f969a64b3cc7fea115f507adaccc44fe,111116ea1362e8b80c1c2d1c99bbcf898dc86e66..14715de76b69bff643abecc5687989175ee0127e
+++ b/cache.h
@@@ -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);
@@@ -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;
@@@ -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);
@@@ -1682,10 -1692,7 +1682,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
@@@ -1828,11 -1835,6 +1828,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 961a0ed76f89133dc01fc86a07be37eb5728651a,e5b2ccbdd2208069f25f2ce9505a2f88a8c8b5ff..99dfedbd0bbb8fb7d12512331d4913a5ae3a8572
@@@ -284,11 -284,7 +284,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
  #
@@@ -394,7 -390,12 +394,7 @@@ __git_index_files (
        local root="${2-.}" file
  
        __git_ls_files_helper "$root" "$1" |
 -      while read -r file; do
 -              case "$file" in
 -              ?*/*) echo "${file%%/*}" ;;
 -              *) echo "$file" ;;
 -              esac
 -      done | sort | uniq
 +      cut -f1 -d/ | sort | uniq
  }
  
  # Lists branches from the local repository.
@@@ -833,127 -834,11 +833,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,
@@@ -971,11 -856,6 +855,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 ()
  {
@@@ -1583,13 -1463,12 +1462,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 ()
@@@ -1949,7 -1828,7 +1827,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
@@@ -2120,7 -1999,7 +1998,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
@@@ -2775,21 -2653,13 +2653,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"
@@@ -3058,7 -2928,7 +2936,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
@@@ -3144,8 -3007,14 +3022,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
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 1ce63261a32a2398c3916fd41f577db58478fe0f,1288a36ebd6eeca2529313333f311dcff3dee017..5a72cbd03f945a248b15a31f830ed7fb86c53b22
@@@ -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,13 -121,6 +123,13 @@@ 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 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")
@@@ -254,35 -245,18 +255,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;
  }
@@@ -372,14 -346,12 +373,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);
@@@ -528,8 -500,8 +529,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);
@@@ -590,7 -562,7 +591,7 @@@ static int is_index_unchanged(void
                        return error(_("unable to update cache tree"));
  
        return !oidcmp(&active_cache_tree->oid,
 -                     &head_commit->tree->object.oid);
 +                     get_commit_tree_oid(head_commit));
  }
  
  static int write_author_script(const char *message)
@@@ -746,8 -718,6 +747,8 @@@ static int run_git_commit(const char *d
                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))
@@@ -1149,7 -1119,7 +1150,7 @@@ static int try_to_commit(struct strbuf 
        }
  
        if (!(flags & ALLOW_EMPTY) && !oidcmp(current_head ?
 -                                            &current_head->tree->object.oid :
 +                                            get_commit_tree_oid(current_head) :
                                              &empty_tree_oid, &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"));
@@@ -1249,12 -1217,12 +1250,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));
  }
  
  /*
@@@ -1310,9 -1278,6 +1311,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,
@@@ -1331,9 -1296,6 +1332,9 @@@ static struct 
        { 'f', "fixup" },
        { 's', "squash" },
        { 'x', "exec" },
 +      { 'l', "label" },
 +      { 't', "reset" },
 +      { 'm', "merge" },
        { 0,   "noop" },
        { 'd', "drop" },
        { 0,   NULL }
@@@ -1367,23 -1329,34 +1368,23 @@@ static int update_squash_messages(enum 
                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;
  }
  
@@@ -1642,7 -1605,7 +1643,7 @@@ static int do_pick_commit(enum todo_com
                }
        }
  
 -      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)
@@@ -1713,9 -1676,6 +1714,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:
@@@ -1763,14 -1723,9 +1764,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;
@@@ -1805,8 -1760,6 +1806,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';
@@@ -1932,23 -1869,6 +1933,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;
@@@ -2124,24 -2044,9 +2125,24 @@@ 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++;
 +                      }
 +              }
 +
                return 0;
        }
  
@@@ -2215,9 -2120,9 +2216,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());
@@@ -2338,27 -2243,29 +2339,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;
  }
@@@ -2486,9 -2393,10 +2487,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'"),
@@@ -2541,349 -2449,6 +2542,349 @@@ 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;
 +
 +      /* 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 (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;
@@@ -2974,20 -2539,9 +2975,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;
@@@ -3248,16 -2762,19 +3249,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;
  }
  
@@@ -3382,16 -2811,14 +3383,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)) {
@@@ -3450,8 -2877,7 +3451,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));
@@@ -3562,347 -2988,6 +3563,347 @@@ 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 onto\n", cmd_reset);
 +              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));
@@@ -4039,16 -3115,8 +4040,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');
@@@ -4228,7 -3296,8 +4229,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);
@@@ -4323,7 -3392,7 +4324,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"