From: Junio C Hamano Date: Fri, 9 Sep 2016 04:49:50 +0000 (-0700) Subject: Merge branch 'jh/status-v2-porcelain' X-Git-Tag: v2.11.0-rc0~173 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/00d27937bf0348e7da615f04b65f535a58e096c1?ds=inline;hp=-c Merge branch 'jh/status-v2-porcelain' 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[=] status: cleanup API to wt_status_print status: rename long-format print routines --- 00d27937bf0348e7da615f04b65f535a58e096c1 diff --combined builtin/commit.c index 77e3dc8494,5504afe53a..bb9f79b6ef --- a/builtin/commit.c +++ b/builtin/commit.c @@@ -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, @@@ -1109,6 -1108,7 +1108,7 @@@ 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), @@@ -1380,7 -1380,13 +1380,13 @@@ 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) @@@ -1389,23 -1395,7 +1395,7 @@@ 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 4d17363a92,00e0cebff3..53cf42fac1 --- a/t/t7060-wtstatus.sh +++ b/t/t7060-wtstatus.sh @@@ -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 ..." 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 ..." 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 ..." 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 6225a2d89f,ebe4a1e247..539aac15a3 --- a/wt-status.c +++ b/wt-status.c @@@ -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; @@@ -191,7 -191,7 +191,7 @@@ 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); @@@ -207,9 -207,9 +207,9 @@@ 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); @@@ -226,9 -226,9 +226,9 @@@ 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); @@@ -238,7 -238,7 +238,7 @@@ 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; @@@ -331,9 -331,9 +331,9 @@@ 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); @@@ -378,7 -378,7 +378,7 @@@ 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); } @@@ -388,7 -388,7 +388,7 @@@ 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; @@@ -640,17 -697,17 +697,17 @@@ 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; @@@ -664,14 -721,14 +721,14 @@@ 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); @@@ -711,7 -768,7 +768,7 @@@ 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; @@@ -721,12 -778,12 +778,12 @@@ 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; @@@ -772,10 -829,10 +829,10 @@@ 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; @@@ -785,7 -842,7 +842,7 @@@ 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; @@@ -878,7 -935,7 +935,7 @@@ 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/"; @@@ -896,7 -953,7 +953,7 @@@ } } - 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, @@@ -962,7 -1016,7 +1019,7 @@@ 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, @@@ -983,7 -1037,7 +1040,7 @@@ 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, @@@ -1226,7 -1280,7 +1283,7 @@@ 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, @@@ -1245,7 -1299,7 +1302,7 @@@ 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, @@@ -1262,7 -1316,7 +1319,7 @@@ 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) @@@ -1450,7 -1504,7 +1507,7 @@@ 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); @@@ -1487,10 -1541,10 +1544,10 @@@ 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); @@@ -1501,19 -1555,19 +1558,19 @@@ 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, @@@ -1528,7 -1582,7 +1585,7 @@@ ? _(" (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; @@@ -1749,7 -1803,7 +1806,7 @@@ } } - 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; @@@ -1757,3 -1811,398 +1814,398 @@@ 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 + * # branch.head + * [# branch.upstream + * [# branch.ab + -]] + * + * ::= the current commit hash or the the literal + * "(initial)" to indicate an initialized repo + * with no commits. + * + * ::= the current branch name or + * "(detached)" literal when detached head or + * "(unknown)" when something is wrong. + * + * ::= the upstream branch name, when set. + * + * ::= integer ahead value, when upstream set + * and the commit is present (not gone). + * + * ::= integer behind value, when upstream set + * and commit is present. + * + * + * The end-of-line is defined by the -z flag. + * + * ::= 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. + * + * [] + * []* + * []* + * []* + * []* + * + */ + 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; + } + }