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 <phillip.wood@dunelm.org.uk> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Phillip Wood committed
Mar 31, 2022 at 16:21 UTC
d97eb302ea848ff6f8f9af50a176734930964b9a
3 files changed
+55
-20
Documentation/git-worktree.txt
+12
-4
@@ -10,7 +10,7 @@ SYNOPSIS
10
--------
11
[verse]
12
'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]]
14
'git worktree lock' [--reason <string>] <worktree>
15
'git worktree move' <worktree> <new-path>
16
'git worktree prune' [-n] [-v] [--expire <expire>]
@@ -223,7 +223,14 @@ This can also be set up as the default behaviour by using the
223
--porcelain::
224
With `list`, output in an easy-to-parse format for scripts.
225
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.
234
235
-q::
236
--quiet::
@@ -411,7 +418,8 @@ working tree itself.
418
419
Porcelain Format
420
~~~~~~~~~~~~~~~~
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
423
label and value separated by a single space. Boolean attributes (like `bare`
424
and `detached`) are listed as a label only, and are present only
425
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
457
458
------------
459
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
461
are escaped and the entire reason is quoted as explained for the
462
configuration variable `core.quotePath` (see linkgit:git-config[1]).
463
For Example:
builtin/worktree.c
+24
-16
@@ -575,35 +575,37 @@ static int add(int ac, const char **av, const char *prefix)
575
return add_worktree(path, branch, &opts);
576
}
577
578
-static void show_worktree_porcelain(struct worktree *wt)
578
+static void show_worktree_porcelain(struct worktree *wt, int line_terminator)
579
{
580
const char *reason;
581
582
- printf("worktree %s\n", wt->path);
582
+ printf("worktree %s%c", wt->path, line_terminator);
583
if (wt->is_bare)
584
- printf("bare\n");
584
+ printf("bare%c", line_terminator);
585
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);
587
if (wt->is_detached)
588
- printf("detached\n");
588
+ printf("detached%c", line_terminator);
589
else if (wt->head_ref)
590
- printf("branch %s\n", wt->head_ref);
590
+ printf("branch %s%c", wt->head_ref, line_terminator);
591
}
592
593
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
+ }
603
604
reason = worktree_prune_reason(wt, expire);
605
if (reason)
604
- printf("prunable %s\n", reason);
606
+ printf("prunable %s%c", reason, line_terminator);
607
606
- printf("\n");
608
+ fputc(line_terminator, stdout);
609
}
610
611
static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
@@ -681,12 +683,15 @@ static void pathsort(struct worktree **wt)
683
static int list(int ac, const char **av, const char *prefix)
684
{
685
int porcelain = 0;
686
+ int line_terminator = '\n';
687
688
struct option options[] = {
689
OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")),
690
OPT__VERBOSE(&verbose, N_("show extended annotations and reasons, if available")),
691
OPT_EXPIRY_DATE(0, "expire", &expire,
692
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'),
695
OPT_END()
696
};
697
@@ -696,6 +701,8 @@ static int list(int ac, const char **av, const char *prefix)
701
usage_with_options(worktree_usage, options);
702
else if (verbose && porcelain)
703
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");
706
else {
707
struct worktree **worktrees = get_worktrees();
708
int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i;
@@ -708,7 +715,8 @@ static int list(int ac, const char **av, const char *prefix)
715
716
for (i = 0; worktrees[i]; i++) {
717
if (porcelain)
711
- show_worktree_porcelain(worktrees[i]);
718
+ show_worktree_porcelain(worktrees[i],
719
+ line_terminator);
720
else
721
show_worktree(worktrees[i], path_maxlen, abbrev);
722
}
t/t2402-worktree-list.sh
+19
@@ -64,6 +64,25 @@ test_expect_success '"list" all worktrees --porcelain' '
64
test_cmp expect actual
65
'
66
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
+
86
test_expect_success '"list" all worktrees with locked annotation' '
87
test_when_finished "rm -rf locked unlocked out && git worktree prune" &&
88
git worktree add --detach locked main &&