Merge branch 'nd/branch-show-rebase-bisect-state'

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()
This commit is contained in:
Junio C Hamano 2013-04-01 09:05:45 -07:00
commit afc2e81247
7 changed files with 216 additions and 91 deletions

View File

@ -18,6 +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]"),
@ -550,6 +551,29 @@ static int calc_maxwidth(struct ref_list *refs)
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)
{
@ -557,7 +581,7 @@ 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;

View File

@ -94,13 +94,13 @@ test_expect_success 'git branch -v pattern does not show branch summaries' '
test_must_fail git branch -v branch*
'
cat >expect <<'EOF'
* (no branch)
test_expect_success 'git branch shows detached HEAD properly' '
cat >expect <<EOF &&
* (detached from $(git rev-parse --short HEAD^0))
branch-one
branch-two
master
EOF
test_expect_success 'git branch shows detached HEAD properly' '
git checkout HEAD^0 &&
git branch >actual &&
test_i18ncmp expect actual

View File

@ -164,7 +164,7 @@ test_expect_success 'bisect start: existing ".git/BISECT_START" not modified if
cp .git/BISECT_START saved &&
test_must_fail git bisect start $HASH4 foo -- &&
git branch > branch.output &&
test_i18ngrep "* (no branch)" branch.output > /dev/null &&
test_i18ngrep "* (no branch, bisect started on other)" branch.output > /dev/null &&
test_cmp saved .git/BISECT_START
'
test_expect_success 'bisect start: no ".git/BISECT_START" if mistaken rev' '

View File

@ -665,8 +665,10 @@ test_expect_success 'submodule add properly re-creates deeper level submodules'
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 &&
@ -675,7 +677,7 @@ test_expect_success 'submodule update properly revives a moved submodule' '
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
)
'

View File

@ -77,7 +77,7 @@ test_expect_success 'status when rebase in progress before resolving conflicts'
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 +104,7 @@ test_expect_success 'status when rebase in progress before rebase --continue' '
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 +136,7 @@ test_expect_success 'status during rebase -i when conflicts unresolved' '
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 +162,7 @@ test_expect_success 'status during rebase -i after resolving conflicts' '
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 +188,10 @@ test_expect_success 'status when rebasing -i in edit mode' '
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 +216,9 @@ test_expect_success 'status when splitting a commit' '
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 +246,11 @@ test_expect_success 'status after editing the last commit with --amend during a
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 +280,7 @@ test_expect_success 'status: (continue first edit) second edit' '
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 +302,7 @@ test_expect_success 'status: (continue first edit) second edit and split' '
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 +329,7 @@ test_expect_success 'status: (continue first edit) second edit and amend' '
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 +351,7 @@ test_expect_success 'status: (amend first edit) second edit' '
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 +374,7 @@ test_expect_success 'status: (amend first edit) second edit and split' '
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 +402,7 @@ test_expect_success 'status: (amend first edit) second edit and amend' '
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 +426,7 @@ test_expect_success 'status: (split first edit) second edit' '
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 +451,7 @@ test_expect_success 'status: (split first edit) second edit and split' '
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 +481,7 @@ test_expect_success 'status: (split first edit) second edit and amend' '
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 +576,10 @@ test_expect_success 'status when bisecting' '
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 +601,7 @@ test_expect_success 'status when rebase conflicts with statushints disabled' '
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 +667,15 @@ test_expect_success 'status when cherry-picking after resolving conflicts' '
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

View File

@ -971,7 +971,7 @@ static void show_bisect_in_progress(struct wt_status *s,
{
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,
@ -985,96 +985,164 @@ static void show_bisect_in_progress(struct wt_status *s,
/*
* 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)
{
struct grab_1st_switch_cbdata *cb = cb_data;
const char *target = NULL, *end;
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)
{
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;
memset(&state, 0, sizeof(state));
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 ");
@ -1082,9 +1150,19 @@ void wt_status_print(struct wt_status *s)
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);
@ -1093,7 +1171,11 @@ void wt_status_print(struct wt_status *s)
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"));

View File

@ -80,13 +80,16 @@ 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);