Merge branch 'js/status-pre-rebase-i'
authorJunio C Hamano <gitster@pobox.com>
Thu, 2 Feb 2017 21:36:54 +0000 (13:36 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 2 Feb 2017 21:36:54 +0000 (13:36 -0800)
After starting "git rebase -i", which first opens the user's editor
to edit the series of patches to apply, but before saving the
contents of that file, "git status" failed to show the current
state (i.e. you are in an interactive rebase session, but you have
applied no steps yet) correctly.

* js/status-pre-rebase-i:
status: be prepared for not-yet-started interactive rebase

1  2 
wt-status.c
diff --combined wt-status.c
index a715e71906a9a2fbd62180dd017d140f68ad14a0,1dd2026e1316a62172e6e16723678f8e9400aace..4dff0b3e2108bbd41e638c7beb69e62b20fa8740
@@@ -16,7 -16,6 +16,7 @@@
  #include "strbuf.h"
  #include "utf8.h"
  #include "worktree.h"
 +#include "lockfile.h"
  
  static const char cut_line[] =
  "------------------------ >8 ------------------------\n";
@@@ -140,7 -139,7 +140,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", "");
  }
@@@ -305,8 -304,8 +305,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);
        }
  
@@@ -435,31 -434,6 +435,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:
 +                      d->mode_worktree = p->two->mode;
 +                      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;
 +              }
 +
        }
  }
  
@@@ -505,36 -479,12 +505,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;
                }
        }
@@@ -548,7 -498,6 +548,7 @@@ static void wt_status_collect_changes_w
        setup_revisions(0, NULL, &rev, NULL);
        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
        DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES);
 +      rev.diffopt.ita_invisible_in_index = 1;
        if (!s->show_untracked_files)
                DIFF_OPT_SET(&rev.diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
        if (s->ignore_submodule_arg) {
@@@ -572,7 -521,6 +572,7 @@@ static void wt_status_collect_changes_i
        setup_revisions(0, NULL, &rev, &opt);
  
        DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
 +      rev.diffopt.ita_invisible_in_index = 1;
        if (s->ignore_submodule_arg) {
                handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
        } else {
@@@ -608,8 -556,6 +608,8 @@@ static void wt_status_collect_changes_i
  
                if (!ce_path_match(ce, &s->pathspec, NULL))
                        continue;
 +              if (ce_intent_to_add(ce))
 +                      continue;
                it = string_list_insert(&s->change, ce->name);
                d = it->util;
                if (!d) {
                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->oid.hash);
 +              }
        }
  }
  
@@@ -689,7 -627,7 +689,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);
  }
  
  /*
@@@ -765,7 -703,7 +765,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;
@@@ -907,7 -845,7 +907,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;
  
        init_revisions(&rev, NULL);
        DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
 +      rev.diffopt.ita_invisible_in_index = 1;
  
        memset(&opt, 0, sizeof(opt));
        opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
        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;
@@@ -1025,7 -962,7 +1025,7 @@@ static void show_merge_in_progress(stru
                        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)
@@@ -1135,14 -1072,17 +1135,17 @@@ static void abbrev_sha1_in_line(struct 
        strbuf_list_free(split);
  }
  
- static void read_rebase_todolist(const char *fname, struct string_list *lines)
+ static int read_rebase_todolist(const char *fname, struct string_list *lines)
  {
        struct strbuf line = STRBUF_INIT;
        FILE *f = fopen(git_path("%s", fname), "r");
  
-       if (!f)
+       if (!f) {
+               if (errno == ENOENT)
+                       return -1;
                die_errno("Could not open file %s for reading",
                          git_path("%s", fname));
+       }
        while (!strbuf_getline_lf(&line, f)) {
                if (line.len && line.buf[0] == comment_line_char)
                        continue;
                abbrev_sha1_in_line(&line);
                string_list_append(lines, line.buf);
        }
+       return 0;
  }
  
  static void show_rebase_information(struct wt_status *s,
                struct string_list yet_to_do = STRING_LIST_INIT_DUP;
  
                read_rebase_todolist("rebase-merge/done", &have_done);
-               read_rebase_todolist("rebase-merge/git-rebase-todo", &yet_to_do);
+               if (read_rebase_todolist("rebase-merge/git-rebase-todo",
+                                        &yet_to_do))
+                       status_printf_ln(s, color,
+                               _("git-rebase-todo is missing."));
                if (have_done.nr == 0)
                        status_printf_ln(s, color, _("No commands done."));
                else {
@@@ -1270,7 -1213,7 +1276,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);
  }
  
  /*
@@@ -1492,8 -1435,8 +1498,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"));
@@@ -1777,7 -1720,7 +1783,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->oid.hash);
 +              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;
 +      }
 +}
 +
 +/**
 + * Returns 1 if there are unstaged changes, 0 otherwise.
 + */
 +int has_unstaged_changes(int ignore_submodules)
 +{
 +      struct rev_info rev_info;
 +      int result;
 +
 +      init_revisions(&rev_info, NULL);
 +      if (ignore_submodules)
 +              DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
 +      DIFF_OPT_SET(&rev_info.diffopt, QUICK);
 +      diff_setup_done(&rev_info.diffopt);
 +      result = run_diff_files(&rev_info, 0);
 +      return diff_result_code(&rev_info.diffopt, result);
 +}
 +
 +/**
 + * Returns 1 if there are uncommitted changes, 0 otherwise.
 + */
 +int has_uncommitted_changes(int ignore_submodules)
 +{
 +      struct rev_info rev_info;
 +      int result;
 +
 +      if (is_cache_unborn())
 +              return 0;
 +
 +      init_revisions(&rev_info, NULL);
 +      if (ignore_submodules)
 +              DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
 +      DIFF_OPT_SET(&rev_info.diffopt, QUICK);
 +      add_head_to_pending(&rev_info);
 +      diff_setup_done(&rev_info.diffopt);
 +      result = run_diff_index(&rev_info, 1);
 +      return diff_result_code(&rev_info.diffopt, result);
 +}
 +
 +/**
 + * If the work tree has unstaged or uncommitted changes, dies with the
 + * appropriate message.
 + */
 +int require_clean_work_tree(const char *action, const char *hint, int ignore_submodules, int gently)
 +{
 +      struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file));
 +      int err = 0, fd;
 +
 +      fd = hold_locked_index(lock_file, 0);
 +      refresh_cache(REFRESH_QUIET);
 +      if (0 <= fd)
 +              update_index_if_able(&the_index, lock_file);
 +      rollback_lock_file(lock_file);
 +
 +      if (has_unstaged_changes(ignore_submodules)) {
 +              /* TRANSLATORS: the action is e.g. "pull with rebase" */
 +              error(_("cannot %s: You have unstaged changes."), _(action));
 +              err = 1;
 +      }
 +
 +      if (has_uncommitted_changes(ignore_submodules)) {
 +              if (err)
 +                      error(_("additionally, your index contains uncommitted changes."));
 +              else
 +                      error(_("cannot %s: Your index contains uncommitted changes."),
 +                            _(action));
 +              err = 1;
 +      }
 +
 +      if (err) {
 +              if (hint)
 +                      error("%s", hint);
 +              if (!gently)
 +                      exit(128);
 +      }
 +
 +      return err;
 +}