Code

Make git_status_config() file scope static to builtin-commit.c
[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 const char *color(int slot, struct wt_status *s)
23 {
24         return s->use_color > 0 ? s->color_palette[slot] : "";
25 }
27 void wt_status_prepare(struct wt_status *s)
28 {
29         unsigned char sha1[20];
30         const char *head;
32         memset(s, 0, sizeof(*s));
33         memcpy(s->color_palette, default_wt_status_colors,
34                sizeof(default_wt_status_colors));
35         s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
36         s->use_color = -1;
37         s->relative_paths = 1;
38         head = resolve_ref("HEAD", sha1, 0, NULL);
39         s->branch = head ? xstrdup(head) : NULL;
40         s->reference = "HEAD";
41         s->fp = stdout;
42         s->index_file = get_index_file();
43         s->change.strdup_strings = 1;
44 }
46 static void wt_status_print_unmerged_header(struct wt_status *s)
47 {
48         const char *c = color(WT_STATUS_HEADER, s);
49         color_fprintf_ln(s->fp, c, "# Unmerged paths:");
50         if (!s->is_initial)
51                 color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
52         else
53                 color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
54         color_fprintf_ln(s->fp, c, "#   (use \"git add <file>...\" to mark resolution)");
55         color_fprintf_ln(s->fp, c, "#");
56 }
58 static void wt_status_print_cached_header(struct wt_status *s)
59 {
60         const char *c = color(WT_STATUS_HEADER, s);
61         color_fprintf_ln(s->fp, c, "# Changes to be committed:");
62         if (!s->is_initial) {
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 }
70 static void wt_status_print_dirty_header(struct wt_status *s,
71                                          int has_deleted)
72 {
73         const char *c = color(WT_STATUS_HEADER, s);
74         color_fprintf_ln(s->fp, c, "# Changed but not updated:");
75         if (!has_deleted)
76                 color_fprintf_ln(s->fp, c, "#   (use \"git add <file>...\" to update what will be committed)");
77         else
78                 color_fprintf_ln(s->fp, c, "#   (use \"git add/rm <file>...\" to update what will be committed)");
79         color_fprintf_ln(s->fp, c, "#   (use \"git checkout -- <file>...\" to discard changes in working directory)");
80         color_fprintf_ln(s->fp, c, "#");
81 }
83 static void wt_status_print_untracked_header(struct wt_status *s)
84 {
85         const char *c = color(WT_STATUS_HEADER, s);
86         color_fprintf_ln(s->fp, c, "# Untracked files:");
87         color_fprintf_ln(s->fp, c, "#   (use \"git add <file>...\" to include in what will be committed)");
88         color_fprintf_ln(s->fp, c, "#");
89 }
91 static void wt_status_print_trailer(struct wt_status *s)
92 {
93         color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
94 }
96 #define quote_path quote_path_relative
98 static void wt_status_print_unmerged_data(struct wt_status *s,
99                                           struct string_list_item *it)
101         const char *c = color(WT_STATUS_UNMERGED, s);
102         struct wt_status_change_data *d = it->util;
103         struct strbuf onebuf = STRBUF_INIT;
104         const char *one, *how = "bug";
106         one = quote_path(it->string, -1, &onebuf, s->prefix);
107         color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
108         switch (d->stagemask) {
109         case 1: how = "both deleted:"; break;
110         case 2: how = "added by us:"; break;
111         case 3: how = "deleted by them:"; break;
112         case 4: how = "added by them:"; break;
113         case 5: how = "deleted by us:"; break;
114         case 6: how = "both added:"; break;
115         case 7: how = "both modified:"; break;
116         }
117         color_fprintf(s->fp, c, "%-20s%s\n", how, one);
118         strbuf_release(&onebuf);
121 static void wt_status_print_change_data(struct wt_status *s,
122                                         int change_type,
123                                         struct string_list_item *it)
125         struct wt_status_change_data *d = it->util;
126         const char *c = color(change_type, s);
127         int status = status;
128         char *one_name;
129         char *two_name;
130         const char *one, *two;
131         struct strbuf onebuf = STRBUF_INIT, twobuf = STRBUF_INIT;
133         one_name = two_name = it->string;
134         switch (change_type) {
135         case WT_STATUS_UPDATED:
136                 status = d->index_status;
137                 if (d->head_path)
138                         one_name = d->head_path;
139                 break;
140         case WT_STATUS_CHANGED:
141                 status = d->worktree_status;
142                 break;
143         }
145         one = quote_path(one_name, -1, &onebuf, s->prefix);
146         two = quote_path(two_name, -1, &twobuf, s->prefix);
148         color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
149         switch (status) {
150         case DIFF_STATUS_ADDED:
151                 color_fprintf(s->fp, c, "new file:   %s", one);
152                 break;
153         case DIFF_STATUS_COPIED:
154                 color_fprintf(s->fp, c, "copied:     %s -> %s", one, two);
155                 break;
156         case DIFF_STATUS_DELETED:
157                 color_fprintf(s->fp, c, "deleted:    %s", one);
158                 break;
159         case DIFF_STATUS_MODIFIED:
160                 color_fprintf(s->fp, c, "modified:   %s", one);
161                 break;
162         case DIFF_STATUS_RENAMED:
163                 color_fprintf(s->fp, c, "renamed:    %s -> %s", one, two);
164                 break;
165         case DIFF_STATUS_TYPE_CHANGED:
166                 color_fprintf(s->fp, c, "typechange: %s", one);
167                 break;
168         case DIFF_STATUS_UNKNOWN:
169                 color_fprintf(s->fp, c, "unknown:    %s", one);
170                 break;
171         case DIFF_STATUS_UNMERGED:
172                 color_fprintf(s->fp, c, "unmerged:   %s", one);
173                 break;
174         default:
175                 die("bug: unhandled diff status %c", status);
176         }
177         fprintf(s->fp, "\n");
178         strbuf_release(&onebuf);
179         strbuf_release(&twobuf);
182 static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
183                                          struct diff_options *options,
184                                          void *data)
186         struct wt_status *s = data;
187         int i;
189         if (!q->nr)
190                 return;
191         s->workdir_dirty = 1;
192         for (i = 0; i < q->nr; i++) {
193                 struct diff_filepair *p;
194                 struct string_list_item *it;
195                 struct wt_status_change_data *d;
197                 p = q->queue[i];
198                 it = string_list_insert(p->one->path, &s->change);
199                 d = it->util;
200                 if (!d) {
201                         d = xcalloc(1, sizeof(*d));
202                         it->util = d;
203                 }
204                 if (!d->worktree_status)
205                         d->worktree_status = p->status;
206         }
209 static int unmerged_mask(const char *path)
211         int pos, mask;
212         struct cache_entry *ce;
214         pos = cache_name_pos(path, strlen(path));
215         if (0 <= pos)
216                 return 0;
218         mask = 0;
219         pos = -pos-1;
220         while (pos < active_nr) {
221                 ce = active_cache[pos++];
222                 if (strcmp(ce->name, path) || !ce_stage(ce))
223                         break;
224                 mask |= (1 << (ce_stage(ce) - 1));
225         }
226         return mask;
229 static void wt_status_collect_updated_cb(struct diff_queue_struct *q,
230                                          struct diff_options *options,
231                                          void *data)
233         struct wt_status *s = data;
234         int i;
236         for (i = 0; i < q->nr; i++) {
237                 struct diff_filepair *p;
238                 struct string_list_item *it;
239                 struct wt_status_change_data *d;
241                 p = q->queue[i];
242                 it = string_list_insert(p->two->path, &s->change);
243                 d = it->util;
244                 if (!d) {
245                         d = xcalloc(1, sizeof(*d));
246                         it->util = d;
247                 }
248                 if (!d->index_status)
249                         d->index_status = p->status;
250                 switch (p->status) {
251                 case DIFF_STATUS_COPIED:
252                 case DIFF_STATUS_RENAMED:
253                         d->head_path = xstrdup(p->one->path);
254                         break;
255                 case DIFF_STATUS_UNMERGED:
256                         d->stagemask = unmerged_mask(p->two->path);
257                         break;
258                 }
259         }
262 static void wt_status_collect_changes_worktree(struct wt_status *s)
264         struct rev_info rev;
266         init_revisions(&rev, NULL);
267         setup_revisions(0, NULL, &rev, NULL);
268         rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
269         rev.diffopt.format_callback = wt_status_collect_changed_cb;
270         rev.diffopt.format_callback_data = s;
271         run_diff_files(&rev, 0);
274 static void wt_status_collect_changes_index(struct wt_status *s)
276         struct rev_info rev;
278         init_revisions(&rev, NULL);
279         setup_revisions(0, NULL, &rev,
280                 s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference);
281         rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
282         rev.diffopt.format_callback = wt_status_collect_updated_cb;
283         rev.diffopt.format_callback_data = s;
284         rev.diffopt.detect_rename = 1;
285         rev.diffopt.rename_limit = 200;
286         rev.diffopt.break_opt = 0;
287         run_diff_index(&rev, 1);
290 static void wt_status_collect_changes_initial(struct wt_status *s)
292         int i;
294         for (i = 0; i < active_nr; i++) {
295                 struct string_list_item *it;
296                 struct wt_status_change_data *d;
297                 struct cache_entry *ce = active_cache[i];
299                 it = string_list_insert(ce->name, &s->change);
300                 d = it->util;
301                 if (!d) {
302                         d = xcalloc(1, sizeof(*d));
303                         it->util = d;
304                 }
305                 if (ce_stage(ce)) {
306                         d->index_status = DIFF_STATUS_UNMERGED;
307                         d->stagemask |= (1 << (ce_stage(ce) - 1));
308                 }
309                 else
310                         d->index_status = DIFF_STATUS_ADDED;
311         }
314 void wt_status_collect_changes(struct wt_status *s)
316         wt_status_collect_changes_worktree(s);
318         if (s->is_initial)
319                 wt_status_collect_changes_initial(s);
320         else
321                 wt_status_collect_changes_index(s);
324 static void wt_status_print_unmerged(struct wt_status *s)
326         int shown_header = 0;
327         int i;
329         for (i = 0; i < s->change.nr; i++) {
330                 struct wt_status_change_data *d;
331                 struct string_list_item *it;
332                 it = &(s->change.items[i]);
333                 d = it->util;
334                 if (!d->stagemask)
335                         continue;
336                 if (!shown_header) {
337                         wt_status_print_unmerged_header(s);
338                         shown_header = 1;
339                 }
340                 wt_status_print_unmerged_data(s, it);
341         }
342         if (shown_header)
343                 wt_status_print_trailer(s);
347 static void wt_status_print_updated(struct wt_status *s)
349         int shown_header = 0;
350         int i;
352         for (i = 0; i < s->change.nr; i++) {
353                 struct wt_status_change_data *d;
354                 struct string_list_item *it;
355                 it = &(s->change.items[i]);
356                 d = it->util;
357                 if (!d->index_status ||
358                     d->index_status == DIFF_STATUS_UNMERGED)
359                         continue;
360                 if (!shown_header) {
361                         wt_status_print_cached_header(s);
362                         s->commitable = 1;
363                         shown_header = 1;
364                 }
365                 wt_status_print_change_data(s, WT_STATUS_UPDATED, it);
366         }
367         if (shown_header)
368                 wt_status_print_trailer(s);
371 /*
372  * -1 : has delete
373  *  0 : no change
374  *  1 : some change but no delete
375  */
376 static int wt_status_check_worktree_changes(struct wt_status *s)
378         int i;
379         int changes = 0;
381         for (i = 0; i < s->change.nr; i++) {
382                 struct wt_status_change_data *d;
383                 d = s->change.items[i].util;
384                 if (!d->worktree_status ||
385                     d->worktree_status == DIFF_STATUS_UNMERGED)
386                         continue;
387                 changes = 1;
388                 if (d->worktree_status == DIFF_STATUS_DELETED)
389                         return -1;
390         }
391         return changes;
394 static void wt_status_print_changed(struct wt_status *s)
396         int i;
397         int worktree_changes = wt_status_check_worktree_changes(s);
399         if (!worktree_changes)
400                 return;
402         wt_status_print_dirty_header(s, worktree_changes < 0);
404         for (i = 0; i < s->change.nr; i++) {
405                 struct wt_status_change_data *d;
406                 struct string_list_item *it;
407                 it = &(s->change.items[i]);
408                 d = it->util;
409                 if (!d->worktree_status ||
410                     d->worktree_status == DIFF_STATUS_UNMERGED)
411                         continue;
412                 wt_status_print_change_data(s, WT_STATUS_CHANGED, it);
413         }
414         wt_status_print_trailer(s);
417 static void wt_status_print_submodule_summary(struct wt_status *s)
419         struct child_process sm_summary;
420         char summary_limit[64];
421         char index[PATH_MAX];
422         const char *env[] = { index, NULL };
423         const char *argv[] = {
424                 "submodule",
425                 "summary",
426                 "--cached",
427                 "--for-status",
428                 "--summary-limit",
429                 summary_limit,
430                 s->amend ? "HEAD^" : "HEAD",
431                 NULL
432         };
434         sprintf(summary_limit, "%d", s->submodule_summary);
435         snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", s->index_file);
437         memset(&sm_summary, 0, sizeof(sm_summary));
438         sm_summary.argv = argv;
439         sm_summary.env = env;
440         sm_summary.git_cmd = 1;
441         sm_summary.no_stdin = 1;
442         fflush(s->fp);
443         sm_summary.out = dup(fileno(s->fp));    /* run_command closes it */
444         run_command(&sm_summary);
447 static void wt_status_print_untracked(struct wt_status *s)
449         struct dir_struct dir;
450         int i;
451         int shown_header = 0;
452         struct strbuf buf = STRBUF_INIT;
454         memset(&dir, 0, sizeof(dir));
455         if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
456                 dir.flags |=
457                         DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
458         setup_standard_excludes(&dir);
460         fill_directory(&dir, NULL);
461         for(i = 0; i < dir.nr; i++) {
462                 struct dir_entry *ent = dir.entries[i];
463                 if (!cache_name_is_other(ent->name, ent->len))
464                         continue;
465                 if (!shown_header) {
466                         s->workdir_untracked = 1;
467                         wt_status_print_untracked_header(s);
468                         shown_header = 1;
469                 }
470                 color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
471                 color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED, s), "%s",
472                                 quote_path(ent->name, ent->len,
473                                         &buf, s->prefix));
474         }
475         strbuf_release(&buf);
478 static void wt_status_print_verbose(struct wt_status *s)
480         struct rev_info rev;
482         init_revisions(&rev, NULL);
483         DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
484         setup_revisions(0, NULL, &rev,
485                 s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference);
486         rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
487         rev.diffopt.detect_rename = 1;
488         rev.diffopt.file = s->fp;
489         rev.diffopt.close_file = 0;
490         /*
491          * If we're not going to stdout, then we definitely don't
492          * want color, since we are going to the commit message
493          * file (and even the "auto" setting won't work, since it
494          * will have checked isatty on stdout).
495          */
496         if (s->fp != stdout)
497                 DIFF_OPT_CLR(&rev.diffopt, COLOR_DIFF);
498         run_diff_index(&rev, 1);
501 static void wt_status_print_tracking(struct wt_status *s)
503         struct strbuf sb = STRBUF_INIT;
504         const char *cp, *ep;
505         struct branch *branch;
507         assert(s->branch && !s->is_initial);
508         if (prefixcmp(s->branch, "refs/heads/"))
509                 return;
510         branch = branch_get(s->branch + 11);
511         if (!format_tracking_info(branch, &sb))
512                 return;
514         for (cp = sb.buf; (ep = strchr(cp, '\n')) != NULL; cp = ep + 1)
515                 color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s),
516                                  "# %.*s", (int)(ep - cp), cp);
517         color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
520 void wt_status_print(struct wt_status *s)
522         unsigned char sha1[20];
523         const char *branch_color = color(WT_STATUS_HEADER, s);
525         s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
526         if (s->branch) {
527                 const char *on_what = "On branch ";
528                 const char *branch_name = s->branch;
529                 if (!prefixcmp(branch_name, "refs/heads/"))
530                         branch_name += 11;
531                 else if (!strcmp(branch_name, "HEAD")) {
532                         branch_name = "";
533                         branch_color = color(WT_STATUS_NOBRANCH, s);
534                         on_what = "Not currently on any branch.";
535                 }
536                 color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "# ");
537                 color_fprintf_ln(s->fp, branch_color, "%s%s", on_what, branch_name);
538                 if (!s->is_initial)
539                         wt_status_print_tracking(s);
540         }
542         wt_status_collect_changes(s);
544         if (s->is_initial) {
545                 color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
546                 color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# Initial commit");
547                 color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
548         }
550         wt_status_print_unmerged(s);
551         wt_status_print_updated(s);
552         wt_status_print_changed(s);
553         if (s->submodule_summary)
554                 wt_status_print_submodule_summary(s);
555         if (s->show_untracked_files)
556                 wt_status_print_untracked(s);
557         else if (s->commitable)
558                  fprintf(s->fp, "# Untracked files not listed (use -u option to show untracked files)\n");
560         if (s->verbose)
561                 wt_status_print_verbose(s);
562         if (!s->commitable) {
563                 if (s->amend)
564                         fprintf(s->fp, "# No changes\n");
565                 else if (s->nowarn)
566                         ; /* nothing */
567                 else if (s->workdir_dirty)
568                         printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
569                 else if (s->workdir_untracked)
570                         printf("nothing added to commit but untracked files present (use \"git add\" to track)\n");
571                 else if (s->is_initial)
572                         printf("nothing to commit (create/copy files and use \"git add\" to track)\n");
573                 else if (!s->show_untracked_files)
574                         printf("nothing to commit (use -u to show untracked files)\n");
575                 else
576                         printf("nothing to commit (working directory clean)\n");
577         }