wt-status.con commit wt-status.*: better advices for git status added (83c750a)
   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        GIT_COLOR_NORMAL, /* WT_STATUS_IN_PROGRESS */
  27};
  28
  29static const char *color(int slot, struct wt_status *s)
  30{
  31        const char *c = "";
  32        if (want_color(s->use_color))
  33                c = s->color_palette[slot];
  34        if (slot == WT_STATUS_ONBRANCH && color_is_nil(c))
  35                c = s->color_palette[WT_STATUS_HEADER];
  36        return c;
  37}
  38
  39static void status_vprintf(struct wt_status *s, int at_bol, const char *color,
  40                const char *fmt, va_list ap, const char *trail)
  41{
  42        struct strbuf sb = STRBUF_INIT;
  43        struct strbuf linebuf = STRBUF_INIT;
  44        const char *line, *eol;
  45
  46        strbuf_vaddf(&sb, fmt, ap);
  47        if (!sb.len) {
  48                strbuf_addch(&sb, '#');
  49                if (!trail)
  50                        strbuf_addch(&sb, ' ');
  51                color_print_strbuf(s->fp, color, &sb);
  52                if (trail)
  53                        fprintf(s->fp, "%s", trail);
  54                strbuf_release(&sb);
  55                return;
  56        }
  57        for (line = sb.buf; *line; line = eol + 1) {
  58                eol = strchr(line, '\n');
  59
  60                strbuf_reset(&linebuf);
  61                if (at_bol) {
  62                        strbuf_addch(&linebuf, '#');
  63                        if (*line != '\n' && *line != '\t')
  64                                strbuf_addch(&linebuf, ' ');
  65                }
  66                if (eol)
  67                        strbuf_add(&linebuf, line, eol - line);
  68                else
  69                        strbuf_addstr(&linebuf, line);
  70                color_print_strbuf(s->fp, color, &linebuf);
  71                if (eol)
  72                        fprintf(s->fp, "\n");
  73                else
  74                        break;
  75                at_bol = 1;
  76        }
  77        if (trail)
  78                fprintf(s->fp, "%s", trail);
  79        strbuf_release(&linebuf);
  80        strbuf_release(&sb);
  81}
  82
  83void status_printf_ln(struct wt_status *s, const char *color,
  84                        const char *fmt, ...)
  85{
  86        va_list ap;
  87
  88        va_start(ap, fmt);
  89        status_vprintf(s, 1, color, fmt, ap, "\n");
  90        va_end(ap);
  91}
  92
  93void status_printf(struct wt_status *s, const char *color,
  94                        const char *fmt, ...)
  95{
  96        va_list ap;
  97
  98        va_start(ap, fmt);
  99        status_vprintf(s, 1, color, fmt, ap, NULL);
 100        va_end(ap);
 101}
 102
 103void status_printf_more(struct wt_status *s, const char *color,
 104                        const char *fmt, ...)
 105{
 106        va_list ap;
 107
 108        va_start(ap, fmt);
 109        status_vprintf(s, 0, color, fmt, ap, NULL);
 110        va_end(ap);
 111}
 112
 113void wt_status_prepare(struct wt_status *s)
 114{
 115        unsigned char sha1[20];
 116
 117        memset(s, 0, sizeof(*s));
 118        memcpy(s->color_palette, default_wt_status_colors,
 119               sizeof(default_wt_status_colors));
 120        s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
 121        s->use_color = -1;
 122        s->relative_paths = 1;
 123        s->branch = resolve_refdup("HEAD", sha1, 0, 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        static struct string_list output = STRING_LIST_INIT_DUP;
 647        struct column_options copts;
 648
 649        if (!l->nr)
 650                return;
 651
 652        wt_status_print_other_header(s, what, how);
 653
 654        for (i = 0; i < l->nr; i++) {
 655                struct string_list_item *it;
 656                const char *path;
 657                it = &(l->items[i]);
 658                path = quote_path(it->string, strlen(it->string),
 659                                  &buf, s->prefix);
 660                if (column_active(s->colopts)) {
 661                        string_list_append(&output, path);
 662                        continue;
 663                }
 664                status_printf(s, color(WT_STATUS_HEADER, s), "\t");
 665                status_printf_more(s, color(WT_STATUS_UNTRACKED, s),
 666                                   "%s\n", path);
 667        }
 668
 669        strbuf_release(&buf);
 670        if (!column_active(s->colopts))
 671                return;
 672
 673        strbuf_addf(&buf, "%s#\t%s",
 674                    color(WT_STATUS_HEADER, s),
 675                    color(WT_STATUS_UNTRACKED, s));
 676        memset(&copts, 0, sizeof(copts));
 677        copts.padding = 1;
 678        copts.indent = buf.buf;
 679        if (want_color(s->use_color))
 680                copts.nl = GIT_COLOR_RESET "\n";
 681        print_columns(&output, s->colopts, &copts);
 682        string_list_clear(&output, 0);
 683        strbuf_release(&buf);
 684}
 685
 686static void wt_status_print_verbose(struct wt_status *s)
 687{
 688        struct rev_info rev;
 689        struct setup_revision_opt opt;
 690
 691        init_revisions(&rev, NULL);
 692        DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
 693
 694        memset(&opt, 0, sizeof(opt));
 695        opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
 696        setup_revisions(0, NULL, &rev, &opt);
 697
 698        rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
 699        rev.diffopt.detect_rename = 1;
 700        rev.diffopt.file = s->fp;
 701        rev.diffopt.close_file = 0;
 702        /*
 703         * If we're not going to stdout, then we definitely don't
 704         * want color, since we are going to the commit message
 705         * file (and even the "auto" setting won't work, since it
 706         * will have checked isatty on stdout).
 707         */
 708        if (s->fp != stdout)
 709                rev.diffopt.use_color = 0;
 710        run_diff_index(&rev, 1);
 711}
 712
 713static void wt_status_print_tracking(struct wt_status *s)
 714{
 715        struct strbuf sb = STRBUF_INIT;
 716        const char *cp, *ep;
 717        struct branch *branch;
 718
 719        assert(s->branch && !s->is_initial);
 720        if (prefixcmp(s->branch, "refs/heads/"))
 721                return;
 722        branch = branch_get(s->branch + 11);
 723        if (!format_tracking_info(branch, &sb))
 724                return;
 725
 726        for (cp = sb.buf; (ep = strchr(cp, '\n')) != NULL; cp = ep + 1)
 727                color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s),
 728                                 "# %.*s", (int)(ep - cp), cp);
 729        color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
 730}
 731
 732static int has_unmerged(struct wt_status *s)
 733{
 734        int i;
 735
 736        for (i = 0; i < s->change.nr; i++) {
 737                struct wt_status_change_data *d;
 738                d = s->change.items[i].util;
 739                if (d->stagemask)
 740                        return 1;
 741        }
 742        return 0;
 743}
 744
 745static void show_merge_in_progress(struct wt_status *s,
 746                                struct wt_status_state *state,
 747                                const char *color)
 748{
 749        if (has_unmerged(s)) {
 750                status_printf_ln(s, color, _("You have unmerged paths."));
 751                if (advice_status_hints)
 752                        status_printf_ln(s, color,
 753                                _("  (fix conflicts and run \"git commit\")"));
 754        } else {
 755                status_printf_ln(s, color,
 756                        _("All conflicts fixed but you are still merging."));
 757                if (advice_status_hints)
 758                        status_printf_ln(s, color,
 759                                _("  (use \"git commit\" to conclude merge)"));
 760        }
 761        wt_status_print_trailer(s);
 762}
 763
 764static void show_am_in_progress(struct wt_status *s,
 765                                struct wt_status_state *state,
 766                                const char *color)
 767{
 768        status_printf_ln(s, color,
 769                _("You are in the middle of an am session."));
 770        if (state->am_empty_patch)
 771                status_printf_ln(s, color,
 772                        _("The current patch is empty."));
 773        if (advice_status_hints) {
 774                if (!state->am_empty_patch)
 775                        status_printf_ln(s, color,
 776                                _("  (fix conflicts and then run \"git am --resolved\")"));
 777                status_printf_ln(s, color,
 778                        _("  (use \"git am --skip\" to skip this patch)"));
 779                status_printf_ln(s, color,
 780                        _("  (use \"git am --abort\" to restore the original branch)"));
 781        }
 782        wt_status_print_trailer(s);
 783}
 784
 785static void show_rebase_in_progress(struct wt_status *s,
 786                                struct wt_status_state *state,
 787                                const char *color)
 788{
 789        struct stat st;
 790
 791        if (has_unmerged(s)) {
 792                status_printf_ln(s, color, _("You are currently rebasing."));
 793                if (advice_status_hints) {
 794                        status_printf_ln(s, color,
 795                                _("  (fix conflicts and then run \"git rebase --continue\")"));
 796                        status_printf_ln(s, color,
 797                                _("  (use \"git rebase --skip\" to skip this patch)"));
 798                        status_printf_ln(s, color,
 799                                _("  (use \"git rebase --abort\" to check out the original branch)"));
 800                }
 801        } else if (state->rebase_in_progress || !stat(git_path("MERGE_MSG"), &st)) {
 802                status_printf_ln(s, color, _("You are currently rebasing."));
 803                if (advice_status_hints)
 804                        status_printf_ln(s, color,
 805                                _("  (all conflicts fixed: run \"git rebase --continue\")"));
 806        } else {
 807                status_printf_ln(s, color, _("You are currently editing a commit during a rebase."));
 808                if (advice_status_hints && !s->amend) {
 809                        status_printf_ln(s, color,
 810                                _("  (use \"git commit --amend\" to amend the current commit)"));
 811                        status_printf_ln(s, color,
 812                                _("  (use \"git rebase --continue\" once you are satisfied with your changes)"));
 813                }
 814        }
 815        wt_status_print_trailer(s);
 816}
 817
 818static void show_cherry_pick_in_progress(struct wt_status *s,
 819                                        struct wt_status_state *state,
 820                                        const char *color)
 821{
 822        status_printf_ln(s, color, _("You are currently cherry-picking."));
 823        if (advice_status_hints) {
 824                if (has_unmerged(s))
 825                        status_printf_ln(s, color,
 826                                _("  (fix conflicts and run \"git commit\")"));
 827                else
 828                        status_printf_ln(s, color,
 829                                _("  (all conflicts fixed: run \"git commit\")"));
 830        }
 831        wt_status_print_trailer(s);
 832}
 833
 834static void show_bisect_in_progress(struct wt_status *s,
 835                                struct wt_status_state *state,
 836                                const char *color)
 837{
 838        status_printf_ln(s, color, _("You are currently bisecting."));
 839        if (advice_status_hints)
 840                status_printf_ln(s, color,
 841                        _("  (use \"git bisect reset\" to get back to the original branch)"));
 842        wt_status_print_trailer(s);
 843}
 844
 845static void wt_status_print_state(struct wt_status *s)
 846{
 847        const char *state_color = color(WT_STATUS_IN_PROGRESS, s);
 848        struct wt_status_state state;
 849        struct stat st;
 850
 851        memset(&state, 0, sizeof(state));
 852
 853        if (!stat(git_path("MERGE_HEAD"), &st)) {
 854                state.merge_in_progress = 1;
 855        } else if (!stat(git_path("rebase-apply"), &st)) {
 856                if (!stat(git_path("rebase-apply/applying"), &st)) {
 857                        state.am_in_progress = 1;
 858                        if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size)
 859                                state.am_empty_patch = 1;
 860                } else {
 861                        state.rebase_in_progress = 1;
 862                }
 863        } else if (!stat(git_path("rebase-merge"), &st)) {
 864                if (!stat(git_path("rebase-merge/interactive"), &st))
 865                        state.rebase_interactive_in_progress = 1;
 866                else
 867                        state.rebase_in_progress = 1;
 868        } else if (!stat(git_path("CHERRY_PICK_HEAD"), &st)) {
 869                state.cherry_pick_in_progress = 1;
 870        }
 871        if (!stat(git_path("BISECT_LOG"), &st))
 872                state.bisect_in_progress = 1;
 873
 874        if (state.merge_in_progress)
 875                show_merge_in_progress(s, &state, state_color);
 876        else if (state.am_in_progress)
 877                show_am_in_progress(s, &state, state_color);
 878        else if (state.rebase_in_progress || state.rebase_interactive_in_progress)
 879                show_rebase_in_progress(s, &state, state_color);
 880        else if (state.cherry_pick_in_progress)
 881                show_cherry_pick_in_progress(s, &state, state_color);
 882        if (state.bisect_in_progress)
 883                show_bisect_in_progress(s, &state, state_color);
 884}
 885
 886void wt_status_print(struct wt_status *s)
 887{
 888        const char *branch_color = color(WT_STATUS_ONBRANCH, s);
 889        const char *branch_status_color = color(WT_STATUS_HEADER, s);
 890
 891        if (s->branch) {
 892                const char *on_what = _("On branch ");
 893                const char *branch_name = s->branch;
 894                if (!prefixcmp(branch_name, "refs/heads/"))
 895                        branch_name += 11;
 896                else if (!strcmp(branch_name, "HEAD")) {
 897                        branch_name = "";
 898                        branch_status_color = color(WT_STATUS_NOBRANCH, s);
 899                        on_what = _("Not currently on any branch.");
 900                }
 901                status_printf(s, color(WT_STATUS_HEADER, s), "");
 902                status_printf_more(s, branch_status_color, "%s", on_what);
 903                status_printf_more(s, branch_color, "%s\n", branch_name);
 904                if (!s->is_initial)
 905                        wt_status_print_tracking(s);
 906        }
 907
 908        wt_status_print_state(s);
 909        if (s->is_initial) {
 910                status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
 911                status_printf_ln(s, color(WT_STATUS_HEADER, s), _("Initial commit"));
 912                status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
 913        }
 914
 915        wt_status_print_updated(s);
 916        wt_status_print_unmerged(s);
 917        wt_status_print_changed(s);
 918        if (s->submodule_summary &&
 919            (!s->ignore_submodule_arg ||
 920             strcmp(s->ignore_submodule_arg, "all"))) {
 921                wt_status_print_submodule_summary(s, 0);  /* staged */
 922                wt_status_print_submodule_summary(s, 1);  /* unstaged */
 923        }
 924        if (s->show_untracked_files) {
 925                wt_status_print_other(s, &s->untracked, _("Untracked"), "add");
 926                if (s->show_ignored_files)
 927                        wt_status_print_other(s, &s->ignored, _("Ignored"), "add -f");
 928        } else if (s->commitable)
 929                status_printf_ln(s, GIT_COLOR_NORMAL, _("Untracked files not listed%s"),
 930                        advice_status_hints
 931                        ? _(" (use -u option to show untracked files)") : "");
 932
 933        if (s->verbose)
 934                wt_status_print_verbose(s);
 935        if (!s->commitable) {
 936                if (s->amend)
 937                        status_printf_ln(s, GIT_COLOR_NORMAL, _("No changes"));
 938                else if (s->nowarn)
 939                        ; /* nothing */
 940                else if (s->workdir_dirty)
 941                        printf(_("no changes added to commit%s\n"),
 942                                advice_status_hints
 943                                ? _(" (use \"git add\" and/or \"git commit -a\")") : "");
 944                else if (s->untracked.nr)
 945                        printf(_("nothing added to commit but untracked files present%s\n"),
 946                                advice_status_hints
 947                                ? _(" (use \"git add\" to track)") : "");
 948                else if (s->is_initial)
 949                        printf(_("nothing to commit%s\n"), advice_status_hints
 950                                ? _(" (create/copy files and use \"git add\" to track)") : "");
 951                else if (!s->show_untracked_files)
 952                        printf(_("nothing to commit%s\n"), advice_status_hints
 953                                ? _(" (use -u to show untracked files)") : "");
 954                else
 955                        printf(_("nothing to commit%s\n"), advice_status_hints
 956                                ? _(" (working directory clean)") : "");
 957        }
 958}
 959
 960static void wt_shortstatus_unmerged(struct string_list_item *it,
 961                           struct wt_status *s)
 962{
 963        struct wt_status_change_data *d = it->util;
 964        const char *how = "??";
 965
 966        switch (d->stagemask) {
 967        case 1: how = "DD"; break; /* both deleted */
 968        case 2: how = "AU"; break; /* added by us */
 969        case 3: how = "UD"; break; /* deleted by them */
 970        case 4: how = "UA"; break; /* added by them */
 971        case 5: how = "DU"; break; /* deleted by us */
 972        case 6: how = "AA"; break; /* both added */
 973        case 7: how = "UU"; break; /* both modified */
 974        }
 975        color_fprintf(s->fp, color(WT_STATUS_UNMERGED, s), "%s", how);
 976        if (s->null_termination) {
 977                fprintf(stdout, " %s%c", it->string, 0);
 978        } else {
 979                struct strbuf onebuf = STRBUF_INIT;
 980                const char *one;
 981                one = quote_path(it->string, -1, &onebuf, s->prefix);
 982                printf(" %s\n", one);
 983                strbuf_release(&onebuf);
 984        }
 985}
 986
 987static void wt_shortstatus_status(struct string_list_item *it,
 988                         struct wt_status *s)
 989{
 990        struct wt_status_change_data *d = it->util;
 991
 992        if (d->index_status)
 993                color_fprintf(s->fp, color(WT_STATUS_UPDATED, s), "%c", d->index_status);
 994        else
 995                putchar(' ');
 996        if (d->worktree_status)
 997                color_fprintf(s->fp, color(WT_STATUS_CHANGED, s), "%c", d->worktree_status);
 998        else
 999                putchar(' ');
1000        putchar(' ');
1001        if (s->null_termination) {
1002                fprintf(stdout, "%s%c", it->string, 0);
1003                if (d->head_path)
1004                        fprintf(stdout, "%s%c", d->head_path, 0);
1005        } else {
1006                struct strbuf onebuf = STRBUF_INIT;
1007                const char *one;
1008                if (d->head_path) {
1009                        one = quote_path(d->head_path, -1, &onebuf, s->prefix);
1010                        if (*one != '"' && strchr(one, ' ') != NULL) {
1011                                putchar('"');
1012                                strbuf_addch(&onebuf, '"');
1013                                one = onebuf.buf;
1014                        }
1015                        printf("%s -> ", one);
1016                        strbuf_release(&onebuf);
1017                }
1018                one = quote_path(it->string, -1, &onebuf, s->prefix);
1019                if (*one != '"' && strchr(one, ' ') != NULL) {
1020                        putchar('"');
1021                        strbuf_addch(&onebuf, '"');
1022                        one = onebuf.buf;
1023                }
1024                printf("%s\n", one);
1025                strbuf_release(&onebuf);
1026        }
1027}
1028
1029static void wt_shortstatus_other(struct string_list_item *it,
1030                                 struct wt_status *s, const char *sign)
1031{
1032        if (s->null_termination) {
1033                fprintf(stdout, "%s %s%c", sign, it->string, 0);
1034        } else {
1035                struct strbuf onebuf = STRBUF_INIT;
1036                const char *one;
1037                one = quote_path(it->string, -1, &onebuf, s->prefix);
1038                color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign);
1039                printf(" %s\n", one);
1040                strbuf_release(&onebuf);
1041        }
1042}
1043
1044static void wt_shortstatus_print_tracking(struct wt_status *s)
1045{
1046        struct branch *branch;
1047        const char *header_color = color(WT_STATUS_HEADER, s);
1048        const char *branch_color_local = color(WT_STATUS_LOCAL_BRANCH, s);
1049        const char *branch_color_remote = color(WT_STATUS_REMOTE_BRANCH, s);
1050
1051        const char *base;
1052        const char *branch_name;
1053        int num_ours, num_theirs;
1054
1055        color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "## ");
1056
1057        if (!s->branch)
1058                return;
1059        branch_name = s->branch;
1060
1061        if (!prefixcmp(branch_name, "refs/heads/"))
1062                branch_name += 11;
1063        else if (!strcmp(branch_name, "HEAD")) {
1064                branch_name = _("HEAD (no branch)");
1065                branch_color_local = color(WT_STATUS_NOBRANCH, s);
1066        }
1067
1068        branch = branch_get(s->branch + 11);
1069        if (s->is_initial)
1070                color_fprintf(s->fp, header_color, _("Initial commit on "));
1071        if (!stat_tracking_info(branch, &num_ours, &num_theirs)) {
1072                color_fprintf(s->fp, branch_color_local, "%s", branch_name);
1073                fputc(s->null_termination ? '\0' : '\n', s->fp);
1074                return;
1075        }
1076
1077        base = branch->merge[0]->dst;
1078        base = shorten_unambiguous_ref(base, 0);
1079        color_fprintf(s->fp, branch_color_local, "%s", branch_name);
1080        color_fprintf(s->fp, header_color, "...");
1081        color_fprintf(s->fp, branch_color_remote, "%s", base);
1082
1083        color_fprintf(s->fp, header_color, " [");
1084        if (!num_ours) {
1085                color_fprintf(s->fp, header_color, _("behind "));
1086                color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
1087        } else if (!num_theirs) {
1088                color_fprintf(s->fp, header_color, _("ahead "));
1089                color_fprintf(s->fp, branch_color_local, "%d", num_ours);
1090        } else {
1091                color_fprintf(s->fp, header_color, _("ahead "));
1092                color_fprintf(s->fp, branch_color_local, "%d", num_ours);
1093                color_fprintf(s->fp, header_color, _(", behind "));
1094                color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
1095        }
1096
1097        color_fprintf(s->fp, header_color, "]");
1098        fputc(s->null_termination ? '\0' : '\n', s->fp);
1099}
1100
1101void wt_shortstatus_print(struct wt_status *s)
1102{
1103        int i;
1104
1105        if (s->show_branch)
1106                wt_shortstatus_print_tracking(s);
1107
1108        for (i = 0; i < s->change.nr; i++) {
1109                struct wt_status_change_data *d;
1110                struct string_list_item *it;
1111
1112                it = &(s->change.items[i]);
1113                d = it->util;
1114                if (d->stagemask)
1115                        wt_shortstatus_unmerged(it, s);
1116                else
1117                        wt_shortstatus_status(it, s);
1118        }
1119        for (i = 0; i < s->untracked.nr; i++) {
1120                struct string_list_item *it;
1121
1122                it = &(s->untracked.items[i]);
1123                wt_shortstatus_other(it, s, "??");
1124        }
1125        for (i = 0; i < s->ignored.nr; i++) {
1126                struct string_list_item *it;
1127
1128                it = &(s->ignored.items[i]);
1129                wt_shortstatus_other(it, s, "!!");
1130        }
1131}
1132
1133void wt_porcelain_print(struct wt_status *s)
1134{
1135        s->use_color = 0;
1136        s->relative_paths = 0;
1137        s->prefix = NULL;
1138        wt_shortstatus_print(s);
1139}