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