Merge branch 'jh/status-v2-porcelain'
authorJunio C Hamano <gitster@pobox.com>
Fri, 9 Sep 2016 04:49:50 +0000 (21:49 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 9 Sep 2016 04:49:50 +0000 (21:49 -0700)
Enhance "git status --porcelain" output by collecting more data on
the state of the index and the working tree files, which may
further be used to teach git-prompt (in contrib/) to make fewer
calls to git.

* jh/status-v2-porcelain:
status: unit tests for --porcelain=v2
test-lib-functions.sh: add lf_to_nul helper
git-status.txt: describe --porcelain=v2 format
status: print branch info with --porcelain=v2 --branch
status: print per-file porcelain v2 status data
status: collect per-file data for --porcelain=v2
status: support --porcelain[=<version>]
status: cleanup API to wt_status_print
status: rename long-format print routines

1  2 
builtin/commit.c
t/t7060-wtstatus.sh
wt-status.c
diff --combined builtin/commit.c
index 77e3dc849419e697abe8f2f4c7d753cce17634a8,5504afe53ab42f4ec77fde817fd3396d2c816384..bb9f79b6ef4b5cd01cafac551bb6665a433334aa
@@@ -142,14 -142,24 +142,24 @@@ static int show_ignored_in_status, have
  static const char *only_include_assumed;
  static struct strbuf message = STRBUF_INIT;
  
- static enum status_format {
-       STATUS_FORMAT_NONE = 0,
-       STATUS_FORMAT_LONG,
-       STATUS_FORMAT_SHORT,
-       STATUS_FORMAT_PORCELAIN,
+ static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
  
-       STATUS_FORMAT_UNSPECIFIED
- } status_format = STATUS_FORMAT_UNSPECIFIED;
+ static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
+ {
+       enum wt_status_format *value = (enum wt_status_format *)opt->value;
+       if (unset)
+               *value = STATUS_FORMAT_NONE;
+       else if (!arg)
+               *value = STATUS_FORMAT_PORCELAIN;
+       else if (!strcmp(arg, "v1") || !strcmp(arg, "1"))
+               *value = STATUS_FORMAT_PORCELAIN;
+       else if (!strcmp(arg, "v2") || !strcmp(arg, "2"))
+               *value = STATUS_FORMAT_PORCELAIN_V2;
+       else
+               die("unsupported porcelain version '%s'", arg);
+       return 0;
+ }
  
  static int opt_parse_m(const struct option *opt, const char *arg, int unset)
  {
@@@ -500,24 -510,13 +510,13 @@@ static int run_status(FILE *fp, const c
        s->fp = fp;
        s->nowarn = nowarn;
        s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
+       if (!s->is_initial)
+               hashcpy(s->sha1_commit, sha1);
+       s->status_format = status_format;
+       s->ignore_submodule_arg = ignore_submodule_arg;
  
        wt_status_collect(s);
-       switch (status_format) {
-       case STATUS_FORMAT_SHORT:
-               wt_shortstatus_print(s);
-               break;
-       case STATUS_FORMAT_PORCELAIN:
-               wt_porcelain_print(s);
-               break;
-       case STATUS_FORMAT_UNSPECIFIED:
-               die("BUG: finalize_deferred_config() should have been called");
-               break;
-       case STATUS_FORMAT_NONE:
-       case STATUS_FORMAT_LONG:
-               wt_status_print(s);
-               break;
-       }
+       wt_status_print(s);
  
        return s->commitable;
  }
@@@ -1099,7 -1098,7 +1098,7 @@@ static const char *read_commit_message(
   * is not in effect here.
   */
  static struct status_deferred_config {
-       enum status_format status_format;
+       enum wt_status_format status_format;
        int show_branch;
  } status_deferred_config = {
        STATUS_FORMAT_UNSPECIFIED,
  static void finalize_deferred_config(struct wt_status *s)
  {
        int use_deferred_config = (status_format != STATUS_FORMAT_PORCELAIN &&
+                                  status_format != STATUS_FORMAT_PORCELAIN_V2 &&
                                   !s->null_termination);
  
        if (s->null_termination) {
@@@ -1336,9 -1336,9 +1336,9 @@@ int cmd_status(int argc, const char **a
                            N_("show status concisely"), STATUS_FORMAT_SHORT),
                OPT_BOOL('b', "branch", &s.show_branch,
                         N_("show branch information")),
-               OPT_SET_INT(0, "porcelain", &status_format,
-                           N_("machine-readable output"),
-                           STATUS_FORMAT_PORCELAIN),
+               { OPTION_CALLBACK, 0, "porcelain", &status_format,
+                 N_("version"), N_("machine-readable output"),
+                 PARSE_OPT_OPTARG, opt_parse_porcelain },
                OPT_SET_INT(0, "long", &status_format,
                            N_("show status in long format (default)"),
                            STATUS_FORMAT_LONG),
        fd = hold_locked_index(&index_lock, 0);
  
        s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
+       if (!s.is_initial)
+               hashcpy(s.sha1_commit, sha1);
        s.ignore_submodule_arg = ignore_submodule_arg;
+       s.status_format = status_format;
+       s.verbose = verbose;
        wt_status_collect(&s);
  
        if (0 <= fd)
        if (s.relative_paths)
                s.prefix = prefix;
  
-       switch (status_format) {
-       case STATUS_FORMAT_SHORT:
-               wt_shortstatus_print(&s);
-               break;
-       case STATUS_FORMAT_PORCELAIN:
-               wt_porcelain_print(&s);
-               break;
-       case STATUS_FORMAT_UNSPECIFIED:
-               die("BUG: finalize_deferred_config() should have been called");
-               break;
-       case STATUS_FORMAT_NONE:
-       case STATUS_FORMAT_LONG:
-               s.verbose = verbose;
-               s.ignore_submodule_arg = ignore_submodule_arg;
-               wt_status_print(&s);
-               break;
-       }
+       wt_status_print(&s);
        return 0;
  }
  
@@@ -1617,7 -1607,7 +1607,7 @@@ int cmd_commit(int argc, const char **a
                OPT_BOOL(0, "interactive", &interactive, N_("interactively add files")),
                OPT_BOOL('p', "patch", &patch_interactive, N_("interactively add changes")),
                OPT_BOOL('o', "only", &only, N_("commit only specified files")),
 -              OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit hook")),
 +              OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit and commit-msg hooks")),
                OPT_BOOL(0, "dry-run", &dry_run, N_("show what would be committed")),
                OPT_SET_INT(0, "short", &status_format, N_("show status concisely"),
                            STATUS_FORMAT_SHORT),
diff --combined t/t7060-wtstatus.sh
index 4d17363a926c8938c6c8d78e997a53ffb81b3c6c,00e0cebff3e8dbeb9124e063e72a611e692b8c24..53cf42fac19c83f2da7de1cd5b6c4ca0cf6e5ccf
@@@ -34,7 -34,6 +34,7 @@@ test_expect_success 'M/D conflict does 
  On branch side
  You have unmerged paths.
    (fix conflicts and run "git commit")
 +  (use "git merge --abort" to abort the merge)
  
  Unmerged paths:
    (use "git add/rm <file>..." as appropriate to mark resolution)
@@@ -139,7 -138,6 +139,7 @@@ test_expect_success 'status when confli
  On branch master
  You have unmerged paths.
    (fix conflicts and run "git commit")
 +  (use "git merge --abort" to abort the merge)
  
  Unmerged paths:
    (use "git add/rm <file>..." as appropriate to mark resolution)
@@@ -173,7 -171,6 +173,7 @@@ test_expect_success 'status when confli
  On branch conflict_second
  You have unmerged paths.
    (fix conflicts and run "git commit")
 +  (use "git merge --abort" to abort the merge)
  
  Unmerged paths:
    (use "git add/rm <file>..." as appropriate to mark resolution)
@@@ -198,7 -195,6 +198,7 @@@ test_expect_success 'status when confli
  On branch conflict_second
  You have unmerged paths.
    (fix conflicts and run "git commit")
 +  (use "git merge --abort" to abort the merge)
  
  Changes to be committed:
  
@@@ -232,4 -228,25 +232,25 @@@ test_expect_success 'status --branch wi
        test_i18ncmp expected actual
  '
  
+ ## Duplicate the above test and verify --porcelain=v1 arg parsing.
+ test_expect_success 'status --porcelain=v1 --branch with detached HEAD' '
+       git reset --hard &&
+       git checkout master^0 &&
+       git status --branch --porcelain=v1 >actual &&
+       cat >expected <<-EOF &&
+       ## HEAD (no branch)
+       ?? .gitconfig
+       ?? actual
+       ?? expect
+       ?? expected
+       ?? mdconflict/
+       EOF
+       test_i18ncmp expected actual
+ '
+ ## Verify parser error on invalid --porcelain argument.
+ test_expect_success 'status --porcelain=bogus' '
+       test_must_fail git status --porcelain=bogus
+ '
  test_done
diff --combined wt-status.c
index 6225a2d89f2c38bea6ed64295a28e7e07d19522c,ebe4a1e2470986b77dd6182ea3180e01cf2fb1bc..539aac15a37adcce6429c4d82f401a7db17fac28
@@@ -139,7 -139,7 +139,7 @@@ void wt_status_prepare(struct wt_statu
        s->display_comment_prefix = 0;
  }
  
- static void wt_status_print_unmerged_header(struct wt_status *s)
+ static void wt_longstatus_print_unmerged_header(struct wt_status *s)
  {
        int i;
        int del_mod_conflict = 0;
        status_printf_ln(s, c, "%s", "");
  }
  
- static void wt_status_print_cached_header(struct wt_status *s)
+ static void wt_longstatus_print_cached_header(struct wt_status *s)
  {
        const char *c = color(WT_STATUS_HEADER, s);
  
        status_printf_ln(s, c, "%s", "");
  }
  
- static void wt_status_print_dirty_header(struct wt_status *s,
-                                        int has_deleted,
-                                        int has_dirty_submodules)
+ static void wt_longstatus_print_dirty_header(struct wt_status *s,
+                                            int has_deleted,
+                                            int has_dirty_submodules)
  {
        const char *c = color(WT_STATUS_HEADER, s);
  
        status_printf_ln(s, c, "%s", "");
  }
  
- static void wt_status_print_other_header(struct wt_status *s,
-                                        const char *what,
-                                        const char *how)
+ static void wt_longstatus_print_other_header(struct wt_status *s,
+                                            const char *what,
+                                            const char *how)
  {
        const char *c = color(WT_STATUS_HEADER, s);
        status_printf_ln(s, c, "%s:", what);
        status_printf_ln(s, c, "%s", "");
  }
  
- static void wt_status_print_trailer(struct wt_status *s)
+ static void wt_longstatus_print_trailer(struct wt_status *s)
  {
        status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", "");
  }
@@@ -263,7 -263,7 +263,7 @@@ static const char *wt_status_unmerged_s
        case 7:
                return _("both modified:");
        default:
 -              die("bug: unhandled unmerged status %x", stagemask);
 +              die("BUG: unhandled unmerged status %x", stagemask);
        }
  }
  
@@@ -304,8 -304,8 +304,8 @@@ static int maxwidth(const char *(*label
        return result;
  }
  
- static void wt_status_print_unmerged_data(struct wt_status *s,
-                                         struct string_list_item *it)
+ static void wt_longstatus_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;
        strbuf_release(&onebuf);
  }
  
- static void wt_status_print_change_data(struct wt_status *s,
-                                       int change_type,
-                                       struct string_list_item *it)
+ static void wt_longstatus_print_change_data(struct wt_status *s,
+                                           int change_type,
+                                           struct string_list_item *it)
  {
        struct wt_status_change_data *d = it->util;
        const char *c = color(change_type, s);
                status = d->worktree_status;
                break;
        default:
-               die("BUG: unhandled change_type %d in wt_status_print_change_data",
+               die("BUG: unhandled change_type %d in wt_longstatus_print_change_data",
                    change_type);
        }
  
        status_printf(s, color(WT_STATUS_HEADER, s), "\t");
        what = wt_status_diff_status_string(status);
        if (!what)
 -              die("bug: unhandled diff status %c", status);
 +              die("BUG: unhandled diff status %c", status);
        len = label_width - utf8_strwidth(what);
        assert(len >= 0);
        if (status == DIFF_STATUS_COPIED || status == DIFF_STATUS_RENAMED)
@@@ -434,6 -434,31 +434,31 @@@ static void wt_status_collect_changed_c
                if (S_ISGITLINK(p->two->mode))
                        d->new_submodule_commits = !!oidcmp(&p->one->oid,
                                                            &p->two->oid);
+               switch (p->status) {
+               case DIFF_STATUS_ADDED:
+                       die("BUG: worktree status add???");
+                       break;
+               case DIFF_STATUS_DELETED:
+                       d->mode_index = p->one->mode;
+                       oidcpy(&d->oid_index, &p->one->oid);
+                       /* mode_worktree is zero for a delete. */
+                       break;
+               case DIFF_STATUS_MODIFIED:
+               case DIFF_STATUS_TYPE_CHANGED:
+               case DIFF_STATUS_UNMERGED:
+                       d->mode_index = p->one->mode;
+                       d->mode_worktree = p->two->mode;
+                       oidcpy(&d->oid_index, &p->one->oid);
+                       break;
+               case DIFF_STATUS_UNKNOWN:
+                       die("BUG: worktree status unknown???");
+                       break;
+               }
        }
  }
  
@@@ -479,12 -504,36 +504,36 @@@ static void wt_status_collect_updated_c
                if (!d->index_status)
                        d->index_status = p->status;
                switch (p->status) {
+               case DIFF_STATUS_ADDED:
+                       /* Leave {mode,oid}_head zero for an add. */
+                       d->mode_index = p->two->mode;
+                       oidcpy(&d->oid_index, &p->two->oid);
+                       break;
+               case DIFF_STATUS_DELETED:
+                       d->mode_head = p->one->mode;
+                       oidcpy(&d->oid_head, &p->one->oid);
+                       /* Leave {mode,oid}_index zero for a delete. */
+                       break;
                case DIFF_STATUS_COPIED:
                case DIFF_STATUS_RENAMED:
                        d->head_path = xstrdup(p->one->path);
+                       d->score = p->score * 100 / MAX_SCORE;
+                       /* fallthru */
+               case DIFF_STATUS_MODIFIED:
+               case DIFF_STATUS_TYPE_CHANGED:
+                       d->mode_head = p->one->mode;
+                       d->mode_index = p->two->mode;
+                       oidcpy(&d->oid_head, &p->one->oid);
+                       oidcpy(&d->oid_index, &p->two->oid);
                        break;
                case DIFF_STATUS_UNMERGED:
                        d->stagemask = unmerged_mask(p->two->path);
+                       /*
+                        * Don't bother setting {mode,oid}_{head,index} since the print
+                        * code will output the stage values directly and not use the
+                        * values in these fields.
+                        */
                        break;
                }
        }
@@@ -565,9 -614,17 +614,17 @@@ static void wt_status_collect_changes_i
                if (ce_stage(ce)) {
                        d->index_status = DIFF_STATUS_UNMERGED;
                        d->stagemask |= (1 << (ce_stage(ce) - 1));
-               }
-               else
+                       /*
+                        * Don't bother setting {mode,oid}_{head,index} since the print
+                        * code will output the stage values directly and not use the
+                        * values in these fields.
+                        */
+               } else {
                        d->index_status = DIFF_STATUS_ADDED;
+                       /* Leave {mode,oid}_head zero for adds. */
+                       d->mode_index = ce->ce_mode;
+                       hashcpy(d->oid_index.hash, ce->sha1);
+               }
        }
  }
  
