git-remote-hg: improve sanitation of local repo urls
[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                         continue;
395                 if (!match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
396                         continue;
397                 string_list_insert(&s->untracked, ent->name);
398                 free(ent);
399         }
400
401         if (s->show_ignored_files) {
402                 dir.nr = 0;
403                 dir.flags = DIR_SHOW_IGNORED | DIR_SHOW_OTHER_DIRECTORIES;
404                 fill_directory(&dir, s->pathspec);
405                 for (i = 0; i < dir.nr; i++) {
406                         struct dir_entry *ent = dir.entries[i];
407                         if (!cache_name_is_other(ent->name, ent->len))
408                                 continue;
409                         if (!match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
410                                 continue;
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_HEADER, s);
633
634         if (s->branch) {
635                 const char *on_what = "On branch ";
636                 const char *branch_name = s->branch;
637                 if (!prefixcmp(branch_name, "refs/heads/"))
638                         branch_name += 11;
639                 else if (!strcmp(branch_name, "HEAD")) {
640                         branch_name = "";
641                         branch_color = color(WT_STATUS_NOBRANCH, s);
642                         on_what = "Not currently on any branch.";
643                 }
644                 color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "# ");
645                 color_fprintf_ln(s->fp, branch_color, "%s%s", on_what, branch_name);
646                 if (!s->is_initial)
647                         wt_status_print_tracking(s);
648         }
649
650         if (s->is_initial) {
651                 color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
652                 color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# Initial commit");
653                 color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
654         }
655
656         wt_status_print_updated(s);
657         wt_status_print_unmerged(s);
658         wt_status_print_changed(s);
659         if (s->submodule_summary &&
660             (!s->ignore_submodule_arg ||
661              strcmp(s->ignore_submodule_arg, "all"))) {
662                 wt_status_print_submodule_summary(s, 0);  /* staged */
663                 wt_status_print_submodule_summary(s, 1);  /* unstaged */
664         }
665         if (s->show_untracked_files) {
666                 wt_status_print_other(s, &s->untracked, "Untracked", "add");
667                 if (s->show_ignored_files)
668                         wt_status_print_other(s, &s->ignored, "Ignored", "add -f");
669         } else if (s->commitable)
670                 fprintf(s->fp, "# Untracked files not listed%s\n",
671                         advice_status_hints
672                         ? " (use -u option to show untracked files)" : "");
673
674         if (s->verbose)
675                 wt_status_print_verbose(s);
676         if (!s->commitable) {
677                 if (s->amend)
678                         fprintf(s->fp, "# No changes\n");
679                 else if (s->nowarn)
680                         ; /* nothing */
681                 else if (s->workdir_dirty)
682                         printf("no changes added to commit%s\n",
683                                 advice_status_hints
684                                 ? " (use \"git add\" and/or \"git commit -a\")" : "");
685                 else if (s->untracked.nr)
686                         printf("nothing added to commit but untracked files present%s\n",
687                                 advice_status_hints
688                                 ? " (use \"git add\" to track)" : "");
689                 else if (s->is_initial)
690                         printf("nothing to commit%s\n", advice_status_hints
691                                 ? " (create/copy files and use \"git add\" to track)" : "");
692                 else if (!s->show_untracked_files)
693                         printf("nothing to commit%s\n", advice_status_hints
694                                 ? " (use -u to show untracked files)" : "");
695                 else
696                         printf("nothing to commit%s\n", advice_status_hints
697                                 ? " (working directory clean)" : "");
698         }
699 }
700
701 static void wt_shortstatus_unmerged(int null_termination, struct string_list_item *it,
702                            struct wt_status *s)
703 {
704         struct wt_status_change_data *d = it->util;
705         const char *how = "??";
706
707         switch (d->stagemask) {
708         case 1: how = "DD"; break; /* both deleted */
709         case 2: how = "AU"; break; /* added by us */
710         case 3: how = "UD"; break; /* deleted by them */
711         case 4: how = "UA"; break; /* added by them */
712         case 5: how = "DU"; break; /* deleted by us */
713         case 6: how = "AA"; break; /* both added */
714         case 7: how = "UU"; break; /* both modified */
715         }
716         color_fprintf(s->fp, color(WT_STATUS_UNMERGED, s), "%s", how);
717         if (null_termination) {
718                 fprintf(stdout, " %s%c", it->string, 0);
719         } else {
720                 struct strbuf onebuf = STRBUF_INIT;
721                 const char *one;
722                 one = quote_path(it->string, -1, &onebuf, s->prefix);
723                 printf(" %s\n", one);
724                 strbuf_release(&onebuf);
725         }
726 }
727
728 static void wt_shortstatus_status(int null_termination, struct string_list_item *it,
729                          struct wt_status *s)
730 {
731         struct wt_status_change_data *d = it->util;
732
733         if (d->index_status)
734                 color_fprintf(s->fp, color(WT_STATUS_UPDATED, s), "%c", d->index_status);
735         else
736                 putchar(' ');
737         if (d->worktree_status)
738                 color_fprintf(s->fp, color(WT_STATUS_CHANGED, s), "%c", d->worktree_status);
739         else
740                 putchar(' ');
741         putchar(' ');
742         if (null_termination) {
743                 fprintf(stdout, "%s%c", it->string, 0);
744                 if (d->head_path)
745                         fprintf(stdout, "%s%c", d->head_path, 0);
746         } else {
747                 struct strbuf onebuf = STRBUF_INIT;
748                 const char *one;
749                 if (d->head_path) {
750                         one = quote_path(d->head_path, -1, &onebuf, s->prefix);
751                         printf("%s -> ", one);
752                         strbuf_release(&onebuf);
753                 }
754                 one = quote_path(it->string, -1, &onebuf, s->prefix);
755                 printf("%s\n", one);
756                 strbuf_release(&onebuf);
757         }
758 }
759
760 static void wt_shortstatus_other(int null_termination, struct string_list_item *it,
761                                  struct wt_status *s, const char *sign)
762 {
763         if (null_termination) {
764                 fprintf(stdout, "%s %s%c", sign, it->string, 0);
765         } else {
766                 struct strbuf onebuf = STRBUF_INIT;
767                 const char *one;
768                 one = quote_path(it->string, -1, &onebuf, s->prefix);
769                 color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign);
770                 printf(" %s\n", one);
771                 strbuf_release(&onebuf);
772         }
773 }
774
775 static void wt_shortstatus_print_tracking(struct wt_status *s)
776 {
777         struct branch *branch;
778         const char *header_color = color(WT_STATUS_HEADER, s);
779         const char *branch_color_local = color(WT_STATUS_LOCAL_BRANCH, s);
780         const char *branch_color_remote = color(WT_STATUS_REMOTE_BRANCH, s);
781
782         const char *base;
783         const char *branch_name;
784         int num_ours, num_theirs;
785
786         color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "## ");
787
788         if (!s->branch)
789                 return;
790         branch_name = s->branch;
791
792         if (!prefixcmp(branch_name, "refs/heads/"))
793                 branch_name += 11;
794         else if (!strcmp(branch_name, "HEAD")) {
795                 branch_name = "HEAD (no branch)";
796                 branch_color_local = color(WT_STATUS_NOBRANCH, s);
797         }
798
799         branch = branch_get(s->branch + 11);
800         if (s->is_initial)
801                 color_fprintf(s->fp, header_color, "Initial commit on ");
802         if (!stat_tracking_info(branch, &num_ours, &num_theirs)) {
803                 color_fprintf_ln(s->fp, branch_color_local,
804                         "%s", branch_name);
805                 return;
806         }
807
808         base = branch->merge[0]->dst;
809         base = shorten_unambiguous_ref(base, 0);
810         color_fprintf(s->fp, branch_color_local, "%s", branch_name);
811         color_fprintf(s->fp, header_color, "...");
812         color_fprintf(s->fp, branch_color_remote, "%s", base);
813
814         color_fprintf(s->fp, header_color, " [");
815         if (!num_ours) {
816                 color_fprintf(s->fp, header_color, "behind ");
817                 color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
818         } else if (!num_theirs) {
819                 color_fprintf(s->fp, header_color, "ahead ");
820                 color_fprintf(s->fp, branch_color_local, "%d", num_ours);
821         } else {
822                 color_fprintf(s->fp, header_color, "ahead ");
823                 color_fprintf(s->fp, branch_color_local, "%d", num_ours);
824                 color_fprintf(s->fp, header_color, ", behind ");
825                 color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
826         }
827
828         color_fprintf_ln(s->fp, header_color, "]");
829 }
830
831 void wt_shortstatus_print(struct wt_status *s, int null_termination, int show_branch)
832 {
833         int i;
834
835         if (show_branch)
836                 wt_shortstatus_print_tracking(s);
837
838         for (i = 0; i < s->change.nr; i++) {
839                 struct wt_status_change_data *d;
840                 struct string_list_item *it;
841
842                 it = &(s->change.items[i]);
843                 d = it->util;
844                 if (d->stagemask)
845                         wt_shortstatus_unmerged(null_termination, it, s);
846                 else
847                         wt_shortstatus_status(null_termination, it, s);
848         }
849         for (i = 0; i < s->untracked.nr; i++) {
850                 struct string_list_item *it;
851
852                 it = &(s->untracked.items[i]);
853                 wt_shortstatus_other(null_termination, it, s, "??");
854         }
855         for (i = 0; i < s->ignored.nr; i++) {
856                 struct string_list_item *it;
857
858                 it = &(s->ignored.items[i]);
859                 wt_shortstatus_other(null_termination, it, s, "!!");
860         }
861 }
862
863 void wt_porcelain_print(struct wt_status *s, int null_termination)
864 {
865         s->use_color = 0;
866         s->relative_paths = 0;
867         s->prefix = NULL;
868         wt_shortstatus_print(s, null_termination, 0);
869 }