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