@@@ -627,7 -684,7 +684,7 @@@ void wt_status_collect(struct wt_statu
        wt_status_collect_untracked(s);
  }
  
- static void wt_status_print_unmerged(struct wt_status *s)
+ static void wt_longstatus_print_unmerged(struct wt_status *s)
  {
        int shown_header = 0;
        int i;
                if (!d->stagemask)
                        continue;
                if (!shown_header) {
-                       wt_status_print_unmerged_header(s);
+                       wt_longstatus_print_unmerged_header(s);
                        shown_header = 1;
                }
-               wt_status_print_unmerged_data(s, it);
+               wt_longstatus_print_unmerged_data(s, it);
        }
        if (shown_header)
-               wt_status_print_trailer(s);
+               wt_longstatus_print_trailer(s);
  
  }
  
- static void wt_status_print_updated(struct wt_status *s)
+ static void wt_longstatus_print_updated(struct wt_status *s)
  {
        int shown_header = 0;
        int i;
                    d->index_status == DIFF_STATUS_UNMERGED)
                        continue;
                if (!shown_header) {
-                       wt_status_print_cached_header(s);
+                       wt_longstatus_print_cached_header(s);
                        s->commitable = 1;
                        shown_header = 1;
                }
-               wt_status_print_change_data(s, WT_STATUS_UPDATED, it);
+               wt_longstatus_print_change_data(s, WT_STATUS_UPDATED, it);
        }
        if (shown_header)
