Merge branch 'jl/status-added-submodule-is-never-ignored' into maint
authorJunio C Hamano <gitster@pobox.com>
Wed, 25 Jun 2014 18:50:03 +0000 (11:50 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 25 Jun 2014 18:50:03 +0000 (11:50 -0700)
"git status" (and "git commit") behaved as if changes in a modified
submodule are not there if submodule.*.ignore configuration is set,
which was misleading.  The configuration is only to unclutter diff
output during the course of development, and should not to hide
changes in the "status" output to cause the users forget to commit
them.

* jl/status-added-submodule-is-never-ignored:
  commit -m: commit staged submodules regardless of ignore config
  status/commit: show staged submodules regardless of ignore config

1  2 
Documentation/config.txt
builtin/commit.c
wt-status.c

diff --combined Documentation/config.txt
@@@ -78,8 -78,8 +78,8 @@@ be escaped: use `\"` for `"` and `\\` f
  
  The following escape sequences (beside `\"` and `\\`) are recognized:
  `\n` for newline character (NL), `\t` for horizontal tabulation (HT, TAB)
 -and `\b` for backspace (BS).  No other char escape sequence, nor octal
 -char sequences are valid.
 +and `\b` for backspace (BS).  Other char escape sequences (including octal
 +escape sequences) are invalid.
  
  Variable values ending in a `\` are continued on the next line in the
  customary UNIX fashion.
@@@ -131,13 -131,8 +131,13 @@@ Variable
  
  Note that this list is non-comprehensive and not necessarily complete.
  For command-specific variables, you will find a more detailed description
 -in the appropriate manual page. You will find a description of non-core
 -porcelain configuration variables in the respective porcelain documentation.
 +in the appropriate manual page.
 +
 +Other git-related tools may and do use their own variables.  When
 +inventing new variables for use in your own tool, make sure their
 +names do not conflict with those that are used by Git itself and
 +other popular tools, and describe them in your documentation.
 +
  
  advice.*::
        These variables control various optional help messages designed to
  --
        pushUpdateRejected::
                Set this variable to 'false' if you want to disable
 -              'pushNonFFCurrent', 'pushNonFFDefault',
 +              'pushNonFFCurrent',
                'pushNonFFMatching', 'pushAlreadyExists',
                'pushFetchFirst', and 'pushNeedsForce'
                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
@@@ -826,7 -827,7 +826,7 @@@ color.diff:
        commands will only use color when output is to the terminal.
        Defaults to false.
  +
 -This does not affect linkgit:git-format-patch[1] nor the
 +This does not affect linkgit:git-format-patch[1] or the
  'git-diff-{asterisk}' plumbing commands.  Can be overridden on the
  command line with the `--color[=<when>]` option.
  
@@@ -991,14 -992,6 +991,14 @@@ commit.cleanup:
        have to remove the help lines that begin with `#` in the commit log
        template yourself, if you do this).
  
 +commit.gpgsign::
 +
 +      A boolean to specify whether all commits should be GPG signed.
 +      Use of this option when doing operations such as rebase can
 +      result in a large number of commits being signed. It may be
 +      convenient to use an agent to avoid typing your GPG passphrase
 +      several times.
 +
  commit.status::
        A boolean to enable/disable inclusion of status information in the
        commit message template when using an editor to prepare the commit
@@@ -1156,11 -1149,6 +1156,11 @@@ filter.<driver>.smudge:
        object to a worktree file upon checkout.  See
        linkgit:gitattributes[5] for details.
  
 +gc.aggressiveDepth::
 +      The depth parameter used in the delta compression
 +      algorithm used by 'git gc --aggressive'.  This defaults
 +      to 250.
 +
  gc.aggressiveWindow::
        The window size parameter used in the delta compression
        algorithm used by 'git gc --aggressive'.  This defaults
