git-bisect.txt: clarify that reset quits bisect
[git.git] / wt-status.c
index dd6d8c41068e6664cff1c07100e482e7ca9f0959..c110cbc1250b08fe491d60244e1c8f27c0201532 100644 (file)
@@ -12,6 +12,7 @@
 #include "refs.h"
 #include "submodule.h"
 #include "column.h"
+#include "strbuf.h"
 
 static char default_wt_status_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
@@ -130,9 +131,34 @@ void wt_status_prepare(struct wt_status *s)
 
 static void wt_status_print_unmerged_header(struct wt_status *s)
 {
+       int i;
+       int del_mod_conflict = 0;
+       int both_deleted = 0;
+       int not_deleted = 0;
        const char *c = color(WT_STATUS_HEADER, s);
 
        status_printf_ln(s, c, _("Unmerged paths:"));
+
+       for (i = 0; i < s->change.nr; i++) {
+               struct string_list_item *it = &(s->change.items[i]);
+               struct wt_status_change_data *d = it->util;
+
+               switch (d->stagemask) {
+               case 0:
+                       break;
+               case 1:
+                       both_deleted = 1;
+                       break;
+               case 3:
+               case 5:
+                       del_mod_conflict = 1;
+                       break;
+               default:
+                       not_deleted = 1;
+                       break;
+               }
+       }
+
        if (!advice_status_hints)
                return;
        if (s->whence != FROM_COMMIT)
@@ -141,7 +167,17 @@ static void wt_status_print_unmerged_header(struct wt_status *s)
                status_printf_ln(s, c, _("  (use \"git reset %s <file>...\" to unstage)"), s->reference);
        else
                status_printf_ln(s, c, _("  (use \"git rm --cached <file>...\" to unstage)"));
-       status_printf_ln(s, c, _("  (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
+
+       if (!both_deleted) {
+               if (!del_mod_conflict)
+                       status_printf_ln(s, c, _("  (use \"git add <file>...\" to mark resolution)"));
+               else
+                       status_printf_ln(s, c, _("  (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
+       } else if (!del_mod_conflict && !not_deleted) {
+               status_printf_ln(s, c, _("  (use \"git rm <file>...\" to mark resolution)"));
+       } else {
+               status_printf_ln(s, c, _("  (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
+       }
        status_printf_ln(s, c, "");
 }
 
@@ -728,6 +764,211 @@ static void wt_status_print_tracking(struct wt_status *s)
        color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
 }
 
+static int has_unmerged(struct wt_status *s)
+{
+       int i;
+
+       for (i = 0; i < s->change.nr; i++) {
+               struct wt_status_change_data *d;
+               d = s->change.items[i].util;
+               if (d->stagemask)
+                       return 1;
+       }
+       return 0;
+}
+
+static void show_merge_in_progress(struct wt_status *s,
+                               struct wt_status_state *state,
+                               const char *color)
+{
+       if (has_unmerged(s)) {
+               status_printf_ln(s, color, _("You have unmerged paths."));
+               if (advice_status_hints)
+                       status_printf_ln(s, color,
+                               _("  (fix conflicts and run \"git commit\")"));
+       } else {
+               status_printf_ln(s, color,
+                       _("All conflicts fixed but you are still merging."));
+               if (advice_status_hints)
+                       status_printf_ln(s, color,
+                               _("  (use \"git commit\" to conclude merge)"));
+       }
+       wt_status_print_trailer(s);
+}
+
+static void show_am_in_progress(struct wt_status *s,
+                               struct wt_status_state *state,
+                               const char *color)
+{
+       status_printf_ln(s, color,
+               _("You are in the middle of an am session."));
+       if (state->am_empty_patch)
+               status_printf_ln(s, color,
+                       _("The current patch is empty."));
+       if (advice_status_hints) {
+               if (!state->am_empty_patch)
+                       status_printf_ln(s, color,
+                               _("  (fix conflicts and then run \"git am --resolved\")"));
+               status_printf_ln(s, color,
+                       _("  (use \"git am --skip\" to skip this patch)"));
+               status_printf_ln(s, color,
+                       _("  (use \"git am --abort\" to restore the original branch)"));
+       }
+       wt_status_print_trailer(s);
+}
+
+static char *read_line_from_git_path(const char *filename)
+{
+       struct strbuf buf = STRBUF_INIT;
+       FILE *fp = fopen(git_path("%s", filename), "r");
+       if (!fp) {
+               strbuf_release(&buf);
+               return NULL;
+       }
+       strbuf_getline(&buf, fp, '\n');
+       if (!fclose(fp)) {
+               return strbuf_detach(&buf, NULL);
+       } else {
+               strbuf_release(&buf);
+               return NULL;
+       }
+}
+
+static int split_commit_in_progress(struct wt_status *s)
+{
+       int split_in_progress = 0;
+       char *head = read_line_from_git_path("HEAD");
+       char *orig_head = read_line_from_git_path("ORIG_HEAD");
+       char *rebase_amend = read_line_from_git_path("rebase-merge/amend");
+       char *rebase_orig_head = read_line_from_git_path("rebase-merge/orig-head");
+
+       if (!head || !orig_head || !rebase_amend || !rebase_orig_head ||
+           !s->branch || strcmp(s->branch, "HEAD"))
+               return split_in_progress;
+
+       if (!strcmp(rebase_amend, rebase_orig_head)) {
+               if (strcmp(head, rebase_amend))
+                       split_in_progress = 1;
+       } else if (strcmp(orig_head, rebase_orig_head)) {
+               split_in_progress = 1;
+       }
+
+       if (!s->amend && !s->nowarn && !s->workdir_dirty)
+               split_in_progress = 0;
+
+       free(head);
+       free(orig_head);
+       free(rebase_amend);
+       free(rebase_orig_head);
+       return split_in_progress;
+}
+
+static void show_rebase_in_progress(struct wt_status *s,
+                               struct wt_status_state *state,
+                               const char *color)
+{
+       struct stat st;
+
+       if (has_unmerged(s)) {
+               status_printf_ln(s, color, _("You are currently rebasing."));
+               if (advice_status_hints) {
+                       status_printf_ln(s, color,
+                               _("  (fix conflicts and then run \"git rebase --continue\")"));
+                       status_printf_ln(s, color,
+                               _("  (use \"git rebase --skip\" to skip this patch)"));
+                       status_printf_ln(s, color,
+                               _("  (use \"git rebase --abort\" to check out the original branch)"));
+               }
+       } else if (state->rebase_in_progress || !stat(git_path("MERGE_MSG"), &st)) {
+               status_printf_ln(s, color, _("You are currently rebasing."));
+               if (advice_status_hints)
+                       status_printf_ln(s, color,
+                               _("  (all conflicts fixed: run \"git rebase --continue\")"));
+       } else if (split_commit_in_progress(s)) {
+               status_printf_ln(s, color, _("You are currently splitting a commit during a rebase."));
+               if (advice_status_hints)
+                       status_printf_ln(s, color,
+                               _("  (Once your working directory is clean, run \"git rebase --continue\")"));
+       } else {
+               status_printf_ln(s, color, _("You are currently editing a commit during a rebase."));
+               if (advice_status_hints && !s->amend) {
+                       status_printf_ln(s, color,
+                               _("  (use \"git commit --amend\" to amend the current commit)"));
+                       status_printf_ln(s, color,
+                               _("  (use \"git rebase --continue\" once you are satisfied with your changes)"));
+               }
+       }
+       wt_status_print_trailer(s);
+}
+
+static void show_cherry_pick_in_progress(struct wt_status *s,
+                                       struct wt_status_state *state,
+                                       const char *color)
+{
+       status_printf_ln(s, color, _("You are currently cherry-picking."));
+       if (advice_status_hints) {
+               if (has_unmerged(s))
+                       status_printf_ln(s, color,
+                               _("  (fix conflicts and run \"git commit\")"));
+               else
+                       status_printf_ln(s, color,
+                               _("  (all conflicts fixed: run \"git commit\")"));
+       }
+       wt_status_print_trailer(s);
+}
+
+static void show_bisect_in_progress(struct wt_status *s,
+                               struct wt_status_state *state,
+                               const char *color)
+{
+       status_printf_ln(s, color, _("You are currently bisecting."));
+       if (advice_status_hints)
+               status_printf_ln(s, color,
+                       _("  (use \"git bisect reset\" to get back to the original branch)"));
+       wt_status_print_trailer(s);
+}
+
+static void wt_status_print_state(struct wt_status *s)
+{
+       const char *state_color = color(WT_STATUS_HEADER, s);
+       struct wt_status_state state;
+       struct stat st;
+
+       memset(&state, 0, sizeof(state));
+
+       if (!stat(git_path("MERGE_HEAD"), &st)) {
+               state.merge_in_progress = 1;
+       } else if (!stat(git_path("rebase-apply"), &st)) {
+               if (!stat(git_path("rebase-apply/applying"), &st)) {
+                       state.am_in_progress = 1;
+                       if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size)
+                               state.am_empty_patch = 1;
+               } else {
+                       state.rebase_in_progress = 1;
+               }
+       } else if (!stat(git_path("rebase-merge"), &st)) {
+               if (!stat(git_path("rebase-merge/interactive"), &st))
+                       state.rebase_interactive_in_progress = 1;
+               else
+                       state.rebase_in_progress = 1;
+       } else if (!stat(git_path("CHERRY_PICK_HEAD"), &st)) {
+               state.cherry_pick_in_progress = 1;
+       }
+       if (!stat(git_path("BISECT_LOG"), &st))
+               state.bisect_in_progress = 1;
+
+       if (state.merge_in_progress)
+               show_merge_in_progress(s, &state, state_color);
+       else if (state.am_in_progress)
+               show_am_in_progress(s, &state, state_color);
+       else if (state.rebase_in_progress || state.rebase_interactive_in_progress)
+               show_rebase_in_progress(s, &state, state_color);
+       else if (state.cherry_pick_in_progress)
+               show_cherry_pick_in_progress(s, &state, state_color);
+       if (state.bisect_in_progress)
+               show_bisect_in_progress(s, &state, state_color);
+}
+
 void wt_status_print(struct wt_status *s)
 {
        const char *branch_color = color(WT_STATUS_ONBRANCH, s);
@@ -750,6 +991,7 @@ void wt_status_print(struct wt_status *s)
                        wt_status_print_tracking(s);
        }
 
+       wt_status_print_state(s);
        if (s->is_initial) {
                status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
                status_printf_ln(s, color(WT_STATUS_HEADER, s), _("Initial commit"));