Merge branch 'jk/maint-1.7.2-status-ignored'
authorJunio C Hamano <gitster@pobox.com>
Thu, 30 Jun 2011 00:03:12 +0000 (17:03 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 30 Jun 2011 00:03:12 +0000 (17:03 -0700)
* jk/maint-1.7.2-status-ignored:
git status --ignored: tests and docs
status: fix bug with missing --ignore files

Conflicts:
Documentation/git-status.txt
t/t7508-status.sh

1  2 
Documentation/git-status.txt
t/t7508-status.sh
wt-status.c
index 38cb741f180e0869a91a61a5f7706eb664b86d91,b663e5144480e915790ca592af3eccb270207dc9..8ecc99d995fb86e4327c018467ed52d5345e6930
@@@ -27,48 -27,31 +27,51 @@@ OPTION
  --short::
        Give the output in the short-format.
  
 +-b::
 +--branch::
 +      Show the branch and tracking info even in short-format.
 +
  --porcelain::
 -      Give the output in a stable, easy-to-parse format for scripts.
 -      Currently this is identical to --short output, but is guaranteed
 -      not to change in the future, making it safe for scripts.
 +      Give the output in an easy-to-parse format for scripts.
 +      This is similar to the short output, but will remain stable
 +      across git versions and regardless of user configuration. See
 +      below for details.
  
  -u[<mode>]::
  --untracked-files[=<mode>]::
 -      Show untracked files (Default: 'all').
 +      Show untracked files.
 ++
 +The mode parameter is optional (defaults to 'all'), and is used to
 +specify the handling of untracked files; when -u is not used, the
 +default is 'normal', i.e. show untracked files and directories.
  +
 -The mode parameter is optional, and is used to specify
 -the handling of untracked files. The possible options are:
 +The possible options are:
  +
 ---
        - 'no'     - Show no untracked files
        - 'normal' - Shows untracked files and directories
        - 'all'    - Also shows individual files in untracked directories.
 ---
  +
 -See linkgit:git-config[1] for configuration variable
 -used to change the default for when the option is not
 -specified.
 +The default can be changed using the status.showUntrackedFiles
 +configuration variable documented in linkgit:git-config[1].
 +
 +--ignore-submodules[=<when>]::
 +      Ignore changes to submodules when looking for changes. <when> can be
 +      either "none", "untracked", "dirty" or "all", which is the default.
 +      Using "none" will consider the submodule modified when it either contains
 +      untracked or modified files or its HEAD differs from the commit recorded
 +      in the superproject and can be used to override any settings of the
 +      'ignore' option in linkgit:git-config[1] or linkgit:gitmodules[5]. When
 +      "untracked" is used submodules are not considered dirty when they only
 +      contain untracked content (but they are still scanned for modified
 +      content). Using "dirty" ignores all changes to the work tree of submodules,
 +      only changes to the commits stored in the superproject are shown (this was
 +      the behavior before 1.7.0). Using "all" hides all changes to submodules
 +      (and suppresses the output of submodule summaries when the config option
 +      `status.submodulesummary` is set).
  
+ --ignored::
+       Show ignored files as well.
  -z::
        Terminate entries with NUL, instead of LF.  This implies
        the `--porcelain` output format if no other format is given.
@@@ -79,54 -62,36 +82,55 @@@ OUTPU
  The output from this command is designed to be used as a commit
  template comment, and all the output lines are prefixed with '#'.
  The default, long format, is designed to be human readable,
 -verbose and descriptive.  They are subject to change in any time.
 +verbose and descriptive.  Its contents and format are subject to change
 +at any time.
  
  The paths mentioned in the output, unlike many other git commands, are
  made relative to the current directory if you are working in a
  subdirectory (this is on purpose, to help cutting and pasting). See
  the status.relativePaths config option below.
  
 -In short-format, the status of each path is shown as
 +Short Format
 +~~~~~~~~~~~~
 +
 +In the short-format, the status of each path is shown as
  
        XY PATH1 -> PATH2
  
 -where `PATH1` is the path in the `HEAD`, and -> 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. renamed).
 -
 -For unmerged entries, `X` shows the status of stage #2 (i.e. ours) and `Y`
 -shows the status of stage #3 (i.e. theirs).
 -
 -For entries that do not have conflicts, `X` shows the status of the index,
 -and `Y` shows the status of the work tree.  For untracked paths, `XY` are
 -`??`.
 -For ignored paths, `XY` are `!!`; they are shown only when the `--ignored`
 -option is in effect.
 +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
 +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
 +interior special characters backslash-escaped.
 +
 +For paths with merge conflicts, `X` and 'Y' show the modification
 +states of each side of the merge. For paths that do not have merge
 +conflicts, `X` shows the status of the index, and `Y` shows the status
 +of the work tree.  For untracked paths, `XY` are `??`.  Other status
 +codes can be interpreted as follows:
 +
 +* ' ' = unmodified
 +* 'M' = modified
 +* 'A' = added
 +* 'D' = deleted
 +* 'R' = renamed
 +* 'C' = copied
 +* 'U' = updated but unmerged
 +
- Ignored files are not listed.
++Ignored files are not listed, unless `--ignored` option is in effect,
++in which case `XY` are `!!`.
  
      X          Y     Meaning
      -------------------------------------------------
                [MD]   not updated
      M        [ MD]   updated in index
      A        [ MD]   added to index
 -    D        [ MD]   deleted from index
 +    D         [ M]   deleted from index
      R        [ MD]   renamed in index
      C        [ MD]   copied in index
      [MARC]           index and work tree matches
      U           U    unmerged, both modified
      -------------------------------------------------
      ?           ?    untracked
+     !           !    ignored
      -------------------------------------------------
  
 +If -b is used the short-format status is preceded by a line
 +
 +## branchname tracking info
 +
 +Porcelain Format
 +~~~~~~~~~~~~~~~~
 +
 +The porcelain format is similar to the short format, but is guaranteed
 +not to change in a backwards-incompatible way between git versions or
 +based on user configuration. This makes it ideal for parsing by scripts.
 +The description of the short format above also describes the porcelain
 +format, with a few exceptions:
 +
 +1. The user's color.status configuration is not respected; color will
 +   always be off.
 +
 +2. The user's status.relativePaths configuration is not respected; paths
 +   shown will always be relative to the repository root.
 +
 +There is also an alternate -z format recommended for machine parsing. In
 +that format, the status field is the same, but some other things
 +change.  First, the '\->' is omitted from rename entries and the field
 +order is reversed (e.g 'from \-> to' becomes 'to from'). Second, a NUL
 +(ASCII 0) follows each filename, replacing space as a field separator
 +and the terminating newline (but a space still separates the status
 +field from the first filename).  Third, filenames containing special
 +characters are not specially formatted; no quoting or
 +backslash-escaping is performed. Fourth, there is no branch line.
  
  CONFIGURATION
  -------------
@@@ -194,6 -132,14 +199,6 @@@ SEE ALS
  --------
  linkgit:gitignore[5]
  
 -Author
 -------
 -Written by Junio C Hamano <gitster@pobox.com>.
 -
 -Documentation
 ---------------
 -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
 -
  GIT
  ---
  Part of the linkgit:git[1] suite
diff --combined t/t7508-status.sh
index 1fdfbd38654a83771f677f2219403e3713abef51,4856f9eeeaad1d0ffbf88b9371e664edf557119f..905255adf0ca5b15d9befa772cda4a650bd15f34
@@@ -7,30 -7,6 +7,30 @@@ test_description='git status
  
  . ./test-lib.sh
  
 +test_expect_success 'status -h in broken repository' '
 +      mkdir broken &&
 +      test_when_finished "rm -fr broken" &&
 +      (
 +              cd broken &&
 +              git init &&
 +              echo "[status] showuntrackedfiles = CORRUPT" >>.git/config &&
 +              test_expect_code 129 git status -h >usage 2>&1
 +      ) &&
 +      test_i18ngrep "[Uu]sage" broken/usage
 +'
 +
 +test_expect_success 'commit -h in broken repository' '
 +      mkdir broken &&
 +      test_when_finished "rm -fr broken" &&
 +      (
 +              cd broken &&
 +              git init &&
 +              echo "[status] showuntrackedfiles = CORRUPT" >>.git/config &&
 +              test_expect_code 129 git commit -h >usage 2>&1
 +      ) &&
 +      test_i18ngrep "[Uu]sage" broken/usage
 +'
 +
  test_expect_success 'setup' '
        : >tracked &&
        : >modified &&
