Skip to content

Commit d97eb30

Browse files
phillipwoodgitster
authored andcommitted
worktree: add -z option for list subcommand
Add a -z option to be used in conjunction with --porcelain that gives NUL-terminated output. As 'worktree list --porcelain' does not quote worktree paths this enables it to handle worktree paths that contain newlines. Signed-off-by: Phillip Wood <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent dab1b79 commit d97eb30

File tree

3 files changed

+55
-20
lines changed

3 files changed

+55
-20
lines changed

Documentation/git-worktree.txt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ SYNOPSIS
1010
--------
1111
[verse]
1212
'git worktree add' [-f] [--detach] [--checkout] [--lock [--reason <string>]] [-b <new-branch>] <path> [<commit-ish>]
13-
'git worktree list' [-v | --porcelain]
13+
'git worktree list' [-v | --porcelain [-z]]
1414
'git worktree lock' [--reason <string>] <worktree>
1515
'git worktree move' <worktree> <new-path>
1616
'git worktree prune' [-n] [-v] [--expire <expire>]
@@ -223,7 +223,14 @@ This can also be set up as the default behaviour by using the
223223
--porcelain::
224224
With `list`, output in an easy-to-parse format for scripts.
225225
This format will remain stable across Git versions and regardless of user
226-
configuration. See below for details.
226+
configuration. It is recommended to combine this with `-z`.
227+
See below for details.
228+
229+
-z::
230+
Terminate each line with a NUL rather than a newline when
231+
`--porcelain` is specified with `list`. This makes it possible
232+
to parse the output when a worktree path contains a newline
233+
character.
227234

228235
-q::
229236
--quiet::
@@ -411,7 +418,8 @@ working tree itself.
411418

412419
Porcelain Format
413420
~~~~~~~~~~~~~~~~
414-
The porcelain format has a line per attribute. Attributes are listed with a
421+
The porcelain format has a line per attribute. If `-z` is given then the lines
422+
are terminated with NUL rather than a newline. Attributes are listed with a
415423
label and value separated by a single space. Boolean attributes (like `bare`
416424
and `detached`) are listed as a label only, and are present only
417425
if the value is true. Some attributes (like `locked`) can be listed as a label
@@ -449,7 +457,7 @@ prunable gitdir file points to non-existent location
449457

450458
------------
451459

452-
If the lock reason contains "unusual" characters such as newline, they
460+
Unless `-z` is used any "unusual" characters in the lock reason such as newlines
453461
are escaped and the entire reason is quoted as explained for the
454462
configuration variable `core.quotePath` (see linkgit:git-config[1]).
455463
For Example:

builtin/worktree.c

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -575,35 +575,37 @@ static int add(int ac, const char **av, const char *prefix)
575575
return add_worktree(path, branch, &opts);
576576
}
577577

578-
static void show_worktree_porcelain(struct worktree *wt)
578+
static void show_worktree_porcelain(struct worktree *wt, int line_terminator)
579579
{
580580
const char *reason;
581581

582-
printf("worktree %s\n", wt->path);
582+
printf("worktree %s%c", wt->path, line_terminator);
583583
if (wt->is_bare)
584-
printf("bare\n");
584+
printf("bare%c", line_terminator);
585585
else {
586-
printf("HEAD %s\n", oid_to_hex(&wt->head_oid));
586+
printf("HEAD %s%c", oid_to_hex(&wt->head_oid), line_terminator);
587587
if (wt->is_detached)
588-
printf("detached\n");
588+
printf("detached%c", line_terminator);
589589
else if (wt->head_ref)
590-
printf("branch %s\n", wt->head_ref);
590+
printf("branch %s%c", wt->head_ref, line_terminator);
591591
}
592592

593593
reason = worktree_lock_reason(wt);
594-
if (reason && *reason) {
595-
struct strbuf sb = STRBUF_INIT;
596-
quote_c_style(reason, &sb, NULL, 0);
597-
printf("locked %s\n", sb.buf);
598-
strbuf_release(&sb);
599-
} else if (reason)
600-
printf("locked\n");
594+
if (reason) {
595+
fputs("locked", stdout);
596+
if (*reason) {
597+
fputc(' ', stdout);
598+
write_name_quoted(reason, stdout, line_terminator);
599+
} else {
600+
fputc(line_terminator, stdout);
601+
}
602+
}
601603

602604
reason = worktree_prune_reason(wt, expire);
603605
if (reason)
604-
printf("prunable %s\n", reason);
606+
printf("prunable %s%c", reason, line_terminator);
605607

606-
printf("\n");
608+
fputc(line_terminator, stdout);
607609
}
608610

609611
static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
@@ -681,12 +683,15 @@ static void pathsort(struct worktree **wt)
681683
static int list(int ac, const char **av, const char *prefix)
682684
{
683685
int porcelain = 0;
686+
int line_terminator = '\n';
684687

685688
struct option options[] = {
686689
OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")),
687690
OPT__VERBOSE(&verbose, N_("show extended annotations and reasons, if available")),
688691
OPT_EXPIRY_DATE(0, "expire", &expire,
689692
N_("add 'prunable' annotation to worktrees older than <time>")),
693+
OPT_SET_INT('z', NULL, &line_terminator,
694+
N_("terminate records with a NUL character"), '\0'),
690695
OPT_END()
691696
};
692697

@@ -696,6 +701,8 @@ static int list(int ac, const char **av, const char *prefix)
696701
usage_with_options(worktree_usage, options);
697702
else if (verbose && porcelain)
698703
die(_("options '%s' and '%s' cannot be used together"), "--verbose", "--porcelain");
704+
else if (!line_terminator && !porcelain)
705+
die(_("the option '%s' requires '%s'"), "-z", "--porcelain");
699706
else {
700707
struct worktree **worktrees = get_worktrees();
701708
int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i;
@@ -708,7 +715,8 @@ static int list(int ac, const char **av, const char *prefix)
708715

709716
for (i = 0; worktrees[i]; i++) {
710717
if (porcelain)
711-
show_worktree_porcelain(worktrees[i]);
718+
show_worktree_porcelain(worktrees[i],
719+
line_terminator);
712720
else
713721
show_worktree(worktrees[i], path_maxlen, abbrev);
714722
}

t/t2402-worktree-list.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,25 @@ test_expect_success '"list" all worktrees --porcelain' '
6464
test_cmp expect actual
6565
'
6666

67+
test_expect_success '"list" all worktrees --porcelain -z' '
68+
test_when_finished "rm -rf here _actual actual expect &&
69+
git worktree prune" &&
70+
printf "worktree %sQHEAD %sQbranch %sQQ" \
71+
"$(git rev-parse --show-toplevel)" \
72+
$(git rev-parse HEAD --symbolic-full-name HEAD) >expect &&
73+
git worktree add --detach here main &&
74+
printf "worktree %sQHEAD %sQdetachedQQ" \
75+
"$(git -C here rev-parse --show-toplevel)" \
76+
"$(git rev-parse HEAD)" >>expect &&
77+
git worktree list --porcelain -z >_actual &&
78+
nul_to_q <_actual >actual &&
79+
test_cmp expect actual
80+
'
81+
82+
test_expect_success '"list" -z fails without --porcelain' '
83+
test_must_fail git worktree list -z
84+
'
85+
6786
test_expect_success '"list" all worktrees with locked annotation' '
6887
test_when_finished "rm -rf locked unlocked out && git worktree prune" &&
6988
git worktree add --detach locked main &&

0 commit comments

Comments
 (0)