9cf70287d9aae08e74d5afb9d54a6ba9aad62121
   1#include "cache.h"
   2#include "pathspec.h"
   3#include "wt-status.h"
   4#include "object.h"
   5#include "dir.h"
   6#include "commit.h"
   7#include "diff.h"
   8#include "revision.h"
   9#include "diffcore.h"
  10#include "quote.h"
  11#include "run-command.h"
  12#include "argv-array.h"
  13#include "remote.h"
  14#include "refs.h"
  15#include "submodule.h"
  16#include "column.h"
  17#include "strbuf.h"
  18#include "utf8.h"
  19
  20static char cut_line[] =
  21"------------------------ >8 ------------------------\n";
  22
  23static char default_wt_status_colors[][COLOR_MAXLEN] = {
  24        GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
  25        GIT_COLOR_GREEN,  /* WT_STATUS_UPDATED */
  26        GIT_COLOR_RED,    /* WT_STATUS_CHANGED */
  27        GIT_COLOR_RED,    /* WT_STATUS_UNTRACKED */
  28        GIT_COLOR_RED,    /* WT_STATUS_NOBRANCH */
  29        GIT_COLOR_RED,    /* WT_STATUS_UNMERGED */
  30        GIT_COLOR_GREEN,  /* WT_STATUS_LOCAL_BRANCH */
  31        GIT_COLOR_RED,    /* WT_STATUS_REMOTE_BRANCH */
  32        GIT_COLOR_NIL,    /* WT_STATUS_ONBRANCH */
  33};
  34
  35static const char *color(int slot, struct wt_status *s)
  36{
  37        const char *c = "";
  38        if (want_color(s->use_color))
  39                c = s->color_palette[slot];
  40        if (slot == WT_STATUS_ONBRANCH && color_is_nil(c))
  41                c = s->color_palette[WT_STATUS_HEADER];
  42        return c;
  43}
  44
  45static void status_vprintf(struct wt_status *s, int at_bol, const char *color,
  46                const char *fmt, va_list ap, const char *trail)
  47{
  48        struct strbuf sb = STRBUF_INIT;
  49        struct strbuf linebuf = STRBUF_INIT;
  50        const char *line, *eol;
  51
  52        strbuf_vaddf(&sb, fmt, ap);
  53        if (!sb.len) {
  54                if (s->display_comment_prefix) {
  55                        strbuf_addch(&sb, comment_line_char);
  56                        if (!trail)
  57                                strbuf_addch(&sb, ' ');
  58                }
  59                color_print_strbuf(s->fp, color, &sb);
  60                if (trail)
  61                        fprintf(s->fp, "%s", trail);
  62                strbuf_release(&sb);
  63                return;
  64        }
  65        for (line = sb.buf; *line; line = eol + 1) {
  66                eol = strchr(line, '\n');
  67
  68                strbuf_reset(&linebuf);
  69                if (at_bol && s->display_comment_prefix) {
  70                        strbuf_addch(&linebuf, comment_line_char);
  71                        if (*line != '\n' && *line != '\t')
  72                                strbuf_addch(&linebuf, ' ');
  73                }
  74                if (eol)
  75                        strbuf_add(&linebuf, line, eol - line);
  76                else
  77                        strbuf_addstr(&linebuf, line);
  78                color_print_strbuf(s->fp, color, &linebuf);
  79                if (eol)
  80                        fprintf(s->fp, "\n");
  81                else
  82                        break;
  83                at_bol = 1;
  84        }
  85        if (trail)
  86                fprintf(s->fp, "%s", trail);
  87        strbuf_release(&linebuf);
  88        strbuf_release(&sb);
  89}
  90
  91void status_printf_ln(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, "\n");
  98        va_end(ap);
  99}
 100
 101void status_printf(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, 1, color, fmt, ap, NULL);
 108        va_end(ap);
 109}
 110
 111static void status_printf_more(struct wt_status *s, const char *color,
 112                               const char *fmt, ...)
 113{
 114        va_list ap;
 115
 116        va_start(ap, fmt);
 117        status_vprintf(s, 0, color, fmt, ap, NULL);
 118        va_end(ap);
 119}
 120
 121void wt_status_prepare(struct wt_status *s)
 122{
 123        unsigned char sha1[20];
 124
 125        memset(s, 0, sizeof(*s));
 126        memcpy(s->color_palette, default_wt_status_colors,
 127               sizeof(default_wt_status_colors));
 128        s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
 129        s->use_color = -1;
 130        s->relative_paths = 1;
 131        s->branch = resolve_refdup("HEAD", sha1, 0, NULL);
 132        s->reference = "HEAD";
 133        s->fp = stdout;
 134        s->index_file = get_index_file();
 135        s->change.strdup_strings = 1;
 136        s->untracked.strdup_strings = 1;
 137        s->ignored.strdup_strings = 1;
 138        s->show_branch = -1;  /* unspecified */
 139        s->display_comment_prefix = 0;
 140}
 141
 142static void wt_status_print_unmerged_header(struct wt_status *s)
 143{
 144        int i;
 145        int del_mod_conflict = 0;
 146        int both_deleted = 0;
 147        int not_deleted = 0;
 148        const char *c = color(WT_STATUS_HEADER, s);
 149
 150        status_printf_ln(s, c, _("Unmerged paths:"));
 151
 152        for (i = 0; i < s->change.nr; i++) {
 153                struct string_list_item *it = &(s->change.items[i]);
 154                struct wt_status_change_data *d = it->util;
 155
 156                switch (d->stagemask) {
 157                case 0:
 158                        break;
 159                case 1:
 160                        both_deleted = 1;
 161                        break;
 162                case 3:
 163                case 5:
 164                        del_mod_conflict = 1;
 165                        break;
 166                default:
 167                        not_deleted = 1;
 168                        break;
 169                }
 170        }
 171
 172        if (!s->hints)
 173                return;
 174        if (s->whence != FROM_COMMIT)
 175                ;
 176        else if (!s->is_initial)
 177                status_printf_ln(s, c, _("  (use \"git reset %s <file>...\" to unstage)"), s->reference);
 178        else
 179                status_printf_ln(s, c, _("  (use \"git rm --cached <file>...\" to unstage)"));
 180
 181        if (!both_deleted) {
 182                if (!del_mod_conflict)
 183                        status_printf_ln(s, c, _("  (use \"git add <file>...\" to mark resolution)"));
 184                else
 185                        status_printf_ln(s, c, _("  (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
 186        } else if (!del_mod_conflict && !not_deleted) {
 187                status_printf_ln(s, c, _("  (use \"git rm <file>...\" to mark resolution)"));
 188        } else {
 189                status_printf_ln(s, c, _("  (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
 190        }
 191        status_printf_ln(s, c, "");
 192}
 193
 194static void wt_status_print_cached_header(struct wt_status *s)
 195{
 196        const char *c = color(WT_STATUS_HEADER, s);
 197
 198        status_printf_ln(s, c, _("Changes to be committed:"));
 199        if (!s->hints)
 200                return;
 201        if (s->whence != FROM_COMMIT)
 202                ; /* NEEDSWORK: use "git reset --unresolve"??? */
 203        else if (!s->is_initial)
 204                status_printf_ln(s, c, _("  (use \"git reset %s <file>...\" to unstage)"), s->reference);
 205        else
 206                status_printf_ln(s, c, _("  (use \"git rm --cached <file>...\" to unstage)"));
 207        status_printf_ln(s, c, "");
 208}
 209
 210static void wt_status_print_dirty_header(struct wt_status *s,
 211                                         int has_deleted,
 212                                         int has_dirty_submodules)
 213{
 214        const char *c = color(WT_STATUS_HEADER, s);
 215
 216        status_printf_ln(s, c, _("Changes not staged for commit:"));
 217        if (!s->hints)
 218                return;
 219        if (!has_deleted)
 220                status_printf_ln(s, c, _("  (use \"git add <file>...\" to update what will be committed)"));
 221        else
 222                status_printf_ln(s, c, _("  (use \"git add/rm <file>...\" to update what will be committed)"));
 223        status_printf_ln(s, c, _("  (use \"git checkout -- <file>...\" to discard changes in working directory)"));
 224        if (has_dirty_submodules)
 225                status_printf_ln(s, c, _("  (commit or discard the untracked or modified content in submodules)"));
 226        status_printf_ln(s, c, "");
 227}
 228
 229static void wt_status_print_other_header(struct wt_status *s,
 230                                         const char *what,
 231                                         const char *how)
 232{
 233        const char *c = color(WT_STATUS_HEADER, s);
 234        status_printf_ln(s, c, "%s:", what);
 235        if (!s->hints)
 236                return;
 237        status_printf_ln(s, c, _("  (use \"git %s <file>...\" to include in what will be committed)"), how);
 238        status_printf_ln(s, c, "");
 239}
 240
 241static void wt_status_print_trailer(struct wt_status *s)
 242{
 243        status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
 244}
 245
 246#define quote_path quote_path_relative
 247
 248static void wt_status_print_unmerged_data(struct wt_status *s,
 249                                          struct string_list_item *it)
 250{
 251        const char *c = color(WT_STATUS_UNMERGED, s);
 252        struct wt_status_change_data *d = it->util;
 253        struct strbuf onebuf = STRBUF_INIT;
 254        const char *one, *how = _("bug");
 255
 256        one = quote_path(it->string, s->prefix, &onebuf);
 257        status_printf(s, color(WT_STATUS_HEADER, s), "\t");
 258        switch (d->stagemask) {
 259        case 1: how = _("both deleted:"); break;
 260        case 2: how = _("added by us:"); break;
 261        case 3: how = _("deleted by them:"); break;
 262        case 4: how = _("added by them:"); break;
 263        case 5: how = _("deleted by us:"); break;
 264        case 6: how = _("both added:"); break;
 265        case 7: how = _("both modified:"); break;
 266        }
 267        status_printf_more(s, c, "%-20s%s\n", how, one);
 268        strbuf_release(&onebuf);
 269}
 270
 271static const char *wt_status_diff_status_string(int status)
 272{
 273        switch (status) {
 274        case DIFF_STATUS_ADDED:
 275                return _("new file:");
 276        case DIFF_STATUS_COPIED:
 277                return _("copied:");
 278        case DIFF_STATUS_DELETED:
 279                return _("deleted:");
 280        case DIFF_STATUS_MODIFIED:
 281                return _("modified:");
 282        case DIFF_STATUS_RENAMED:
 283                return _("renamed:");
 284        case DIFF_STATUS_TYPE_CHANGED:
 285                return _("typechange:");
 286        case DIFF_STATUS_UNKNOWN:
 287                return _("unknown:");
 288        case DIFF_STATUS_UNMERGED:
 289                return _("unmerged:");
 290        default:
 291                return NULL;
 292        }
 293}
 294
 295static void wt_status_print_change_data(struct wt_status *s,
 296                                        int change_type,
 297                                        struct string_list_item *it)
 298{
 299        struct wt_status_change_data *d = it->util;
 300        const char *c = color(change_type, s);
 301        int status;
 302        char *one_name;
 303        char *two_name;
 304        const char *one, *two;
 305        struct strbuf onebuf = STRBUF_INIT, twobuf = STRBUF_INIT;
 306        struct strbuf extra = STRBUF_INIT;
 307        static char *padding;
 308        static int label_width;
 309        const char *what;
 310        int len;
 311
 312        if (!padding) {
 313                /* If DIFF_STATUS_* uses outside this range, we're in trouble */
 314                for (status = 'A'; status <= 'Z'; status++) {
 315                        what = wt_status_diff_status_string(status);
 316                        len = what ? strlen(what) : 0;
 317                        if (len > label_width)
 318                                label_width = len;
 319                }
 320                label_width += strlen(" ");
 321                padding = xmallocz(label_width);
 322                memset(padding, ' ', label_width);
 323        }
 324
 325        one_name = two_name = it->string;
 326        switch (change_type) {
 327        case WT_STATUS_UPDATED:
 328                status = d->index_status;
 329                if (d->head_path)
 330                        one_name = d->head_path;
 331                break;
 332        case WT_STATUS_CHANGED:
 333                if (d->new_submodule_commits || d->dirty_submodule) {
 334                        strbuf_addstr(&extra, " (");
 335                        if (d->new_submodule_commits)
 336                                strbuf_addf(&extra, _("new commits, "));
 337                        if (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
 338                                strbuf_addf(&extra, _("modified content, "));
 339                        if (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
 340                                strbuf_addf(&extra, _("untracked content, "));
 341                        strbuf_setlen(&extra, extra.len - 2);
 342                        strbuf_addch(&extra, ')');
 343                }
 344                status = d->worktree_status;
 345                break;
 346        default:
 347                die("BUG: unhandled change_type %d in wt_status_print_change_data",
 348                    change_type);
 349        }
 350
 351        one = quote_path(one_name, s->prefix, &onebuf);
 352        two = quote_path(two_name, s->prefix, &twobuf);
 353
 354        status_printf(s, color(WT_STATUS_HEADER, s), "\t");
 355        what = wt_status_diff_status_string(status);
 356        if (!what)
 357                die(_("bug: unhandled diff status %c"), status);
 358        len = label_width - utf8_strwidth(what);
 359        assert(len >= 0);
 360        if (status == DIFF_STATUS_COPIED || status == DIFF_STATUS_RENAMED)
 361                status_printf_more(s, c, "%s%.*s%s -> %s",
 362                                   what, len, padding, one, two);
 363        else
 364                status_printf_more(s, c, "%s%.*s%s",
 365                                   what, len, padding, one);
 366        if (extra.len) {
 367                status_printf_more(s, color(WT_STATUS_HEADER, s), "%s", extra.buf);
 368                strbuf_release(&extra);
 369        }
 370        status_printf_more(s, GIT_COLOR_NORMAL, "\n");
 371        strbuf_release(&onebuf);
 372        strbuf_release(&twobuf);
 373}
 374
 375static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
 376                                         struct diff_options *options,
 377                                         void *data)
 378{
 379        struct wt_status *s = data;
 380        int i;
 381
 382        if (!q->nr)
 383                return;
 384        s->workdir_dirty = 1;
 385        for (i = 0; i < q->nr; i++) {
 386                struct diff_filepair *p;
 387                struct string_list_item *it;
 388                struct wt_status_change_data *d;
 389
 390                p = q->queue[i];
 391                it = string_list_insert(&s->change, p->one->path);
 392                d = it->util;
 393                if (!d) {
 394                        d = xcalloc(1, sizeof(*d));
 395                        it->util = d;
 396                }
 397                if (!d->worktree_status)
 398                        d->worktree_status = p->status;
 399                d->dirty_submodule = p->two->dirty_submodule;
 400                if (S_ISGITLINK(p->two->mode))
 401                        d->new_submodule_commits = !!hashcmp(p->one->sha1, p->two->sha1);
 402        }
 403}
 404
 405static int unmerged_mask(const char *path)
 406{
 407        int pos, mask;
 408        const struct cache_entry *ce;
 409
 410        pos = cache_name_pos(path, strlen(path));
 411        if (0 <= pos)
 412                return 0;
 413
 414        mask = 0;
 415        pos = -pos-1;
 416        while (pos < active_nr) {
 417                ce = active_cache[pos++];
 418                if (strcmp(ce->name, path) || !ce_stage(ce))
 419                        break;
 420                mask |= (1 << (ce_stage(ce) - 1));
 421        }
 422        return mask;
 423}
 424
 425static void wt_status_collect_updated_cb(struct diff_queue_struct *q,
 426                                         struct diff_options *options,
 427                                         void *data)
 428{
 429        struct wt_status *s = data;
 430        int i;
 431
 432        for (i = 0; i < q->nr; i++) {
 433                struct diff_filepair *p;
 434                struct string_list_item *it;
 435                struct wt_status_change_data *d;
 436
 437                p = q->queue[i];
 438                it = string_list_insert(&s->change, p->two->path);
 439                d = it->util;
 440                if (!d) {
 441                        d = xcalloc(1, sizeof(*d));
 442                        it->util = d;
 443                }
 444                if (!d->index_status)
 445                        d->index_status = p->status;
 446                switch (p->status) {
 447                case DIFF_STATUS_COPIED:
 448                case DIFF_STATUS_RENAMED:
 449                        d->head_path = xstrdup(p->one->path);
 450                        break;
 451                case DIFF_STATUS_UNMERGED:
 452                        d->stagemask = unmerged_mask(p->two->path);
 453                        break;
 454                }
 455        }
 456}
 457
 458static void wt_status_collect_changes_worktree(struct wt_status *s)
 459{
 460        struct rev_info rev;
 461
 462        init_revisions(&rev, NULL);
 463        setup_revisions(0, NULL, &rev, NULL);
 464        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
 465        DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES);
 466        if (!s->show_untracked_files)
 467                DIFF_OPT_SET(&rev.diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
 468        if (s->ignore_submodule_arg) {
 469                DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
 470                handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
 471        }
 472        rev.diffopt.format_callback = wt_status_collect_changed_cb;
 473        rev.diffopt.format_callback_data = s;
 474        copy_pathspec(&rev.prune_data, &s->pathspec);
 475        run_diff_files(&rev, 0);
 476}
 477
 478static void wt_status_collect_changes_index(struct wt_status *s)
 479{
 480        struct rev_info rev;
 481        struct setup_revision_opt opt;
 482
 483        init_revisions(&rev, NULL);
 484        memset(&opt, 0, sizeof(opt));
 485        opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
 486        setup_revisions(0, NULL, &rev, &opt);
 487
 488        if (s->ignore_submodule_arg) {
 489                DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
 490                handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
 491        }
 492
 493        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
 494        rev.diffopt.format_callback = wt_status_collect_updated_cb;
 495        rev.diffopt.format_callback_data = s;
 496        rev.diffopt.detect_rename = 1;
 497        rev.diffopt.rename_limit = 200;
 498        rev.diffopt.break_opt = 0;
 499        copy_pathspec(&rev.prune_data, &s->pathspec);
 500        run_diff_index(&rev, 1);
 501}
 502
 503static void wt_status_collect_changes_initial(struct wt_status *s)
 504{
 505        int i;
 506
 507        for (i = 0; i < active_nr; i++) {
 508                struct string_list_item *it;
 509                struct wt_status_change_data *d;
 510                const struct cache_entry *ce = active_cache[i];
 511
 512                if (!ce_path_match(ce, &s->pathspec))
 513                        continue;
 514                it = string_list_insert(&s->change, ce->name);
 515                d = it->util;
 516                if (!d) {
 517                        d = xcalloc(1, sizeof(*d));
 518                        it->util = d;
 519                }
 520                if (ce_stage(ce)) {
 521                        d->index_status = DIFF_STATUS_UNMERGED;
 522                        d->stagemask |= (1 << (ce_stage(ce) - 1));
 523                }
 524                else
 525                        d->index_status = DIFF_STATUS_ADDED;
 526        }
 527}
 528
 529static void wt_status_collect_untracked(struct wt_status *s)
 530{
 531        int i;
 532        struct dir_struct dir;
 533        struct timeval t_begin;
 534
 535        if (!s->show_untracked_files)
 536                return;
 537
 538        if (advice_status_u_option)
 539                gettimeofday(&t_begin, NULL);
 540
 541        memset(&dir, 0, sizeof(dir));
 542        if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
 543                dir.flags |=
 544                        DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
 545        if (s->show_ignored_files)
 546                dir.flags |= DIR_SHOW_IGNORED_TOO;
 547        setup_standard_excludes(&dir);
 548
 549        fill_directory(&dir, &s->pathspec);
 550
 551        for (i = 0; i < dir.nr; i++) {
 552                struct dir_entry *ent = dir.entries[i];
 553                if (cache_name_is_other(ent->name, ent->len) &&
 554                    match_pathspec_depth(&s->pathspec, ent->name, ent->len, 0, NULL))
 555                        string_list_insert(&s->untracked, ent->name);
 556                free(ent);
 557        }
 558
 559        for (i = 0; i < dir.ignored_nr; i++) {
 560                struct dir_entry *ent = dir.ignored[i];
 561                if (cache_name_is_other(ent->name, ent->len) &&
 562                    match_pathspec_depth(&s->pathspec, ent->name, ent->len, 0, NULL))
 563                        string_list_insert(&s->ignored, ent->name);
 564                free(ent);
 565        }
 566
 567        free(dir.entries);
 568        free(dir.ignored);
 569        clear_directory(&dir);
 570
 571        if (advice_status_u_option) {
 572                struct timeval t_end;
 573                gettimeofday(&t_end, NULL);
 574                s->untracked_in_ms =
 575                        (uint64_t)t_end.tv_sec * 1000 + t_end.tv_usec / 1000 -
 576                        ((uint64_t)t_begin.tv_sec * 1000 + t_begin.tv_usec / 1000);
 577        }
 578}
 579
 580void wt_status_collect(struct wt_status *s)
 581{
 582        wt_status_collect_changes_worktree(s);
 583
 584        if (s->is_initial)
 585                wt_status_collect_changes_initial(s);
 586        else
 587                wt_status_collect_changes_index(s);
 588        wt_status_collect_untracked(s);
 589}
 590
 591static void wt_status_print_unmerged(struct wt_status *s)
 592{
 593        int shown_header = 0;
 594        int i;
 595
 596        for (i = 0; i < s->change.nr; i++) {
 597                struct wt_status_change_data *d;
 598                struct string_list_item *it;
 599                it = &(s->change.items[i]);
 600                d = it->util;
 601                if (!d->stagemask)
 602                        continue;
 603                if (!shown_header) {
 604                        wt_status_print_unmerged_header(s);
 605                        shown_header = 1;
 606                }
 607                wt_status_print_unmerged_data(s, it);
 608        }
 609        if (shown_header)
 610                wt_status_print_trailer(s);
 611
 612}
 613
 614static void wt_status_print_updated(struct wt_status *s)
 615{
 616        int shown_header = 0;
 617        int i;
 618
 619        for (i = 0; i < s->change.nr; i++) {
 620                struct wt_status_change_data *d;
 621                struct string_list_item *it;
 622                it = &(s->change.items[i]);
 623                d = it->util;
 624                if (!d->index_status ||
 625                    d->index_status == DIFF_STATUS_UNMERGED)
 626                        continue;
 627                if (!shown_header) {
 628                        wt_status_print_cached_header(s);
 629                        s->commitable = 1;
 630                        shown_header = 1;
 631                }
 632                wt_status_print_change_data(s, WT_STATUS_UPDATED, it);
 633        }
 634        if (shown_header)
 635                wt_status_print_trailer(s);
 636}
 637
 638/*
 639 * -1 : has delete
 640 *  0 : no change
 641 *  1 : some change but no delete
 642 */
 643static int wt_status_check_worktree_changes(struct wt_status *s,
 644                                             int *dirty_submodules)
 645{
 646        int i;
 647        int changes = 0;
 648
 649        *dirty_submodules = 0;
 650
 651        for (i = 0; i < s->change.nr; i++) {
 652                struct wt_status_change_data *d;
 653                d = s->change.items[i].util;
 654                if (!d->worktree_status ||
 655                    d->worktree_status == DIFF_STATUS_UNMERGED)
 656                        continue;
 657                if (!changes)
 658                        changes = 1;
 659                if (d->dirty_submodule)
 660                        *dirty_submodules = 1;
 661                if (d->worktree_status == DIFF_STATUS_DELETED)
 662                        changes = -1;
 663        }
 664        return changes;
 665}
 666
 667static void wt_status_print_changed(struct wt_status *s)
 668{
 669        int i, dirty_submodules;
 670        int worktree_changes = wt_status_check_worktree_changes(s, &dirty_submodules);
 671
 672        if (!worktree_changes)
 673                return;
 674
 675        wt_status_print_dirty_header(s, worktree_changes < 0, dirty_submodules);
 676
 677        for (i = 0; i < s->change.nr; i++) {
 678                struct wt_status_change_data *d;
 679                struct string_list_item *it;
 680                it = &(s->change.items[i]);
 681                d = it->util;
 682                if (!d->worktree_status ||
 683                    d->worktree_status == DIFF_STATUS_UNMERGED)
 684                        continue;
 685                wt_status_print_change_data(s, WT_STATUS_CHANGED, it);
 686        }
 687        wt_status_print_trailer(s);
 688}
 689
 690static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitted)
 691{
 692        struct child_process sm_summary;
 693        char summary_limit[64];
 694        char index[PATH_MAX];
 695        const char *env[] = { NULL, NULL };
 696        struct argv_array argv = ARGV_ARRAY_INIT;
 697        struct strbuf cmd_stdout = STRBUF_INIT;
 698        struct strbuf summary = STRBUF_INIT;
 699        char *summary_content;
 700        size_t len;
 701
 702        sprintf(summary_limit, "%d", s->submodule_summary);
 703        snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", s->index_file);
 704
 705        env[0] = index;
 706        argv_array_push(&argv, "submodule");
 707        argv_array_push(&argv, "summary");
 708        argv_array_push(&argv, uncommitted ? "--files" : "--cached");
 709        argv_array_push(&argv, "--for-status");
 710        argv_array_push(&argv, "--summary-limit");
 711        argv_array_push(&argv, summary_limit);
 712        if (!uncommitted)
 713                argv_array_push(&argv, s->amend ? "HEAD^" : "HEAD");
 714
 715        memset(&sm_summary, 0, sizeof(sm_summary));
 716        sm_summary.argv = argv.argv;
 717        sm_summary.env = env;
 718        sm_summary.git_cmd = 1;
 719        sm_summary.no_stdin = 1;
 720        fflush(s->fp);
 721        sm_summary.out = -1;
 722
 723        run_command(&sm_summary);
 724        argv_array_clear(&argv);
 725
 726        len = strbuf_read(&cmd_stdout, sm_summary.out, 1024);
 727
 728        /* prepend header, only if there's an actual output */
 729        if (len) {
 730                if (uncommitted)
 731                        strbuf_addstr(&summary, _("Submodules changed but not updated:"));
 732                else
 733                        strbuf_addstr(&summary, _("Submodule changes to be committed:"));
 734                strbuf_addstr(&summary, "\n\n");
 735        }
 736        strbuf_addbuf(&summary, &cmd_stdout);
 737        strbuf_release(&cmd_stdout);
 738
 739        if (s->display_comment_prefix) {
 740                summary_content = strbuf_detach(&summary, &len);
 741                strbuf_add_commented_lines(&summary, summary_content, len);
 742                free(summary_content);
 743        }
 744
 745        fputs(summary.buf, s->fp);
 746        strbuf_release(&summary);
 747}
 748
 749static void wt_status_print_other(struct wt_status *s,
 750                                  struct string_list *l,
 751                                  const char *what,
 752                                  const char *how)
 753{
 754        int i;
 755        struct strbuf buf = STRBUF_INIT;
 756        static struct string_list output = STRING_LIST_INIT_DUP;
 757        struct column_options copts;
 758
 759        if (!l->nr)
 760                return;
 761
 762        wt_status_print_other_header(s, what, how);
 763
 764        for (i = 0; i < l->nr; i++) {
 765                struct string_list_item *it;
 766                const char *path;
 767                it = &(l->items[i]);
 768                path = quote_path(it->string, s->prefix, &buf);
 769                if (column_active(s->colopts)) {
 770                        string_list_append(&output, path);
 771                        continue;
 772                }
 773                status_printf(s, color(WT_STATUS_HEADER, s), "\t");
 774                status_printf_more(s, color(WT_STATUS_UNTRACKED, s),
 775                                   "%s\n", path);
 776        }
 777
 778        strbuf_release(&buf);
 779        if (!column_active(s->colopts))
 780                goto conclude;
 781
 782        strbuf_addf(&buf, "%s%s\t%s",
 783                    color(WT_STATUS_HEADER, s),
 784                    s->display_comment_prefix ? "#" : "",
 785                    color(WT_STATUS_UNTRACKED, s));
 786        memset(&copts, 0, sizeof(copts));
 787        copts.padding = 1;
 788        copts.indent = buf.buf;
 789        if (want_color(s->use_color))
 790                copts.nl = GIT_COLOR_RESET "\n";
 791        print_columns(&output, s->colopts, &copts);
 792        string_list_clear(&output, 0);
 793        strbuf_release(&buf);
 794conclude:
 795        status_printf_ln(s, GIT_COLOR_NORMAL, "");
 796}
 797
 798void wt_status_truncate_message_at_cut_line(struct strbuf *buf)
 799{
 800        const char *p;
 801        struct strbuf pattern = STRBUF_INIT;
 802
 803        strbuf_addf(&pattern, "%c %s", comment_line_char, cut_line);
 804        p = strstr(buf->buf, pattern.buf);
 805        if (p && (p == buf->buf || p[-1] == '\n'))
 806                strbuf_setlen(buf, p - buf->buf);
 807        strbuf_release(&pattern);
 808}
 809
 810static void wt_status_print_verbose(struct wt_status *s)
 811{
 812        struct rev_info rev;
 813        struct setup_revision_opt opt;
 814
 815        init_revisions(&rev, NULL);
 816        DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
 817
 818        memset(&opt, 0, sizeof(opt));
 819        opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
 820        setup_revisions(0, NULL, &rev, &opt);
 821
 822        rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
 823        rev.diffopt.detect_rename = 1;
 824        rev.diffopt.file = s->fp;
 825        rev.diffopt.close_file = 0;
 826        /*
 827         * If we're not going to stdout, then we definitely don't
 828         * want color, since we are going to the commit message
 829         * file (and even the "auto" setting won't work, since it
 830         * will have checked isatty on stdout). But we then do want
 831         * to insert the scissor line here to reliably remove the
 832         * diff before committing.
 833         */
 834        if (s->fp != stdout) {
 835                const char *explanation = _("Do not touch the line above.\nEverything below will be removed.");
 836                struct strbuf buf = STRBUF_INIT;
 837
 838                rev.diffopt.use_color = 0;
 839                fprintf(s->fp, "%c %s", comment_line_char, cut_line);
 840                strbuf_add_commented_lines(&buf, explanation, strlen(explanation));
 841                fputs(buf.buf, s->fp);
 842                strbuf_release(&buf);
 843        }
 844        run_diff_index(&rev, 1);
 845}
 846
 847static void wt_status_print_tracking(struct wt_status *s)
 848{
 849        struct strbuf sb = STRBUF_INIT;
 850        const char *cp, *ep;
 851        struct branch *branch;
 852        char comment_line_string[3];
 853        int i;
 854
 855        assert(s->branch && !s->is_initial);
 856        if (!starts_with(s->branch, "refs/heads/"))
 857                return;
 858        branch = branch_get(s->branch + 11);
 859        if (!format_tracking_info(branch, &sb))
 860                return;
 861
 862        i = 0;
 863        if (s->display_comment_prefix) {
 864                comment_line_string[i++] = comment_line_char;
 865                comment_line_string[i++] = ' ';
 866        }
 867        comment_line_string[i] = '\0';
 868
 869        for (cp = sb.buf; (ep = strchr(cp, '\n')) != NULL; cp = ep + 1)
 870                color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s),
 871                                 "%s%.*s", comment_line_string,
 872                                 (int)(ep - cp), cp);
 873        if (s->display_comment_prefix)
 874                color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "%c",
 875                                 comment_line_char);
 876        else
 877                fprintf_ln(s->fp, "");
 878}
 879
 880static int has_unmerged(struct wt_status *s)
 881{
 882        int i;
 883
 884        for (i = 0; i < s->change.nr; i++) {
 885                struct wt_status_change_data *d;
 886                d = s->change.items[i].util;
 887                if (d->stagemask)
 888                        return 1;
 889        }
 890        return 0;
 891}
 892
 893static void show_merge_in_progress(struct wt_status *s,
 894                                struct wt_status_state *state,
 895                                const char *color)
 896{
 897        if (has_unmerged(s)) {
 898                status_printf_ln(s, color, _("You have unmerged paths."));
 899                if (s->hints)
 900                        status_printf_ln(s, color,
 901                                _("  (fix conflicts and run \"git commit\")"));
 902        } else {
 903                status_printf_ln(s, color,
 904                        _("All conflicts fixed but you are still merging."));
 905                if (s->hints)
 906                        status_printf_ln(s, color,
 907                                _("  (use \"git commit\" to conclude merge)"));
 908        }
 909        wt_status_print_trailer(s);
 910}
 911
 912static void show_am_in_progress(struct wt_status *s,
 913                                struct wt_status_state *state,
 914                                const char *color)
 915{
 916        status_printf_ln(s, color,
 917                _("You are in the middle of an am session."));
 918        if (state->am_empty_patch)
 919                status_printf_ln(s, color,
 920                        _("The current patch is empty."));
 921        if (s->hints) {
 922                if (!state->am_empty_patch)
 923                        status_printf_ln(s, color,
 924                                _("  (fix conflicts and then run \"git am --continue\")"));
 925                status_printf_ln(s, color,
 926                        _("  (use \"git am --skip\" to skip this patch)"));
 927                status_printf_ln(s, color,
 928                        _("  (use \"git am --abort\" to restore the original branch)"));
 929        }
 930        wt_status_print_trailer(s);
 931}
 932
 933static char *read_line_from_git_path(const char *filename)
 934{
 935        struct strbuf buf = STRBUF_INIT;
 936        FILE *fp = fopen(git_path("%s", filename), "r");
 937        if (!fp) {
 938                strbuf_release(&buf);
 939                return NULL;
 940        }
 941        strbuf_getline(&buf, fp, '\n');
 942        if (!fclose(fp)) {
 943                return strbuf_detach(&buf, NULL);
 944        } else {
 945                strbuf_release(&buf);
 946                return NULL;
 947        }
 948}
 949
 950static int split_commit_in_progress(struct wt_status *s)
 951{
 952        int split_in_progress = 0;
 953        char *head = read_line_from_git_path("HEAD");
 954        char *orig_head = read_line_from_git_path("ORIG_HEAD");
 955        char *rebase_amend = read_line_from_git_path("rebase-merge/amend");
 956        char *rebase_orig_head = read_line_from_git_path("rebase-merge/orig-head");
 957
 958        if (!head || !orig_head || !rebase_amend || !rebase_orig_head ||
 959            !s->branch || strcmp(s->branch, "HEAD"))
 960                return split_in_progress;
 961
 962        if (!strcmp(rebase_amend, rebase_orig_head)) {
 963                if (strcmp(head, rebase_amend))
 964                        split_in_progress = 1;
 965        } else if (strcmp(orig_head, rebase_orig_head)) {
 966                split_in_progress = 1;
 967        }
 968
 969        if (!s->amend && !s->nowarn && !s->workdir_dirty)
 970                split_in_progress = 0;
 971
 972        free(head);
 973        free(orig_head);
 974        free(rebase_amend);
 975        free(rebase_orig_head);
 976        return split_in_progress;
 977}
 978
 979static void show_rebase_in_progress(struct wt_status *s,
 980                                struct wt_status_state *state,
 981                                const char *color)
 982{
 983        struct stat st;
 984
 985        if (has_unmerged(s)) {
 986                if (state->branch)
 987                        status_printf_ln(s, color,
 988                                         _("You are currently rebasing branch '%s' on '%s'."),
 989                                         state->branch,
 990                                         state->onto);
 991                else
 992                        status_printf_ln(s, color,
 993                                         _("You are currently rebasing."));
 994                if (s->hints) {
 995                        status_printf_ln(s, color,
 996                                _("  (fix conflicts and then run \"git rebase --continue\")"));
 997                        status_printf_ln(s, color,
 998                                _("  (use \"git rebase --skip\" to skip this patch)"));
 999                        status_printf_ln(s, color,
1000                                _("  (use \"git rebase --abort\" to check out the original branch)"));
1001                }
1002        } else if (state->rebase_in_progress || !stat(git_path("MERGE_MSG"), &st)) {
1003                if (state->branch)
1004                        status_printf_ln(s, color,
1005                                         _("You are currently rebasing branch '%s' on '%s'."),
1006                                         state->branch,
1007                                         state->onto);
1008                else
1009                        status_printf_ln(s, color,
1010                                         _("You are currently rebasing."));
1011                if (s->hints)
1012                        status_printf_ln(s, color,
1013                                _("  (all conflicts fixed: run \"git rebase --continue\")"));
1014        } else if (split_commit_in_progress(s)) {
1015                if (state->branch)
1016                        status_printf_ln(s, color,
1017                                         _("You are currently splitting a commit while rebasing branch '%s' on '%s'."),
1018                                         state->branch,
1019                                         state->onto);
1020                else
1021                        status_printf_ln(s, color,
1022                                         _("You are currently splitting a commit during a rebase."));
1023                if (s->hints)
1024                        status_printf_ln(s, color,
1025                                _("  (Once your working directory is clean, run \"git rebase --continue\")"));
1026        } else {
1027                if (state->branch)
1028                        status_printf_ln(s, color,
1029                                         _("You are currently editing a commit while rebasing branch '%s' on '%s'."),
1030                                         state->branch,
1031                                         state->onto);
1032                else
1033                        status_printf_ln(s, color,
1034                                         _("You are currently editing a commit during a rebase."));
1035                if (s->hints && !s->amend) {
1036                        status_printf_ln(s, color,
1037                                _("  (use \"git commit --amend\" to amend the current commit)"));
1038                        status_printf_ln(s, color,
1039                                _("  (use \"git rebase --continue\" once you are satisfied with your changes)"));
1040                }
1041        }
1042        wt_status_print_trailer(s);
1043}
1044
1045static void show_cherry_pick_in_progress(struct wt_status *s,
1046                                        struct wt_status_state *state,
1047                                        const char *color)
1048{
1049        status_printf_ln(s, color, _("You are currently cherry-picking commit %s."),
1050                        find_unique_abbrev(state->cherry_pick_head_sha1, DEFAULT_ABBREV));
1051        if (s->hints) {
1052                if (has_unmerged(s))
1053                        status_printf_ln(s, color,
1054                                _("  (fix conflicts and run \"git cherry-pick --continue\")"));
1055                else
1056                        status_printf_ln(s, color,
1057                                _("  (all conflicts fixed: run \"git cherry-pick --continue\")"));
1058                status_printf_ln(s, color,
1059                        _("  (use \"git cherry-pick --abort\" to cancel the cherry-pick operation)"));
1060        }
1061        wt_status_print_trailer(s);
1062}
1063
1064static void show_revert_in_progress(struct wt_status *s,
1065                                        struct wt_status_state *state,
1066                                        const char *color)
1067{
1068        status_printf_ln(s, color, _("You are currently reverting commit %s."),
1069                         find_unique_abbrev(state->revert_head_sha1, DEFAULT_ABBREV));
1070        if (s->hints) {
1071                if (has_unmerged(s))
1072                        status_printf_ln(s, color,
1073                                _("  (fix conflicts and run \"git revert --continue\")"));
1074                else
1075                        status_printf_ln(s, color,
1076                                _("  (all conflicts fixed: run \"git revert --continue\")"));
1077                status_printf_ln(s, color,
1078                        _("  (use \"git revert --abort\" to cancel the revert operation)"));
1079        }
1080        wt_status_print_trailer(s);
1081}
1082
1083static void show_bisect_in_progress(struct wt_status *s,
1084                                struct wt_status_state *state,
1085                                const char *color)
1086{
1087        if (state->branch)
1088                status_printf_ln(s, color,
1089                                 _("You are currently bisecting, started from branch '%s'."),
1090                                 state->branch);
1091        else
1092                status_printf_ln(s, color,
1093                                 _("You are currently bisecting."));
1094        if (s->hints)
1095                status_printf_ln(s, color,
1096                        _("  (use \"git bisect reset\" to get back to the original branch)"));
1097        wt_status_print_trailer(s);
1098}
1099
1100/*
1101 * Extract branch information from rebase/bisect
1102 */
1103static char *read_and_strip_branch(const char *path)
1104{
1105        struct strbuf sb = STRBUF_INIT;
1106        unsigned char sha1[20];
1107
1108        if (strbuf_read_file(&sb, git_path("%s", path), 0) <= 0)
1109                goto got_nothing;
1110
1111        while (&sb.len && sb.buf[sb.len - 1] == '\n')
1112                strbuf_setlen(&sb, sb.len - 1);
1113        if (!sb.len)
1114                goto got_nothing;
1115        if (starts_with(sb.buf, "refs/heads/"))
1116                strbuf_remove(&sb,0, strlen("refs/heads/"));
1117        else if (starts_with(sb.buf, "refs/"))
1118                ;
1119        else if (!get_sha1_hex(sb.buf, sha1)) {
1120                const char *abbrev;
1121                abbrev = find_unique_abbrev(sha1, DEFAULT_ABBREV);
1122                strbuf_reset(&sb);
1123                strbuf_addstr(&sb, abbrev);
1124        } else if (!strcmp(sb.buf, "detached HEAD")) /* rebase */
1125                goto got_nothing;
1126        else                    /* bisect */
1127                ;
1128        return strbuf_detach(&sb, NULL);
1129
1130got_nothing:
1131        strbuf_release(&sb);
1132        return NULL;
1133}
1134
1135struct grab_1st_switch_cbdata {
1136        struct strbuf buf;
1137        unsigned char nsha1[20];
1138};
1139
1140static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1,
1141                           const char *email, unsigned long timestamp, int tz,
1142                           const char *message, void *cb_data)
1143{
1144        struct grab_1st_switch_cbdata *cb = cb_data;
1145        const char *target = NULL, *end;
1146
1147        if (!starts_with(message, "checkout: moving from "))
1148                return 0;
1149        message += strlen("checkout: moving from ");
1150        target = strstr(message, " to ");
1151        if (!target)
1152                return 0;
1153        target += strlen(" to ");
1154        strbuf_reset(&cb->buf);
1155        hashcpy(cb->nsha1, nsha1);
1156        for (end = target; *end && *end != '\n'; end++)
1157                ;
1158        strbuf_add(&cb->buf, target, end - target);
1159        return 1;
1160}
1161
1162static void wt_status_get_detached_from(struct wt_status_state *state)
1163{
1164        struct grab_1st_switch_cbdata cb;
1165        struct commit *commit;
1166        unsigned char sha1[20];
1167        char *ref = NULL;
1168
1169        strbuf_init(&cb.buf, 0);
1170        if (for_each_reflog_ent_reverse("HEAD", grab_1st_switch, &cb) <= 0) {
1171                strbuf_release(&cb.buf);
1172                return;
1173        }
1174
1175        if (dwim_ref(cb.buf.buf, cb.buf.len, sha1, &ref) == 1 &&
1176            /* sha1 is a commit? match without further lookup */
1177            (!hashcmp(cb.nsha1, sha1) ||
1178             /* perhaps sha1 is a tag, try to dereference to a commit */
1179             ((commit = lookup_commit_reference_gently(sha1, 1)) != NULL &&
1180              !hashcmp(cb.nsha1, commit->object.sha1)))) {
1181                int ofs;
1182                if (starts_with(ref, "refs/tags/"))
1183                        ofs = strlen("refs/tags/");
1184                else if (starts_with(ref, "refs/remotes/"))
1185                        ofs = strlen("refs/remotes/");
1186                else
1187                        ofs = 0;
1188                state->detached_from = xstrdup(ref + ofs);
1189        } else
1190                state->detached_from =
1191                        xstrdup(find_unique_abbrev(cb.nsha1, DEFAULT_ABBREV));
1192        hashcpy(state->detached_sha1, cb.nsha1);
1193
1194        free(ref);
1195        strbuf_release(&cb.buf);
1196}
1197
1198void wt_status_get_state(struct wt_status_state *state,
1199                         int get_detached_from)
1200{
1201        struct stat st;
1202        unsigned char sha1[20];
1203
1204        if (!stat(git_path("MERGE_HEAD"), &st)) {
1205                state->merge_in_progress = 1;
1206        } else if (!stat(git_path("rebase-apply"), &st)) {
1207                if (!stat(git_path("rebase-apply/applying"), &st)) {
1208                        state->am_in_progress = 1;
1209                        if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size)
1210                                state->am_empty_patch = 1;
1211                } else {
1212                        state->rebase_in_progress = 1;
1213                        state->branch = read_and_strip_branch("rebase-apply/head-name");
1214                        state->onto = read_and_strip_branch("rebase-apply/onto");
1215                }
1216        } else if (!stat(git_path("rebase-merge"), &st)) {
1217                if (!stat(git_path("rebase-merge/interactive"), &st))
1218                        state->rebase_interactive_in_progress = 1;
1219                else
1220                        state->rebase_in_progress = 1;
1221                state->branch = read_and_strip_branch("rebase-merge/head-name");
1222                state->onto = read_and_strip_branch("rebase-merge/onto");
1223        } else if (!stat(git_path("CHERRY_PICK_HEAD"), &st) &&
1224                        !get_sha1("CHERRY_PICK_HEAD", sha1)) {
1225                state->cherry_pick_in_progress = 1;
1226                hashcpy(state->cherry_pick_head_sha1, sha1);
1227        }
1228        if (!stat(git_path("BISECT_LOG"), &st)) {
1229                state->bisect_in_progress = 1;
1230                state->branch = read_and_strip_branch("BISECT_START");
1231        }
1232        if (!stat(git_path("REVERT_HEAD"), &st) &&
1233            !get_sha1("REVERT_HEAD", sha1)) {
1234                state->revert_in_progress = 1;
1235                hashcpy(state->revert_head_sha1, sha1);
1236        }
1237
1238        if (get_detached_from)
1239                wt_status_get_detached_from(state);
1240}
1241
1242static void wt_status_print_state(struct wt_status *s,
1243                                  struct wt_status_state *state)
1244{
1245        const char *state_color = color(WT_STATUS_HEADER, s);
1246        if (state->merge_in_progress)
1247                show_merge_in_progress(s, state, state_color);
1248        else if (state->am_in_progress)
1249                show_am_in_progress(s, state, state_color);
1250        else if (state->rebase_in_progress || state->rebase_interactive_in_progress)
1251                show_rebase_in_progress(s, state, state_color);
1252        else if (state->cherry_pick_in_progress)
1253                show_cherry_pick_in_progress(s, state, state_color);
1254        else if (state->revert_in_progress)
1255                show_revert_in_progress(s, state, state_color);
1256        if (state->bisect_in_progress)
1257                show_bisect_in_progress(s, state, state_color);
1258}
1259
1260void wt_status_print(struct wt_status *s)
1261{
1262        const char *branch_color = color(WT_STATUS_ONBRANCH, s);
1263        const char *branch_status_color = color(WT_STATUS_HEADER, s);
1264        struct wt_status_state state;
1265
1266        memset(&state, 0, sizeof(state));
1267        wt_status_get_state(&state,
1268                            s->branch && !strcmp(s->branch, "HEAD"));
1269
1270        if (s->branch) {
1271                const char *on_what = _("On branch ");
1272                const char *branch_name = s->branch;
1273                if (starts_with(branch_name, "refs/heads/"))
1274                        branch_name += 11;
1275                else if (!strcmp(branch_name, "HEAD")) {
1276                        branch_status_color = color(WT_STATUS_NOBRANCH, s);
1277                        if (state.rebase_in_progress || state.rebase_interactive_in_progress) {
1278                                on_what = _("rebase in progress; onto ");
1279                                branch_name = state.onto;
1280                        } else if (state.detached_from) {
1281                                unsigned char sha1[20];
1282                                branch_name = state.detached_from;
1283                                if (!get_sha1("HEAD", sha1) &&
1284                                    !hashcmp(sha1, state.detached_sha1))
1285                                        on_what = _("HEAD detached at ");
1286                                else
1287                                        on_what = _("HEAD detached from ");
1288                        } else {
1289                                branch_name = "";
1290                                on_what = _("Not currently on any branch.");
1291                        }
1292                }
1293                status_printf(s, color(WT_STATUS_HEADER, s), "");
1294                status_printf_more(s, branch_status_color, "%s", on_what);
1295                status_printf_more(s, branch_color, "%s\n", branch_name);
1296                if (!s->is_initial)
1297                        wt_status_print_tracking(s);
1298        }
1299
1300        wt_status_print_state(s, &state);
1301        free(state.branch);
1302        free(state.onto);
1303        free(state.detached_from);
1304
1305        if (s->is_initial) {
1306                status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
1307                status_printf_ln(s, color(WT_STATUS_HEADER, s), _("Initial commit"));
1308                status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
1309        }
1310
1311        wt_status_print_updated(s);
1312        wt_status_print_unmerged(s);
1313        wt_status_print_changed(s);
1314        if (s->submodule_summary &&
1315            (!s->ignore_submodule_arg ||
1316             strcmp(s->ignore_submodule_arg, "all"))) {
1317                wt_status_print_submodule_summary(s, 0);  /* staged */
1318                wt_status_print_submodule_summary(s, 1);  /* unstaged */
1319        }
1320        if (s->show_untracked_files) {
1321                wt_status_print_other(s, &s->untracked, _("Untracked files"), "add");
1322                if (s->show_ignored_files)
1323                        wt_status_print_other(s, &s->ignored, _("Ignored files"), "add -f");
1324                if (advice_status_u_option && 2000 < s->untracked_in_ms) {
1325                        status_printf_ln(s, GIT_COLOR_NORMAL, "");
1326                        status_printf_ln(s, GIT_COLOR_NORMAL,
1327                                         _("It took %.2f seconds to enumerate untracked files. 'status -uno'\n"
1328                                           "may speed it up, but you have to be careful not to forget to add\n"
1329                                           "new files yourself (see 'git help status')."),
1330                                         s->untracked_in_ms / 1000.0);
1331                }
1332        } else if (s->commitable)
1333                status_printf_ln(s, GIT_COLOR_NORMAL, _("Untracked files not listed%s"),
1334                        s->hints
1335                        ? _(" (use -u option to show untracked files)") : "");
1336
1337        if (s->verbose)
1338                wt_status_print_verbose(s);
1339        if (!s->commitable) {
1340                if (s->amend)
1341                        status_printf_ln(s, GIT_COLOR_NORMAL, _("No changes"));
1342                else if (s->nowarn)
1343                        ; /* nothing */
1344                else if (s->workdir_dirty) {
1345                        if (s->hints)
1346                                printf(_("no changes added to commit "
1347                                         "(use \"git add\" and/or \"git commit -a\")\n"));
1348                        else
1349                                printf(_("no changes added to commit\n"));
1350                } else if (s->untracked.nr) {
1351                        if (s->hints)
1352                                printf(_("nothing added to commit but untracked files "
1353                                         "present (use \"git add\" to track)\n"));
1354                        else
1355                                printf(_("nothing added to commit but untracked files present\n"));
1356                } else if (s->is_initial) {
1357                        if (s->hints)
1358                                printf(_("nothing to commit (create/copy files "
1359                                         "and use \"git add\" to track)\n"));
1360                        else
1361                                printf(_("nothing to commit\n"));
1362                } else if (!s->show_untracked_files) {
1363                        if (s->hints)
1364                                printf(_("nothing to commit (use -u to show untracked files)\n"));
1365                        else
1366                                printf(_("nothing to commit\n"));
1367                } else
1368                        printf(_("nothing to commit, working directory clean\n"));
1369        }
1370}
1371
1372static void wt_shortstatus_unmerged(struct string_list_item *it,
1373                           struct wt_status *s)
1374{
1375        struct wt_status_change_data *d = it->util;
1376        const char *how = "??";
1377
1378        switch (d->stagemask) {
1379        case 1: how = "DD"; break; /* both deleted */
1380        case 2: how = "AU"; break; /* added by us */
1381        case 3: how = "UD"; break; /* deleted by them */
1382        case 4: how = "UA"; break; /* added by them */
1383        case 5: how = "DU"; break; /* deleted by us */
1384        case 6: how = "AA"; break; /* both added */
1385        case 7: how = "UU"; break; /* both modified */
1386        }
1387        color_fprintf(s->fp, color(WT_STATUS_UNMERGED, s), "%s", how);
1388        if (s->null_termination) {
1389                fprintf(stdout, " %s%c", it->string, 0);
1390        } else {
1391                struct strbuf onebuf = STRBUF_INIT;
1392                const char *one;
1393                one = quote_path(it->string, s->prefix, &onebuf);
1394                printf(" %s\n", one);
1395                strbuf_release(&onebuf);
1396        }
1397}
1398
1399static void wt_shortstatus_status(struct string_list_item *it,
1400                         struct wt_status *s)
1401{
1402        struct wt_status_change_data *d = it->util;
1403
1404        if (d->index_status)
1405                color_fprintf(s->fp, color(WT_STATUS_UPDATED, s), "%c", d->index_status);
1406        else
1407                putchar(' ');
1408        if (d->worktree_status)
1409                color_fprintf(s->fp, color(WT_STATUS_CHANGED, s), "%c", d->worktree_status);
1410        else
1411                putchar(' ');
1412        putchar(' ');
1413        if (s->null_termination) {
1414                fprintf(stdout, "%s%c", it->string, 0);
1415                if (d->head_path)
1416                        fprintf(stdout, "%s%c", d->head_path, 0);
1417        } else {
1418                struct strbuf onebuf = STRBUF_INIT;
1419                const char *one;
1420                if (d->head_path) {
1421                        one = quote_path(d->head_path, s->prefix, &onebuf);
1422                        if (*one != '"' && strchr(one, ' ') != NULL) {
1423                                putchar('"');
1424                                strbuf_addch(&onebuf, '"');
1425                                one = onebuf.buf;
1426                        }
1427                        printf("%s -> ", one);
1428                        strbuf_release(&onebuf);
1429                }
1430                one = quote_path(it->string, s->prefix, &onebuf);
1431                if (*one != '"' && strchr(one, ' ') != NULL) {
1432                        putchar('"');
1433                        strbuf_addch(&onebuf, '"');
1434                        one = onebuf.buf;
1435                }
1436                printf("%s\n", one);
1437                strbuf_release(&onebuf);
1438        }
1439}
1440
1441static void wt_shortstatus_other(struct string_list_item *it,
1442                                 struct wt_status *s, const char *sign)
1443{
1444        if (s->null_termination) {
1445                fprintf(stdout, "%s %s%c", sign, it->string, 0);
1446        } else {
1447                struct strbuf onebuf = STRBUF_INIT;
1448                const char *one;
1449                one = quote_path(it->string, s->prefix, &onebuf);
1450                color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign);
1451                printf(" %s\n", one);
1452                strbuf_release(&onebuf);
1453        }
1454}
1455
1456static void wt_shortstatus_print_tracking(struct wt_status *s)
1457{
1458        struct branch *branch;
1459        const char *header_color = color(WT_STATUS_HEADER, s);
1460        const char *branch_color_local = color(WT_STATUS_LOCAL_BRANCH, s);
1461        const char *branch_color_remote = color(WT_STATUS_REMOTE_BRANCH, s);
1462
1463        const char *base;
1464        const char *branch_name;
1465        int num_ours, num_theirs;
1466        int upstream_is_gone = 0;
1467
1468        color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "## ");
1469
1470        if (!s->branch)
1471                return;
1472        branch_name = s->branch;
1473
1474        if (starts_with(branch_name, "refs/heads/"))
1475                branch_name += 11;
1476        else if (!strcmp(branch_name, "HEAD")) {
1477                branch_name = _("HEAD (no branch)");
1478                branch_color_local = color(WT_STATUS_NOBRANCH, s);
1479        }
1480
1481        branch = branch_get(s->branch + 11);
1482        if (s->is_initial)
1483                color_fprintf(s->fp, header_color, _("Initial commit on "));
1484
1485        color_fprintf(s->fp, branch_color_local, "%s", branch_name);
1486
1487        switch (stat_tracking_info(branch, &num_ours, &num_theirs)) {
1488        case 0:
1489                /* no base */
1490                fputc(s->null_termination ? '\0' : '\n', s->fp);
1491                return;
1492        case -1:
1493                /* with "gone" base */
1494                upstream_is_gone = 1;
1495                break;
1496        default:
1497                /* with base */
1498                break;
1499        }
1500
1501        base = branch->merge[0]->dst;
1502        base = shorten_unambiguous_ref(base, 0);
1503        color_fprintf(s->fp, header_color, "...");
1504        color_fprintf(s->fp, branch_color_remote, "%s", base);
1505
1506        if (!upstream_is_gone && !num_ours && !num_theirs) {
1507                fputc(s->null_termination ? '\0' : '\n', s->fp);
1508                return;
1509        }
1510
1511        color_fprintf(s->fp, header_color, " [");
1512        if (upstream_is_gone) {
1513                color_fprintf(s->fp, header_color, _("gone"));
1514        } else if (!num_ours) {
1515                color_fprintf(s->fp, header_color, _("behind "));
1516                color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
1517        } else if (!num_theirs) {
1518                color_fprintf(s->fp, header_color, _("ahead "));
1519                color_fprintf(s->fp, branch_color_local, "%d", num_ours);
1520        } else {
1521                color_fprintf(s->fp, header_color, _("ahead "));
1522                color_fprintf(s->fp, branch_color_local, "%d", num_ours);
1523                color_fprintf(s->fp, header_color, _(", behind "));
1524                color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
1525        }
1526
1527        color_fprintf(s->fp, header_color, "]");
1528        fputc(s->null_termination ? '\0' : '\n', s->fp);
1529}
1530
1531void wt_shortstatus_print(struct wt_status *s)
1532{
1533        int i;
1534
1535        if (s->show_branch)
1536                wt_shortstatus_print_tracking(s);
1537
1538        for (i = 0; i < s->change.nr; i++) {
1539                struct wt_status_change_data *d;
1540                struct string_list_item *it;
1541
1542                it = &(s->change.items[i]);
1543                d = it->util;
1544                if (d->stagemask)
1545                        wt_shortstatus_unmerged(it, s);
1546                else
1547                        wt_shortstatus_status(it, s);
1548        }
1549        for (i = 0; i < s->untracked.nr; i++) {
1550                struct string_list_item *it;
1551
1552                it = &(s->untracked.items[i]);
1553                wt_shortstatus_other(it, s, "??");
1554        }
1555        for (i = 0; i < s->ignored.nr; i++) {
1556                struct string_list_item *it;
1557
1558                it = &(s->ignored.items[i]);
1559                wt_shortstatus_other(it, s, "!!");
1560        }
1561}
1562
1563void wt_porcelain_print(struct wt_status *s)
1564{
1565        s->use_color = 0;
1566        s->relative_paths = 0;
1567        s->prefix = NULL;
1568        wt_shortstatus_print(s);
1569}