@@@ -56,7 -32,9 +56,7 @@@
  '
  
  test_expect_success 'status (1)' '
 -
 -      grep "use \"git rm --cached <file>\.\.\.\" to unstage" output
 -
 +      test_i18ngrep "use \"git rm --cached <file>\.\.\.\" to unstage" output
  '
  
  cat >expect <<\EOF
@@@ -66,7 -44,7 +66,7 @@@
  #
  #     new file:   dir2/added
  #
 -# Changed but not updated:
 +# Changes not staged for commit:
  #   (use "git add <file>..." to update what will be committed)
  #   (use "git checkout -- <file>..." to discard changes in working directory)
  #
  EOF
  
  test_expect_success 'status (2)' '
 +      git status >output &&
 +      test_i18ncmp expect output
 +'
  
 +cat >expect <<\EOF
 +# On branch master
 +# Changes to be committed:
 +#     new file:   dir2/added
 +#
 +# Changes not staged for commit:
 +#     modified:   dir1/modified
 +#
 +# Untracked files:
 +#     dir1/untracked
 +#     dir2/modified
 +#     dir2/untracked
 +#     expect
 +#     output
 +#     untracked
 +EOF
 +
 +test_expect_success 'status (advice.statusHints false)' '
 +      test_when_finished "git config --unset advice.statusHints" &&
 +      git config advice.statusHints false &&
        git status >output &&
 -      test_cmp expect output
 +      test_i18ncmp expect output
  
  '
  
@@@ -124,38 -79,134 +124,159 @@@ A  dir2/adde
  ?? untracked
  EOF
  
 -test_expect_success 'status -s (2)' '
 +test_expect_success 'status -s' '
  
        git status -s >output &&
        test_cmp expect output
  
  '
  
 -      # Changed but not updated:
+ test_expect_success 'status with gitignore' '
+       {
+               echo ".gitignore" &&
+               echo "expect" &&
+               echo "output" &&
+               echo "untracked"
+       } >.gitignore &&
+       cat >expect <<-\EOF &&
+        M dir1/modified
+       A  dir2/added
+       ?? dir2/modified
+       EOF
+       git status -s >output &&
+       test_cmp expect output &&
+       cat >expect <<-\EOF &&
+        M dir1/modified
+       A  dir2/added
+       ?? dir2/modified
+       !! .gitignore
+       !! dir1/untracked
+       !! dir2/untracked
+       !! expect
+       !! output
+       !! untracked
+       EOF
+       git status -s --ignored >output &&
+       test_cmp expect output &&
+       cat >expect <<-\EOF &&
+       # On branch master
+       # Changes to be committed:
+       #   (use "git reset HEAD <file>..." to unstage)
+       #
+       #       new file:   dir2/added
+       #
 -      # Changed but not updated:
++      # Changes not staged for commit:
+       #   (use "git add <file>..." to update what will be committed)
+       #   (use "git checkout -- <file>..." to discard changes in working directory)
+       #
+       #       modified:   dir1/modified
+       #
+       # Untracked files:
+       #   (use "git add <file>..." to include in what will be committed)
+       #
+       #       dir2/modified
+       # Ignored files:
+       #   (use "git add -f <file>..." to include in what will be committed)
+       #
+       #       .gitignore
+       #       dir1/untracked
+       #       dir2/untracked
+       #       expect
+       #       output
+       #       untracked
+       EOF
+       git status --ignored >output &&
+       test_cmp expect output
+ '
+ test_expect_success 'status with gitignore (nothing untracked)' '
+       {
+               echo ".gitignore" &&
+               echo "expect" &&
+               echo "dir2/modified" &&
+               echo "output" &&
+               echo "untracked"
+       } >.gitignore &&
+       cat >expect <<-\EOF &&
+        M dir1/modified
+       A  dir2/added
+       EOF
+       git status -s >output &&
+       test_cmp expect output &&
+       cat >expect <<-\EOF &&
+        M dir1/modified
+       A  dir2/added
+       !! .gitignore
+       !! dir1/untracked
+       !! dir2/modified
+       !! dir2/untracked
+       !! expect
+       !! output
+       !! untracked
+       EOF
+       git status -s --ignored >output &&
+       test_cmp expect output &&
+       cat >expect <<-\EOF &&
+       # On branch master
+       # Changes to be committed:
+       #   (use "git reset HEAD <file>..." to unstage)
+       #
+       #       new file:   dir2/added
+       #
++      # Changes not staged for commit:
+       #   (use "git add <file>..." to update what will be committed)
+       #   (use "git checkout -- <file>..." to discard changes in working directory)
+       #
+       #       modified:   dir1/modified
+       #
+       # Ignored files:
+       #   (use "git add -f <file>..." to include in what will be committed)
+       #
+       #       .gitignore
+       #       dir1/untracked
+       #       dir2/modified
+       #       dir2/untracked
+       #       expect
+       #       output
+       #       untracked
+       EOF
+       git status --ignored >output &&
+       test_cmp expect output
+ '
+ rm -f .gitignore
 +cat >expect <<\EOF
 +## master
 + M dir1/modified
 +A  dir2/added
 +?? dir1/untracked
 +?? dir2/modified
 +?? dir2/untracked
 +?? expect
 +?? output
 +?? untracked
 +EOF
 +
 +test_expect_success 'status -s -b' '
 +
 +      git status -s -b >output &&
 +      test_cmp expect output
 +
 +'
 +
 +test_expect_success 'setup dir3' '
 +      mkdir dir3 &&
 +      : >dir3/untracked1 &&
 +      : >dir3/untracked2
 +'
 +
  cat >expect <<EOF
  # On branch master
  # Changes to be committed:
  #
  #     new file:   dir2/added
  #
 -# Changed but not updated:
 +# Changes not staged for commit:
  #   (use "git add <file>..." to update what will be committed)
  #   (use "git checkout -- <file>..." to discard changes in working directory)
  #
  # Untracked files not listed (use -u option to show untracked files)
  EOF
  test_expect_success 'status -uno' '
 -      mkdir dir3 &&
 -      : >dir3/untracked1 &&
 -      : >dir3/untracked2 &&
        git status -uno >output &&
 -      test_cmp expect output
 +      test_i18ncmp expect output
  '
  
  test_expect_success 'status (status.showUntrackedFiles no)' '
        git config status.showuntrackedfiles no
 +      test_when_finished "git config --unset status.showuntrackedfiles" &&
        git status >output &&
 -      test_cmp expect output
 +      test_i18ncmp expect output
 +'
 +
 +cat >expect <<EOF
 +# On branch master
 +# Changes to be committed:
 +#     new file:   dir2/added
 +#
 +# Changes not staged for commit:
 +#     modified:   dir1/modified
 +#
 +# Untracked files not listed
 +EOF
 +git config advice.statusHints false
 +test_expect_success 'status -uno (advice.statusHints false)' '
 +      git status -uno >output &&
 +      test_i18ncmp expect output
  '
 +git config --unset advice.statusHints
  
  cat >expect << EOF
   M dir1/modified
  A  dir2/added
  EOF
  test_expect_success 'status -s -uno' '
 -      git config --unset status.showuntrackedfiles
        git status -s -uno >output &&
        test_cmp expect output
  '
@@@ -222,7 -259,7 +343,7 @@@ cat >expect <<EO
  #
  #     new file:   dir2/added
  #
 -# Changed but not updated:
 +# Changes not staged for commit:
  #   (use "git add <file>..." to update what will be committed)
  #   (use "git checkout -- <file>..." to discard changes in working directory)
  #
  EOF
  test_expect_success 'status -unormal' '
        git status -unormal >output &&
 -      test_cmp expect output
 +      test_i18ncmp expect output
  '
  
  test_expect_success 'status (status.showUntrackedFiles normal)' '
        git config status.showuntrackedfiles normal
 +      test_when_finished "git config --unset status.showuntrackedfiles" &&
        git status >output &&
 -      test_cmp expect output
 +      test_i18ncmp expect output
  '
  
  cat >expect <<EOF