-               wt_status_print_trailer(s);
+               wt_longstatus_print_trailer(s);
  }
  
  /*
@@@ -703,7 -760,7 +760,7 @@@ static int wt_status_check_worktree_cha
        return changes;
  }
  
- static void wt_status_print_changed(struct wt_status *s)
+ static void wt_longstatus_print_changed(struct wt_status *s)
  {
        int i, dirty_submodules;
        int worktree_changes = wt_status_check_worktree_changes(s, &dirty_submodules);
        if (!worktree_changes)
                return;
  
-       wt_status_print_dirty_header(s, worktree_changes < 0, dirty_submodules);
+       wt_longstatus_print_dirty_header(s, worktree_changes < 0, dirty_submodules);
  
        for (i = 0; i < s->change.nr; i++) {
                struct wt_status_change_data *d;
                if (!d->worktree_status ||
                    d->worktree_status == DIFF_STATUS_UNMERGED)
                        continue;
-               wt_status_print_change_data(s, WT_STATUS_CHANGED, it);
+               wt_longstatus_print_change_data(s, WT_STATUS_CHANGED, it);
        }
-       wt_status_print_trailer(s);
+       wt_longstatus_print_trailer(s);
  }
  
- static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitted)
+ static void wt_longstatus_print_submodule_summary(struct wt_status *s, int uncommitted)
  {
        struct child_process sm_summary = CHILD_PROCESS_INIT;
        struct strbuf cmd_stdout = STRBUF_INIT;
        strbuf_release(&summary);
  }
  
- static void wt_status_print_other(struct wt_status *s,
-                                 struct string_list *l,
-                                 const char *what,
-                                 const char *how)
+ static void wt_longstatus_print_other(struct wt_status *s,
+                                     struct string_list *l,
+                                     const char *what,
+                                     const char *how)
  {
        int i;
        struct strbuf buf = STRBUF_INIT;
        if (!l->nr)
                return;
  
-       wt_status_print_other_header(s, what, how);
+       wt_longstatus_print_other_header(s, what, how);
  
        for (i = 0; i < l->nr; i++) {
                struct string_list_item *it;
@@@ -845,7 -902,7 +902,7 @@@ void wt_status_add_cut_line(FILE *fp
        strbuf_release(&buf);
  }
  
- static void wt_status_print_verbose(struct wt_status *s)
+ static void wt_longstatus_print_verbose(struct wt_status *s)
  {
        struct rev_info rev;
        struct setup_revision_opt opt;
        if (s->verbose > 1 && s->commitable) {
                /* print_updated() printed a header, so do we */
                if (s->fp != stdout)