@@@ -1179,10 -1167,6 +1179,10 @@@ gc.autopacklimit:
        --auto` consolidates them into one larger pack.  The
        default value is 50.  Setting this to 0 disables it.
  
 +gc.autodetach::
 +      Make `git gc --auto` return immediately andrun in background
 +      if the system supports it. Default is true.
 +
  gc.packrefs::
        Running `git pack-refs` in a repository renders it
        unclonable by Git versions prior to 1.5.1.2 over dumb
@@@ -1340,10 -1324,6 +1340,10 @@@ gui.diffcontext:
        Specifies how many context lines should be used in calls to diff
        made by the linkgit:git-gui[1]. The default is "5".
  
 +gui.displayuntracked::
 +      Determines if linkgit::git-gui[1] shows untracked files
 +      in the file list. The default is "true".
 +
  gui.encoding::
        Specifies the default encoding to use for displaying of
        file contents in linkgit:git-gui[1] and linkgit:gitk[1].
@@@ -1621,10 -1601,6 +1621,10 @@@ imap:
        The configuration variables in the 'imap' section are described
        in linkgit:git-imap-send[1].
  
 +index.version::
 +      Specify the version with which new index files should be
 +      initialized.  This does not affect existing repositories.
 +
  init.templatedir::
        Specify the directory from which templates will be copied.
        (See the "TEMPLATE DIRECTORY" section of linkgit:git-init[1].)
@@@ -1657,7 -1633,7 +1657,7 @@@ interactive.singlekey:
        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
 -      is not available.
 +      is not available; requires the Perl module Term::ReadKey.
  
  log.abbrevCommit::
        If true, makes linkgit:git-log[1], linkgit:git-show[1], and
@@@ -1886,31 -1862,6 +1886,31 @@@ pack.packSizeLimit:
        Common unit suffixes of 'k', 'm', or 'g' are
        supported.
  
 +pack.useBitmaps::
 +      When true, git will use pack bitmaps (if available) when packing
 +      to stdout (e.g., during the server side of a fetch). Defaults to
 +      true. You should not generally need to turn this off unless
 +      you are debugging pack bitmaps.
 +
 +pack.writebitmaps::
 +      When true, git will write a bitmap index when packing all
 +      objects to disk (e.g., when `git repack -a` is run).  This
 +      index can speed up the "counting objects" phase of subsequent
 +      packs created for clones and fetches, at the cost of some disk
 +      space and extra time spent on the initial repack.  Defaults to
 +      false.
 +
 +pack.writeBitmapHashCache::
 +      When true, git will include a "hash cache" section in the bitmap
 +      index (if one is written). This cache can be used to feed git's
 +      delta heuristics, potentially leading to better deltas between
 +      bitmapped and non-bitmapped objects (e.g., when serving a fetch
 +      between an older, bitmapped pack and objects that have been
 +      pushed since the last gc). The downside is that it consumes 4
 +      bytes per object of disk space, and that JGit's bitmap
 +      implementation does not understand it, causing it to complain if
 +      Git and JGit are used on the same repository. Defaults to false.
 +
  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.
@@@ -1930,16 -1881,6 +1930,16 @@@ pretty.<name>:
        Note that an alias with the same name as a built-in format
        will be silently ignored.
  
 +pull.ff::
 +      By default, Git does not create an extra merge commit when merging
 +      a commit that is a descendant of the current commit. Instead, the
 +      tip of the current branch is fast-forwarded. When set to `false`,
 +      this variable tells Git to create an extra merge commit in such
 +      a case (equivalent to giving the `--no-ff` option from the command
 +      line). When set to `only`, only such fast-forward merges are
 +      allowed (equivalent to giving the `--ff-only` option from the
 +      command line).
 +
  pull.rebase::
        When true, rebase branches on top of the fetched branch, instead
        of merging the default branch from the default remote when "git
@@@ -1992,7 -1933,7 +1992,7 @@@ When pushing to a remote that is differ
  pull from, work as `current`.  This is the safest option and is suited
  for beginners.
  +
 -This mode will become the default in Git 2.0.
 +This mode has become the default in Git 2.0.
  
  * `matching` - push all branches having the same name on both ends.
    This makes the repository you are pushing to remember the set of
@@@ -2011,8 -1952,8 +2011,8 @@@ suitable for pushing into a shared cent
  people may add new branches there, or update the tip of existing
  branches outside your control.
  +
 -This is currently the default, but Git 2.0 will change the default
 -to `simple`.
 +This used to be the default, but not since Git 2.0 (`simple` is the
 +new default).
  
  --
  
@@@ -2170,13 -2111,6 +2170,13 @@@ repack.usedeltabaseoffset:
        "false" and repack. Access from old Git versions over the
        native protocol are unaffected by this option.
  
 +repack.packKeptObjects::
 +      If set to true, makes `git repack` act as if
 +      `--pack-kept-objects` was passed. See linkgit:git-repack[1] for
 +      details. Defaults to `false` normally, but `true` if a bitmap
 +      index is being written (either via `--write-bitmap-index` or
 +      `pack.writeBitmaps`).
 +
  rerere.autoupdate::
        When set to true, `git-rerere` updates the index with the
        resulting contents after it cleanly resolves conflicts using
@@@ -2293,7 -2227,9 +2293,9 @@@ status.submodulesummary:
        --summary-limit option of linkgit:git-submodule[1]). Please note
        that the summary output command will be suppressed for all
        submodules when `diff.ignoreSubmodules` is set to 'all' or only
-       for those submodules where `submodule.<name>.ignore=all`. To
+       for those submodules where `submodule.<name>.ignore=all`. The only
+       exception to that rule is that status and commit will show staged
+       submodule changes. To
        also view the summary for ignored submodules you can either use
        the --ignore-submodules=dirty command line option or the 'git
        submodule summary' command, which shows a similar output but does
@@@ -2324,7 -2260,9 +2326,9 @@@ submodule.<name>.fetchRecurseSubmodules
  submodule.<name>.ignore::
        Defines under what circumstances "git status" and the diff family show
        a submodule as modified. When set to "all", it will never be considered
-       modified, "dirty" will ignore all changes to the submodules work tree and
+       modified (but it will nonetheless show up in the output of status and
+       commit when it has been staged), "dirty" will ignore all changes
+       to the submodules work tree and
        takes only differences between the HEAD of the submodule and the commit
        recorded in the superproject into account. "untracked" will additionally
        let submodules with modified tracked files in their work tree show up.
@@@ -2357,13 -2295,6 +2361,13 @@@ transfer.unpackLimit:
        not set, the value of this variable is used instead.
        The default value is 100.
  
 +uploadarchive.allowUnreachable::
 +      If true, allow clients to use `git archive --remote` to request
 +      any tree, whether reachable from the ref tips or not. See the
 +      discussion in the `SECURITY` section of
 +      linkgit:git-upload-archive[1] for more details. Defaults to
 +      `false`.
 +
  uploadpack.hiderefs::
        String(s) `upload-pack` uses to decide which refs to omit
        from its initial advertisement.  Use more than one
diff --combined builtin/commit.c
@@@ -113,7 -113,6 +113,7 @@@ static char *sign_commit
  static enum {
        CLEANUP_SPACE,
        CLEANUP_NONE,
 +      CLEANUP_SCISSORS,
        CLEANUP_ALL
  } cleanup_mode;
  static const char *cleanup_arg;
@@@ -235,7 -234,7 +235,7 @@@ static int list_paths(struct string_lis
  
                if (ce->ce_flags & CE_UPDATE)
                        continue;
 -              if (!match_pathspec_depth(pattern, ce->name, ce_namelen(ce), 0, m))
 +              if (!ce_path_match(ce, pattern, m))
                        continue;
                item = string_list_insert(list, ce->name);
                if (ce_skip_worktree(ce))
@@@ -308,6 -307,7 +308,6 @@@ static char *prepare_index(int argc, co
        int fd;
        struct string_list partial;
        struct pathspec pathspec;
 -      char *old_index_env = NULL;
        int refresh_flags = REFRESH_QUIET;
  
        if (is_status)
                die(_("index file corrupt"));
  
        if (interactive) {
 +              char *old_index_env = NULL;
                fd = hold_locked_index(&index_lock, 1);
  
                refresh_cache_or_die(refresh_flags);
@@@ -601,17 -600,19 +601,17 @@@ static int prepare_to_commit(const cha
  {
        struct stat statbuf;
        struct strbuf committer_ident = STRBUF_INIT;
 -      int commitable, saved_color_setting;
 +      int commitable;
        struct strbuf sb = STRBUF_INIT;
 -      char *buffer;
        const char *hook_arg1 = NULL;
        const char *hook_arg2 = NULL;
 -      int ident_shown = 0;
        int clean_message_contents = (cleanup_mode != CLEANUP_NONE);
        int old_display_comment_prefix;
  
        /* This checks and barfs if author is badly specified */
        determine_author_info(author_ident);
  
 -      if (!no_verify && run_hook(index_file, "pre-commit", NULL))
 +      if (!no_verify && run_commit_hook(use_editor, index_file, "pre-commit", NULL))
                return 0;
  
        if (squash_message) {
                                  logfile);
                hook_arg1 = "message";
        } else if (use_message) {
 +              char *buffer;
                buffer = strstr(use_message_buffer, "\n\n");
 -              if (!use_editor && (!buffer || buffer[2] == '\0'))
 -                      die(_("commit has empty message"));
 -              strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
 +              if (buffer)
 +                      strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
                hook_arg1 = "commit";
                hook_arg2 = use_message;
        } else if (fixup_message) {
        /* This checks if committer ident is explicitly given */
        strbuf_addstr(&committer_ident, git_committer_info(IDENT_STRICT));
        if (use_editor && include_status) {
 +              int ident_shown = 0;
 +              int saved_color_setting;
                char *ai_tmp, *ci_tmp;
 -              if (whence != FROM_COMMIT)
 +              if (whence != FROM_COMMIT) {
 +                      if (cleanup_mode == CLEANUP_SCISSORS)
 +                              wt_status_add_cut_line(s->fp);
                        status_printf_ln(s, GIT_COLOR_NORMAL,
                            whence == FROM_MERGE
                                ? _("\n"
                                git_path(whence == FROM_MERGE
                                         ? "MERGE_HEAD"
                                         : "CHERRY_PICK_HEAD"));
 +              }
  
                fprintf(s->fp, "\n");
                if (cleanup_mode == CLEANUP_ALL)
                                _("Please enter the commit message for your changes."
                                  " Lines starting\nwith '%c' will be ignored, and an empty"
                                  " message aborts the commit.\n"), comment_line_char);
 +              else if (cleanup_mode == CLEANUP_SCISSORS && whence == FROM_COMMIT)
 +                      wt_status_add_cut_line(s->fp);
                else /* CLEANUP_SPACE, that is. */
                        status_printf(s, GIT_COLOR_NORMAL,
                                _("Please enter the commit message for your changes."
  
                if (get_sha1(parent, sha1))
                        commitable = !!active_nr;
-               else
-                       commitable = index_differs_from(parent, 0);
+               else {
+                       /*
+                        * Unless the user did explicitly request a submodule
+                        * ignore mode by passing a command line option we do
+                        * not ignore any changed submodule SHA-1s when
+                        * comparing index and parent, no matter what is
+                        * configured. Otherwise we won't commit any
+                        * submodules which were manually staged, which would
+                        * be really confusing.
+                        */
+                       int diff_flags = DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG;
+                       if (ignore_submodule_arg &&
+                           !strcmp(ignore_submodule_arg, "all"))
+                               diff_flags |= DIFF_OPT_IGNORE_SUBMODULES;
+                       commitable = index_differs_from(parent, diff_flags);
+               }
        }
        strbuf_release(&committer_ident);
  
                return 0;
        }
  
 -      if (run_hook(index_file, "prepare-commit-msg",
 -                   git_path(commit_editmsg), hook_arg1, hook_arg2, NULL))
 +      if (run_commit_hook(use_editor, index_file, "prepare-commit-msg",
 +                          git_path(commit_editmsg), hook_arg1, hook_arg2, NULL))
                return 0;
  
        if (use_editor) {
        }
  
        if (!no_verify &&
 -          run_hook(index_file, "commit-msg", git_path(commit_editmsg), NULL)) {
 +          run_commit_hook(use_editor, index_file, "commit-msg", git_path(commit_editmsg), NULL)) {
                return 0;
        }
  
@@@ -1073,6 -1081,8 +1087,6 @@@ static int parse_and_validate_options(i
                use_editor = 0;
        if (0 <= edit_flag)
                use_editor = edit_flag;
 -      if (!use_editor)
 -              setenv("GIT_EDITOR", ":", 1);
  
        /* Sanity check options */
        if (amend && !current_head)
        if (argc == 0 && only && amend)
                only_include_assumed = _("Clever... amending the last one with dirty index.");
        if (argc > 0 && !also && !only)
 -              only_include_assumed = _("Explicit paths specified without -i nor -o; assuming --only paths...");
 +              only_include_assumed = _("Explicit paths specified without -i or -o; assuming --only paths...");
        if (!cleanup_arg || !strcmp(cleanup_arg, "default"))
                cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE;
        else if (!strcmp(cleanup_arg, "verbatim"))
                cleanup_mode = CLEANUP_SPACE;
        else if (!strcmp(cleanup_arg, "strip"))
                cleanup_mode = CLEANUP_ALL;
 +      else if (!strcmp(cleanup_arg, "scissors"))
 +              cleanup_mode = use_editor ? CLEANUP_SCISSORS : CLEANUP_SPACE;
        else
                die(_("Invalid cleanup mode %s"), cleanup_arg);
  
@@@ -1412,10 -1420,6 +1426,10 @@@ static int git_commit_config(const cha
        }
        if (!strcmp(k, "commit.cleanup"))
                return git_config_string(&cleanup_arg, k, v);
 +      if (!strcmp(k, "commit.gpgsign")) {
 +              sign_commit = git_config_bool(k, v) ? "" : NULL;
 +              return 0;
 +      }
  
        status = git_gpg_config(k, v, NULL);
        if (status)
@@@ -1455,29 -1459,6 +1469,29 @@@ static int run_rewrite_hook(const unsig
        return finish_command(&proc);
  }
  
 +int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...)
 +{
 +      const char *hook_env[3] =  { NULL };
 +      char index[PATH_MAX];
 +      va_list args;
 +      int ret;
 +
 +      snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
 +      hook_env[0] = index;
 +
 +      /*
 +       * Let the hook know that no editor will be launched.
 +       */
 +      if (!editor_is_used)
 +              hook_env[1] = "GIT_EDITOR=:";
 +
 +      va_start(args, name);
 +      ret = run_hook_ve(hook_env, name, args);
 +      va_end(args);
 +
 +      return ret;
 +}
 +
  int cmd_commit(int argc, const char **argv, const char *prefix)
  {
        static struct wt_status s;
                OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
                OPT_STRING(0, "cleanup", &cleanup_arg, N_("default"), N_("how to strip spaces and #comments from message")),
                OPT_BOOL(0, "status", &include_status, N_("include status in commit message template")),
 -              { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key id"),
 +              { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"),
                  N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
                /* end commit message options */
  
        struct ref_lock *ref_lock;
        struct commit_list *parents = NULL, **pptr = &parents;
        struct stat statbuf;
 -      int allow_fast_forward = 1;
        struct commit *current_head = NULL;
        struct commit_extra_header *extra = NULL;
  
        } else if (whence == FROM_MERGE) {
                struct strbuf m = STRBUF_INIT;
                FILE *fp;
 +              int allow_fast_forward = 1;
  
                if (!reflog_msg)
                        reflog_msg = "commit (merge)";
                die(_("could not read commit message: %s"), strerror(saved_errno));
        }
  
 -      /* Truncate the message just before the diff, if any. */
 -      if (verbose)
 +      if (verbose || /* Truncate the message just before the diff, if any. */
 +          cleanup_mode == CLEANUP_SCISSORS)
                wt_status_truncate_message_at_cut_line(&sb);
  
        if (cleanup_mode != CLEANUP_NONE)
                     "not exceeded, and then \"git reset HEAD\" to recover."));
  
        rerere(0);
 -      run_hook(get_index_file(), "post-commit", NULL);
 +      run_commit_hook(use_editor, get_index_file(), "post-commit", NULL);
        if (amend && !no_post_rewrite) {
                struct notes_rewrite_cfg *cfg;
                cfg = init_copy_notes_for_rewrite("amend");
diff --combined wt-status.c
@@@ -17,7 -17,7 +17,7 @@@
  #include "strbuf.h"
  #include "utf8.h"
  
 -static char cut_line[] =
 +static const char cut_line[] =
  "------------------------ >8 ------------------------\n";
  
  static char default_wt_status_colors[][COLOR_MAXLEN] = {
@@@ -245,92 -245,53 +245,92 @@@ static void wt_status_print_trailer(str
  
  #define quote_path quote_path_relative
  
 -static void wt_status_print_unmerged_data(struct wt_status *s,
 -                                        struct string_list_item *it)
 +static const char *wt_status_unmerged_status_string(int stagemask)
  {
 -      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");
 -
 -      one = quote_path(it->string, s->prefix, &onebuf);
 -      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;
 +      switch (stagemask) {
 +      case 1:
 +              return _("both deleted:");
 +      case 2:
 +              return _("added by us:");
 +      case 3:
 +              return _("deleted by them:");
 +      case 4:
 +              return _("added by them:");
 +      case 5:
 +              return _("deleted by us:");
 +      case 6:
 +              return _("both added:");
 +      case 7:
 +              return _("both modified:");
 +      default:
 +              die(_("bug: unhandled unmerged status %x"), stagemask);
        }
 -      status_printf_more(s, c, "%-20s%s\n", how, one);
 -      strbuf_release(&onebuf);
  }
  
  static const char *wt_status_diff_status_string(int status)
  {
        switch (status) {
        case DIFF_STATUS_ADDED:
 -              return _("new file");
 +              return _("new file:");
        case DIFF_STATUS_COPIED:
 -              return _("copied");
 +              return _("copied:");
        case DIFF_STATUS_DELETED:
 -              return _("deleted");
 +              return _("deleted:");
        case DIFF_STATUS_MODIFIED:
 -              return _("modified");
 +              return _("modified:");
        case DIFF_STATUS_RENAMED:
 -              return _("renamed");
 +              return _("renamed:");
        case DIFF_STATUS_TYPE_CHANGED:
 -              return _("typechange");
 +              return _("typechange:");
        case DIFF_STATUS_UNKNOWN:
 -              return _("unknown");
 +              return _("unknown:");
        case DIFF_STATUS_UNMERGED:
 -              return _("unmerged");
 +              return _("unmerged:");
        default:
                return NULL;
        }
  }
  
 +static int maxwidth(const char *(*label)(int), int minval, int maxval)
 +{
 +      int result = 0, i;
 +
 +      for (i = minval; i <= maxval; i++) {
 +              const char *s = label(i);
 +              int len = s ? utf8_strwidth(s) : 0;
 +              if (len > result)
 +                      result = len;
 +      }
 +      return result;
 +}
 +
 +static void wt_status_print_unmerged_data(struct wt_status *s,
 +                                        struct string_list_item *it)
 +{
 +      const char *c = color(WT_STATUS_UNMERGED, s);
 +      struct wt_status_change_data *d = it->util;
 +      struct strbuf onebuf = STRBUF_INIT;
 +      static char *padding;
 +      static int label_width;
 +      const char *one, *how;
 +      int len;
 +
 +      if (!padding) {
 +              label_width = maxwidth(wt_status_unmerged_status_string, 1, 7);
 +              label_width += strlen(" ");
 +              padding = xmallocz(label_width);
 +              memset(padding, ' ', label_width);
 +      }
 +
 +      one = quote_path(it->string, s->prefix, &onebuf);
 +      status_printf(s, color(WT_STATUS_HEADER, s), "\t");
 +
 +      how = wt_status_unmerged_status_string(d->stagemask);
 +      len = label_width - utf8_strwidth(how);
 +      status_printf_more(s, c, "%s%.*s%s\n", how, len, padding, one);
 +      strbuf_release(&onebuf);
 +}
 +
  static void wt_status_print_change_data(struct wt_status *s,
                                        int change_type,
                                        struct string_list_item *it)
        struct strbuf onebuf = STRBUF_INIT, twobuf = STRBUF_INIT;
        struct strbuf extra = STRBUF_INIT;
        static char *padding;
 +      static int label_width;
        const char *what;
        int len;
  
        if (!padding) {
 -              int width = 0;
 -              /* If DIFF_STATUS_* uses outside this range, we're in trouble */
 -              for (status = 'A'; status <= 'Z'; status++) {
 -                      what = wt_status_diff_status_string(status);
 -                      len = what ? strlen(what) : 0;
 -                      if (len > width)
 -                              width = len;
 -              }
 -              width += 2;     /* colon and a space */
 -              padding = xmallocz(width);
 -              memset(padding, ' ', width);
 +              /* If DIFF_STATUS_* uses outside the range [A..Z], we're in trouble */
 +              label_width = maxwidth(wt_status_diff_status_string, 'A', 'Z');
 +              label_width += strlen(" ");
 +              padding = xmallocz(label_width);
 +              memset(padding, ' ', label_width);
        }
  
        one_name = two_name = it->string;
        what = wt_status_diff_status_string(status);
        if (!what)
                die(_("bug: unhandled diff status %c"), status);
 -      /* 1 for colon, which is not part of "what" */
 -      len = strlen(padding) - (utf8_strwidth(what) + 1);
 +      len = label_width - utf8_strwidth(what);
        assert(len >= 0);
        if (status == DIFF_STATUS_COPIED || status == DIFF_STATUS_RENAMED)
 -              status_printf_more(s, c, "%s:%.*s%s -> %s",
 +              status_printf_more(s, c, "%s%.*s%s -> %s",
                                   what, len, padding, one, two);
        else
 -              status_printf_more(s, c, "%s:%.*s%s",
 +              status_printf_more(s, c, "%s%.*s%s",
                                   what, len, padding, one);
        if (extra.len) {
                status_printf_more(s, color(WT_STATUS_HEADER, s), "%s", extra.buf);
@@@ -519,9 -486,19 +519,19 @@@ static void wt_status_collect_changes_i
        opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
        setup_revisions(0, NULL, &rev, &opt);
  
+       DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
        if (s->ignore_submodule_arg) {
-               DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
                handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
+       } else {
+               /*
+                * Unless the user did explicitly request a submodule ignore
+                * mode by passing a command line option we do not ignore any
+                * changed submodule SHA-1s when comparing index and HEAD, no
+                * matter what is configured. Otherwise the user won't be
+                * shown any submodules she manually added (and which are
+                * staged to be committed), which would be really confusing.
+                */
+               handle_ignore_submodules_arg(&rev.diffopt, "dirty");
        }
  
        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
@@@ -543,7 -520,7 +553,7 @@@ static void wt_status_collect_changes_i
                struct wt_status_change_data *d;
                const struct cache_entry *ce = active_cache[i];
  
 -              if (!ce_path_match(ce, &s->pathspec))
 +              if (!ce_path_match(ce, &s->pathspec, NULL))
                        continue;
                it = string_list_insert(&s->change, ce->name);
                d = it->util;
@@@ -585,7 -562,7 +595,7 @@@ static void wt_status_collect_untracked
        for (i = 0; i < dir.nr; i++) {
                struct dir_entry *ent = dir.entries[i];
                if (cache_name_is_other(ent->name, ent->len) &&
 -                  match_pathspec_depth(&s->pathspec, ent->name, ent->len, 0, NULL))
 +                  dir_path_match(ent, &s->pathspec, 0, NULL))
                        string_list_insert(&s->untracked, ent->name);
                free(ent);
        }
        for (i = 0; i < dir.ignored_nr; i++) {
                struct dir_entry *ent = dir.ignored[i];
                if (cache_name_is_other(ent->name, ent->len) &&
 -                  match_pathspec_depth(&s->pathspec, ent->name, ent->len, 0, NULL))
 +                  dir_path_match(ent, &s->pathspec, 0, NULL))
                        string_list_insert(&s->ignored, ent->name);
                free(ent);
        }
@@@ -841,17 -818,6 +851,17 @@@ void wt_status_truncate_message_at_cut_
        strbuf_release(&pattern);
  }
  
 +void wt_status_add_cut_line(FILE *fp)
 +{
 +      const char *explanation = _("Do not touch the line above.\nEverything below will be removed.");
 +      struct strbuf buf = STRBUF_INIT;
 +
 +      fprintf(fp, "%c %s", comment_line_char, cut_line);
 +      strbuf_add_commented_lines(&buf, explanation, strlen(explanation));
 +      fputs(buf.buf, fp);
 +      strbuf_release(&buf);
 +}
 +
  static void wt_status_print_verbose(struct wt_status *s)
  {
        struct rev_info rev;
         * diff before committing.
         */
        if (s->fp != stdout) {
 -              const char *explanation = _("Do not touch the line above.\nEverything below will be removed.");
 -              struct strbuf buf = STRBUF_INIT;
 -
                rev.diffopt.use_color = 0;
 -              fprintf(s->fp, "%c %s", comment_line_char, cut_line);
 -              strbuf_add_commented_lines(&buf, explanation, strlen(explanation));
 -              fputs(buf.buf, s->fp);
 -              strbuf_release(&buf);
 +              wt_status_add_cut_line(s->fp);
        }
        run_diff_index(&rev, 1);
  }
@@@ -1547,21 -1519,19 +1557,21 @@@ static void wt_shortstatus_print_tracki
                return;
        }
  
 +#define LABEL(string) (s->no_gettext ? (string) : _(string))
 +
        color_fprintf(s->fp, header_color, " [");
        if (upstream_is_gone) {
 -              color_fprintf(s->fp, header_color, _("gone"));
 +              color_fprintf(s->fp, header_color, LABEL(N_("gone")));
        } else if (!num_ours) {
 -              color_fprintf(s->fp, header_color, _("behind "));
 +              color_fprintf(s->fp, header_color, LABEL(N_("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, header_color, LABEL(N_(("ahead "))));
                color_fprintf(s->fp, branch_color_local, "%d", num_ours);
        } else {
 -              color_fprintf(s->fp, header_color, _("ahead "));
 +              color_fprintf(s->fp, header_color, LABEL(N_(("ahead "))));
                color_fprintf(s->fp, branch_color_local, "%d", num_ours);
 -              color_fprintf(s->fp, header_color, _(", behind "));
 +              color_fprintf(s->fp, header_color, ", %s", LABEL(N_("behind ")));
                color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
        }
  
@@@ -1606,6 -1576,5 +1616,6 @@@ void wt_porcelain_print(struct wt_statu
        s->use_color = 0;
        s->relative_paths = 0;
        s->prefix = NULL;
 +      s->no_gettext = 1;
        wt_shortstatus_print(s);
  }