Code

wt-status: move wt_status_colors[] into wt_status structure
[git.git] / wt-status.c
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"
13 static char default_wt_status_colors[][COLOR_MAXLEN] = {
14         GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
15         GIT_COLOR_GREEN,  /* WT_STATUS_UPDATED */
16         GIT_COLOR_RED,    /* WT_STATUS_CHANGED */
17         GIT_COLOR_RED,    /* WT_STATUS_UNTRACKED */
18         GIT_COLOR_RED,    /* WT_STATUS_NOBRANCH */
19         GIT_COLOR_RED,    /* WT_STATUS_UNMERGED */
20 };
22 static int parse_status_slot(const char *var, int offset)
23 {
24         if (!strcasecmp(var+offset, "header"))
25                 return WT_STATUS_HEADER;
26         if (!strcasecmp(var+offset, "updated")
27                 || !strcasecmp(var+offset, "added"))
28                 return WT_STATUS_UPDATED;
29         if (!strcasecmp(var+offset, "changed"))
30                 return WT_STATUS_CHANGED;
31         if (!strcasecmp(var+offset, "untracked"))
32                 return WT_STATUS_UNTRACKED;
33         if (!strcasecmp(var+offset, "nobranch"))
34                 return WT_STATUS_NOBRANCH;
35         if (!strcasecmp(var+offset, "unmerged"))
36                 return WT_STATUS_UNMERGED;
37         die("bad config variable '%s'", var);
38 }
40 static const char *color(int slot, struct wt_status *s)
41 {
42         return s->use_color > 0 ? s->color_palette[slot] : "";
43 }
45 void wt_status_prepare(struct wt_status *s)
46 {
47         unsigned char sha1[20];
48         const char *head;
50         memset(s, 0, sizeof(*s));
51         memcpy(s->color_palette, default_wt_status_colors,
52                sizeof(default_wt_status_colors));
53         s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
54         s->use_color = -1;
55         s->relative_paths = 1;
56         head = resolve_ref("HEAD", sha1, 0, NULL);
57         s->branch = head ? xstrdup(head) : NULL;
58         s->reference = "HEAD";
59         s->fp = stdout;
60         s->index_file = get_index_file();
61         s->change.strdup_strings = 1;
62 }
64 static void wt_status_print_unmerged_header(struct wt_status *s)
65 {
66         const char *c = color(WT_STATUS_HEADER, s);
67         color_fprintf_ln(s->fp, c, "# Unmerged paths:");
68         if (!s->is_initial)
69                 color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
70         else
71                 color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
72         color_fprintf_ln(s->fp, c, "#   (use \"git add <file>...\" to mark resolution)");
73         color_fprintf_ln(s->fp, c, "#");
74 }
76 static void wt_status_print_cached_header(struct wt_status *s)
77 {
78         const char *c = color(WT_STATUS_HEADER, s);
79         color_fprintf_ln(s->fp, c, "# Changes to be committed:");
80         if (!s->is_initial) {
81                 color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
82         } else {
83                 color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
84         }
85         color_fprintf_ln(s->fp, c, "#");
86 }
88 static void wt_status_print_dirty_header(struct wt_status *s,
89                                          int has_deleted)
90 {
91         const char *c = color(WT_STATUS_HEADER, s);
92         color_fprintf_ln(s->fp, c, "# Changed but not updated:");
93         if (!has_deleted)
94                 color_fprintf_ln(s->fp, c, "#   (use \"git add <file>...\" to update what will be committed)");
95         else
96                 color_fprintf_ln(s->fp, c, "#   (use \"git add/rm <file>...\" to update what will be committed)");
97         color_fprintf_ln(s->fp, c, "#   (use \"git checkout -- <file>...\" to discard changes in working directory)");
98         color_fprintf_ln(s->fp, c, "#");
99 }
101 static void wt_status_print_untracked_header(struct wt_status *s)
103         const char *c = color(WT_STATUS_HEADER, s);
104         color_fprintf_ln(s->fp, c, "# Untracked files:");
105         color_fprintf_ln(s->fp, c, "#   (use \"git add <file>...\" to include in what will be committed)");
106         color_fprintf_ln(s->fp, c, "#");
109 static void wt_status_print_trailer(struct wt_status *s)
111         color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
114 #define quote_path quote_path_relative
116 static void wt_status_print_unmerged_data(struct wt_status *s,
117                                           struct string_list_item *it)
119         const char *c = color(WT_STATUS_UNMERGED, s);
120         struct wt_status_change_data *d = it->util;
121         struct strbuf onebuf = STRBUF_INIT;
122         const char *one, *how = "bug";
124         one = quote_path(it->string, -1, &onebuf, s->prefix);
125         color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
126         switch (d->stagemask) {
127         case 1: how = "both deleted:"; break;
128         case 2: how = "added by us:"; break;
129         case 3: how = "deleted by them:"; break;
130         case 4: how = "added by them:"; break;
131         case 5: how = "deleted by us:"; break;
132         case 6: how = "both added:"; break;
133         case 7: how = "both modified:"; break;
134         }
135         color_fprintf(s->fp, c, "%-20s%s\n", how, one);
136         strbuf_release(&onebuf);
139 static void wt_status_print_change_data(struct wt_status *s,
140                                         int change_type,
141                                         struct string_list_item *it)
143         struct wt_status_change_data *d = it->util;
144         const char *c = color(change_type, s);
145         int status = status;
146         char *one_name;
147         char *two_name;
148         const char *one, *two;
149         struct strbuf onebuf = STRBUF_INIT, twobuf = STRBUF_INIT;
151         one_name = two_name = it->string;
152         switch (change_type) {
153         case WT_STATUS_UPDATED:
154                 status = d->index_status;
155                 if (d->head_path)
156                         one_name = d->head_path;
157                 break;
158         case WT_STATUS_CHANGED:
159                 status = d->worktree_status;
160                 break;
161         }
163         one = quote_path(one_name, -1, &onebuf, s->prefix);
164         two = quote_path(two_name, -1, &twobuf, s->prefix);
166         color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
167         switch (status) {
168         case DIFF_STATUS_ADDED:
169                 color_fprintf(s->fp, c, "new file:   %s", one);
170                 break;
171         case DIFF_STATUS_COPIED:
172                 color_fprintf(s->fp, c, "copied:     %s -> %s", one, two);
173                 break;
174         case DIFF_STATUS_DELETED:
175                 color_fprintf(s->fp, c, "deleted:    %s", one);
176                 break;
177         case DIFF_STATUS_MODIFIED:
178                 color_fprintf(s->fp, c, "modified:   %s", one);
179                 break;
180         case DIFF_STATUS_RENAMED:
181                 color_fprintf(s->fp, c, "renamed:    %s -> %s", one, two);
182                 break;
183         case DIFF_STATUS_TYPE_CHANGED:
184                 color_fprintf(s->fp, c, "typechange: %s", one);
185                 break;
186         case DIFF_STATUS_UNKNOWN:
187                 color_fprintf(s->fp, c, "unknown:    %s", one);
188                 break;
189         case DIFF_STATUS_UNMERGED:
190                 color_fprintf(s->fp, c, "unmerged:   %s", one);
191                 break;
192         default:
193                 die("bug: unhandled diff status %c", status);
194         }
195         fprintf(s->fp, "\n");
196         strbuf_release(&onebuf);
197         strbuf_release(&twobuf);
200 static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
201                                          struct diff_options *options,
202                                          void *data)
204         struct wt_status *s = data;
205         int i;
207         if (!q->nr)
208                 return;
209         s->workdir_dirty = 1;
210         for (i = 0; i < q->nr; i++) {
211                 struct diff_filepair *p;
212                 struct string_list_item *it;
213                 struct wt_status_change_data *d;
215                 p = q->queue[i];
216                 it = string_list_insert(p->one->path, &s->change);
217                 d = it->util;
218                 if (!d) {
219                         d = xcalloc(1, sizeof(*d));
220                         it->util = d;
221                 }
222                 if (!d->worktree_status)
223                         d->worktree_status = p->status;
224         }
227 static int unmerged_mask(const char *path)
229         int pos, mask;
230         struct cache_entry *ce;
232         pos = cache_name_pos(path, strlen(path));
233         if (0 <= pos)
234                 return 0;
236         mask = 0;
237         pos = -pos-1;
238         while (pos < active_nr) {
239                 ce = active_cache[pos++];
240                 if (strcmp(ce->name, path) || !ce_stage(ce))
241                         break;
242                 mask |= (1 << (ce_stage(ce) - 1));
243         }
244         return mask;
247 static void wt_status_collect_updated_cb(struct diff_queue_struct *q,
248                                          struct diff_options *options,
249                                          void *data)
251         struct wt_status *s = data;
252         int i;
254         for (i = 0; i < q->nr; i++) {
255                 struct diff_filepair *p;
256                 struct string_list_item *it;
257                 struct wt_status_change_data *d;
259                 p = q->queue[i];
260                 it = string_list_insert(p->two->path, &s->change);
261                 d = it->util;
262                 if (!d) {
263                         d = xcalloc(1, sizeof(*d));
264                         it->util = d;
265                 }
266                 if (!d->index_status)
267                         d->index_status = p->status;
268                 switch (p->status) {
269                 case DIFF_STATUS_COPIED:
270                 case DIFF_STATUS_RENAMED:
271                         d->head_path = xstrdup(p->one->path);
272                         break;
273                 case DIFF_STATUS_UNMERGED:
274                         d->stagemask = unmerged_mask(p->two->path);
275                         break;
276                 }
277         }
280 static void wt_status_collect_changes_worktree(struct wt_status *s)
282         struct rev_info rev;
284         init_revisions(&rev, NULL);
285         setup_revisions(0, NULL, &rev, NULL);
286         rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
287         rev.diffopt.format_callback = wt_status_collect_changed_cb;
288         rev.diffopt.format_callback_data = s;
289         run_diff_files(&rev, 0);
292 static void wt_status_collect_changes_index(struct wt_status *s)
294         struct rev_info rev;
296         init_revisions(&rev, NULL);
297         setup_revisions(0, NULL, &rev,
298                 s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference);
299         rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
300         rev.diffopt.format_callback = wt_status_collect_updated_cb;
301         rev.diffopt.format_callback_data = s;
302         rev.diffopt.detect_rename = 1;
303         rev.diffopt.rename_limit = 200;
304         rev.diffopt.break_opt = 0;
305         run_diff_index(&rev, 1);
308 static void wt_status_collect_changes_initial(struct wt_status *s)
310         int i;
312         for (i = 0; i < active_nr; i++) {
313                 struct string_list_item *it;
314                 struct wt_status_change_data *d;
315                 struct cache_entry *ce = active_cache[i];
317                 it = string_list_insert(ce->name, &s->change);
318                 d = it->util;
319                 if (!d) {
320                         d = xcalloc(1, sizeof(*d));
321                         it->util = d;
322                 }
323                 if (ce_stage(ce)) {
324                         d->index_status = DIFF_STATUS_UNMERGED;
325                         d->stagemask |= (1 << (ce_stage(ce) - 1));
326                 }
327                 else
328                         d->index_status = DIFF_STATUS_ADDED;
329         }
332 void wt_status_collect_changes(struct wt_status *s)
334         wt_status_collect_changes_worktree(s);
336         if (s->is_initial)
337                 wt_status_collect_changes_initial(s);
338         else
339                 wt_status_collect_changes_index(s);
342 static void wt_status_print_unmerged(struct wt_status *s)
344         int shown_header = 0;
345         int i;
347         for (i = 0; i < s->change.nr; i++) {
348                 struct wt_status_change_data *d;
349                 struct string_list_item *it;
350                 it = &(s->change.items[i]);
351                 d = it->util;
352                 if (!d->stagemask)
353                         continue;
354                 if (!shown_header) {
355                         wt_status_print_unmerged_header(s);
356                         shown_header = 1;
357                 }
358                 wt_status_print_unmerged_data(s, it);
359         }
360         if (shown_header)
361                 wt_status_print_trailer(s);
365 static void wt_status_print_updated(struct wt_status *s)
367         int shown_header = 0;
368         int i;
370         for (i = 0; i < s->change.nr; i++) {
371                 struct wt_status_change_data *d;
372                 struct string_list_item *it;
373                 it = &(s->change.items[i]);
374                 d = it->util;
375                 if (!d->index_status ||
376                     d->index_status == DIFF_STATUS_UNMERGED)
377                         continue;
378                 if (!shown_header) {
379                         wt_status_print_cached_header(s);
380                         s->commitable = 1;
381                         shown_header = 1;
382                 }
383                 wt_status_print_change_data(s, WT_STATUS_UPDATED, it);
384         }
385         if (shown_header)
386                 wt_status_print_trailer(s);
389 /*
390  * -1 : has delete
391  *  0 : no change
392  *  1 : some change but no delete
393  */
394 static int wt_status_check_worktree_changes(struct wt_status *s)
396         int i;
397         int changes = 0;
399         for (i = 0; i < s->change.nr; i++) {
400                 struct wt_status_change_data *d;
401                 d = s->change.items[i].util;
402                 if (!d->worktree_status ||
403                     d->worktree_status == DIFF_STATUS_UNMERGED)
404                         continue;
405                 changes = 1;
406                 if (d->worktree_status == DIFF_STATUS_DELETED)
407                         return -1;
408         }
409         return changes;
412 static void wt_status_print_changed(struct wt_status *s)
414         int i;
415         int worktree_changes = wt_status_check_worktree_changes(s);
417         if (!worktree_changes)
418                 return;
420         wt_status_print_dirty_header(s, worktree_changes < 0);
422         for (i = 0; i < s->change.nr; i++) {
423                 struct wt_status_change_data *d;
424                 struct string_list_item *it;
425                 it = &(s->change.items[i]);
426                 d = it->util;
427                 if (!d->worktree_status ||
428                     d->worktree_status == DIFF_STATUS_UNMERGED)
429                         continue;
430                 wt_status_print_change_data(s, WT_STATUS_CHANGED, it);
431         }
432         wt_status_print_trailer(s);
435 static void wt_status_print_submodule_summary(struct wt_status *s)
437         struct child_process sm_summary;
438         char summary_limit[64];
439         char index[PATH_MAX];
440         const char *env[] = { index, NULL };
441         const char *argv[] = {
442                 "submodule",
443                 "summary",
444                 "--cached",
445                 "--for-status",
446                 "--summary-limit",
447                 summary_limit,
448                 s->amend ? "HEAD^" : "HEAD",
449                 NULL
450         };
452         sprintf(summary_limit, "%d", s->submodule_summary);
453         snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", s->index_file);
455         memset(&sm_summary, 0, sizeof(sm_summary));
456         sm_summary.argv = argv;
457         sm_summary.env = env;
458         sm_summary.git_cmd = 1;
459         sm_summary.no_stdin = 1;
460         fflush(s->fp);
461         sm_summary.out = dup(fileno(s->fp));    /* run_command closes it */
462         run_command(&sm_summary);
465 static void wt_status_print_untracked(struct wt_status *s)
467         struct dir_struct dir;
468         int i;
469         int shown_header = 0;
470         struct strbuf buf = STRBUF_INIT;
472         memset(&dir, 0, sizeof(dir));
473         if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
474                 dir.flags |=
475                         DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
476         setup_standard_excludes(&dir);
478         fill_directory(&dir, NULL);
479         for(i = 0; i < dir.nr; i++) {
480                 struct dir_entry *ent = dir.entries[i];
481                 if (!cache_name_is_other(ent->name, ent->len))
482                         continue;
483                 if (!shown_header) {
484                         s->workdir_untracked = 1;
485                         wt_status_print_untracked_header(s);
486                         shown_header = 1;
487                 }
488                 color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
489                 color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED, s), "%s",
490                                 quote_path(ent->name, ent->len,
491                                         &buf, s->prefix));
492         }
493         strbuf_release(&buf);
496 static void wt_status_print_verbose(struct wt_status *s)
498         struct rev_info rev;
500         init_revisions(&rev, NULL);
501         DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
502         setup_revisions(0, NULL, &rev,
503                 s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference);
504         rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
505         rev.diffopt.detect_rename = 1;
506         rev.diffopt.file = s->fp;
507         rev.diffopt.close_file = 0;
508         /*
509          * If we're not going to stdout, then we definitely don't
510          * want color, since we are going to the commit message
511          * file (and even the "auto" setting won't work, since it
512          * will have checked isatty on stdout).
513          */
514         if (s->fp != stdout)
515                 DIFF_OPT_CLR(&rev.diffopt, COLOR_DIFF);
516         run_diff_index(&rev, 1);
519 static void wt_status_print_tracking(struct wt_status *s)
521         struct strbuf sb = STRBUF_INIT;
522         const char *cp, *ep;
523         struct branch *branch;
525         assert(s->branch && !s->is_initial);
526         if (prefixcmp(s->branch, "refs/heads/"))
527                 return;
528         branch = branch_get(s->branch + 11);
529         if (!format_tracking_info(branch, &sb))
530                 return;
532         for (cp = sb.buf; (ep = strchr(cp, '\n')) != NULL; cp = ep + 1)
533                 color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s),
534                                  "# %.*s", (int)(ep - cp), cp);
535         color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
538 void wt_status_print(struct wt_status *s)
540         unsigned char sha1[20];
541         const char *branch_color = color(WT_STATUS_HEADER, s);
543         s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
544         if (s->branch) {
545                 const char *on_what = "On branch ";
546                 const char *branch_name = s->branch;
547                 if (!prefixcmp(branch_name, "refs/heads/"))
548                         branch_name += 11;
549                 else if (!strcmp(branch_name, "HEAD")) {
550                         branch_name = "";
551                         branch_color = color(WT_STATUS_NOBRANCH, s);
552                         on_what = "Not currently on any branch.";
553                 }
554                 color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "# ");
555                 color_fprintf_ln(s->fp, branch_color, "%s%s", on_what, branch_name);
556                 if (!s->is_initial)
557                         wt_status_print_tracking(s);
558         }
560         wt_status_collect_changes(s);
562         if (s->is_initial) {
563                 color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
564                 color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# Initial commit");
565                 color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
566         }
568         wt_status_print_unmerged(s);
569         wt_status_print_updated(s);
570         wt_status_print_changed(s);
571         if (s->submodule_summary)
572                 wt_status_print_submodule_summary(s);
573         if (s->show_untracked_files)
574                 wt_status_print_untracked(s);
575         else if (s->commitable)
576                  fprintf(s->fp, "# Untracked files not listed (use -u option to show untracked files)\n");
578         if (s->verbose)
579                 wt_status_print_verbose(s);
580         if (!s->commitable) {
581                 if (s->amend)
582                         fprintf(s->fp, "# No changes\n");
583                 else if (s->nowarn)
584                         ; /* nothing */
585                 else if (s->workdir_dirty)
586                         printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
587                 else if (s->workdir_untracked)
588                         printf("nothing added to commit but untracked files present (use \"git add\" to track)\n");
589                 else if (s->is_initial)
590                         printf("nothing to commit (create/copy files and use \"git add\" to track)\n");
591                 else if (!s->show_untracked_files)
592                         printf("nothing to commit (use -u to show untracked files)\n");
593                 else
594                         printf("nothing to commit (working directory clean)\n");
595         }
598 int git_status_config(const char *k, const char *v, void *cb)
600         struct wt_status *s = cb;
602         if (!strcmp(k, "status.submodulesummary")) {
603                 int is_bool;
604                 s->submodule_summary = git_config_bool_or_int(k, v, &is_bool);
605                 if (is_bool && s->submodule_summary)
606                         s->submodule_summary = -1;
607                 return 0;
608         }
609         if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
610                 s->use_color = git_config_colorbool(k, v, -1);
611                 return 0;
612         }
613         if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
614                 int slot = parse_status_slot(k, 13);
615                 if (!v)
616                         return config_error_nonbool(k);
617                 color_parse(v, k, s->color_palette[slot]);
618                 return 0;
619         }
620         if (!strcmp(k, "status.relativepaths")) {
621                 s->relative_paths = git_config_bool(k, v);
622                 return 0;
623         }
624         if (!strcmp(k, "status.showuntrackedfiles")) {
625                 if (!v)
626                         return config_error_nonbool(k);
627                 else if (!strcmp(v, "no"))
628                         s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
629                 else if (!strcmp(v, "normal"))
630                         s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
631                 else if (!strcmp(v, "all"))
632                         s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
633                 else
634                         return error("Invalid untracked files mode '%s'", v);
635                 return 0;
636         }
637         return git_diff_ui_config(k, v, NULL);