Merge branch 'nd/columns'
authorJunio C Hamano <gitster@pobox.com>
Thu, 3 May 2012 22:13:31 +0000 (15:13 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 3 May 2012 22:13:31 +0000 (15:13 -0700)
A couple of commands learn --column option to produce columnar output.

By Nguyễn Thái Ngọc Duy (9) and Zbigniew Jędrzejewski-Szmek (1)
* nd/columns:
tag: add --column
column: support piping stdout to external git-column process
status: add --column
branch: add --column
help: reuse print_columns() for help -a
column: add dense layout support
t9002: work around shells that are unable to set COLUMNS to 1
column: add columnar layout
Stop starting pager recursively
Add column layout skeleton and git-column

14 files changed:
1  2 
.gitignore
Documentation/config.txt
Documentation/git-branch.txt
Documentation/git-status.txt
Makefile
builtin/branch.c
builtin/commit.c
builtin/help.c
command-list.txt
git.c
help.c
help.h
parse-options.h
t/t3200-branch.sh
diff --combined .gitignore
index 1dbeb668dbdcf13ccdbb532e4314c901ae6f7d1c,25402643f36a38ff02a08007a03a986dcc93781b..bf66648e2c5f59cb92470dbda546c5348bcc85ef
@@@ -26,6 -26,7 +26,7 @@@
  /git-cherry-pick
  /git-clean
  /git-clone
+ /git-column
  /git-commit
  /git-commit-tree
  /git-config
@@@ -92,7 -93,6 +93,7 @@@
  /git-name-rev
  /git-mv
  /git-notes
 +/git-p4
  /git-pack-redundant
  /git-pack-objects
  /git-pack-refs
  /test-index-version
  /test-line-buffer
  /test-match-trees
 +/test-mergesort
  /test-mktemp
  /test-parse-options
  /test-path-utils
 +/test-revision-walking
  /test-run-command
  /test-sha1
  /test-sigchain
diff --combined Documentation/config.txt
index 355ee53652aad455a94df81096a7b4ef3fefdd14,75ecf36157f61d4dd9b087ff9c994faa897a84d9..915cb5a547896966377e9f39059527e3142b27c8
@@@ -12,9 -12,8 +12,9 @@@ The configuration variables are used b
  and the porcelains. The variables are divided into sections, wherein
  the fully qualified variable name of the variable itself is the last
  dot-separated segment and the section name is everything before the last
 -dot. The variable names are case-insensitive and only alphanumeric
 -characters are allowed. Some variables may appear multiple times.
 +dot. The variable names are case-insensitive, allow only alphanumeric
 +characters and `-`, and must start with an alphabetic character.  Some
 +variables may appear multiple times.
  
  Syntax
  ~~~~~~
@@@ -55,10 -54,9 +55,10 @@@ All the other lines (and the remainder 
  header) are recognized as setting variables, in the form
  'name = value'.  If there is no equal sign on the line, the entire line
  is taken as 'name' and the variable is recognized as boolean "true".
 -The variable names are case-insensitive and only alphanumeric
 -characters and `-` are allowed.  There can be more than one value
 -for a given variable; we say then that variable is multivalued.
 +The variable names are case-insensitive, allow only alphanumeric characters
 +and `-`, and must start with an alphabetic character.  There can be more
 +than one value for a given variable; we say then that the variable is
 +multivalued.
  
  Leading and trailing whitespace in a variable value is discarded.
  Internal whitespace within a variable value is retained verbatim.
@@@ -95,9 -93,7 +95,9 @@@ included file is expanded immediately, 
  found at the location of the include directive. If the value of the
  `include.path` variable is a relative path, the path is considered to be
  relative to the configuration file in which the include directive was
 -found. See below for examples.
 +found. The value of `include.path` is subject to tilde expansion: `~/`
 +is expanded to the value of `$HOME`, and `~user/` to the specified
 +user's home directory. See below for examples.
  
  Example
  ~~~~~~~
        [include]
                path = /path/to/foo.inc ; include by absolute path
                path = foo ; expand "foo" relative to the current file
 +              path = ~/foo ; expand "foo" in your $HOME directory
  
  Variables
  ~~~~~~~~~
@@@ -141,23 -136,8 +141,23 @@@ advice.*:
  +
  --
        pushNonFastForward::
 -              Advice shown when linkgit:git-push[1] refuses
 -              non-fast-forward refs.
 +              Set this variable to 'false' if you want to disable
 +              'pushNonFFCurrent', 'pushNonFFDefault', and
 +              'pushNonFFMatching' simultaneously.
 +      pushNonFFCurrent::
 +              Advice shown when linkgit:git-push[1] fails due to a
 +              non-fast-forward update to the current branch.
 +      pushNonFFDefault::
 +              Advice to set 'push.default' to 'upstream' or 'current'
 +              when you ran linkgit:git-push[1] and pushed 'matching
 +              refs' by default (i.e. you did not provide an explicit
 +              refspec, and no 'push.default' configuration was set)
 +              and it resulted in a non-fast-forward error.
 +      pushNonFFMatching::
 +              Advice shown when you ran linkgit:git-push[1] and pushed
 +              'matching refs' explicitly (i.e. you used ':', or
 +              specified a refspec that isn't your current branch) and
 +              it resulted in a non-fast-forward error.
        statusHints::
                Directions on how to stage/unstage/add shown in the
                output of linkgit:git-status[1] and the template shown
@@@ -481,8 -461,8 +481,8 @@@ Common unit suffixes of 'k', 'm', or 'g
  core.excludesfile::
        In addition to '.gitignore' (per-directory) and
        '.git/info/exclude', git looks into this file for patterns
 -      of files which are not meant to be tracked.  "{tilde}/" is expanded
 -      to the value of `$HOME` and "{tilde}user/" to the specified user's
 +      of files which are not meant to be tracked.  "`~/`" is expanded
 +      to the value of `$HOME` and "`~user/`" to the specified user's
        home directory.  See linkgit:gitignore[5].
  
  core.askpass::
@@@ -856,6 -836,44 +856,44 @@@ color.ui:
        `never` if you prefer git commands not to use color unless enabled
        explicitly with some other configuration or the `--color` option.
  
+ column.ui::
+       Specify whether supported commands should output in columns.
+       This variable consists of a list of tokens separated by spaces
+       or commas:
+ +
+ --
+ `always`;;
+       always show in columns
+ `never`;;
+       never show in columns
+ `auto`;;
+       show in columns if the output is to the terminal
+ `column`;;
+       fill columns before rows (default)
+ `row`;;
+       fill rows before columns
+ `plain`;;
+       show in one column
+ `dense`;;
+       make unequal size columns to utilize more space
+ `nodense`;;
+       make equal size columns
+ --
+ +
+       This option defaults to 'never'.
+ column.branch::
+       Specify whether to output branch listing in `git branch` in columns.
+       See `column.ui` for details.
+ column.status::
+       Specify whether to output untracked files in `git status` in columns.
+       See `column.ui` for details.
+ column.tag::
+       Specify whether to output tag listing in `git tag` in columns.
+       See `column.ui` for details.
  commit.status::
        A boolean to enable/disable inclusion of status information in the
        commit message template when using an editor to prepare the commit
  
  commit.template::
        Specify a file to use as the template for new commit messages.
 -      "{tilde}/" is expanded to the value of `$HOME` and "{tilde}user/" to the
 +      "`~/`" is expanded to the value of `$HOME` and "`~user/`" to the
        specified user's home directory.
  
  credential.helper::
@@@ -988,7 -1006,7 +1026,7 @@@ format.thread:
        a boolean value, or `shallow` or `deep`.  `shallow` threading
        makes every mail a reply to the head of the series,
        where the head is chosen from the cover letter, the
 -      `\--in-reply-to`, and the first patch mail, in this order.
 +      `--in-reply-to`, and the first patch mail, in this order.
        `deep` threading makes every mail a reply to the previous one.
        A true boolean value is the same as `shallow`, and a false
        value disables threading.
@@@ -1293,10 -1311,9 +1331,10 @@@ help.autocorrect:
        This is the default.
  
  http.proxy::
 -      Override the HTTP proxy, normally configured using the 'http_proxy'
 -      environment variable (see linkgit:curl[1]).  This can be overridden
 -      on a per-remote basis; see remote.<name>.proxy
 +      Override the HTTP proxy, normally configured using the 'http_proxy',
 +      'https_proxy', and 'all_proxy' environment variables (see
 +      `curl(1)`).  This can be overridden on a per-remote basis; see
 +      remote.<name>.proxy
  
  http.cookiefile::
        File containing previously stored cookie lines which should be used
@@@ -1419,7 -1436,7 +1457,7 @@@ instaweb.port:
  interactive.singlekey::
        In interactive commands, allow the user to provide one-letter
        input with a single key (i.e., without hitting enter).
 -      Currently this is used by the `\--patch` mode of
 +      Currently this is used by the `--patch` mode of
        linkgit:git-add[1], linkgit:git-checkout[1], linkgit:git-commit[1],
        linkgit:git-reset[1], and linkgit:git-stash[1]. Note that this
        setting is silently ignored if portable keystroke input
  
  log.abbrevCommit::
        If true, makes linkgit:git-log[1], linkgit:git-show[1], and
 -      linkgit:git-whatchanged[1] assume `\--abbrev-commit`. You may
 -      override this option with `\--no-abbrev-commit`.
 +      linkgit:git-whatchanged[1] assume `--abbrev-commit`. You may
 +      override this option with `--no-abbrev-commit`.
  
  log.date::
        Set the default date-time mode for the 'log' command.
        Setting a value for log.date is similar to using 'git log''s
 -      `\--date` option.  Possible values are `relative`, `local`,
 +      `--date` option.  Possible values are `relative`, `local`,
        `default`, `iso`, `rfc`, and `short`; see linkgit:git-log[1]
        for details.
  
@@@ -1623,18 -1640,18 +1661,18 @@@ pack.indexVersion:
        and this config option ignored whenever the corresponding pack is
        larger than 2 GB.
  +
 -If you have an old git that does not understand the version 2 `{asterisk}.idx` file,
 +If you have an old git that does not understand the version 2 `*.idx` file,
  cloning or fetching over a non native protocol (e.g. "http" and "rsync")
 -that will copy both `{asterisk}.pack` file and corresponding `{asterisk}.idx` file from the
 +that will copy both `*.pack` file and corresponding `*.idx` file from the
  other side may give you a repository that cannot be accessed with your
 -older version of git. If the `{asterisk}.pack` file is smaller than 2 GB, however,
 +older version of git. If the `*.pack` file is smaller than 2 GB, however,
  you can use linkgit:git-index-pack[1] on the *.pack file to regenerate
 -the `{asterisk}.idx` file.
 +the `*.idx` file.
  
  pack.packSizeLimit::
        The maximum size of a pack.  This setting only affects
        packing to a file when repacking, i.e. the git:// protocol
 -      is unaffected.  It can be overridden by the `\--max-pack-size`
 +      is unaffected.  It can be overridden by the `--max-pack-size`
        option of linkgit:git-repack[1]. The minimum size allowed is
        limited to 1 MiB. The default is unlimited.
        Common unit suffixes of 'k', 'm', or 'g' are
@@@ -1644,8 -1661,8 +1682,8 @@@ pager.<cmd>:
        If the value is boolean, turns on or off pagination of the
        output of a particular git subcommand when writing to a tty.
        Otherwise, turns on pagination for the subcommand using the
 -      pager specified by the value of `pager.<cmd>`.  If `\--paginate`
 -      or `\--no-pager` is specified on the command line, it takes
 +      pager specified by the value of `pager.<cmd>`.  If `--paginate`
 +      or `--no-pager` is specified on the command line, it takes
        precedence over this option.  To disable pagination for all
        commands, set `core.pager` or `GIT_PAGER` to `cat`.
  
@@@ -1653,9 -1670,9 +1691,9 @@@ pretty.<name>:
        Alias for a --pretty= format string, as specified in
        linkgit:git-log[1]. Any aliases defined here can be used just
        as the built-in pretty formats could. For example,
 -      running `git config pretty.changelog "format:{asterisk} %H %s"`
 +      running `git config pretty.changelog "format:* %H %s"`
        would cause the invocation `git log --pretty=changelog`
 -      to be equivalent to running `git log "--pretty=format:{asterisk} %H %s"`.
 +      to be equivalent to running `git log "--pretty=format:* %H %s"`.
        Note that an alias with the same name as a built-in format
        will be silently ignored.
  
@@@ -1683,30 -1700,12 +1721,30 @@@ push.default:
        line. Possible values are:
  +
  * `nothing` - do not push anything.
 -* `matching` - push all matching branches.
 -  All branches having the same name in both ends are considered to be
 -  matching. This is the default.
 +* `matching` - push all branches having the same name in both ends.
 +  This is for those who prepare all the branches into a publishable
 +  shape and then push them out with a single command.  It is not
 +  appropriate for pushing into a repository shared by multiple users,
 +  since locally stalled branches will attempt a non-fast forward push
 +  if other users updated the branch.
 +  +
 +  This is currently the default, but Git 2.0 will change the default
 +  to `simple`.
  * `upstream` - push the current branch to its upstream branch.
 -* `tracking` - deprecated synonym for `upstream`.
 +  With this, `git push` will update the same remote ref as the one which
 +  is merged by `git pull`, making `push` and `pull` symmetrical.
 +  See "branch.<name>.merge" for how to configure the upstream branch.
 +* `simple` - like `upstream`, but refuses to push if the upstream
 +  branch's name is different from the local one. This is the safest
 +  option and is well-suited for beginners. It will become the default
 +  in Git 2.0.
  * `current` - push the current branch to a branch of the same name.
 +  +
 +  The `simple`, `current` and `upstream` modes are for those who want to
 +  push out a single branch after finishing work, even when the other
 +  branches are not yet ready to be pushed out. If you are working with
 +  other people to push into the same shared repository, you would want
 +  to use one of these.
  
  rebase.stat::
        Whether to show a diffstat of what changed upstream since the last
@@@ -1786,7 -1785,7 +1824,7 @@@ remote.<name>.push:
  
  remote.<name>.mirror::
        If true, pushing to this remote will automatically behave
 -      as if the `\--mirror` option was given on the command line.
 +      as if the `--mirror` option was given on the command line.
  
  remote.<name>.skipDefaultUpdate::
        If true, this remote will be skipped by default when updating
index e71370d6b49f58ae479318b89834b4829b45d423,ba5cccb519d6b3beed033314d415b4d3c516a2f0..47235bea0403bf25fa73529a37a7c5f87d0d7eb9
@@@ -10,6 -10,7 +10,7 @@@ SYNOPSI
  [verse]
  'git branch' [--color[=<when>] | --no-color] [-r | -a]
        [--list] [-v [--abbrev=<length> | --no-abbrev]]
+       [--column[=<options>] | --no-column]
        [(--merged | --no-merged | --contains) [<commit>]] [<pattern>...]
  'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
  'git branch' (-m | -M) [<oldbranch>] <newbranch>
@@@ -24,8 -25,8 +25,8 @@@ be highlighted with an asterisk.  Optio
  branches to be listed, and option `-a` shows both. This list mode is also
  activated by the `--list` option (see below).
  <pattern> restricts the output to matching branches, the pattern is a shell
 -wildcard (i.e., matched using fnmatch(3))
 -Multiple patterns may be given; if any of them matches, the tag is shown.
 +wildcard (i.e., matched using fnmatch(3)).
 +Multiple patterns may be given; if any of them matches, the branch is shown.
  
  With `--contains`, shows only the branches that contain the named commit
  (in other words, the branches whose tip commits are descendants of the
@@@ -49,7 -50,7 +50,7 @@@ the remote-tracking branch. This behavi
  overridden by using the `--track` and `--no-track` options, and
  changed later using `git branch --set-upstream`.
  
 -With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>.
 +With a `-m` or `-M` option, <oldbranch> will be renamed to <newbranch>.
  If <oldbranch> had a corresponding reflog, it is renamed to match
  <newbranch>, and a reflog entry is created to remember the branch
  renaming. If <newbranch> exists, -M must be used to force the rename
@@@ -59,7 -60,7 +60,7 @@@ With a `-d` or `-D` option, `<branchnam
  specify more than one branch for deletion.  If the branch currently
  has a reflog then the reflog will also be deleted.
  
 -Use -r together with -d to delete remote-tracking branches. Note, that it
 +Use `-r` together with `-d` to delete remote-tracking branches. Note, that it
  only makes sense to delete remote-tracking branches if they no longer exist
  in the remote repository or if 'git fetch' was configured not to fetch
  them again. See also the 'prune' subcommand of linkgit:git-remote[1] for a
@@@ -107,6 -108,14 +108,14 @@@ OPTION
        default to color output.
        Same as `--color=never`.
  
+ --column[=<options>]::
+ --no-column::
+       Display branch listing in columns. See configuration variable
+       column.branch for option syntax.`--column` and `--no-column`
+       without options are equivalent to 'always' and 'never' respectively.
+ +
+ This option is only applicable in non-verbose mode.
  -r::
  --remotes::
        List or delete (if used with -d) the remote-tracking branches.
        relationship to upstream branch (if any). If given twice, print
        the name of the upstream branch, as well.
  
 +-q::
 +--quiet::
 +      Be more quiet when creating or deleting a branch, suppressing
 +      non-error messages.
 +
  --abbrev=<length>::
        Alter the sha1's minimum display length in the output listing.
        The default value is 7 and can be overridden by the `core.abbrev`
@@@ -159,18 -163,17 +168,18 @@@ start-point is either a local or remote
        branch.autosetupmerge configuration variable is true.
  
  --set-upstream::
 -      If specified branch does not exist yet or if '--force' has been
 -      given, acts exactly like '--track'. Otherwise sets up configuration
 -      like '--track' would when creating the branch, except that where
 +      If specified branch does not exist yet or if `--force` has been
 +      given, acts exactly like `--track`. Otherwise sets up configuration
 +      like `--track` would when creating the branch, except that where
        branch points to is not changed.
  
  --edit-description::
        Open an editor and edit the text to explain what the branch is
        for, to be used by various other commands (e.g. `request-pull`).
  
 ---contains <commit>::
 -      Only list branches which contain the specified commit.
 +--contains [<commit>]::
 +      Only list branches which contain the specified commit (HEAD
 +      if not specified).
  
  --merged [<commit>]::
        Only list branches whose tips are reachable from the
index a29aae60cded28263785b3f35640246837b0df30,2f87207e6fe0824d33f7efe65f789fc7a31243c8..2883a285bae11598b7b1539c87f7aecb2a5ab42c
@@@ -77,6 -77,13 +77,13 @@@ configuration variable documented in li
        Terminate entries with NUL, instead of LF.  This implies
        the `--porcelain` output format if no other format is given.
  
+ --column[=<options>]::
+ --no-column::
+       Display untracked files in columns. See configuration variable
+       column.status for option syntax.`--column` and `--no-column`
+       without options are equivalent to 'always' and 'never'
+       respectively.
  
  OUTPUT
  ------
@@@ -98,12 -105,12 +105,12 @@@ In the short-format, the status of eac
  
        XY PATH1 -> PATH2
  
 -where `PATH1` is the path in the `HEAD`, and the ` \-> PATH2` part is
 +where `PATH1` is the path in the `HEAD`, and the " `-> PATH2`" part is
  shown only when `PATH1` corresponds to a different path in the
  index/worktree (i.e. the file is renamed). The 'XY' is a two-letter
  status code.
  
 -The fields (including the `\->`) are separated from each other by a
 +The fields (including the `->`) are separated from each other by a
  single space. If a filename contains whitespace or other nonprintable
  characters, that field will be quoted in the manner of a C string
  literal: surrounded by ASCII double quote (34) characters, and with
diff --combined Makefile
index bdf2a578df684224c640332861f7093a38cbe8f3,53e27cc955e85d7a0c571f61503d9f21c9219901..ac40e24625f7b1c53fc9d2e7658abb6d772bf5b3
+++ b/Makefile
@@@ -288,11 -288,6 +288,11 @@@ all:
  # dependency rules.
  #
  # Define NATIVE_CRLF if your platform uses CRLF for line endings.
 +#
 +# Define XDL_FAST_HASH to use an alternative line-hashing method in
 +# the diff algorithm.  It gives a nice speedup if your processor has
 +# fast unaligned word loads.  Does NOT work on big-endian systems!
 +# Enabled by default on x86_64.
  
  GIT-VERSION-FILE: FORCE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
@@@ -391,7 -386,6 +391,7 @@@ XDIFF_OBJS 
  VCSSVN_H =
  VCSSVN_OBJS =
  VCSSVN_TEST_OBJS =
 +MISC_H =
  EXTRA_CPPFLAGS =
  LIB_H =
  LIB_OBJS =
@@@ -446,7 -440,6 +446,7 @@@ SCRIPT_PERL += git-send-email.per
  SCRIPT_PERL += git-svn.perl
  
  SCRIPT_PYTHON += git-remote-testgit.py
 +SCRIPT_PYTHON += git-p4.py
  
  SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.perl,%,$(SCRIPT_PERL)) \
@@@ -487,11 -480,9 +487,11 @@@ TEST_PROGRAMS_NEED_X += test-genrando
  TEST_PROGRAMS_NEED_X += test-index-version
  TEST_PROGRAMS_NEED_X += test-line-buffer
  TEST_PROGRAMS_NEED_X += test-match-trees
 +TEST_PROGRAMS_NEED_X += test-mergesort
  TEST_PROGRAMS_NEED_X += test-mktemp
  TEST_PROGRAMS_NEED_X += test-parse-options
  TEST_PROGRAMS_NEED_X += test-path-utils
 +TEST_PROGRAMS_NEED_X += test-revision-walking
  TEST_PROGRAMS_NEED_X += test-run-command
  TEST_PROGRAMS_NEED_X += test-sha1
  TEST_PROGRAMS_NEED_X += test-sigchain
@@@ -552,36 -543,6 +552,36 @@@ LIB_FILE=libgit.
  XDIFF_LIB=xdiff/lib.a
  VCSSVN_LIB=vcs-svn/lib.a
  
 +XDIFF_H += xdiff/xinclude.h
 +XDIFF_H += xdiff/xmacros.h
 +XDIFF_H += xdiff/xdiff.h
 +XDIFF_H += xdiff/xtypes.h
 +XDIFF_H += xdiff/xutils.h
 +XDIFF_H += xdiff/xprepare.h
 +XDIFF_H += xdiff/xdiffi.h
 +XDIFF_H += xdiff/xemit.h
 +
 +VCSSVN_H += vcs-svn/line_buffer.h
 +VCSSVN_H += vcs-svn/sliding_window.h
 +VCSSVN_H += vcs-svn/repo_tree.h
 +VCSSVN_H += vcs-svn/fast_export.h
 +VCSSVN_H += vcs-svn/svndiff.h
 +VCSSVN_H += vcs-svn/svndump.h
 +
 +MISC_H += branch.h
 +MISC_H += bundle.h
 +MISC_H += bisect.h
 +MISC_H += common-cmds.h
 +MISC_H += fetch-pack.h
 +MISC_H += thread-utils.h
 +MISC_H += send-pack.h
 +MISC_H += shortlog.h
 +MISC_H += reachable.h
 +MISC_H += wt-status.h
 +MISC_H += tar.h
 +MISC_H += url.h
 +MISC_H += walker.h
 +
  LIB_H += advice.h
  LIB_H += archive.h
  LIB_H += argv-array.h
@@@ -629,7 -590,6 +629,7 @@@ LIB_H += log-tree.
  LIB_H += mailmap.h
  LIB_H += merge-file.h
  LIB_H += merge-recursive.h
 +LIB_H += mergesort.h
  LIB_H += notes.h
  LIB_H += notes-cache.h
  LIB_H += notes-merge.h
@@@ -660,14 -620,12 +660,14 @@@ LIB_H += streaming.
  LIB_H += string-list.h
  LIB_H += submodule.h
  LIB_H += tag.h
 +LIB_H += thread-utils.h
  LIB_H += transport.h
  LIB_H += tree.h
  LIB_H += tree-walk.h
  LIB_H += unpack-trees.h
  LIB_H += userdiff.h
  LIB_H += utf8.h
 +LIB_H += varint.h
  LIB_H += xdiff-interface.h
  LIB_H += xdiff/xdiff.h
  
@@@ -688,6 -646,7 +688,7 @@@ LIB_OBJS += bulk-checkin.
  LIB_OBJS += bundle.o
  LIB_OBJS += cache-tree.o
  LIB_OBJS += color.o
+ LIB_OBJS += column.o
  LIB_OBJS += combine-diff.o
  LIB_OBJS += commit.o
  LIB_OBJS += compat/obstack.o
@@@ -735,7 -694,6 +736,7 @@@ LIB_OBJS += mailmap.
  LIB_OBJS += match-trees.o
  LIB_OBJS += merge-file.o
  LIB_OBJS += merge-recursive.o
 +LIB_OBJS += mergesort.o
  LIB_OBJS += name-hash.o
  LIB_OBJS += notes.o
  LIB_OBJS += notes-cache.o
@@@ -794,7 -752,6 +795,7 @@@ LIB_OBJS += url.
  LIB_OBJS += usage.o
  LIB_OBJS += userdiff.o
  LIB_OBJS += utf8.o
 +LIB_OBJS += varint.o
  LIB_OBJS += walker.o
  LIB_OBJS += wrapper.o
  LIB_OBJS += write_or_die.o
@@@ -818,6 -775,7 +819,7 @@@ BUILTIN_OBJS += builtin/checkout-index.
  BUILTIN_OBJS += builtin/checkout.o
  BUILTIN_OBJS += builtin/clean.o
  BUILTIN_OBJS += builtin/clone.o
+ BUILTIN_OBJS += builtin/column.o
  BUILTIN_OBJS += builtin/commit-tree.o
  BUILTIN_OBJS += builtin/commit.o
  BUILTIN_OBJS += builtin/config.o
@@@ -907,9 -865,6 +909,9 @@@ EXTLIBS 
  # because maintaining the nesting to match is a pain.  If
  # we had "elif" things would have been much nicer...
  
 +ifeq ($(uname_M),x86_64)
 +      XDL_FAST_HASH = YesPlease
 +endif
  ifeq ($(uname_S),OSF1)
        # Need this for u_short definitions et al
        BASIC_CFLAGS += -D_OSF_SOURCE
@@@ -1783,10 -1738,6 +1785,10 @@@ ifndef NO_MSGFMT_EXTENDED_OPTION
        MSGFMT += --check --statistics
  endif
  
 +ifneq (,$(XDL_FAST_HASH))
 +      BASIC_CFLAGS += -DXDL_FAST_HASH
 +endif
 +
  ifeq ($(TCLTK_PATH),)
  NO_TCLTK=NoThanks
  endif
@@@ -1899,13 -1850,6 +1901,13 @@@ DEFAULT_PAGER_CQ_SQ = $(subst ','\'',$(
  BASIC_CFLAGS += -DDEFAULT_PAGER='$(DEFAULT_PAGER_CQ_SQ)'
  endif
  
 +ifdef SHELL_PATH
 +SHELL_PATH_CQ = "$(subst ",\",$(subst \,\\,$(SHELL_PATH)))"
 +SHELL_PATH_CQ_SQ = $(subst ','\'',$(SHELL_PATH_CQ))
 +
 +BASIC_CFLAGS += -DSHELL_PATH='$(SHELL_PATH_CQ_SQ)'
 +endif
 +
  ALL_CFLAGS += $(BASIC_CFLAGS)
  ALL_LDFLAGS += $(BASIC_LDFLAGS)
  
@@@ -2224,11 -2168,28 +2226,12 @@@ builtin/prune.o builtin/reflog.o reacha
  builtin/commit.o builtin/revert.o wt-status.o: wt-status.h
  builtin/tar-tree.o archive-tar.o: tar.h
  connect.o transport.o url.o http-backend.o: url.h
+ builtin/branch.o builtin/commit.o builtin/tag.o column.o help.o pager.o: column.h
  http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h
  http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h
  
 -XDIFF_H += xdiff/xinclude.h
 -XDIFF_H += xdiff/xmacros.h
 -XDIFF_H += xdiff/xdiff.h
 -XDIFF_H += xdiff/xtypes.h
 -XDIFF_H += xdiff/xutils.h
 -XDIFF_H += xdiff/xprepare.h
 -XDIFF_H += xdiff/xdiffi.h
 -XDIFF_H += xdiff/xemit.h
 -
  xdiff-interface.o $(XDIFF_OBJS): $(XDIFF_H)
  
 -VCSSVN_H += vcs-svn/line_buffer.h
 -VCSSVN_H += vcs-svn/sliding_window.h
 -VCSSVN_H += vcs-svn/repo_tree.h
 -VCSSVN_H += vcs-svn/fast_export.h
 -VCSSVN_H += vcs-svn/svndiff.h
 -VCSSVN_H += vcs-svn/svndump.h
 -
  $(VCSSVN_OBJS) $(VCSSVN_TEST_OBJS): $(LIB_H) $(VCSSVN_H)
  endif
  
@@@ -2299,8 -2260,6 +2302,8 @@@ $(XDIFF_LIB): $(XDIFF_OBJS
  $(VCSSVN_LIB): $(VCSSVN_OBJS)
        $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(VCSSVN_OBJS)
  
 +export DEFAULT_EDITOR DEFAULT_PAGER
 +
  doc:
        $(MAKE) -C Documentation all
  
@@@ -2325,7 -2284,7 +2328,7 @@@ XGETTEXT_FLAGS_C = $(XGETTEXT_FLAGS) --
        --keyword=_ --keyword=N_ --keyword="Q_:1,2"
  XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell
  XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --keyword=__ --language=Perl
 -LOCALIZED_C := $(C_OBJ:o=c)
 +LOCALIZED_C := $(C_OBJ:o=c) $(LIB_H) $(XDIFF_H) $(VCSSVN_H) $(MISC_H)
  LOCALIZED_SH := $(SCRIPT_SH)
  LOCALIZED_PERL := $(SCRIPT_PERL)
  
@@@ -2678,6 -2637,7 +2681,6 @@@ dist-doc
  
  distclean: clean
        $(RM) configure
 -      $(RM) po/git.pot
  
  profile-clean:
        $(RM) $(addsuffix *.gcda,$(addprefix $(PROFILE_DIR)/, $(object_dirs)))
diff --combined builtin/branch.c
index f12b626a0ece679cd18a77345c3591b15d5f6cbc,9634c71ea20777964c0aa86416d4849103be793d..d51648fee4e73ef7fb72f32c722b79a913d6b275
@@@ -15,6 -15,8 +15,8 @@@
  #include "branch.h"
  #include "diff.h"
  #include "revision.h"
+ #include "string-list.h"
+ #include "column.h"
  
  static const char * const builtin_branch_usage[] = {
        "git branch [options] [-r | -a] [--merged | --no-merged]",
@@@ -53,6 -55,9 +55,9 @@@ static enum merge_filter 
  } merge_filter;
  static unsigned char merge_filter_ref[20];
  
+ static struct string_list output = STRING_LIST_INIT_DUP;
+ static unsigned int colopts;
  static int parse_branch_color_slot(const char *var, int ofs)
  {
        if (!strcasecmp(var+ofs, "plain"))
@@@ -70,6 -75,8 +75,8 @@@
  
  static int git_branch_config(const char *var, const char *value, void *cb)
  {
+       if (!prefixcmp(var, "column."))
+               return git_column_config(var, value, "branch", &colopts);
        if (!strcmp(var, "color.branch")) {
                branch_use_color = git_config_colorbool(var, value);
                return 0;
@@@ -146,28 -153,26 +153,28 @@@ static int branch_merged(int kind, cons
        return merged;
  }
  
 -static int delete_branches(int argc, const char **argv, int force, int kinds)
 +static int delete_branches(int argc, const char **argv, int force, int kinds,
 +                         int quiet)
  {
        struct commit *rev, *head_rev = NULL;
        unsigned char sha1[20];
        char *name = NULL;
 -      const char *fmt, *remote;
 +      const char *fmt;
        int i;
        int ret = 0;
 +      int remote_branch = 0;
        struct strbuf bname = STRBUF_INIT;
  
        switch (kinds) {
        case REF_REMOTE_BRANCH:
                fmt = "refs/remotes/%s";
 -              /* TRANSLATORS: This is "remote " in "remote branch '%s' not found" */
 -              remote = _("remote ");
 +              /* For subsequent UI messages */
 +              remote_branch = 1;
 +
                force = 1;
                break;
        case REF_LOCAL_BRANCH:
                fmt = "refs/heads/%s";
 -              remote = "";
                break;
        default:
                die(_("cannot use -a with -d"));
  
                name = xstrdup(mkpath(fmt, bname.buf));
                if (read_ref(name, sha1)) {
 -                      error(_("%sbranch '%s' not found."),
 -                                      remote, bname.buf);
 +                      error(remote_branch
 +                            ? _("remote branch '%s' not found.")
 +                            : _("branch '%s' not found."), bname.buf);
                        ret = 1;
                        continue;
                }
                }
  
                if (delete_ref(name, sha1, 0)) {
 -                      error(_("Error deleting %sbranch '%s'"), remote,
 +                      error(remote_branch
 +                            ? _("Error deleting remote branch '%s'")
 +                            : _("Error deleting branch '%s'"),
                              bname.buf);
                        ret = 1;
                } else {
                        struct strbuf buf = STRBUF_INIT;
 -                      printf(_("Deleted %sbranch %s (was %s).\n"), remote,
 -                             bname.buf,
 -                             find_unique_abbrev(sha1, DEFAULT_ABBREV));
 +                      if (!quiet)
 +                              printf(remote_branch
 +                                     ? _("Deleted remote branch %s (was %s).\n")
 +                                     : _("Deleted branch %s (was %s).\n"),
 +                                     bname.buf,
 +                                     find_unique_abbrev(sha1, DEFAULT_ABBREV));
                        strbuf_addf(&buf, "branch.%s", bname.buf);
                        if (git_config_rename_section(buf.buf, NULL) < 0)
                                warning(_("Update of config-file failed"));
@@@ -482,7 -481,12 +489,12 @@@ static void print_ref_item(struct ref_i
        else if (verbose)
                /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */
                add_verbose_info(&out, item, verbose, abbrev);
-       printf("%s\n", out.buf);
+       if (column_active(colopts)) {
+               assert(!verbose && "--column and --verbose are incompatible");
+               string_list_append(&output, out.buf);
+       } else {
+               printf("%s\n", out.buf);
+       }
        strbuf_release(&name);
        strbuf_release(&out);
  }
@@@ -538,10 -542,6 +550,10 @@@ static int print_ref_list(int kinds, in
        if (merge_filter != NO_FILTER) {
                struct commit *filter;
                filter = lookup_commit_reference_gently(merge_filter_ref, 0);
 +              if (!filter)
 +                      die("object '%s' does not point to a commit",
 +                          sha1_to_hex(merge_filter_ref));
 +
                filter->object.flags |= UNINTERESTING;
                add_pending_object(&ref_list.revs,
                                   (struct object *) filter, "");
@@@ -663,7 -663,7 +675,7 @@@ static int edit_branch_description(cons
        fp = fopen(git_path(edit_description), "w");
        if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) {
                strbuf_release(&buf);
 -              return error(_("could not write branch description template: %s\n"),
 +              return error(_("could not write branch description template: %s"),
                             strerror(errno));
        }
        strbuf_reset(&buf);
@@@ -686,7 -686,6 +698,7 @@@ int cmd_branch(int argc, const char **a
        int delete = 0, rename = 0, force_create = 0, list = 0;
        int verbose = 0, abbrev = -1, detached = 0;
        int reflog = 0, edit_description = 0;
 +      int quiet = 0;
        enum branch_track track;
        int kinds = REF_LOCAL_BRANCH;
        struct commit_list *with_commit = NULL;
                OPT_GROUP("Generic options"),
                OPT__VERBOSE(&verbose,
                        "show hash and subject, give twice for upstream branch"),
 +              OPT__QUIET(&quiet, "suppress informational messages"),
                OPT_SET_INT('t', "track",  &track, "set up tracking mode (see git-pull(1))",
                        BRANCH_TRACK_EXPLICIT),
                OPT_SET_INT( 0, "set-upstream",  &track, "change upstream info",
                        PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
                        opt_parse_merge_filter, (intptr_t) "HEAD",
                },
+               OPT_COLUMN(0, "column", &colopts, "list branches in columns"),
                OPT_END(),
        };
  
        }
        hashcpy(merge_filter_ref, head_sha1);
  
        argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
                             0);
  
  
        if (abbrev == -1)
                abbrev = DEFAULT_ABBREV;
+       finalize_colopts(&colopts, -1);
+       if (verbose) {
+               if (explicitly_enable_column(colopts))
+                       die(_("--column and --verbose are incompatible"));
+               colopts = 0;
+       }
  
        if (delete)
 -              return delete_branches(argc, argv, delete > 1, kinds);
 +              return delete_branches(argc, argv, delete > 1, kinds, quiet);
-       else if (list)
-               return print_ref_list(kinds, detached, verbose, abbrev,
-                                     with_commit, argv);
+       else if (list) {
+               int ret = print_ref_list(kinds, detached, verbose, abbrev,
+                                        with_commit, argv);
+               print_columns(&output, colopts, NULL);
+               string_list_clear(&output, 0);
+               return ret;
+       }
        else if (edit_description) {
                const char *branch_name;
                struct strbuf branch_ref = STRBUF_INIT;
                if (kinds != REF_LOCAL_BRANCH)
                        die(_("-a and -r options to 'git branch' do not make sense with a branch name"));
                create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
 -                            force_create, reflog, 0, track);
 +                            force_create, reflog, 0, quiet, track);
        } else
                usage_with_options(builtin_branch_usage, options);
  
diff --combined builtin/commit.c
index 01780293aa6afcd81e2f37490b59e44cf553bdca,cc1a70902984a7b3f84fc13a0cb96e29cd5ac501..a876a73e6b4c1f1690839ce2d447a4b77573c9f5
@@@ -27,6 -27,7 +27,7 @@@
  #include "quote.h"
  #include "submodule.h"
  #include "gpg-interface.h"
+ #include "column.h"
  
  static const char * const builtin_commit_usage[] = {
        "git commit [options] [--] <filepattern>...",
@@@ -88,6 -89,7 +89,7 @@@ static int quiet, verbose, no_verify, a
  static int no_post_rewrite, allow_empty_message;
  static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
  static char *sign_commit;
+ static unsigned int colopts;
  
  /*
   * The default commit message cleanup mode will remove the lines
@@@ -194,6 -196,24 +196,6 @@@ static void determine_whence(struct wt_
                s->whence = whence;
  }
  
 -static const char *whence_s(void)
 -{
 -      const char *s = "";
 -
 -      switch (whence) {
 -      case FROM_COMMIT:
 -              break;
 -      case FROM_MERGE:
 -              s = _("merge");
 -              break;
 -      case FROM_CHERRY_PICK:
 -              s = _("cherry-pick");
 -              break;
 -      }
 -
 -      return s;
 -}
 -
  static void rollback_index_files(void)
  {
        switch (commit_style) {
@@@ -435,12 -455,8 +437,12 @@@ static char *prepare_index(int argc, co
         */
        commit_style = COMMIT_PARTIAL;
  
 -      if (whence != FROM_COMMIT)
 -              die(_("cannot do a partial commit during a %s."), whence_s());
 +      if (whence != FROM_COMMIT) {
 +              if (whence == FROM_MERGE)
 +                      die(_("cannot do a partial commit during a merge."));
 +              else if (whence == FROM_CHERRY_PICK)
 +                      die(_("cannot do a partial commit during a cherry-pick."));
 +      }
  
        memset(&partial, 0, sizeof(partial));
        partial.strdup_strings = 1;
@@@ -519,20 -535,9 +521,20 @@@ static int is_a_merge(const struct comm
  
  static const char sign_off_header[] = "Signed-off-by: ";
  
 +static void export_one(const char *var, const char *s, const char *e, int hack)
 +{
 +      struct strbuf buf = STRBUF_INIT;
 +      if (hack)
 +              strbuf_addch(&buf, hack);
 +      strbuf_addf(&buf, "%.*s", (int)(e - s), s);
 +      setenv(var, buf.buf, 1);
 +      strbuf_release(&buf);
 +}
 +
  static void determine_author_info(struct strbuf *author_ident)
  {
        char *name, *email, *date;
 +      struct ident_split author;
  
        name = getenv("GIT_AUTHOR_NAME");
        email = getenv("GIT_AUTHOR_EMAIL");
                date = force_date;
        strbuf_addstr(author_ident, fmt_ident(name, email, date,
                                              IDENT_ERROR_ON_NO_NAME));
 +      if (!split_ident_line(&author, author_ident->buf, author_ident->len)) {
 +              export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0);
 +              export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0);
 +              export_one("GIT_AUTHOR_DATE", author.date_begin, author.tz_end, '@');
 +      }
  }
  
  static int ends_rfc2822_footer(struct strbuf *sb)
@@@ -654,9 -654,6 +656,9 @@@ static int prepare_to_commit(const cha
        int ident_shown = 0;
        int clean_message_contents = (cleanup_mode != CLEANUP_NONE);
  
 +      /* This checks and barfs if author is badly specified */
 +      determine_author_info(author_ident);
 +
        if (!no_verify && run_hook(index_file, "pre-commit", NULL))
                return 0;
  
  
        strbuf_release(&sb);
  
 -      /* This checks and barfs if author is badly specified */
 -      determine_author_info(author_ident);
 -
        /* This checks if committer ident is explicitly given */
        strbuf_addstr(&committer_ident, git_committer_info(0));
        if (use_editor && include_status) {
                char *ai_tmp, *ci_tmp;
                if (whence != FROM_COMMIT)
                        status_printf_ln(s, GIT_COLOR_NORMAL,
 -                              _("\n"
 -                              "It looks like you may be committing a %s.\n"
 -                              "If this is not correct, please remove the file\n"
 -                              "       %s\n"
 -                              "and try again.\n"
 -                              ""),
 -                              whence_s(),
 +                          whence == FROM_MERGE
 +                              ? _("\n"
 +                                      "It looks like you may be committing a merge.\n"
 +                                      "If this is not correct, please remove the file\n"
 +                                      "       %s\n"
 +                                      "and try again.\n")
 +                              : _("\n"
 +                                      "It looks like you may be committing a cherry-pick.\n"
 +                                      "If this is not correct, please remove the file\n"
 +                                      "       %s\n"
 +                                      "and try again.\n"),
                                git_path(whence == FROM_MERGE
                                         ? "MERGE_HEAD"
                                         : "CHERRY_PICK_HEAD"));
  
                fprintf(s->fp, "\n");
 -              status_printf(s, GIT_COLOR_NORMAL,
 -                      _("Please enter the commit message for your changes."));
                if (cleanup_mode == CLEANUP_ALL)
 -                      status_printf_more(s, GIT_COLOR_NORMAL,
 -                              _(" Lines starting\n"
 -                              "with '#' will be ignored, and an empty"
 +                      status_printf(s, GIT_COLOR_NORMAL,
 +                              _("Please enter the commit message for your changes."
 +                              " Lines starting\nwith '#' will be ignored, and an empty"
                                " message aborts the commit.\n"));
                else /* CLEANUP_SPACE, that is. */
 -                      status_printf_more(s, GIT_COLOR_NORMAL,
 -                              _(" Lines starting\n"
 +                      status_printf(s, GIT_COLOR_NORMAL,
 +                              _("Please enter the commit message for your changes."
 +                              " Lines starting\n"
                                "with '#' will be kept; you may remove them"
                                " yourself if you want to.\n"
                                "An empty message aborts the commit.\n"));
        return 1;
  }
  
 -/*
 - * Find out if the message in the strbuf contains only whitespace and
 - * Signed-off-by lines.
 - */
 -static int message_is_empty(struct strbuf *sb)
 +static int rest_is_empty(struct strbuf *sb, int start)
  {
 -      struct strbuf tmpl = STRBUF_INIT;
 +      int i, eol;
        const char *nl;
 -      int eol, i, start = 0;
 -
 -      if (cleanup_mode == CLEANUP_NONE && sb->len)
 -              return 0;
 -
 -      /* See if the template is just a prefix of the message. */
 -      if (template_file && strbuf_read_file(&tmpl, template_file, 0) > 0) {
 -              stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
 -              if (start + tmpl.len <= sb->len &&
 -                  memcmp(tmpl.buf, sb->buf + start, tmpl.len) == 0)
 -                      start += tmpl.len;
 -      }
 -      strbuf_release(&tmpl);
  
        /* Check if the rest is just whitespace and Signed-of-by's. */
        for (i = start; i < sb->len; i++) {
        return 1;
  }
  
 +/*
 + * Find out if the message in the strbuf contains only whitespace and
 + * Signed-off-by lines.
 + */
 +static int message_is_empty(struct strbuf *sb)
 +{
 +      if (cleanup_mode == CLEANUP_NONE && sb->len)
 +              return 0;
 +      return rest_is_empty(sb, 0);
 +}
 +
 +/*
 + * See if the user edited the message in the editor or left what
 + * was in the template intact
 + */
 +static int template_untouched(struct strbuf *sb)
 +{
 +      struct strbuf tmpl = STRBUF_INIT;
 +      char *start;
 +
 +      if (cleanup_mode == CLEANUP_NONE && sb->len)
 +              return 0;
 +
 +      if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0)
 +              return 0;
 +
 +      stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
 +      start = (char *)skip_prefix(sb->buf, tmpl.buf);
 +      if (!start)
 +              start = sb->buf;
 +      strbuf_release(&tmpl);
 +      return rest_is_empty(sb, start - sb->buf);
 +}
 +
  static const char *find_author_by_nickname(const char *name)
  {
        struct rev_info revs;
@@@ -1061,12 -1041,8 +1063,12 @@@ static int parse_and_validate_options(i
        /* Sanity check options */
        if (amend && !current_head)
                die(_("You have nothing to amend."));
 -      if (amend && whence != FROM_COMMIT)
 -              die(_("You are in the middle of a %s -- cannot amend."), whence_s());
 +      if (amend && whence != FROM_COMMIT) {
 +              if (whence == FROM_MERGE)
 +                      die(_("You are in the middle of a merge -- cannot amend."));
 +              else if (whence == FROM_CHERRY_PICK)
 +                      die(_("You are in the middle of a cherry-pick -- cannot amend."));
 +      }
        if (fixup_message && squash_message)
                die(_("Options --squash and --fixup cannot be used together"));
        if (use_message)
                die(_("Only one of -c/-C/-F/--fixup can be used."));
        if (message.len && f > 0)
                die((_("Option -m cannot be combined with -c/-C/-F/--fixup.")));
 +      if (f || message.len)
 +              template_file = NULL;
        if (edit_message)
                use_message = edit_message;
        if (amend && !use_message && !fixup_message)
@@@ -1173,6 -1147,8 +1175,8 @@@ static int git_status_config(const cha
  {
        struct wt_status *s = cb;
  
+       if (!prefixcmp(k, "column."))
+               return git_column_config(k, v, "status", &colopts);
        if (!strcmp(k, "status.submodulesummary")) {
                int is_bool;
                s->submodule_summary = git_config_bool_or_int(k, v, &is_bool);
@@@ -1238,6 -1214,7 +1242,7 @@@ int cmd_status(int argc, const char **a
                { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, "when",
                  "ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)",
                  PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+               OPT_COLUMN(0, "column", &colopts, "list untracked files in columns"),
                OPT_END(),
        };
  
        argc = parse_options(argc, argv, prefix,
                             builtin_status_options,
                             builtin_status_usage, 0);
+       finalize_colopts(&colopts, -1);
+       s.colopts = colopts;
  
        if (null_termination && status_format == STATUS_FORMAT_LONG)
                status_format = STATUS_FORMAT_PORCELAIN;
@@@ -1522,11 -1501,6 +1529,11 @@@ int cmd_commit(int argc, const char **a
  
        if (cleanup_mode != CLEANUP_NONE)
                stripspace(&sb, cleanup_mode == CLEANUP_ALL);
 +      if (template_untouched(&sb) && !allow_empty_message) {
 +              rollback_index_files();
 +              fprintf(stderr, _("Aborting commit; you did not edit the message.\n"));
 +              exit(1);
 +      }
        if (message_is_empty(&sb) && !allow_empty_message) {
                rollback_index_files();
                fprintf(stderr, _("Aborting commit due to empty commit message.\n"));
diff --combined builtin/help.c
index e63668ade4dd0721c27ca5045bc8a524a147f9c1,c64f1522d839446b48154f15e189b831b047cab7..43d3c84449a57ec7028acb663d4ab4bea134c97e
@@@ -9,6 -9,7 +9,7 @@@
  #include "common-cmds.h"
  #include "parse-options.h"
  #include "run-command.h"
+ #include "column.h"
  #include "help.h"
  
  static struct man_viewer_list {
@@@ -30,6 -31,7 +31,7 @@@ enum help_format 
  };
  
  static int show_all = 0;
+ static unsigned int colopts;
  static enum help_format help_format = HELP_FORMAT_NONE;
  static struct option builtin_help_options[] = {
        OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
@@@ -54,7 -56,7 +56,7 @@@ static enum help_format parse_help_form
                return HELP_FORMAT_INFO;
        if (!strcmp(format, "web") || !strcmp(format, "html"))
                return HELP_FORMAT_WEB;
 -      die("unrecognized help format '%s'", format);
 +      die(_("unrecognized help format '%s'"), format);
  }
  
  static const char *get_man_viewer_info(const char *name)
@@@ -82,7 -84,7 +84,7 @@@ static int check_emacsclient_version(vo
        ec_process.err = -1;
        ec_process.stdout_to_stderr = 1;
        if (start_command(&ec_process))
 -              return error("Failed to start emacsclient.");
 +              return error(_("Failed to start emacsclient."));
  
        strbuf_read(&buffer, ec_process.err, 20);
        close(ec_process.err);
@@@ -95,7 -97,7 +97,7 @@@
  
        if (prefixcmp(buffer.buf, "emacsclient")) {
                strbuf_release(&buffer);
 -              return error("Failed to parse emacsclient version.");
 +              return error(_("Failed to parse emacsclient version."));
        }
  
        strbuf_remove(&buffer, 0, strlen("emacsclient"));
  
        if (version < 22) {
                strbuf_release(&buffer);
 -              return error("emacsclient version '%d' too old (< 22).",
 +              return error(_("emacsclient version '%d' too old (< 22)."),
                        version);
        }
  
@@@ -121,7 -123,7 +123,7 @@@ static void exec_woman_emacs(const cha
                        path = "emacsclient";
                strbuf_addf(&man_page, "(woman \"%s\")", page);
                execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL);
 -              warning("failed to exec '%s': %s", path, strerror(errno));
 +              warning(_("failed to exec '%s': %s"), path, strerror(errno));
        }
  }
  
@@@ -149,7 -151,7 +151,7 @@@ static void exec_man_konqueror(const ch
                        path = "kfmclient";
                strbuf_addf(&man_page, "man:%s(1)", page);
                execlp(path, filename, "newTab", man_page.buf, (char *)NULL);
 -              warning("failed to exec '%s': %s", path, strerror(errno));
 +              warning(_("failed to exec '%s': %s"), path, strerror(errno));
        }
  }
  
@@@ -158,7 -160,7 +160,7 @@@ static void exec_man_man(const char *pa
        if (!path)
                path = "man";
        execlp(path, "man", page, (char *)NULL);
 -      warning("failed to exec '%s': %s", path, strerror(errno));
 +      warning(_("failed to exec '%s': %s"), path, strerror(errno));
  }
  
  static void exec_man_cmd(const char *cmd, const char *page)
        struct strbuf shell_cmd = STRBUF_INIT;
        strbuf_addf(&shell_cmd, "%s %s", cmd, page);
        execl("/bin/sh", "sh", "-c", shell_cmd.buf, (char *)NULL);
 -      warning("failed to exec '%s': %s", cmd, strerror(errno));
 +      warning(_("failed to exec '%s': %s"), cmd, strerror(errno));
  }
  
  static void add_man_viewer(const char *name)
@@@ -206,8 -208,8 +208,8 @@@ static int add_man_viewer_path(const ch
        if (supported_man_viewer(name, len))
                do_add_man_viewer_info(name, len, value);
        else
 -              warning("'%s': path for unsupported man viewer.\n"
 -                      "Please consider using 'man.<tool>.cmd' instead.",
 +              warning(_("'%s': path for unsupported man viewer.\n"
 +                        "Please consider using 'man.<tool>.cmd' instead."),
                        name);
  
        return 0;
@@@ -218,8 -220,8 +220,8 @@@ static int add_man_viewer_cmd(const cha
                              const char *value)
  {
        if (supported_man_viewer(name, len))
 -              warning("'%s': cmd for supported man viewer.\n"
 -                      "Please consider using 'man.<tool>.path' instead.",
 +              warning(_("'%s': cmd for supported man viewer.\n"
 +                        "Please consider using 'man.<tool>.path' instead."),
                        name);
        else
                do_add_man_viewer_info(name, len, value);
@@@ -251,6 -253,8 +253,8 @@@ static int add_man_viewer_info(const ch
  
  static int git_help_config(const char *var, const char *value, void *cb)
  {
+       if (!prefixcmp(var, "column."))
+               return git_column_config(var, value, "help", &colopts);
        if (!strcmp(var, "help.format")) {
                if (!value)
                        return config_error_nonbool(var);
@@@ -280,11 -284,11 +284,11 @@@ void list_common_cmds_help(void
                        longest = strlen(common_cmds[i].name);
        }
  
 -      puts("The most commonly used git commands are:");
 +      puts(_("The most commonly used git commands are:"));
        for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
                printf("   %s   ", common_cmds[i].name);
                mput_char(' ', longest - strlen(common_cmds[i].name));
 -              puts(common_cmds[i].help);
 +              puts(_(common_cmds[i].help));
        }
  }
  
@@@ -348,7 -352,7 +352,7 @@@ static void exec_viewer(const char *nam
        else if (info)
                exec_man_cmd(info, page);
        else
 -              warning("'%s': unknown man viewer.", name);
 +              warning(_("'%s': unknown man viewer."), name);
  }
  
  static void show_man_page(const char *git_cmd)
        if (fallback)
                exec_viewer(fallback, page);
        exec_viewer("man", page);
 -      die("no man viewer handled the request");
 +      die(_("no man viewer handled the request"));
  }
  
  static void show_info_page(const char *git_cmd)
        const char *page = cmd_to_page(git_cmd);
        setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
        execlp("info", "info", "gitman", page, (char *)NULL);
 -      die("no info viewer handled the request");
 +      die(_("no info viewer handled the request"));
  }
  
  static void get_html_page_path(struct strbuf *page_path, const char *page)
        /* Check that we have a git documentation directory. */
        if (stat(mkpath("%s/git.html", html_path), &st)
            || !S_ISREG(st.st_mode))
 -              die("'%s': not a documentation directory.", html_path);
 +              die(_("'%s': not a documentation directory."), html_path);
  
        strbuf_init(page_path, 0);
        strbuf_addf(page_path, "%s/%s.html", html_path, page);
@@@ -424,16 -428,17 +428,17 @@@ int cmd_help(int argc, const char **arg
        parsed_help_format = help_format;
  
        if (show_all) {
 -              printf("usage: %s\n\n", git_usage_string);
 -              list_commands("git commands", colopts, &main_cmds, &other_cmds);
 -              printf("%s\n", git_more_info_string);
+               git_config(git_help_config, NULL);
-               list_commands(&main_cmds, &other_cmds);
 +              printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
++              list_commands(colopts, &main_cmds, &other_cmds);
 +              printf("%s\n", _(git_more_info_string));
                return 0;
        }
  
        if (!argv[0]) {
 -              printf("usage: %s\n\n", git_usage_string);
 +              printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
                list_common_cmds_help();
 -              printf("\n%s\n", git_more_info_string);
 +              printf("\n%s\n", _(git_more_info_string));
                return 0;
        }
  
  
        alias = alias_lookup(argv[0]);
        if (alias && !is_git_command(argv[0])) {
 -              printf("`git %s' is aliased to `%s'\n", argv[0], alias);
 +              printf_ln(_("`git %s' is aliased to `%s'"), argv[0], alias);
                return 0;
        }
  
diff --combined command-list.txt
index 38ec5f7b8617ad66f95597ab1bfefce8480c39a3,fe06f158fee39484b1b42495d119e5b7f8583438..14ea67af038fbfce388dd20b8adcda503106911d
@@@ -20,6 -20,7 +20,7 @@@ git-cherry-pic
  git-citool                              mainporcelain
  git-clean                               mainporcelain
  git-clone                               mainporcelain common
+ git-column                              purehelpers
  git-commit                              mainporcelain common
  git-commit-tree                         plumbingmanipulators
  git-config                              ancillarymanipulators
@@@ -76,7 -77,6 +77,7 @@@ git-mktre
  git-mv                                  mainporcelain common
  git-name-rev                            plumbinginterrogators
  git-notes                               mainporcelain
 +git-p4                                  foreignscminterface
  git-pack-objects                        plumbingmanipulators
  git-pack-redundant                      plumbinginterrogators
  git-pack-refs                           ancillarymanipulators
diff --combined git.c
index 4486debf7dfb2db263cf88a825979505fee5c635,ee727cbe1a1764c2ff07391c9b7f29b80acaa4f8..d232de92e496bd5750015edce22d3b67f236b605
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -13,7 -13,7 +13,7 @@@ const char git_usage_string[] 
        "           <command> [<args>]";
  
  const char git_more_info_string[] =
 -      "See 'git help <command>' for more information on a specific command.";
 +      N_("See 'git help <command>' for more information on a specific command.");
  
  static struct startup_info git_startup_info;
  static int use_pager = -1;
@@@ -348,6 -348,7 +348,7 @@@ static void handle_internal_command(in
                { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
                { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
                { "clone", cmd_clone },
+               { "column", cmd_column, RUN_SETUP_GENTLY },
                { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
                { "commit-tree", cmd_commit_tree, RUN_SETUP },
                { "config", cmd_config, RUN_SETUP_GENTLY },
diff --combined help.c
index a39b7bf8971e3ea54f0e0c0f675b3613b156eecd,a815ae62a68f55bcee4b0584f1423063450c4d8c..69d483d8d3b7e109345b1e8124df6b023ed312fb
--- 1/help.c
--- 2/help.c
+++ b/help.c
@@@ -4,6 -4,8 +4,8 @@@
  #include "levenshtein.h"
  #include "help.h"
  #include "common-cmds.h"
+ #include "string-list.h"
+ #include "column.h"
  
  void add_cmdname(struct cmdnames *cmds, const char *name, int len)
  {
@@@ -70,31 -72,25 +72,25 @@@ void exclude_cmds(struct cmdnames *cmds
        cmds->cnt = cj;
  }
  
- static void pretty_print_string_list(struct cmdnames *cmds, int longest)
+ static void pretty_print_string_list(struct cmdnames *cmds,
+                                    unsigned int colopts)
  {
-       int cols = 1, rows;
-       int space = longest + 1; /* min 1 SP between words */
-       int max_cols = term_columns() - 1; /* don't print *on* the edge */
-       int i, j;
-       if (space < max_cols)
-               cols = max_cols / space;
-       rows = DIV_ROUND_UP(cmds->cnt, cols);
-       for (i = 0; i < rows; i++) {
-               printf("  ");
+       struct string_list list = STRING_LIST_INIT_NODUP;
+       struct column_options copts;
+       int i;
  
-               for (j = 0; j < cols; j++) {
-                       int n = j * rows + i;
-                       int size = space;
-                       if (n >= cmds->cnt)
-                               break;
-                       if (j == cols-1 || n + rows >= cmds->cnt)
-                               size = 1;
-                       printf("%-*s", size, cmds->names[n]->name);
-               }
-               putchar('\n');
-       }
+       for (i = 0; i < cmds->cnt; i++)
+               string_list_append(&list, cmds->names[i]->name);
+       /*
+        * always enable column display, we only consult column.*
+        * about layout strategy and stuff
+        */
+       colopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
+       memset(&copts, 0, sizeof(copts));
+       copts.indent = "  ";
+       copts.padding = 2;
+       print_columns(&list, colopts, &copts);
+       string_list_clear(&list, 0);
  }
  
  static int is_executable(const char *name)
@@@ -203,29 -199,25 +199,21 @@@ void load_command_list(const char *pref
        exclude_cmds(other_cmds, main_cmds);
  }
  
- void list_commands(struct cmdnames *main_cmds, struct cmdnames *other_cmds)
 -void list_commands(const char *title, unsigned int colopts,
++void list_commands(unsigned int colopts,
+                  struct cmdnames *main_cmds, struct cmdnames *other_cmds)
  {
-       int i, longest = 0;
-       for (i = 0; i < main_cmds->cnt; i++)
-               if (longest < main_cmds->names[i]->len)
-                       longest = main_cmds->names[i]->len;
-       for (i = 0; i < other_cmds->cnt; i++)
-               if (longest < other_cmds->names[i]->len)
-                       longest = other_cmds->names[i]->len;
        if (main_cmds->cnt) {
                const char *exec_path = git_exec_path();
 -              printf("available %s in '%s'\n", title, exec_path);
 -              printf("----------------");
 -              mput_char('-', strlen(title) + strlen(exec_path));
 +              printf_ln(_("available git commands in '%s'"), exec_path);
                putchar('\n');
-               pretty_print_string_list(main_cmds, longest);
+               pretty_print_string_list(main_cmds, colopts);
                putchar('\n');
        }
  
        if (other_cmds->cnt) {
 -              printf("%s available from elsewhere on your $PATH\n", title);
 -              printf("---------------------------------------");
 -              mput_char('-', strlen(title));
 +              printf_ln(_("git commands available from elsewhere on your $PATH"));
                putchar('\n');
-               pretty_print_string_list(other_cmds, longest);
+               pretty_print_string_list(other_cmds, colopts);
                putchar('\n');
        }
  }
@@@ -336,7 -328,7 +324,7 @@@ const char *help_unknown_cmd(const cha
              sizeof(*main_cmds.names), levenshtein_compare);
  
        if (!main_cmds.cnt)
 -              die ("Uh oh. Your system reports no Git commands at all.");
 +              die(_("Uh oh. Your system reports no Git commands at all."));
  
        /* skip and count prefix matches */
        for (n = 0; n < main_cmds.cnt && !main_cmds.names[n]->len; n++)
                const char *assumed = main_cmds.names[0]->name;
                main_cmds.names[0] = NULL;
                clean_cmdnames(&main_cmds);
 -              fprintf(stderr, "WARNING: You called a Git command named '%s', "
 -                      "which does not exist.\n"
 -                      "Continuing under the assumption that you meant '%s'\n",
 +              fprintf_ln(stderr,
 +                         _("WARNING: You called a Git command named '%s', "
 +                           "which does not exist.\n"
 +                           "Continuing under the assumption that you meant '%s'"),
                        cmd, assumed);
                if (autocorrect > 0) {
 -                      fprintf(stderr, "in %0.1f seconds automatically...\n",
 +                      fprintf_ln(stderr, _("in %0.1f seconds automatically..."),
                                (float)autocorrect/10.0);
                        poll(NULL, 0, autocorrect * 100);
                }
                return assumed;
        }
  
 -      fprintf(stderr, "git: '%s' is not a git command. See 'git --help'.\n", cmd);
 +      fprintf_ln(stderr, _("git: '%s' is not a git command. See 'git --help'."), cmd);
  
        if (SIMILAR_ENOUGH(best_similarity)) {
 -              fprintf(stderr, "\nDid you mean %s?\n",
 -                      n < 2 ? "this": "one of these");
 +              fprintf_ln(stderr,
 +                         Q_("\nDid you mean this?",
 +                            "\nDid you mean one of these?",
 +                         n));
  
                for (i = 0; i < n; i++)
                        fprintf(stderr, "\t%s\n", main_cmds.names[i]->name);
diff --combined help.h
index dc406c8c50ad2c851b76d446e829550c6c444631,854d2d43d77f089767b828de48085c1490ecfd7f..0ae5a124a3af9912d551caed909aac77acc59b0b
--- 1/help.h
--- 2/help.h
+++ b/help.h
@@@ -25,7 -25,8 +25,6 @@@ extern void add_cmdname(struct cmdname
  /* Here we require that excludes is a sorted list. */
  extern void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes);
  extern int is_in_cmdlist(struct cmdnames *cmds, const char *name);
- extern void list_commands(struct cmdnames *main_cmds,
 -extern void list_commands(const char *title, unsigned int colopts,
 -                        struct cmdnames *main_cmds,
--                        struct cmdnames *other_cmds);
++extern void list_commands(unsigned int colopts, struct cmdnames *main_cmds, struct cmdnames *other_cmds);
  
  #endif /* HELP_H */
diff --combined parse-options.h
index def9ced739b675cbcdac7598c3baebf5423239a0,56fcafda5a788220328229ff3bc3d095880815ba..da999f8995ee79865bd4c2f72a1e009502a69926
@@@ -40,6 -40,7 +40,6 @@@ enum parse_opt_option_flags 
        PARSE_OPT_LASTARG_DEFAULT = 16,
        PARSE_OPT_NODASH = 32,
        PARSE_OPT_LITERAL_ARGHELP = 64,
 -      PARSE_OPT_NEGHELP = 128,
        PARSE_OPT_SHELL_EVAL = 256
  };
  
@@@ -89,6 -90,9 +89,6 @@@ typedef int parse_opt_ll_cb(struct pars
   *   PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets
   *                            (i.e. '<argh>') in the help message.
   *                            Useful for options with multiple parameters.
 - *   PARSE_OPT_NEGHELP: says that the long option should always be shown with
 - *                            the --no prefix in the usage message. Sometimes
 - *                            useful for users of OPTION_NEGBIT.
   *
   * `callback`::
   *   pointer to the callback to use for OPTION_CALLBACK or
@@@ -234,5 -238,7 +234,7 @@@ extern int parse_opt_noop_cb(const stru
          PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }
  #define OPT__COLOR(var, h) \
        OPT_COLOR_FLAG(0, "color", (var), (h))
+ #define OPT_COLUMN(s, l, v, h) \
+       { OPTION_CALLBACK, (s), (l), (v), "style", (h), PARSE_OPT_OPTARG, parseopt_column_callback }
  
  #endif
diff --combined t/t3200-branch.sh
index 9fe1d8feab419e1a8065b2ea5881f991edc68855,9f82d5ed1ada696a292ae1837b9f199f729ecaa5..a17f8b2a407c2de2bf81d8a858957da409f1c980
@@@ -160,6 -160,83 +160,83 @@@ test_expect_success 'git branch --list 
        test_path_is_missing .git/refs/heads/t
  '
  
+ test_expect_success 'git branch --column' '
+       COLUMNS=81 git branch --column=column >actual &&
+       cat >expected <<\EOF &&
+   a/b/c     bam       foo       l       * master    n         o/p       r
+   abc       bar       j/k       m/m       master2   o/o       q
+ EOF
+       test_cmp expected actual
+ '
+ test_expect_success 'git branch --column with an extremely long branch name' '
+       long=this/is/a/part/of/long/branch/name &&
+       long=z$long/$long/$long/$long &&
+       test_when_finished "git branch -d $long" &&
+       git branch $long &&
+       COLUMNS=80 git branch --column=column >actual &&
+       cat >expected <<EOF &&
+   a/b/c
+   abc
+   bam
+   bar
+   foo
+   j/k
+   l
+   m/m
+ * master
+   master2
+   n
+   o/o
+   o/p
+   q
+   r
+   $long
+ EOF
+       test_cmp expected actual
+ '
+ test_expect_success 'git branch with column.*' '
+       git config column.ui column &&
+       git config column.branch "dense" &&
+       COLUMNS=80 git branch >actual &&
+       git config --unset column.branch &&
+       git config --unset column.ui &&
+       cat >expected <<\EOF &&
+   a/b/c   bam   foo   l   * master    n     o/p   r
+   abc     bar   j/k   m/m   master2   o/o   q
+ EOF
+       test_cmp expected actual
+ '
+ test_expect_success 'git branch --column -v should fail' '
+       test_must_fail git branch --column -v
+ '
+ test_expect_success 'git branch -v with column.ui ignored' '
+       git config column.ui column &&
+       COLUMNS=80 git branch -v | cut -c -10 | sed "s/ *$//" >actual &&
+       git config --unset column.ui &&
+       cat >expected <<\EOF &&
+   a/b/c
+   abc
+   bam
+   bar
+   foo
+   j/k
+   l
+   m/m
+ * master
+   master2
+   n
+   o/o
+   o/p
+   q
+   r
+ EOF
+       test_cmp expected actual
+ '
  mv .git/config .git/config-saved
  
  test_expect_success 'git branch -m q q2 without config should succeed' '
@@@ -653,8 -730,4 +730,8 @@@ test_expect_success 'refuse --edit-desc
        )
  '
  
 +test_expect_success '--merged catches invalid object names' '
 +      test_must_fail git branch --merged 0000000000000000000000000000000000000000
 +'
 +
  test_done