Code

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