status: show branchname with a configurable color
[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"
12 #include "refs.h"
13 #include "submodule.h"
14
15 static char default_wt_status_colors[][COLOR_MAXLEN] = {
16         GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
17         GIT_COLOR_GREEN,  /* WT_STATUS_UPDATED */
18         GIT_COLOR_RED,    /* WT_STATUS_CHANGED */
19         GIT_COLOR_RED,    /* WT_STATUS_UNTRACKED */
20         GIT_COLOR_RED,    /* WT_STATUS_NOBRANCH */
21         GIT_COLOR_RED,    /* WT_STATUS_UNMERGED */
22         GIT_COLOR_GREEN,  /* WT_STATUS_LOCAL_BRANCH */
23         GIT_COLOR_RED,    /* WT_STATUS_REMOTE_BRANCH */
24         GIT_COLOR_NORMAL, /* WT_STATUS_ONBRANCH */
25 };
26
27 static const char *color(int slot, struct wt_status *s)
28 {
29         return s->use_color > 0 ? s->color_palette[slot] : "";
30 }
31
32 void wt_status_prepare(struct wt_status *s)
33 {
34         unsigned char sha1[20];
35         const char *head;
36
37         memset(s, 0, sizeof(*s));
38         memcpy(s->color_palette, default_wt_status_colors,
39                sizeof(default_wt_status_colors));
40         s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
41         s->use_color = -1;
42         s->relative_paths = 1;
43         head = resolve_ref("HEAD", sha1, 0, NULL);
44         s->branch = head ? xstrdup(head) : NULL;
45         s->reference = "HEAD";
46         s->fp = stdout;
47         s->index_file = get_index_file();
48         s->change.strdup_strings = 1;
49         s->untracked.strdup_strings = 1;
50         s->ignored.strdup_strings = 1;
51 }
52
53 static void wt_status_print_unmerged_header(struct wt_status *s)
54 {
55         const char *c = color(WT_STATUS_HEADER, s);
56
57         color_fprintf_ln(s->fp, c, "# Unmerged paths:");
58         if (!advice_status_hints)
59                 return;
60         if (s->in_merge)
61                 ;
62         else 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         color_fprintf_ln(s->fp, c, "#   (use \"git add/rm <file>...\" as appropriate to mark resolution)");
67         color_fprintf_ln(s->fp, c, "#");
68 }
69
70 static void wt_status_print_cached_header(struct wt_status *s)
71 {
72         const char *c = color(WT_STATUS_HEADER, s);
73
74         color_fprintf_ln(s->fp, c, "# Changes to be committed:");
75         if (!advice_status_hints)
76                 return;
77         if (s->in_merge)
78                 ; /* NEEDSWORK: use "git reset --unresolve"??? */
79         else 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         color_fprintf_ln(s->fp, c, "#");
84 }
85
86 static void wt_status_print_dirty_header(struct wt_status *s,
87                                          int has_deleted,
88                                          int has_dirty_submodules)
89 {
90         const char *c = color(WT_STATUS_HEADER, s);
91
92         color_fprintf_ln(s->fp, c, "# Changes not staged for commit:");
93         if (!advice_status_hints)
94                 return;
95         if (!has_deleted)
96                 color_fprintf_ln(s->fp, c, "#   (use \"git add <file>...\" to update what will be committed)");
97         else
98                 color_fprintf_ln(s->fp, c, "#   (use \"git add/rm <file>...\" to update what will be committed)");
99         color_fprintf_ln(s->fp, c, "#   (use \"git checkout -- <file>...\" to discard changes in working directory)");
100         if (has_dirty_submodules)
101                 color_fprintf_ln(s->fp, c, "#   (commit or discard the untracked or modified content in submodules)");
102         color_fprintf_ln(s->fp, c, "#");
103 }
104
105 static void wt_status_print_other_header(struct wt_status *s,
106                                          const char *what,
107                                          const char *how)
108 {
109         const char *c = color(WT_STATUS_HEADER, s);
110         color_fprintf_ln(s->fp, c, "# %s files:", what);
111         if (!advice_status_hints)
112                 return;
113         color_fprintf_ln(s->fp, c, "#   (use \"git %s <file>...\" to include in what will be committed)", how);
114         color_fprintf_ln(s->fp, c, "#");
115 }
116
117 static void wt_status_print_trailer(struct wt_status *s)
118 {
119         color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
120 }
121
122 #define quote_path quote_path_relative
123
124 static void wt_status_print_unmerged_data(struct wt_status *s,
125                                           struct string_list_item *it)
126 {
127         const char *c = color(WT_STATUS_UNMERGED, s);
128         struct wt_status_change_data *d = it->util;
129         struct strbuf onebuf = STRBUF_INIT;
130         const char *one, *how = "bug";
131
132         one = quote_path(it->string, -1, &onebuf, s->prefix);
133         color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
134         switch (d->stagemask) {
135         case 1: how = "both deleted:"; break;
136         case 2: how = "added by us:"; break;
137         case 3: how = "deleted by them:"; break;
138         case 4: how = "added by them:"; break;
139         case 5: how = "deleted by us:"; break;
140         case 6: how = "both added:"; break;
141         case 7: how = "both modified:"; break;
142         }
143         color_fprintf(s->fp, c, "%-20s%s\n", how, one);
144         strbuf_release(&onebuf);
145 }
146
147 static void wt_status_print_change_data(struct wt_status *s,
148                                         int change_type,
149                                         struct string_list_item *it)
150 {
151         struct wt_status_change_data *d = it->util;
152         const char *c = color(change_type, s);
153         int status = status;
154         char *one_name;
155         char *two_name;
156         const char *one, *two;
157         struct strbuf onebuf = STRBUF_INIT, twobuf = STRBUF_INIT;
158         struct strbuf extra = STRBUF_INIT;
159
160         one_name = two_name = it->string;
161         switch (change_type) {
162         case WT_STATUS_UPDATED:
163                 status = d->index_status;
164                 if (d->head_path)
165                         one_name = d->head_path;
166                 break;
167         case WT_STATUS_CHANGED:
168                 if (d->new_submodule_commits || d->dirty_submodule) {
169                         strbuf_addstr(&extra, " (");
170                         if (d->new_submodule_commits)
171                                 strbuf_addf(&extra, "new commits, ");
172                         if (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
173                                 strbuf_addf(&extra, "modified content, ");
174                         if (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
175                                 strbuf_addf(&extra, "untracked content, ");
176                         strbuf_setlen(&extra, extra.len - 2);
177                         strbuf_addch(&extra, ')');
178                 }
179                 status = d->worktree_status;
180                 break;
181         }
182
183         one = quote_path(one_name, -1, &onebuf, s->prefix);
184         two = quote_path(two_name, -1, &twobuf, s->prefix);
185
186         color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
187         switch (status) {
188         case DIFF_STATUS_ADDED:
189                 color_fprintf(s->fp, c, "new file:   %s", one);
190                 break;
191         case DIFF_STATUS_COPIED:
192                 color_fprintf(s->fp, c, "copied:     %s -> %s", one, two);
193                 break;
194         case DIFF_STATUS_DELETED:
195                 color_fprintf(s->fp, c, "deleted:    %s", one);
196                 break;
197         case DIFF_STATUS_MODIFIED:
198                 color_fprintf(s->fp, c, "modified:   %s", one);
199                 break;
200         case DIFF_STATUS_RENAMED:
201                 color_fprintf(s->fp, c, "renamed:    %s -> %s", one, two);
202                 break;
203         case DIFF_STATUS_TYPE_CHANGED:
204                 color_fprintf(s->fp, c, "typechange: %s", one);
205                 break;
206         case DIFF_STATUS_UNKNOWN:
207                 color_fprintf(s->fp, c, "unknown:    %s", one);
208                 break;
209         case DIFF_STATUS_UNMERGED:
210                 color_fprintf(s->fp, c, "unmerged:   %s", one);
211                 break;
212         default:
213                 die("bug: unhandled diff status %c", status);
214         }
215         if (extra.len) {
216                 color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "%s", extra.buf);
217                 strbuf_release(&extra);
218         }
219         fprintf(s->fp, "\n");
220         strbuf_release(&onebuf);
221         strbuf_release(&twobuf);
222 }
223
224 static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
225                                          struct diff_options *options,
226                                          void *data)
227 {
228         struct wt_status *s = data;
229         int i;
230
231         if (!q->nr)
232                 return;
233         s->workdir_dirty = 1;
234         for (i = 0; i < q->nr; i++) {
235                 struct diff_filepair *p;
236                 struct string_list_item *it;
237                 struct wt_status_change_data *d;
238
239                 p = q->queue[i];
240                 it = string_list_insert(&s->change, p->one->path);
241                 d = it->util;
242                 if (!d) {
243                         d = xcalloc(1, sizeof(*d));
244                         it->util = d;
245                 }
246                 if (!d->worktree_status)
247                         d->worktree_status = p->status;
248                 d->dirty_submodule = p->two->dirty_submodule;
249                 if (S_ISGITLINK(p->two->mode))
250                         d->new_submodule_commits = !!hashcmp(p->one->sha1, p->two->sha1);
251         }
252 }
253
254 static int unmerged_mask(const char *path)
255 {
256         int pos, mask;
257         struct cache_entry *ce;
258
259         pos = cache_name_pos(path, strlen(path));
260         if (0 <= pos)
261                 return 0;
262
263         mask = 0;
264         pos = -pos-1;
265         while (pos < active_nr) {
266                 ce = active_cache[pos++];
267                 if (strcmp(ce->name, path) || !ce_stage(ce))
268                         break;
269                 mask |= (1 << (ce_stage(ce) - 1));
270         }
271         return mask;
272 }
273
274 static void wt_status_collect_updated_cb(struct diff_queue_struct *q,
275                                          struct diff_options *options,
276                                          void *data)
277 {
278         struct wt_status *s = data;
279         int i;
280
281         for (i = 0; i < q->nr; i++) {
282                 struct diff_filepair *p;
283                 struct string_list_item *it;
284                 struct wt_status_change_data *d;
285
286                 p = q->queue[i];
287                 it = string_list_insert(&s->change, p->two->path);
288                 d = it->util;
289                 if (!d) {
290                         d = xcalloc(1, sizeof(*d));
291                         it->util = d;
292                 }
293                 if (!d->index_status)
294                         d->index_status = p->status;
295                 switch (p->status) {
296                 case DIFF_STATUS_COPIED:
297                 case DIFF_STATUS_RENAMED:
298                         d->head_path = xstrdup(p->one->path);
299                         break;
300                 case DIFF_STATUS_UNMERGED:
301                         d->stagemask = unmerged_mask(p->two->path);
302                         break;
303                 }
304         }
305 }
306
307 static void wt_status_collect_changes_worktree(struct wt_status *s)
308 {
309         struct rev_info rev;
310
311         init_revisions(&rev, NULL);
312         setup_revisions(0, NULL, &rev, NULL);
313         rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
314         DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES);
315         if (!s->show_untracked_files)
316                 DIFF_OPT_SET(&rev.diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
317         if (s->ignore_submodule_arg) {
318                 DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
319                 handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
320     }
321         rev.diffopt.format_callback = wt_status_collect_changed_cb;
322         rev.diffopt.format_callback_data = s;
323         rev.prune_data = s->pathspec;
324         run_diff_files(&rev, 0);
325 }
326
327 static void wt_status_collect_changes_index(struct wt_status *s)
328 {
329         struct rev_info rev;
330         struct setup_revision_opt opt;
331
332         init_revisions(&rev, NULL);
333         memset(&opt, 0, sizeof(opt));
334         opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
335         setup_revisions(0, NULL, &rev, &opt);
336
337         if (s->ignore_submodule_arg) {
338                 DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
339                 handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
340         }
341
342         rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
343         rev.diffopt.format_callback = wt_status_collect_updated_cb;
344         rev.diffopt.format_callback_data = s;
345         rev.diffopt.detect_rename = 1;
346         rev.diffopt.rename_limit = 200;
347         rev.diffopt.break_opt = 0;
348         rev.prune_data = s->pathspec;
349         run_diff_index(&rev, 1);
350 }
351
352 static void wt_status_collect_changes_initial(struct wt_status *s)
353 {
354         int i;
355
356         for (i = 0; i < active_nr; i++) {
357                 struct string_list_item *it;
358                 struct wt_status_change_data *d;
359                 struct cache_entry *ce = active_cache[i];
360
361                 if (!ce_path_match(ce, s->pathspec))
362                         continue;
363                 it = string_list_insert(&s->change, ce->name);
364                 d = it->util;
365                 if (!d) {
366                         d = xcalloc(1, sizeof(*d));
367                         it->util = d;
368                 }
369                 if (ce_stage(ce)) {
370                         d->index_status = DIFF_STATUS_UNMERGED;
371                         d->stagemask |= (1 << (ce_stage(ce) - 1));
372                 }
373                 else
374                         d->index_status = DIFF_STATUS_ADDED;
375         }
376 }
377
378 static void wt_status_collect_untracked(struct wt_status *s)
379 {
380         int i;
381         struct dir_struct dir;
382
383         if (!s->show_untracked_files)
384                 return;
385         memset(&dir, 0, sizeof(dir));
386         if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
387                 dir.flags |=
388                         DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
389         setup_standard_excludes(&dir);
390
391         fill_directory(&dir, s->pathspec);
392         for (i = 0; i < dir.nr; i++) {
393                 struct dir_entry *ent = dir.entries[i];
394                 if (cache_name_is_other(ent->name, ent->len) &&
395                     match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
396                         string_list_insert(&s->untracked, ent->name);
397                 free(ent);
398         }
399
400         if (s->show_ignored_files) {
401                 dir.nr = 0;
402                 dir.flags = DIR_SHOW_IGNORED | DIR_SHOW_OTHER_DIRECTORIES;
403                 fill_directory(&dir, s->pathspec);
404                 for (i = 0; i < dir.nr; i++) {
405                         struct dir_entry *ent = dir.entries[i];
406                         if (cache_name_is_other(ent->name, ent->len) &&
407                             match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
408                                 string_list_insert(&s->ignored, ent->name);
409                         free(ent);
410                 }
411         }
412
413         free(dir.entries);
414 }
415
416 void wt_status_collect(struct wt_status *s)
417 {
418         wt_status_collect_changes_worktree(s);
419
420         if (s->is_initial)
421                 wt_status_collect_changes_initial(s);
422         else
423                 wt_status_collect_changes_index(s);
424         wt_status_collect_untracked(s);
425 }
426
427 static void wt_status_print_unmerged(struct wt_status *s)
428 {
429         int shown_header = 0;
430         int i;
431
432         for (i = 0; i < s->change.nr; i++) {
433                 struct wt_status_change_data *d;
434                 struct string_list_item *it;
435                 it = &(s->change.items[i]);
436                 d = it->util;
437                 if (!d->stagemask)
438                         continue;
439                 if (!shown_header) {
440                         wt_status_print_unmerged_header(s);
441                         shown_header = 1;
442                 }
443                 wt_status_print_unmerged_data(s, it);
444         }
445         if (shown_header)
446                 wt_status_print_trailer(s);
447
448 }
449
450 static void wt_status_print_updated(struct wt_status *s)
451 {
452         int shown_header = 0;
453         int i;
454
455         for (i = 0; i < s->change.nr; i++) {
456                 struct wt_status_change_data *d;
457                 struct string_list_item *it;
458                 it = &(s->change.items[i]);
459                 d = it->util;
460                 if (!d->index_status ||
461                     d->index_status == DIFF_STATUS_UNMERGED)
462                         continue;
463                 if (!shown_header) {
464                         wt_status_print_cached_header(s);
465                         s->commitable = 1;
466                         shown_header = 1;
467                 }
468                 wt_status_print_change_data(s, WT_STATUS_UPDATED, it);
469         }
470         if (shown_header)
471                 wt_status_print_trailer(s);
472 }
473
474 /*
475  * -1 : has delete
476  *  0 : no change
477  *  1 : some change but no delete
478  */
479 static int wt_status_check_worktree_changes(struct wt_status *s,
480                                              int *dirty_submodules)
481 {
482         int i;
483         int changes = 0;
484
485         *dirty_submodules = 0;
486
487         for (i = 0; i < s->change.nr; i++) {
488                 struct wt_status_change_data *d;
489                 d = s->change.items[i].util;
490                 if (!d->worktree_status ||
491                     d->worktree_status == DIFF_STATUS_UNMERGED)
492                         continue;
493                 if (!changes)
494                         changes = 1;
495                 if (d->dirty_submodule)
496                         *dirty_submodules = 1;
497                 if (d->worktree_status == DIFF_STATUS_DELETED)
498                         changes = -1;
499         }
500         return changes;
501 }
502
503 static void wt_status_print_changed(struct wt_status *s)
504 {
505         int i, dirty_submodules;
506         int worktree_changes = wt_status_check_worktree_changes(s, &dirty_submodules);
507
508         if (!worktree_changes)
509                 return;
510
511         wt_status_print_dirty_header(s, worktree_changes < 0, dirty_submodules);
512
513         for (i = 0; i < s->change.nr; i++) {
514                 struct wt_status_change_data *d;
515                 struct string_list_item *it;
516                 it = &(s->change.items[i]);
517                 d = it->util;
518                 if (!d->worktree_status ||
519                     d->worktree_status == DIFF_STATUS_UNMERGED)
520                         continue;
521                 wt_status_print_change_data(s, WT_STATUS_CHANGED, it);
522         }
523         wt_status_print_trailer(s);
524 }
525
526 static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitted)
527 {
528         struct child_process sm_summary;
529         char summary_limit[64];
530         char index[PATH_MAX];
531         const char *env[] = { NULL, NULL };
532         const char *argv[8];
533
534         env[0] =        index;
535         argv[0] =       "submodule";
536         argv[1] =       "summary";
537         argv[2] =       uncommitted ? "--files" : "--cached";
538         argv[3] =       "--for-status";
539         argv[4] =       "--summary-limit";
540         argv[5] =       summary_limit;
541         argv[6] =       uncommitted ? NULL : (s->amend ? "HEAD^" : "HEAD");
542         argv[7] =       NULL;
543
544         sprintf(summary_limit, "%d", s->submodule_summary);
545         snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", s->index_file);
546
547         memset(&sm_summary, 0, sizeof(sm_summary));
548         sm_summary.argv = argv;
549         sm_summary.env = env;
550         sm_summary.git_cmd = 1;
551         sm_summary.no_stdin = 1;
552         fflush(s->fp);
553         sm_summary.out = dup(fileno(s->fp));    /* run_command closes it */
554         run_command(&sm_summary);
555 }
556
557 static void wt_status_print_other(struct wt_status *s,
558                                   struct string_list *l,
559                                   const char *what,
560                                   const char *how)
561 {
562         int i;
563         struct strbuf buf = STRBUF_INIT;
564
565         if (!s->untracked.nr)
566                 return;
567
568         wt_status_print_other_header(s, what, how);
569
570         for (i = 0; i < l->nr; i++) {
571                 struct string_list_item *it;
572                 it = &(l->items[i]);
573                 color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
574                 color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED, s), "%s",
575                                  quote_path(it->string, strlen(it->string),
576                                             &buf, s->prefix));
577         }
578         strbuf_release(&buf);
579 }
580
581 static void wt_status_print_verbose(struct wt_status *s)
582 {
583         struct rev_info rev;
584         struct setup_revision_opt opt;
585
586         init_revisions(&rev, NULL);
587         DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
588
589         memset(&opt, 0, sizeof(opt));
590         opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
591         setup_revisions(0, NULL, &rev, &opt);
592
593         rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
594         rev.diffopt.detect_rename = 1;
595         rev.diffopt.file = s->fp;
596         rev.diffopt.close_file = 0;
597         /*
598          * If we're not going to stdout, then we definitely don't
599          * want color, since we are going to the commit message
600          * file (and even the "auto" setting won't work, since it
601          * will have checked isatty on stdout).
602          */
603         if (s->fp != stdout)
604                 DIFF_OPT_CLR(&rev.diffopt, COLOR_DIFF);
605         run_diff_index(&rev, 1);
606 }
607
608 static void wt_status_print_tracking(struct wt_status *s)
609 {
610         struct strbuf sb = STRBUF_INIT;
611         const char *cp, *ep;
612         struct branch *branch;
613
614         assert(s->branch && !s->is_initial);
615         if (prefixcmp(s->branch, "refs/heads/"))
616                 return;
617         branch = branch_get(s->branch + 11);
618         if (!format_tracking_info(branch, &sb))
619                 return;
620
621         for (cp = sb.buf; (ep = strchr(cp, '\n')) != NULL; cp = ep + 1)
622                 color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s),
623                                  "# %.*s", (int)(ep - cp), cp);
624         color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
625 }
626
627 void wt_status_print(struct wt_status *s)
628 {
629         const char *branch_color = color(WT_STATUS_ONBRANCH, s);
630         const char *branch_status_color = color(WT_STATUS_HEADER, s);
631
632         if (s->branch) {
633                 const char *on_what = "On branch ";
634                 const char *branch_name = s->branch;
635                 if (!prefixcmp(branch_name, "refs/heads/"))
636                         branch_name += 11;
637                 else if (!strcmp(branch_name, "HEAD")) {
638                         branch_name = "";
639                         branch_status_color = color(WT_STATUS_NOBRANCH, s);
640                         on_what = "Not currently on any branch.";
641                 }
642                 color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "# ");
643                 color_fprintf(s->fp, branch_status_color, "%s", on_what);
644                 color_fprintf_ln(s->fp, branch_color, "%s", branch_name);
645                 if (!s->is_initial)
646                         wt_status_print_tracking(s);
647         }
648
649         if (s->is_initial) {
650                 color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
651                 color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# Initial commit");
652                 color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
653         }
654
655         wt_status_print_updated(s);
656         wt_status_print_unmerged(s);
657         wt_status_print_changed(s);
658         if (s->submodule_summary &&
659             (!s->ignore_submodule_arg ||
660              strcmp(s->ignore_submodule_arg, "all"))) {
661                 wt_status_print_submodule_summary(s, 0);  /* staged */
662                 wt_status_print_submodule_summary(s, 1);  /* unstaged */
663         }
664         if (s->show_untracked_files) {
665                 wt_status_print_other(s, &s->untracked, "Untracked", "add");
666                 if (s->show_ignored_files)
667                         wt_status_print_other(s, &s->ignored, "Ignored", "add -f");
668         } else if (s->commitable)
669                 fprintf(s->fp, "# Untracked files not listed%s\n",
670                         advice_status_hints
671                         ? " (use -u option to show untracked files)" : "");
672
673         if (s->verbose)
674                 wt_status_print_verbose(s);
675         if (!s->commitable) {
676                 if (s->amend)
677                         fprintf(s->fp, "# No changes\n");
678                 else if (s->nowarn)
679                         ; /* nothing */
680                 else if (s->workdir_dirty)
681                         printf("no changes added to commit%s\n",
682                                 advice_status_hints
683                                 ? " (use \"git add\" and/or \"git commit -a\")" : "");
684                 else if (s->untracked.nr)
685                         printf("nothing added to commit but untracked files present%s\n",
686                                 advice_status_hints
687                                 ? " (use \"git add\" to track)" : "");
688                 else if (s->is_initial)
689                         printf("nothing to commit%s\n", advice_status_hints
690                                 ? " (create/copy files and use \"git add\" to track)" : "");
691                 else if (!s->show_untracked_files)
692                         printf("nothing to commit%s\n", advice_status_hints
693                                 ? " (use -u to show untracked files)" : "");
694                 else
695                         printf("nothing to commit%s\n", advice_status_hints
696                                 ? " (working directory clean)" : "");
697         }
698 }
699
700 static void wt_shortstatus_unmerged(int null_termination, struct string_list_item *it,
701                            struct wt_status *s)
702 {
703         struct wt_status_change_data *d = it->util;
704         const char *how = "??";
705
706         switch (d->stagemask) {
707         case 1: how = "DD"; break; /* both deleted */
708         case 2: how = "AU"; break; /* added by us */
709         case 3: how = "UD"; break; /* deleted by them */
710         case 4: how = "UA"; break; /* added by them */
711         case 5: how = "DU"; break; /* deleted by us */
712         case 6: how = "AA"; break; /* both added */
713         case 7: how = "UU"; break; /* both modified */
714         }
715         color_fprintf(s->fp, color(WT_STATUS_UNMERGED, s), "%s", how);
716         if (null_termination) {
717                 fprintf(stdout, " %s%c", it->string, 0);
718         } else {
719                 struct strbuf onebuf = STRBUF_INIT;
720                 const char *one;
721                 one = quote_path(it->string, -1, &onebuf, s->prefix);
722                 printf(" %s\n", one);
723                 strbuf_release(&onebuf);
724         }
725 }
726
727 static void wt_shortstatus_status(int null_termination, struct string_list_item *it,
728                          struct wt_status *s)
729 {
730         struct wt_status_change_data *d = it->util;
731
732         if (d->index_status)
733                 color_fprintf(s->fp, color(WT_STATUS_UPDATED, s), "%c", d->index_status);
734         else
735                 putchar(' ');
736         if (d->worktree_status)
737                 color_fprintf(s->fp, color(WT_STATUS_CHANGED, s), "%c", d->worktree_status);
738         else
739                 putchar(' ');
740         putchar(' ');
741         if (null_termination) {
742                 fprintf(stdout, "%s%c", it->string, 0);
743                 if (d->head_path)
744                         fprintf(stdout, "%s%c", d->head_path, 0);
745         } else {
746                 struct strbuf onebuf = STRBUF_INIT;
747                 const char *one;
748                 if (d->head_path) {
749                         one = quote_path(d->head_path, -1, &onebuf, s->prefix);
750                         printf("%s -> ", one);
751                         strbuf_release(&onebuf);
752                 }
753                 one = quote_path(it->string, -1, &onebuf, s->prefix);
754                 printf("%s\n", one);
755                 strbuf_release(&onebuf);
756         }
757 }
758
759 static void wt_shortstatus_other(int null_termination, struct string_list_item *it,
760                                  struct wt_status *s, const char *sign)
761 {
762         if (null_termination) {
763                 fprintf(stdout, "%s %s%c", sign, it->string, 0);
764         } else {
765                 struct strbuf onebuf = STRBUF_INIT;
766                 const char *one;
767                 one = quote_path(it->string, -1, &onebuf, s->prefix);
768                 color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign);
769                 printf(" %s\n", one);
770                 strbuf_release(&onebuf);
771         }
772 }
773
774 static void wt_shortstatus_print_tracking(struct wt_status *s)
775 {
776         struct branch *branch;
777         const char *header_color = color(WT_STATUS_HEADER, s);
778         const char *branch_color_local = color(WT_STATUS_LOCAL_BRANCH, s);
779         const char *branch_color_remote = color(WT_STATUS_REMOTE_BRANCH, s);
780
781         const char *base;
782         const char *branch_name;
783         int num_ours, num_theirs;
784
785         color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "## ");
786
787         if (!s->branch)
788                 return;
789         branch_name = s->branch;
790
791         if (!prefixcmp(branch_name, "refs/heads/"))
792                 branch_name += 11;
793         else if (!strcmp(branch_name, "HEAD")) {
794                 branch_name = "HEAD (no branch)";
795                 branch_color_local = color(WT_STATUS_NOBRANCH, s);
796         }
797
798         branch = branch_get(s->branch + 11);
799         if (s->is_initial)
800                 color_fprintf(s->fp, header_color, "Initial commit on ");
801         if (!stat_tracking_info(branch, &num_ours, &num_theirs)) {
802                 color_fprintf_ln(s->fp, branch_color_local,
803                         "%s", branch_name);
804                 return;
805         }
806
807         base = branch->merge[0]->dst;
808         base = shorten_unambiguous_ref(base, 0);
809         color_fprintf(s->fp, branch_color_local, "%s", branch_name);
810         color_fprintf(s->fp, header_color, "...");
811         color_fprintf(s->fp, branch_color_remote, "%s", base);
812
813         color_fprintf(s->fp, header_color, " [");
814         if (!num_ours) {
815                 color_fprintf(s->fp, header_color, "behind ");
816                 color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
817         } else if (!num_theirs) {
818                 color_fprintf(s->fp, header_color, "ahead ");
819                 color_fprintf(s->fp, branch_color_local, "%d", num_ours);
820         } else {
821                 color_fprintf(s->fp, header_color, "ahead ");
822                 color_fprintf(s->fp, branch_color_local, "%d", num_ours);
823                 color_fprintf(s->fp, header_color, ", behind ");
824                 color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
825         }
826
827         color_fprintf_ln(s->fp, header_color, "]");
828 }
829
830 void wt_shortstatus_print(struct wt_status *s, int null_termination, int show_branch)
831 {
832         int i;
833
834         if (show_branch)
835                 wt_shortstatus_print_tracking(s);
836
837         for (i = 0; i < s->change.nr; i++) {
838                 struct wt_status_change_data *d;
839                 struct string_list_item *it;
840
841                 it = &(s->change.items[i]);
842                 d = it->util;
843                 if (d->stagemask)
844                         wt_shortstatus_unmerged(null_termination, it, s);
845                 else
846                         wt_shortstatus_status(null_termination, it, s);
847         }
848         for (i = 0; i < s->untracked.nr; i++) {
849                 struct string_list_item *it;
850
851                 it = &(s->untracked.items[i]);
852                 wt_shortstatus_other(null_termination, it, s, "??");
853         }
854         for (i = 0; i < s->ignored.nr; i++) {
855                 struct string_list_item *it;
856
857                 it = &(s->ignored.items[i]);
858                 wt_shortstatus_other(null_termination, it, s, "!!");
859         }
860 }
861
862 void wt_porcelain_print(struct wt_status *s, int null_termination)
863 {
864         s->use_color = 0;
865         s->relative_paths = 0;
866         s->prefix = NULL;
867         wt_shortstatus_print(s, null_termination, 0);
868 }