Skip to content

Commit c26dd5f

Browse files
committed
Merge branch 'nb/branch-show-other-worktrees-head' into pu
"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
2 parents efa92da + 6e93814 commit c26dd5f

File tree

7 files changed

+169
-15
lines changed

7 files changed

+169
-15
lines changed

Documentation/git-branch.txt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ DESCRIPTION
2626
-----------
2727

2828
If `--list` is given, or if there are no non-option arguments, existing
29-
branches are listed; the current branch will be highlighted with an
30-
asterisk. Option `-r` causes the remote-tracking branches to be listed,
29+
branches are listed; the current branch will be highlighted in green and
30+
marked with an asterisk. Any branches checked out in linked worktrees will
31+
be highlighted in cyan and marked with a plus sign. Option `-r` causes the
32+
remote-tracking branches to be listed,
3133
and option `-a` shows both local and remote branches. If a `<pattern>`
3234
is given, it is used as a shell wildcard to restrict the output to
3335
matching branches. If multiple patterns are given, a branch is shown if
@@ -174,8 +176,10 @@ This option is only applicable in non-verbose mode.
174176
When in list mode,
175177
show sha1 and commit subject line for each head, along with
176178
relationship to upstream branch (if any). If given twice, print
177-
the name of the upstream branch, as well (see also `git remote
178-
show <remote>`).
179+
the path of the linked worktree (if any) and the name of the upstream
180+
branch, as well (see also `git remote show <remote>`). Note that the
181+
current worktree's HEAD will not have its path printed (it will always
182+
be your current directory).
179183

180184
-q::
181185
--quiet::

Documentation/git-for-each-ref.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,11 @@ symref::
214214
`:lstrip` and `:rstrip` options in the same way as `refname`
215215
above.
216216

217+
worktreepath::
218+
The absolute path to the worktree in which the ref is checked
219+
out, if it is checked out in any linked worktree. Empty string
220+
otherwise.
221+
217222
In addition to the above, for commit and tag objects, the header
218223
field names (`tree`, `parent`, `object`, `type`, and `tag`) can
219224
be used to specify the value in the header field.

builtin/branch.c

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,16 @@ static char branch_colors[][COLOR_MAXLEN] = {
4747
GIT_COLOR_NORMAL, /* LOCAL */
4848
GIT_COLOR_GREEN, /* CURRENT */
4949
GIT_COLOR_BLUE, /* UPSTREAM */
50+
GIT_COLOR_CYAN, /* WORKTREE */
5051
};
5152
enum color_branch {
5253
BRANCH_COLOR_RESET = 0,
5354
BRANCH_COLOR_PLAIN = 1,
5455
BRANCH_COLOR_REMOTE = 2,
5556
BRANCH_COLOR_LOCAL = 3,
5657
BRANCH_COLOR_CURRENT = 4,
57-
BRANCH_COLOR_UPSTREAM = 5
58+
BRANCH_COLOR_UPSTREAM = 5,
59+
BRANCH_COLOR_WORKTREE = 6
5860
};
5961

