From: Junio C Hamano Date: Tue, 9 Jul 2019 22:25:33 +0000 (-0700) Subject: Merge branch 'nb/branch-show-other-worktrees-head' X-Git-Tag: v2.23.0-rc0~95 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/99eea645832d376a3b214b78a57adabf1fa96547?hp=-c Merge branch 'nb/branch-show-other-worktrees-head' "git branch --list" learned to show branches that are checked out in other worktrees connected to the same repository prefixed with '+', similar to the way the currently checked out branch is shown with '*' in front. * nb/branch-show-other-worktrees-head: branch: add worktree info on verbose output branch: update output to include worktree info ref-filter: add worktreepath atom --- 99eea645832d376a3b214b78a57adabf1fa96547 diff --combined Documentation/git-branch.txt index 6ebd512b4f,d11d00583a..7c8fa3b64f --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@@ -26,8 -26,10 +26,10 @@@ DESCRIPTIO ----------- If `--list` is given, or if there are no non-option arguments, existing - branches are listed; the current branch will be highlighted with an - asterisk. Option `-r` causes the remote-tracking branches to be listed, + branches are listed; the current branch will be highlighted in green and + marked with an asterisk. Any branches checked out in linked worktrees will + be highlighted in cyan and marked with a plus sign. Option `-r` causes the + remote-tracking branches to be listed, and option `-a` shows both local and remote branches. If a `` is given, it is used as a shell wildcard to restrict the output to matching branches. If multiple patterns are given, a branch is shown if @@@ -45,11 -47,7 +47,11 @@@ argument is missing it defaults to `HEA branch). The command's second form creates a new branch head named -which points to the current `HEAD`, or if given. +which points to the current `HEAD`, or if given. As a +special case, for , you may use `"A...B"` as a shortcut for +the merge base of `A` and `B` if there is exactly one merge base. You +can leave out at most one of `A` and `B`, in which case it defaults to +`HEAD`. Note that this will create the new branch, but it will not switch the working tree to it; use "git checkout " to switch to the @@@ -174,8 -172,10 +176,10 @@@ This option is only applicable in non-v When in list mode, show sha1 and commit subject line for each head, along with relationship to upstream branch (if any). If given twice, print - the name of the upstream branch, as well (see also `git remote - show `). + the path of the linked worktree (if any) and the name of the upstream + branch, as well (see also `git remote show `). Note that the + current worktree's HEAD will not have its path printed (it will always + be your current directory). -q:: --quiet:: diff --combined builtin/branch.c index d4359b33ac,3d1872babc..2cb45e42e1 --- a/builtin/branch.c +++ b/builtin/branch.c @@@ -47,6 -47,7 +47,7 @@@ static char branch_colors[][COLOR_MAXLE GIT_COLOR_NORMAL, /* LOCAL */ GIT_COLOR_GREEN, /* CURRENT */ GIT_COLOR_BLUE, /* UPSTREAM */ + GIT_COLOR_CYAN, /* WORKTREE */ }; enum color_branch { BRANCH_COLOR_RESET = 0, @@@ -54,7 -55,8 +55,8 @@@ BRANCH_COLOR_REMOTE = 2, BRANCH_COLOR_LOCAL = 3, BRANCH_COLOR_CURRENT = 4, - BRANCH_COLOR_UPSTREAM = 5 + BRANCH_COLOR_UPSTREAM = 5, + BRANCH_COLOR_WORKTREE = 6 }; static const char *color_branch_slots[] = { @@@ -64,6 -66,7 +66,7 @@@ [BRANCH_COLOR_LOCAL] = "local", [BRANCH_COLOR_CURRENT] = "current", [BRANCH_COLOR_UPSTREAM] = "upstream", + [BRANCH_COLOR_WORKTREE] = "worktree", }; static struct string_list output = STRING_LIST_INIT_DUP; @@@ -342,9 -345,10 +345,10 @@@ static char *build_format(struct ref_fi struct strbuf local = STRBUF_INIT; struct strbuf remote = STRBUF_INIT; - strbuf_addf(&local, "%%(if)%%(HEAD)%%(then)* %s%%(else) %s%%(end)", - branch_get_color(BRANCH_COLOR_CURRENT), - branch_get_color(BRANCH_COLOR_LOCAL)); + strbuf_addf(&local, "%%(if)%%(HEAD)%%(then)* %s%%(else)%%(if)%%(worktreepath)%%(then)+ %s%%(else) %s%%(end)%%(end)", + branch_get_color(BRANCH_COLOR_CURRENT), + branch_get_color(BRANCH_COLOR_WORKTREE), + branch_get_color(BRANCH_COLOR_LOCAL)); strbuf_addf(&remote, " %s", branch_get_color(BRANCH_COLOR_REMOTE)); @@@ -363,9 -367,13 +367,13 @@@ strbuf_addf(&local, " %s ", obname.buf); if (filter->verbose > 1) + { + strbuf_addf(&local, "%%(if:notequals=*)%%(HEAD)%%(then)%%(if)%%(worktreepath)%%(then)(%s%%(worktreepath)%s) %%(end)%%(end)", + branch_get_color(BRANCH_COLOR_WORKTREE), branch_get_color(BRANCH_COLOR_RESET)); strbuf_addf(&local, "%%(if)%%(upstream)%%(then)[%s%%(upstream:short)%s%%(if)%%(upstream:track)" "%%(then): %%(upstream:track,nobracket)%%(end)] %%(end)%%(contents:subject)", branch_get_color(BRANCH_COLOR_UPSTREAM), branch_get_color(BRANCH_COLOR_RESET)); + } else strbuf_addf(&local, "%%(if)%%(upstream:track)%%(then)%%(upstream:track) %%(end)%%(contents:subject)"); @@@ -644,7 -652,8 +652,7 @@@ int cmd_branch(int argc, const char **a OPT_MERGED(&filter, N_("print only branches that are merged")), OPT_NO_MERGED(&filter, N_("print only branches that are not merged")), OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")), - OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"), - N_("field name to sort on"), &parse_opt_ref_sorting), + OPT_REF_SORT(sorting_tail), { OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"), N_("print only branches of the object"), 0, parse_opt_object_name diff --combined ref-filter.c index 8500671bc6,79cfec914a..8fb25c1412 --- a/ref-filter.c +++ b/ref-filter.c @@@ -20,6 -20,8 +20,8 @@@ #include "commit-slab.h" #include "commit-graph.h" #include "commit-reach.h" + #include "worktree.h" + #include "hashmap.h" static struct ref_msg { const char *gone; @@@ -75,6 -77,27 +77,27 @@@ static struct expand_data struct object_info info; } oi, oi_deref; + struct ref_to_worktree_entry { + struct hashmap_entry ent; /* must be the first member! */ + struct worktree *wt; /* key is wt->head_ref */ + }; + + static int ref_to_worktree_map_cmpfnc(const void *unused_lookupdata, + const void *existing_hashmap_entry_to_test, + const void *key, + const void *keydata_aka_refname) + { + const struct ref_to_worktree_entry *e = existing_hashmap_entry_to_test; + const struct ref_to_worktree_entry *k = key; + return strcmp(e->wt->head_ref, + keydata_aka_refname ? keydata_aka_refname : k->wt->head_ref); + } + + static struct ref_to_worktree_map { + struct hashmap map; + struct worktree **worktrees; + } ref_to_worktree_map; + /* * An atom is a valid field atom listed below, possibly prefixed with * a "*" to denote deref_tag(). @@@ -480,6 -503,7 +503,7 @@@ static struct { "flag", SOURCE_NONE }, { "HEAD", SOURCE_NONE, FIELD_STR, head_atom_parser }, { "color", SOURCE_NONE, FIELD_STR, color_atom_parser }, + { "worktreepath", SOURCE_NONE }, { "align", SOURCE_NONE, FIELD_STR, align_atom_parser }, { "end", SOURCE_NONE }, { "if", SOURCE_NONE, FIELD_STR, if_atom_parser }, @@@ -1392,8 -1416,7 +1416,8 @@@ static void fill_remote_ref_details(str *s = show_ref(&atom->u.remote_ref.refname, refname); else if (atom->u.remote_ref.option == RR_TRACK) { if (stat_tracking_info(branch, &num_ours, &num_theirs, - NULL, AHEAD_BEHIND_FULL) < 0) { + NULL, atom->u.remote_ref.push, + AHEAD_BEHIND_FULL) < 0) { *s = xstrdup(msgs.gone); } else if (!num_ours && !num_theirs) *s = xstrdup(""); @@@ -1411,8 -1434,7 +1435,8 @@@ } } else if (atom->u.remote_ref.option == RR_TRACKSHORT) { if (stat_tracking_info(branch, &num_ours, &num_theirs, - NULL, AHEAD_BEHIND_FULL) < 0) { + NULL, atom->u.remote_ref.push, + AHEAD_BEHIND_FULL) < 0) { *s = xstrdup(""); return; } @@@ -1531,6 -1553,48 +1555,48 @@@ static int get_object(struct ref_array_ return 0; } + static void populate_worktree_map(struct hashmap *map, struct worktree **worktrees) + { + int i; + + for (i = 0; worktrees[i]; i++) { + if (worktrees[i]->head_ref) { + struct ref_to_worktree_entry *entry; + entry = xmalloc(sizeof(*entry)); + entry->wt = worktrees[i]; + hashmap_entry_init(entry, strhash(worktrees[i]->head_ref)); + + hashmap_add(map, entry); + } + } + } + + static void lazy_init_worktree_map(void) + { + if (ref_to_worktree_map.worktrees) + return; + + ref_to_worktree_map.worktrees = get_worktrees(0); + hashmap_init(&(ref_to_worktree_map.map), ref_to_worktree_map_cmpfnc, NULL, 0); + populate_worktree_map(&(ref_to_worktree_map.map), ref_to_worktree_map.worktrees); + } + + static char *get_worktree_path(const struct used_atom *atom, const struct ref_array_item *ref) + { + struct hashmap_entry entry; + struct ref_to_worktree_entry *lookup_result; + + lazy_init_worktree_map(); + + hashmap_entry_init(&entry, strhash(ref->refname)); + lookup_result = hashmap_get(&(ref_to_worktree_map.map), &entry, ref->refname); + + if (lookup_result) + return xstrdup(lookup_result->wt->path); + else + return xstrdup(""); + } + /* * Parse the object referred by ref, and grab needed value. */ @@@ -1568,6 -1632,13 +1634,13 @@@ static int populate_value(struct ref_ar if (starts_with(name, "refname")) refname = get_refname(atom, ref); + else if (!strcmp(name, "worktreepath")) { + if (ref->kind == FILTER_REFS_BRANCHES) + v->s = get_worktree_path(atom, ref); + else + v->s = xstrdup(""); + continue; + } else if (starts_with(name, "symref")) refname = get_symref(atom, ref); else if (starts_with(name, "upstream")) { @@@ -2051,6 -2122,11 +2124,11 @@@ void ref_array_clear(struct ref_array * free_array_item(array->items[i]); FREE_AND_NULL(array->items); array->nr = array->alloc = 0; + if (ref_to_worktree_map.worktrees) { + hashmap_free(&(ref_to_worktree_map.map), 1); + free_worktrees(ref_to_worktree_map.worktrees); + ref_to_worktree_map.worktrees = NULL; + } } static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata) @@@ -2339,13 -2415,8 +2417,13 @@@ void parse_ref_sorting(struct ref_sorti int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset) { - if (!arg) /* should --no-sort void the list ? */ - return -1; + /* + * NEEDSWORK: We should probably clear the list in this case, but we've + * already munged the global used_atoms list, which would need to be + * undone. + */ + BUG_ON_OPT_NEG(unset); + parse_ref_sorting(opt->value, arg); return 0; } diff --combined t/t3200-branch.sh index e9d7084d19,88719cc02c..411a70b0ce --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@@ -42,10 -42,6 +42,10 @@@ test_expect_success 'git branch a/b/c s git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c ' +test_expect_success 'git branch mb master... should create a branch' ' + git branch mb master... && test_path_is_file .git/refs/heads/mb +' + test_expect_success 'git branch HEAD should fail' ' test_must_fail git branch HEAD ' @@@ -206,18 -202,22 +206,22 @@@ test_expect_success 'git branch -M baz git worktree add -f bazdir2 baz && git branch -M baz bam && test $(git -C bazdir rev-parse --abbrev-ref HEAD) = bam && - test $(git -C bazdir2 rev-parse --abbrev-ref HEAD) = bam + test $(git -C bazdir2 rev-parse --abbrev-ref HEAD) = bam && + rm -r bazdir bazdir2 && + git worktree prune ' test_expect_success 'git branch -M baz bam should succeed within a worktree in which baz is checked out' ' git checkout -b baz && - git worktree add -f bazdir3 baz && + git worktree add -f bazdir baz && ( - cd bazdir3 && + cd bazdir && git branch -M baz bam && test $(git rev-parse --abbrev-ref HEAD) = bam ) && - test $(git rev-parse --abbrev-ref HEAD) = bam + test $(git rev-parse --abbrev-ref HEAD) = bam && + rm -r bazdir && + git worktree prune ' test_expect_success 'git branch -M master should work when master is checked out' ' @@@ -268,30 -268,6 +272,30 @@@ test_expect_success 'git branch --list test_must_fail git rev-parse refs/heads/t ' +test_expect_success 'deleting checked-out branch from repo that is a submodule' ' + test_when_finished "rm -rf repo1 repo2" && + + git init repo1 && + git init repo1/sub && + test_commit -C repo1/sub x && + git -C repo1 submodule add ./sub && + git -C repo1 commit -m "adding sub" && + + git clone --recurse-submodules repo1 repo2 && + git -C repo2/sub checkout -b work && + test_must_fail git -C repo2/sub branch -D work +' + +test_expect_success 'bare main worktree has HEAD at branch deleted by secondary worktree' ' + test_when_finished "rm -rf nonbare base secondary" && + + git init nonbare && + test_commit -C nonbare x && + git clone --bare nonbare bare && + git -C bare worktree add --detach ../secondary master && + git -C secondary branch -D master +' + test_expect_success 'git branch --list -v with --abbrev' ' test_when_finished "git branch -D t" && git branch t && @@@ -320,8 -296,8 +324,8 @@@ test_expect_success 'git branch --column' ' COLUMNS=81 git branch --column=column >actual && cat >expected <<\EOF && - a/b/c bam foo l * master n o/p r - abc bar j/k m/m master2 o/o q + a/b/c bam foo l * master mb o/o q + abc bar j/k m/m master2 n o/p r EOF test_cmp expected actual ' @@@ -343,7 -319,6 +347,7 @@@ test_expect_success 'git branch --colum m/m * master master2 + mb n o/o o/p @@@ -361,8 -336,8 +365,8 @@@ test_expect_success 'git branch with co git config --unset column.branch && git config --unset column.ui && cat >expected <<\EOF && - a/b/c bam foo l * master n o/p r - abc bar j/k m/m master2 o/o q + a/b/c bam foo l * master mb o/o q + abc bar j/k m/m master2 n o/p r EOF test_cmp expected actual ' @@@ -386,7 -361,6 +390,7 @@@ test_expect_success 'git branch -v wit m/m * master master2 + mb n o/o o/p @@@ -804,7 -778,9 +808,9 @@@ test_expect_success 'test deleting bran test_expect_success 'deleting currently checked out branch fails' ' git worktree add -b my7 my7 && test_must_fail git -C my7 branch -d my7 && - test_must_fail git branch -d my7 + test_must_fail git branch -d my7 && + rm -r my7 && + git worktree prune ' test_expect_success 'test --track without .fetch entries' ' diff --combined t/t3203-branch-output.sh index be55148930,4bef8c7569..71818b90f0 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@@ -136,11 -136,13 +136,14 @@@ test_expect_success 'git branch `--show branch-two EOF git checkout branch-one && - git worktree add worktree branch-two && ++ test_when_finished " ++ git worktree remove worktree_dir ++ " && + git worktree add worktree_dir branch-two && { git branch --show-current && - git -C worktree branch --show-current + git -C worktree_dir branch --show-current } >actual && - rm -r worktree_dir && - git worktree prune && test_cmp expect actual ' @@@ -284,6 -286,24 +287,24 @@@ test_expect_success 'git branch --forma test_i18ncmp expect actual ' + test_expect_success 'worktree colors correct' ' + cat >expect <<-EOF && + * (HEAD detached from fromtag) + ambiguous + branch-one + + branch-two + master + ref-to-branch -> branch-one + ref-to-remote -> origin/branch-one + EOF + git worktree add worktree_dir branch-two && + git branch --color >actual.raw && + rm -r worktree_dir && + git worktree prune && + test_decode_color actual && + test_i18ncmp expect actual + ' + test_expect_success "set up color tests" ' echo "master" >expect.color && echo "master" >expect.bare && @@@ -308,4 -328,23 +329,23 @@@ test_expect_success '--color overrides test_cmp expect.color actual ' + test_expect_success 'verbose output lists worktree path' ' + one=$(git rev-parse --short HEAD) && + two=$(git rev-parse --short master) && + cat >expect <<-EOF && + * (HEAD detached from fromtag) $one one + ambiguous $one one + branch-one $two two + + branch-two $one ($(pwd)/worktree_dir) one + master $two two + ref-to-branch $two two + ref-to-remote $two two + EOF + git worktree add worktree_dir branch-two && + git branch -vv >actual && + rm -r worktree_dir && + git worktree prune && + test_i18ncmp expect actual + ' + test_done