@@@ -263,6 -299,7 +384,6 @@@ A  dir2/adde
  ?? untracked
  EOF
  test_expect_success 'status -s -unormal' '
 -      git config --unset status.showuntrackedfiles
        git status -s -unormal >output &&
        test_cmp expect output
  '
@@@ -280,7 -317,7 +401,7 @@@ cat >expect <<EO
  #
  #     new file:   dir2/added
  #
 -# Changed but not updated:
 +# Changes not staged for commit:
  #   (use "git add <file>..." to update what will be committed)
  #   (use "git checkout -- <file>..." to discard changes in working directory)
  #
  EOF
  test_expect_success 'status -uall' '
        git status -uall >output &&
 -      test_cmp expect output
 +      test_i18ncmp expect output
  '
 +
  test_expect_success 'status (status.showUntrackedFiles all)' '
        git config status.showuntrackedfiles all
 +      test_when_finished "git config --unset status.showuntrackedfiles" &&
        git status >output &&
 -      rm -rf dir3 &&
 -      git config --unset status.showuntrackedfiles &&
 -      test_cmp expect output
 +      test_i18ncmp expect output
 +'
 +
 +test_expect_success 'teardown dir3' '
 +      rm -rf dir3
  '
  
  cat >expect <<EOF
@@@ -344,7 -377,7 +465,7 @@@ cat >expect <<\EO
  #
  #     new file:   ../dir2/added
  #
 -# Changed but not updated:
 +# Changes not staged for commit:
  #   (use "git add <file>..." to update what will be committed)
  #   (use "git checkout -- <file>..." to discard changes in working directory)
  #
  EOF
  
  test_expect_success 'status with relative paths' '
 -
        (cd dir1 && git status) >output &&
 -      test_cmp expect output
 -
 +      test_i18ncmp expect output
  '
  
  cat >expect <<\EOF
@@@ -403,19 -438,18 +524,19 @@@ test_expect_success 'status --porcelai
  
  test_expect_success 'setup unique colors' '
  
 -      git config status.color.untracked blue
 +      git config status.color.untracked blue &&
 +      git config status.color.branch green
  
  '
  
  cat >expect <<\EOF
 -# On branch master
 +# On branch <GREEN>master<RESET>
  # Changes to be committed:
  #   (use "git reset HEAD <file>..." to unstage)
  #
  #     <GREEN>new file:   dir2/added<RESET>
  #
 -# Changed but not updated:
 +# Changes not staged for commit:
  #   (use "git add <file>..." to update what will be committed)
  #   (use "git checkout -- <file>..." to discard changes in working directory)
  #
  EOF
  
  test_expect_success 'status with color.ui' '
 -
        git config color.ui always &&
 +      test_when_finished "git config --unset color.ui" &&
        git status | test_decode_color >output &&
 -      test_cmp expect output
 -
 +      test_i18ncmp expect output
  '
  
  test_expect_success 'status with color.status' '
 -
 -      git config --unset color.ui &&
        git config color.status always &&
 +      test_when_finished "git config --unset color.status" &&
        git status | test_decode_color >output &&
 -      test_cmp expect output
 -
 +      test_i18ncmp expect output
  '
  
  cat >expect <<\EOF
@@@ -459,6 -496,7 +580,6 @@@ EO
  
  test_expect_success 'status -s with color.ui' '
  
 -      git config --unset color.status &&
        git config color.ui always &&
        git status -s | test_decode_color >output &&
        test_cmp expect output
@@@ -474,25 -512,6 +595,25 @@@ test_expect_success 'status -s with col
  
  '
  
 +cat >expect <<\EOF
 +## <GREEN>master<RESET>
 + <RED>M<RESET> dir1/modified
 +<GREEN>A<RESET>  dir2/added
 +<BLUE>??<RESET> dir1/untracked
 +<BLUE>??<RESET> dir2/modified
 +<BLUE>??<RESET> dir2/untracked
 +<BLUE>??<RESET> expect
 +<BLUE>??<RESET> output
 +<BLUE>??<RESET> untracked
 +EOF
 +
 +test_expect_success 'status -s -b with color.status' '
 +
 +      git status -s -b | test_decode_color >output &&
 +      test_cmp expect output
 +
 +'
 +
  cat >expect <<\EOF
   M dir1/modified
  A  dir2/added
@@@ -526,13 -545,6 +647,13 @@@ test_expect_success 'status --porcelai
  git config --unset color.status
  git config --unset color.ui
  
 +test_expect_success 'status --porcelain ignores -b' '
 +
 +      git status --porcelain -b >output &&
 +      test_cmp expect output
 +
 +'
 +
  cat >expect <<\EOF
  # On branch master
  # Changes to be committed:
  #
  #     new file:   dir2/added
  #
 -# Changed but not updated:
 +# Changes not staged for commit:
  #   (use "git add <file>..." to update what will be committed)
  #   (use "git checkout -- <file>..." to discard changes in working directory)
  #
@@@ -560,10 -572,9 +681,10 @@@ EO
  
  test_expect_success 'status without relative paths' '
  
 -      git config status.relativePaths false
 +      git config status.relativePaths false &&
 +      test_when_finished "git config --unset status.relativePaths" &&
        (cd dir1 && git status) >output &&
 -      test_cmp expect output
 +      test_i18ncmp expect output
  
  '
  
@@@ -580,8 -591,6 +701,8 @@@ EO
  
  test_expect_success 'status -s without relative paths' '
  
 +      git config status.relativePaths false &&
 +      test_when_finished "git config --unset status.relativePaths" &&
        (cd dir1 && git status -s) >output &&
        test_cmp expect output
  
@@@ -605,16 -614,6 +726,16 @@@ cat <<EOF >expec
  EOF
  test_expect_success 'dry-run of partial commit excluding new file in index' '
        git commit --dry-run dir1/modified >output &&
 +      test_i18ncmp expect output
 +'
 +
 +cat >expect <<EOF
 +:100644 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0000000000000000000000000000000000000000 M    dir1/modified
 +EOF
 +test_expect_success 'status refreshes the index' '
 +      touch dir2/added &&
 +      git status &&
 +      git diff-files >output &&
        test_cmp expect output
  '
  
@@@ -636,7 -635,7 +757,7 @@@ cat >expect <<EO
  #     new file:   dir2/added
  #     new file:   sm
  #
 -# Changed but not updated:
 +# Changes not staged for commit:
  #   (use "git add <file>..." to update what will be committed)
  #   (use "git checkout -- <file>..." to discard changes in working directory)
  #
  EOF
  test_expect_success 'status submodule summary is disabled by default' '
        git status >output &&
 -      test_cmp expect output
 +      test_i18ncmp expect output
  '
  
  # we expect the same as the previous test
  test_expect_success 'status --untracked-files=all does not show submodule' '
        git status --untracked-files=all >output &&
 -      test_cmp expect output
 +      test_i18ncmp expect output
  '
  
  cat >expect <<EOF
@@@ -695,7 -694,7 +816,7 @@@ cat >expect <<EO
  #     new file:   dir2/added
  #     new file:   sm
  #
 -# Changed but not updated:
 +# Changes not staged for commit:
  #   (use "git add <file>..." to update what will be committed)
  #   (use "git checkout -- <file>..." to discard changes in working directory)
  #
@@@ -719,7 -718,7 +840,7 @@@ EO
  test_expect_success 'status submodule summary' '
        git config status.submodulesummary 10 &&
        git status >output &&
 -      test_cmp expect output
 +      test_i18ncmp expect output
  '
  
  cat >expect <<EOF
@@@ -740,7 -739,7 +861,7 @@@ test_expect_success 'status -s submodul
  
  cat >expect <<EOF
  # On branch master
 -# Changed but not updated:
 +# Changes not staged for commit:
  #   (use "git add <file>..." to update what will be committed)
  #   (use "git checkout -- <file>..." to discard changes in working directory)
  #
  #     untracked
  no changes added to commit (use "git add" and/or "git commit -a")
  EOF
 -test_expect_success 'status submodule summary (clean submodule)' '
 +test_expect_success 'status submodule summary (clean submodule): commit' '
        git commit -m "commit submodule" &&
        git config status.submodulesummary 10 &&
        test_must_fail git commit --dry-run >output &&
 -      test_cmp expect output &&
 +      test_i18ncmp expect output &&
        git status >output &&
 -      test_cmp expect output
 +      test_i18ncmp expect output
  '
  
  cat >expect <<EOF
