status: show more info than "currently not on any branch"

When a remote ref or a tag is checked out, HEAD is automatically detached. There is no user-friendly way to find out what ref is checked out in this case. This patch digs in reflog for this information and shows "HEAD detached from origin/master" or "HEAD detached at v1.8.0" instead of "currently not on any branch". When it cannot figure out the original ref, it shows an abbreviated SHA-1. "Currently not on any branch" would never display (unless reflog is pruned to near empty that the last checkout entry is lost). Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Nguyễn Thái Ngọc Duy committed Mar 13, 2013 at 18:42 UTC b397ea4863a143e97cb9da76c6c18cc555537d64
4 files changed +125 -26
t/t7406-submodule-update.sh
+4 -2
@@ -664,8 +664,10 @@ test_expect_success 'submodule add properly re-creates deeper level submodules'
664
665 test_expect_success 'submodule update properly revives a moved submodule' '
666 (cd super &&
667 + H=$(git rev-parse --short HEAD) &&
668 git commit -am "pre move" &&
668 - git status >expect&&
669 + H2=$(git rev-parse --short HEAD) &&
670 + git status | sed "s/$H/XXX/" >expect &&
671 H=$(cd submodule2; git rev-parse HEAD) &&
672 git rm --cached submodule2 &&
673 rm -rf submodule2 &&
@@ -674,7 +676,7 @@ test_expect_success 'submodule update properly revives a moved submodule' '
676 git config -f .gitmodules submodule.submodule2.path "moved/sub module"
677 git commit -am "post move" &&
678 git submodule update &&
677 - git status >actual &&
679 + git status | sed "s/$H2/XXX/" >actual &&
680 test_cmp expect actual
681 )
682 '
t/t7512-status-help.sh
+33 -19
@@ -76,7 +76,7 @@ test_expect_success 'status when rebase in progress before resolving conflicts'
76 ONTO=$(git rev-parse --short HEAD^^) &&
77 test_must_fail git rebase HEAD^ --onto HEAD^^ &&
78 cat >expected <<-EOF &&
79 - # Not currently on any branch.
79 + # HEAD detached at $ONTO
80 # You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
81 # (fix conflicts and then run "git rebase --continue")
82 # (use "git rebase --skip" to skip this patch)
@@ -103,7 +103,7 @@ test_expect_success 'status when rebase in progress before rebase --continue' '
103 echo three >main.txt &&
104 git add main.txt &&
105 cat >expected <<-EOF &&
106 - # Not currently on any branch.
106 + # HEAD detached at $ONTO
107 # You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
108 # (all conflicts fixed: run "git rebase --continue")
109 #
@@ -135,7 +135,7 @@ test_expect_success 'status during rebase -i when conflicts unresolved' '
135 ONTO=$(git rev-parse --short rebase_i_conflicts) &&
136 test_must_fail git rebase -i rebase_i_conflicts &&
137 cat >expected <<-EOF &&
138 - # Not currently on any branch.
138 + # HEAD detached at $ONTO
139 # You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''.
140 # (fix conflicts and then run "git rebase --continue")
141 # (use "git rebase --skip" to skip this patch)
@@ -161,7 +161,7 @@ test_expect_success 'status during rebase -i after resolving conflicts' '
161 test_must_fail git rebase -i rebase_i_conflicts &&
162 git add main.txt &&
163 cat >expected <<-EOF &&
164 - # Not currently on any branch.
164 + # HEAD detached at $ONTO
165 # You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''.
166 # (all conflicts fixed: run "git rebase --continue")
167 #
@@ -187,9 +187,10 @@ test_expect_success 'status when rebasing -i in edit mode' '
187 export FAKE_LINES &&
188 test_when_finished "git rebase --abort" &&
189 ONTO=$(git rev-parse --short HEAD~2) &&
190 + TGT=$(git rev-parse --short two_rebase_i) &&
191 git rebase -i HEAD~2 &&
192 cat >expected <<-EOF &&
192 - # Not currently on any branch.
193 + # HEAD detached from $TGT
194 # You are currently editing a commit while rebasing branch '\''rebase_i_edit'\'' on '\''$ONTO'\''.
195 # (use "git commit --amend" to amend the current commit)
196 # (use "git rebase --continue" once you are satisfied with your changes)
@@ -214,8 +215,9 @@ test_expect_success 'status when splitting a commit' '
215 ONTO=$(git rev-parse --short HEAD~3) &&
216 git rebase -i HEAD~3 &&
217 git reset HEAD^ &&
218 + TGT=$(git rev-parse --short HEAD) &&
219 cat >expected <<-EOF &&
218 - # Not currently on any branch.
220 + # HEAD detached at $TGT
221 # You are currently splitting a commit while rebasing branch '\''split_commit'\'' on '\''$ONTO'\''.
222 # (Once your working directory is clean, run "git rebase --continue")
223 #
@@ -243,10 +245,11 @@ test_expect_success 'status after editing the last commit with --amend during a
245 export FAKE_LINES &&
246 test_when_finished "git rebase --abort" &&
247 ONTO=$(git rev-parse --short HEAD~3) &&
248 + TGT=$(git rev-parse --short three_amend) &&
249 git rebase -i HEAD~3 &&
250 git commit --amend -m "foo" &&
251 cat >expected <<-EOF &&
249 - # Not currently on any branch.
252 + # HEAD detached from $TGT
253 # You are currently editing a commit while rebasing branch '\''amend_last'\'' on '\''$ONTO'\''.
254 # (use "git commit --amend" to amend the current commit)
255 # (use "git rebase --continue" once you are satisfied with your changes)
@@ -276,7 +279,7 @@ test_expect_success 'status: (continue first edit) second edit' '
279 git rebase -i HEAD~3 &&
280 git rebase --continue &&
281 cat >expected <<-EOF &&
279 - # Not currently on any branch.
282 + # HEAD detached from $ONTO
283 # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
284 # (use "git commit --amend" to amend the current commit)
285 # (use "git rebase --continue" once you are satisfied with your changes)
@@ -298,7 +301,7 @@ test_expect_success 'status: (continue first edit) second edit and split' '
301 git rebase --continue &&
302 git reset HEAD^ &&
303 cat >expected <<-EOF &&
301 - # Not currently on any branch.
304 + # HEAD detached from $ONTO
305 # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
306 # (Once your working directory is clean, run "git rebase --continue")
307 #
@@ -325,7 +328,7 @@ test_expect_success 'status: (continue first edit) second edit and amend' '
328 git rebase --continue &&
329 git commit --amend -m "foo" &&
330 cat >expected <<-EOF &&
328 - # Not currently on any branch.
331 + # HEAD detached from $ONTO
332 # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
333 # (use "git commit --amend" to amend the current commit)
334 # (use "git rebase --continue" once you are satisfied with your changes)
@@ -347,7 +350,7 @@ test_expect_success 'status: (amend first edit) second edit' '
350 git commit --amend -m "a" &&
351 git rebase --continue &&
352 cat >expected <<-EOF &&
350 - # Not currently on any branch.
353 + # HEAD detached from $ONTO
354 # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
355 # (use "git commit --amend" to amend the current commit)
356 # (use "git rebase --continue" once you are satisfied with your changes)
@@ -370,7 +373,7 @@ test_expect_success 'status: (amend first edit) second edit and split' '
373 git rebase --continue &&
374 git reset HEAD^ &&
375 cat >expected <<-EOF &&
373 - # Not currently on any branch.
376 + # HEAD detached from $ONTO
377 # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
378 # (Once your working directory is clean, run "git rebase --continue")
379 #
@@ -398,7 +401,7 @@ test_expect_success 'status: (amend first edit) second edit and amend' '
401 git rebase --continue &&
402 git commit --amend -m "d" &&
403 cat >expected <<-EOF &&
401 - # Not currently on any branch.
404 + # HEAD detached from $ONTO
405 # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
406 # (use "git commit --amend" to amend the current commit)
407 # (use "git rebase --continue" once you are satisfied with your changes)
@@ -422,7 +425,7 @@ test_expect_success 'status: (split first edit) second edit' '
425 git commit -m "e" &&
426 git rebase --continue &&
427 cat >expected <<-EOF &&
425 - # Not currently on any branch.
428 + # HEAD detached from $ONTO
429 # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
430 # (use "git commit --amend" to amend the current commit)
431 # (use "git rebase --continue" once you are satisfied with your changes)
@@ -447,7 +450,7 @@ test_expect_success 'status: (split first edit) second edit and split' '
450 git rebase --continue &&
451 git reset HEAD^ &&
452 cat >expected <<-EOF &&
450 - # Not currently on any branch.
453 + # HEAD detached from $ONTO
454 # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
455 # (Once your working directory is clean, run "git rebase --continue")
456 #
@@ -477,7 +480,7 @@ test_expect_success 'status: (split first edit) second edit and amend' '
480 git rebase --continue &&
481 git commit --amend -m "h" &&
482 cat >expected <<-EOF &&
480 - # Not currently on any branch.
483 + # HEAD detached from $ONTO
484 # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
485 # (use "git commit --amend" to amend the current commit)
486 # (use "git rebase --continue" once you are satisfied with your changes)
@@ -572,8 +575,9 @@ test_expect_success 'status when bisecting' '
575 git bisect start &&
576 git bisect bad &&
577 git bisect good one_bisect &&
575 - cat >expected <<-\EOF &&
576 - # Not currently on any branch.
578 + TGT=$(git rev-parse --short two_bisect) &&
579 + cat >expected <<-EOF &&
580 + # HEAD detached at $TGT
581 # You are currently bisecting branch '\''bisect'\''.
582 # (use "git bisect reset" to get back to the original branch)
583 #
@@ -596,7 +600,7 @@ test_expect_success 'status when rebase conflicts with statushints disabled' '
600 ONTO=$(git rev-parse --short HEAD^^) &&
601 test_must_fail git rebase HEAD^ --onto HEAD^^ &&
602 cat >expected <<-EOF &&
599 - # Not currently on any branch.
603 + # HEAD detached at $ONTO
604 # You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''.
605 #
606 # Unmerged paths:
@@ -662,5 +666,15 @@ test_expect_success 'status when cherry-picking after resolving conflicts' '
666 test_i18ncmp expected actual
667 '
668
669 +test_expect_success 'status showing detached from a tag' '
670 + test_commit atag tagging &&
671 + git checkout atag &&
672 + cat >expected <<-\EOF
673 + # HEAD detached at atag
674 + nothing to commit (use -u to show untracked files)
675 + EOF
676 + git status --untracked-files=no >actual &&
677 + test_i18ncmp expected actual
678 +'
679
680 test_done
wt-status.c
+85 -4
@@ -999,7 +999,73 @@ got_nothing:
999 return NULL;
1000 }
1001
1002 -void wt_status_get_state(struct wt_status_state *state)
1002 +struct grab_1st_switch_cbdata {
1003 + int found;
1004 + struct strbuf buf;
1005 + unsigned char nsha1[20];
1006 +};
1007 +
1008 +static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1,
1009 + const char *email, unsigned long timestamp, int tz,
1010 + const char *message, void *cb_data)
1011 +{
1012 + struct grab_1st_switch_cbdata *cb = cb_data;
1013 + const char *target = NULL, *end;
1014 +
1015 + if (prefixcmp(message, "checkout: moving from "))
1016 + return 0;
1017 + message += strlen("checkout: moving from ");
1018 + target = strstr(message, " to ");
1019 + if (!target)
1020 + return 0;
1021 + target += strlen(" to ");
1022 + strbuf_reset(&cb->buf);
1023 + hashcpy(cb->nsha1, nsha1);
1024 + for (end = target; *end && *end != '\n'; end++)
1025 + ;
1026 + strbuf_add(&cb->buf, target, end - target);
1027 + cb->found = 1;
1028 + return 1;
1029 +}
1030 +
1031 +static void wt_status_get_detached_from(struct wt_status_state *state)
1032 +{
1033 + struct grab_1st_switch_cbdata cb;
1034 + struct commit *commit;
1035 + unsigned char sha1[20];
1036 + char *ref = NULL;
1037 +
1038 + strbuf_init(&cb.buf, 0);
1039 + if (for_each_reflog_ent_reverse("HEAD", grab_1st_switch, &cb) <= 0) {
1040 + strbuf_release(&cb.buf);
1041 + return;
1042 + }
1043 +
1044 + if (dwim_ref(cb.buf.buf, cb.buf.len, sha1, &ref) == 1 &&
1045 + /* sha1 is a commit? match without further lookup */
1046 + (!hashcmp(cb.nsha1, sha1) ||
1047 + /* perhaps sha1 is a tag, try to dereference to a commit */
1048 + ((commit = lookup_commit_reference_gently(sha1, 1)) != NULL &&
1049 + !hashcmp(cb.nsha1, commit->object.sha1)))) {
1050 + int ofs;
1051 + if (!prefixcmp(ref, "refs/tags/"))
1052 + ofs = strlen("refs/tags/");
1053 + else if (!prefixcmp(ref, "refs/remotes/"))
1054 + ofs = strlen("refs/remotes/");
1055 + else
1056 + ofs = 0;
1057 + state->detached_from = xstrdup(ref + ofs);
1058 + } else
1059 + state->detached_from =
1060 + xstrdup(find_unique_abbrev(cb.nsha1, DEFAULT_ABBREV));
1061 + hashcpy(state->detached_sha1, cb.nsha1);
1062 +
1063 + free(ref);
1064 + strbuf_release(&cb.buf);
1065 +}
1066 +
1067 +void wt_status_get_state(struct wt_status_state *state,
1068 + int get_detached_from)
1069 {
1070 struct stat st;
1071
@@ -1029,6 +1095,9 @@ void wt_status_get_state(struct wt_status_state *state)
1095 state->bisect_in_progress = 1;
1096 state->branch = read_and_strip_branch("BISECT_START");
1097 }
1098 +
1099 + if (get_detached_from)
1100 + wt_status_get_detached_from(state);
1101 }
1102
1103 static void wt_status_print_state(struct wt_status *s,
@@ -1054,7 +1123,8 @@ void wt_status_print(struct wt_status *s)
1123 struct wt_status_state state;
1124
1125 memset(&state, 0, sizeof(state));
1057 - wt_status_get_state(&state);
1126 + wt_status_get_state(&state,
1127 + s->branch && !strcmp(s->branch, "HEAD"));
1128
1129 if (s->branch) {
1130 const char *on_what = _("On branch ");
@@ -1062,9 +1132,19 @@ void wt_status_print(struct wt_status *s)
1132 if (!prefixcmp(branch_name, "refs/heads/"))
1133 branch_name += 11;
1134 else if (!strcmp(branch_name, "HEAD")) {
1065 - branch_name = "";
1135 branch_status_color = color(WT_STATUS_NOBRANCH, s);
1067 - on_what = _("Not currently on any branch.");
1136 + if (state.detached_from) {
1137 + unsigned char sha1[20];
1138 + branch_name = state.detached_from;
1139 + if (!get_sha1("HEAD", sha1) &&
1140 + !hashcmp(sha1, state.detached_sha1))
1141 + on_what = _("HEAD detached at ");
1142 + else
1143 + on_what = _("HEAD detached from ");
1144 + } else {
1145 + branch_name = "";
1146 + on_what = _("Not currently on any branch.");
1147 + }
1148 }
1149 status_printf(s, color(WT_STATUS_HEADER, s), "");
1150 status_printf_more(s, branch_status_color, "%s", on_what);
@@ -1076,6 +1156,7 @@ void wt_status_print(struct wt_status *s)
1156 wt_status_print_state(s, &state);
1157 free(state.branch);
1158 free(state.onto);
1159 + free(state.detached_from);
1160
1161 if (s->is_initial) {
1162 status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
wt-status.h
+3 -1
@@ -81,12 +81,14 @@ struct wt_status_state {
81 int bisect_in_progress;
82 char *branch;
83 char *onto;
84 + char *detached_from;
85 + unsigned char detached_sha1[20];
86 };
87
88 void wt_status_prepare(struct wt_status *s);
89 void wt_status_print(struct wt_status *s);
90 void wt_status_collect(struct wt_status *s);
89 -void wt_status_get_state(struct wt_status_state *state);
91 +void wt_status_get_state(struct wt_status_state *state, int get_detached_from);
92
93 void wt_shortstatus_print(struct wt_status *s);
94 void wt_porcelain_print(struct wt_status *s);