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