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