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