Code

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