@@@ -780,13 -779,6 +901,13 @@@ test_expect_success 'status -s submodul
        test_cmp expect output
  '
  
 +test_expect_success 'status -z implies porcelain' '
 +      git status --porcelain |
 +      perl -pe "s/\012/\000/g" >expect &&
 +      git status -z >output &&
 +      test_cmp expect output
 +'
 +
  cat >expect <<EOF
  # On branch master
  # Changes to be committed:
  #     new file:   dir2/added
  #     new file:   sm
  #
 -# Changed but not updated:
 +# Changes not staged for commit:
  #   (use "git add <file>..." to update what will be committed)
  #   (use "git checkout -- <file>..." to discard changes in working directory)
  #
  test_expect_success 'commit --dry-run submodule summary (--amend)' '
        git config status.submodulesummary 10 &&
        git commit --dry-run --amend >output &&
 -      test_cmp expect output
 +      test_i18ncmp expect output
 +'
 +
 +test_expect_success POSIXPERM,SANITY 'status succeeds in a read-only repository' '
 +      (
 +              chmod a-w .git &&
 +              # make dir1/tracked stat-dirty
 +              >dir1/tracked1 && mv -f dir1/tracked1 dir1/tracked &&
 +              git status -s >output &&
 +              ! grep dir1/tracked output &&
 +              # make sure "status" succeeded without writing index out
 +              git diff-files | grep dir1/tracked
 +      )
 +      status=$?
 +      chmod 775 .git
 +      (exit $status)
 +'
 +
 +(cd sm && echo > bar && git add bar && git commit -q -m 'Add bar') && git add sm
 +new_head=$(cd sm && git rev-parse --short=7 --verify HEAD)
 +touch .gitmodules
 +
 +cat > expect << EOF
 +# On branch master
 +# Changes to be committed:
 +#   (use "git reset HEAD <file>..." to unstage)
 +#
 +#     modified:   sm
 +#
 +# Changes not staged for commit:
 +#   (use "git add <file>..." to update what will be committed)
 +#   (use "git checkout -- <file>..." to discard changes in working directory)
 +#
 +#     modified:   dir1/modified
 +#
 +# Submodule changes to be committed:
 +#
 +# * sm $head...$new_head (1):
 +#   > Add bar
 +#
 +# Untracked files:
 +#   (use "git add <file>..." to include in what will be committed)
 +#
 +#     .gitmodules
 +#     dir1/untracked
 +#     dir2/modified
 +#     dir2/untracked
 +#     expect
 +#     output
 +#     untracked
 +EOF
 +
 +test_expect_success '--ignore-submodules=untracked suppresses submodules with untracked content' '
 +      echo modified  sm/untracked &&
 +      git status --ignore-submodules=untracked >output &&
 +      test_i18ncmp expect output
 +'
 +
 +test_expect_success '.gitmodules ignore=untracked suppresses submodules with untracked content' '
 +      git config diff.ignoreSubmodules dirty &&
 +      git status >output &&
 +      test_i18ncmp expect output &&
 +      git config --add -f .gitmodules submodule.subname.ignore untracked &&
 +      git config --add -f .gitmodules submodule.subname.path sm &&
 +      git status >output &&
 +      test_i18ncmp expect output &&
 +      git config -f .gitmodules  --remove-section submodule.subname &&
 +      git config --unset diff.ignoreSubmodules
 +'
 +
 +test_expect_success '.git/config ignore=untracked suppresses submodules with untracked content' '
 +      git config --add -f .gitmodules submodule.subname.ignore none &&
 +      git config --add -f .gitmodules submodule.subname.path sm &&
 +      git config --add submodule.subname.ignore untracked &&
 +      git config --add submodule.subname.path sm &&
 +      git status >output &&
 +      test_i18ncmp expect output &&
 +      git config --remove-section submodule.subname &&
 +      git config --remove-section -f .gitmodules submodule.subname
 +'
 +
 +test_expect_success '--ignore-submodules=dirty suppresses submodules with untracked content' '
 +      git status --ignore-submodules=dirty >output &&
 +      test_i18ncmp expect output
 +'
 +
 +test_expect_success '.gitmodules ignore=dirty suppresses submodules with untracked content' '
 +      git config diff.ignoreSubmodules dirty &&
 +      git status >output &&
 +      ! test -s actual &&
 +      git config --add -f .gitmodules submodule.subname.ignore dirty &&
 +      git config --add -f .gitmodules submodule.subname.path sm &&
 +      git status >output &&
 +      test_i18ncmp expect output &&
 +      git config -f .gitmodules  --remove-section submodule.subname &&
 +      git config --unset diff.ignoreSubmodules
 +'
 +
 +test_expect_success '.git/config ignore=dirty suppresses submodules with untracked content' '
 +      git config --add -f .gitmodules submodule.subname.ignore none &&
 +      git config --add -f .gitmodules submodule.subname.path sm &&
 +      git config --add submodule.subname.ignore dirty &&
 +      git config --add submodule.subname.path sm &&
 +      git status >output &&
 +      test_i18ncmp expect output &&
 +      git config --remove-section submodule.subname &&
 +      git config -f .gitmodules  --remove-section submodule.subname
 +'
 +
 +test_expect_success '--ignore-submodules=dirty suppresses submodules with modified content' '
 +      echo modified >sm/foo &&
 +      git status --ignore-submodules=dirty >output &&
 +      test_i18ncmp expect output
 +'
 +
 +test_expect_success '.gitmodules ignore=dirty suppresses submodules with modified content' '
 +      git config --add -f .gitmodules submodule.subname.ignore dirty &&
 +      git config --add -f .gitmodules submodule.subname.path sm &&
 +      git status >output &&
 +      test_i18ncmp expect output &&
 +      git config -f .gitmodules  --remove-section submodule.subname
 +'
 +
 +test_expect_success '.git/config ignore=dirty suppresses submodules with modified content' '
 +      git config --add -f .gitmodules submodule.subname.ignore none &&
 +      git config --add -f .gitmodules submodule.subname.path sm &&
 +      git config --add submodule.subname.ignore dirty &&
 +      git config --add submodule.subname.path sm &&
 +      git status >output &&
 +      test_i18ncmp expect output &&
 +      git config --remove-section submodule.subname &&
 +      git config -f .gitmodules  --remove-section submodule.subname
 +'
 +
 +cat > expect << EOF
 +# On branch master
 +# Changes to be committed:
 +#   (use "git reset HEAD <file>..." to unstage)
 +#
 +#     modified:   sm
 +#
 +# Changes not staged for commit:
 +#   (use "git add <file>..." to update what will be committed)
 +#   (use "git checkout -- <file>..." to discard changes in working directory)
 +#   (commit or discard the untracked or modified content in submodules)
 +#
 +#     modified:   dir1/modified
 +#     modified:   sm (modified content)
 +#
 +# Submodule changes to be committed:
 +#
 +# * sm $head...$new_head (1):
 +#   > Add bar
 +#
 +# Untracked files:
 +#   (use "git add <file>..." to include in what will be committed)
 +#
 +#     .gitmodules
 +#     dir1/untracked
 +#     dir2/modified
 +#     dir2/untracked
 +#     expect
 +#     output
 +#     untracked
 +EOF
 +
 +test_expect_success "--ignore-submodules=untracked doesn't suppress submodules with modified content" '
 +      git status --ignore-submodules=untracked > output &&
 +      test_i18ncmp expect output
 +'
 +
 +test_expect_success ".gitmodules ignore=untracked doesn't suppress submodules with modified content" '
 +      git config --add -f .gitmodules submodule.subname.ignore untracked &&
 +      git config --add -f .gitmodules submodule.subname.path sm &&
 +      git status >output &&
 +      test_i18ncmp expect output &&
 +      git config -f .gitmodules  --remove-section submodule.subname
 +'
 +
 +test_expect_success ".git/config ignore=untracked doesn't suppress submodules with modified content" '
 +      git config --add -f .gitmodules submodule.subname.ignore none &&
 +      git config --add -f .gitmodules submodule.subname.path sm &&
 +      git config --add submodule.subname.ignore untracked &&
 +      git config --add submodule.subname.path sm &&
 +      git status >output &&
 +      test_i18ncmp expect output &&
 +      git config --remove-section submodule.subname &&
 +      git config -f .gitmodules  --remove-section submodule.subname
 +'
 +
 +head2=$(cd sm && git commit -q -m "2nd commit" foo && git rev-parse --short=7 --verify HEAD)
 +
 +cat > expect << EOF
 +# On branch master
 +# Changes to be committed:
 +#   (use "git reset HEAD <file>..." to unstage)
 +#
 +#     modified:   sm
 +#
 +# Changes not staged for commit:
 +#   (use "git add <file>..." to update what will be committed)
 +#   (use "git checkout -- <file>..." to discard changes in working directory)
 +#
 +#     modified:   dir1/modified
 +#     modified:   sm (new commits)
 +#
 +# Submodule changes to be committed:
 +#
 +# * sm $head...$new_head (1):
 +#   > Add bar
 +#
 +# Submodules changed but not updated:
 +#
 +# * sm $new_head...$head2 (1):
 +#   > 2nd commit
 +#
 +# Untracked files:
 +#   (use "git add <file>..." to include in what will be committed)
 +#
 +#     .gitmodules
 +#     dir1/untracked
 +#     dir2/modified
 +#     dir2/untracked
 +#     expect
 +#     output
 +#     untracked
 +EOF
 +
 +test_expect_success "--ignore-submodules=untracked doesn't suppress submodule summary" '
 +      git status --ignore-submodules=untracked > output &&
 +      test_i18ncmp expect output
 +'
 +
 +test_expect_success ".gitmodules ignore=untracked doesn't suppress submodule summary" '
 +      git config --add -f .gitmodules submodule.subname.ignore untracked &&
 +      git config --add -f .gitmodules submodule.subname.path sm &&
 +      git status >output &&
 +      test_i18ncmp expect output &&
 +      git config -f .gitmodules  --remove-section submodule.subname
 +'
 +
 +test_expect_success ".git/config ignore=untracked doesn't suppress submodule summary" '
 +      git config --add -f .gitmodules submodule.subname.ignore none &&
 +      git config --add -f .gitmodules submodule.subname.path sm &&
 +      git config --add submodule.subname.ignore untracked &&
 +      git config --add submodule.subname.path sm &&
 +      git status >output &&
 +      test_i18ncmp expect output &&
 +      git config --remove-section submodule.subname &&
 +      git config -f .gitmodules  --remove-section submodule.subname
 +'
 +
 +test_expect_success "--ignore-submodules=dirty doesn't suppress submodule summary" '
 +      git status --ignore-submodules=dirty > output &&
 +      test_i18ncmp expect output
 +'
 +test_expect_success ".gitmodules ignore=dirty doesn't suppress submodule summary" '
 +      git config --add -f .gitmodules submodule.subname.ignore dirty &&
 +      git config --add -f .gitmodules submodule.subname.path sm &&
 +      git status >output &&
 +      test_i18ncmp expect output &&
 +      git config -f .gitmodules  --remove-section submodule.subname
 +'
 +
 +test_expect_success ".git/config ignore=dirty doesn't suppress submodule summary" '
 +      git config --add -f .gitmodules submodule.subname.ignore none &&
 +      git config --add -f .gitmodules submodule.subname.path sm &&
 +      git config --add submodule.subname.ignore dirty &&
 +      git config --add submodule.subname.path sm &&
 +      git status >output &&
 +      test_i18ncmp expect output &&
 +      git config --remove-section submodule.subname &&
 +      git config -f .gitmodules  --remove-section submodule.subname
 +'
 +
 +cat > expect << EOF
 +# On branch master
 +# Changes not staged for commit:
 +#   (use "git add <file>..." to update what will be committed)
 +#   (use "git checkout -- <file>..." to discard changes in working directory)
 +#
 +#     modified:   dir1/modified
 +#
 +# Untracked files:
 +#   (use "git add <file>..." to include in what will be committed)
 +#
 +#     .gitmodules
 +#     dir1/untracked
 +#     dir2/modified
 +#     dir2/untracked
 +#     expect
 +#     output
 +#     untracked
 +no changes added to commit (use "git add" and/or "git commit -a")
 +EOF
 +
 +test_expect_success "--ignore-submodules=all suppresses submodule summary" '
 +      git status --ignore-submodules=all > output &&
 +      test_i18ncmp expect output
 +'
 +
 +test_expect_failure '.gitmodules ignore=all suppresses submodule summary' '
 +      git config --add -f .gitmodules submodule.subname.ignore all &&
 +      git config --add -f .gitmodules submodule.subname.path sm &&
 +      git status > output &&
 +      test_cmp expect output &&
 +      git config -f .gitmodules  --remove-section submodule.subname
 +'
 +
 +test_expect_failure '.git/config ignore=all suppresses submodule summary' '
 +      git config --add -f .gitmodules submodule.subname.ignore none &&
 +      git config --add -f .gitmodules submodule.subname.path sm &&
 +      git config --add submodule.subname.ignore all &&
 +      git config --add submodule.subname.path sm &&
 +      git status > output &&
 +      test_cmp expect output &&
 +      git config --remove-section submodule.subname &&
 +      git config -f .gitmodules  --remove-section submodule.subname
  '
  
  test_done