-                       wt_status_print_trailer(s);
+                       wt_longstatus_print_trailer(s);
                status_printf_ln(s, c, _("Changes to be committed:"));
                rev.diffopt.a_prefix = "c/";
                rev.diffopt.b_prefix = "i/";
        }
  }
  
- static void wt_status_print_tracking(struct wt_status *s)
+ static void wt_longstatus_print_tracking(struct wt_status *s)
  {
        struct strbuf sb = STRBUF_INIT;
        const char *cp, *ep, *branch_name;
@@@ -948,12 -1005,9 +1005,12 @@@ static void show_merge_in_progress(stru
  {
        if (has_unmerged(s)) {
                status_printf_ln(s, color, _("You have unmerged paths."));
 -              if (s->hints)
 +              if (s->hints) {
 +                      status_printf_ln(s, color,
 +                                       _("  (fix conflicts and run \"git commit\")"));
                        status_printf_ln(s, color,
 -                              _("  (fix conflicts and run \"git commit\")"));
 +                                       _("  (use \"git merge --abort\" to abort the merge)"));
 +              }
        } else {
                s-> commitable = 1;
                status_printf_ln(s, color,
                        status_printf_ln(s, color,
                                _("  (use \"git commit\" to conclude merge)"));
        }
-       wt_status_print_trailer(s);
+       wt_longstatus_print_trailer(s);
  }
  
  static void show_am_in_progress(struct wt_status *s,
                status_printf_ln(s, color,
                        _("  (use \"git am --abort\" to restore the original branch)"));
        }
-       wt_status_print_trailer(s);
+       wt_longstatus_print_trailer(s);
  }
  
  static char *read_line_from_git_path(const char *filename)
@@@ -1207,7 -1261,7 +1264,7 @@@ static void show_rebase_in_progress(str
                                _("  (use \"git rebase --continue\" once you are satisfied with your changes)"));
                }
        }
