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