diff --combined wt-status.c
index 9f4e0ba9c17120ca2903b30b95b6d8fbcc62cd9c,160e841261fffdaa2160d8ab3767fd68c9f0da82..02377729c401fa6e041a713a89fe986fa0f562a0
@@@ -9,8 -9,6 +9,8 @@@
  #include "quote.h"
  #include "run-command.h"
  #include "remote.h"
 +#include "refs.h"
 +#include "submodule.h"
  
  static char default_wt_status_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
        GIT_COLOR_RED,    /* WT_STATUS_UNTRACKED */
        GIT_COLOR_RED,    /* WT_STATUS_NOBRANCH */
        GIT_COLOR_RED,    /* WT_STATUS_UNMERGED */
 +      GIT_COLOR_GREEN,  /* WT_STATUS_LOCAL_BRANCH */
 +      GIT_COLOR_RED,    /* WT_STATUS_REMOTE_BRANCH */
 +      GIT_COLOR_NIL,    /* WT_STATUS_ONBRANCH */
  };
  
  static const char *color(int slot, struct wt_status *s)
  {
 -      return s->use_color > 0 ? s->color_palette[slot] : "";
 +      const char *c = s->use_color > 0 ? s->color_palette[slot] : "";
 +      if (slot == WT_STATUS_ONBRANCH && color_is_nil(c))
 +              c = s->color_palette[WT_STATUS_HEADER];
 +      return c;
 +}
 +
 +static void status_vprintf(struct wt_status *s, int at_bol, const char *color,
 +              const char *fmt, va_list ap, const char *trail)
 +{
 +      struct strbuf sb = STRBUF_INIT;
 +      struct strbuf linebuf = STRBUF_INIT;
 +      const char *line, *eol;
 +
 +      strbuf_vaddf(&sb, fmt, ap);
 +      if (!sb.len) {
 +              strbuf_addch(&sb, '#');
 +              if (!trail)
 +                      strbuf_addch(&sb, ' ');
 +              color_print_strbuf(s->fp, color, &sb);
 +              if (trail)
 +                      fprintf(s->fp, "%s", trail);
 +              strbuf_release(&sb);
 +              return;
 +      }
 +      for (line = sb.buf; *line; line = eol + 1) {
 +              eol = strchr(line, '\n');
 +
 +              strbuf_reset(&linebuf);
 +              if (at_bol) {
 +                      strbuf_addch(&linebuf, '#');
 +                      if (*line != '\n' && *line != '\t')
 +                              strbuf_addch(&linebuf, ' ');
 +              }
 +              if (eol)
 +                      strbuf_add(&linebuf, line, eol - line);
 +              else
 +                      strbuf_addstr(&linebuf, line);
 +              color_print_strbuf(s->fp, color, &linebuf);
 +              if (eol)
 +                      fprintf(s->fp, "\n");
 +              else
 +                      break;
 +              at_bol = 1;
 +      }
 +      if (trail)
 +              fprintf(s->fp, "%s", trail);
 +      strbuf_release(&linebuf);
 +      strbuf_release(&sb);
 +}
 +
 +void status_printf_ln(struct wt_status *s, const char *color,
 +                      const char *fmt, ...)
 +{
 +      va_list ap;
 +
 +      va_start(ap, fmt);
 +      status_vprintf(s, 1, color, fmt, ap, "\n");
 +      va_end(ap);
 +}
 +
 +void status_printf(struct wt_status *s, const char *color,
 +                      const char *fmt, ...)
 +{
 +      va_list ap;
 +
 +      va_start(ap, fmt);
 +      status_vprintf(s, 1, color, fmt, ap, NULL);
 +      va_end(ap);
 +}
 +
 +void status_printf_more(struct wt_status *s, const char *color,
 +                      const char *fmt, ...)
 +{
 +      va_list ap;
 +
 +      va_start(ap, fmt);
 +      status_vprintf(s, 0, color, fmt, ap, NULL);
 +      va_end(ap);
  }
  
  void wt_status_prepare(struct wt_status *s)