-       wt_status_print_trailer(s);
+       wt_longstatus_print_trailer(s);
  }
  
  static void show_cherry_pick_in_progress(struct wt_status *s,
                status_printf_ln(s, color,
                        _("  (use \"git cherry-pick --abort\" to cancel the cherry-pick operation)"));
        }
-       wt_status_print_trailer(s);
+       wt_longstatus_print_trailer(s);
  }
  
  static void show_revert_in_progress(struct wt_status *s,
                status_printf_ln(s, color,
                        _("  (use \"git revert --abort\" to cancel the revert operation)"));
        }
-       wt_status_print_trailer(s);
+       wt_longstatus_print_trailer(s);
  }
  
  static void show_bisect_in_progress(struct wt_status *s,
        if (s->hints)
                status_printf_ln(s, color,
                        _("  (use \"git bisect reset\" to get back to the original branch)"));
-       wt_status_print_trailer(s);
+       wt_longstatus_print_trailer(s);
  }
  
  /*
@@@ -1432,8 -1486,8 +1489,8 @@@ void wt_status_get_state(struct wt_stat
                wt_status_get_detached_from(state);
  }
  
- static void wt_status_print_state(struct wt_status *s,
-                                 struct wt_status_state *state)
+ static void wt_longstatus_print_state(struct wt_status *s,
+                                     struct wt_status_state *state)
  {
        const char *state_color = color(WT_STATUS_HEADER, s);
        if (state->merge_in_progress)
                show_bisect_in_progress(s, state, state_color);
  }
  
void wt_status_print(struct wt_status *s)
static void wt_longstatus_print(struct wt_status *s)
  {
        const char *branch_color = color(WT_STATUS_ONBRANCH, s);
        const char *branch_status_color = 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);
+                       wt_longstatus_print_tracking(s);
        }
  
-       wt_status_print_state(s, &state);
+       wt_longstatus_print_state(s, &state);
        free(state.branch);
        free(state.onto);
        free(state.detached_from);
                status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", "");
        }
  
-       wt_status_print_updated(s);
-       wt_status_print_unmerged(s);
-       wt_status_print_changed(s);
+       wt_longstatus_print_updated(s);
+       wt_longstatus_print_unmerged(s);
+       wt_longstatus_print_changed(s);
        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 */
