Code

Merge branch 'so/http-user-agent'
[git.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"
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 };
26 static const char *color(int slot, struct wt_status *s)
27 {
28         return s->use_color > 0 ? s->color_palette[slot] : "";
29 }
31 void wt_status_prepare(struct wt_status *s)
32 {
33         unsigned char sha1[20];
34         const char *head;
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 }
52 static void wt_status_print_unmerged_header(struct wt_status *s)
53 {
54         const char *c = color(WT_STATUS_HEADER, s);
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 }
69 static void wt_status_print_cached_header(struct wt_status *s)
70 {
71         const char *c = color(WT_STATUS_HEADER, s);
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 }
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);
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, "#");
104 static void wt_status_print_other_header(struct wt_status *s,
105                                          const char *what,
106                                          const char *how)
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, "#");
116 static void wt_status_print_trailer(struct wt_status *s)
118         color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
121 #define quote_path quote_path_relative
123 static void wt_status_print_unmerged_data(struct wt_status *s,
124                                           struct string_list_item *it)
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";
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);
146 static void wt_status_print_change_data(struct wt_status *s,
147                                         int change_type,
148                                         struct string_list_item *it)
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;
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         }
182         one = quote_path(one_name, -1, &onebuf, s->prefix);
183         two = quote_path(two_name, -1, &twobuf, s->prefix);
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);
223 static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
224                                          struct diff_options *options,
225                                          void *data)
227         struct wt_status *s = data;
228         int i;
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;
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         }
253 static int unmerged_mask(const char *path)
255         int pos, mask;
256         struct cache_entry *ce;
258         pos = cache_name_pos(path, strlen(path));
259         if (0 <= pos)
260                 return 0;
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;
273 static void wt_status_collect_updated_cb(struct diff_queue_struct *q,
274                                          struct diff_options *options,
275                                          void *data)
277         struct wt_status *s = data;
278         int i;
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;
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         }
306 static void wt_status_collect_changes_worktree(struct wt_status *s)
308         struct rev_info rev;
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);
326 static void wt_status_collect_changes_index(struct wt_status *s)
328         struct rev_info rev;
329         struct setup_revision_opt opt;
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);
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         }
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);
351 static void wt_status_collect_changes_initial(struct wt_status *s)
353         int i;
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];
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         }
377 static void wt_status_collect_untracked(struct wt_status *s)
379         int i;
380         struct dir_struct dir;
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);
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         }
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         }
416         free(dir.entries);
419 void wt_status_collect(struct wt_status *s)
421         wt_status_collect_changes_worktree(s);
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);
430 static void wt_status_print_unmerged(struct wt_status *s)
432         int shown_header = 0;
433         int i;
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);
453 static void wt_status_print_updated(struct wt_status *s)
455         int shown_header = 0;
456         int i;
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);
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)
485         int i;
486         int changes = 0;
488         *dirty_submodules = 0;
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;
506 static void wt_status_print_changed(struct wt_status *s)
508         int i, dirty_submodules;
509         int worktree_changes = wt_status_check_worktree_changes(s, &dirty_submodules);
511         if (!worktree_changes)
512                 return;
514         wt_status_print_dirty_header(s, worktree_changes < 0, dirty_submodules);
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);
529 static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitted)
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];
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;
547         sprintf(summary_limit, "%d", s->submodule_summary);
548         snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", s->index_file);
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);
560 static void wt_status_print_other(struct wt_status *s,
561                                   struct string_list *l,
562                                   const char *what,
563                                   const char *how)
565         int i;
566         struct strbuf buf = STRBUF_INIT;
568         if (!s->untracked.nr)
569                 return;
571         wt_status_print_other_header(s, what, how);
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);
584 static void wt_status_print_verbose(struct wt_status *s)
586         struct rev_info rev;
587         struct setup_revision_opt opt;
589         init_revisions(&rev, NULL);
590         DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
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);
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);
611 static void wt_status_print_tracking(struct wt_status *s)
613         struct strbuf sb = STRBUF_INIT;
614         const char *cp, *ep;
615         struct branch *branch;
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;
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), "#");
630 void wt_status_print(struct wt_status *s)
632         const char *branch_color = color(WT_STATUS_HEADER, s);
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         }
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         }
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)" : "");
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         }
701 static void wt_shortstatus_unmerged(int null_termination, struct string_list_item *it,
702                            struct wt_status *s)
704         struct wt_status_change_data *d = it->util;
705         const char *how = "??";
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         }
728 static void wt_shortstatus_status(int null_termination, struct string_list_item *it,
729                          struct wt_status *s)
731         struct wt_status_change_data *d = it->util;
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         }
760 static void wt_shortstatus_other(int null_termination, struct string_list_item *it,
761                                  struct wt_status *s, const char *sign)
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         }
775 static void wt_shortstatus_print_tracking(struct wt_status *s)
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);
782         const char *base;
783         const char *branch_name;
784         int num_ours, num_theirs;
786         color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "## ");
788         if (!s->branch)
789                 return;
790         branch_name = s->branch;
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         }
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         }
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);
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         }
828         color_fprintf_ln(s->fp, header_color, "]");
831 void wt_shortstatus_print(struct wt_status *s, int null_termination, int show_branch)
833         int i;
835         if (show_branch)
836                 wt_shortstatus_print_tracking(s);
838         for (i = 0; i < s->change.nr; i++) {
839                 struct wt_status_change_data *d;
840                 struct string_list_item *it;
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;
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;
858                 it = &(s->ignored.items[i]);
859                 wt_shortstatus_other(null_termination, it, s, "!!");
860         }
863 void wt_porcelain_print(struct wt_status *s, int null_termination)
865         s->use_color = 0;
866         s->relative_paths = 0;
867         s->prefix = NULL;
868         wt_shortstatus_print(s, null_termination, 0);