@@@ -131,33 -49,33 +131,33 @@@ static void wt_status_print_unmerged_he
  {
        const char *c = color(WT_STATUS_HEADER, s);
  
 -      color_fprintf_ln(s->fp, c, "# Unmerged paths:");
 +      status_printf_ln(s, c, _("Unmerged paths:"));
        if (!advice_status_hints)
                return;
 -      if (s->in_merge)
 +      if (s->whence != FROM_COMMIT)
                ;
        else if (!s->is_initial)
 -              color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
 +              status_printf_ln(s, c, _("  (use \"git reset %s <file>...\" to unstage)"), s->reference);
        else
 -              color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
 -      color_fprintf_ln(s->fp, c, "#   (use \"git add/rm <file>...\" as appropriate to mark resolution)");
 -      color_fprintf_ln(s->fp, c, "#");
 +              status_printf_ln(s, c, _("  (use \"git rm --cached <file>...\" to unstage)"));
 +      status_printf_ln(s, c, _("  (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
 +      status_printf_ln(s, c, "");
  }
  
  static void wt_status_print_cached_header(struct wt_status *s)
  {
        const char *c = color(WT_STATUS_HEADER, s);
  
 -      color_fprintf_ln(s->fp, c, "# Changes to be committed:");
 +      status_printf_ln(s, c, _("Changes to be committed:"));
        if (!advice_status_hints)
                return;
 -      if (s->in_merge)
 +      if (s->whence != FROM_COMMIT)
                ; /* NEEDSWORK: use "git reset --unresolve"??? */
        else if (!s->is_initial)
 -              color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
 +              status_printf_ln(s, c, _("  (use \"git reset %s <file>...\" to unstage)"), s->reference);
        else
 -              color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
 -      color_fprintf_ln(s->fp, c, "#");
 +              status_printf_ln(s, c, _("  (use \"git rm --cached <file>...\" to unstage)"));
 +      status_printf_ln(s, c, "");
  }
  
  static void wt_status_print_dirty_header(struct wt_status *s,
  {
        const char *c = color(WT_STATUS_HEADER, s);
  
 -      color_fprintf_ln(s->fp, c, "# Changed but not updated:");
 +      status_printf_ln(s, c, _("Changes not staged for commit:"));
        if (!advice_status_hints)
                return;
        if (!has_deleted)
 -              color_fprintf_ln(s->fp, c, "#   (use \"git add <file>...\" to update what will be committed)");
 +              status_printf_ln(s, c, _("  (use \"git add <file>...\" to update what will be committed)"));
        else
 -              color_fprintf_ln(s->fp, c, "#   (use \"git add/rm <file>...\" to update what will be committed)");
 -      color_fprintf_ln(s->fp, c, "#   (use \"git checkout -- <file>...\" to discard changes in working directory)");
 +              status_printf_ln(s, c, _("  (use \"git add/rm <file>...\" to update what will be committed)"));
 +      status_printf_ln(s, c, _("  (use \"git checkout -- <file>...\" to discard changes in working directory)"));
        if (has_dirty_submodules)
 -              color_fprintf_ln(s->fp, c, "#   (commit or discard the untracked or modified content in submodules)");
 -      color_fprintf_ln(s->fp, c, "#");
 +              status_printf_ln(s, c, _("  (commit or discard the untracked or modified content in submodules)"));
 +      status_printf_ln(s, c, "");
  }
  
  static void wt_status_print_other_header(struct wt_status *s,
                                         const char *how)
  {
        const char *c = color(WT_STATUS_HEADER, s);
 -      color_fprintf_ln(s->fp, c, "# %s files:", what);
 +      status_printf_ln(s, c, _("%s files:"), what);
        if (!advice_status_hints)
                return;
 -      color_fprintf_ln(s->fp, c, "#   (use \"git %s <file>...\" to include in what will be committed)", how);
 -      color_fprintf_ln(s->fp, c, "#");
 +      status_printf_ln(s, c, _("  (use \"git %s <file>...\" to include in what will be committed)"), how);
 +      status_printf_ln(s, c, "");
  }
  
  static void wt_status_print_trailer(struct wt_status *s)
  {
 -      color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
 +      status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
  }
  
  #define quote_path quote_path_relative
@@@ -204,20 -122,20 +204,20 @@@ static void wt_status_print_unmerged_da
        const char *c = color(WT_STATUS_UNMERGED, s);
        struct wt_status_change_data *d = it->util;
        struct strbuf onebuf = STRBUF_INIT;
 -      const char *one, *how = "bug";
 +      const char *one, *how = _("bug");
  
        one = quote_path(it->string, -1, &onebuf, s->prefix);
 -      color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
 +      status_printf(s, color(WT_STATUS_HEADER, s), "\t");
        switch (d->stagemask) {
 -      case 1: how = "both deleted:"; break;
 -      case 2: how = "added by us:"; break;
 -      case 3: how = "deleted by them:"; break;
 -      case 4: how = "added by them:"; break;
 -      case 5: how = "deleted by us:"; break;
 -      case 6: how = "both added:"; break;
 -      case 7: how = "both modified:"; break;
 -      }
 -      color_fprintf(s->fp, c, "%-20s%s\n", how, one);
 +      case 1: how = _("both deleted:"); break;
 +      case 2: how = _("added by us:"); break;
 +      case 3: how = _("deleted by them:"); break;
 +      case 4: how = _("added by them:"); break;
 +      case 5: how = _("deleted by us:"); break;
 +      case 6: how = _("both added:"); break;
 +      case 7: how = _("both modified:"); break;
 +      }
 +      status_printf_more(s, c, "%-20s%s\n", how, one);
        strbuf_release(&onebuf);
  }
  
@@@ -245,11 -163,11 +245,11 @@@ static void wt_status_print_change_data
                if (d->new_submodule_commits || d->dirty_submodule) {
                        strbuf_addstr(&extra, " (");
                        if (d->new_submodule_commits)
 -                              strbuf_addf(&extra, "new commits, ");
 +                              strbuf_addf(&extra, _("new commits, "));
                        if (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
 -                              strbuf_addf(&extra, "modified content, ");
 +                              strbuf_addf(&extra, _("modified content, "));
                        if (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
 -                              strbuf_addf(&extra, "untracked content, ");
 +                              strbuf_addf(&extra, _("untracked content, "));
                        strbuf_setlen(&extra, extra.len - 2);
                        strbuf_addch(&extra, ')');
                }
        one = quote_path(one_name, -1, &onebuf, s->prefix);
        two = quote_path(two_name, -1, &twobuf, s->prefix);
  
 -      color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
 +      status_printf(s, color(WT_STATUS_HEADER, s), "\t");
        switch (status) {
        case DIFF_STATUS_ADDED:
 -              color_fprintf(s->fp, c, "new file:   %s", one);
 +              status_printf_more(s, c, _("new file:   %s"), one);
                break;
        case DIFF_STATUS_COPIED:
 -              color_fprintf(s->fp, c, "copied:     %s -> %s", one, two);
 +              status_printf_more(s, c, _("copied:     %s -> %s"), one, two);
                break;
        case DIFF_STATUS_DELETED:
 -              color_fprintf(s->fp, c, "deleted:    %s", one);
 +              status_printf_more(s, c, _("deleted:    %s"), one);
                break;
        case DIFF_STATUS_MODIFIED:
 -              color_fprintf(s->fp, c, "modified:   %s", one);
 +              status_printf_more(s, c, _("modified:   %s"), one);
                break;
        case DIFF_STATUS_RENAMED:
 -              color_fprintf(s->fp, c, "renamed:    %s -> %s", one, two);
 +              status_printf_more(s, c, _("renamed:    %s -> %s"), one, two);
                break;
        case DIFF_STATUS_TYPE_CHANGED:
 -              color_fprintf(s->fp, c, "typechange: %s", one);
 +              status_printf_more(s, c, _("typechange: %s"), one);
                break;
        case DIFF_STATUS_UNKNOWN:
 -              color_fprintf(s->fp, c, "unknown:    %s", one);
 +              status_printf_more(s, c, _("unknown:    %s"), one);
                break;
        case DIFF_STATUS_UNMERGED:
 -              color_fprintf(s->fp, c, "unmerged:   %s", one);
 +              status_printf_more(s, c, _("unmerged:   %s"), one);
                break;
        default:
 -              die("bug: unhandled diff status %c", status);
 +              die(_("bug: unhandled diff status %c"), status);
        }
        if (extra.len) {
 -              color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "%s", extra.buf);
 +              status_printf_more(s, color(WT_STATUS_HEADER, s), "%s", extra.buf);
                strbuf_release(&extra);
        }
 -      fprintf(s->fp, "\n");
 +      status_printf_more(s, GIT_COLOR_NORMAL, "\n");
        strbuf_release(&onebuf);
        strbuf_release(&twobuf);
  }
@@@ -314,7 -232,7 +314,7 @@@ static void wt_status_collect_changed_c
                struct wt_status_change_data *d;
  
                p = q->queue[i];
 -              it = string_list_insert(p->one->path, &s->change);
 +              it = string_list_insert(&s->change, p->one->path);
                d = it->util;
                if (!d) {
                        d = xcalloc(1, sizeof(*d));
@@@ -361,7 -279,7 +361,7 @@@ static void wt_status_collect_updated_c
                struct wt_status_change_data *d;
  
                p = q->queue[i];
 -              it = string_list_insert(p->two->path, &s->change);
 +              it = string_list_insert(&s->change, p->two->path);
                d = it->util;
                if (!d) {
                        d = xcalloc(1, sizeof(*d));
@@@ -391,13 -309,9 +391,13 @@@ static void wt_status_collect_changes_w
        DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES);
        if (!s->show_untracked_files)
                DIFF_OPT_SET(&rev.diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
 +      if (s->ignore_submodule_arg) {
 +              DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
 +              handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
 +    }
        rev.diffopt.format_callback = wt_status_collect_changed_cb;
        rev.diffopt.format_callback_data = s;
 -      rev.prune_data = s->pathspec;
 +      init_pathspec(&rev.prune_data, s->pathspec);
        run_diff_files(&rev, 0);
  }
  
@@@ -411,35 -325,28 +411,35 @@@ static void wt_status_collect_changes_i
        opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
        setup_revisions(0, NULL, &rev, &opt);
  
 +      if (s->ignore_submodule_arg) {
 +              DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
 +              handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
 +      }
 +
        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
        rev.diffopt.format_callback = wt_status_collect_updated_cb;
        rev.diffopt.format_callback_data = s;
        rev.diffopt.detect_rename = 1;
        rev.diffopt.rename_limit = 200;
        rev.diffopt.break_opt = 0;
 -      rev.prune_data = s->pathspec;
 +      init_pathspec(&rev.prune_data, s->pathspec);
        run_diff_index(&rev, 1);
  }
  
  static void wt_status_collect_changes_initial(struct wt_status *s)
  {
 +      struct pathspec pathspec;
        int i;
  
 +      init_pathspec(&pathspec, s->pathspec);
        for (i = 0; i < active_nr; i++) {
                struct string_list_item *it;
                struct wt_status_change_data *d;
                struct cache_entry *ce = active_cache[i];
  
 -              if (!ce_path_match(ce, s->pathspec))
 +              if (!ce_path_match(ce, &pathspec))
                        continue;
 -              it = string_list_insert(ce->name, &s->change);
 +              it = string_list_insert(&s->change, ce->name);
                d = it->util;
                if (!d) {
                        d = xcalloc(1, sizeof(*d));
                else
                        d->index_status = DIFF_STATUS_ADDED;
        }
 +      free_pathspec(&pathspec);
  }
  
  static void wt_status_collect_untracked(struct wt_status *s)
        fill_directory(&dir, s->pathspec);
        for (i = 0; i < dir.nr; i++) {
                struct dir_entry *ent = dir.entries[i];
 -              if (!cache_name_is_other(ent->name, ent->len))
 -                      continue;
 -              if (!match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
 -                      continue;
 -              string_list_insert(ent->name, &s->untracked);
 +              if (cache_name_is_other(ent->name, ent->len) &&
 +                  match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
 +                      string_list_insert(&s->untracked, ent->name);
                free(ent);
        }
  
                fill_directory(&dir, s->pathspec);
                for (i = 0; i < dir.nr; i++) {
                        struct dir_entry *ent = dir.entries[i];
 -                      if (!cache_name_is_other(ent->name, ent->len))
 -                              continue;
 -                      if (!match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
 -                              continue;
 -                      string_list_insert(ent->name, &s->ignored);
 +                      if (cache_name_is_other(ent->name, ent->len) &&
 +                          match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
 +                              string_list_insert(&s->ignored, ent->name);
                        free(ent);
                }
        }
@@@ -608,18 -518,17 +608,18 @@@ static void wt_status_print_submodule_s
        struct child_process sm_summary;
        char summary_limit[64];
        char index[PATH_MAX];
 -      const char *env[] = { index, NULL };
 -      const char *argv[] = {
 -              "submodule",
 -              "summary",
 -              uncommitted ? "--files" : "--cached",
 -              "--for-status",
 -              "--summary-limit",
 -              summary_limit,
 -              uncommitted ? NULL : (s->amend ? "HEAD^" : "HEAD"),
 -              NULL
 -      };
 +      const char *env[] = { NULL, NULL };
 +      const char *argv[8];
 +
 +      env[0] =        index;
 +      argv[0] =       "submodule";
 +      argv[1] =       "summary";
 +      argv[2] =       uncommitted ? "--files" : "--cached";
 +      argv[3] =       "--for-status";
 +      argv[4] =       "--summary-limit";
 +      argv[5] =       summary_limit;
 +      argv[6] =       uncommitted ? NULL : (s->amend ? "HEAD^" : "HEAD");
 +      argv[7] =       NULL;
  
        sprintf(summary_limit, "%d", s->submodule_summary);
        snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", s->index_file);
@@@ -642,7 -551,7 +642,7 @@@ static void wt_status_print_other(struc
        int i;
        struct strbuf buf = STRBUF_INIT;
  
-       if (!s->untracked.nr)
+       if (!l->nr)
                return;
  
        wt_status_print_other_header(s, what, how);
        for (i = 0; i < l->nr; i++) {
                struct string_list_item *it;
                it = &(l->items[i]);
 -              color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
 -              color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED, s), "%s",
 -                               quote_path(it->string, strlen(it->string),
 +              status_printf(s, color(WT_STATUS_HEADER, s), "\t");
 +              status_printf_more(s, color(WT_STATUS_UNTRACKED, s),
 +                      "%s\n", quote_path(it->string, strlen(it->string),
                                            &buf, s->prefix));
        }
        strbuf_release(&buf);
@@@ -706,74 -615,61 +706,74 @@@ static void wt_status_print_tracking(st
  
  void wt_status_print(struct wt_status *s)
  {
 -      const char *branch_color = color(WT_STATUS_HEADER, s);
 +      const char *branch_color = color(WT_STATUS_ONBRANCH, s);
 +      const char *branch_status_color = color(WT_STATUS_HEADER, s);
  
        if (s->branch) {
 -              const char *on_what = "On branch ";
 +              const char *on_what = _("On branch ");
                const char *branch_name = s->branch;
                if (!prefixcmp(branch_name, "refs/heads/"))
                        branch_name += 11;
                else if (!strcmp(branch_name, "HEAD")) {
                        branch_name = "";
 -                      branch_color = color(WT_STATUS_NOBRANCH, s);
 -                      on_what = "Not currently on any branch.";
 +                      branch_status_color = color(WT_STATUS_NOBRANCH, s);
 +                      on_what = _("Not currently on any branch.");
                }
 -              color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "# ");
 -              color_fprintf_ln(s->fp, branch_color, "%s%s", on_what, branch_name);
 +              status_printf(s, color(WT_STATUS_HEADER, s), "");
 +              status_printf_more(s, branch_status_color, "%s", on_what);
 +              status_printf_more(s, branch_color, "%s\n", branch_name);
                if (!s->is_initial)
                        wt_status_print_tracking(s);
        }
  
        if (s->is_initial) {
 -              color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
 -              color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# Initial commit");
 -              color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
 +              status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
 +              status_printf_ln(s, color(WT_STATUS_HEADER, s), _("Initial commit"));
 +              status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
        }
  
        wt_status_print_updated(s);
        wt_status_print_unmerged(s);
        wt_status_print_changed(s);
 -      if (s->submodule_summary) {
 +      if (s->submodule_summary &&
 +          (!s->ignore_submodule_arg ||
 +           strcmp(s->ignore_submodule_arg, "all"))) {
                wt_status_print_submodule_summary(s, 0);  /* staged */
                wt_status_print_submodule_summary(s, 1);  /* unstaged */
        }
        if (s->show_untracked_files) {
 -              wt_status_print_other(s, &s->untracked, "Untracked", "add");
 +              wt_status_print_other(s, &s->untracked, _("Untracked"), "add");
                if (s->show_ignored_files)
 -                      wt_status_print_other(s, &s->ignored, "Ignored", "add -f");
 +                      wt_status_print_other(s, &s->ignored, _("Ignored"), "add -f");
        } else if (s->commitable)
 -               fprintf(s->fp, "# Untracked files not listed (use -u option to show untracked files)\n");
 +              status_printf_ln(s, GIT_COLOR_NORMAL, _("Untracked files not listed%s"),
 +                      advice_status_hints
 +                      ? _(" (use -u option to show untracked files)") : "");
  
        if (s->verbose)
                wt_status_print_verbose(s);
        if (!s->commitable) {
                if (s->amend)
 -                      fprintf(s->fp, "# No changes\n");
 +                      status_printf_ln(s, GIT_COLOR_NORMAL, _("No changes"));
                else if (s->nowarn)
                        ; /* nothing */
                else if (s->workdir_dirty)
 -                      printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
 +                      printf(_("no changes added to commit%s\n"),
 +                              advice_status_hints
 +                              ? _(" (use \"git add\" and/or \"git commit -a\")") : "");
                else if (s->untracked.nr)
 -                      printf("nothing added to commit but untracked files present (use \"git add\" to track)\n");
 +                      printf(_("nothing added to commit but untracked files present%s\n"),
 +                              advice_status_hints
 +                              ? _(" (use \"git add\" to track)") : "");
                else if (s->is_initial)
 -                      printf("nothing to commit (create/copy files and use \"git add\" to track)\n");
 +                      printf(_("nothing to commit%s\n"), advice_status_hints
 +                              ? _(" (create/copy files and use \"git add\" to track)") : "");
                else if (!s->show_untracked_files)
 -                      printf("nothing to commit (use -u to show untracked files)\n");
 +                      printf(_("nothing to commit%s\n"), advice_status_hints
 +                              ? _(" (use -u to show untracked files)") : "");
                else
 -                      printf("nothing to commit (working directory clean)\n");
 +                      printf(_("nothing to commit%s\n"), advice_status_hints
 +                              ? _(" (working directory clean)") : "");
        }
  }
  
@@@ -827,20 -723,10 +827,20 @@@ static void wt_shortstatus_status(int n
                const char *one;
                if (d->head_path) {
                        one = quote_path(d->head_path, -1, &onebuf, s->prefix);
 +                      if (*one != '"' && strchr(one, ' ') != NULL) {
 +                              putchar('"');
 +                              strbuf_addch(&onebuf, '"');
 +                              one = onebuf.buf;
 +                      }
                        printf("%s -> ", one);
                        strbuf_release(&onebuf);
                }
                one = quote_path(it->string, -1, &onebuf, s->prefix);
 +              if (*one != '"' && strchr(one, ' ') != NULL) {
 +                      putchar('"');
 +                      strbuf_addch(&onebuf, '"');
 +                      one = onebuf.buf;
 +              }
                printf("%s\n", one);
                strbuf_release(&onebuf);
        }
@@@ -855,75 -741,15 +855,75 @@@ static void wt_shortstatus_other(int nu
                struct strbuf onebuf = STRBUF_INIT;
                const char *one;
                one = quote_path(it->string, -1, &onebuf, s->prefix);
 -              color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), sign);
 +              color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign);
                printf(" %s\n", one);
                strbuf_release(&onebuf);
        }
  }
  
 -void wt_shortstatus_print(struct wt_status *s, int null_termination)
 +static void wt_shortstatus_print_tracking(struct wt_status *s)
 +{
 +      struct branch *branch;
 +      const char *header_color = color(WT_STATUS_HEADER, s);
 +      const char *branch_color_local = color(WT_STATUS_LOCAL_BRANCH, s);
 +      const char *branch_color_remote = color(WT_STATUS_REMOTE_BRANCH, s);
 +
 +      const char *base;
 +      const char *branch_name;
 +      int num_ours, num_theirs;
 +
 +      color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "## ");
 +
 +      if (!s->branch)
 +              return;
 +      branch_name = s->branch;
 +
 +      if (!prefixcmp(branch_name, "refs/heads/"))
 +              branch_name += 11;
 +      else if (!strcmp(branch_name, "HEAD")) {
 +              branch_name = _("HEAD (no branch)");
 +              branch_color_local = color(WT_STATUS_NOBRANCH, s);
 +      }
 +
 +      branch = branch_get(s->branch + 11);
 +      if (s->is_initial)
 +              color_fprintf(s->fp, header_color, _("Initial commit on "));
 +      if (!stat_tracking_info(branch, &num_ours, &num_theirs)) {
 +              color_fprintf_ln(s->fp, branch_color_local,
 +                      "%s", branch_name);
 +              return;
 +      }
 +
 +      base = branch->merge[0]->dst;
 +      base = shorten_unambiguous_ref(base, 0);
 +      color_fprintf(s->fp, branch_color_local, "%s", branch_name);
 +      color_fprintf(s->fp, header_color, "...");
 +      color_fprintf(s->fp, branch_color_remote, "%s", base);
 +
 +      color_fprintf(s->fp, header_color, " [");
 +      if (!num_ours) {
 +              color_fprintf(s->fp, header_color, _("behind "));
 +              color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
 +      } else if (!num_theirs) {
 +              color_fprintf(s->fp, header_color, _("ahead "));
 +              color_fprintf(s->fp, branch_color_local, "%d", num_ours);
 +      } else {
 +              color_fprintf(s->fp, header_color, _("ahead "));
 +              color_fprintf(s->fp, branch_color_local, "%d", num_ours);
 +              color_fprintf(s->fp, header_color, _(", behind "));
 +              color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
 +      }
 +
 +      color_fprintf_ln(s->fp, header_color, "]");
 +}
 +
 +void wt_shortstatus_print(struct wt_status *s, int null_termination, int show_branch)
  {
        int i;
 +
 +      if (show_branch)
 +              wt_shortstatus_print_tracking(s);
 +
        for (i = 0; i < s->change.nr; i++) {
                struct wt_status_change_data *d;
                struct string_list_item *it;
@@@ -954,5 -780,5 +954,5 @@@ void wt_porcelain_print(struct wt_statu
        s->use_color = 0;
        s->relative_paths = 0;
        s->prefix = NULL;
 -      wt_shortstatus_print(s, null_termination);
 +      wt_shortstatus_print(s, null_termination, 0);
  }