wt-status.con commit git-svn: avoid filling up the disk with temp files. (29c70e0)
   1#include "cache.h"
   2#include "wt-status.h"
   3#include "color.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
  13int wt_status_relative_paths = 1;
  14int wt_status_use_color = -1;
  15int wt_status_submodule_summary;
  16static char wt_status_colors[][COLOR_MAXLEN] = {
  17        "",         /* WT_STATUS_HEADER: normal */
  18        "\033[32m", /* WT_STATUS_UPDATED: green */
  19        "\033[31m", /* WT_STATUS_CHANGED: red */
  20        "\033[31m", /* WT_STATUS_UNTRACKED: red */
  21        "\033[31m", /* WT_STATUS_NOBRANCH: red */
  22};
  23
  24static const char use_add_msg[] =
  25"use \"git add <file>...\" to update what will be committed";
  26static const char use_add_rm_msg[] =
  27"use \"git add/rm <file>...\" to update what will be committed";
  28static const char use_add_to_include_msg[] =
  29"use \"git add <file>...\" to include in what will be committed";
  30
  31static int parse_status_slot(const char *var, int offset)
  32{
  33        if (!strcasecmp(var+offset, "header"))
  34                return WT_STATUS_HEADER;
  35        if (!strcasecmp(var+offset, "updated")
  36                || !strcasecmp(var+offset, "added"))
  37                return WT_STATUS_UPDATED;
  38        if (!strcasecmp(var+offset, "changed"))
  39                return WT_STATUS_CHANGED;
  40        if (!strcasecmp(var+offset, "untracked"))
  41                return WT_STATUS_UNTRACKED;
  42        if (!strcasecmp(var+offset, "nobranch"))
  43                return WT_STATUS_NOBRANCH;
  44        die("bad config variable '%s'", var);
  45}
  46
  47static const char* color(int slot)
  48{
  49        return wt_status_use_color > 0 ? wt_status_colors[slot] : "";
  50}
  51
  52void wt_status_prepare(struct wt_status *s)
  53{
  54        unsigned char sha1[20];
  55        const char *head;
  56
  57        memset(s, 0, sizeof(*s));
  58        head = resolve_ref("HEAD", sha1, 0, NULL);
  59        s->branch = head ? xstrdup(head) : NULL;
  60        s->reference = "HEAD";
  61        s->fp = stdout;
  62        s->index_file = get_index_file();
  63}
  64
  65static void wt_status_print_cached_header(struct wt_status *s)
  66{
  67        const char *c = color(WT_STATUS_HEADER);
  68        color_fprintf_ln(s->fp, c, "# Changes to be committed:");
  69        if (!s->is_initial) {
  70                color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
  71        } else {
  72                color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
  73        }
  74        color_fprintf_ln(s->fp, c, "#");
  75}
  76
  77static void wt_status_print_header(struct wt_status *s,
  78                                   const char *main, const char *sub)
  79{
  80        const char *c = color(WT_STATUS_HEADER);
  81        color_fprintf_ln(s->fp, c, "# %s:", main);
  82        color_fprintf_ln(s->fp, c, "#   (%s)", sub);
  83        color_fprintf_ln(s->fp, c, "#");
  84}
  85
  86static void wt_status_print_trailer(struct wt_status *s)
  87{
  88        color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
  89}
  90
  91#define quote_path quote_path_relative
  92
  93static void wt_status_print_filepair(struct wt_status *s,
  94                                     int t, struct diff_filepair *p)
  95{
  96        const char *c = color(t);
  97        const char *one, *two;
  98        struct strbuf onebuf, twobuf;
  99
 100        strbuf_init(&onebuf, 0);
 101        strbuf_init(&twobuf, 0);
 102        one = quote_path(p->one->path, -1, &onebuf, s->prefix);
 103        two = quote_path(p->two->path, -1, &twobuf, s->prefix);
 104
 105        color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
 106        switch (p->status) {
 107        case DIFF_STATUS_ADDED:
 108                color_fprintf(s->fp, c, "new file:   %s", one);
 109                break;
 110        case DIFF_STATUS_COPIED:
 111                color_fprintf(s->fp, c, "copied:     %s -> %s", one, two);
 112                break;
 113        case DIFF_STATUS_DELETED:
 114                color_fprintf(s->fp, c, "deleted:    %s", one);
 115                break;
 116        case DIFF_STATUS_MODIFIED:
 117                color_fprintf(s->fp, c, "modified:   %s", one);
 118                break;
 119        case DIFF_STATUS_RENAMED:
 120                color_fprintf(s->fp, c, "renamed:    %s -> %s", one, two);
 121                break;
 122        case DIFF_STATUS_TYPE_CHANGED:
 123                color_fprintf(s->fp, c, "typechange: %s", one);
 124                break;
 125        case DIFF_STATUS_UNKNOWN:
 126                color_fprintf(s->fp, c, "unknown:    %s", one);
 127                break;
 128        case DIFF_STATUS_UNMERGED:
 129                color_fprintf(s->fp, c, "unmerged:   %s", one);
 130                break;
 131        default:
 132                die("bug: unhandled diff status %c", p->status);
 133        }
 134        fprintf(s->fp, "\n");
 135        strbuf_release(&onebuf);
 136        strbuf_release(&twobuf);
 137}
 138
 139static void wt_status_print_updated_cb(struct diff_queue_struct *q,
 140                struct diff_options *options,
 141                void *data)
 142{
 143        struct wt_status *s = data;
 144        int shown_header = 0;
 145        int i;
 146        for (i = 0; i < q->nr; i++) {
 147                if (q->queue[i]->status == 'U')
 148                        continue;
 149                if (!shown_header) {
 150                        wt_status_print_cached_header(s);
 151                        s->commitable = 1;
 152                        shown_header = 1;
 153                }
 154                wt_status_print_filepair(s, WT_STATUS_UPDATED, q->queue[i]);
 155        }
 156        if (shown_header)
 157                wt_status_print_trailer(s);
 158}
 159
 160static void wt_status_print_changed_cb(struct diff_queue_struct *q,
 161                        struct diff_options *options,
 162                        void *data)
 163{
 164        struct wt_status *s = data;
 165        int i;
 166        if (q->nr) {
 167                const char *msg = use_add_msg;
 168                s->workdir_dirty = 1;
 169                for (i = 0; i < q->nr; i++)
 170                        if (q->queue[i]->status == DIFF_STATUS_DELETED) {
 171                                msg = use_add_rm_msg;
 172                                break;
 173                        }
 174                wt_status_print_header(s, "Changed but not updated", msg);
 175        }
 176        for (i = 0; i < q->nr; i++)
 177                wt_status_print_filepair(s, WT_STATUS_CHANGED, q->queue[i]);
 178        if (q->nr)
 179                wt_status_print_trailer(s);
 180}
 181
 182static void wt_status_print_initial(struct wt_status *s)
 183{
 184        int i;
 185        struct strbuf buf;
 186
 187        strbuf_init(&buf, 0);
 188        if (active_nr) {
 189                s->commitable = 1;
 190                wt_status_print_cached_header(s);
 191        }
 192        for (i = 0; i < active_nr; i++) {
 193                color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
 194                color_fprintf_ln(s->fp, color(WT_STATUS_UPDATED), "new file: %s",
 195                                quote_path(active_cache[i]->name, -1,
 196                                           &buf, s->prefix));
 197        }
 198        if (active_nr)
 199                wt_status_print_trailer(s);
 200        strbuf_release(&buf);
 201}
 202
 203static void wt_status_print_updated(struct wt_status *s)
 204{
 205        struct rev_info rev;
 206        init_revisions(&rev, NULL);
 207        setup_revisions(0, NULL, &rev, s->reference);
 208        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
 209        rev.diffopt.format_callback = wt_status_print_updated_cb;
 210        rev.diffopt.format_callback_data = s;
 211        rev.diffopt.detect_rename = 1;
 212        rev.diffopt.rename_limit = 200;
 213        rev.diffopt.break_opt = 0;
 214        run_diff_index(&rev, 1);
 215}
 216
 217static void wt_status_print_changed(struct wt_status *s)
 218{
 219        struct rev_info rev;
 220        init_revisions(&rev, "");
 221        setup_revisions(0, NULL, &rev, NULL);
 222        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
 223        rev.diffopt.format_callback = wt_status_print_changed_cb;
 224        rev.diffopt.format_callback_data = s;
 225        run_diff_files(&rev, 0);
 226}
 227
 228static void wt_status_print_submodule_summary(struct wt_status *s)
 229{
 230        struct child_process sm_summary;
 231        char summary_limit[64];
 232        char index[PATH_MAX];
 233        const char *env[] = { index, NULL };
 234        const char *argv[] = {
 235                "submodule",
 236                "summary",
 237                "--cached",
 238                "--for-status",
 239                "--summary-limit",
 240                summary_limit,
 241                s->amend ? "HEAD^" : "HEAD",
 242                NULL
 243        };
 244
 245        sprintf(summary_limit, "%d", wt_status_submodule_summary);
 246        snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", s->index_file);
 247
 248        memset(&sm_summary, 0, sizeof(sm_summary));
 249        sm_summary.argv = argv;
 250        sm_summary.env = env;
 251        sm_summary.git_cmd = 1;
 252        sm_summary.no_stdin = 1;
 253        fflush(s->fp);
 254        sm_summary.out = dup(fileno(s->fp));    /* run_command closes it */
 255        run_command(&sm_summary);
 256}
 257
 258static void wt_status_print_untracked(struct wt_status *s)
 259{
 260        struct dir_struct dir;
 261        int i;
 262        int shown_header = 0;
 263        struct strbuf buf;
 264
 265        strbuf_init(&buf, 0);
 266        memset(&dir, 0, sizeof(dir));
 267
 268        if (!s->untracked) {
 269                dir.show_other_directories = 1;
 270                dir.hide_empty_directories = 1;
 271        }
 272        setup_standard_excludes(&dir);
 273
 274        read_directory(&dir, ".", "", 0, NULL);
 275        for(i = 0; i < dir.nr; i++) {
 276                /* check for matching entry, which is unmerged; lifted from
 277                 * builtin-ls-files:show_other_files */
 278                struct dir_entry *ent = dir.entries[i];
 279                int pos = cache_name_pos(ent->name, ent->len);
 280                struct cache_entry *ce;
 281                if (0 <= pos)
 282                        die("bug in wt_status_print_untracked");
 283                pos = -pos - 1;
 284                if (pos < active_nr) {
 285                        ce = active_cache[pos];
 286                        if (ce_namelen(ce) == ent->len &&
 287                            !memcmp(ce->name, ent->name, ent->len))
 288                                continue;
 289                }
 290                if (!shown_header) {
 291                        s->workdir_untracked = 1;
 292                        wt_status_print_header(s, "Untracked files",
 293                                               use_add_to_include_msg);
 294                        shown_header = 1;
 295                }
 296                color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
 297                color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED), "%s",
 298                                quote_path(ent->name, ent->len,
 299                                        &buf, s->prefix));
 300        }
 301        strbuf_release(&buf);
 302}
 303
 304static void wt_status_print_verbose(struct wt_status *s)
 305{
 306        struct rev_info rev;
 307
 308        init_revisions(&rev, NULL);
 309        setup_revisions(0, NULL, &rev, s->reference);
 310        rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
 311        rev.diffopt.detect_rename = 1;
 312        rev.diffopt.file = s->fp;
 313        rev.diffopt.close_file = 0;
 314        run_diff_index(&rev, 1);
 315}
 316
 317void wt_status_print(struct wt_status *s)
 318{
 319        unsigned char sha1[20];
 320        const char *branch_color = color(WT_STATUS_HEADER);
 321
 322        s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
 323        if (s->branch) {
 324                const char *on_what = "On branch ";
 325                const char *branch_name = s->branch;
 326                if (!prefixcmp(branch_name, "refs/heads/"))
 327                        branch_name += 11;
 328                else if (!strcmp(branch_name, "HEAD")) {
 329                        branch_name = "";
 330                        branch_color = color(WT_STATUS_NOBRANCH);
 331                        on_what = "Not currently on any branch.";
 332                }
 333                color_fprintf(s->fp, color(WT_STATUS_HEADER), "# ");
 334                color_fprintf_ln(s->fp, branch_color, "%s%s", on_what, branch_name);
 335        }
 336
 337        if (s->is_initial) {
 338                color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
 339                color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "# Initial commit");
 340                color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
 341                wt_status_print_initial(s);
 342        }
 343        else {
 344                wt_status_print_updated(s);
 345        }
 346
 347        wt_status_print_changed(s);
 348        if (wt_status_submodule_summary)
 349                wt_status_print_submodule_summary(s);
 350        wt_status_print_untracked(s);
 351
 352        if (s->verbose && !s->is_initial)
 353                wt_status_print_verbose(s);
 354        if (!s->commitable) {
 355                if (s->amend)
 356                        fprintf(s->fp, "# No changes\n");
 357                else if (s->nowarn)
 358                        ; /* nothing */
 359                else if (s->workdir_dirty)
 360                        printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
 361                else if (s->workdir_untracked)
 362                        printf("nothing added to commit but untracked files present (use \"git add\" to track)\n");
 363                else if (s->is_initial)
 364                        printf("nothing to commit (create/copy files and use \"git add\" to track)\n");
 365                else
 366                        printf("nothing to commit (working directory clean)\n");
 367        }
 368}
 369
 370int git_status_config(const char *k, const char *v, void *cb)
 371{
 372        if (!strcmp(k, "status.submodulesummary")) {
 373                int is_bool;
 374                wt_status_submodule_summary = git_config_bool_or_int(k, v, &is_bool);
 375                if (is_bool && wt_status_submodule_summary)
 376                        wt_status_submodule_summary = -1;
 377                return 0;
 378        }
 379        if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
 380                wt_status_use_color = git_config_colorbool(k, v, -1);
 381                return 0;
 382        }
 383        if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
 384                int slot = parse_status_slot(k, 13);
 385                if (!v)
 386                        return config_error_nonbool(k);
 387                color_parse(v, k, wt_status_colors[slot]);
 388                return 0;
 389        }
 390        if (!strcmp(k, "status.relativepaths")) {
 391                wt_status_relative_paths = git_config_bool(k, v);
 392                return 0;
 393        }
 394        return git_color_default_config(k, v, cb);
 395}