Merge branch 'jk/merge-rename-ux'
[git/jnareb-git.git] / wt-status.c
blob4daa8bb5242fc689973839a13a3a28477efbb6bf
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 */
27 static const char *color(int slot, struct wt_status *s)
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;
35 void wt_status_prepare(struct wt_status *s)
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;
56 static void wt_status_print_unmerged_header(struct wt_status *s)
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)
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, "#");
73 static void wt_status_print_cached_header(struct wt_status *s)
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, "#");
89 static void wt_status_print_dirty_header(struct wt_status *s,
90 int has_deleted,
91 int has_dirty_submodules)
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;
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, ')');
182 status = d->worktree_status;
183 break;
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);
218 if (extra.len) {
219 color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "%s", extra.buf);
220 strbuf_release(&extra);
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;
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);
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));
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;
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;
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);
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);
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;
374 if (ce_stage(ce)) {
375 d->index_status = DIFF_STATUS_UNMERGED;
376 d->stagemask |= (1 << (ce_stage(ce) - 1));
378 else
379 d->index_status = DIFF_STATUS_ADDED;
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);
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);
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;
449 wt_status_print_unmerged_data(s, it);
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;
474 wt_status_print_change_data(s, WT_STATUS_UPDATED, it);
476 if (shown_header)
477 wt_status_print_trailer(s);
481 * -1 : has delete
482 * 0 : no change
483 * 1 : some change but no delete
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;
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);
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));
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;
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).
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.";
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);
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), "#");
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 */
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)" : "");
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 */
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);
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;
761 printf("%s -> ", one);
762 strbuf_release(&onebuf);
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;
770 printf("%s\n", one);
771 strbuf_release(&onebuf);
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);
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);
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;
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);
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);
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, "??");
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, "!!");
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);