6062
static const char *color_branch_slots[] = {
@@ -64,6 +66,7 @@ static const char *color_branch_slots[] = {
6466
[BRANCH_COLOR_LOCAL] = "local",
6567
[BRANCH_COLOR_CURRENT] = "current",
6668
[BRANCH_COLOR_UPSTREAM] = "upstream",
69+
[BRANCH_COLOR_WORKTREE] = "worktree",
6770
};
6871

6972
static struct string_list output = STRING_LIST_INIT_DUP;
@@ -342,9 +345,10 @@ static char *build_format(struct ref_filter *filter, int maxwidth, const char *r
342345
struct strbuf local = STRBUF_INIT;
343346
struct strbuf remote = STRBUF_INIT;
344347

345-
strbuf_addf(&local, "%%(if)%%(HEAD)%%(then)* %s%%(else) %s%%(end)",
346-
branch_get_color(BRANCH_COLOR_CURRENT),
347-
branch_get_color(BRANCH_COLOR_LOCAL));
348+
strbuf_addf(&local, "%%(if)%%(HEAD)%%(then)* %s%%(else)%%(if)%%(worktreepath)%%(then)+ %s%%(else) %s%%(end)%%(end)",
349+
branch_get_color(BRANCH_COLOR_CURRENT),
350+
branch_get_color(BRANCH_COLOR_WORKTREE),
351+
branch_get_color(BRANCH_COLOR_LOCAL));
348352
strbuf_addf(&remote, " %s",
349353
branch_get_color(BRANCH_COLOR_REMOTE));
350354

@@ -363,9 +367,13 @@ static char *build_format(struct ref_filter *filter, int maxwidth, const char *r
363367
strbuf_addf(&local, " %s ", obname.buf);
364368

365369
if (filter->verbose > 1)
370+
{
371+
strbuf_addf(&local, "%%(if:notequals=*)%%(HEAD)%%(then)%%(if)%%(worktreepath)%%(then)(%s%%(worktreepath)%s) %%(end)%%(end)",
372+
branch_get_color(BRANCH_COLOR_WORKTREE), branch_get_color(BRANCH_COLOR_RESET));
366373
strbuf_addf(&local, "%%(if)%%(upstream)%%(then)[%s%%(upstream:short)%s%%(if)%%(upstream:track)"
367374
"%%(then): %%(upstream:track,nobracket)%%(end)] %%(end)%%(contents:subject)",
368375
branch_get_color(BRANCH_COLOR_UPSTREAM), branch_get_color(BRANCH_COLOR_RESET));
376+
}
369377
else
370378
strbuf_addf(&local, "%%(if)%%(upstream:track)%%(then)%%(upstream:track) %%(end)%%(contents:subject)");
371379

ref-filter.c

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
#include "commit-slab.h"
2121
#include "commit-graph.h"
2222
#include "commit-reach.h"
23+
#include "worktree.h"
24+
#include "hashmap.h"
2325

2426
static struct ref_msg {
2527
const char *gone;
@@ -75,6 +77,27 @@ static struct expand_data {
7577
struct object_info info;
7678
} oi, oi_deref;
7779

80+
struct ref_to_worktree_entry {
81+
struct hashmap_entry ent; /* must be the first member! */
82+
struct worktree *wt; /* key is wt->head_ref */
83+
};
84+
85+
static int ref_to_worktree_map_cmpfnc(const void *unused_lookupdata,
86+
const void *existing_hashmap_entry_to_test,
87+
const void *key,
88+
const void *keydata_aka_refname)
89+
{
90+
const struct ref_to_worktree_entry *e = existing_hashmap_entry_to_test;
91+
const struct ref_to_worktree_entry *k = key;
92+
return strcmp(e->wt->head_ref,
93+
keydata_aka_refname ? keydata_aka_refname : k->wt->head_ref);
94+
}
95+
96+
static struct ref_to_worktree_map {
97+
struct hashmap map;
98+
struct worktree **worktrees;
99+
} ref_to_worktree_map;
100+
78101
/*
79102
* An atom is a valid field atom listed below, possibly prefixed with
80103
* a "*" to denote deref_tag().
@@ -480,6 +503,7 @@ static struct {
480503
{ "flag", SOURCE_NONE },
481504
{ "HEAD", SOURCE_NONE, FIELD_STR, head_atom_parser },
482505
{ "color", SOURCE_NONE, FIELD_STR, color_atom_parser },
506+
{ "worktreepath", SOURCE_NONE },
483507
{ "align", SOURCE_NONE, FIELD_STR, align_atom_parser },
484508
{ "end", SOURCE_NONE },
485509
{ "if", SOURCE_NONE, FIELD_STR, if_atom_parser },
@@ -1531,6 +1555,48 @@ static int get_object(struct ref_array_item *ref, int deref, struct object **obj
15311555
return 0;
15321556
}
15331557

1558+
static void populate_worktree_map(struct hashmap *map, struct worktree **worktrees)
1559+
{
1560+
int i;
1561+
1562+
for (i = 0; worktrees[i]; i++) {
1563+
if (worktrees[i]->head_ref) {
1564+
struct ref_to_worktree_entry *entry;
1565+
entry = xmalloc(sizeof(*entry));
1566+
entry->wt = worktrees[i];
1567+
hashmap_entry_init(entry, strhash(worktrees[i]->head_ref));
1568+
1569+
hashmap_add(map, entry);
1570+
}
1571+
}
1572+
}
1573+
1574+
static void lazy_init_worktree_map(void)
1575+
{
1576+
if (ref_to_worktree_map.worktrees)
1577+
return;
1578+
1579+
ref_to_worktree_map.worktrees = get_worktrees(0);
1580+
hashmap_init(&(ref_to_worktree_map.map), ref_to_worktree_map_cmpfnc, NULL, 0);
1581+
populate_worktree_map(&(ref_to_worktree_map.map), ref_to_worktree_map.worktrees);
1582+
}
1583+
1584+
static char *get_worktree_path(const struct used_atom *atom, const struct ref_array_item *ref)
1585+
{
1586+
struct hashmap_entry entry;
1587+
struct ref_to_worktree_entry *lookup_result;
1588+
1589+
lazy_init_worktree_map();
1590+
1591+
hashmap_entry_init(&entry, strhash(ref->refname));
1592+
lookup_result = hashmap_get(&(ref_to_worktree_map.map), &entry, ref->refname);
1593+
1594+
if (lookup_result)
1595+
return xstrdup(lookup_result->wt->path);
1596+
else
1597+
return xstrdup("");
1598+
}
1599+
15341600
/*
15351601
* Parse the object referred by ref, and grab needed value.
15361602
*/
@@ -1568,6 +1634,13 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
15681634

15691635
if (starts_with(name, "refname"))
15701636
refname = get_refname(atom, ref);
1637+
else if (!strcmp(name, "worktreepath")) {
1638+
if (ref->kind == FILTER_REFS_BRANCHES)
1639+
v->s = get_worktree_path(atom, ref);
1640+
else
1641+
v->s = xstrdup("");
1642+
continue;
1643+
}
15711644
else if (starts_with(name, "symref"))
15721645
refname = get_symref(atom, ref);
15731646
else if (starts_with(name, "upstream")) {
@@ -2051,6 +2124,11 @@ void ref_array_clear(struct ref_array *array)
20512124
free_array_item(array->items[i]);
20522125
FREE_AND_NULL(array->items);
20532126
array->nr = array->alloc = 0;
2127+
if (ref_to_worktree_map.worktrees) {
2128+
hashmap_free(&(ref_to_worktree_map.map), 1);
2129+
free_worktrees(ref_to_worktree_map.worktrees);
2130+
ref_to_worktree_map.worktrees = NULL;
2131+
}
20542132
}
20552133

20562134
static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata)

t/t3200-branch.sh

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -206,18 +206,22 @@ test_expect_success 'git branch -M baz bam should succeed when baz is checked ou
206206
git worktree add -f bazdir2 baz &&
207207
git branch -M baz bam &&
208208
test $(git -C bazdir rev-parse --abbrev-ref HEAD) = bam &&
209-
test $(git -C bazdir2 rev-parse --abbrev-ref HEAD) = bam
209+
test $(git -C bazdir2 rev-parse --abbrev-ref HEAD) = bam &&
210+
rm -r bazdir bazdir2 &&
211+
git worktree prune
210212
'
211213

212214
test_expect_success 'git branch -M baz bam should succeed within a worktree in which baz is checked out' '
213215
git checkout -b baz &&
214-
git worktree add -f bazdir3 baz &&
216+
git worktree add -f bazdir baz &&
215217
(
216-
cd bazdir3 &&
218+
cd bazdir &&
217219
git branch -M baz bam &&
218220
test $(git rev-parse --abbrev-ref HEAD) = bam
219221
) &&
220-
test $(git rev-parse --abbrev-ref HEAD) = bam
222+
test $(git rev-parse --abbrev-ref HEAD) = bam &&
223+
rm -r bazdir &&
224+
git worktree prune
221225
'
222226

223227
test_expect_success 'git branch -M master should work when master is checked out' '
@@ -804,7 +808,9 @@ test_expect_success 'test deleting branch without config' '
804808
test_expect_success 'deleting currently checked out branch fails' '
805809
git worktree add -b my7 my7 &&
806810
test_must_fail git -C my7 branch -d my7 &&
807-
test_must_fail git branch -d my7
811+
test_must_fail git branch -d my7 &&
812+
rm -r my7 &&
813+
git worktree prune
808814
'
809815

810816
test_expect_success 'test --track without .fetch entries' '

t/t3203-branch-output.sh

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,13 @@ test_expect_success 'git branch `--show-current` works properly with worktrees'
136136
branch-two
137137
EOF
138138
git checkout branch-one &&
139-
git worktree add worktree branch-two &&
139+
test_when_finished "
140+
git worktree remove worktree_dir
141+
" &&
142+
git worktree add worktree_dir branch-two &&
140143
{
141144
git branch --show-current &&
142-
git -C worktree branch --show-current
145+
git -C worktree_dir branch --show-current
143146
} >actual &&
144147
test_cmp expect actual
145148
'
@@ -284,6 +287,24 @@ test_expect_success 'git branch --format option' '
284287
test_i18ncmp expect actual
285288
'
286289

290+
test_expect_success 'worktree colors correct' '
291+
cat >expect <<-EOF &&
292+
* <GREEN>(HEAD detached from fromtag)<RESET>
293+
ambiguous<RESET>
294+
branch-one<RESET>
295+
+ <CYAN>branch-two<RESET>
296+
master<RESET>
297+
ref-to-branch<RESET> -> branch-one
298+
ref-to-remote<RESET> -> origin/branch-one
299+
EOF
300+
git worktree add worktree_dir branch-two &&
301+
git branch --color >actual.raw &&
302+
rm -r worktree_dir &&
303+
git worktree prune &&
304+
test_decode_color <actual.raw >actual &&
305+
test_i18ncmp expect actual
306+
'
307+
287308
test_expect_success "set up color tests" '
288309
echo "<RED>master<RESET>" >expect.color &&
289310
echo "master" >expect.bare &&
@@ -308,4 +329,23 @@ test_expect_success '--color overrides auto-color' '
308329
test_cmp expect.color actual
309330
'
310331

332+
test_expect_success 'verbose output lists worktree path' '
333+
one=$(git rev-parse --short HEAD) &&
334+
two=$(git rev-parse --short master) &&
335+
cat >expect <<-EOF &&
336+
* (HEAD detached from fromtag) $one one
337+
ambiguous $one one
338+
branch-one $two two
339+
+ branch-two $one ($(pwd)/worktree_dir) one
340+
master $two two
341+
ref-to-branch $two two
342+
ref-to-remote $two two
343+
EOF
344+
git worktree add worktree_dir branch-two &&
345+
git branch -vv >actual &&
346+
rm -r worktree_dir &&
347+
git worktree prune &&
348+
test_i18ncmp expect actual
349+
'
350+
311351
test_done

t/t6302-for-each-ref-filter.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,4 +441,17 @@ test_expect_success '--merged is incompatible with --no-merged' '
441441
test_must_fail git for-each-ref --merged HEAD --no-merged HEAD
442442
'
443443

444+
test_expect_success 'validate worktree atom' '
445+
cat >expect <<-EOF &&
446+
master: $(pwd)
447+
master_worktree: $(pwd)/worktree_dir
448+
side: not checked out
449+
EOF
450+
git worktree add -b master_worktree worktree_dir master &&
451+
git for-each-ref --format="%(refname:short): %(if)%(worktreepath)%(then)%(worktreepath)%(else)not checked out%(end)" refs/heads/ >actual &&
452+
rm -r worktree_dir &&
453+
git worktree prune &&
454+
test_cmp expect actual
455+
'
456+
444457
test_done

0 commit comments

Comments
 (0)