+               wt_longstatus_print_submodule_summary(s, 0);  /* staged */
+               wt_longstatus_print_submodule_summary(s, 1);  /* unstaged */
        }
        if (s->show_untracked_files) {
-               wt_status_print_other(s, &s->untracked, _("Untracked files"), "add");
+               wt_longstatus_print_other(s, &s->untracked, _("Untracked files"), "add");
                if (s->show_ignored_files)
-                       wt_status_print_other(s, &s->ignored, _("Ignored files"), "add -f");
+                       wt_longstatus_print_other(s, &s->ignored, _("Ignored files"), "add -f");
                if (advice_status_u_option && 2000 < s->untracked_in_ms) {
                        status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
                        status_printf_ln(s, GIT_COLOR_NORMAL,
                        ? _(" (use -u option to show untracked files)") : "");
  
        if (s->verbose)
-               wt_status_print_verbose(s);
+               wt_longstatus_print_verbose(s);
        if (!s->commitable) {
                if (s->amend)
                        status_printf_ln(s, GIT_COLOR_NORMAL, _("No changes"));
@@@ -1717,7 -1771,7 +1774,7 @@@ static void wt_shortstatus_print_tracki
        fputc(s->null_termination ? '\0' : '\n', s->fp);
  }
  
- void wt_shortstatus_print(struct wt_status *s)
static void wt_shortstatus_print(struct wt_status *s)
  {
        int i;
  
        }
  }
  
- void wt_porcelain_print(struct wt_status *s)
static void wt_porcelain_print(struct wt_status *s)
  {
        s->use_color = 0;
        s->relative_paths = 0;
        s->no_gettext = 1;
        wt_shortstatus_print(s);
  }
+ /*
+  * Print branch information for porcelain v2 output.  These lines
+  * are printed when the '--branch' parameter is given.
+  *
+  *    # branch.oid <commit><eol>
+  *    # branch.head <head><eol>
+  *   [# branch.upstream <upstream><eol>
+  *   [# branch.ab +<ahead> -<behind><eol>]]
+  *
+  *      <commit> ::= the current commit hash or the the literal
+  *                   "(initial)" to indicate an initialized repo
+  *                   with no commits.
+  *
+  *        <head> ::= <branch_name> the current branch name or
+  *                   "(detached)" literal when detached head or
+  *                   "(unknown)" when something is wrong.
+  *
+  *    <upstream> ::= the upstream branch name, when set.
+  *
+  *       <ahead> ::= integer ahead value, when upstream set
+  *                   and the commit is present (not gone).
+  *
+  *      <behind> ::= integer behind value, when upstream set
+  *                   and commit is present.
+  *
+  *
+  * The end-of-line is defined by the -z flag.
+  *
+  *                 <eol> ::= NUL when -z,
+  *                           LF when NOT -z.
+  *
+  */
+ static void wt_porcelain_v2_print_tracking(struct wt_status *s)
+ {
+       struct branch *branch;
+       const char *base;
+       const char *branch_name;
+       struct wt_status_state state;
+       int ab_info, nr_ahead, nr_behind;
+       char eol = s->null_termination ? '\0' : '\n';
+       memset(&state, 0, sizeof(state));
+       wt_status_get_state(&state, s->branch && !strcmp(s->branch, "HEAD"));
+       fprintf(s->fp, "# branch.oid %s%c",
+                       (s->is_initial ? "(initial)" : sha1_to_hex(s->sha1_commit)),
+                       eol);
+       if (!s->branch)
+               fprintf(s->fp, "# branch.head %s%c", "(unknown)", eol);
+       else {
+               if (!strcmp(s->branch, "HEAD")) {
+                       fprintf(s->fp, "# branch.head %s%c", "(detached)", eol);
+                       if (state.rebase_in_progress || state.rebase_interactive_in_progress)
+                               branch_name = state.onto;
+                       else if (state.detached_from)
+                               branch_name = state.detached_from;
+                       else
+                               branch_name = "";
+               } else {
+                       branch_name = NULL;
+                       skip_prefix(s->branch, "refs/heads/", &branch_name);
+                       fprintf(s->fp, "# branch.head %s%c", branch_name, eol);
+               }
+               /* Lookup stats on the upstream tracking branch, if set. */
+               branch = branch_get(branch_name);
+               base = NULL;
+               ab_info = (stat_tracking_info(branch, &nr_ahead, &nr_behind, &base) == 0);
+               if (base) {
+                       base = shorten_unambiguous_ref(base, 0);
+                       fprintf(s->fp, "# branch.upstream %s%c", base, eol);
+                       free((char *)base);
+                       if (ab_info)
+                               fprintf(s->fp, "# branch.ab +%d -%d%c", nr_ahead, nr_behind, eol);
+               }
+       }
+       free(state.branch);
+       free(state.onto);
+       free(state.detached_from);
+ }
+ /*
+  * Convert various submodule status values into a
+  * fixed-length string of characters in the buffer provided.
+  */
+ static void wt_porcelain_v2_submodule_state(
+       struct wt_status_change_data *d,
+       char sub[5])
+ {
+       if (S_ISGITLINK(d->mode_head) ||
+               S_ISGITLINK(d->mode_index) ||
+               S_ISGITLINK(d->mode_worktree)) {
+               sub[0] = 'S';
+               sub[1] = d->new_submodule_commits ? 'C' : '.';
+               sub[2] = (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED) ? 'M' : '.';
+               sub[3] = (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) ? 'U' : '.';
+       } else {
+               sub[0] = 'N';
+               sub[1] = '.';
+               sub[2] = '.';
+               sub[3] = '.';
+       }
+       sub[4] = 0;
+ }
+ /*
+  * Fix-up changed entries before we print them.
+  */
+ static void wt_porcelain_v2_fix_up_changed(
+       struct string_list_item *it,
+       struct wt_status *s)
+ {
+       struct wt_status_change_data *d = it->util;
+       if (!d->index_status) {
+               /*
+                * This entry is unchanged in the index (relative to the head).
+                * Therefore, the collect_updated_cb was never called for this
+                * entry (during the head-vs-index scan) and so the head column
+                * fields were never set.
+                *
+                * We must have data for the index column (from the
+                * index-vs-worktree scan (otherwise, this entry should not be
+                * in the list of changes)).
+                *
+                * Copy index column fields to the head column, so that our
+                * output looks complete.
+                */
+               assert(d->mode_head == 0);
+               d->mode_head = d->mode_index;
+               oidcpy(&d->oid_head, &d->oid_index);
+       }
+       if (!d->worktree_status) {
+               /*
+                * This entry is unchanged in the worktree (relative to the index).
+                * Therefore, the collect_changed_cb was never called for this entry
+                * (during the index-vs-worktree scan) and so the worktree column
+                * fields were never set.
+                *
+                * We must have data for the index column (from the head-vs-index
+                * scan).
+                *
+                * Copy the index column fields to the worktree column so that
+                * our output looks complete.
+                *
+                * Note that we only have a mode field in the worktree column
+                * because the scan code tries really hard to not have to compute it.
+                */
+               assert(d->mode_worktree == 0);
+               d->mode_worktree = d->mode_index;
+       }
+ }
+ /*
+  * Print porcelain v2 info for tracked entries with changes.
+  */
+ static void wt_porcelain_v2_print_changed_entry(
+       struct string_list_item *it,
+       struct wt_status *s)
+ {
+       struct wt_status_change_data *d = it->util;
+       struct strbuf buf_index = STRBUF_INIT;
+       struct strbuf buf_head = STRBUF_INIT;
+       const char *path_index = NULL;
+       const char *path_head = NULL;
+       char key[3];
+       char submodule_token[5];
+       char sep_char, eol_char;
+       wt_porcelain_v2_fix_up_changed(it, s);
+       wt_porcelain_v2_submodule_state(d, submodule_token);
+       key[0] = d->index_status ? d->index_status : '.';
+       key[1] = d->worktree_status ? d->worktree_status : '.';
+       key[2] = 0;
+       if (s->null_termination) {
+               /*
+                * In -z mode, we DO NOT C-quote pathnames.  Current path is ALWAYS first.
+                * A single NUL character separates them.
+                */
+               sep_char = '\0';
+               eol_char = '\0';
+               path_index = it->string;
+               path_head = d->head_path;
+       } else {
+               /*
+                * Path(s) are C-quoted if necessary. Current path is ALWAYS first.
+                * The source path is only present when necessary.
+                * A single TAB separates them (because paths can contain spaces
+                * which are not escaped and C-quoting does escape TAB characters).
+                */
+               sep_char = '\t';
+               eol_char = '\n';
+               path_index = quote_path(it->string, s->prefix, &buf_index);
+               if (d->head_path)
+                       path_head = quote_path(d->head_path, s->prefix, &buf_head);
+       }
+       if (path_head)
+               fprintf(s->fp, "2 %s %s %06o %06o %06o %s %s %c%d %s%c%s%c",
+                               key, submodule_token,
+                               d->mode_head, d->mode_index, d->mode_worktree,
+                               oid_to_hex(&d->oid_head), oid_to_hex(&d->oid_index),
+                               key[0], d->score,
+                               path_index, sep_char, path_head, eol_char);
+       else
+               fprintf(s->fp, "1 %s %s %06o %06o %06o %s %s %s%c",
+                               key, submodule_token,
+                               d->mode_head, d->mode_index, d->mode_worktree,
+                               oid_to_hex(&d->oid_head), oid_to_hex(&d->oid_index),
+                               path_index, eol_char);
+       strbuf_release(&buf_index);
+       strbuf_release(&buf_head);
+ }
+ /*
+  * Print porcelain v2 status info for unmerged entries.
+  */
+ static void wt_porcelain_v2_print_unmerged_entry(
+       struct string_list_item *it,
+       struct wt_status *s)
+ {
+       struct wt_status_change_data *d = it->util;
+       const struct cache_entry *ce;
+       struct strbuf buf_index = STRBUF_INIT;
+       const char *path_index = NULL;
+       int pos, stage, sum;
+       struct {
+               int mode;
+               struct object_id oid;
+       } stages[3];
+       char *key;
+       char submodule_token[5];
+       char unmerged_prefix = 'u';
+       char eol_char = s->null_termination ? '\0' : '\n';
+       wt_porcelain_v2_submodule_state(d, submodule_token);
+       switch (d->stagemask) {
+       case 1: key = "DD"; break; /* both deleted */
+       case 2: key = "AU"; break; /* added by us */
+       case 3: key = "UD"; break; /* deleted by them */
+       case 4: key = "UA"; break; /* added by them */
+       case 5: key = "DU"; break; /* deleted by us */
+       case 6: key = "AA"; break; /* both added */
+       case 7: key = "UU"; break; /* both modified */
+       default:
+               die("BUG: unhandled unmerged status %x", d->stagemask);
+       }
+       /*
+        * Disregard d.aux.porcelain_v2 data that we accumulated
+        * for the head and index columns during the scans and
+        * replace with the actual stage data.
+        *
+        * Note that this is a last-one-wins for each the individual
+        * stage [123] columns in the event of multiple cache entries
+        * for same stage.
+        */
+       memset(stages, 0, sizeof(stages));
+       sum = 0;
+       pos = cache_name_pos(it->string, strlen(it->string));
+       assert(pos < 0);
+       pos = -pos-1;
+       while (pos < active_nr) {
+               ce = active_cache[pos++];
+               stage = ce_stage(ce);
+               if (strcmp(ce->name, it->string) || !stage)
+                       break;
+               stages[stage - 1].mode = ce->ce_mode;
+               hashcpy(stages[stage - 1].oid.hash, ce->sha1);
+               sum |= (1 << (stage - 1));
+       }
+       if (sum != d->stagemask)
+               die("BUG: observed stagemask 0x%x != expected stagemask 0x%x", sum, d->stagemask);
+       if (s->null_termination)
+               path_index = it->string;
+       else
+               path_index = quote_path(it->string, s->prefix, &buf_index);
+       fprintf(s->fp, "%c %s %s %06o %06o %06o %06o %s %s %s %s%c",
+                       unmerged_prefix, key, submodule_token,
+                       stages[0].mode, /* stage 1 */
+                       stages[1].mode, /* stage 2 */
+                       stages[2].mode, /* stage 3 */
+                       d->mode_worktree,
+                       oid_to_hex(&stages[0].oid), /* stage 1 */
+                       oid_to_hex(&stages[1].oid), /* stage 2 */
+                       oid_to_hex(&stages[2].oid), /* stage 3 */
+                       path_index,
+                       eol_char);
+       strbuf_release(&buf_index);
+ }
+ /*
+  * Print porcelain V2 status info for untracked and ignored entries.
+  */
+ static void wt_porcelain_v2_print_other(
+       struct string_list_item *it,
+       struct wt_status *s,
+       char prefix)
+ {
+       struct strbuf buf = STRBUF_INIT;
+       const char *path;
+       char eol_char;
+       if (s->null_termination) {
+               path = it->string;
+               eol_char = '\0';
+       } else {
+               path = quote_path(it->string, s->prefix, &buf);
+               eol_char = '\n';
+       }
+       fprintf(s->fp, "%c %s%c", prefix, path, eol_char);
+       strbuf_release(&buf);
+ }
+ /*
+  * Print porcelain V2 status.
+  *
+  * [<v2_branch>]
+  * [<v2_changed_items>]*
+  * [<v2_unmerged_items>]*
+  * [<v2_untracked_items>]*
+  * [<v2_ignored_items>]*
+  *
+  */
+ static void wt_porcelain_v2_print(struct wt_status *s)
+ {
+       struct wt_status_change_data *d;
+       struct string_list_item *it;
+       int i;
+       if (s->show_branch)
+               wt_porcelain_v2_print_tracking(s);
+       for (i = 0; i < s->change.nr; i++) {
+               it = &(s->change.items[i]);
+               d = it->util;
+               if (!d->stagemask)
+                       wt_porcelain_v2_print_changed_entry(it, s);
+       }
+       for (i = 0; i < s->change.nr; i++) {
+               it = &(s->change.items[i]);
+               d = it->util;
+               if (d->stagemask)
+                       wt_porcelain_v2_print_unmerged_entry(it, s);
+       }
+       for (i = 0; i < s->untracked.nr; i++) {
+               it = &(s->untracked.items[i]);
+               wt_porcelain_v2_print_other(it, s, '?');
+       }
+       for (i = 0; i < s->ignored.nr; i++) {
+               it = &(s->ignored.items[i]);
+               wt_porcelain_v2_print_other(it, s, '!');
+       }
+ }
+ void wt_status_print(struct wt_status *s)
+ {
+       switch (s->status_format) {
+       case STATUS_FORMAT_SHORT:
+               wt_shortstatus_print(s);
+               break;
+       case STATUS_FORMAT_PORCELAIN:
+               wt_porcelain_print(s);
+               break;
+       case STATUS_FORMAT_PORCELAIN_V2:
+               wt_porcelain_v2_print(s);
+               break;
+       case STATUS_FORMAT_UNSPECIFIED:
+               die("BUG: finalize_deferred_config() should have been called");
+               break;
+       case STATUS_FORMAT_NONE:
+       case STATUS_FORMAT_LONG:
+               wt_longstatus_print(s);
+               break;
+       }
+ }