Merge branch 'nd/branch-show-rebase-bisect-state'
authorJunio C Hamano <gitster@pobox.com>
Mon, 1 Apr 2013 16:05:45 +0000 (09:05 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 1 Apr 2013 16:05:45 +0000 (09:05 -0700)
Add a bit more information to "git status" during a rebase/bisect
session.

* nd/branch-show-rebase-bisect-state:
status, branch: fix the misleading "bisecting" message
branch: show more information when HEAD is detached
status: show more info than "currently not on any branch"
wt-status: move wt_status_get_state() out to wt_status_print()
wt-status: split wt_status_state parsing function out
wt-status: move strbuf into read_and_strip_branch()

1  2 
builtin/branch.c
t/t7406-submodule-update.sh
t/t7512-status-help.sh
wt-status.c
wt-status.h
diff --combined builtin/branch.c
index 00d17d25d1866323f9e1dffcb334a573f5de5221,8f00203d76c663818297d2d47be548a71b0a2b6c..e09ce51c2ee59ee4a9f44dbf43c97a2d31911b7b
@@@ -18,6 -18,7 +18,7 @@@
  #include "string-list.h"
  #include "column.h"
  #include "utf8.h"
+ #include "wt-status.h"
  
  static const char * const builtin_branch_usage[] = {
        N_("git branch [options] [-r | -a] [--merged | --no-merged]"),
@@@ -466,7 -467,7 +467,7 @@@ static void add_verbose_info(struct str
                             int verbose, int abbrev)
  {
        struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT;
 -      const char *sub = " **** invalid ref ****";
 +      const char *sub = _(" **** invalid ref ****");
        struct commit *commit = item->commit;
  
        if (commit && !parse_commit(commit)) {
@@@ -550,6 -551,29 +551,29 @@@ static int calc_maxwidth(struct ref_lis
        return w;
  }
  
+ static char *get_head_description(void)
+ {
+       struct strbuf desc = STRBUF_INIT;
+       struct wt_status_state state;
+       memset(&state, 0, sizeof(state));
+       wt_status_get_state(&state, 1);
+       if (state.rebase_in_progress ||
+           state.rebase_interactive_in_progress)
+               strbuf_addf(&desc, _("(no branch, rebasing %s)"),
+                           state.branch);
+       else if (state.bisect_in_progress)
+               strbuf_addf(&desc, _("(no branch, bisect started on %s)"),
+                           state.branch);
+       else if (state.detached_from)
+               strbuf_addf(&desc, _("(detached from %s)"),
+                           state.detached_from);
+       else
+               strbuf_addstr(&desc, _("(no branch)"));
+       free(state.branch);
+       free(state.onto);
+       free(state.detached_from);
+       return strbuf_detach(&desc, NULL);
+ }
  
  static void show_detached(struct ref_list *ref_list)
  {
  
        if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) {
                struct ref_item item;
-               item.name = xstrdup(_("(no branch)"));
+               item.name = get_head_description();
                item.width = utf8_strwidth(item.name);
                item.kind = REF_LOCAL_BRANCH;
                item.dest = NULL;
@@@ -590,7 -614,7 +614,7 @@@ static int print_ref_list(int kinds, in
                struct commit *filter;
                filter = lookup_commit_reference_gently(merge_filter_ref, 0);
                if (!filter)
 -                      die("object '%s' does not point to a commit",
 +                      die(_("object '%s' does not point to a commit"),
                            sha1_to_hex(merge_filter_ref));
  
                filter->object.flags |= UNINTERESTING;
@@@ -706,11 -730,11 +730,11 @@@ static int edit_branch_description(cons
        read_branch_desc(&buf, branch_name);
        if (!buf.len || buf.buf[buf.len-1] != '\n')
                strbuf_addch(&buf, '\n');
 -      strbuf_addf(&buf,
 -                  "Please edit the description for the branch\n"
 -                  "  %s\n"
 -                  "# Lines starting with '#' will be stripped.\n",
 -                  branch_name);
 +      strbuf_commented_addf(&buf,
 +                  "Please edit the description for the branch\n"
 +                  "  %s\n"
 +                  "Lines starting with '%c' will be stripped.\n",
 +                  branch_name, comment_line_char);
        fp = fopen(git_path(edit_description), "w");
        if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) {
                strbuf_release(&buf);
@@@ -825,9 -849,6 +849,9 @@@ int cmd_branch(int argc, const char **a
        if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0)
                list = 1;
  
 +      if (with_commit || merge_filter != NO_FILTER)
 +              list = 1;
 +
        if (!!delete + !!rename + !!force_create + !!list + !!new_upstream + !!unset_upstream > 1)
                usage_with_options(builtin_branch_usage, options);
  
                colopts = 0;
        }
  
 -      if (delete)
 +      if (delete) {
 +              if (!argc)
 +                      die(_("branch name required"));
                return delete_branches(argc, argv, delete > 1, kinds, quiet);
 -      else if (list) {
 +      else if (list) {
                int ret = print_ref_list(kinds, detached, verbose, abbrev,
                                         with_commit, argv);
                print_columns(&output, colopts, NULL);
  
                if (!argc) {
                        if (detached)
 -                              die("Cannot give description to detached HEAD");
 +                              die(_("Cannot give description to detached HEAD"));
                        branch_name = head;
                } else if (argc == 1)
                        branch_name = argv[0];
                else
 -                      usage_with_options(builtin_branch_usage, options);
 +                      die(_("cannot edit description of more than one branch"));
  
                strbuf_addf(&branch_ref, "refs/heads/%s", branch_name);
                if (!ref_exists(branch_ref.buf)) {
                        strbuf_release(&branch_ref);
  
                        if (!argc)
 -                              return error("No commit on branch '%s' yet.",
 +                              return error(_("No commit on branch '%s' yet."),
                                             branch_name);
                        else
 -                              return error("No such branch '%s'.", branch_name);
 +                              return error(_("No branch named '%s'."),
 +                                           branch_name);
                }
                strbuf_release(&branch_ref);
  
                else if (argc == 2)
                        rename_branch(argv[0], argv[1], rename > 1);
                else
 -                      usage_with_options(builtin_branch_usage, options);
 +                      die(_("too many branches for a rename operation"));
        } else if (new_upstream) {
                struct branch *branch = branch_get(argv[0]);
  
 +              if (argc > 1)
 +                      die(_("too many branches to set new upstream"));
 +
 +              if (!branch) {
 +                      if (!argc || !strcmp(argv[0], "HEAD"))
 +                              die(_("could not set upstream of HEAD to %s when "
 +                                    "it does not point to any branch."),
 +                                  new_upstream);
 +                      die(_("no such branch '%s'"), argv[0]);
 +              }
 +
                if (!ref_exists(branch->refname))
                        die(_("branch '%s' does not exist"), branch->name);
  
                struct branch *branch = branch_get(argv[0]);
                struct strbuf buf = STRBUF_INIT;
  
 +              if (argc > 1)
 +                      die(_("too many branches to unset upstream"));
 +
 +              if (!branch) {
 +                      if (!argc || !strcmp(argv[0], "HEAD"))
 +                              die(_("could not unset upstream of HEAD when "
 +                                    "it does not point to any branch."));
 +                      die(_("no such branch '%s'"), argv[0]);
 +              }
 +
                if (!branch_has_merge_config(branch)) {
                        die(_("Branch '%s' has no upstream information"), branch->name);
                }
                int branch_existed = 0, remote_tracking = 0;
                struct strbuf buf = STRBUF_INIT;
  
 +              if (!strcmp(argv[0], "HEAD"))
 +                      die(_("it does not make sense to create 'HEAD' manually"));
 +
 +              if (!branch)
 +                      die(_("no such branch '%s'"), argv[0]);
 +
                if (kinds != REF_LOCAL_BRANCH)
                        die(_("-a and -r options to 'git branch' do not make sense with a branch name"));
  
index 1a3d20bdc36c63c5261090d76858d43bd7f64113,50ac02028df0cc83e3f30cc73373ddf930634e3f..2a0cfaac329c6c0974f205c81c45a0304a04897d
@@@ -643,8 -643,7 +643,8 @@@ test_expect_success 'submodule update p
        rm -rf super_update_r2 &&
        git clone super_update_r super_update_r2 &&
        (cd super_update_r2 &&
 -       git submodule update --init --recursive &&
 +       git submodule update --init --recursive >actual &&
 +       test_i18ngrep "Submodule path .submodule/subsubmodule.: checked out" actual &&
         (cd submodule/subsubmodule &&
          git log > ../../expected
         ) &&
@@@ -665,8 -664,10 +665,10 @@@ test_expect_success 'submodule add prop
  
  test_expect_success 'submodule update properly revives a moved submodule' '
        (cd super &&
+        H=$(git rev-parse --short HEAD) &&
         git commit -am "pre move" &&
-        git status >expect&&
+        H2=$(git rev-parse --short HEAD) &&
+        git status | sed "s/$H/XXX/" >expect &&
         H=$(cd submodule2; git rev-parse HEAD) &&
         git rm --cached submodule2 &&
         rm -rf submodule2 &&
         git config -f .gitmodules submodule.submodule2.path "moved/sub module"
         git commit -am "post move" &&
         git submodule update &&
-        git status >actual &&
+        git status | sed "s/$H2/XXX/" >actual &&
         test_cmp expect actual
        )
  '
diff --combined t/t7512-status-help.sh
index 9d4610629d725c22b9de5187abf71b2b8544292d,c35d01de54122de549ea7ec4f4ab66309ec6fe36..06749a6aa07e40a5e5081c8912264d9c15b385ef
@@@ -5,7 -5,7 +5,7 @@@
  #                  Grenoble INP Ensimag
  #
  
 -test_description='git status advices'
 +test_description='git status advice'
  
  . ./test-lib.sh
  
@@@ -14,7 -14,6 +14,7 @@@
  set_fake_editor
  
  test_expect_success 'prepare for conflicts' '
 +      git config --global advice.statusuoption false &&
        test_commit init main.txt init &&
        git branch conflicts &&
        test_commit on_master main.txt on_master &&
@@@ -77,7 -76,7 +77,7 @@@ test_expect_success 'status when rebas
        ONTO=$(git rev-parse --short HEAD^^) &&
        test_must_fail git rebase HEAD^ --onto HEAD^^ &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached at $ONTO
        # You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
        #   (fix conflicts and then run "git rebase --continue")
        #   (use "git rebase --skip" to skip this patch)
@@@ -104,7 -103,7 +104,7 @@@ test_expect_success 'status when rebas
        echo three >main.txt &&
        git add main.txt &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached at $ONTO
        # You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
        #   (all conflicts fixed: run "git rebase --continue")
        #
@@@ -136,7 -135,7 +136,7 @@@ test_expect_success 'status during reba
        ONTO=$(git rev-parse --short rebase_i_conflicts) &&
        test_must_fail git rebase -i rebase_i_conflicts &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached at $ONTO
        # You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''.
        #   (fix conflicts and then run "git rebase --continue")
        #   (use "git rebase --skip" to skip this patch)
@@@ -162,7 -161,7 +162,7 @@@ test_expect_success 'status during reba
        test_must_fail git rebase -i rebase_i_conflicts &&
        git add main.txt &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached at $ONTO
        # You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''.
        #   (all conflicts fixed: run "git rebase --continue")
        #
@@@ -188,9 -187,10 +188,10 @@@ test_expect_success 'status when rebasi
        export FAKE_LINES &&
        test_when_finished "git rebase --abort" &&
        ONTO=$(git rev-parse --short HEAD~2) &&
+       TGT=$(git rev-parse --short two_rebase_i) &&
        git rebase -i HEAD~2 &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $TGT
        # You are currently editing a commit while rebasing branch '\''rebase_i_edit'\'' on '\''$ONTO'\''.
        #   (use "git commit --amend" to amend the current commit)
        #   (use "git rebase --continue" once you are satisfied with your changes)
@@@ -215,8 -215,9 +216,9 @@@ test_expect_success 'status when splitt
        ONTO=$(git rev-parse --short HEAD~3) &&
        git rebase -i HEAD~3 &&
        git reset HEAD^ &&
+       TGT=$(git rev-parse --short HEAD) &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached at $TGT
        # You are currently splitting a commit while rebasing branch '\''split_commit'\'' on '\''$ONTO'\''.
        #   (Once your working directory is clean, run "git rebase --continue")
        #
@@@ -244,10 -245,11 +246,11 @@@ test_expect_success 'status after editi
        export FAKE_LINES &&
        test_when_finished "git rebase --abort" &&
        ONTO=$(git rev-parse --short HEAD~3) &&
+       TGT=$(git rev-parse --short three_amend) &&
        git rebase -i HEAD~3 &&
        git commit --amend -m "foo" &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $TGT
        # You are currently editing a commit while rebasing branch '\''amend_last'\'' on '\''$ONTO'\''.
        #   (use "git commit --amend" to amend the current commit)
        #   (use "git rebase --continue" once you are satisfied with your changes)
@@@ -277,7 -279,7 +280,7 @@@ test_expect_success 'status: (continue 
        git rebase -i HEAD~3 &&
        git rebase --continue &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $ONTO
        # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
        #   (use "git commit --amend" to amend the current commit)
        #   (use "git rebase --continue" once you are satisfied with your changes)
@@@ -299,7 -301,7 +302,7 @@@ test_expect_success 'status: (continue 
        git rebase --continue &&
        git reset HEAD^ &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $ONTO
        # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
        #   (Once your working directory is clean, run "git rebase --continue")
        #
@@@ -326,7 -328,7 +329,7 @@@ test_expect_success 'status: (continue 
        git rebase --continue &&
        git commit --amend -m "foo" &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $ONTO
        # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
        #   (use "git commit --amend" to amend the current commit)
        #   (use "git rebase --continue" once you are satisfied with your changes)
@@@ -348,7 -350,7 +351,7 @@@ test_expect_success 'status: (amend fir
        git commit --amend -m "a" &&
        git rebase --continue &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $ONTO
        # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
        #   (use "git commit --amend" to amend the current commit)
        #   (use "git rebase --continue" once you are satisfied with your changes)
@@@ -371,7 -373,7 +374,7 @@@ test_expect_success 'status: (amend fir
        git rebase --continue &&
        git reset HEAD^ &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $ONTO
        # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
        #   (Once your working directory is clean, run "git rebase --continue")
        #
@@@ -399,7 -401,7 +402,7 @@@ test_expect_success 'status: (amend fir
        git rebase --continue &&
        git commit --amend -m "d" &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $ONTO
        # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
        #   (use "git commit --amend" to amend the current commit)
        #   (use "git rebase --continue" once you are satisfied with your changes)
@@@ -423,7 -425,7 +426,7 @@@ test_expect_success 'status: (split fir
        git commit -m "e" &&
        git rebase --continue &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $ONTO
        # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
        #   (use "git commit --amend" to amend the current commit)
        #   (use "git rebase --continue" once you are satisfied with your changes)
@@@ -448,7 -450,7 +451,7 @@@ test_expect_success 'status: (split fir
        git rebase --continue &&
        git reset HEAD^ &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $ONTO
        # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
        #   (Once your working directory is clean, run "git rebase --continue")
        #
@@@ -478,7 -480,7 +481,7 @@@ test_expect_success 'status: (split fir
        git rebase --continue &&
        git commit --amend -m "h" &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached from $ONTO
        # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
        #   (use "git commit --amend" to amend the current commit)
        #   (use "git rebase --continue" once you are satisfied with your changes)
@@@ -573,9 -575,10 +576,10 @@@ test_expect_success 'status when bisect
        git bisect start &&
        git bisect bad &&
        git bisect good one_bisect &&
-       cat >expected <<-\EOF &&
-       # Not currently on any branch.
-       # You are currently bisecting branch '\''bisect'\''.
+       TGT=$(git rev-parse --short two_bisect) &&
+       cat >expected <<-EOF &&
+       # HEAD detached at $TGT
+       # You are currently bisecting, started from branch '\''bisect'\''.
        #   (use "git bisect reset" to get back to the original branch)
        #
        nothing to commit (use -u to show untracked files)
@@@ -597,7 -600,7 +601,7 @@@ test_expect_success 'status when rebas
        ONTO=$(git rev-parse --short HEAD^^) &&
        test_must_fail git rebase HEAD^ --onto HEAD^^ &&
        cat >expected <<-EOF &&
-       # Not currently on any branch.
+       # HEAD detached at $ONTO
        # You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''.
        #
        # Unmerged paths:
@@@ -663,5 -666,15 +667,15 @@@ test_expect_success 'status when cherry
        test_i18ncmp expected actual
  '
  
+ test_expect_success 'status showing detached from a tag' '
+       test_commit atag tagging &&
+       git checkout atag &&
+       cat >expected <<-\EOF
+       # HEAD detached at atag
+       nothing to commit (use -u to show untracked files)
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+ '
  
  test_done
diff --combined wt-status.c
index 54f4391f9cc86d3d485ed80383cb142b1f25b1e7,cf3d81ab29b0316a1c606006019ae19013d03d48..cea8e55d8bc1d9117a6104c4181be9ff875c3ffa
@@@ -45,7 -45,7 +45,7 @@@ static void status_vprintf(struct wt_st
  
        strbuf_vaddf(&sb, fmt, ap);
        if (!sb.len) {
 -              strbuf_addch(&sb, '#');
 +              strbuf_addch(&sb, comment_line_char);
                if (!trail)
                        strbuf_addch(&sb, ' ');
                color_print_strbuf(s->fp, color, &sb);
@@@ -59,7 -59,7 +59,7 @@@
  
                strbuf_reset(&linebuf);
                if (at_bol) {
 -                      strbuf_addch(&linebuf, '#');
 +                      strbuf_addch(&linebuf, comment_line_char);
                        if (*line != '\n' && *line != '\t')
                                strbuf_addch(&linebuf, ' ');
                }
@@@ -264,7 -264,7 +264,7 @@@ static void wt_status_print_change_data
  {
        struct wt_status_change_data *d = it->util;
        const char *c = color(change_type, s);
 -      int status = status;
 +      int status;
        char *one_name;
        char *two_name;
        const char *one, *two;
                }
                status = d->worktree_status;
                break;
 +      default:
 +              die("BUG: unhandled change_type %d in wt_status_print_change_data",
 +                  change_type);
        }
  
        one = quote_path(one_name, -1, &onebuf, s->prefix);
@@@ -499,14 -496,9 +499,14 @@@ static void wt_status_collect_untracked
  {
        int i;
        struct dir_struct dir;
 +      struct timeval t_begin;
  
        if (!s->show_untracked_files)
                return;
 +
 +      if (advice_status_u_option)
 +              gettimeofday(&t_begin, NULL);
 +
        memset(&dir, 0, sizeof(dir));
        if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
                dir.flags |=
        }
  
        free(dir.entries);
 +
 +      if (advice_status_u_option) {
 +              struct timeval t_end;
 +              gettimeofday(&t_end, NULL);
 +              s->untracked_in_ms =
 +                      (uint64_t)t_end.tv_sec * 1000 + t_end.tv_usec / 1000 -
 +                      ((uint64_t)t_begin.tv_sec * 1000 + t_begin.tv_usec / 1000);
 +      }
  }
  
  void wt_status_collect(struct wt_status *s)
@@@ -778,10 -762,8 +778,10 @@@ static void wt_status_print_tracking(st
  
        for (cp = sb.buf; (ep = strchr(cp, '\n')) != NULL; cp = ep + 1)
                color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s),
 -                               "# %.*s", (int)(ep - cp), cp);
 -      color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
 +                               "%c %.*s", comment_line_char,
 +                               (int)(ep - cp), cp);
 +      color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "%c",
 +                       comment_line_char);
  }
  
  static int has_unmerged(struct wt_status *s)
@@@ -971,7 -953,7 +971,7 @@@ static void show_bisect_in_progress(str
  {
        if (state->branch)
                status_printf_ln(s, color,
-                                _("You are currently bisecting branch '%s'."),
+                                _("You are currently bisecting, started from branch '%s'."),
                                 state->branch);
        else
                status_printf_ln(s, color,
  /*
   * Extract branch information from rebase/bisect
   */
- static void read_and_strip_branch(struct strbuf *sb,
-                                 const char **branch,
-                                 const char *path)
+ static char *read_and_strip_branch(const char *path)
  {
+       struct strbuf sb = STRBUF_INIT;
        unsigned char sha1[20];
  
-       strbuf_reset(sb);
-       if (strbuf_read_file(sb, git_path("%s", path), 0) <= 0)
-               return;
+       if (strbuf_read_file(&sb, git_path("%s", path), 0) <= 0)
+               goto got_nothing;
  
-       while (sb->len && sb->buf[sb->len - 1] == '\n')
-               strbuf_setlen(sb, sb->len - 1);
-       if (!sb->len)
-               return;
-       if (!prefixcmp(sb->buf, "refs/heads/"))
-               *branch = sb->buf + strlen("refs/heads/");
-       else if (!prefixcmp(sb->buf, "refs/"))
-               *branch = sb->buf;
-       else if (!get_sha1_hex(sb->buf, sha1)) {
+       while (&sb.len && sb.buf[sb.len - 1] == '\n')
+               strbuf_setlen(&sb, sb.len - 1);
+       if (!sb.len)
+               goto got_nothing;
+       if (!prefixcmp(sb.buf, "refs/heads/"))
+               strbuf_remove(&sb,0, strlen("refs/heads/"));
+       else if (!prefixcmp(sb.buf, "refs/"))
+               ;
+       else if (!get_sha1_hex(sb.buf, sha1)) {
                const char *abbrev;
                abbrev = find_unique_abbrev(sha1, DEFAULT_ABBREV);
-               strbuf_reset(sb);
-               strbuf_addstr(sb, abbrev);
-               *branch = sb->buf;
-       } else if (!strcmp(sb->buf, "detached HEAD")) /* rebase */
-               ;
+               strbuf_reset(&sb);
+               strbuf_addstr(&sb, abbrev);
+       } else if (!strcmp(sb.buf, "detached HEAD")) /* rebase */
+               goto got_nothing;
        else                    /* bisect */
-               *branch = sb->buf;
+               ;
+       return strbuf_detach(&sb, NULL);
+ got_nothing:
+       strbuf_release(&sb);
+       return NULL;
  }
  
- static void wt_status_print_state(struct wt_status *s)
+ struct grab_1st_switch_cbdata {
+       int found;
+       struct strbuf buf;
+       unsigned char nsha1[20];
+ };
+ static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1,
+                          const char *email, unsigned long timestamp, int tz,
+                          const char *message, void *cb_data)
  {
-       const char *state_color = color(WT_STATUS_HEADER, s);
-       struct strbuf branch = STRBUF_INIT;
-       struct strbuf onto = STRBUF_INIT;
-       struct wt_status_state state;
-       struct stat st;
+       struct grab_1st_switch_cbdata *cb = cb_data;
+       const char *target = NULL, *end;
  
-       memset(&state, 0, sizeof(state));
+       if (prefixcmp(message, "checkout: moving from "))
+               return 0;
+       message += strlen("checkout: moving from ");
+       target = strstr(message, " to ");
+       if (!target)
+               return 0;
+       target += strlen(" to ");
+       strbuf_reset(&cb->buf);
+       hashcpy(cb->nsha1, nsha1);
+       for (end = target; *end && *end != '\n'; end++)
+               ;
+       strbuf_add(&cb->buf, target, end - target);
+       cb->found = 1;
+       return 1;
+ }
+ static void wt_status_get_detached_from(struct wt_status_state *state)
+ {
+       struct grab_1st_switch_cbdata cb;
+       struct commit *commit;
+       unsigned char sha1[20];
+       char *ref = NULL;
+       strbuf_init(&cb.buf, 0);
+       if (for_each_reflog_ent_reverse("HEAD", grab_1st_switch, &cb) <= 0) {
+               strbuf_release(&cb.buf);
+               return;
+       }
+       if (dwim_ref(cb.buf.buf, cb.buf.len, sha1, &ref) == 1 &&
+           /* sha1 is a commit? match without further lookup */
+           (!hashcmp(cb.nsha1, sha1) ||
+            /* perhaps sha1 is a tag, try to dereference to a commit */
+            ((commit = lookup_commit_reference_gently(sha1, 1)) != NULL &&
+             !hashcmp(cb.nsha1, commit->object.sha1)))) {
+               int ofs;
+               if (!prefixcmp(ref, "refs/tags/"))
+                       ofs = strlen("refs/tags/");
+               else if (!prefixcmp(ref, "refs/remotes/"))
+                       ofs = strlen("refs/remotes/");
+               else
+                       ofs = 0;
+               state->detached_from = xstrdup(ref + ofs);
+       } else
+               state->detached_from =
+                       xstrdup(find_unique_abbrev(cb.nsha1, DEFAULT_ABBREV));
+       hashcpy(state->detached_sha1, cb.nsha1);
+       free(ref);
+       strbuf_release(&cb.buf);
+ }
+ void wt_status_get_state(struct wt_status_state *state,
+                        int get_detached_from)
+ {
+       struct stat st;
  
        if (!stat(git_path("MERGE_HEAD"), &st)) {
-               state.merge_in_progress = 1;
+               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;
+                       state->am_in_progress = 1;
                        if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size)
-                               state.am_empty_patch = 1;
+                               state->am_empty_patch = 1;
                } else {
-                       state.rebase_in_progress = 1;
-                       read_and_strip_branch(&branch, &state.branch,
-                                             "rebase-apply/head-name");
-                       read_and_strip_branch(&onto, &state.onto,
-                                             "rebase-apply/onto");
+                       state->rebase_in_progress = 1;
+                       state->branch = read_and_strip_branch("rebase-apply/head-name");
+                       state->onto = read_and_strip_branch("rebase-apply/onto");
                }
        } else if (!stat(git_path("rebase-merge"), &st)) {
                if (!stat(git_path("rebase-merge/interactive"), &st))
-                       state.rebase_interactive_in_progress = 1;
+                       state->rebase_interactive_in_progress = 1;
                else
-                       state.rebase_in_progress = 1;
-               read_and_strip_branch(&branch, &state.branch,
-                                     "rebase-merge/head-name");
-               read_and_strip_branch(&onto, &state.onto,
-                                     "rebase-merge/onto");
+                       state->rebase_in_progress = 1;
+               state->branch = read_and_strip_branch("rebase-merge/head-name");
+               state->onto = read_and_strip_branch("rebase-merge/onto");
        } else if (!stat(git_path("CHERRY_PICK_HEAD"), &st)) {
-               state.cherry_pick_in_progress = 1;
+               state->cherry_pick_in_progress = 1;
        }
        if (!stat(git_path("BISECT_LOG"), &st)) {
-               state.bisect_in_progress = 1;
-               read_and_strip_branch(&branch, &state.branch,
-                                     "BISECT_START");
+               state->bisect_in_progress = 1;
+               state->branch = read_and_strip_branch("BISECT_START");
        }
  
-       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);
-       strbuf_release(&branch);
-       strbuf_release(&onto);
+       if (get_detached_from)
+               wt_status_get_detached_from(state);
+ }
+ static void wt_status_print_state(struct wt_status *s,
+                                 struct wt_status_state *state)
+ {
+       const char *state_color = color(WT_STATUS_HEADER, s);
+       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);
        const char *branch_status_color = color(WT_STATUS_HEADER, s);
+       struct wt_status_state state;
+       memset(&state, 0, sizeof(state));
+       wt_status_get_state(&state,
+                           s->branch && !strcmp(s->branch, "HEAD"));
  
        if (s->branch) {
                const char *on_what = _("On branch ");
                if (!prefixcmp(branch_name, "refs/heads/"))
                        branch_name += 11;
                else if (!strcmp(branch_name, "HEAD")) {
-                       branch_name = "";
                        branch_status_color = color(WT_STATUS_NOBRANCH, s);
-                       on_what = _("Not currently on any branch.");
+                       if (state.detached_from) {
+                               unsigned char sha1[20];
+                               branch_name = state.detached_from;
+                               if (!get_sha1("HEAD", sha1) &&
+                                   !hashcmp(sha1, state.detached_sha1))
+                                       on_what = _("HEAD detached at ");
+                               else
+                                       on_what = _("HEAD detached from ");
+                       } else {
+                               branch_name = "";
+                               on_what = _("Not currently on any branch.");
+                       }
                }
                status_printf(s, color(WT_STATUS_HEADER, s), "");
                status_printf_more(s, branch_status_color, "%s", on_what);
                        wt_status_print_tracking(s);
        }
  
-       wt_status_print_state(s);
+       wt_status_print_state(s, &state);
+       free(state.branch);
+       free(state.onto);
+       free(state.detached_from);
        if (s->is_initial) {
                status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
                status_printf_ln(s, color(WT_STATUS_HEADER, s), _("Initial commit"));
                wt_status_print_other(s, &s->untracked, _("Untracked files"), "add");
                if (s->show_ignored_files)
                        wt_status_print_other(s, &s->ignored, _("Ignored files"), "add -f");
 +              if (advice_status_u_option && 2000 < s->untracked_in_ms) {
 +                      status_printf_ln(s, GIT_COLOR_NORMAL, "");
 +                      status_printf_ln(s, GIT_COLOR_NORMAL,
 +                               _("It took %.2f seconds to enumerate untracked files."
 +                                 "  'status -uno'"),
 +                               s->untracked_in_ms / 1000.0);
 +                      status_printf_ln(s, GIT_COLOR_NORMAL,
 +                               _("may speed it up, but you have to be careful not"
 +                                 " to forget to add"));
 +                      status_printf_ln(s, GIT_COLOR_NORMAL,
 +                               _("new files yourself (see 'git help status')."));
 +              }
        } else if (s->commitable)
                status_printf_ln(s, GIT_COLOR_NORMAL, _("Untracked files not listed%s"),
                        advice_status_hints
diff --combined wt-status.h
index 74208c06fd08b49041489c869d3c5f16804493da,5cb7df91571c79bbd809943287ddbe1eb76d98ed..be7a016173487780fdd4d37e24b4a47fcb766454
@@@ -69,7 -69,6 +69,7 @@@ struct wt_status 
        struct string_list change;
        struct string_list untracked;
        struct string_list ignored;
 +      uint32_t untracked_in_ms;
  };
  
  struct wt_status_state {
        int rebase_interactive_in_progress;
        int cherry_pick_in_progress;
        int bisect_in_progress;
-       const char *branch;
-       const char *onto;
+       char *branch;
+       char *onto;
+       char *detached_from;
+       unsigned char detached_sha1[20];
  };
  
  void wt_status_prepare(struct wt_status *s);
  void wt_status_print(struct wt_status *s);
  void wt_status_collect(struct wt_status *s);
+ void wt_status_get_state(struct wt_status_state *state, int get_detached_from);
  
  void wt_shortstatus_print(struct wt_status *s);
  void wt_porcelain_print(struct wt_status *s);