wt-status.con commit Merge branch 'jk/doc-fsck' into maint (81eaa06)
   1#include "cache.h"
   2#include "wt-status.h"
   3#include "object.h"
   4#include "dir.h"
   5#include "commit.h"
   6#include "diff.h"
   7#include "revision.h"
   8#include "diffcore.h"
   9#include "quote.h"
  10#include "run-command.h"
  11#include "remote.h"
  12#include "refs.h"
  13#include "submodule.h"
  14
  15static char default_wt_status_colors[][COLOR_MAXLEN] = {
  16        GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
  17        GIT_COLOR_GREEN,  /* WT_STATUS_UPDATED */
  18        GIT_COLOR_RED,    /* WT_STATUS_CHANGED */
  19        GIT_COLOR_RED,    /* WT_STATUS_UNTRACKED */
  20        GIT_COLOR_RED,    /* WT_STATUS_NOBRANCH */
  21        GIT_COLOR_RED,    /* WT_STATUS_UNMERGED */
  22        GIT_COLOR_GREEN,  /* WT_STATUS_LOCAL_BRANCH */
  23        GIT_COLOR_RED,    /* WT_STATUS_REMOTE_BRANCH */
  24        GIT_COLOR_NIL,    /* WT_STATUS_ONBRANCH */
  25};
  26
  27static const char *color(int slot, struct wt_status *s)
  28{
  29        const char *c = "";
  30        if (want_color(s->use_color))
  31                c = s->color_palette[slot];
  32        if (slot == WT_STATUS_ONBRANCH && color_is_nil(c))
  33                c = s->color_palette[WT_STATUS_HEADER];
  34        return c;
  35}
  36
  37static void status_vprintf(struct wt_status *s, int at_bol, const char *color,
  38                const char *fmt, va_list ap, const char *trail)
  39{
  40        struct strbuf sb = STRBUF_INIT;
  41        struct strbuf linebuf = STRBUF_INIT;
  42        const char *line, *eol;
  43
  44        strbuf_vaddf(&sb, fmt, ap);
  45        if (!sb.len) {
  46                strbuf_addch(&sb, '#');
  47                if (!trail)
  48                        strbuf_addch(&sb, ' ');
  49                color_print_strbuf(s->fp, color, &sb);
  50                if (trail)
  51                        fprintf(s->fp, "%s", trail);
  52                strbuf_release(&sb);
  53                return;
  54        }
  55        for (line = sb.buf; *line; line = eol + 1) {
  56                eol = strchr(line, '\n');
  57
  58                strbuf_reset(&linebuf);
  59                if (at_bol) {
  60                        strbuf_addch(&linebuf, '#');
  61                        if (*line != '\n' && *line != '\t')
  62                                strbuf_addch(&linebuf, ' ');
  63                }
  64                if (eol)
  65                        strbuf_add(&linebuf, line, eol - line);
  66                else
  67                        strbuf_addstr(&linebuf, line);
  68                color_print_strbuf(s->fp, color, &linebuf);
  69                if (eol)
  70                        fprintf(s->fp, "\n");
  71                else
  72                        break;
  73                at_bol = 1;
  74        }
  75        if (trail)
  76                fprintf(s->fp, "%s", trail);
  77        strbuf_release(&linebuf);
  78        strbuf_release(&sb);
  79}
  80
  81void status_printf_ln(struct wt_status *s, const char *color,
  82                        const char *fmt, ...)
  83{
  84        va_list ap;
  85
  86        va_start(ap, fmt);
  87        status_vprintf(s, 1, color, fmt, ap, "\n");
  88        va_end(ap);
  89}
  90
  91void status_printf(struct wt_status *s, const char *color,
  92                        const char *fmt, ...)
  93{
  94        va_list ap;
  95
  96        va_start(ap, fmt);
  97        status_vprintf(s, 1, color, fmt, ap, NULL);
  98        va_end(ap);
  99}
 100
 101void status_printf_more(struct wt_status *s, const char *color,
 102                        const char *fmt, ...)
 103{
 104        va_list ap;
 105
 106        va_start(ap, fmt);
 107        status_vprintf(s, 0, color, fmt, ap, NULL);
 108        va_end(ap);
 109}
 110
 111void wt_status_prepare(struct wt_status *s)
 112{
 113        unsigned char sha1[20];
 114        const char *head;
 115
 116        memset(s, 0, sizeof(*s));
 117        memcpy(s->color_palette, default_wt_status_colors,
 118               sizeof(default_wt_status_colors));
 119        s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
 120        s->use_color = -1;
 121        s->relative_paths = 1;
 122        head = resolve_ref("HEAD", sha1, 0, NULL);
 123        s->branch = head ? xstrdup(head) : NULL;
 124        s->reference = "HEAD";
 125        s->fp = stdout;
 126        s->index_file = get_index_file();
 127        s->change.strdup_strings = 1;
 128        s->untracked.strdup_strings = 1;
 129        s->ignored.strdup_strings = 1;
 130}
 131
 132static void wt_status_print_unmerged_header(struct wt_status *s)
 133{
 134        const char *c = color(WT_STATUS_HEADER, s);
 135
 136        status_printf_ln(s, c, _("Unmerged paths:"));
 137        if (!advice_status_hints)
 138                return;
 139        if (s->whence != FROM_COMMIT)
 140                ;
 141        else if (!s->is_initial)
 142                status_printf_ln(s, c, _("  (use \"git reset %s <file>...\" to unstage)"), s->reference);
 143        else
 144                status_printf_ln(s, c, _("  (use \"git rm --cached <file>...\" to unstage)"));
 145        status_printf_ln(s, c, _("  (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
 146        status_printf_ln(s, c, "");
 147}
 148
 149static void wt_status_print_cached_header(struct wt_status *s)
 150{
 151        const char *c = color(WT_STATUS_HEADER, s);
 152
 153        status_printf_ln(s, c, _("Changes to be committed:"));
 154        if (!advice_status_hints)
 155                return;
 156        if (s->whence != FROM_COMMIT)
 157                ; /* NEEDSWORK: use "git reset --unresolve"??? */
 158        else if (!s->is_initial)
 159                status_printf_ln(s, c, _("  (use \"git reset %s <file>...\" to unstage)"), s->reference);
 160        else
 161                status_printf_ln(s, c, _("  (use \"git rm --cached <file>...\" to unstage)"));
 162        status_printf_ln(s, c, "");
 163}
 164
 165static void wt_status_print_dirty_header(struct wt_status *s,
 166                                         int has_deleted,
 167                                         int has_dirty_submodules)
 168{
 169        const char *c = color(WT_STATUS_HEADER, s);
 170
 171        status_printf_ln(s, c, _("Changes not staged for commit:"));
 172        if (!advice_status_hints)
 173                return;
 174        if (!has_deleted)
 175                status_printf_ln(s, c, _("  (use \"git add <file>...\" to update what will be committed)"));
 176        else
 177                status_printf_ln(s, c, _("  (use \"git add/rm <file>...\" to update what will be committed)"));
 178        status_printf_ln(s, c, _("  (use \"git checkout -- <file>...\" to discard changes in working directory)"));
 179        if (has_dirty_submodules)
 180                status_printf_ln(s, c, _("  (commit or discard the untracked or modified content in submodules)"));
 181        status_printf_ln(s, c, "");
 182}
 183
 184static void wt_status_print_other_header(struct wt_status *s,
 185                                         const char *what,
 186                                         const char *how)
 187{
 188        const char *c = color(WT_STATUS_HEADER, s);
 189        status_printf_ln(s, c, _("%s files:"), what);
 190        if (!advice_status_hints)
 191                return;
 192        status_printf_ln(s, c, _("  (use \"git %s <file>...\" to include in what will be committed)"), how);
 193        status_printf_ln(s, c, "");
 194}
 195
 196static void wt_status_print_trailer(struct wt_status *s)
 197{
 198        status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
 199}
 200
 201#define quote_path quote_path_relative
 202
 203static void wt_status_print_unmerged_data(struct wt_status *s,
 204                                          struct string_list_item *it)
 205{
 206        const char *c = color(WT_STATUS_UNMERGED, s);
 207        struct wt_status_change_data *d = it->util;
 208        struct strbuf onebuf = STRBUF_INIT;
 209        const char *one, *how = _("bug");
 210
 211        one = quote_path(it->string, -1, &onebuf, s->prefix);
 212        status_printf(s, color(WT_STATUS_HEADER, s), "\t");
 213        switch (d->stagemask) {
 214        case 1: how = _("both deleted:"); break;
 215        case 2: how = _("added by us:"); break;
 216        case 3: how = _("deleted by them:"); break;
 217        case 4: how = _("added by them:"); break;
 218        case 5: how = _("deleted by us:"); break;
 219        case 6: how = _("both added:"); break;
 220        case 7: how = _("both modified:"); break;
 221        }
 222        status_printf_more(s, c, "%-20s%s\n", how, one);
 223        strbuf_release(&onebuf);
 224}
 225
 226static void wt_status_print_change_data(struct wt_status *s,
 227                                        int change_type,
 228                                        struct string_list_item *it)
 229{
 230        struct wt_status_change_data *d = it->util;
 231        const char *c = color(change_type, s);
 232        int status = status;
 233        char *one_name;
 234        char *two_name;
 235        const char *one, *two;
 236        struct strbuf onebuf = STRBUF_INIT, twobuf = STRBUF_INIT;
 237        struct strbuf extra = STRBUF_INIT;
 238
 239        one_name = two_name = it->string;
 240        switch (change_type) {
 241        case WT_STATUS_UPDATED:
 242                status = d->index_status;
 243                if (d->head_path)
 244                        one_name = d->head_path;
 245                break;
 246        case WT_STATUS_CHANGED:
 247                if (d->new_submodule_commits || d->dirty_submodule) {
 248                        strbuf_addstr(&extra, " (");
 249                        if (d->new_submodule_commits)
 250                                strbuf_addf(&extra, _("new commits, "));
 251                        if (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
 252                                strbuf_addf(&extra, _("modified content, "));
 253                        if (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
 254                                strbuf_addf(&extra, _("untracked content, "));
 255                        strbuf_setlen(&extra, extra.len - 2);
 256                        strbuf_addch(&extra, ')');
 257                }
 258                status = d->worktree_status;
 259                break;
 260        }
 261
 262        one = quote_path(one_name, -1, &onebuf, s->prefix);
 263        two = quote_path(two_name, -1, &twobuf, s->prefix);
 264
 265        status_printf(s, color(WT_STATUS_HEADER, s), "\t");
 266        switch (status) {
 267        case DIFF_STATUS_ADDED:
 268                status_printf_more(s, c, _("new file:   %s"), one);
 269                break;
 270        case DIFF_STATUS_COPIED:
 271                status_printf_more(s, c, _("copied:     %s -> %s"), one, two);
 272                break;
 273        case DIFF_STATUS_DELETED:
 274                status_printf_more(s, c, _("deleted:    %s"), one);
 275                break;
 276        case DIFF_STATUS_MODIFIED:
 277                status_printf_more(s, c, _("modified:   %s"), one);
 278                break;
 279        case DIFF_STATUS_RENAMED:
 280                status_printf_more(s, c, _("renamed:    %s -> %s"), one, two);
 281                break;
 282        case DIFF_STATUS_TYPE_CHANGED:
 283                status_printf_more(s, c, _("typechange: %s"), one);
 284                break;
 285        case DIFF_STATUS_UNKNOWN:
 286                status_printf_more(s, c, _("unknown:    %s"), one);
 287                break;
 288        case DIFF_STATUS_UNMERGED:
 289                status_printf_more(s, c, _("unmerged:   %s"), one);
 290                break;
 291        default:
 292                die(_("bug: unhandled diff status %c"), status);
 293        }
 294        if (extra.len) {
 295                status_printf_more(s, color(WT_STATUS_HEADER, s), "%s", extra.buf);
 296                strbuf_release(&extra);
 297        }
 298        status_printf_more(s, GIT_COLOR_NORMAL, "\n");
 299        strbuf_release(&onebuf);
 300        strbuf_release(&twobuf);
 301}
 302
 303static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
 304                                         struct diff_options *options,
 305                                         void *data)
 306{
 307        struct wt_status *s = data;
 308        int i;
 309
 310        if (!q->nr)
 311                return;
 312        s->workdir_dirty = 1;
 313        for (i = 0; i < q->nr; i++) {
 314                struct diff_filepair *p;
 315                struct string_list_item *it;
 316                struct wt_status_change_data *d;
 317
 318                p = q->queue[i];
 319                it = string_list_insert(&s->change, p->one->path);
 320                d = it->util;
 321                if (!d) {
 322                        d = xcalloc(1, sizeof(*d));
 323                        it->util = d;
 324                }
 325                if (!d->worktree_status)
 326                        d->worktree_status = p->status;
 327                d->dirty_submodule = p->two->dirty_submodule;
 328                if (S_ISGITLINK(p->two->mode))
 329                        d->new_submodule_commits = !!hashcmp(p->one->sha1, p->two->sha1);
 330        }
 331}
 332
 333static int unmerged_mask(const char *path)
 334{
 335        int pos, mask;
 336        struct cache_entry *ce;
 337
 338        pos = cache_name_pos(path, strlen(path));
 339        if (0 <= pos)
 340                return 0;
 341
 342        mask = 0;
 343        pos = -pos-1;
 344        while (pos < active_nr) {
 345                ce = active_cache[pos++];
 346                if (strcmp(ce->name, path) || !ce_stage(ce))
 347                        break;
 348                mask |= (1 << (ce_stage(ce) - 1));
 349        }
 350        return mask;
 351}
 352
 353static void wt_status_collect_updated_cb(struct diff_queue_struct *q,
 354                                         struct diff_options *options,
 355                                         void *data)
 356{
 357        struct wt_status *s = data;
 358        int i;
 359
 360        for (i = 0; i < q->nr; i++) {
 361                struct diff_filepair *p;
 362                struct string_list_item *it;
 363                struct wt_status_change_data *d;
 364
 365                p = q->queue[i];
 366                it = string_list_insert(&s->change, p->two->path);
 367                d = it->util;
 368                if (!d) {
 369                        d = xcalloc(1, sizeof(*d));
 370                        it->util = d;
 371                }
 372                if (!d->index_status)
 373                        d->index_status = p->status;
 374                switch (p->status) {
 375                case DIFF_STATUS_COPIED:
 376                case DIFF_STATUS_RENAMED:
 377                        d->head_path = xstrdup(p->one->path);
 378                        break;
 379                case DIFF_STATUS_UNMERGED:
 380                        d->stagemask = unmerged_mask(p->two->path);
 381                        break;
 382                }
 383        }
 384}
 385
 386static void wt_status_collect_changes_worktree(struct wt_status *s)
 387{
 388        struct rev_info rev;
 389
 390        init_revisions(&rev, NULL);
 391        setup_revisions(0, NULL, &rev, NULL);
 392        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
 393        DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES);
 394        if (!s->show_untracked_files)
 395                DIFF_OPT_SET(&rev.diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
 396        if (s->ignore_submodule_arg) {
 397                DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
 398                handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
 399        }
 400        rev.diffopt.format_callback = wt_status_collect_changed_cb;
 401        rev.diffopt.format_callback_data = s;
 402        init_pathspec(&rev.prune_data, s->pathspec);
 403        run_diff_files(&rev, 0);
 404}
 405
 406static void wt_status_collect_changes_index(struct wt_status *s)
 407{
 408        struct rev_info rev;
 409        struct setup_revision_opt opt;
 410
 411        init_revisions(&rev, NULL);
 412        memset(&opt, 0, sizeof(opt));
 413        opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
 414        setup_revisions(0, NULL, &rev, &opt);
 415
 416        if (s->ignore_submodule_arg) {
 417                DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
 418                handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
 419        }
 420
 421        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
 422        rev.diffopt.format_callback = wt_status_collect_updated_cb;
 423        rev.diffopt.format_callback_data = s;
 424        rev.diffopt.detect_rename = 1;
 425        rev.diffopt.rename_limit = 200;
 426        rev.diffopt.break_opt = 0;
 427        init_pathspec(&rev.prune_data, s->pathspec);
 428        run_diff_index(&rev, 1);
 429}
 430
 431static void wt_status_collect_changes_initial(struct wt_status *s)
 432{
 433        struct pathspec pathspec;
 434        int i;
 435
 436        init_pathspec(&pathspec, s->pathspec);
 437        for (i = 0; i < active_nr; i++) {
 438                struct string_list_item *it;
 439                struct wt_status_change_data *d;
 440                struct cache_entry *ce = active_cache[i];
 441
 442                if (!ce_path_match(ce, &pathspec))
 443                        continue;
 444                it = string_list_insert(&s->change, ce->name);
 445                d = it->util;
 446                if (!d) {
 447                        d = xcalloc(1, sizeof(*d));
 448                        it->util = d;
 449                }
 450                if (ce_stage(ce)) {
 451                        d->index_status = DIFF_STATUS_UNMERGED;
 452                        d->stagemask |= (1 << (ce_stage(ce) - 1));
 453                }
 454                else
 455                        d->index_status = DIFF_STATUS_ADDED;
 456        }
 457        free_pathspec(&pathspec);
 458}
 459
 460static void wt_status_collect_untracked(struct wt_status *s)
 461{
 462        int i;
 463        struct dir_struct dir;
 464
 465        if (!s->show_untracked_files)
 466                return;
 467        memset(&dir, 0, sizeof(dir));
 468        if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
 469                dir.flags |=
 470                        DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
 471        setup_standard_excludes(&dir);
 472
 473        fill_directory(&dir, s->pathspec);
 474        for (i = 0; i < dir.nr; i++) {
 475                struct dir_entry *ent = dir.entries[i];
 476                if (cache_name_is_other(ent->name, ent->len) &&
 477                    match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
 478                        string_list_insert(&s->untracked, ent->name);
 479                free(ent);
 480        }
 481
 482        if (s->show_ignored_files) {
 483                dir.nr = 0;
 484                dir.flags = DIR_SHOW_IGNORED | DIR_SHOW_OTHER_DIRECTORIES;
 485                fill_directory(&dir, s->pathspec);
 486                for (i = 0; i < dir.nr; i++) {
 487                        struct dir_entry *ent = dir.entries[i];
 488                        if (cache_name_is_other(ent->name, ent->len) &&
 489                            match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
 490                                string_list_insert(&s->ignored, ent->name);
 491                        free(ent);
 492                }
 493        }
 494
 495        free(dir.entries);
 496}
 497
 498void wt_status_collect(struct wt_status *s)
 499{
 500        wt_status_collect_changes_worktree(s);
 501
 502        if (s->is_initial)
 503                wt_status_collect_changes_initial(s);
 504        else
 505                wt_status_collect_changes_index(s);
 506        wt_status_collect_untracked(s);
 507}
 508
 509static void wt_status_print_unmerged(struct wt_status *s)
 510{
 511        int shown_header = 0;
 512        int i;
 513
 514        for (i = 0; i < s->change.nr; i++) {
 515                struct wt_status_change_data *d;
 516                struct string_list_item *it;
 517                it = &(s->change.items[i]);
 518                d = it->util;
 519                if (!d->stagemask)
 520                        continue;
 521                if (!shown_header) {
 522                        wt_status_print_unmerged_header(s);
 523                        shown_header = 1;
 524                }
 525                wt_status_print_unmerged_data(s, it);
 526        }
 527        if (shown_header)
 528                wt_status_print_trailer(s);
 529
 530}
 531
 532static void wt_status_print_updated(struct wt_status *s)
 533{
 534        int shown_header = 0;
 535        int i;
 536
 537        for (i = 0; i < s->change.nr; i++) {
 538                struct wt_status_change_data *d;
 539                struct string_list_item *it;
 540                it = &(s->change.items[i]);
 541                d = it->util;
 542                if (!d->index_status ||
 543                    d->index_status == DIFF_STATUS_UNMERGED)
 544                        continue;
 545                if (!shown_header) {
 546                        wt_status_print_cached_header(s);
 547                        s->commitable = 1;
 548                        shown_header = 1;
 549                }
 550                wt_status_print_change_data(s, WT_STATUS_UPDATED, it);
 551        }
 552        if (shown_header)
 553                wt_status_print_trailer(s);
 554}
 555
 556/*
 557 * -1 : has delete
 558 *  0 : no change
 559 *  1 : some change but no delete
 560 */
 561static int wt_status_check_worktree_changes(struct wt_status *s,
 562                                             int *dirty_submodules)
 563{
 564        int i;
 565        int changes = 0;
 566
 567        *dirty_submodules = 0;
 568
 569        for (i = 0; i < s->change.nr; i++) {
 570                struct wt_status_change_data *d;
 571                d = s->change.items[i].util;
 572                if (!d->worktree_status ||
 573                    d->worktree_status == DIFF_STATUS_UNMERGED)
 574                        continue;
 575                if (!changes)
 576                        changes = 1;
 577                if (d->dirty_submodule)
 578                        *dirty_submodules = 1;
 579                if (d->worktree_status == DIFF_STATUS_DELETED)
 580                        changes = -1;
 581        }
 582        return changes;
 583}
 584
 585static void wt_status_print_changed(struct wt_status *s)
 586{
 587        int i, dirty_submodules;
 588        int worktree_changes = wt_status_check_worktree_changes(s, &dirty_submodules);
 589
 590        if (!worktree_changes)
 591                return;
 592
 593        wt_status_print_dirty_header(s, worktree_changes < 0, dirty_submodules);
 594
 595        for (i = 0; i < s->change.nr; i++) {
 596                struct wt_status_change_data *d;
 597                struct string_list_item *it;
 598                it = &(s->change.items[i]);
 599                d = it->util;
 600                if (!d->worktree_status ||
 601                    d->worktree_status == DIFF_STATUS_UNMERGED)
 602                        continue;
 603                wt_status_print_change_data(s, WT_STATUS_CHANGED, it);
 604        }
 605        wt_status_print_trailer(s);
 606}
 607
 608static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitted)
 609{
 610        struct child_process sm_summary;
 611        char summary_limit[64];
 612        char index[PATH_MAX];
 613        const char *env[] = { NULL, NULL };
 614        const char *argv[8];
 615
 616        env[0] =        index;
 617        argv[0] =       "submodule";
 618        argv[1] =       "summary";
 619        argv[2] =       uncommitted ? "--files" : "--cached";
 620        argv[3] =       "--for-status";
 621        argv[4] =       "--summary-limit";
 622        argv[5] =       summary_limit;
 623        argv[6] =       uncommitted ? NULL : (s->amend ? "HEAD^" : "HEAD");
 624        argv[7] =       NULL;
 625
 626        sprintf(summary_limit, "%d", s->submodule_summary);
 627        snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", s->index_file);
 628
 629        memset(&sm_summary, 0, sizeof(sm_summary));
 630        sm_summary.argv = argv;
 631        sm_summary.env = env;
 632        sm_summary.git_cmd = 1;
 633        sm_summary.no_stdin = 1;
 634        fflush(s->fp);
 635        sm_summary.out = dup(fileno(s->fp));    /* run_command closes it */
 636        run_command(&sm_summary);
 637}
 638
 639static void wt_status_print_other(struct wt_status *s,
 640                                  struct string_list *l,
 641                                  const char *what,
 642                                  const char *how)
 643{
 644        int i;
 645        struct strbuf buf = STRBUF_INIT;
 646
 647        if (!l->nr)
 648                return;
 649
 650        wt_status_print_other_header(s, what, how);
 651
 652        for (i = 0; i < l->nr; i++) {
 653                struct string_list_item *it;
 654                it = &(l->items[i]);
 655                status_printf(s, color(WT_STATUS_HEADER, s), "\t");
 656                status_printf_more(s, color(WT_STATUS_UNTRACKED, s),
 657                        "%s\n", quote_path(it->string, strlen(it->string),
 658                                            &buf, s->prefix));
 659        }
 660        strbuf_release(&buf);
 661}
 662
 663static void wt_status_print_verbose(struct wt_status *s)
 664{
 665        struct rev_info rev;
 666        struct setup_revision_opt opt;
 667
 668        init_revisions(&rev, NULL);
 669        DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
 670
 671        memset(&opt, 0, sizeof(opt));
 672        opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
 673        setup_revisions(0, NULL, &rev, &opt);
 674
 675        rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
 676        rev.diffopt.detect_rename = 1;
 677        rev.diffopt.file = s->fp;
 678        rev.diffopt.close_file = 0;
 679        /*
 680         * If we're not going to stdout, then we definitely don't
 681         * want color, since we are going to the commit message
 682         * file (and even the "auto" setting won't work, since it
 683         * will have checked isatty on stdout).
 684         */
 685        if (s->fp != stdout)
 686                rev.diffopt.use_color = 0;
 687        run_diff_index(&rev, 1);
 688}
 689
 690static void wt_status_print_tracking(struct wt_status *s)
 691{
 692        struct strbuf sb = STRBUF_INIT;
 693        const char *cp, *ep;
 694        struct branch *branch;
 695
 696        assert(s->branch && !s->is_initial);
 697        if (prefixcmp(s->branch, "refs/heads/"))
 698                return;
 699        branch = branch_get(s->branch + 11);
 700        if (!format_tracking_info(branch, &sb))
 701                return;
 702
 703        for (cp = sb.buf; (ep = strchr(cp, '\n')) != NULL; cp = ep + 1)
 704                color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s),
 705                                 "# %.*s", (int)(ep - cp), cp);
 706        color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
 707}
 708
 709void wt_status_print(struct wt_status *s)
 710{
 711        const char *branch_color = color(WT_STATUS_ONBRANCH, s);
 712        const char *branch_status_color = color(WT_STATUS_HEADER, s);
 713
 714        if (s->branch) {
 715                const char *on_what = _("On branch ");
 716                const char *branch_name = s->branch;
 717                if (!prefixcmp(branch_name, "refs/heads/"))
 718                        branch_name += 11;
 719                else if (!strcmp(branch_name, "HEAD")) {
 720                        branch_name = "";
 721                        branch_status_color = color(WT_STATUS_NOBRANCH, s);
 722                        on_what = _("Not currently on any branch.");
 723                }
 724                status_printf(s, color(WT_STATUS_HEADER, s), "");
 725                status_printf_more(s, branch_status_color, "%s", on_what);
 726                status_printf_more(s, branch_color, "%s\n", branch_name);
 727                if (!s->is_initial)
 728                        wt_status_print_tracking(s);
 729        }
 730
 731        if (s->is_initial) {
 732                status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
 733                status_printf_ln(s, color(WT_STATUS_HEADER, s), _("Initial commit"));
 734                status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
 735        }
 736
 737        wt_status_print_updated(s);
 738        wt_status_print_unmerged(s);
 739        wt_status_print_changed(s);
 740        if (s->submodule_summary &&
 741            (!s->ignore_submodule_arg ||
 742             strcmp(s->ignore_submodule_arg, "all"))) {
 743                wt_status_print_submodule_summary(s, 0);  /* staged */
 744                wt_status_print_submodule_summary(s, 1);  /* unstaged */
 745        }
 746        if (s->show_untracked_files) {
 747                wt_status_print_other(s, &s->untracked, _("Untracked"), "add");
 748                if (s->show_ignored_files)
 749                        wt_status_print_other(s, &s->ignored, _("Ignored"), "add -f");
 750        } else if (s->commitable)
 751                status_printf_ln(s, GIT_COLOR_NORMAL, _("Untracked files not listed%s"),
 752                        advice_status_hints
 753                        ? _(" (use -u option to show untracked files)") : "");
 754
 755        if (s->verbose)
 756                wt_status_print_verbose(s);
 757        if (!s->commitable) {
 758                if (s->amend)
 759                        status_printf_ln(s, GIT_COLOR_NORMAL, _("No changes"));
 760                else if (s->nowarn)
 761                        ; /* nothing */
 762                else if (s->workdir_dirty)
 763                        printf(_("no changes added to commit%s\n"),
 764                                advice_status_hints
 765                                ? _(" (use \"git add\" and/or \"git commit -a\")") : "");
 766                else if (s->untracked.nr)
 767                        printf(_("nothing added to commit but untracked files present%s\n"),
 768                                advice_status_hints
 769                                ? _(" (use \"git add\" to track)") : "");
 770                else if (s->is_initial)
 771                        printf(_("nothing to commit%s\n"), advice_status_hints
 772                                ? _(" (create/copy files and use \"git add\" to track)") : "");
 773                else if (!s->show_untracked_files)
 774                        printf(_("nothing to commit%s\n"), advice_status_hints
 775                                ? _(" (use -u to show untracked files)") : "");
 776                else
 777                        printf(_("nothing to commit%s\n"), advice_status_hints
 778                                ? _(" (working directory clean)") : "");
 779        }
 780}
 781
 782static void wt_shortstatus_unmerged(int null_termination, struct string_list_item *it,
 783                           struct wt_status *s)
 784{
 785        struct wt_status_change_data *d = it->util;
 786        const char *how = "??";
 787
 788        switch (d->stagemask) {
 789        case 1: how = "DD"; break; /* both deleted */
 790        case 2: how = "AU"; break; /* added by us */
 791        case 3: how = "UD"; break; /* deleted by them */
 792        case 4: how = "UA"; break; /* added by them */
 793        case 5: how = "DU"; break; /* deleted by us */
 794        case 6: how = "AA"; break; /* both added */
 795        case 7: how = "UU"; break; /* both modified */
 796        }
 797        color_fprintf(s->fp, color(WT_STATUS_UNMERGED, s), "%s", how);
 798        if (null_termination) {
 799                fprintf(stdout, " %s%c", it->string, 0);
 800        } else {
 801                struct strbuf onebuf = STRBUF_INIT;
 802                const char *one;
 803                one = quote_path(it->string, -1, &onebuf, s->prefix);
 804                printf(" %s\n", one);
 805                strbuf_release(&onebuf);
 806        }
 807}
 808
 809static void wt_shortstatus_status(int null_termination, struct string_list_item *it,
 810                         struct wt_status *s)
 811{
 812        struct wt_status_change_data *d = it->util;
 813
 814        if (d->index_status)
 815                color_fprintf(s->fp, color(WT_STATUS_UPDATED, s), "%c", d->index_status);
 816        else
 817                putchar(' ');
 818        if (d->worktree_status)
 819                color_fprintf(s->fp, color(WT_STATUS_CHANGED, s), "%c", d->worktree_status);
 820        else
 821                putchar(' ');
 822        putchar(' ');
 823        if (null_termination) {
 824                fprintf(stdout, "%s%c", it->string, 0);
 825                if (d->head_path)
 826                        fprintf(stdout, "%s%c", d->head_path, 0);
 827        } else {
 828                struct strbuf onebuf = STRBUF_INIT;
 829                const char *one;
 830                if (d->head_path) {
 831                        one = quote_path(d->head_path, -1, &onebuf, s->prefix);
 832                        if (*one != '"' && strchr(one, ' ') != NULL) {
 833                                putchar('"');
 834                                strbuf_addch(&onebuf, '"');
 835                                one = onebuf.buf;
 836                        }
 837                        printf("%s -> ", one);
 838                        strbuf_release(&onebuf);
 839                }
 840                one = quote_path(it->string, -1, &onebuf, s->prefix);
 841                if (*one != '"' && strchr(one, ' ') != NULL) {
 842                        putchar('"');
 843                        strbuf_addch(&onebuf, '"');
 844                        one = onebuf.buf;
 845                }
 846                printf("%s\n", one);
 847                strbuf_release(&onebuf);
 848        }
 849}
 850
 851static void wt_shortstatus_other(int null_termination, struct string_list_item *it,
 852                                 struct wt_status *s, const char *sign)
 853{
 854        if (null_termination) {
 855                fprintf(stdout, "%s %s%c", sign, it->string, 0);
 856        } else {
 857                struct strbuf onebuf = STRBUF_INIT;
 858                const char *one;
 859                one = quote_path(it->string, -1, &onebuf, s->prefix);
 860                color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign);
 861                printf(" %s\n", one);
 862                strbuf_release(&onebuf);
 863        }
 864}
 865
 866static void wt_shortstatus_print_tracking(struct wt_status *s)
 867{
 868        struct branch *branch;
 869        const char *header_color = color(WT_STATUS_HEADER, s);
 870        const char *branch_color_local = color(WT_STATUS_LOCAL_BRANCH, s);
 871        const char *branch_color_remote = color(WT_STATUS_REMOTE_BRANCH, s);
 872
 873        const char *base;
 874        const char *branch_name;
 875        int num_ours, num_theirs;
 876
 877        color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "## ");
 878
 879        if (!s->branch)
 880                return;
 881        branch_name = s->branch;
 882
 883        if (!prefixcmp(branch_name, "refs/heads/"))
 884                branch_name += 11;
 885        else if (!strcmp(branch_name, "HEAD")) {
 886                branch_name = _("HEAD (no branch)");
 887                branch_color_local = color(WT_STATUS_NOBRANCH, s);
 888        }
 889
 890        branch = branch_get(s->branch + 11);
 891        if (s->is_initial)
 892                color_fprintf(s->fp, header_color, _("Initial commit on "));
 893        if (!stat_tracking_info(branch, &num_ours, &num_theirs)) {
 894                color_fprintf_ln(s->fp, branch_color_local,
 895                        "%s", branch_name);
 896                return;
 897        }
 898
 899        base = branch->merge[0]->dst;
 900        base = shorten_unambiguous_ref(base, 0);
 901        color_fprintf(s->fp, branch_color_local, "%s", branch_name);
 902        color_fprintf(s->fp, header_color, "...");
 903        color_fprintf(s->fp, branch_color_remote, "%s", base);
 904
 905        color_fprintf(s->fp, header_color, " [");
 906        if (!num_ours) {
 907                color_fprintf(s->fp, header_color, _("behind "));
 908                color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
 909        } else if (!num_theirs) {
 910                color_fprintf(s->fp, header_color, _("ahead "));
 911                color_fprintf(s->fp, branch_color_local, "%d", num_ours);
 912        } else {
 913                color_fprintf(s->fp, header_color, _("ahead "));
 914                color_fprintf(s->fp, branch_color_local, "%d", num_ours);
 915                color_fprintf(s->fp, header_color, _(", behind "));
 916                color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
 917        }
 918
 919        color_fprintf_ln(s->fp, header_color, "]");
 920}
 921
 922void wt_shortstatus_print(struct wt_status *s, int null_termination, int show_branch)
 923{
 924        int i;
 925
 926        if (show_branch)
 927                wt_shortstatus_print_tracking(s);
 928
 929        for (i = 0; i < s->change.nr; i++) {
 930                struct wt_status_change_data *d;
 931                struct string_list_item *it;
 932
 933                it = &(s->change.items[i]);
 934                d = it->util;
 935                if (d->stagemask)
 936                        wt_shortstatus_unmerged(null_termination, it, s);
 937                else
 938                        wt_shortstatus_status(null_termination, it, s);
 939        }
 940        for (i = 0; i < s->untracked.nr; i++) {
 941                struct string_list_item *it;
 942
 943                it = &(s->untracked.items[i]);
 944                wt_shortstatus_other(null_termination, it, s, "??");
 945        }
 946        for (i = 0; i < s->ignored.nr; i++) {
 947                struct string_list_item *it;
 948
 949                it = &(s->ignored.items[i]);
 950                wt_shortstatus_other(null_termination, it, s, "!!");
 951        }
 952}
 953
 954void wt_porcelain_print(struct wt_status *s, int null_termination)
 955{
 956        s->use_color = 0;
 957        s->relative_paths = 0;
 958        s->prefix = NULL;
 959        wt_shortstatus_print(s, null_termination, 0);
 960}