From: Rafael Silva <hidden> Date: 2021-01-04 16:23:11
In c57b3367be (worktree: teach `list` to annotate locked worktree,
2020-10-11) we taught `git worktree list` to annotate working tree that
is locked by appending "locked" text in order to signalize to the user
that a working tree is locked. During the review, there was some
discussion about additional annotations and information that `list`
command could provide to the user that has long been envisioned and
mentioned in [2], [3] and [4].
This patch series address some of these changes by teaching
`worktree list` to show "prunable" annotation, adding verbose mode and
extending the --porcelain format with prunable and locked annotation as
follow up from [1]. Additionally, it address one shortcoming for porcelain
format to escape any newline characters (LF and CRLF) for the lock reason
to prevent breaking format mentioned in [4] and [1] during the review
cycle.
I have tried to organise in a way the series is easy to review, the
patches are organised in seven patches.
The first moves the should_prune_worktree() machinery to the top-level
worktree.c exposing the function as general API, that will be reference
by should_prune_worktree() wrapper implemented on the second patch. The
original idea was to not only move should_prune_worktree() but also
refactor to accept "struct worktree" and load the information directly,
which can simplify the `prune` command by reusing get_worktrees().
However this seems to also require refactoring get_worktrees() itself
to return "non-valid" working trees that can/should be pruned. This is
also mentioned in [5]. Having the wrapper function makes it easier to add
the prunable annotation without touching the get_worktrees() and the
other worktree sub commands. The refactoring can be addressed in a
future patch, if this turns out to be good idea. One possible approach
is to teach get_worktrees() to take additional flags that will tell
whether to return only valid or all worktrees in GIT_DIR/worktrees/
directory and address its own possible shortcoming, if any.
The third patch changes worktree_lock_reason() to be more gentle for the
main working tree to simply returning NULL instead of aborting the
program via assert() macro. This allow us to simplify the code that
checks if the working tree is locked for default and porcelain format.
This changes is also mentioned in [6].
The forth patch is the one that teaches `list` command to show prunable
annotation, for both default and porcelain format, and adds the verbose
option. These changes are also done for the "locked" annotation that was
not done on in [1]. Additionally, `list` learns to fail when both
--verbose and --porcelain format as used together.
The fifth patch adds worktree_escape_reason() that accepts a (char *)
text and returned the text with any LF or CRLF escaped. The caller is
responsible to freeing the escaped text. This is used by the locked
annotation in porcelain format. Currently, this is defined within
builtin/worktree.c as I was not sure whether libfying the function as
part of this series is a good idea. At this time it seems more sensible
to leave the code internally and libfying later once we are confident
about the implementation and whether it can be used in other part of the
code base but I'm open for suggestion.
The sixth and seventh patches adds tests and update the git-worktree.txt
documentation accordingly.
[1] https://lore.kernel.org/git/20200928154953.30396-1-rafaeloliveira.cs@gmail.com/
[2] https://lore.kernel.org/git/CAPig+cQF6V8HNdMX5AZbmz3_w2WhSfA4SFfNhQqxXBqPXTZL+w@mail.gmail.com/
[3] https://lore.kernel.org/git/CAPig+cSGXqJuaZPhUhOVX5X=LMrjVfv8ye_6ncMUbyKox1i7QA@mail.gmail.com/
[4] https://lore.kernel.org/git/CAPig+cTitWCs5vB=0iXuUyEY22c0gvjXvY1ZtTT90s74ydhE=A@mail.gmail.com/
[5] https://lore.kernel.org/git/CACsJy8ChM99n6skQCv-GmFiod19mnwwH4j-6R+cfZSiVFAxjgA@mail.gmail.com/
[6] https://lore.kernel.org/git/xmqq8sctlgzx.fsf@gitster.c.googlers.com/
Series is built on top of 71ca53e8125e36efbda17293c50027d31681a41f, currently
poiting to master and v2.30.0 tag.
Rafael Silva (7):
worktree: move should_prune_worktree() to worktree.c
worktree: implement worktree_prune_reason() wrapper
worktree: teach worktree_lock_reason() to gently handle main worktree
worktree: teach `list` prunable annotation and verbose
worktree: `list` escape lock reason in --porcelain
worktree: add tests for `list` verbose and annotations
worktree: document `list` verbose and prunable annotations
Documentation/git-worktree.txt | 59 ++++++++++++++-
builtin/worktree.c | 130 ++++++++++++++-------------------
t/t2402-worktree-list.sh | 94 +++++++++++++++++++++++-
worktree.c | 96 +++++++++++++++++++++++-
worktree.h | 17 +++++
5 files changed, 313 insertions(+), 83 deletions(-)
--
2.30.0.391.g469bf2a980
From: Rafael Silva <hidden> Date: 2021-01-04 16:23:11
The main worktree should not be locked and the worktree_lock_reason() API
is aware of this fact and avoids running the check code for the main
worktree. This checks is done via assert() macro, Therefore the caller
needs to ensure the function is never called, usually by additional code.
We can handle that case more gently by just returning false for the main
worktree and not bother checking if the "locked" file exists. This will
allowed further simplification from the caller as they will not need to
ensure the main worktree is never passed to the API.
Teach worktree_lock_reason() to be more gently and just return false for
the main working tree.
Signed-off-by: Rafael Silva <redacted>
---
worktree.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
From: Rafael Silva <hidden> Date: 2021-01-04 16:23:12
Add tests for "git worktree list" verbose mode, prunable and locked
annotations for both default and porcelain format and ensure the
"prunable" annotation is consistent with what "git worktree prune"
command will eventually remove. Additionally, add one test case to
ensure any newline characters are escaped from locked reason for the
porcelain format to prevent breaking the format.
The c57b3367be (worktree: teach `list` to annotate locked worktree,
2020-10-11) introduced a new test to ensure locked worktrees are listed
with "locked" annotation. However, the test does not remove the worktree
as the "git worktree prune" is not going to remove any locked worktrees.
Let's fix that by unlocking the worktree before the "prune" command.
Signed-off-by: Rafael Silva <redacted>
---
t/t2402-worktree-list.sh | 94 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 93 insertions(+), 1 deletion(-)
@@ -62,7 +62,9 @@ test_expect_success '"list" all worktrees --porcelain' '' test_expect_success'"list" all worktrees with locked annotation''-test_when_finished"rm -rf locked unlocked out && git worktree prune"&&+test_when_finished"rm -rf locked unlocked out &&+gitworktreeunlocklocked&&+gitworktreeprune" &&gitworktreeadd--detachlockedmaster&&gitworktreeadd--detachunlockedmaster&&gitworktreelocklocked&&
@@ -71,6 +73,96 @@ test_expect_success '"list" all worktrees with locked annotation' '!grep"/unlocked *[0-9a-f].* locked$"out'+test_expect_success'"list" all worktrees with prunable annotation''+test_when_finished"rm -rf prunable unprunable out && git worktree prune"&&+gitworktreeadd--detachprunable&&+gitworktreeadd--detachunprunable&&+rm-rfprunable&&+gitworktreelist>out&&+grep"/prunable *[0-9a-f].* prunable$"out&&+!grep"/unprunable *[0-9a-f].* prunable$"+'++test_expect_success'"list" all worktrees with prunable consistent with "prune"''+test_when_finished"rm -rf prunable out && git worktree prune"&&+gitworktreeadd--detachprunable&&+rm-rfprunable&&+gitworktreelist>out&&+grep"/prunable *[0-9a-f].* prunable$"out&&+gitworktreeprune--verbose>out&&+test_i18ngrep"^Removing worktrees/prunable"out+'++test_expect_success'"list" all worktrees --porcelain with locked''+test_when_finished"rm -rf locked1 locked2 out actual expect &&+gitworktreeunlocklocked1&&+gitworktreeunlocklocked2&&+gitworktreeprune" &&+echo"locked">expect&&+echo"locked with reason">>expect&&+gitworktreeaddlocked1--detach&&+gitworktreeaddlocked2--detach&&+gitworktreelocklocked1&&+gitworktreelocklocked2--reason"with reason"&&+gitworktreelist--porcelain>out&&+grep"^locked"out>actual&&+test_cmpexpectactual+'++test_expect_success'"list" all worktrees --porcelain with newline escaped locked reason''+test_when_finished"rm -rf locked_lf locked_crlf reason_lf reason_crlf out actual expect reason &&+gitworktreeunlocklocked_lf&&+gitworktreeunlocklocked_crlf&&+gitworktreeprune" &&+printf"locked locked\\\\r\\\\nreason\n">expect&&+printf"locked locked\\\\nreason\n">>expect&&+gitworktreeaddlocked_lf--detach&&+gitworktreeaddlocked_crlf--detach&&+printf"locked\nreason\n\n">reason_lf&&+printf"locked\r\nreason\n\n">reason_crlf&&+gitworktreelocklocked_lf--reason"$(catreason_lf)"&&+gitworktreelocklocked_crlf--reason"$(catreason_crlf)"&&+gitworktreelist--porcelain>out&&+grep"^locked"out>actual&&+test_cmpexpectactual+'++test_expect_success'"list" all worktrees --porcelain with prunable''+test_when_finished"rm -rf prunable list out && git worktree prune"&&+gitworktreeadd--detachprunable&&+rm-rfprunable&&+gitworktreelist--porcelain>out&&+test_i18ngrep"^prunable gitdir file points to non-existent location$"out+'++test_expect_success'"list" all worktrees --verbose and --porcelain mutually exclusive''+test_must_failgitworktreelist--verbose--porcelain+'++test_expect_success'"list" all worktrees --verbose with locked''+test_when_finished"rm -rf locked out actual expect &&+gitworktreeunlocklocked&&+gitworktreeprune" &&+gitworktreeaddlocked--detach&&+gitworktreelocklocked--reason"with reason"&&+echo"$(git-Clockedrev-parse--show-toplevel)$(gitrev-parse--shortHEAD) (detached HEAD)">expect&&+printf"\tlocked: with reason\n">>expect&&+gitworktreelist--verbose>out&&+sed-n"s/ */ /g;/\/locked *[0-9a-f].*$/,/locked: .*$/p"<out>actual&&+test_cmpactualexpect+'++test_expect_success'"list" all worktrees --verbose with prunable''+test_when_finished"rm -rf prunable out actual expect && git worktree prune"&&+gitworktreeaddprunable--detach&&+echo"$(git-Cprunablerev-parse--show-toplevel)$(gitrev-parse--shortHEAD) (detached HEAD)">expect&&+printf"\tprunable: gitdir file points to non-existent location\n">>expect&&+rm-rfprunable&&+gitworktreelist--verbose>out&&+sed-n"s/ */ /g;/\/prunable *[0-9a-f].*$/,/prunable: .*$/p"<out>actual&&+test_i18ncmpactualexpect+'+ test_expect_success'bare repo setup''gitinit--barebare1&&echo"data">file1&&
From: Rafael Silva <hidden> Date: 2021-01-04 16:23:33
The "git worktree list" command shows the absolute path to the worktree,
the commit that is checked out, the name of the branch, and a "locked"
annotation if the worktree is locked. It is not clear whether a worktree,
if any, is prunable. The "prune" command will remove a worktree in case
is a prunable candidate unless --dry-run option is specified. This could
lead to a worktree being removed without the user realizing before is to
late, in case the user forgets to pass --dry-run for instance.
If the "list" command shows which worktree is prunable, the user could
verify before running "git worktree prune" and hopefully prevents the
working tree to be removed "accidently" on the worse case scenario.
Let's teach "git worktree list" to show when a worktree is prunable by
appending "prunable" text to its output by default and show a prunable
label for the porcelain format followed by the reason, if the avaiable.
While at it, let's do the same for the "locked" annotation.
Also, the worktree_prune_reason() stores the reason why git is selecting
the worktree to be pruned, so let's leverage that and also display this
information to the user. However, the reason is human readable text that
can take virtually any size which might make harder to extend the "list"
command with additional annotations and not fit nicely on the screen.
In order to address this shortcoming, let's teach "git worktree list" to
take a verbose option that will output the prune reason on the next line
indented, if the reason is available, otherwise the annotation is kept on
the same line. While at it, let's do the same for the "locked"
annotation.
The output of "git worktree list" with verbose becomes like so:
$ git worktree list --verbose
/path/to/main aba123 [main]
/path/to/locked acb124 [branch-a] locked
/path/to/locked-reason acc125 [branch-b]
locked: worktree with locked reason
/path/to/prunable acd126 [branch-c] prunable
/path/to/prunable-reason ace127 [branch-d]
prunable: gitdir file points to non-existent location
And the output of the "git worktree list --porcelain" becomes like so:
$ git worktree list --porcelain
worktree /path/to/main
HEAD abc1234abc1234abc1234abc1234abc1234abc12
branch refs/heads/main
worktree /path/to/linked
HEAD 123abc456def789abc1234def5678abc9123abce
branch refs/heads/linked
worktree /path/to/locked
HEAD 123abcdea123abcd123acbd123acbda123abcd12
detached
locked lock reason
worktree /path/to/prunable
HEAD def1234def1234def1234def1234def1234def1a
detached
prunable prunable reason
Signed-off-by: Rafael Silva <redacted>
---
builtin/worktree.c | 35 +++++++++++++++++++++++++++++++++--
1 file changed, 33 insertions(+), 2 deletions(-)
@@ -604,8 +618,19 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)strbuf_addstr(&sb,"(error)");}-if(!is_main_worktree(wt)&&worktree_lock_reason(wt))-strbuf_addstr(&sb," locked");+if(worktree_lock_reason(wt)){+if(verbose&&*wt->lock_reason)+strbuf_addf(&sb,"\n\tlocked: %s",wt->lock_reason);+else+strbuf_addstr(&sb," locked");+}++if(worktree_prune_reason(wt,expire)){+if(verbose&&*wt->prune_reason)+strbuf_addf(&sb,"\n\tprunable: %s",wt->prune_reason);+else+strbuf_addstr(&sb," prunable");+}printf("%s\n",sb.buf);strbuf_release(&sb);
@@ -650,12 +675,18 @@ static int list(int ac, const char **av, const char *prefix)structoptionoptions[]={OPT_BOOL(0,"porcelain",&porcelain,N_("machine-readable output")),+OPT__VERBOSE(&verbose,N_("show extended annotations and reasons, if available")),+OPT_EXPIRY_DATE(0,"expire",&expire,+N_("show working trees that is candidate to be pruned older than <time>")),OPT_END()};+expire=TIME_MAX;ac=parse_options(ac,av,prefix,options,worktree_usage,0);if(ac)usage_with_options(worktree_usage,options);+elseif(verbose&&porcelain)+die(_("--verbose and --porcelain are mutually exclusive"));else{structworktree**worktrees=get_worktrees();intpath_maxlen=0,abbrev=DEFAULT_ABBREV,i;
From: Rafael Silva <hidden> Date: 2021-01-04 16:23:33
Update the documentation with "git worktree list" verbose mode, prunable
and locked annotations for the default and porcelain format as part of
teaching the command to show prunable working trees and verbose mode.
Signed-off-by: Rafael Silva <redacted>
---
Documentation/git-worktree.txt | 59 ++++++++++++++++++++++++++++++++--
1 file changed, 56 insertions(+), 3 deletions(-)
@@ -97,8 +97,9 @@ list:: List details of each working tree. The main working tree is listed first, followed by each of the linked working trees. The output details include whether the working tree is bare, the revision currently checked out, the-branch currently checked out (or "detached HEAD" if none), and "locked" if-the worktree is locked.+branch currently checked out (or "detached HEAD" if none), "locked" if+the worktree is locked, "prunable" if the worktree can be pruned by `prune`+command. lock::
@@ -226,9 +227,12 @@ This can also be set up as the default behaviour by using the -v:: --verbose:: With `prune`, report all removals.+ With `list`, output additional information for working trees. --expire <time>:: With `prune`, only expire unused working trees older than `<time>`.+ With `list`, annotate unused working trees older than `<time>` as prunable+ candidates that will be remove by `prune` command if the same option is used. --reason <string>:: With `lock`, an explanation why the working tree is locked.
@@ -367,13 +371,48 @@ $ git worktree list /path/to/other-linked-worktree 1234abc (detached HEAD) ------------+The command also shows annotations for each working tree, according to its state.+These annotations are:++ * "locked", if any working tree is locked+ * "prunable", if any working tree can be pruned via "git worktree prune".++------------+$ git worktree list+/path/to/linked-worktree abcd1234 [master]+/path/to/locked-worktreee acbd5678 (brancha) locked+/path/to/prunable-worktree 5678abc (detached HEAD) prunable+------------++For these annotations, a reason might also be available and this can be+seen using the verbose mode. The annotation is then moved to the next line+indented followed by the additional information.++------------+$ git worktree list --verbose+/path/to/linked-worktree abcd1234 [master]+/path/to/locked-worktreee acbd5678 (brancha)+ locked: working tree path is mounted on a removable device+/path/to/locked-no-reason abcd578 (detached HEAD) locked+/path/to/prunable-worktree 5678abc (detached HEAD)+ prunable: gitdir file points to non-existent location+------------++Note that, the annotation is only moved to the next line only if the+additional text is available, otherwise the text is kept on the same.+ Porcelain Format ~~~~~~~~~~~~~~~~ The porcelain format has a line per attribute. Attributes are listed with a label and value separated by a single space. Boolean attributes (like `bare` and `detached`) are listed as a label only, and are present only if the value is true. The first attribute of a working tree is always-`worktree`, an empty line indicates the end of the record. For example:+`worktree`, an empty line indicates the end of the record.+++In case any of the working trees are locked or is a candidate for pruning+(See DESCRIPTION above) the labels "locked" and "prunable" is also shown+followed by a reason, if available, otherwise only the labels are listed.+For example: ------------ $ git worktree list --porcelain
@@ -388,6 +427,20 @@ worktree /path/to/other-linked-worktree HEAD 1234abc1234abc1234abc1234abc1234abc1234a detached+worktree /path/to/linked-worktree-locked+HEAD 5678abc5678abc5678abc5678abc5678abc5678c+branch refs/heads/locked+locked++worktree /path/to/linked-worktree-locked-with-reason+HEAD 3456def3456def3456def3456def3456def3456b+branch refs/heads/locked-with-reason+locked reason why is locked++worktree /path/to/linked-worktree-prunable+HEAD 1233def1234def1234def1234def1234def1234b+detached+prunable gitdir file points to non-existent location ------------ EXAMPLES
From: Rafael Silva <hidden> Date: 2021-01-04 16:24:07
"git worktree list" porcelain format shows one attribute per line, each
attribute is listed with a label and value separated by a single space.
If any worktree is locked, a "locked" attribute is listed followed by the
reason, if available, otherwise only "locked" is shown.
In case the lock reason contains newline characters (i.e: LF or CRLF)
this will cause the format to break as each line should correspond to
one attribute. This leads to unwanted behavior, specially as the
porcelain is intended to be machine-readable. To address this shortcoming
let's teach "git worktree list" to escape any newline character before
outputting the locked reason for porcelain format.
Signed-off-by: Rafael Silva <redacted>
---
builtin/worktree.c | 26 +++++++++++++++++++++++---
1 file changed, 23 insertions(+), 3 deletions(-)
From: Rafael Silva <hidden> Date: 2021-01-04 16:24:08
As part of teaching "git worktree list" to annotate worktree that is a
candidate for pruning, let's move should_prune_worktree() from
builtin/worktree.c to worktree.c in order to make part of the worktree
public API.
This function will be used by another API function, implemented in the
next patch that will accept a pointer to "worktree" structure directly
making it easier to implement the "prunable" annotations.
should_prune_worktree() knows how to select the given worktree for pruning
based on an expiration date, however the expiration value is stored on a
global variable and it is not local to the function. In order to move the
function, teach should_prune_worktree() to take the expiration date as an
argument.
Signed-off-by: Rafael Silva <redacted>
---
builtin/worktree.c | 75 +---------------------------------------------
worktree.c | 73 ++++++++++++++++++++++++++++++++++++++++++++
worktree.h | 10 +++++++
3 files changed, 84 insertions(+), 74 deletions(-)
From: Rafael Silva <hidden> Date: 2021-01-04 16:24:09
The should_prune_worktree() machinery is used by the "prune" command to
identify whether a worktree is a candidate for pruning. This function
however, is not prepared to work directly with "struct worktree" and
refactoring is required not only on the function itself, but also also
changing get_worktrees() to return non-valid worktrees and address the
changes in all "worktree" sub commands.
Instead let's implement worktree_prune_reason() that accepts
"struct worktree" and uses should_prune_worktree() and returns whether
the given worktree is a candidate for pruning. As the "list" sub command
already uses a list of "struct worktree", this allow to simply check if
the working tree prunable by passing the structure directly without the
others parameters.
Also, let's add prune_reason field to the worktree structure that will
store the reason why the worktree can be pruned that is returned by
should_prune_worktree() when such reason is available.
Signed-off-by: Rafael Silva <redacted>
---
worktree.c | 19 +++++++++++++++++++
worktree.h | 7 +++++++
2 files changed, 26 insertions(+)
@@ -11,6 +11,7 @@ struct worktree {char*id;char*head_ref;/* NULL if HEAD is broken or detached */char*lock_reason;/* private - use worktree_lock_reason */+char*prune_reason;/* private - use worktree_prune_reason */structobject_idhead_oid;intis_detached;intis_bare;
@@ -73,6 +74,12 @@ int is_main_worktree(const struct worktree *wt);*/constchar*worktree_lock_reason(structworktree*wt);+/*+*Returnthereasonstringifthegivenworktreeshouldbepruned+*orNULLotherwise.+*/+constchar*worktree_prune_reason(structworktree*wt,timestamp_texpire);+/**Returntrueifworktreeentryshouldbepruned,alongwiththereasonfor*pruning.Otherwise,returnfalseandtheworktree'spath,orNULLifit
Hi Rafael
Thanks for working on this
On 04/01/2021 16:21, Rafael Silva wrote:
"git worktree list" porcelain format shows one attribute per line, each
attribute is listed with a label and value separated by a single space.
If any worktree is locked, a "locked" attribute is listed followed by the
reason, if available, otherwise only "locked" is shown.
In case the lock reason contains newline characters (i.e: LF or CRLF)
this will cause the format to break as each line should correspond to
one attribute. This leads to unwanted behavior, specially as the
porcelain is intended to be machine-readable. To address this shortcoming
let's teach "git worktree list" to escape any newline character before
outputting the locked reason for porcelain format.
As the porcelain output format cannot handle worktree paths that contain newlines either I think it would be better to add a `-z` flag which uses NUL termination as we have for many other commands (diff, ls-files etc). This would fix the problem forever without having to worry about quoting each time a new field is added.
If you really need to quote the lock reason then please use one of the path quoting routines (probably quote_c_style() or write_name_quoted()) in quote.c rather than rolling your own - the code below gives the same output for a string that contains the two characters `\` followed by `n` as it does for the single character `\n`. People are (hopefully) used to dequoting our path quoting and there are routines out there to do it (we have one git Git.pm) using a function in quote.c will allow them to use those routines here.
Best Wishes
Phillip
From: Phillip Wood <redacted>
Add a -z option to be used in conjunction with --porcelain that gives
NUL-terminated output. This enables 'worktree list --porcelain' to
handle worktree paths that contain newlines.
Signed-off-by: Phillip Wood <redacted>
---
I found an old patch that added NUL termination. I've rebased it
but the new test fails as there seems to be another worktree thats
been added since I wrote this, anyway I thought it might be a useful
start for adding a `-z` option.
Documentation/git-worktree.txt | 13 +++++++++---
builtin/worktree.c | 36 ++++++++++++++++++++++------------
t/t2402-worktree-list.sh | 22 +++++++++++++++++++++
3 files changed, 56 insertions(+), 15 deletions(-)
@@ -217,7 +217,13 @@ This can also be set up as the default behaviour by using the --porcelain:: With `list`, output in an easy-to-parse format for scripts. This format will remain stable across Git versions and regardless of user- configuration. See below for details.+ configuration. It is recommended to combine this with `-z`.+ See below for details.++-z::+ When `--porcelain` is specified with `list` terminate each line with a+ NUL rather than a newline. This makes it possible to parse the output+ when a worktree path contains a newline character. -q:: --quiet::
@@ -369,7 +375,8 @@ $ git worktree list Porcelain Format ~~~~~~~~~~~~~~~~-The porcelain format has a line per attribute. Attributes are listed with a+The porcelain format has a line per attribute. If `-z` is given then the lines+are terminated with NUL rather than a newline. Attributes are listed with a label and value separated by a single space. Boolean attributes (like `bare` and `detached`) are listed as a label only, and are present only if the value is true. The first attribute of a working tree is always
From: Eric Sunshine <hidden> Date: 2021-01-06 05:37:40
On Mon, Jan 4, 2021 at 11:22 AM Rafael Silva
[off-list ref] wrote:
In c57b3367be (worktree: teach `list` to annotate locked worktree,
2020-10-11) we taught `git worktree list` to annotate working tree that
is locked by appending "locked" text in order to signalize to the user
that a working tree is locked. During the review, there was some
discussion about additional annotations and information that `list`
command could provide to the user that has long been envisioned and
mentioned in [2], [3] and [4].
This patch series address some of these changes by teaching
`worktree list` to show "prunable" annotation, adding verbose mode and
extending the --porcelain format with prunable and locked annotation as
follow up from [1]. Additionally, it address one shortcoming for porcelain
format to escape any newline characters (LF and CRLF) for the lock reason
to prevent breaking format mentioned in [4] and [1] during the review
cycle.
Thank you for working on this. I'm happy to see these long-envisioned
enhancements finally taking shape. Before even reviewing the patches,
I decided to apply them and play with the new features, and I'm very
pleased to see that they behave exactly as I had envisioned all those
years ago.
Very nicely done.
I'll review the patches when I finish responding to this cover letter.
The first moves the should_prune_worktree() machinery to the top-level
worktree.c exposing the function as general API, that will be reference
by should_prune_worktree() wrapper implemented on the second patch. The
original idea was to not only move should_prune_worktree() but also
refactor to accept "struct worktree" and load the information directly,
which can simplify the `prune` command by reusing get_worktrees().
However this seems to also require refactoring get_worktrees() itself
to return "non-valid" working trees that can/should be pruned. This is
also mentioned in [5]. Having the wrapper function makes it easier to add
the prunable annotation without touching the get_worktrees() and the
other worktree sub commands. The refactoring can be addressed in a
future patch, if this turns out to be good idea. One possible approach
is to teach get_worktrees() to take additional flags that will tell
whether to return only valid or all worktrees in GIT_DIR/worktrees/
directory and address its own possible shortcoming, if any.
I haven't looked at the patches yet, so I can't respond to this right now.
The third patch changes worktree_lock_reason() to be more gentle for the
main working tree to simply returning NULL instead of aborting the
program via assert() macro. This allow us to simplify the code that
checks if the working tree is locked for default and porcelain format.
This changes is also mentioned in [6].
Sounds good.
The forth patch is the one that teaches `list` command to show prunable
annotation, for both default and porcelain format, and adds the verbose
option. These changes are also done for the "locked" annotation that was
not done on in [1]. Additionally, `list` learns to fail when both
--verbose and --porcelain format as used together.
My knee-jerk response to disallowing --verbose and --porcelain
together was that it seemed unnecessarily harsh, but upon reflection,
I think it's the correct thing to do. After all, porcelain output,
even though extensible and flexible, should be predictable, so it
doesn't make sense for options such as --verbose to affect it.
The fifth patch adds worktree_escape_reason() that accepts a (char *)
text and returned the text with any LF or CRLF escaped. The caller is
responsible to freeing the escaped text. This is used by the locked
annotation in porcelain format. Currently, this is defined within
builtin/worktree.c as I was not sure whether libfying the function as
part of this series is a good idea. At this time it seems more sensible
to leave the code internally and libfying later once we are confident
about the implementation and whether it can be used in other part of the
code base but I'm open for suggestion.
Perhaps I misunderstand, but I had envisioned employing one of the
codebase's existing quoting/escaping functions rather than crafting a
new one from scratch. However, I'll reserve judgment until I actually
read the patch.
From: Eric Sunshine <hidden> Date: 2021-01-06 06:00:24
On Mon, Jan 4, 2021 at 11:22 AM Rafael Silva
[off-list ref] wrote:
worktree: move should_prune_worktree() to worktree.c
The subject is a bit confusing since this function already lives in
`worktree.c` (albeit within the `builtin` directory). An alternate way
to word this might be:
worktree: libify and publish should_prune_worktree()
or just:
worktree: libify should_prune_worktree()
As part of teaching "git worktree list" to annotate worktree that is a
candidate for pruning, let's move should_prune_worktree() from
builtin/worktree.c to worktree.c in order to make part of the worktree
public API.
Good.
This function will be used by another API function, implemented in the
next patch that will accept a pointer to "worktree" structure directly
making it easier to implement the "prunable" annotations.
Okay, though I'm not sure this paragraph adds value since the reader
understands implicitly from the first paragraph that this is the
reason this patch is libifying the function. (I'd probably drop this
paragraph if I was composing the message.)
should_prune_worktree() knows how to select the given worktree for pruning
based on an expiration date, however the expiration value is stored on a
global variable and it is not local to the function. In order to move the
function, teach should_prune_worktree() to take the expiration date as an
argument.
Rather than "on a global variable", it would be more accurate to say
"static file-scope variable".
quoted hunk
diff --git a/worktree.c b/worktree.c
@@ -700,3 +700,76 @@ void repair_worktree_at_path(const char *path,+/*+ * Return true if worktree entry should be pruned, along with the reason for+ * pruning. Otherwise, return false and the worktree's path, or NULL if it+ * cannot be determined. Caller is responsible for freeing returned path.+ */+int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, timestamp_t expire)
The function documentation which was moved here (worktree.c) from
builtin/worktree.c is duplicated in the header worktree.h. Having it
in the header is good. Having it here in the source file doesn't
necessarily hurt, but I worry about it becoming out-of-sync with the
copy in the header. For that reason, it might make sense to keep it
only in the header.
All of the above review comments are very minor and several of them
are subjective, thus not necessarily worth a re-roll.
From: Eric Sunshine <hidden> Date: 2021-01-06 06:56:27
On Mon, Jan 4, 2021 at 11:22 AM Rafael Silva
[off-list ref] wrote:
quoted hunk
diff --git a/worktree.h b/worktree.h
@@ -73,6 +73,16 @@ int is_main_worktree(const struct worktree *wt);+/*+ * Return true if worktree entry should be pruned, along with the reason for+ * pruning. Otherwise, return false and the worktree's path, or NULL if it+ * cannot be determined. Caller is responsible for freeing returned path.+ */+int should_prune_worktree(const char *id,+ struct strbuf *reason,+ char **wtpath,+ timestamp_t expire);
A few more comments...
It would be good to update the documentation to explain what `expire`
is since it's not necessarily obvious. The documentation could also be
tweaked to say that the worktree's path is returned in `wtpath` rather
than saying only that it is returned. If you choose to make these
changes, they should be probably done in a separate patch from the
patch which moves the code. This is a very minor issue, not
necessarily worth a re-roll.
Now that this is a public function, it might make sense for `wtpath`
to be optional; if the caller is not interested in it, then NULL would
be passed in for this argument. The implementation of
should_prune_worktree() would need to be updated to check that
`wtpath` is not NULL before assigning to it. This change, too, would
be done in a separate patch. However, this is just a "would be nice to
have" item, not at all required, and I don't think it should be added
to this series since it's not needed by anything in this series, thus
would just be noise (wasting your time and reviewer time). It's just
something we can keep in mind for the future.
We might be able to give the reader more useful information in the
subject by explaining a bit more the goal of this patch. Perhaps
something like this:
worktree: teach worktree to lazy-load "prunable" reason
The should_prune_worktree() machinery is used by the "prune" command to
identify whether a worktree is a candidate for pruning. This function
however, is not prepared to work directly with "struct worktree" and
refactoring is required not only on the function itself, but also also
changing get_worktrees() to return non-valid worktrees and address the
changes in all "worktree" sub commands.
Instead let's implement worktree_prune_reason() that accepts
"struct worktree" and uses should_prune_worktree() and returns whether
the given worktree is a candidate for pruning. As the "list" sub command
already uses a list of "struct worktree", this allow to simply check if
the working tree prunable by passing the structure directly without the
others parameters.
Everything through "not prepared to work directly with `struct
worktree`" makes sense when explaining why you are adding this wrapper
function, however, most of what follows is describing an aborted idea
about how you originally intended to implement this. The bit about
get_worktrees() not being able to return non-valid worktrees is
certainly an important limitation in the overall scheme of things, but
doesn't really help to sell the change made by this patch, and it
probably confuses the reader who didn't also read the cover-letter,
especially since this patch does nothing to help that situation.
It also isn't really necessary to talk about `git worktree list` at
this stage since this patch stands on its own by fleshing out the API
without having to cite a specific client.
Also, let's add prune_reason field to the worktree structure that will
store the reason why the worktree can be pruned that is returned by
should_prune_worktree() when such reason is available.
In my opinion, this is the real reason this patch exists, thus should
be the focus of the commit message. All the description above this
paragraph can likely be dropped.
Taking the above comments into account, perhaps the entire commit
message could be collapsed to something like this:
worktree: teach worktree to lazy-load "prunable" reason
Add worktree_prune_reason() to allow a caller to discover whether
a worktree is prunable and the reason that it is, much like
worktree_lock_reason() indicates whether a worktree is locked and
the reason for the lock. As with worktree_lock_reason(), retrieve
the prunable reason lazily and cache it in the `worktree`
structure.
A couple observations...
I realize you patterned this after worktree_lock_reason(), however, it
is more common in this codebase to return early from the function for
conditions such as `is_main_worktree(wt)` which don't require any
additional processing. One reason is that doing so allows us to lose
an indentation level. Another is that it is easier to reason about the
rest of the function if we get the simple cases out of the way early,
such that we don't have to think about them again while reading the
remainder of the code.
If I'm not mistaken, the intention here was to cache `prune_reason`
for reuse, however, this function just overwrites it each time it's
called for a particular worktree, thus providing no caching and
leaking the previously-retrieved reason as well. To fix this, I think
you need to add a private `prune_reason_valid` member to `struct
worktree` (similar to `lock_reason_valid`) and check it before calling
should_prune_worktree().
Taking the above comments into account, perhaps it should be written like this:
if (is_main_worktree(wt))
return NULL;
if (wt->prune_reason_valid)
return wt->prune_reason;
if (should_prune_worktree(wt->id, &reason, &path, expire))
wt->prune_reason = strbuf_detach(&reason, NULL);
wt_prune_reason_valid = 1;
free(path);
strbuf_release(&reason);
quoted hunk
diff --git a/worktree.h b/worktree.h
@@ -11,6 +11,7 @@ struct worktree { char *id; char *head_ref; /* NULL if HEAD is broken or detached */ char *lock_reason; /* private - use worktree_lock_reason */+ char *prune_reason; /* private - use worktree_prune_reason */
As noted above, we also probably need a new `prune_reason_valid`
member, similar to the existing `lock_reason_valid`.
quoted hunk
@@ -73,6 +74,12 @@ int is_main_worktree(const struct worktree *wt);+/*+ * Return the reason string if the given worktree should be pruned+ * or NULL otherwise.+ */+const char *worktree_prune_reason(struct worktree *wt, timestamp_t expire);
The documentation should also talk about `expire` since its purpose
and meaning is unclear.
Nit: I realize that you patterned this description after the one for
worktree_lock_reason(), but it's a bit unclear. Perhaps rephrasing it
like this would help:
Return the reason the worktree should be pruned, otherwise
NULL if it should not be pruned.
From: Eric Sunshine <hidden> Date: 2021-01-06 07:30:57
On Mon, Jan 4, 2021 at 11:22 AM Rafael Silva
[off-list ref] wrote:
The main worktree should not be locked and the worktree_lock_reason() API
is aware of this fact and avoids running the check code for the main
worktree. This checks is done via assert() macro, Therefore the caller
s/Therefore/therefore/
needs to ensure the function is never called, usually by additional code.
We can handle that case more gently by just returning false for the main
worktree and not bother checking if the "locked" file exists. This will
allowed further simplification from the caller as they will not need to
ensure the main worktree is never passed to the API.
Teach worktree_lock_reason() to be more gently and just return false for
the main working tree.
The situation is even a bit worse since the main worktree restriction
isn't even documented. Here's a possible rewrite of the commit message
which addresses that point too:
worktree_lock_reason() aborts with an assertion failure when
called on the main worktree since locking the main worktree is
nonsensical. Not only is this behavior undocumented, thus callers
might not even be aware that the call could potentially crash the
program, but it also forces clients to be extra careful:
if (!is_main_worktree(wt) && worktree_locked_reason(...))
...
Since we know that locking makes no sense in the context of the
main worktree, we can simply return false for the main worktree,
thus making client code less complex by eliminating the need for
callers to have inside knowledge about the implementation:
if (worktree_locked_reason(...))
...
quoted hunk
Signed-off-by: Rafael Silva <redacted>
---
diff --git a/worktree.c b/worktree.c
@@ -225,9 +225,7 @@ int is_main_worktree(const struct worktree *wt) const char *worktree_lock_reason(struct worktree *wt) {- assert(!is_main_worktree(wt));-- if (!wt->lock_reason_valid) {+ if (!is_main_worktree(wt) && !wt->lock_reason_valid) { struct strbuf path = STRBUF_INIT;
As mentioned in my review of patch [2/7], this would be more idiomatic
and easier to reason about if the function returns early for the main
worktree case, thus freeing the reader from having to think about that
case for the remainder of the code. So:
if (is_main_worktree(wt))
return NULL;
if (!wt->lock_reason_valid) {
...
Subjective, and not necessarily worth a re-roll, though.
From: Eric Sunshine <hidden> Date: 2021-01-06 08:32:42
On Mon, Jan 4, 2021 at 11:22 AM Rafael Silva
[off-list ref] wrote:
The "git worktree list" command shows the absolute path to the worktree,
the commit that is checked out, the name of the branch, and a "locked"
annotation if the worktree is locked. It is not clear whether a worktree,
if any, is prunable.
Maybe this could just say...
... "locked" annotation if the worktree is locked,
however, it does not indicate whether it is prunable.
The "prune" command will remove a worktree in case
is a prunable candidate unless --dry-run option is specified. This could
s/case is/case it is/
Or better:
... will remove a worktree if it is prunable unless...
lead to a worktree being removed without the user realizing before is to
late, in case the user forgets to pass --dry-run for instance.
s/before is/before it is/
s/to/too/
If the "list" command shows which worktree is prunable, the user could
verify before running "git worktree prune" and hopefully prevents the
working tree to be removed "accidently" on the worse case scenario.
Let's teach "git worktree list" to show when a worktree is prunable by
appending "prunable" text to its output by default and show a prunable
label for the porcelain format followed by the reason, if the avaiable.
While at it, let's do the same for the "locked" annotation.
s/avaiable/available/
Also, the worktree_prune_reason() stores the reason why git is selecting
the worktree to be pruned, so let's leverage that and also display this
information to the user. However, the reason is human readable text that
can take virtually any size which might make harder to extend the "list"
command with additional annotations and not fit nicely on the screen.
In order to address this shortcoming, let's teach "git worktree list" to
take a verbose option that will output the prune reason on the next line
indented, if the reason is available, otherwise the annotation is kept on
the same line. While at it, let's do the same for the "locked"
annotation.
This is a lot of changes for one patch to be making, and it's hard for
a reader to digest all those changes from the commit message. I think
I counted four distinct changes being made here:
1. extend porcelain to include lock line (with optional reason)
2. add prunable annotation to normal list output
3. add prune line (with optional reason) to porcelain output
4. extend normal list output with --verbose to include reasons
The patch itself is not overly large or complicated, so perhaps
combining all these changes together is reasonable, although I'm quite
tempted to ask for them to be separated into at least four patches
(probably in the order shown above). A benefit of splitting them into
distinct patches is that you can add targeted documentation and test
updates to each individual patch rather than saving all the
documentation and test updates for a single (large) patch at the end
of the series. This helps reviewers reason about the changes more
easily since they get to see how each change impacts the documentation
and tests rather than having to wait for a patch late in the series to
make all the documentation and test updates, at which point the
reviewers may have forgotten details of the earlier patches.
(I've come back here after reading and reviewing the patch itself, and
I'm on the fence as to whether to suggest splitting this into multiple
patches. The changes in this patch are easy enough to understand and
digest, so I'm not convinced it makes sense to ask you to do all the
extra work of splitting it into smaller pieces. On the other hand, it
might be nice to have each documentation and test update done in the
patch which makes each particular change. So, I don't have a good
answer. Use your best judgment and do the amount of work you feel is
appropriate.)
The output of "git worktree list" with verbose becomes like so:
$ git worktree list --verbose
/path/to/main aba123 [main]
/path/to/locked acb124 [branch-a] locked
/path/to/locked-reason acc125 [branch-b]
locked: worktree with locked reason
/path/to/prunable acd126 [branch-c] prunable
/path/to/prunable-reason ace127 [branch-d]
prunable: gitdir file points to non-existent location
One "weird" aesthetic issue is that if the lock reason contains
newlines, the subsequent lines of the lock reason are not indented.
However, this is a very minor point and I don't think this patch
series should worry about it. We can think about how to address it
some time in the future if someone ever complains about it.
A couple observations...
As a consumer of `struct worktree`, builtin/worktree.c should not be
poking at or accessing the structure's private fields `prune_reason`
and `lock_reason`; instead it should be retrieving those values via
worktree_prune_reason() and worktree_lock_reason() which are part of
the public API.
If a worktree is prunable, then worktree_prune_reason() will
unconditionally return a (non-empty) string; if it is not prunable,
then it will unconditionally return NULL. This means that the
`printf("prunable\n")` case is dead-code; it will never be reached.
This differs from worktree_lock_reason() which can return an empty
string to indicate a locked worktree for which no reason has been
specified.
Taking the above observations into account, a reasonable rewrite would be:
const char *reason;
reason = worktree_lock_reason(wt);
if (reason && *reason)
printf("locked %s\n", reason);
else if (reason)
printf("locked\n");
reason = worktree_prune_reason(wt, expire);
if (reason)
printf("prunable %s\n", reason);
quoted hunk
@@ -604,8 +618,19 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len) strbuf_addstr(&sb, "(error)"); }- if (!is_main_worktree(wt) && worktree_lock_reason(wt))- strbuf_addstr(&sb, " locked");+ if (worktree_lock_reason(wt)) {+ if (verbose && *wt->lock_reason)+ strbuf_addf(&sb, "\n\tlocked: %s", wt->lock_reason);+ else+ strbuf_addstr(&sb, " locked");+ }++ if (worktree_prune_reason(wt, expire)) {+ if (verbose && *wt->prune_reason)+ strbuf_addf(&sb, "\n\tprunable: %s", wt->prune_reason);+ else+ strbuf_addstr(&sb, " prunable");+ }
Same observations here about using public API rather than touching
private `struct worktree` fields, and about the final `else` case
being dead code.
quoted hunk
@@ -650,12 +675,18 @@ static int list(int ac, const char **av, const char *prefix) OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")),+ OPT__VERBOSE(&verbose, N_("show extended annotations and reasons, if available")),+ OPT_EXPIRY_DATE(0, "expire", &expire,+ N_("show working trees that is candidate to be pruned older than <time>")),
Perhaps:
"add 'prunable' annotation to worktrees older than <time>"
quoted hunk
OPT_END() };+ expire = TIME_MAX;
Good, this mirrors how prune() initializes this variable.
From: Eric Sunshine <hidden> Date: 2021-01-06 09:00:25
On Mon, Jan 4, 2021 at 11:22 AM Rafael Silva
[off-list ref] wrote:
"git worktree list" porcelain format shows one attribute per line, each
attribute is listed with a label and value separated by a single space.
If any worktree is locked, a "locked" attribute is listed followed by the
reason, if available, otherwise only "locked" is shown.
In case the lock reason contains newline characters (i.e: LF or CRLF)
this will cause the format to break as each line should correspond to
one attribute. This leads to unwanted behavior, specially as the
porcelain is intended to be machine-readable. To address this shortcoming
let's teach "git worktree list" to escape any newline character before
outputting the locked reason for porcelain format.
The `*(r +1)` in the middle of the condition is redundant. The same
case is already handled by the `*(r + 1) == '\n'` which follows it
since EOL ('\0') won't match '\n'.
As Phillip already mentioned upstream, we can use one of the functions
from quote.c rather than rolling out own here. quote_c_style(), as
Phillip suggested, is almost certainly the best choice for a couple
reasons. First, when called with CQUOTE_NODQ, it adds quotes around
the string if there are any characters which need to be escaped, but
doesn't quote the string if it contains no special characters. This
makes the output more nicely readable in the typical case when there
are no special characters, and makes it possible to distinguish the
case Phillip pointed out between literal two characters "\n" in a
string and '\n' representing a newline. Second, consumers of our
porcelain are already used to consuming strings produced by
quote_c_style().
It would be preferable to fold this change directly into the preceding
patch, thus eliminating this patch altogether. That way this patch
series doesn't introduce a state in which the behavior is knowingly
"broken", but instead is correct from the start. Also, since you can
use the existing quote_c_style(), the change made by this patch
becomes tiny, thus is easily folded into the earlier patch.
From: Eric Sunshine <hidden> Date: 2021-01-06 09:08:38
On Tue, Jan 5, 2021 at 5:29 AM Phillip Wood [off-list ref] wrote:
On 04/01/2021 16:21, Rafael Silva wrote:
quoted
In case the lock reason contains newline characters (i.e: LF or CRLF)
this will cause the format to break as each line should correspond to
one attribute. This leads to unwanted behavior, specially as the
porcelain is intended to be machine-readable. To address this shortcoming
let's teach "git worktree list" to escape any newline character before
outputting the locked reason for porcelain format.
As the porcelain output format cannot handle worktree paths that contain
newlines either I think it would be better to add a `-z` flag which uses
NUL termination as we have for many other commands (diff, ls-files etc).
This would fix the problem forever without having to worry about quoting
each time a new field is added.
Adding a `-z` flag makes sense, but that doesn't mean the existing
`--porcelain` is meaningless without it. I see `-z` as a good
complement to `--porcelain`.
Yes, we should fix the problem that the existing porcelain output
doesn't escape the path, but that can be done as a separate bug fix;
it doesn't need to be rolled into this series.
If you really need to quote the lock reason then please use one of the
path quoting routines (probably quote_c_style() or write_name_quoted())
in quote.c rather than rolling your own - the code below gives the same
output for a string that contains the two characters `\` followed by `n`
as it does for the single character `\n`. People are (hopefully) used to
dequoting our path quoting and there are routines out there to do it (we
have one git Git.pm) using a function in quote.c will allow them to use
those routines here.
From: Eric Sunshine <hidden> Date: 2021-01-06 09:39:56
On Mon, Jan 4, 2021 at 11:22 AM Rafael Silva
[off-list ref] wrote:
quoted hunk
Add tests for "git worktree list" verbose mode, prunable and locked
annotations for both default and porcelain format and ensure the
"prunable" annotation is consistent with what "git worktree prune"
command will eventually remove. Additionally, add one test case to
ensure any newline characters are escaped from locked reason for the
porcelain format to prevent breaking the format.
The c57b3367be (worktree: teach `list` to annotate locked worktree,
2020-10-11) introduced a new test to ensure locked worktrees are listed
with "locked" annotation. However, the test does not remove the worktree
as the "git worktree prune" is not going to remove any locked worktrees.
Let's fix that by unlocking the worktree before the "prune" command.
Signed-off-by: Rafael Silva <redacted>
---
You might need to be a bit more careful here. If the test fails before
the worktree is locked, then the `git worktree unlock` in the cleanup
code will return an error, which will make the code executed by
test_when_finished() fail, as well, which makes it harder to debug
problems. Moving the `unlock` cleanup after you know the lock
succeeded will address this issue:
test_when_finished "rm -rf locked unlocked out && git worktree prune" &&
git worktree add --detach locked master &&
git worktree add --detach unlocked master &&
git worktree lock locked &&
test_when_finished "git worktree unlock locked" &&
...
Same comment applies to other new tests added by this patch which lock
worktrees.
quoted hunk
@@ -71,6 +73,96 @@ test_expect_success '"list" all worktrees with locked annotation' '+test_expect_success '"list" all worktrees with prunable consistent with "prune"' '+ test_when_finished "rm -rf prunable out && git worktree prune" &&+ git worktree add --detach prunable &&+ rm -rf prunable &&+ git worktree list >out &&+ grep "/prunable *[0-9a-f].* prunable$" out &&+ git worktree prune --verbose >out &&+ test_i18ngrep "^Removing worktrees/prunable" out+'
To be trustworthy, doesn't this test also need to have an unprunable
worktree, and test that `git worktree list` doesn't annotate it as
"prunable" _and_ that `git worktree prune` didn't prune it? Otherwise,
we really don't know that the two commands agree about what is and is
not prunable -- we only know they agree that this particular worktree
was prunable.
quoted hunk
+test_expect_success '"list" all worktrees --porcelain with newline escaped locked reason' '+ test_when_finished "rm -rf locked_lf locked_crlf reason_lf reason_crlf out actual expect reason &&+ git worktree unlock locked_lf &&+ git worktree unlock locked_crlf &&+ git worktree prune" &&
Nit: It's not a big deal, but we don't normally bother cleaning up
every junk file in tests, such as `out`, `actual`, `expect` if those
files aren't going to be a problem for subsequent tests. We are
explicitly cleaning up the worktrees in these tests because this
script is all about testing worktree behavior, and some random
leftover worktree could be a problem for other tests. I don't care
strongly one way or the other, but I worry a tiny bit that the list of
files being cleaned up could become outdated as changes are made to
the tests later...
quoted hunk
+test_expect_success '"list" all worktrees --porcelain with prunable' '+ test_when_finished "rm -rf prunable list out && git worktree prune" &&+ git worktree add --detach prunable &&+ rm -rf prunable &&+ git worktree list --porcelain >out &&+ test_i18ngrep "^prunable gitdir file points to non-existent location$" out+'
... for instance, the file `list` being cleaned up in this test is not
even created by this test.
quoted hunk
++test_expect_success '"list" all worktrees --verbose and --porcelain mutually exclusive' '+ test_must_fail git worktree list --verbose --porcelain+'
Nit: This test title could probably be shortened to:
'"list' --verbose and --porcelain mutually exclusive' '
Finally, I haven't tried it myself, but I was wondering if it is
possible to make a test which shows both "locked" and "prunable"
annotations for the same worktree. Would that be possible by
specifying a particular value for `--expire`? If it's too much work,
don't worry about it.
From: Eric Sunshine <hidden> Date: 2021-01-06 09:58:54
On Mon, Jan 4, 2021 at 11:22 AM Rafael Silva
[off-list ref] wrote:
quoted hunk
Update the documentation with "git worktree list" verbose mode, prunable
and locked annotations for the default and porcelain format as part of
teaching the command to show prunable working trees and verbose mode.
Signed-off-by: Rafael Silva <redacted>
---
@@ -226,9 +227,12 @@ This can also be set up as the default behaviour by using the -v:: --verbose:: With `prune`, report all removals.+ With `list`, output additional information for working trees.
This leaves the reader wondering what additional information is output
for `list`. Perhaps a small tweak will help:
With `list`, output additional information about worktrees (see below).
quoted hunk
--expire <time>:: With `prune`, only expire unused working trees older than `<time>`.+ With `list`, annotate unused working trees older than `<time>` as prunable+ candidates that will be remove by `prune` command if the same option is used.
Perhaps just minimal:
With `list`, annotate missing worktrees as prunable if they
are older than `<time>`.
quoted hunk
@@ -367,13 +371,48 @@ $ git worktree list+The command also shows annotations for each working tree, according to its state.+These annotations are:++ * "locked", if any working tree is locked+ * "prunable", if any working tree can be pruned via "git worktree prune".
s/any/the/g
We might want to use backticks around these annotations rather than
double quotes, and we certainly do want to use backticks around the
`git worktree prune` command to ensure it is styled consistently with
other commands in this document.
+Note that, the annotation is only moved to the next line only if the
+additional text is available, otherwise the text is kept on the same.
Drop the comma between "that, the". Also, too many "only"s in this
sentence. You can actually drop both of them and the sentence will
still read fine:
Note that the annotation is moved to the next line if the
additional information is available, otherwise it stays on
the same line as the worktree itself.
or something.
quoted hunk
Porcelain Format ~~~~~~~~~~~~~~~~ The porcelain format has a line per attribute. Attributes are listed with a label and value separated by a single space. Boolean attributes (like `bare` and `detached`) are listed as a label only, and are present only if the value is true. The first attribute of a working tree is always-`worktree`, an empty line indicates the end of the record. For example:+`worktree`, an empty line indicates the end of the record.+++In case any of the working trees are locked or is a candidate for pruning+(See DESCRIPTION above) the labels "locked" and "prunable" is also shown+followed by a reason, if available, otherwise only the labels are listed.+For example:
s/(See/(see/
s/is also/are also/
Let's also use backticks rather than double quotes around `locked` and
`prunable` to ensure consistent formatting with the other porcelain
labels `bare` and `detached` which are already in backticks.
Also, the unnecessary `+` line (seen as `++` in the diff) makes this
render incorrectly. It renders as:
+In case any...
To fix it, just leave the line blank between paragraphs.
(If possible, install `asciidoc` and `xmlto` and then run `make html`
to render the documentation yourself, and open
Documentation/git-worktree.html in your browser to check the output.)
From: Eric Sunshine <hidden> Date: 2021-01-07 03:35:24
On Tue, Jan 5, 2021 at 6:02 AM Phillip Wood [off-list ref] wrote:
Add a -z option to be used in conjunction with --porcelain that gives
NUL-terminated output. This enables 'worktree list --porcelain' to
handle worktree paths that contain newlines.
Adding a -z mode makes a lot of sense. This, along with a fix to call
quote_c_style() on paths in normal (not `-z`) porcelain mode, can
easily be done after Rafael's series lands. Or they could be done
before his series, though that might make a lot of extra work for him.
Signed-off-by: Phillip Wood <redacted>
---
I found an old patch that added NUL termination. I've rebased it
but the new test fails as there seems to be another worktree thats
been added since I wrote this, anyway I thought it might be a useful
start for adding a `-z` option.
@@ -217,7 +217,13 @@ This can also be set up as the default behaviour by using the+-z::+ When `--porcelain` is specified with `list` terminate each line with a+ NUL rather than a newline. This makes it possible to parse the output+ when a worktree path contains a newline character.
If we fix normal-mode porcelain to call quote_c_style(), then we can
drop the last sentence or refine it to say something along the lines
of it being easier to deal with paths with embedded newlines than in
normal porcelain mode.
Perhaps this could all be done a bit more concisely with printf()
alone rather than combining it with fputc(). For instance:
printf("worktree %s%c", wt->path, line_terminator);
and so on.
- printf("\n");
+ fputc(line_terminator, stdout);
This use of fputc() makes sense.
quoted hunk
@@ -720,15 +726,20 @@ static void pathsort(struct worktree **wt) static int list(int ac, const char **av, const char *prefix) {+ OPT_SET_INT('z', NULL, &line_terminator,+ N_("fields are separated with NUL character"), '\0'),
"fields" sounds a little odd. "lines" might make more sense. "records"
might be even better and matches the wording of some other Git
commands accepting `-z`.
+ else if (!line_terminator && !porcelain)
+ die(_("'-z' requires '--porcelain'"));
Other error messages in this file don't quote command-line options, so:
die(_("-z requires --porcelain"));
would be more consistent.
Or just use nul_to_q():
nul_to_q <_actual >actual &&
(And there's no need to `cat` the file.)
quoted hunk
+ test_cmp expect actual+'++test_expect_success '"list" -z fails without --porcelain' '+ test_when_finished "rm -rf here && git worktree prune" &&+ git worktree add --detach here master &&+ test_must_fail git worktree list -z+'
I don't think there's any need for this test to create (and cleanup) a
worktree. So, the entire test could collapse to:
test_expect_success '"list" -z fails without --porcelain' '
test_must_fail git worktree list -z
'
From: Eric Sunshine <hidden> Date: 2021-01-07 04:10:23
On Wed, Jan 6, 2021 at 4:39 AM Eric Sunshine [off-list ref] wrote:
Finally, I haven't tried it myself, but I was wondering if it is
possible to make a test which shows both "locked" and "prunable"
annotations for the same worktree. Would that be possible by
specifying a particular value for `--expire`? If it's too much work,
don't worry about it.
Ignore this stupid question/suggestion. A locked worktree will never
be considered prunable. Don't know what I was thinking when I asked
it.
From: Eric Sunshine <hidden> Date: 2021-01-07 07:25:32
On Wed, Jan 6, 2021 at 1:55 AM Eric Sunshine [off-list ref] wrote:
On Mon, Jan 4, 2021 at 11:22 AM Rafael Silva
[off-list ref] wrote:
quoted
+/*+ * Return true if worktree entry should be pruned, along with the reason for+ * pruning. Otherwise, return false and the worktree's path, or NULL if it+ * cannot be determined. Caller is responsible for freeing returned path.+ */
It would be good to update the documentation to explain what `expire`
is since it's not necessarily obvious. The documentation could also be
tweaked to say that the worktree's path is returned in `wtpath` rather
than saying only that it is returned. If you choose to make these
changes, they should be probably done in a separate patch from the
patch which moves the code. This is a very minor issue, not
necessarily worth a re-roll.
On second thought, adding a patch just to make a minor update to the
function comment is probably overkill. It should be okay to do it in
this patch. Mention the change in the commit to alert reviewers.
From: Rafael Silva <hidden> Date: 2021-01-08 07:39:22
Eric Sunshine writes:
On Mon, Jan 4, 2021 at 11:22 AM Rafael Silva
[off-list ref] wrote:
quoted
In c57b3367be (worktree: teach `list` to annotate locked worktree,
2020-10-11) we taught `git worktree list` to annotate working tree that
is locked by appending "locked" text in order to signalize to the user
that a working tree is locked. During the review, there was some
discussion about additional annotations and information that `list`
command could provide to the user that has long been envisioned and
mentioned in [2], [3] and [4].
This patch series address some of these changes by teaching
`worktree list` to show "prunable" annotation, adding verbose mode and
extending the --porcelain format with prunable and locked annotation as
follow up from [1]. Additionally, it address one shortcoming for porcelain
format to escape any newline characters (LF and CRLF) for the lock reason
to prevent breaking format mentioned in [4] and [1] during the review
cycle.
Thank you for working on this. I'm happy to see these long-envisioned
enhancements finally taking shape. Before even reviewing the patches,
I decided to apply them and play with the new features, and I'm very
pleased to see that they behave exactly as I had envisioned all those
years ago.
Very nicely done.
Thank you. I'm glad to hear the patches are aligned with what you
envisioned.
I'll review the patches when I finish responding to this cover letter.
Thank you for reviewing and applying the patches, really appreciate it.
quoted
The fifth patch adds worktree_escape_reason() that accepts a (char *)
text and returned the text with any LF or CRLF escaped. The caller is
responsible to freeing the escaped text. This is used by the locked
annotation in porcelain format. Currently, this is defined within
builtin/worktree.c as I was not sure whether libfying the function as
part of this series is a good idea. At this time it seems more sensible
to leave the code internally and libfying later once we are confident
about the implementation and whether it can be used in other part of the
code base but I'm open for suggestion.
Perhaps I misunderstand, but I had envisioned employing one of the
codebase's existing quoting/escaping functions rather than crafting a
new one from scratch. However, I'll reserve judgment until I actually
read the patch.
Agreed. It make sense to reuse one of the already implemented functions
from the code base. for some reason I was not able to find it. I believe
this was cleared out in one of the patches replies by you and Phillip Wood.
I will remove this and reuse one of the existing function on the next
revision.
--
Thanks
Rafael
From: Rafael Silva <hidden> Date: 2021-01-08 07:40:54
Thank you for the detailed review.
Eric Sunshine writes:
On Mon, Jan 4, 2021 at 11:22 AM Rafael Silva
[off-list ref] wrote:
quoted
worktree: move should_prune_worktree() to worktree.c
The subject is a bit confusing since this function already lives in
`worktree.c` (albeit within the `builtin` directory). An alternate way
to word this might be:
worktree: libify and publish should_prune_worktree()
or just:
worktree: libify should_prune_worktree()
yes, I think these seems clearer. I will drop the current subject and
use one of these on the next revision.
quoted
This function will be used by another API function, implemented in the
next patch that will accept a pointer to "worktree" structure directly
making it easier to implement the "prunable" annotations.
Okay, though I'm not sure this paragraph adds value since the reader
understands implicitly from the first paragraph that this is the
reason this patch is libifying the function. (I'd probably drop this
paragraph if I was composing the message.)
Make sense.
quoted
should_prune_worktree() knows how to select the given worktree for pruning
based on an expiration date, however the expiration value is stored on a
global variable and it is not local to the function. In order to move the
function, teach should_prune_worktree() to take the expiration date as an
argument.
Rather than "on a global variable", it would be more accurate to say
"static file-scope variable".
Good point. I will definitely change that to be more accurate on the next
revision.
quoted
diff --git a/worktree.c b/worktree.c
@@ -700,3 +700,76 @@ void repair_worktree_at_path(const char *path,+/*+ * Return true if worktree entry should be pruned, along with the reason for+ * pruning. Otherwise, return false and the worktree's path, or NULL if it+ * cannot be determined. Caller is responsible for freeing returned path.+ */+int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, timestamp_t expire)
The function documentation which was moved here (worktree.c) from
builtin/worktree.c is duplicated in the header worktree.h. Having it
in the header is good. Having it here in the source file doesn't
necessarily hurt, but I worry about it becoming out-of-sync with the
copy in the header. For that reason, it might make sense to keep it
only in the header.
All of the above review comments are very minor and several of them
are subjective, thus not necessarily worth a re-roll.
From: Rafael Silva <hidden> Date: 2021-01-08 07:42:43
Eric Sunshine writes:
On Mon, Jan 4, 2021 at 11:22 AM Rafael Silva
[off-list ref] wrote:
quoted
diff --git a/worktree.h b/worktree.h
@@ -73,6 +73,16 @@ int is_main_worktree(const struct worktree *wt);+/*+ * Return true if worktree entry should be pruned, along with the reason for+ * pruning. Otherwise, return false and the worktree's path, or NULL if it+ * cannot be determined. Caller is responsible for freeing returned path.+ */+int should_prune_worktree(const char *id,+ struct strbuf *reason,+ char **wtpath,+ timestamp_t expire);
A few more comments...
It would be good to update the documentation to explain what `expire`
is since it's not necessarily obvious. The documentation could also be
tweaked to say that the worktree's path is returned in `wtpath` rather
than saying only that it is returned. If you choose to make these
changes, they should be probably done in a separate patch from the
patch which moves the code. This is a very minor issue, not
necessarily worth a re-roll.
Good idea. I will include these changes on the next revision. specially
the documentation about the `expire` field.
Now that this is a public function, it might make sense for `wtpath`
to be optional; if the caller is not interested in it, then NULL would
be passed in for this argument. The implementation of
should_prune_worktree() would need to be updated to check that
`wtpath` is not NULL before assigning to it. This change, too, would
be done in a separate patch. However, this is just a "would be nice to
have" item, not at all required, and I don't think it should be added
to this series since it's not needed by anything in this series, thus
would just be noise (wasting your time and reviewer time). It's just
something we can keep in mind for the future.
Agreed. These seems like reasonable changes and might not be good fit
for this patches but definitely something that we can follow up after it.
--
Thanks
Rafael
We might be able to give the reader more useful information in the
subject by explaining a bit more the goal of this patch. Perhaps
something like this:
worktree: teach worktree to lazy-load "prunable" reason
yeah, that's sounds better. will change it on the next revision.
quoted
The should_prune_worktree() machinery is used by the "prune" command to
identify whether a worktree is a candidate for pruning. This function
however, is not prepared to work directly with "struct worktree" and
refactoring is required not only on the function itself, but also also
changing get_worktrees() to return non-valid worktrees and address the
changes in all "worktree" sub commands.
Instead let's implement worktree_prune_reason() that accepts
"struct worktree" and uses should_prune_worktree() and returns whether
the given worktree is a candidate for pruning. As the "list" sub command
already uses a list of "struct worktree", this allow to simply check if
the working tree prunable by passing the structure directly without the
others parameters.
Everything through "not prepared to work directly with `struct
worktree`" makes sense when explaining why you are adding this wrapper
function, however, most of what follows is describing an aborted idea
about how you originally intended to implement this. The bit about
get_worktrees() not being able to return non-valid worktrees is
certainly an important limitation in the overall scheme of things, but
doesn't really help to sell the change made by this patch, and it
probably confuses the reader who didn't also read the cover-letter,
especially since this patch does nothing to help that situation.
It also isn't really necessary to talk about `git worktree list` at
this stage since this patch stands on its own by fleshing out the API
without having to cite a specific client.
Make sense.
quoted
Also, let's add prune_reason field to the worktree structure that will
store the reason why the worktree can be pruned that is returned by
should_prune_worktree() when such reason is available.
In my opinion, this is the real reason this patch exists, thus should
be the focus of the commit message. All the description above this
paragraph can likely be dropped.
Taking the above comments into account, perhaps the entire commit
message could be collapsed to something like this:
worktree: teach worktree to lazy-load "prunable" reason
Add worktree_prune_reason() to allow a caller to discover whether
a worktree is prunable and the reason that it is, much like
worktree_lock_reason() indicates whether a worktree is locked and
the reason for the lock. As with worktree_lock_reason(), retrieve
the prunable reason lazily and cache it in the `worktree`
structure.
Interesting point. Rewording the commit message like this seems better,
and as you mentioned this patch can stands on its own and can be more
clear about what the commit is introducing to the code instead of all
the previous message trying to explain why this exists.
Thank you for suggesting this commit message. I will revise and change on
the next revision.
A couple observations...
I realize you patterned this after worktree_lock_reason(), however, it
is more common in this codebase to return early from the function for
conditions such as `is_main_worktree(wt)` which don't require any
additional processing. One reason is that doing so allows us to lose
an indentation level. Another is that it is easier to reason about the
rest of the function if we get the simple cases out of the way early,
such that we don't have to think about them again while reading the
remainder of the code.
If I'm not mistaken, the intention here was to cache `prune_reason`
for reuse, however, this function just overwrites it each time it's
called for a particular worktree, thus providing no caching and
leaking the previously-retrieved reason as well. To fix this, I think
you need to add a private `prune_reason_valid` member to `struct
worktree` (similar to `lock_reason_valid`) and check it before calling
should_prune_worktree().
Good point. I totally missed the caching aspect of the implementation
and I greed about getting the simple case out earlier in order to make it
simple to reason about it.
Taking the above comments into account, perhaps it should be written like this:
if (is_main_worktree(wt))
return NULL;
if (wt->prune_reason_valid)
return wt->prune_reason;
if (should_prune_worktree(wt->id, &reason, &path, expire))
wt->prune_reason = strbuf_detach(&reason, NULL);
wt_prune_reason_valid = 1;
free(path);
strbuf_release(&reason);
Thanks. I will add this suggestion in the next revision.
quoted
diff --git a/worktree.h b/worktree.h
@@ -11,6 +11,7 @@ struct worktree { char *id; char *head_ref; /* NULL if HEAD is broken or detached */ char *lock_reason; /* private - use worktree_lock_reason */+ char *prune_reason; /* private - use worktree_prune_reason */
As noted above, we also probably need a new `prune_reason_valid`
member, similar to the existing `lock_reason_valid`.
Indeed. will add on the next revision.
quoted
@@ -73,6 +74,12 @@ int is_main_worktree(const struct worktree *wt);+/*+ * Return the reason string if the given worktree should be pruned+ * or NULL otherwise.+ */+const char *worktree_prune_reason(struct worktree *wt, timestamp_t expire);
The documentation should also talk about `expire` since its purpose
and meaning is unclear.
Good point. I missed adding the message here for the `expire` parameter,
will add it.
Nit: I realize that you patterned this description after the one for
worktree_lock_reason(), but it's a bit unclear. Perhaps rephrasing it
like this would help:
Return the reason the worktree should be pruned, otherwise
NULL if it should not be pruned.
Also make sense to rephrase like this.
Thank you for this review will address all this changes on the v2.
--
Thanks
Rafael
From: Rafael Silva <hidden> Date: 2021-01-08 07:43:55
Eric Sunshine writes:
On Mon, Jan 4, 2021 at 11:22 AM Rafael Silva
[off-list ref] wrote:
quoted
The main worktree should not be locked and the worktree_lock_reason() API
is aware of this fact and avoids running the check code for the main
worktree. This checks is done via assert() macro, Therefore the caller
s/Therefore/therefore/
Nice catch. thanks.
quoted
needs to ensure the function is never called, usually by additional code.
We can handle that case more gently by just returning false for the main
worktree and not bother checking if the "locked" file exists. This will
allowed further simplification from the caller as they will not need to
ensure the main worktree is never passed to the API.
Teach worktree_lock_reason() to be more gently and just return false for
the main working tree.
The situation is even a bit worse since the main worktree restriction
isn't even documented. Here's a possible rewrite of the commit message
which addresses that point too:
worktree_lock_reason() aborts with an assertion failure when
called on the main worktree since locking the main worktree is
nonsensical. Not only is this behavior undocumented, thus callers
might not even be aware that the call could potentially crash the
program, but it also forces clients to be extra careful:
if (!is_main_worktree(wt) && worktree_locked_reason(...))
...
Since we know that locking makes no sense in the context of the
main worktree, we can simply return false for the main worktree,
thus making client code less complex by eliminating the need for
callers to have inside knowledge about the implementation:
if (worktree_locked_reason(...))
...
Yes, this is a nicer commit message to explain the current situation and
having the code example in there makes even more clearer. Thanks for the
suggestion will definitely make this change on the next revision.
quoted
Signed-off-by: Rafael Silva <redacted>
---
diff --git a/worktree.c b/worktree.c
@@ -225,9 +225,7 @@ int is_main_worktree(const struct worktree *wt) const char *worktree_lock_reason(struct worktree *wt) {- assert(!is_main_worktree(wt));-- if (!wt->lock_reason_valid) {+ if (!is_main_worktree(wt) && !wt->lock_reason_valid) { struct strbuf path = STRBUF_INIT;
As mentioned in my review of patch [2/7], this would be more idiomatic
and easier to reason about if the function returns early for the main
worktree case, thus freeing the reader from having to think about that
case for the remainder of the code. So:
if (is_main_worktree(wt))
return NULL;
if (!wt->lock_reason_valid) {
...
Subjective, and not necessarily worth a re-roll, though.
I'm inclined to change here on the next revision as I will do the same
for the patch [2/7] with the worktree_prune_reason() and use the same
pattern for both functions.
--
Thanks
Rafael
From: Rafael Silva <hidden> Date: 2021-01-08 07:46:27
Eric Sunshine writes:
On Mon, Jan 4, 2021 at 11:22 AM Rafael Silva
[off-list ref] wrote:
quoted
The "git worktree list" command shows the absolute path to the worktree,
the commit that is checked out, the name of the branch, and a "locked"
annotation if the worktree is locked. It is not clear whether a worktree,
if any, is prunable.
Maybe this could just say...
... "locked" annotation if the worktree is locked,
however, it does not indicate whether it is prunable.
Make sense.
quoted
The "prune" command will remove a worktree in case
is a prunable candidate unless --dry-run option is specified. This could
s/case is/case it is/
Or better:
... will remove a worktree if it is prunable unless...
Yeah, it seems better.
quoted
lead to a worktree being removed without the user realizing before is to
late, in case the user forgets to pass --dry-run for instance.
s/before is/before it is/
s/to/too/
Nice catch, thanks.
quoted
If the "list" command shows which worktree is prunable, the user could
verify before running "git worktree prune" and hopefully prevents the
working tree to be removed "accidently" on the worse case scenario.
Let's teach "git worktree list" to show when a worktree is prunable by
appending "prunable" text to its output by default and show a prunable
label for the porcelain format followed by the reason, if the avaiable.
While at it, let's do the same for the "locked" annotation.
s/avaiable/available/
Another good catch, thanks.
quoted
Also, the worktree_prune_reason() stores the reason why git is selecting
the worktree to be pruned, so let's leverage that and also display this
information to the user. However, the reason is human readable text that
can take virtually any size which might make harder to extend the "list"
command with additional annotations and not fit nicely on the screen.
In order to address this shortcoming, let's teach "git worktree list" to
take a verbose option that will output the prune reason on the next line
indented, if the reason is available, otherwise the annotation is kept on
the same line. While at it, let's do the same for the "locked"
annotation.
This is a lot of changes for one patch to be making, and it's hard for
a reader to digest all those changes from the commit message. I think
I counted four distinct changes being made here:
1. extend porcelain to include lock line (with optional reason)
2. add prunable annotation to normal list output
3. add prune line (with optional reason) to porcelain output
4. extend normal list output with --verbose to include reasons
The patch itself is not overly large or complicated, so perhaps
combining all these changes together is reasonable, although I'm quite
tempted to ask for them to be separated into at least four patches
(probably in the order shown above). A benefit of splitting them into
distinct patches is that you can add targeted documentation and test
updates to each individual patch rather than saving all the
documentation and test updates for a single (large) patch at the end
of the series. This helps reviewers reason about the changes more
easily since they get to see how each change impacts the documentation
and tests rather than having to wait for a patch late in the series to
make all the documentation and test updates, at which point the
reviewers may have forgotten details of the earlier patches.
(I've come back here after reading and reviewing the patch itself, and
I'm on the fence as to whether to suggest splitting this into multiple
patches. The changes in this patch are easy enough to understand and
digest, so I'm not convinced it makes sense to ask you to do all the
extra work of splitting it into smaller pieces. On the other hand, it
might be nice to have each documentation and test update done in the
patch which makes each particular change. So, I don't have a good
answer. Use your best judgment and do the amount of work you feel is
appropriate.)
Interesting point. During the creation of this series it was not clear
to me how to properly split them in a way that was easier to review it.
I'm inclined to split the series in the way that you are suggesting,
specially about having the tests and documentation together. I believe
this also make sense to reason about in the future when looking over the
commit history of the project without needing to go back and forth
to understand the documentation vs implementation vs testing.
quoted
The output of "git worktree list" with verbose becomes like so:
$ git worktree list --verbose
/path/to/main aba123 [main]
/path/to/locked acb124 [branch-a] locked
/path/to/locked-reason acc125 [branch-b]
locked: worktree with locked reason
/path/to/prunable acd126 [branch-c] prunable
/path/to/prunable-reason ace127 [branch-d]
prunable: gitdir file points to non-existent location
One "weird" aesthetic issue is that if the lock reason contains
newlines, the subsequent lines of the lock reason are not indented.
However, this is a very minor point and I don't think this patch
series should worry about it. We can think about how to address it
some time in the future if someone ever complains about it.
Yes, that is something that was scratching my head and it was not clear
whether something that we should roll out with this patch. But I agree,
that we can address this time in the future.
A couple observations...
As a consumer of `struct worktree`, builtin/worktree.c should not be
poking at or accessing the structure's private fields `prune_reason`
and `lock_reason`; instead it should be retrieving those values via
worktree_prune_reason() and worktree_lock_reason() which are part of
the public API.
Make sense.
If a worktree is prunable, then worktree_prune_reason() will
unconditionally return a (non-empty) string; if it is not prunable,
then it will unconditionally return NULL. This means that the
`printf("prunable\n")` case is dead-code; it will never be reached.
This differs from worktree_lock_reason() which can return an empty
string to indicate a locked worktree for which no reason has been
specified.
Great point. I will remove the dead-code from the implementation as that
state is never going to be reached given the current implementation of
worktree_prune_reason().
Thanks.
Taking the above observations into account, a reasonable rewrite would be:
const char *reason;
reason = worktree_lock_reason(wt);
if (reason && *reason)
printf("locked %s\n", reason);
else if (reason)
printf("locked\n");
reason = worktree_prune_reason(wt, expire);
if (reason)
printf("prunable %s\n", reason);
Yeah, this suggested snippet seems like sensible rewrite and much easier
to reason about.
quoted
@@ -604,8 +618,19 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len) strbuf_addstr(&sb, "(error)"); }- if (!is_main_worktree(wt) && worktree_lock_reason(wt))- strbuf_addstr(&sb, " locked");+ if (worktree_lock_reason(wt)) {+ if (verbose && *wt->lock_reason)+ strbuf_addf(&sb, "\n\tlocked: %s", wt->lock_reason);+ else+ strbuf_addstr(&sb, " locked");+ }++ if (worktree_prune_reason(wt, expire)) {+ if (verbose && *wt->prune_reason)+ strbuf_addf(&sb, "\n\tprunable: %s", wt->prune_reason);+ else+ strbuf_addstr(&sb, " prunable");+ }
Same observations here about using public API rather than touching
private `struct worktree` fields, and about the final `else` case
being dead code.
Make sense.
quoted
@@ -650,12 +675,18 @@ static int list(int ac, const char **av, const char *prefix) OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")),+ OPT__VERBOSE(&verbose, N_("show extended annotations and reasons, if available")),+ OPT_EXPIRY_DATE(0, "expire", &expire,+ N_("show working trees that is candidate to be pruned older than <time>")),
Perhaps:
"add 'prunable' annotation to worktrees older than <time>"
Make sense.
quoted
OPT_END() };+ expire = TIME_MAX;
Good, this mirrors how prune() initializes this variable.
From: Rafael Silva <hidden> Date: 2021-01-08 07:48:29
Hi Phillip,
Thank you for reviewing the patch and the suggestions.
Phillip Wood writes:
On 04/01/2021 16:21, Rafael Silva wrote:
quoted
"git worktree list" porcelain format shows one attribute per line, each
attribute is listed with a label and value separated by a single space.
If any worktree is locked, a "locked" attribute is listed followed by the
reason, if available, otherwise only "locked" is shown.
In case the lock reason contains newline characters (i.e: LF or
CRLF)
this will cause the format to break as each line should correspond to
one attribute. This leads to unwanted behavior, specially as the
porcelain is intended to be machine-readable. To address this shortcoming
let's teach "git worktree list" to escape any newline character before
outputting the locked reason for porcelain format.
As the porcelain output format cannot handle worktree paths that
contain newlines either I think it would be better to add a `-z` flag
which uses NUL termination as we have for many other commands (diff,
ls-files etc). This would fix the problem forever without having to
worry about quoting each time a new field is added.
This is a good point and it seems to be good addition for the porcelain format.
Perhaps it make more sense to apply as separate patch though, something
that Eric Sunshine also mentioned on this message reply.
If you really need to quote the lock reason then please use one of the
path quoting routines (probably quote_c_style() or
write_name_quoted()) in quote.c rather than rolling your own - the
code below gives the same output for a string that contains the two
characters `\` followed by `n` as it does for the single character
`\n`. People are (hopefully) used to dequoting our path quoting and
there are routines out there to do it (we have one git Git.pm) using a
function in quote.c will allow them to use those routines here.
Best Wishes
Phillip
That's great, thank you for pointing this out to me. I will definitely
change this into the next revision by dropping the function written from
scratch on this patch and reuse one of the existing functions.
For what is worth, I just added the function because I didn't know about
the existing ones. So, there is no reason to add the new function.
--
Thanks
Rafael
From: Rafael Silva <hidden> Date: 2021-01-08 07:50:17
Eric Sunshine writes:
On Mon, Jan 4, 2021 at 11:22 AM Rafael Silva
[off-list ref] wrote:
quoted
Add tests for "git worktree list" verbose mode, prunable and locked
annotations for both default and porcelain format and ensure the
"prunable" annotation is consistent with what "git worktree prune"
command will eventually remove. Additionally, add one test case to
ensure any newline characters are escaped from locked reason for the
porcelain format to prevent breaking the format.
The c57b3367be (worktree: teach `list` to annotate locked worktree,
2020-10-11) introduced a new test to ensure locked worktrees are listed
with "locked" annotation. However, the test does not remove the worktree
as the "git worktree prune" is not going to remove any locked worktrees.
Let's fix that by unlocking the worktree before the "prune" command.
Signed-off-by: Rafael Silva <redacted>
---
You might need to be a bit more careful here. If the test fails before
the worktree is locked, then the `git worktree unlock` in the cleanup
code will return an error, which will make the code executed by
test_when_finished() fail, as well, which makes it harder to debug
problems. Moving the `unlock` cleanup after you know the lock
succeeded will address this issue:
test_when_finished "rm -rf locked unlocked out && git worktree prune" &&
git worktree add --detach locked master &&
git worktree add --detach unlocked master &&
git worktree lock locked &&
test_when_finished "git worktree unlock locked" &&
...
Same comment applies to other new tests added by this patch which lock
worktrees.
This is good point. will change on the next revision.
quoted
@@ -71,6 +73,96 @@ test_expect_success '"list" all worktrees with locked annotation' '+test_expect_success '"list" all worktrees with prunable consistent with "prune"' '+ test_when_finished "rm -rf prunable out && git worktree prune" &&+ git worktree add --detach prunable &&+ rm -rf prunable &&+ git worktree list >out &&+ grep "/prunable *[0-9a-f].* prunable$" out &&+ git worktree prune --verbose >out &&+ test_i18ngrep "^Removing worktrees/prunable" out+'
To be trustworthy, doesn't this test also need to have an unprunable
worktree, and test that `git worktree list` doesn't annotate it as
"prunable" _and_ that `git worktree prune` didn't prune it? Otherwise,
we really don't know that the two commands agree about what is and is
not prunable -- we only know they agree that this particular worktree
was prunable.
You're right this test is missing the case the "unprunable case". Will
add into the next revision.
quoted
+test_expect_success '"list" all worktrees --porcelain with newline escaped locked reason' '+ test_when_finished "rm -rf locked_lf locked_crlf reason_lf reason_crlf out actual expect reason &&+ git worktree unlock locked_lf &&+ git worktree unlock locked_crlf &&+ git worktree prune" &&
Nit: It's not a big deal, but we don't normally bother cleaning up
every junk file in tests, such as `out`, `actual`, `expect` if those
files aren't going to be a problem for subsequent tests. We are
explicitly cleaning up the worktrees in these tests because this
script is all about testing worktree behavior, and some random
leftover worktree could be a problem for other tests. I don't care
strongly one way or the other, but I worry a tiny bit that the list of
files being cleaned up could become outdated as changes are made to
the tests later...
Make sense, and ...
quoted
+test_expect_success '"list" all worktrees --porcelain with prunable' '+ test_when_finished "rm -rf prunable list out && git worktree prune" &&+ git worktree add --detach prunable &&+ rm -rf prunable &&+ git worktree list --porcelain >out &&+ test_i18ngrep "^prunable gitdir file points to non-existent location$" out+'
... for instance, the file `list` being cleaned up in this test is not
even created by this test.
This is exactly the case of updating the test and forgetting to
remove from the cleanup part because it was not used anymore. I'm also
inclined to make this changes on the next revision.
--
Thanks
Rafael
From: Rafael Silva <hidden> Date: 2021-01-08 07:50:47
Eric Sunshine writes:
On Mon, Jan 4, 2021 at 11:22 AM Rafael Silva
[off-list ref] wrote:
quoted
Update the documentation with "git worktree list" verbose mode, prunable
and locked annotations for the default and porcelain format as part of
teaching the command to show prunable working trees and verbose mode.
Signed-off-by: Rafael Silva <redacted>
---
@@ -226,9 +227,12 @@ This can also be set up as the default behaviour by using the -v:: --verbose:: With `prune`, report all removals.+ With `list`, output additional information for working trees.
This leaves the reader wondering what additional information is output
for `list`. Perhaps a small tweak will help:
With `list`, output additional information about worktrees (see below).
Make sense.
quoted
--expire <time>:: With `prune`, only expire unused working trees older than `<time>`.+ With `list`, annotate unused working trees older than `<time>` as prunable+ candidates that will be remove by `prune` command if the same option is used.
Perhaps just minimal:
With `list`, annotate missing worktrees as prunable if they
are older than `<time>`.
Sounds reasonable.
quoted
@@ -367,13 +371,48 @@ $ git worktree list+The command also shows annotations for each working tree, according to its state.+These annotations are:++ * "locked", if any working tree is locked+ * "prunable", if any working tree can be pruned via "git worktree prune".
s/any/the/g
We might want to use backticks around these annotations rather than
double quotes, and we certainly do want to use backticks around the
`git worktree prune` command to ensure it is styled consistently with
other commands in this document.
Yes, good catch. It make sense to have backticks here.
quoted
+Note that, the annotation is only moved to the next line only if the
+additional text is available, otherwise the text is kept on the same.
Drop the comma between "that, the". Also, too many "only"s in this
sentence. You can actually drop both of them and the sentence will
still read fine:
Note that the annotation is moved to the next line if the
additional information is available, otherwise it stays on
the same line as the worktree itself.
or something.
Thanks for the alternative suggestion. It reads better like this will
add change the patch message to something more close to your suggestion.
quoted
Porcelain Format ~~~~~~~~~~~~~~~~ The porcelain format has a line per attribute. Attributes are listed with a label and value separated by a single space. Boolean attributes (like `bare` and `detached`) are listed as a label only, and are present only if the value is true. The first attribute of a working tree is always-`worktree`, an empty line indicates the end of the record. For example:+`worktree`, an empty line indicates the end of the record.+++In case any of the working trees are locked or is a candidate for pruning+(See DESCRIPTION above) the labels "locked" and "prunable" is also shown+followed by a reason, if available, otherwise only the labels are listed.+For example:
s/(See/(see/
s/is also/are also/
Let's also use backticks rather than double quotes around `locked` and
`prunable` to ensure consistent formatting with the other porcelain
labels `bare` and `detached` which are already in backticks.
Nice catch.
Also, the unnecessary `+` line (seen as `++` in the diff) makes this
render incorrectly. It renders as:
+In case any...
To fix it, just leave the line blank between paragraphs.
(If possible, install `asciidoc` and `xmlto` and then run `make html`
to render the documentation yourself, and open
Documentation/git-worktree.html in your browser to check the output.)
Sure thing. thanks for the suggestion. I should have rendered the
documentation before sending. Apologize for that.
--
Thanks
Rafael
From: Eric Sunshine <hidden> Date: 2021-01-08 08:20:08
On Fri, Jan 8, 2021 at 2:38 AM Rafael Silva [off-list ref] wrote:
Eric Sunshine writes:
quoted
On Mon, Jan 4, 2021 at 11:22 AM Rafael Silva
[off-list ref] wrote:
quoted
The fifth patch adds worktree_escape_reason() that accepts a (char *)
text and returned the text with any LF or CRLF escaped. [...]
Perhaps I misunderstand, but I had envisioned employing one of the
codebase's existing quoting/escaping functions rather than crafting a
new one from scratch. However, I'll reserve judgment until I actually
read the patch.
Agreed. It make sense to reuse one of the already implemented functions
from the code base. for some reason I was not able to find it. I believe
this was cleared out in one of the patches replies by you and Phillip Wood.
No need to apologize. It's a big project and it can be difficult to
discover existing utility functions. Fortunately, reviewers can often
point out useful alternatives.
Hi Eric & Rafael
On 07/01/2021 03:34, Eric Sunshine wrote:
On Tue, Jan 5, 2021 at 6:02 AM Phillip Wood [off-list ref] wrote:
quoted
Add a -z option to be used in conjunction with --porcelain that gives
NUL-terminated output. This enables 'worktree list --porcelain' to
handle worktree paths that contain newlines.
Adding a -z mode makes a lot of sense. This, along with a fix to call
quote_c_style() on paths in normal (not `-z`) porcelain mode,
I'm concerned that starting to quote paths will break backwards compatibility. Even if we restricted the quoting to just those paths that contain '\n' there is no way to distinguish between a quoted path and one that begins and ends with ". This is the reason I prefer to add `-z` instead of taking Rafael's patch to quote the lock reason as that patch still leaves the output of `git worktree list --porcelain` ambiguous and it cannot be fixed without breaking existing users. A counter argument to all this is that there are thousands of users on file systems that cannot have newlines in paths and Rafael's patch is definitely a net win for them.
can
easily be done after Rafael's series lands. Or they could be done
before his series, though that might make a lot of extra work for him.
I would definitely be easiest to wait for Rafael's series to land if we want to add `-z` separately
quoted
Signed-off-by: Phillip Wood <redacted>
---
I found an old patch that added NUL termination. I've rebased it
but the new test fails as there seems to be another worktree thats
been added since I wrote this, anyway I thought it might be a useful
start for adding a `-z` option.
The test failure is fallout from a "bug" in a test added by Rafael's
earlier series[1] which was not caught by the reviewer[2]. In fact,
his current series has a drive-by fix[3] for this bug in patch [6/7].
Applying that fix (or the refinement[4] I suggested in my review)
makes your test work.
Thanks ever so much for taking the time to track down the cause of the test failure.
Best Wishes
Phillip
@@ -217,7 +217,13 @@ This can also be set up as the default behaviour by using the+-z::+ When `--porcelain` is specified with `list` terminate each line with a+ NUL rather than a newline. This makes it possible to parse the output+ when a worktree path contains a newline character.
If we fix normal-mode porcelain to call quote_c_style(), then we can
drop the last sentence or refine it to say something along the lines
of it being easier to deal with paths with embedded newlines than in
normal porcelain mode.
Perhaps this could all be done a bit more concisely with printf()
alone rather than combining it with fputc(). For instance:
printf("worktree %s%c", wt->path, line_terminator);
and so on.
quoted
- printf("\n");
+ fputc(line_terminator, stdout);
This use of fputc() makes sense.
quoted
@@ -720,15 +726,20 @@ static void pathsort(struct worktree **wt) static int list(int ac, const char **av, const char *prefix) {+ OPT_SET_INT('z', NULL, &line_terminator,+ N_("fields are separated with NUL character"), '\0'),
"fields" sounds a little odd. "lines" might make more sense. "records"
might be even better and matches the wording of some other Git
commands accepting `-z`.
quoted
+ else if (!line_terminator && !porcelain)
+ die(_("'-z' requires '--porcelain'"));
Other error messages in this file don't quote command-line options, so:
die(_("-z requires --porcelain"));
would be more consistent.
Or just use nul_to_q():
nul_to_q <_actual >actual &&
(And there's no need to `cat` the file.)
quoted
+ test_cmp expect actual+'++test_expect_success '"list" -z fails without --porcelain' '+ test_when_finished "rm -rf here && git worktree prune" &&+ git worktree add --detach here master &&+ test_must_fail git worktree list -z+'
I don't think there's any need for this test to create (and cleanup) a
worktree. So, the entire test could collapse to:
test_expect_success '"list" -z fails without --porcelain' '
test_must_fail git worktree list -z
'
From: Eric Sunshine <hidden> Date: 2021-01-10 07:29:01
On Fri, Jan 8, 2021 at 5:33 AM Phillip Wood [off-list ref] wrote:
On 07/01/2021 03:34, Eric Sunshine wrote:
quoted
On Tue, Jan 5, 2021 at 6:02 AM Phillip Wood [off-list ref] wrote:
quoted
Add a -z option to be used in conjunction with --porcelain that gives
NUL-terminated output. This enables 'worktree list --porcelain' to
handle worktree paths that contain newlines.
Adding a -z mode makes a lot of sense. This, along with a fix to call
quote_c_style() on paths in normal (not `-z`) porcelain mode,
I'm concerned that starting to quote paths will break backwards
compatibility. Even if we restricted the quoting to just those paths
that contain '\n' there is no way to distinguish between a quoted path
and one that begins and ends with ".
Backward compatibility is a valid concern, though I haven't managed to
convince myself that it would matter much in this case. In one sense,
the failure of the porcelain format to properly quote/escape paths
when needed can be viewed as an outright bug and, although we value
backward compatibility, we also value correctness, and such bug fixes
have been accepted in the past. Especially in a case such as this, it
seems exceedingly unlikely that fixing the bug would be harmful or
break existing tooling (though, of course that possibility always
exists, even if remotely so).
I can imagine ways in which tooling might be engineered to work around
the shortcoming that `git worktree list --porcelain` doesn't properly
handle newlines embedded in paths, but such tooling would almost
certainly be so fragile anyhow that it would break as we add more keys
to the extensible porcelain format. Coupled with the fact that
newlines embedded in paths are so exceedingly unlikely, it's hard to
imagine that fixing this bug would have a big impact on existing
tooling.
The case you mention about a path which happens to have a double-quote
as its first character does concern me a bit more since existing
tooling wouldn't have had to jump through hoops, or indeed do anything
special, with such paths, unlike the embedded newline case. But then,
too, it's pretty hard to imagine this coming up much, if at all, in
practice. That's not to say that I can't imagine a path, in general,
beginning with a quote, but keeping in mind that we're talking only
about worktree paths, it seems exceedingly unlikely.
My gut feeling (for what it's worth) is that worktree paths containing
embedded newlines (or other special characters) or beginning with a
double-quote is so unlikely to come in in actual practice that viewing
this as a bug fix is probably a reasonable approach, whereas some
other approach -- such as introducing porcelain-v2 or creating a new
porcelain key, say "worktreepath" which supersedes "worktree" (without
retiring "worktree") -- may be overkill.
None of the above is an argument against a complementary `-z` mode,
which I think is a very good idea.
This is the reason I prefer to add
`-z` instead of taking Rafael's patch to quote the lock reason as that
patch still leaves the output of `git worktree list --porcelain`
ambiguous and it cannot be fixed without breaking existing users. A
counter argument to all this is that there are thousands of users on
file systems that cannot have newlines in paths and Rafael's patch is
definitely a net win for them.
Rafael's patch is quoting only the lock-reason, not the worktree path,
so I think it's orthogonal to this discussion. Also, his patch is
introducing `lock` as a new attribute in porcelain output, not
modifying behavior of an existing `lock` attribute.
From: Rafael Silva <hidden> Date: 2021-01-17 23:45:10
As part of teaching "git worktree list" to annotate worktree that is a
candidate for pruning, let's move should_prune_worktree() from
builtin/worktree.c to worktree.c in order to make part of the worktree
public API.
should_prune_worktree() knows how to select the given worktree for
pruning based on an expiration date, however the expiration value is
stored in a static file-scope variable and it is not local to the
function. In order to move the function, teach should_prune_worktree()
to take the expiration date as an argument and document the new
parameter that is not immediately obvious.
Also, change the function comment to clearly state that the worktree's
path is returned in `wtpath` argument.
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva <redacted>
---
builtin/worktree.c | 75 +---------------------------------------------
worktree.c | 68 +++++++++++++++++++++++++++++++++++++++++
worktree.h | 14 +++++++++
3 files changed, 83 insertions(+), 74 deletions(-)
From: Rafael Silva <hidden> Date: 2021-01-17 23:45:35
Commit c57b3367be (worktree: teach `list` to annotate locked worktree,
2020-10-11) taught "git worktree list" to annotate locked worktrees by
appending "locked" text to its output, however, this is not listed in
the --porcelain format.
Teach "list --porcelain" to do the same and add a "locked" attribute
followed by its reason, thus making both default and porcelain format
consistent. If the locked reason is not available then only "locked"
is shown.
The output of the "git worktree list --porcelain" becomes like so:
$ git worktree list --porcelain
...
worktree /path/to/locked
HEAD 123abcdea123abcd123acbd123acbda123abcd12
detached
locked
worktree /path/to/locked-with-reason
HEAD abc123abc123abc123abc123abc123abc123abc1
detached
locked reason why it is locked
...
The locked reason is quoted to prevent newline characters (i.e: LF or
CRLF) breaking the --porcelain format as each attribute is shown per
line. For example:
$ git worktree list --porcelain
...
locked worktree's path mounted in\nremovable device
...
Furthermore, let's update the documentation to state that some
attributes in the porcelain format might be listed alone or together
with its value depending whether the value is available or not. Thus
documenting the case of the new "locked" attribute.
Additionally, c57b3367be (worktree: teach `list` to annotate locked
worktree, 2020-10-11) introduced a new test to ensure locked worktrees
are listed with "locked" annotation. However, the test does not clean up
after itself as "git worktree prune" is not going to remove the locked
worktree in the first place. This not only leaves the test in an unclean
state it also potentially breaks following tests that relies on the
"git worktree list" output. Let's fix that by unlocking the worktree
before the "prune" command.
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva <redacted>
---
Documentation/git-worktree.txt | 16 ++++++++++++++--
builtin/worktree.c | 13 +++++++++++++
t/t2402-worktree-list.sh | 32 ++++++++++++++++++++++++++++++++
3 files changed, 59 insertions(+), 2 deletions(-)
@@ -377,8 +377,10 @@ Porcelain Format The porcelain format has a line per attribute. Attributes are listed with a label and value separated by a single space. Boolean attributes (like `bare` and `detached`) are listed as a label only, and are present only-if the value is true. The first attribute of a working tree is always-`worktree`, an empty line indicates the end of the record. For example:+if the value is true. Some attributes (like `locked`) can be listed as a label+only or with a value depending whether a reason is available. The first+attribute of a working tree is always `worktree`, an empty line indicates the+end of the record. For example: ------------ $ git worktree list --porcelain
From: Rafael Silva <hidden> Date: 2021-01-17 23:46:20
The "git worktree list" command shows the absolute path to the worktree,
the commit that is checked out, the name of the branch, and a "locked"
annotation if the worktree is locked, however, it does not indicate
whether the worktree is prunable.
The "prune" command will remove a worktree if it is prunable unless
`--dry-run` option is specified. This could lead to a worktree being
removed without the user realizing before it is too late, in case the
user forgets to pass --dry-run for instance. If the "list" command shows
which worktree is prunable, the user could verify before running
"git worktree prune" and hopefully prevents the working tree to be
removed "accidentally" on the worse case scenario.
Let's teach "git worktree list" to show when a worktree is a prunable
candidate for both default and porcelain format.
In the default format a "prunable" text is appended:
$ git worktree list
/path/to/main aba123 [main]
/path/to/linked 123abc [branch-a]
/path/to/prunable ace127 (detached HEAD) prunable
In the --porcelain format a prunable label is added followed by
its reason:
$ git worktree list --porcelain
...
worktree /path/to/prunable
HEAD abc1234abc1234abc1234abc1234abc1234abc12
detached
prunable gitdir file points to non-existent location
...
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva <redacted>
---
Documentation/git-worktree.txt | 26 ++++++++++++++++++++++++--
builtin/worktree.c | 15 ++++++++++++++-
t/t2402-worktree-list.sh | 32 ++++++++++++++++++++++++++++++++
3 files changed, 70 insertions(+), 3 deletions(-)
@@ -97,8 +97,9 @@ list:: List details of each working tree. The main working tree is listed first, followed by each of the linked working trees. The output details include whether the working tree is bare, the revision currently checked out, the-branch currently checked out (or "detached HEAD" if none), and "locked" if-the worktree is locked.+branch currently checked out (or "detached HEAD" if none), "locked" if+the worktree is locked, "prunable" if the worktree can be pruned by `prune`+command. lock::
@@ -234,6 +235,9 @@ This can also be set up as the default behaviour by using the --expire <time>:: With `prune`, only expire unused working trees older than `<time>`.+++With `list`, annotate missing working trees as prunable if they are+older than `<mtime>`. --reason <string>:: With `lock`, an explanation why the working tree is locked.
@@ -372,6 +376,19 @@ $ git worktree list /path/to/other-linked-worktree 1234abc (detached HEAD) ------------+The command also shows annotations for each working tree, according to its state.+These annotations are:++ * `locked`, if the working tree is locked.+ * `prunable`, if the working tree can be pruned via `git worktree prune`.++------------+$ git worktree list+/path/to/linked-worktree abcd1234 [master]+/path/to/locked-worktreee acbd5678 (brancha) locked+/path/to/prunable-worktree 5678abc (detached HEAD) prunable+------------+ Porcelain Format ~~~~~~~~~~~~~~~~ The porcelain format has a line per attribute. Attributes are listed with a
@@ -405,6 +422,11 @@ HEAD 3456def3456def3456def3456def3456def3456b branch refs/heads/locked-with-reason locked reason why is locked+worktree /path/to/linked-worktree-prunable+HEAD 1233def1234def1234def1234def1234def1234b+detached+prunable gitdir file points to non-existent location+ ------------ EXAMPLES
From: Rafael Silva <hidden> Date: 2021-01-17 23:46:56
"git worktree list" annotates each worktree according to its state such
as "prunable" or "locked", however it is not immediately obvious why
these worktrees are being annotated. For prunable worktrees a reason
is available that is returned by should_prune_worktree() and for locked
worktrees a reason might be available provided by the user via `lock`
command.
Let's teach "git worktree list" to output the reason why the worktrees
are being annotated. The reason is a text that can take virtually any
size and appending the text on the default columned format will make it
difficult to extend the command with other annotations and not fit nicely
on the screen. In order to address this shortcoming the annotation is
then moved to the next line indented followed by the reason, if the
reason is not available the annotation stays on the same line as the
worktree itself.
The output of "git worktree list" with verbose becomes like so:
$ git worktree list --verbose
...
/path/to/locked acb124 [branch-a] locked
/path/to/locked-with-reason acc125 [branch-b]
locked: worktree with a locked reason
/path/to/prunable-reason ace127 [branch-d]
prunable: gitdir file points to non-existent location
...
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva <redacted>
---
Documentation/git-worktree.txt | 20 ++++++++++++++++++++
builtin/worktree.c | 11 +++++++++--
t/t2402-worktree-list.sh | 27 +++++++++++++++++++++++++++
3 files changed, 56 insertions(+), 2 deletions(-)
@@ -232,6 +232,8 @@ This can also be set up as the default behaviour by using the -v:: --verbose:: With `prune`, report all removals.+++With `list`, output additional information about worktrees (see below). --expire <time>:: With `prune`, only expire unused working trees older than `<time>`.
@@ -389,6 +391,24 @@ $ git worktree list /path/to/prunable-worktree 5678abc (detached HEAD) prunable ------------+For these annotations, a reason might also be available and this can be+seen using the verbose mode. The annotation is then moved to the next line+indented followed by the additional information.++------------+$ git worktree list --verbose+/path/to/linked-worktree abcd1234 [master]+/path/to/locked-worktreee acbd5678 (brancha)+ locked: working tree path is mounted on a removable device+/path/to/locked-no-reason abcd578 (detached HEAD) locked+/path/to/prunable-worktree 5678abc (detached HEAD)+ prunable: gitdir file points to non-existent location+------------++Note that the annotation is moved to the next line if the additional+information is available, otherwise it stays on the same line as the+working tree itself.+ Porcelain Format ~~~~~~~~~~~~~~~~ The porcelain format has a line per attribute. Attributes are listed with a
From: Rafael Silva <hidden> Date: 2021-01-17 23:48:09
In c57b3367be (worktree: teach `list` to annotate locked worktree,
2020-10-11) we taught `git worktree list` to annotate working tree that
is locked by appending "locked" text in order to signalize to the user
that a working tree is locked. During the review, there was some
discussion about additional annotations and information that `list`
command could provide to the user that has long been envisioned and
mentioned in [2], [3] and [4].
This patch series addresses some of these changes by teaching
`worktree list` to show "prunable" annotation, adding verbose mode and
extending the --porcelain format with prunable and locked annotation as
follow up from [1]. Additionally, it addresses one shortcoming for porcelain
format to escape any newline characters (LF or CRLF) for the lock reason
to prevent breaking the format that is mentioned in [4] and [1] during the
review cycle.
The patch series is organizes as:
1. The first patch moves the should_prune_worktree() machinery to the top-level
worktree.c exposing the function as general API, that will be reference
by should_prune_worktree() wrapper implemented on the second patch.
The original idea was to not only move should_prune_worktree() but also
refactor to accept a "struct worktree" and load the information directly,
which allows simplify the `prune` command by reusing get_worktrees().
However this seems to also require refactoring get_worktrees() itself
to return "non-valid" working trees that can/should be pruned. This is
also mentioned in [5]. Having the wrapper function makes it easier to add
the prunable annotation without touching the get_worktrees() and the
other worktree sub commands. The refactoring can be addressed in a
future patch, if this turns out to be good idea. One possible approach
is to teach get_worktrees() to take additional flags that will tell
whether to return only valid or all worktrees in GIT_DIR/worktrees/
directory and address its own possible shortcoming.
2. Introduces the worktree_prune_reason() to discovery whether a worktree
is prunable along with two new fields in the `worktree` structure. The
function mimics the workree_lock_reason() API.
3. The third patch changes worktree_lock_reason() to be more gentle for
the main working tree to simply returning NULL instead of aborting the
program via assert() macro. This allow us to simplify the code that
checks if the working tree is locked for default and porcelain format.
This changes is also mentioned in [6].
4. The fourth patch adds the "locked" attribute for the porcelain format
in order to make both default and --porcelain format consistent.
5. The fifth patch introduces "prunable" annotation for both default
and --porcelain format.
6. The sixth patch introduces verbose mode to expand the `list` default
format and show each annotation reason when its available.
Series is built on top of 66e871b664 (The third batch, 2021-01-15).
[1] https://lore.kernel.org/git/20200928154953.30396-1-rafaeloliveira.cs@gmail.com/
[2] https://lore.kernel.org/git/CAPig+cQF6V8HNdMX5AZbmz3_w2WhSfA4SFfNhQqxXBqPXTZL+w@mail.gmail.com/
[3] https://lore.kernel.org/git/CAPig+cSGXqJuaZPhUhOVX5X=LMrjVfv8ye_6ncMUbyKox1i7QA@mail.gmail.com/
[4] https://lore.kernel.org/git/CAPig+cTitWCs5vB=0iXuUyEY22c0gvjXvY1ZtTT90s74ydhE=A@mail.gmail.com/
[5] https://lore.kernel.org/git/CACsJy8ChM99n6skQCv-GmFiod19mnwwH4j-6R+cfZSiVFAxjgA@mail.gmail.com/
[6] https://lore.kernel.org/git/xmqq8sctlgzx.fsf@gitster.c.googlers.com/
Changes in v2
=============
v2 changes considerably from v1 taking into account several comments
suggested by Eric Sunshine and Phillip Wood (thanks guys for the
insightful comments and patience reviewing my patches :) ).
* The biggest change are the way the series is organized. In v1 all the
code was implemented in the fourth patch, all the tests and documentation
updates was added in sixth and seventh patch respectfully, in v2 each
patch introduces the annotations and verbose mode together with theirs
respective test and documentation updates.
* Several rewrite of the commit messages
* In v1 the fifth patch was introducing a new function to escape newline
characters for the "locked" attribute. However, we already have this
feature from the quote.h API. So the new function is dropped and the
changes are squashed into v2's fourth patch.
* The new prunable annotation and locked annotation that was introduced
by [1] was refactor to not poke around the worktree private fields.
* Refactoring of the worktree_prune_reason() to cache the prune_reason
and refactor to early return the use cases following the more common
pattern used on Git's codebase.
* Few documentation rewrites, most notably the `--verbose` and `--expire`
doc sentences for the list command are moved to its own line to clearly
separate the description from the others commands instead of continuing
on the same paragraph.
* The `git unlock <worktree>` used in the test's cleanup is moved to after
we know the `git worktree locked` is executed successfully. This ensures
the unlock doesn't error in the cleanup stage which will make it harder
to debug the tests.
Rafael Silva (6):
worktree: libify should_prune_worktree()
worktree: teach worktree to lazy-load "prunable" reason
worktree: teach worktree_lock_reason() to gently handle main worktree
worktree: teach `list --porcelain` to annotate locked worktree
worktree: teach `list` to annotate prunable worktree
worktree: teach `list` verbose mode
Documentation/git-worktree.txt | 62 +++++++++++++++++--
builtin/worktree.c | 110 +++++++++++----------------------
t/t2402-worktree-list.sh | 91 +++++++++++++++++++++++++++
worktree.c | 91 ++++++++++++++++++++++++++-
worktree.h | 23 +++++++
5 files changed, 297 insertions(+), 80 deletions(-)
Range-diff against v1:
1: 9d3fff52b4 ! 1: e4df49d882 worktree: move should_prune_worktree() to worktree.c
@@ Metadata
Author: Rafael Silva [off-list ref]
## Commit message ##
- worktree: move should_prune_worktree() to worktree.c
+ worktree: libify should_prune_worktree()
As part of teaching "git worktree list" to annotate worktree that is a
candidate for pruning, let's move should_prune_worktree() from
builtin/worktree.c to worktree.c in order to make part of the worktree
public API.
- This function will be used by another API function, implemented in the
- next patch that will accept a pointer to "worktree" structure directly
- making it easier to implement the "prunable" annotations.
+ should_prune_worktree() knows how to select the given worktree for
+ pruning based on an expiration date, however the expiration value is
+ stored in a static file-scope variable and it is not local to the
+ function. In order to move the function, teach should_prune_worktree()
+ to take the expiration date as an argument and document the new
+ parameter that is not immediately obvious.
- should_prune_worktree() knows how to select the given worktree for pruning
- based on an expiration date, however the expiration value is stored on a
- global variable and it is not local to the function. In order to move the
- function, teach should_prune_worktree() to take the expiration date as an
- argument.
+ Also, change the function comment to clearly state that the worktree's
+ path is returned in `wtpath` argument.
+ Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva [off-list ref]
## builtin/worktree.c ##
@@ worktree.c: void repair_worktree_at_path(const char *path,
strbuf_release(&dotgit);
}
+
-+/*
-+ * Return true if worktree entry should be pruned, along with the reason for
-+ * pruning. Otherwise, return false and the worktree's path, or NULL if it
-+ * cannot be determined. Caller is responsible for freeing returned path.
-+ */
+int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, timestamp_t expire)
+{
+ struct stat st;
@@ worktree.h: int is_main_worktree(const struct worktree *wt);
+/*
+ * Return true if worktree entry should be pruned, along with the reason for
-+ * pruning. Otherwise, return false and the worktree's path, or NULL if it
-+ * cannot be determined. Caller is responsible for freeing returned path.
++ * pruning. Otherwise, return false and the worktree's path in `wtpath`, or
++ * NULL if it cannot be determined. Caller is responsible for freeing
++ * returned path.
++ *
++ * `expire` defines a grace period to prune the worktree when its path
++ * does not exist.
+ */
+int should_prune_worktree(const char *id,
+ struct strbuf *reason,
2: 80044e0ba9 ! 2: d8db43fec8 worktree: implement worktree_prune_reason() wrapper
@@ Metadata
Author: Rafael Silva [off-list ref]
## Commit message ##
- worktree: implement worktree_prune_reason() wrapper
+ worktree: teach worktree to lazy-load "prunable" reason
- The should_prune_worktree() machinery is used by the "prune" command to
- identify whether a worktree is a candidate for pruning. This function
- however, is not prepared to work directly with "struct worktree" and
- refactoring is required not only on the function itself, but also also
- changing get_worktrees() to return non-valid worktrees and address the
- changes in all "worktree" sub commands.
-
- Instead let's implement worktree_prune_reason() that accepts
- "struct worktree" and uses should_prune_worktree() and returns whether
- the given worktree is a candidate for pruning. As the "list" sub command
- already uses a list of "struct worktree", this allow to simply check if
- the working tree prunable by passing the structure directly without the
- others parameters.
-
- Also, let's add prune_reason field to the worktree structure that will
- store the reason why the worktree can be pruned that is returned by
- should_prune_worktree() when such reason is available.
+ Add worktree_prune_reason() to allow a caller to discover whether a
+ worktree is prunable and the reason that it is, much like
+ worktree_lock_reason() indicates whether a worktree is locked and the
+ reason for the lock. As with worktree_lock_reason(), retrieve the
+ prunable reason lazily and cache it in the `worktree` structure.
+ Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva [off-list ref]
## worktree.c ##
@@ worktree.c: const char *worktree_lock_reason(struct worktree *wt)
+const char *worktree_prune_reason(struct worktree *wt, timestamp_t expire)
+{
-+ if (!is_main_worktree(wt)) {
-+ char *path;
-+ struct strbuf reason = STRBUF_INIT;
++ struct strbuf reason = STRBUF_INIT;
++ char *path;
+
-+ if (should_prune_worktree(wt->id, &reason, &path, expire))
-+ wt->prune_reason = strbuf_detach(&reason, NULL);
-+ else
-+ wt->prune_reason = NULL;
++ if (is_main_worktree(wt))
++ return NULL;
++ if (wt->prune_reason_valid)
++ return wt->prune_reason;
+
-+ free(path);
-+ strbuf_release(&reason);
-+ }
++ if (should_prune_worktree(wt->id, &reason, &path, expire))
++ wt->prune_reason = strbuf_detach(&reason, NULL);
++ wt->prune_reason_valid = 1;
+
++ strbuf_release(&reason);
++ free(path);
+ return wt->prune_reason;
+}
+
@@ worktree.h: struct worktree {
struct object_id head_oid;
int is_detached;
int is_bare;
+ int is_current;
+ int lock_reason_valid; /* private */
++ int prune_reason_valid; /* private */
+ };
+
+ /*
@@ worktree.h: int is_main_worktree(const struct worktree *wt);
*/
const char *worktree_lock_reason(struct worktree *wt);
+/*
-+ * Return the reason string if the given worktree should be pruned
-+ * or NULL otherwise.
++ * Return the reason string if the given worktree should be pruned, otherwise
++ * NULL if it should not be pruned. `expire` defines a grace period to prune
++ * the worktree when its path does not exist.
+ */
+const char *worktree_prune_reason(struct worktree *wt, timestamp_t expire);
+
/*
* Return true if worktree entry should be pruned, along with the reason for
- * pruning. Otherwise, return false and the worktree's path, or NULL if it
+ * pruning. Otherwise, return false and the worktree's path in `wtpath`, or
3: fa2297714b < -: ---------- worktree: teach worktree_lock_reason() to gently handle main worktree
4: bcc2e5d745 < -: ---------- worktree: teach `list` prunable annotation and verbose
5: 588051ed5f < -: ---------- worktree: `list` escape lock reason in --porcelain
6: cbff8c993a < -: ---------- worktree: add tests for `list` verbose and annotations
7: 9c5893d2b9 < -: ---------- worktree: document `list` verbose and prunable annotations
-: ---------- > 3: 5b8963292f worktree: teach worktree_lock_reason() to gently handle main worktree
-: ---------- > 4: 6bdd46b9bc worktree: teach `list --porcelain` to annotate locked worktree
-: ---------- > 5: bbf6c53ecc worktree: teach `list` to annotate prunable worktree
-: ---------- > 6: acc0bf22cd worktree: teach `list` verbose mode
--
2.30.0.356.gd4bb5ad4ba
From: Rafael Silva <hidden> Date: 2021-01-17 23:49:18
Add worktree_prune_reason() to allow a caller to discover whether a
worktree is prunable and the reason that it is, much like
worktree_lock_reason() indicates whether a worktree is locked and the
reason for the lock. As with worktree_lock_reason(), retrieve the
prunable reason lazily and cache it in the `worktree` structure.
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva <redacted>
---
worktree.c | 20 ++++++++++++++++++++
worktree.h | 9 +++++++++
2 files changed, 29 insertions(+)
@@ -11,11 +11,13 @@ struct worktree {char*id;char*head_ref;/* NULL if HEAD is broken or detached */char*lock_reason;/* private - use worktree_lock_reason */+char*prune_reason;/* private - use worktree_prune_reason */structobject_idhead_oid;intis_detached;intis_bare;intis_current;intlock_reason_valid;/* private */+intprune_reason_valid;/* private */};/*
@@ -73,6 +75,13 @@ int is_main_worktree(const struct worktree *wt);*/constchar*worktree_lock_reason(structworktree*wt);+/*+*Returnthereasonstringifthegivenworktreeshouldbepruned,otherwise+*NULLifitshouldnotbepruned.`expire`definesagraceperiodtoprune+*theworktreewhenitspathdoesnotexist.+*/+constchar*worktree_prune_reason(structworktree*wt,timestamp_texpire);+/**Returntrueifworktreeentryshouldbepruned,alongwiththereasonfor*pruning.Otherwise,returnfalseandtheworktree'spathin`wtpath`,or
From: Rafael Silva <hidden> Date: 2021-01-17 23:49:18
worktree_lock_reason() aborts with an assertion failure when called on
the main worktree since locking the main worktree is nonsensical. Not
only is this behaviour undocumented, thus callers might not even be aware
that the call could potentially crash the program, but it also forces
clients to be extra careful:
if (!is_main_worktree(wt) && worktree_locked_reason(...))
...
Since we know that locking makes no sense in the context of the main
worktree, we can simpliy return false for the main worktree, thus making
client code less complex by eliminating the need for the callers to have
inside knowledge about the implementation:
if (worktree_lock_reason(...))
...
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva <redacted>
---
worktree.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
From: Eric Sunshine <hidden> Date: 2021-01-18 02:58:18
On Sun, Jan 17, 2021 at 6:43 PM Rafael Silva
[off-list ref] wrote:
quoted hunk
Add worktree_prune_reason() to allow a caller to discover whether a
worktree is prunable and the reason that it is, much like
worktree_lock_reason() indicates whether a worktree is locked and the
reason for the lock. As with worktree_lock_reason(), retrieve the
prunable reason lazily and cache it in the `worktree` structure.
Signed-off-by: Rafael Silva <redacted>
---
...but the very first thing should_prune_worktree() does is
unconditionally set it to NULL, so it's either NULL or an allocated
string regardless of the value returned by should_prune_worktree()...
+ strbuf_release(&reason);
+ free(path);
...which makes the behavior of `free(path)` deterministic. Good.
I'm on the fence about whether or not we should initialize `path` to NULL:
char *path = NULL;
just to make it easier for the next person to reason about it without
having to dig into the implementation of should_prune_worktree(). This
is a really minor point, though, not worth a re-roll.
From: Eric Sunshine <hidden> Date: 2021-01-18 03:56:56
On Sun, Jan 17, 2021 at 6:43 PM Rafael Silva
[off-list ref] wrote:
Commit c57b3367be (worktree: teach `list` to annotate locked worktree,
2020-10-11) taught "git worktree list" to annotate locked worktrees by
appending "locked" text to its output, however, this is not listed in
the --porcelain format.
Teach "list --porcelain" to do the same and add a "locked" attribute
followed by its reason, thus making both default and porcelain format
consistent. If the locked reason is not available then only "locked"
is shown.
The output of the "git worktree list --porcelain" becomes like so:
$ git worktree list --porcelain
...
worktree /path/to/locked
HEAD 123abcdea123abcd123acbd123acbda123abcd12
detached
locked
worktree /path/to/locked-with-reason
HEAD abc123abc123abc123abc123abc123abc123abc1
detached
locked reason why it is locked
...
All good.
The locked reason is quoted to prevent newline characters (i.e: LF or
CRLF) breaking the --porcelain format as each attribute is shown per
line. For example:
$ git worktree list --porcelain
...
locked worktree's path mounted in\nremovable device
...
I have a bit to say about this below.
Additionally, c57b3367be (worktree: teach `list` to annotate locked
worktree, 2020-10-11) introduced a new test to ensure locked worktrees
are listed with "locked" annotation. However, the test does not clean up
after itself as "git worktree prune" is not going to remove the locked
worktree in the first place. This not only leaves the test in an unclean
state it also potentially breaks following tests that relies on the
"git worktree list" output. Let's fix that by unlocking the worktree
before the "prune" command.
The actual code change to fix this bug is about as minimal as it gets,
but the explanation you've written here is lengthy enough and nicely
self-contained that it suggests splitting it off to its own patch. And
since you can re-use this paragraph almost verbatim as the commit
message, it shouldn't require much work to do so. On the other hand,
it is itself not necessarily worth a re-roll, but if you do re-roll,
perhaps it's worth considering.
@@ -377,8 +377,10 @@ Porcelain Format The porcelain format has a line per attribute. Attributes are listed with a label and value separated by a single space. Boolean attributes (like `bare` and `detached`) are listed as a label only, and are present only-if the value is true. The first attribute of a working tree is always-`worktree`, an empty line indicates the end of the record. For example:+if the value is true. Some attributes (like `locked`) can be listed as a label+only or with a value depending whether a reason is available. The first
Perhaps:
s/depending whether/depending upon whether/
quoted hunk
+attribute of a working tree is always `worktree`, an empty line indicates the
+end of the record. For example:
I was momentarily confused by the branch named `locked` with the
`locked` attribute in the first new stanza. Perhaps take a hint from
the second new stanza and call the first one `locked-no-reason`:
worktree /path/to/linked-worktree-locked-no-reason
HEAD 5678abc5678abc5678abc5678abc5678abc5678c
branch refs/heads/locked-no-reason
locked
Again, though, not worth a re-roll.
This needs a change, and it's totally my fault that it does. In my
previous review, I mentioned that if the lock reason contains special
characters, we want those special characters escaped and the reason
quoted, but _only_ if it contains special characters. However, I then
incorrectly said to call quote_c_style() with CQUOTE_NODQ to achieve
that behavior. In fact, CQUOTE_NODQ gives us the wrong behavior since
it avoids quoting the string which, as Phillip pointed out, makes it
impossible to distinguish between a string which just happens to
contain the two-character sequence '\' and 'n', and an escaped newline
"\n". So, the above should really be:
quote_c_style(reason, &sb, NULL, 0);
The example in the commit message should be adjusted to account for
this change, as well:
In porcelain mode, if the lock reason contains special characters
such as newlines, they are escaped with backslashes and the entire
reason is enclosed in double quotes. For example:
$ git worktree list --porcelain
...
locked "worktree's path mounted in\nremovable device"
...
And, of course, the new test will need a slight adjustment.
So, the purpose of the `unlocked` worktree in this test is to prove
that it didn't accidentally get annotated with `locked`? (Since, if it
did get annotated, then `actual` would contain too many lines and not
match `expect`.) Is that correct?
The trailing "\n\n" is unneeded. Due to the way `$(...)` expansion
works (dropping trailing whitespace), you'll get the same successful
test result with:
printf "locked\nreason\n" >reason_lf &&
printf "locked\r\nreason\n" >reason_crlf &&
and even with:
printf "locked\nreason" >reason_lf &&
printf "locked\r\nreason" >reason_crlf &&
You could also just embed the `printf`'s here rather than using these
temporary files.
git worktree lock --reason $(printf "...") <path> &&
Or, if we care only about testing LF, and not about CRLF, even this would work:
git worktree lock --reason "reason with
newline" <path> &&
but that gets a bit ugly.
Anyhow, all the line terminator commentary about this test is a matter
of personal taste, probably not worth a re-roll or even changing.
From: Eric Sunshine <hidden> Date: 2021-01-18 04:46:57
On Sun, Jan 17, 2021 at 6:43 PM Rafael Silva
[off-list ref] wrote:
The "git worktree list" command shows the absolute path to the worktree,
the commit that is checked out, the name of the branch, and a "locked"
annotation if the worktree is locked, however, it does not indicate
whether the worktree is prunable.
[...]
Let's teach "git worktree list" to show when a worktree is a prunable
candidate for both default and porcelain format.
@@ -234,6 +235,9 @@ This can also be set up as the default behaviour by using the --expire <time>:: With `prune`, only expire unused working trees older than `<time>`.+++With `list`, annotate missing working trees as prunable if they are+older than `<mtime>`.
I lean toward not including `*reason` in the condition here. While one
may argue that `*reason` is future-proofing the condition, we know
today that worktree_prune_reason() will only ever return NULL or a
non-empty string. So, having `*reason` in the condition is misleading
and confuses readers into thinking that worktree_prune_reason() could
return an empty string or that perhaps it did in the past. And
studying the history of this file or even this commit won't help them
understand why the author included `*reason` in the condition.
quoted hunk
@@ -617,9 +622,14 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)- if (!is_main_worktree(wt) && worktree_lock_reason(wt))+ reason = worktree_lock_reason(wt);+ if (reason) strbuf_addstr(&sb, " locked");
Although I understand what happened here because I read the entire
series, for anyone reading this patch in the future or even someone
reading this patch in isolation, the disappearance of
is_main_worktree() from the condition is mysterious. They won't know
if its removal was an accident or intentional, or if the change
introduces a bug. Therefore, it would be better to drop
is_main_worktree() from this conditional in patch [3/6], which is the
patch which makes it safe to call worktree_lock_reason() even for the
main worktree. Thus, in [3/6], this would change from:
if (!is_main_worktree(wt) && worktree_lock_reason(wt))
to:
if (worktree_lock_reason(wt))
and then in this patch [5/6], it becomes:
reason = worktree_lock_reason(wt);
if (reason)
quoted hunk
+ reason = worktree_prune_reason(wt, expire);+ if (reason)+ strbuf_addstr(&sb, " prunable");
Looking at this also makes me wonder if patches [5/6] and [6/6] should
be swapped since it's not clear to the reader why you're adding the
`reason` variable in this patch when the same could be accomplished
more simply:
if (worktree_lock_reason(wt))
strbuf_addstr(&sb, " locked");
if (worktree_prune_reason(wt, expire))
strbuf_addstr(&sb, " prunable");
If you swap the patches and add --verbose mode first which requires
this new `reason` variable, then the mystery goes away, and the use of
`reason` is obvious when `prunable` annotation is added in the
subsequent patch.
Having said that, I'm not trying to make extra work for you by
expecting patch perfection. Sometimes it's fine to craft a patch in
such a way that it makes subsequent patches simpler, even if it looks
slightly odd in the current patch, and I haven't read [6/6] yet, so
whatever opinion I express here is based only upon what I've read up
to this point.
From: Eric Sunshine <hidden> Date: 2021-01-18 05:16:03
On Sun, Jan 17, 2021 at 6:43 PM Rafael Silva
[off-list ref] wrote:
"git worktree list" annotates each worktree according to its state such
as "prunable" or "locked", however it is not immediately obvious why
these worktrees are being annotated. For prunable worktrees a reason
is available that is returned by should_prune_worktree() and for locked
worktrees a reason might be available provided by the user via `lock`
command.
Let's teach "git worktree list" to output the reason why the worktrees
are being annotated. The reason is a text that can take virtually any
size and appending the text on the default columned format will make it
difficult to extend the command with other annotations and not fit nicely
on the screen. In order to address this shortcoming the annotation is
then moved to the next line indented followed by the reason, if the
reason is not available the annotation stays on the same line as the
worktree itself.
If you're re-rolling, let's mention the new `--verbose` option
somewhere in the commit message since that is the focus of this patch.
The second paragraph would be a good place:
Let's teach "git worktree list" a --verbose mode which
outputs the reason...
Also, the final sentence is a bit difficult to follow due to the comma
before "if the reason is not available". If you make the "if the
reason is not available" a separate sentence, it becomes simple to
understand.
@@ -135,6 +135,33 @@ test_expect_success '"list" all worktrees with prunable consistent with "prune"'+test_expect_success '"list" all worktrees --verbose with locked' '+ test_when_finished "rm -rf locked out actual expect && git worktree prune" &&+ git worktree add locked --detach &&+ git worktree lock locked --reason "with reason" &&+ test_when_finished "git worktree unlock locked" &&+ echo "$(git -C locked rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >expect &&+ printf "\tlocked: with reason\n" >>expect &&+ git worktree list --verbose >out &&+ sed -n "s/ */ /g;/\/locked *[0-9a-f].*$/,/locked: .*$/p" <out >actual &&+ test_cmp actual expect+'
At first, I wondered if we would also want this test to have a
locked-no-reason worktree to ensure that its `locked` annotation stays
on the same line as the worktree, but that's not needed because that
case is already covered by the existing test. Fine.
quoted hunk
+test_expect_success '"list" all worktrees --verbose with prunable' '+ test_when_finished "rm -rf prunable out actual expect && git worktree prune" &&+ git worktree add prunable --detach &&+ echo "$(git -C prunable rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >expect &&+ printf "\tprunable: gitdir file points to non-existent location\n" >>expect &&+ rm -rf prunable &&+ git worktree list --verbose >out &&+ sed -n "s/ */ /g;/\/prunable *[0-9a-f].*$/,/prunable: .*$/p" <out >actual &&+ test_i18ncmp actual expect+'
An alternative would be to have a single test of --verbose which
includes a locked-no-reason worktree, a locked-with-reason worktree,
and a prunable worktree. However, that's a very minor and subjective
point and certainly not worth a re-roll or changing unless you think
it's a nice simplification.
From: Eric Sunshine <hidden> Date: 2021-01-18 05:34:42
On Sun, Jan 17, 2021 at 6:43 PM Rafael Silva
[off-list ref] wrote:
This patch series addresses some of these changes by teaching
`worktree list` to show "prunable" annotation, adding verbose mode and
extending the --porcelain format with prunable and locked annotation as
follow up from [1]. Additionally, it addresses one shortcoming for porcelain
format to escape any newline characters (LF or CRLF) for the lock reason
to prevent breaking the format that is mentioned in [4] and [1] during the
review cycle.
Thanks for continuing to work on this. I'm pleased to see these
enhancements coming together so nicely.
The patch series is organizes as:
The new organization is a nice improvement over v1.
As mentioned in my review of [5/6], there may be some value in
swapping [5/6] and [6/6] to make it easier for readers to digest the
changes, but it's not a hard requirement.
To avoid being mysterious, there's also a change in [5/6] which
probably belongs in [3/6], as explained in my review of [5/6].
My review of patch [4/6] suggests optionally splitting out a bug fix
into its own patch, but that's a very minor issue. Use your best
judgment whether or not to do so.
4. The fourth patch adds the "locked" attribute for the porcelain format
in order to make both default and --porcelain format consistent.
This patch does need a re-roll, and it's entirely my fault. In my
review of the previous round, I said that if the lock reason contained
special characters, we'd want to escape those characters and quote the
string, but then I gave you a code suggestion which did the opposite
of that. My [4/6] review goes into more detail.
Aside from the one important fix in [4/6], and the possible minor
re-organizations suggested above, all my other review comments were
very minor and probably subjective, thus nothing to sweat over.
Nicely done.
[1]: https://lore.kernel.org/git/CAPig+cSH6PKt8YvDJhMBvzvePYLqbf70uVV8TERoMj+kfxJRHQ@mail.gmail.com/
From: Eric Sunshine <hidden> Date: 2021-01-18 19:41:55
On Mon, Jan 18, 2021 at 12:15 AM Eric Sunshine [off-list ref] wrote:
On Sun, Jan 17, 2021 at 6:43 PM Rafael Silva
[off-list ref] wrote:
quoted
+test_expect_success '"list" all worktrees --verbose with locked' '+ test_when_finished "rm -rf locked out actual expect && git worktree prune" &&+ git worktree add locked --detach &&+ git worktree lock locked --reason "with reason" &&+ test_when_finished "git worktree unlock locked" &&+ echo "$(git -C locked rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >expect &&+ printf "\tlocked: with reason\n" >>expect &&+ git worktree list --verbose >out &&+ sed -n "s/ */ /g;/\/locked *[0-9a-f].*$/,/locked: .*$/p" <out >actual &&+ test_cmp actual expect+'
At first, I wondered if we would also want this test to have a
locked-no-reason worktree to ensure that its `locked` annotation stays
on the same line as the worktree, but that's not needed because that
case is already covered by the existing test. Fine.
Erm, I think what I said is wrong. There is no existing test to show
that `locked` without a reason stays on the same line as the worktree
in --verbose mode. So having a locked-no-reason worktree in this test
could be beneficial.
From: Rafael Silva <hidden> Date: 2021-01-19 07:59:19
Eric Sunshine writes:
On Sun, Jan 17, 2021 at 6:43 PM Rafael Silva
[off-list ref] wrote:
quoted
Add worktree_prune_reason() to allow a caller to discover whether a
worktree is prunable and the reason that it is, much like
worktree_lock_reason() indicates whether a worktree is locked and the
reason for the lock. As with worktree_lock_reason(), retrieve the
prunable reason lazily and cache it in the `worktree` structure.
Signed-off-by: Rafael Silva <redacted>
---
...but the very first thing should_prune_worktree() does is
unconditionally set it to NULL, so it's either NULL or an allocated
string regardless of the value returned by should_prune_worktree()...
quoted
+ strbuf_release(&reason);
+ free(path);
...which makes the behavior of `free(path)` deterministic. Good.
I'm on the fence about whether or not we should initialize `path` to NULL:
char *path = NULL;
just to make it easier for the next person to reason about it without
having to dig into the implementation of should_prune_worktree(). This
is a really minor point, though, not worth a re-roll.
This is a good point and totally agreed. I'm going to include this
change in the next revision.
--
Thanks
Rafael
From: Rafael Silva <hidden> Date: 2021-01-19 08:22:47
Thanks for the review.
Eric Sunshine writes:
On Sun, Jan 17, 2021 at 6:43 PM Rafael Silva
[off-list ref] wrote:
quoted
Additionally, c57b3367be (worktree: teach `list` to annotate locked
worktree, 2020-10-11) introduced a new test to ensure locked worktrees
are listed with "locked" annotation. However, the test does not clean up
after itself as "git worktree prune" is not going to remove the locked
worktree in the first place. This not only leaves the test in an unclean
state it also potentially breaks following tests that relies on the
"git worktree list" output. Let's fix that by unlocking the worktree
before the "prune" command.
The actual code change to fix this bug is about as minimal as it gets,
but the explanation you've written here is lengthy enough and nicely
self-contained that it suggests splitting it off to its own patch. And
since you can re-use this paragraph almost verbatim as the commit
message, it shouldn't require much work to do so. On the other hand,
it is itself not necessarily worth a re-roll, but if you do re-roll,
perhaps it's worth considering.
make sense, I'll re-roll with this change on its own patch. I actually
thought about splitting it off during as well, but I wasn't sure whether
this was a good idea. Now that you mentioned here I guess it sounds a
like reasonable change for the next version.
@@ -377,8 +377,10 @@ Porcelain Format The porcelain format has a line per attribute. Attributes are listed with a label and value separated by a single space. Boolean attributes (like `bare` and `detached`) are listed as a label only, and are present only-if the value is true. The first attribute of a working tree is always-`worktree`, an empty line indicates the end of the record. For example:+if the value is true. Some attributes (like `locked`) can be listed as a label+only or with a value depending whether a reason is available. The first
Perhaps:
s/depending whether/depending upon whether/
Yeah, I think that's sounds bit better.
quoted
+attribute of a working tree is always `worktree`, an empty line indicates the
+end of the record. For example:
I was momentarily confused by the branch named `locked` with the
`locked` attribute in the first new stanza. Perhaps take a hint from
the second new stanza and call the first one `locked-no-reason`:
worktree /path/to/linked-worktree-locked-no-reason
HEAD 5678abc5678abc5678abc5678abc5678abc5678c
branch refs/heads/locked-no-reason
locked
Again, though, not worth a re-roll.
This seems like a nice touch. will include this in the next revision.
This needs a change, and it's totally my fault that it does. In my
previous review, I mentioned that if the lock reason contains special
characters, we want those special characters escaped and the reason
quoted, but _only_ if it contains special characters. However, I then
incorrectly said to call quote_c_style() with CQUOTE_NODQ to achieve
that behavior. In fact, CQUOTE_NODQ gives us the wrong behavior since
it avoids quoting the string which, as Phillip pointed out, makes it
impossible to distinguish between a string which just happens to
contain the two-character sequence '\' and 'n', and an escaped newline
"\n". So, the above should really be:
quote_c_style(reason, &sb, NULL, 0);
The example in the commit message should be adjusted to account for
this change, as well:
In porcelain mode, if the lock reason contains special characters
such as newlines, they are escaped with backslashes and the entire
reason is enclosed in double quotes. For example:
$ git worktree list --porcelain
...
locked "worktree's path mounted in\nremovable device"
...
And, of course, the new test will need a slight adjustment.
Alright, I believe I've got the whole picture now and sorry for the
confusion. You and Phillip clearly stated in the review cycle that the
reason should be quoted because of the aforementioned reasons and I
dropped when I was working on this version.
I will re-roll and change this in the next revision.
So, the purpose of the `unlocked` worktree in this test is to prove
that it didn't accidentally get annotated with `locked`? (Since, if it
did get annotated, then `actual` would contain too many lines and not
match `expect`.) Is that correct?
Yes, this is what I intended to check when adding the `unlocked`
worktree. I'm considering how to make this more explicit so it's clear
for readers why the `unlocked` worktree exists in this test.
The trailing "\n\n" is unneeded. Due to the way `$(...)` expansion
works (dropping trailing whitespace), you'll get the same successful
test result with:
printf "locked\nreason\n" >reason_lf &&
printf "locked\r\nreason\n" >reason_crlf &&
and even with:
printf "locked\nreason" >reason_lf &&
printf "locked\r\nreason" >reason_crlf &&
You could also just embed the `printf`'s here rather than using these
temporary files.
git worktree lock --reason $(printf "...") <path> &&
Having the `printf` together with the $(...) expansion seems like the
good simplification for this test. will include in the next revision.
Or, if we care only about testing LF, and not about CRLF, even this would work:
git worktree lock --reason "reason with
newline" <path> &&
but that gets a bit ugly.
Anyhow, all the line terminator commentary about this test is a matter
of personal taste, probably not worth a re-roll or even changing.
@@ -234,6 +235,9 @@ This can also be set up as the default behaviour by using the --expire <time>:: With `prune`, only expire unused working trees older than `<time>`.+++With `list`, annotate missing working trees as prunable if they are+older than `<mtime>`.
s/mtime/time/
Oops. thanks for catching that. Will fix it in the next revision.
I lean toward not including `*reason` in the condition here. While one
may argue that `*reason` is future-proofing the condition, we know
today that worktree_prune_reason() will only ever return NULL or a
non-empty string. So, having `*reason` in the condition is misleading
and confuses readers into thinking that worktree_prune_reason() could
return an empty string or that perhaps it did in the past. And
studying the history of this file or even this commit won't help them
understand why the author included `*reason` in the condition.
Fair enough. The `*reason` condition was indeed added to be safe and
future-proofing and, as you pointed it out, we know that currently
the worktree_prune_reason() returns a non-empty string when its true
and when the code reaches the `*reason` condition in
"if (reason && *reason)" this will always evaluates to true.
Agreed that removing this part of the condition will make the code
clearer and easier to followed. So I will drop this in the next
revision.
quoted
@@ -617,9 +622,14 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)- if (!is_main_worktree(wt) && worktree_lock_reason(wt))+ reason = worktree_lock_reason(wt);+ if (reason) strbuf_addstr(&sb, " locked");
Although I understand what happened here because I read the entire
series, for anyone reading this patch in the future or even someone
reading this patch in isolation, the disappearance of
is_main_worktree() from the condition is mysterious. They won't know
if its removal was an accident or intentional, or if the change
introduces a bug. Therefore, it would be better to drop
is_main_worktree() from this conditional in patch [3/6], which is the
patch which makes it safe to call worktree_lock_reason() even for the
main worktree. Thus, in [3/6], this would change from:
if (!is_main_worktree(wt) && worktree_lock_reason(wt))
to:
if (worktree_lock_reason(wt))
and then in this patch [5/6], it becomes:
reason = worktree_lock_reason(wt);
if (reason)
That's a good point, and even worse this is not mentioned in my commit
message at all. It make sense to move this change into [3/6] where the
API is changed and all the reason is explained in the commit message.
quoted
+ reason = worktree_prune_reason(wt, expire);+ if (reason)+ strbuf_addstr(&sb, " prunable");
Looking at this also makes me wonder if patches [5/6] and [6/6] should
be swapped since it's not clear to the reader why you're adding the
`reason` variable in this patch when the same could be accomplished
more simply:
if (worktree_lock_reason(wt))
strbuf_addstr(&sb, " locked");
if (worktree_prune_reason(wt, expire))
strbuf_addstr(&sb, " prunable");
If you swap the patches and add --verbose mode first which requires
this new `reason` variable, then the mystery goes away, and the use of
`reason` is obvious when `prunable` annotation is added in the
subsequent patch.
Having said that, I'm not trying to make extra work for you by
expecting patch perfection. Sometimes it's fine to craft a patch in
such a way that it makes subsequent patches simpler, even if it looks
slightly odd in the current patch, and I haven't read [6/6] yet, so
whatever opinion I express here is based only upon what I've read up
to this point.
That's a good point. I'm inclined to leave the [5/6] with the following:
if (worktree_prune_reason(wt, expire))
strbuf_addstr(&sb, " prunable");
And move up the changes that includes the `reason` into the [5/6]
patches that introduces the --verbose option. This line seems easier to
follow when the reader is looking on this patch alone and only care
about a reason when the --verbose comes into play in the next patch
[6/6].
Although your suggestion about changing the patch also sounds reasonable
and I'll take into consideration when I re-roll this series.
Btw, I don't mind spending extra work on and I'm all forward
with the changes so we it would be easier to understand not only now where
all the patches are being reviewed together but in the future when
someone is looking at the history of the project, specially for
debugging/bisecting reasons.
Thanks for the insightful comments.
--
Thanks
Rafael
From: Rafael Silva <hidden> Date: 2021-01-19 18:28:29
Eric Sunshine writes:
On Sun, Jan 17, 2021 at 6:43 PM Rafael Silva
[off-list ref] wrote:
quoted
This patch series addresses some of these changes by teaching
`worktree list` to show "prunable" annotation, adding verbose mode and
extending the --porcelain format with prunable and locked annotation as
follow up from [1]. Additionally, it addresses one shortcoming for porcelain
format to escape any newline characters (LF or CRLF) for the lock reason
to prevent breaking the format that is mentioned in [4] and [1] during the
review cycle.
Thanks for continuing to work on this. I'm pleased to see these
enhancements coming together so nicely.
quoted
The patch series is organizes as:
The new organization is a nice improvement over v1.
As mentioned in my review of [5/6], there may be some value in
swapping [5/6] and [6/6] to make it easier for readers to digest the
changes, but it's not a hard requirement.
To avoid being mysterious, there's also a change in [5/6] which
probably belongs in [3/6], as explained in my review of [5/6].
My review of patch [4/6] suggests optionally splitting out a bug fix
into its own patch, but that's a very minor issue. Use your best
judgment whether or not to do so.
quoted
4. The fourth patch adds the "locked" attribute for the porcelain format
in order to make both default and --porcelain format consistent.
This patch does need a re-roll, and it's entirely my fault. In my
review of the previous round, I said that if the lock reason contained
special characters, we'd want to escape those characters and quote the
string, but then I gave you a code suggestion which did the opposite
of that. My [4/6] review goes into more detail.
I kindly disagree and I actually think is my fault :).
From: Eric Sunshine <hidden> Date: 2021-01-19 18:28:52
On Tue, Jan 19, 2021 at 3:20 AM Rafael Silva
[off-list ref] wrote:
Eric Sunshine writes:
quoted
On Sun, Jan 17, 2021 at 6:43 PM Rafael Silva
[off-list ref] wrote:
quoted
+ quote_c_style(reason, &sb, NULL, CQUOTE_NODQ);
This needs a change, and it's totally my fault that it does. In my
previous review, I mentioned that if the lock reason contains special
characters, we want those special characters escaped and the reason
quoted, but _only_ if it contains special characters. However, I then
incorrectly said to call quote_c_style() with CQUOTE_NODQ to achieve
that behavior. In fact, CQUOTE_NODQ gives us the wrong behavior since
it avoids quoting the string which, as Phillip pointed out, makes it
impossible to distinguish between a string which just happens to
contain the two-character sequence '\' and 'n', and an escaped newline
"\n". So, the above should really be:
Alright, I believe I've got the whole picture now and sorry for the
confusion. You and Phillip clearly stated in the review cycle that the
reason should be quoted because of the aforementioned reasons and I
dropped when I was working on this version.
It was my fault by confusing you with the misleading mention of CQUOTE_NODQ.
So, the purpose of the `unlocked` worktree in this test is to prove
that it didn't accidentally get annotated with `locked`? (Since, if it
did get annotated, then `actual` would contain too many lines and not
match `expect`.) Is that correct?
Yes, this is what I intended to check when adding the `unlocked`
worktree. I'm considering how to make this more explicit so it's clear
for readers why the `unlocked` worktree exists in this test.
A simple in-code comment should suffice, I would think:
git worktree add --detach locked1 &&
git worktree add --detach locked2 &&
# unlocked worktree should not be annotated "locked"
git worktree add --detach unlocked &&
From: Eric Sunshine <hidden> Date: 2021-01-19 18:28:55
On Tue, Jan 19, 2021 at 5:26 AM Rafael Silva
[off-list ref] wrote:
Eric Sunshine writes:
quoted
On Sun, Jan 17, 2021 at 6:43 PM Rafael Silva
[off-list ref] wrote:
quoted
+ reason = worktree_prune_reason(wt, expire);+ if (reason)+ strbuf_addstr(&sb, " prunable");
Looking at this also makes me wonder if patches [5/6] and [6/6] should
be swapped since it's not clear to the reader why you're adding the
`reason` variable in this patch when the same could be accomplished
more simply:
if (worktree_prune_reason(wt, expire))
strbuf_addstr(&sb, " prunable");
If you swap the patches and add --verbose mode first which requires
this new `reason` variable, then the mystery goes away, and the use of
`reason` is obvious when `prunable` annotation is added in the
subsequent patch.
That's a good point. I'm inclined to leave the [5/6] with the following:
if (worktree_prune_reason(wt, expire))
strbuf_addstr(&sb, " prunable");
And move up the changes that includes the `reason` into the [5/6]
patches that introduces the --verbose option. This line seems easier to
follow when the reader is looking on this patch alone and only care
about a reason when the --verbose comes into play in the next patch
[6/6].
I considered this, as well, but figured that it would make patch [6/6]
even noiser, and that swapping the patches would keep the noise level
down. But, I haven't actually tried it myself, so I could be wrong
about the assumption, and maybe the noisiness wouldn't be a problem in
practice or would be so stylized that it wouldn't matter.
From: Rafael Silva <hidden> Date: 2021-01-19 21:29:37
As part of teaching "git worktree list" to annotate worktree that is a
candidate for pruning, let's move should_prune_worktree() from
builtin/worktree.c to worktree.c in order to make part of the worktree
public API.
should_prune_worktree() knows how to select the given worktree for
pruning based on an expiration date, however the expiration value is
stored in a static file-scope variable and it is not local to the
function. In order to move the function, teach should_prune_worktree()
to take the expiration date as an argument and document the new
parameter that is not immediately obvious.
Also, change the function comment to clearly state that the worktree's
path is returned in `wtpath` argument.
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva <redacted>
---
builtin/worktree.c | 75 +---------------------------------------------
worktree.c | 68 +++++++++++++++++++++++++++++++++++++++++
worktree.h | 14 +++++++++
3 files changed, 83 insertions(+), 74 deletions(-)
From: Rafael Silva <hidden> Date: 2021-01-19 21:29:38
"git worktree list" annotates each worktree according to its state such
as "prunable" or "locked", however it is not immediately obvious why
these worktrees are being annotated. For prunable worktrees a reason
is available that is returned by should_prune_worktree() and for locked
worktrees a reason might be available provided by the user via `lock`
command.
Let's teach "git worktree list" a --verbose mode that outputs the reason
why the worktrees are being annotated. The reason is a text that can take
virtually any size and appending the text on the default columned format
will make it difficult to extend the command with other annotations and
not fit nicely on the screen. In order to address this shortcoming the
annotation is then moved to the next line indented followed by the reason
If the reason is not available the annotation stays on the same line as
the worktree itself.
The output of "git worktree list" with verbose becomes like so:
$ git worktree list --verbose
...
/path/to/locked-no-reason acb124 [branch-a] locked
/path/to/locked-with-reason acc125 [branch-b]
locked: worktree with a locked reason
/path/to/prunable-reason ace127 [branch-d]
prunable: gitdir file points to non-existent location
...
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva <redacted>
---
Documentation/git-worktree.txt | 20 ++++++++++++++++++++
builtin/worktree.c | 14 ++++++++++++--
t/t2402-worktree-list.sh | 30 ++++++++++++++++++++++++++++++
3 files changed, 62 insertions(+), 2 deletions(-)
@@ -232,6 +232,8 @@ This can also be set up as the default behaviour by using the -v:: --verbose:: With `prune`, report all removals.+++With `list`, output additional information about worktrees (see below). --expire <time>:: With `prune`, only expire unused working trees older than `<time>`.
@@ -389,6 +391,24 @@ $ git worktree list /path/to/prunable-worktree 5678abc (detached HEAD) prunable ------------+For these annotations, a reason might also be available and this can be+seen using the verbose mode. The annotation is then moved to the next line+indented followed by the additional information.++------------+$ git worktree list --verbose+/path/to/linked-worktree abcd1234 [master]+/path/to/locked-worktree-no-reason abcd5678 (detached HEAD) locked+/path/to/locked-worktree-with-reason 1234abcd (brancha)+ locked: working tree path is mounted on a portable device+/path/to/prunable-worktree 5678abc1 (detached HEAD)+ prunable: gitdir file points to non-existent location+------------++Note that the annotation is moved to the next line if the additional+information is available, otherwise it stays on the same line as the+working tree itself.+ Porcelain Format ~~~~~~~~~~~~~~~~ The porcelain format has a line per attribute. Attributes are listed with a
From: Rafael Silva <hidden> Date: 2021-01-19 21:30:10
Add worktree_prune_reason() to allow a caller to discover whether a
worktree is prunable and the reason that it is, much like
worktree_lock_reason() indicates whether a worktree is locked and the
reason for the lock. As with worktree_lock_reason(), retrieve the
prunable reason lazily and cache it in the `worktree` structure.
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva <redacted>
---
worktree.c | 20 ++++++++++++++++++++
worktree.h | 9 +++++++++
2 files changed, 29 insertions(+)
@@ -11,11 +11,13 @@ struct worktree {char*id;char*head_ref;/* NULL if HEAD is broken or detached */char*lock_reason;/* private - use worktree_lock_reason */+char*prune_reason;/* private - use worktree_prune_reason */structobject_idhead_oid;intis_detached;intis_bare;intis_current;intlock_reason_valid;/* private */+intprune_reason_valid;/* private */};/*
@@ -73,6 +75,13 @@ int is_main_worktree(const struct worktree *wt);*/constchar*worktree_lock_reason(structworktree*wt);+/*+*Returnthereasonstringifthegivenworktreeshouldbepruned,otherwise+*NULLifitshouldnotbepruned.`expire`definesagraceperiodtoprune+*theworktreewhenitspathdoesnotexist.+*/+constchar*worktree_prune_reason(structworktree*wt,timestamp_texpire);+/**Returntrueifworktreeentryshouldbepruned,alongwiththereasonfor*pruning.Otherwise,returnfalseandtheworktree'spathin`wtpath`,or
From: Rafael Silva <hidden> Date: 2021-01-19 21:30:10
The "git worktree list" command shows the absolute path to the worktree,
the commit that is checked out, the name of the branch, and a "locked"
annotation if the worktree is locked, however, it does not indicate
whether the worktree is prunable.
The "prune" command will remove a worktree if it is prunable unless
`--dry-run` option is specified. This could lead to a worktree being
removed without the user realizing before it is too late, in case the
user forgets to pass --dry-run for instance. If the "list" command shows
which worktree is prunable, the user could verify before running
"git worktree prune" and hopefully prevents the working tree to be
removed "accidentally" on the worse case scenario.
Let's teach "git worktree list" to show when a worktree is a prunable
candidate for both default and porcelain format.
In the default format a "prunable" text is appended:
$ git worktree list
/path/to/main aba123 [main]
/path/to/linked 123abc [branch-a]
/path/to/prunable ace127 (detached HEAD) prunable
In the --porcelain format a prunable label is added followed by
its reason:
$ git worktree list --porcelain
...
worktree /path/to/prunable
HEAD abc1234abc1234abc1234abc1234abc1234abc12
detached
prunable gitdir file points to non-existent location
...
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva <redacted>
---
Documentation/git-worktree.txt | 26 ++++++++++++++++++++++++--
builtin/worktree.c | 10 ++++++++++
builtin/worktree.cc | 0
t/t2402-worktree-list.sh | 32 ++++++++++++++++++++++++++++++++
4 files changed, 66 insertions(+), 2 deletions(-)
create mode 100644 builtin/worktree.cc
@@ -97,8 +97,9 @@ list:: List details of each working tree. The main working tree is listed first, followed by each of the linked working trees. The output details include whether the working tree is bare, the revision currently checked out, the-branch currently checked out (or "detached HEAD" if none), and "locked" if-the worktree is locked.+branch currently checked out (or "detached HEAD" if none), "locked" if+the worktree is locked, "prunable" if the worktree can be pruned by `prune`+command. lock::
@@ -234,6 +235,9 @@ This can also be set up as the default behaviour by using the --expire <time>:: With `prune`, only expire unused working trees older than `<time>`.+++With `list`, annotate missing working trees as prunable if they are+older than `<time>`. --reason <string>:: With `lock`, an explanation why the working tree is locked.
@@ -372,6 +376,19 @@ $ git worktree list /path/to/other-linked-worktree 1234abc (detached HEAD) ------------+The command also shows annotations for each working tree, according to its state.+These annotations are:++ * `locked`, if the working tree is locked.+ * `prunable`, if the working tree can be pruned via `git worktree prune`.++------------+$ git worktree list+/path/to/linked-worktree abcd1234 [master]+/path/to/locked-worktreee acbd5678 (brancha) locked+/path/to/prunable-worktree 5678abc (detached HEAD) prunable+------------+ Porcelain Format ~~~~~~~~~~~~~~~~ The porcelain format has a line per attribute. Attributes are listed with a
@@ -405,6 +422,11 @@ HEAD 3456def3456def3456def3456def3456def3456b branch refs/heads/locked-with-reason locked reason why is locked+worktree /path/to/linked-worktree-prunable+HEAD 1233def1234def1234def1234def1234def1234b+detached+prunable gitdir file points to non-existent location+ ------------ EXAMPLES
From: Rafael Silva <hidden> Date: 2021-01-19 21:30:36
worktree_lock_reason() aborts with an assertion failure when called on
the main worktree since locking the main worktree is nonsensical. Not
only is this behavior undocumented, thus callers might not even be aware
that the call could potentially crash the program, but it also forces
clients to be extra careful:
if (!is_main_worktree(wt) && worktree_locked_reason(...))
...
Since we know that locking makes no sense in the context of the main
worktree, we can simply return false for the main worktree, thus making
client code less complex by eliminating the need for the callers to have
inside knowledge about the implementation:
if (worktree_lock_reason(...))
...
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva <redacted>
---
builtin/worktree.c | 2 +-
worktree.c | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
From: Rafael Silva <hidden> Date: 2021-01-19 21:32:28
In c57b3367be (worktree: teach `list` to annotate locked worktree,
2020-10-11) introduced a new test to ensure locked worktrees are listed
with "locked" annotation. However, the test does not clean up after
itself as "git worktree prune" is not going to remove the locked worktree
in the first place. This not only leaves the test in an unclean state it
also potentially breaks following tests that relies on the
"git worktree list" output.
Let's fix that by unlocking the worktree before the "prune" command.
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva <redacted>
---
t/t2402-worktree-list.sh | 1 +
1 file changed, 1 insertion(+)
From: Rafael Silva <hidden> Date: 2021-01-19 21:32:28
Commit c57b3367be (worktree: teach `list` to annotate locked worktree,
2020-10-11) taught "git worktree list" to annotate locked worktrees by
appending "locked" text to its output, however, this is not listed in
the --porcelain format.
Teach "list --porcelain" to do the same and add a "locked" attribute
followed by its reason, thus making both default and porcelain format
consistent. If the locked reason is not available then only "locked"
is shown.
The output of the "git worktree list --porcelain" becomes like so:
$ git worktree list --porcelain
...
worktree /path/to/locked
HEAD 123abcdea123abcd123acbd123acbda123abcd12
detached
locked
worktree /path/to/locked-with-reason
HEAD abc123abc123abc123abc123abc123abc123abc1
detached
locked reason why it is locked
...
In porcelain mode, if the lock reason contains special characters
such as newlines, they are escaped with backslashes and the entire
reason is enclosed in double quotes. For example:
$ git worktree list --porcelain
...
locked "worktree's path mounted in\nremovable device"
...
Furthermore, let's update the documentation to state that some
attributes in the porcelain format might be listed alone or together
with its value depending whether the value is available or not. Thus
documenting the case of the new "locked" attribute.
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva <redacted>
---
Documentation/git-worktree.txt | 16 ++++++++++++++--
builtin/worktree.c | 13 +++++++++++++
t/t2402-worktree-list.sh | 30 ++++++++++++++++++++++++++++++
3 files changed, 57 insertions(+), 2 deletions(-)
@@ -377,8 +377,10 @@ Porcelain Format The porcelain format has a line per attribute. Attributes are listed with a label and value separated by a single space. Boolean attributes (like `bare` and `detached`) are listed as a label only, and are present only-if the value is true. The first attribute of a working tree is always-`worktree`, an empty line indicates the end of the record. For example:+if the value is true. Some attributes (like `locked`) can be listed as a label+only or with a value depending upon whether a reason is available. The first+attribute of a working tree is always `worktree`, an empty line indicates the+end of the record. For example: ------------ $ git worktree list --porcelain
From: Rafael Silva <hidden> Date: 2021-01-19 21:33:38
In c57b3367be (worktree: teach `list` to annotate locked worktree,
2020-10-11) we taught `git worktree list` to annotate working tree that
is locked by appending "locked" text in order to signalize to the user
that a working tree is locked. During the review, there was some
discussion about additional annotations and information that `list`
command could provide to the user that has long been envisioned and
mentioned in [2], [3] and [4].
This patch series addresses some of these changes by teaching
`worktree list` to show "prunable" annotation, adding verbose mode and
extending the --porcelain format with prunable and locked annotation as
follow up from [1]. Additionally, it addresses one shortcoming for porcelain
format to escape any newline characters (LF or CRLF) for the lock reason
to prevent breaking the format that is mentioned in [4] and [1] during the
review cycle.
The patch series is organizes as:
1. The first patch moves the should_prune_worktree() machinery to the top-level
worktree.c exposing the function as general API, that will be reference
by should_prune_worktree() wrapper implemented on the second patch.
The original idea was to not only move should_prune_worktree() but also
refactor to accept a "struct worktree" and load the information directly,
which allows simplify the `prune` command by reusing get_worktrees().
However this seems to also require refactoring get_worktrees() itself
to return "non-valid" working trees that can/should be pruned. This is
also mentioned in [5]. Having the wrapper function makes it easier to add
the prunable annotation without touching the get_worktrees() and the
other worktree sub commands. The refactoring can be addressed in a
future patch, if this turns out to be good idea. One possible approach
is to teach get_worktrees() to take additional flags that will tell
whether to return only valid or all worktrees in GIT_DIR/worktrees/
directory and address its own possible shortcoming.
2. Introduces the worktree_prune_reason() to discovery whether a worktree
is prunable along with two new fields in the `worktree` structure. The
function mimics the workree_lock_reason() API.
3. The third patch changes worktree_lock_reason() to be more gentle for
the main working tree to simply returning NULL instead of aborting the
program via assert() macro. This allow us to simplify the code that
checks if the working tree is locked for default and porcelain format.
This changes is also mentioned in [6].
4. Fix t2402 added in [1] to ensure the locked worktree is properly cleaned up.
5. The fourth patch adds the "locked" attribute for the porcelain format
in order to make both default and --porcelain format consistent.
6. The fifth patch introduces "prunable" annotation for both default
and --porcelain format.
7. The sixth patch introduces verbose mode to expand the `list` default
format and show each annotation reason when its available.
Series is built on top of 66e871b664 (The third batch, 2021-01-15).
[1] https://lore.kernel.org/git/20200928154953.30396-1-rafaeloliveira.cs@gmail.com/
[2] https://lore.kernel.org/git/CAPig+cQF6V8HNdMX5AZbmz3_w2WhSfA4SFfNhQqxXBqPXTZL+w@mail.gmail.com/
[3] https://lore.kernel.org/git/CAPig+cSGXqJuaZPhUhOVX5X=LMrjVfv8ye_6ncMUbyKox1i7QA@mail.gmail.com/
[4] https://lore.kernel.org/git/CAPig+cTitWCs5vB=0iXuUyEY22c0gvjXvY1ZtTT90s74ydhE=A@mail.gmail.com/
[5] https://lore.kernel.org/git/CACsJy8ChM99n6skQCv-GmFiod19mnwwH4j-6R+cfZSiVFAxjgA@mail.gmail.com/
[6] https://lore.kernel.org/git/xmqq8sctlgzx.fsf@gitster.c.googlers.com/
Changes in v3
=============
v3 is 1-patch bigger than v2 and it includes the following changes:
* Dropped CQUOTE_NODQ flag in the the locked reason to return a string
enclosed with double quotes if the text reason contains especial
characters, like newlines.
* fix in t2402 to ensure the locked worktree is properly cleaned up,
is move to its own patch.
* In worktree_prune_reason(), the `path` variable is initialize
with NULL to make the code easier to follow.
* The removal of `is_main_worktree()` before `worktree_lock_reason()`
is moved to the patch that actually changes the API to be more gentle
with the main worktree instead of refactoring the code in the next
patches that adds the annotations.
* Drop the `*reason` in `(reason && *reason)` as we know that
worktree_prune_reason() is either going to return a non-empty string
or NULL which makes the code easier to follow.
* The --verbose test for the locked annotation now tests that "locked"
annotation stays on the same line when there is no locked reason.
* Small documentation updates and make the "git worktree --verbose"
example a little consistent with the worktree path.
Changes in v2
=============
v2 changes considerably from v1 taking into account several comments
suggested by Eric Sunshine and Phillip Wood (thanks guys for the
insightful comments and patience reviewing my patches :) ).
* The biggest change are the way the series is organized. In v1 all the
code was implemented in the fourth patch, all the tests and documentation
updates was added in sixth and seventh patch respectfully, in v2 each
patch introduces the annotations and verbose mode together with theirs
respective test and documentation updates.
* Several rewrite of the commit messages
* In v1 the fifth patch was introducing a new function to escape newline
characters for the "locked" attribute. However, we already have this
feature from the quote.h API. So the new function is dropped and the
changes are squashed into v2's fourth patch.
* The new prunable annotation and locked annotation that was introduced
by [1] was refactor to not poke around the worktree private fields.
* Refactoring of the worktree_prune_reason() to cache the prune_reason
and refactor to early return the use cases following the more common
pattern used on Git's codebase.
* Few documentation rewrites, most notably the `--verbose` and `--expire`
doc sentences for the list command are moved to its own line to clearly
separate the description from the others commands instead of continuing
on the same paragraph.
* The `git unlock <worktree>` used in the test's cleanup is moved to after
we know the `git worktree locked` is executed successfully. This ensures
the unlock doesn't error in the cleanup stage which will make it harder
to debug the tests.
Rafael Silva (7):
worktree: libify should_prune_worktree()
worktree: teach worktree to lazy-load "prunable" reason
worktree: teach worktree_lock_reason() to gently handle main worktree
t2402: ensure locked worktree is properly cleaned up
worktree: teach `list --porcelain` to annotate locked worktree
worktree: teach `list` to annotate prunable worktree
worktree: teach `list` verbose mode
Documentation/git-worktree.txt | 62 +++++++++++++++++--
builtin/worktree.c | 110 +++++++++++----------------------
builtin/worktree.cc | 0
t/t2402-worktree-list.sh | 93 ++++++++++++++++++++++++++++
worktree.c | 91 ++++++++++++++++++++++++++-
worktree.h | 23 +++++++
6 files changed, 299 insertions(+), 80 deletions(-)
create mode 100644 builtin/worktree.cc
Range-diff against v2:
-: ---------- > 1: 66cd83ba42 worktree: libify should_prune_worktree()
1: 9d47fcb4a4 ! 2: 81f4824028 worktree: teach worktree to lazy-load "prunable" reason
@@ worktree.c: const char *worktree_lock_reason(struct worktree *wt)
+const char *worktree_prune_reason(struct worktree *wt, timestamp_t expire)
+{
+ struct strbuf reason = STRBUF_INIT;
-+ char *path;
++ char *path = NULL;
+
+ if (is_main_worktree(wt))
+ return NULL;
2: ac7c375bac ! 3: d0dbf0b709 worktree: teach worktree_lock_reason() to gently handle main worktree
@@ Commit message
worktree_lock_reason() aborts with an assertion failure when called on
the main worktree since locking the main worktree is nonsensical. Not
- only is this behaviour undocumented, thus callers might not even be aware
+ only is this behavior undocumented, thus callers might not even be aware
that the call could potentially crash the program, but it also forces
clients to be extra careful:
@@ Commit message
...
Since we know that locking makes no sense in the context of the main
- worktree, we can simpliy return false for the main worktree, thus making
+ worktree, we can simply return false for the main worktree, thus making
client code less complex by eliminating the need for the callers to have
inside knowledge about the implementation:
@@ Commit message
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva [off-list ref]
+ ## builtin/worktree.c ##
+@@ builtin/worktree.c: static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
+ strbuf_addstr(&sb, "(error)");
+ }
+
+- if (!is_main_worktree(wt) && worktree_lock_reason(wt))
++ if (worktree_lock_reason(wt))
+ strbuf_addstr(&sb, " locked");
+
+ printf("%s\n", sb.buf);
+
## worktree.c ##
@@ worktree.c: int is_main_worktree(const struct worktree *wt)
-: ---------- > 4: c6f8a3e9cd t2402: ensure locked worktree is properly cleaned up
3: ec1eb5a9f8 ! 5: 86c5253288 worktree: teach `list --porcelain` to annotate locked worktree
@@ Commit message
locked reason why it is locked
...
- The locked reason is quoted to prevent newline characters (i.e: LF or
- CRLF) breaking the --porcelain format as each attribute is shown per
- line. For example:
+ In porcelain mode, if the lock reason contains special characters
+ such as newlines, they are escaped with backslashes and the entire
+ reason is enclosed in double quotes. For example:
$ git worktree list --porcelain
...
- locked worktree's path mounted in\nremovable device
+ locked "worktree's path mounted in\nremovable device"
...
Furthermore, let's update the documentation to state that some
@@ Commit message
with its value depending whether the value is available or not. Thus
documenting the case of the new "locked" attribute.
- Additionally, c57b3367be (worktree: teach `list` to annotate locked
- worktree, 2020-10-11) introduced a new test to ensure locked worktrees
- are listed with "locked" annotation. However, the test does not clean up
- after itself as "git worktree prune" is not going to remove the locked
- worktree in the first place. This not only leaves the test in an unclean
- state it also potentially breaks following tests that relies on the
- "git worktree list" output. Let's fix that by unlocking the worktree
- before the "prune" command.
-
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva [off-list ref]
@@ Documentation/git-worktree.txt: Porcelain Format
-if the value is true. The first attribute of a working tree is always
-`worktree`, an empty line indicates the end of the record. For example:
+if the value is true. Some attributes (like `locked`) can be listed as a label
-+only or with a value depending whether a reason is available. The first
++only or with a value depending upon whether a reason is available. The first
+attribute of a working tree is always `worktree`, an empty line indicates the
+end of the record. For example:
@@ Documentation/git-worktree.txt: worktree /path/to/other-linked-worktree
HEAD 1234abc1234abc1234abc1234abc1234abc1234a
detached
-+worktree /path/to/linked-worktree-locked
++worktree /path/to/linked-worktree-locked-no-reason
+HEAD 5678abc5678abc5678abc5678abc5678abc5678c
-+branch refs/heads/locked
++branch refs/heads/locked-no-reason
+locked
+
+worktree /path/to/linked-worktree-locked-with-reason
@@ builtin/worktree.c: static void show_worktree_porcelain(struct worktree *wt)
+ reason = worktree_lock_reason(wt);
+ if (reason && *reason) {
+ struct strbuf sb = STRBUF_INIT;
-+ quote_c_style(reason, &sb, NULL, CQUOTE_NODQ);
++ quote_c_style(reason, &sb, NULL, 0);
+ printf("locked %s\n", sb.buf);
+ strbuf_release(&sb);
+ } else if (reason)
@@ builtin/worktree.c: static void show_worktree_porcelain(struct worktree *wt)
## t/t2402-worktree-list.sh ##
@@ t/t2402-worktree-list.sh: test_expect_success '"list" all worktrees with locked annotation' '
- git worktree add --detach locked master &&
- git worktree add --detach unlocked master &&
- git worktree lock locked &&
-+ test_when_finished "git worktree unlock locked" &&
- git worktree list >out &&
- grep "/locked *[0-9a-f].* locked$" out &&
! grep "/unlocked *[0-9a-f].* locked$" out
'
@@ t/t2402-worktree-list.sh: test_expect_success '"list" all worktrees with locked
+ echo "locked with reason" >>expect &&
+ git worktree add --detach locked1 &&
+ git worktree add --detach locked2 &&
++ # unlocked worktree should not be annotated with "locked"
+ git worktree add --detach unlocked &&
+ git worktree lock locked1 &&
+ git worktree lock locked2 --reason "with reason" &&
@@ t/t2402-worktree-list.sh: test_expect_success '"list" all worktrees with locked
+
+test_expect_success '"list" all worktrees --porcelain with locked reason newline escaped' '
+ test_when_finished "rm -rf locked_lf locked_crlf out actual expect && git worktree prune" &&
-+ printf "locked locked\\\\r\\\\nreason\n" >expect &&
-+ printf "locked locked\\\\nreason\n" >>expect &&
++ printf "locked \"locked\\\\r\\\\nreason\"\n" >expect &&
++ printf "locked \"locked\\\\nreason\"\n" >>expect &&
+ git worktree add --detach locked_lf &&
+ git worktree add --detach locked_crlf &&
-+ printf "locked\nreason\n\n" >reason_lf &&
-+ printf "locked\r\nreason\n\n" >reason_crlf &&
-+ git worktree lock locked_lf --reason "$(cat reason_lf)" &&
-+ git worktree lock locked_crlf --reason "$(cat reason_crlf)" &&
++ git worktree lock locked_lf --reason "$(printf "locked\nreason")" &&
++ git worktree lock locked_crlf --reason "$(printf "locked\r\nreason")" &&
+ test_when_finished "git worktree unlock locked_lf && git worktree unlock locked_crlf" &&
+ git worktree list --porcelain >out &&
+ grep "^locked" out >actual &&
4: f937df6460 ! 6: be814326ee worktree: teach `list` to annotate prunable worktree
@@ Documentation/git-worktree.txt: This can also be set up as the default behaviour
With `prune`, only expire unused working trees older than `<time>`.
++
+With `list`, annotate missing working trees as prunable if they are
-+older than `<mtime>`.
++older than `<time>`.
--reason <string>::
With `lock`, an explanation why the working tree is locked.
@@ Documentation/git-worktree.txt: $ git worktree list
+
+------------
+$ git worktree list
-+/path/to/linked-worktree abcd1234 [master]
-+/path/to/locked-worktreee acbd5678 (brancha) locked
-+/path/to/prunable-worktree 5678abc (detached HEAD) prunable
++/path/to/linked-worktree abcd1234 [master]
++/path/to/locked-worktreee acbd5678 (brancha) locked
++/path/to/prunable-worktree 5678abc (detached HEAD) prunable
+------------
+
Porcelain Format
@@ builtin/worktree.c: static void show_worktree_porcelain(struct worktree *wt)
printf("locked\n");
+ reason = worktree_prune_reason(wt, expire);
-+ if (reason && *reason)
++ if (reason)
+ printf("prunable %s\n", reason);
+
printf("\n");
}
@@ builtin/worktree.c: static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
- struct strbuf sb = STRBUF_INIT;
- int cur_path_len = strlen(wt->path);
- int path_adj = cur_path_len - utf8_strwidth(wt->path);
-+ const char *reason;
-
- strbuf_addf(&sb, "%-*s ", 1 + path_maxlen + path_adj, wt->path);
- if (wt->is_bare)
-@@ builtin/worktree.c: static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
- strbuf_addstr(&sb, "(error)");
- }
-
-- if (!is_main_worktree(wt) && worktree_lock_reason(wt))
-+ reason = worktree_lock_reason(wt);
-+ if (reason)
+ if (worktree_lock_reason(wt))
strbuf_addstr(&sb, " locked");
-+ reason = worktree_prune_reason(wt, expire);
-+ if (reason)
++ if (worktree_prune_reason(wt, expire))
+ strbuf_addstr(&sb, " prunable");
+
printf("%s\n", sb.buf);
@@ builtin/worktree.c: static int list(int ac, const char **av, const char *prefix)
if (ac)
usage_with_options(worktree_usage, options);
+ ## builtin/worktree.cc (new) ##
+
## t/t2402-worktree-list.sh ##
@@ t/t2402-worktree-list.sh: test_expect_success '"list" all worktrees --porcelain with locked reason newline
test_cmp expect actual
5: 0b6ab6dddb ! 7: b0688f142d worktree: teach `list` verbose mode
@@ Commit message
worktrees a reason might be available provided by the user via `lock`
command.
- Let's teach "git worktree list" to output the reason why the worktrees
- are being annotated. The reason is a text that can take virtually any
- size and appending the text on the default columned format will make it
- difficult to extend the command with other annotations and not fit nicely
- on the screen. In order to address this shortcoming the annotation is
- then moved to the next line indented followed by the reason, if the
- reason is not available the annotation stays on the same line as the
- worktree itself.
+ Let's teach "git worktree list" a --verbose mode that outputs the reason
+ why the worktrees are being annotated. The reason is a text that can take
+ virtually any size and appending the text on the default columned format
+ will make it difficult to extend the command with other annotations and
+ not fit nicely on the screen. In order to address this shortcoming the
+ annotation is then moved to the next line indented followed by the reason
+ If the reason is not available the annotation stays on the same line as
+ the worktree itself.
The output of "git worktree list" with verbose becomes like so:
$ git worktree list --verbose
...
- /path/to/locked acb124 [branch-a] locked
- /path/to/locked-with-reason acc125 [branch-b]
+ /path/to/locked-no-reason acb124 [branch-a] locked
+ /path/to/locked-with-reason acc125 [branch-b]
locked: worktree with a locked reason
- /path/to/prunable-reason ace127 [branch-d]
+ /path/to/prunable-reason ace127 [branch-d]
prunable: gitdir file points to non-existent location
...
@@ Documentation/git-worktree.txt: This can also be set up as the default behaviour
--expire <time>::
With `prune`, only expire unused working trees older than `<time>`.
@@ Documentation/git-worktree.txt: $ git worktree list
- /path/to/prunable-worktree 5678abc (detached HEAD) prunable
+ /path/to/prunable-worktree 5678abc (detached HEAD) prunable
------------
+For these annotations, a reason might also be available and this can be
@@ Documentation/git-worktree.txt: $ git worktree list
+
+------------
+$ git worktree list --verbose
-+/path/to/linked-worktree abcd1234 [master]
-+/path/to/locked-worktreee acbd5678 (brancha)
-+ locked: working tree path is mounted on a removable device
-+/path/to/locked-no-reason abcd578 (detached HEAD) locked
-+/path/to/prunable-worktree 5678abc (detached HEAD)
++/path/to/linked-worktree abcd1234 [master]
++/path/to/locked-worktree-no-reason abcd5678 (detached HEAD) locked
++/path/to/locked-worktree-with-reason 1234abcd (brancha)
++ locked: working tree path is mounted on a portable device
++/path/to/prunable-worktree 5678abc1 (detached HEAD)
+ prunable: gitdir file points to non-existent location
+------------
+
@@ Documentation/git-worktree.txt: $ git worktree list
## builtin/worktree.c ##
@@ builtin/worktree.c: static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
+ struct strbuf sb = STRBUF_INIT;
+ int cur_path_len = strlen(wt->path);
+ int path_adj = cur_path_len - utf8_strwidth(wt->path);
++ const char *reason;
+
+ strbuf_addf(&sb, "%-*s ", 1 + path_maxlen + path_adj, wt->path);
+ if (wt->is_bare)
+@@ builtin/worktree.c: static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
+ strbuf_addstr(&sb, "(error)");
}
- reason = worktree_lock_reason(wt);
-- if (reason)
+- if (worktree_lock_reason(wt))
++ reason = worktree_lock_reason(wt);
+ if (verbose && reason && *reason)
+ strbuf_addf(&sb, "\n\tlocked: %s", reason);
+ else if (reason)
strbuf_addstr(&sb, " locked");
- reason = worktree_prune_reason(wt, expire);
-- if (reason)
-+ if (verbose && reason && *reason)
+- if (worktree_prune_reason(wt, expire))
++ reason = worktree_prune_reason(wt, expire);
++ if (verbose && reason)
+ strbuf_addf(&sb, "\n\tprunable: %s", reason);
+ else if (reason)
strbuf_addstr(&sb, " prunable");
@@ t/t2402-worktree-list.sh: test_expect_success '"list" all worktrees with prunabl
+'
+
+test_expect_success '"list" all worktrees --verbose with locked' '
-+ test_when_finished "rm -rf locked out actual expect && git worktree prune" &&
-+ git worktree add locked --detach &&
-+ git worktree lock locked --reason "with reason" &&
-+ test_when_finished "git worktree unlock locked" &&
-+ echo "$(git -C locked rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >expect &&
++ test_when_finished "rm -rf locked1 locked2 out actual expect && git worktree prune" &&
++ git worktree add locked1 --detach &&
++ git worktree add locked2 --detach &&
++ git worktree lock locked1 &&
++ git worktree lock locked2 --reason "with reason" &&
++ test_when_finished "git worktree unlock locked1 && git worktree unlock locked2" &&
++ echo "$(git -C locked2 rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >expect &&
+ printf "\tlocked: with reason\n" >>expect &&
+ git worktree list --verbose >out &&
-+ sed -n "s/ */ /g;/\/locked *[0-9a-f].*$/,/locked: .*$/p" <out >actual &&
++ grep "/locked1 *[0-9a-f].* locked$" out &&
++ sed -n "s/ */ /g;/\/locked2 *[0-9a-f].*$/,/locked: .*$/p" <out >actual &&
+ test_cmp actual expect
+'
+
--
2.30.0.421.g32f838e276
Hi Rafael
Thanks for reworking this to use c_quote_path(). I have a couple of comments below.
On 19/01/2021 21:27, Rafael Silva wrote:
quoted hunk
Commit c57b3367be (worktree: teach `list` to annotate locked worktree,
2020-10-11) taught "git worktree list" to annotate locked worktrees by
appending "locked" text to its output, however, this is not listed in
the --porcelain format.
Teach "list --porcelain" to do the same and add a "locked" attribute
followed by its reason, thus making both default and porcelain format
consistent. If the locked reason is not available then only "locked"
is shown.
The output of the "git worktree list --porcelain" becomes like so:
$ git worktree list --porcelain
...
worktree /path/to/locked
HEAD 123abcdea123abcd123acbd123acbda123abcd12
detached
locked
worktree /path/to/locked-with-reason
HEAD abc123abc123abc123abc123abc123abc123abc1
detached
locked reason why it is locked
...
In porcelain mode, if the lock reason contains special characters
such as newlines, they are escaped with backslashes and the entire
reason is enclosed in double quotes. For example:
$ git worktree list --porcelain
...
locked "worktree's path mounted in\nremovable device"
...
Furthermore, let's update the documentation to state that some
attributes in the porcelain format might be listed alone or together
with its value depending whether the value is available or not. Thus
documenting the case of the new "locked" attribute.
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva <redacted>
---
Documentation/git-worktree.txt | 16 ++++++++++++++--
builtin/worktree.c | 13 +++++++++++++
t/t2402-worktree-list.sh | 30 ++++++++++++++++++++++++++++++
3 files changed, 57 insertions(+), 2 deletions(-)
@@ -377,8 +377,10 @@ Porcelain Format The porcelain format has a line per attribute. Attributes are listed with a label and value separated by a single space. Boolean attributes (like `bare` and `detached`) are listed as a label only, and are present only-if the value is true. The first attribute of a working tree is always-`worktree`, an empty line indicates the end of the record. For example:+if the value is true. Some attributes (like `locked`) can be listed as a label+only or with a value depending upon whether a reason is available. The first+attribute of a working tree is always `worktree`, an empty line indicates the+end of the record. For example:
I think it would be helpful to document that the reasons are quoted according core.quotePath.
I'm not sure if it is worth changing this but I wonder if it would be easier to parse the output if the names of attributes with optional reasons were always followed by a space even when there is no reason, otherwise the code that parses the output has to check for the name followed by a space or newline. A script that only cares if the worktree is locked can parse the output with
l.starts_with("locked ")
rather than having to do
l.starts_with("locked ") || l == "locked\n"
Best Wishes
Phillip
From: Rafael Silva <hidden> Date: 2021-01-21 15:28:49
Hi Phillip,
Phillip Wood writes:
Hi Rafael
Thanks for reworking this to use c_quote_path(). I have a couple of
comments below.
Thanks for reviewing this patch.
On 19/01/2021 21:27, Rafael Silva wrote:
quoted
Commit c57b3367be (worktree: teach `list` to annotate locked worktree,
2020-10-11) taught "git worktree list" to annotate locked worktrees by
appending "locked" text to its output, however, this is not listed in
the --porcelain format.
Teach "list --porcelain" to do the same and add a "locked" attribute
followed by its reason, thus making both default and porcelain format
consistent. If the locked reason is not available then only "locked"
is shown.
The output of the "git worktree list --porcelain" becomes like so:
$ git worktree list --porcelain
...
worktree /path/to/locked
HEAD 123abcdea123abcd123acbd123acbda123abcd12
detached
locked
worktree /path/to/locked-with-reason
HEAD abc123abc123abc123abc123abc123abc123abc1
detached
locked reason why it is locked
...
In porcelain mode, if the lock reason contains special characters
such as newlines, they are escaped with backslashes and the entire
reason is enclosed in double quotes. For example:
$ git worktree list --porcelain
...
locked "worktree's path mounted in\nremovable device"
...
Furthermore, let's update the documentation to state that some
attributes in the porcelain format might be listed alone or together
with its value depending whether the value is available or not. Thus
documenting the case of the new "locked" attribute.
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva <redacted>
---
Documentation/git-worktree.txt | 16 ++++++++++++++--
builtin/worktree.c | 13 +++++++++++++
t/t2402-worktree-list.sh | 30 ++++++++++++++++++++++++++++++
3 files changed, 57 insertions(+), 2 deletions(-)
diff --git a/Documentation/git-worktree.txt
b/Documentation/git-worktree.txt
index 02a706c4c0..7cb8124f28 100644
@@ -377,8 +377,10 @@ Porcelain Format The porcelain format has a line per attribute. Attributes are listed with a label and value separated by a single space. Boolean attributes (like `bare` and `detached`) are listed as a label only, and are present only-if the value is true. The first attribute of a working tree is always-`worktree`, an empty line indicates the end of the record. For example:+if the value is true. Some attributes (like `locked`) can be listed as a label+only or with a value depending upon whether a reason is available. The first+attribute of a working tree is always `worktree`, an empty line indicates the+end of the record. For example:
I think it would be helpful to document that the reasons are quoted
according core.quotePath.
Good point. I'll include this addition on the next revision.
--
Thanks
Rafael
From: Eric Sunshine <hidden> Date: 2021-01-24 07:52:23
On Tue, Jan 19, 2021 at 4:28 PM Rafael Silva
[off-list ref] wrote:
In c57b3367be (worktree: teach `list` to annotate locked worktree,
2020-10-11) introduced a new test to ensure locked worktrees are listed
with "locked" annotation. However, the test does not clean up after
itself as "git worktree prune" is not going to remove the locked worktree
in the first place. This not only leaves the test in an unclean state it
also potentially breaks following tests that relies on the
"git worktree list" output.
A couple grammos:
1) Drop "In" from the start of the first sentence.
2) s/relies/rely/
But please do not re-roll just for this. It's good enough as-is.
The patch itself is fine.
Let's fix that by unlocking the worktree before the "prune" command.
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva <redacted>
From: Eric Sunshine <hidden> Date: 2021-01-24 08:12:07
On Tue, Jan 19, 2021 at 4:28 PM Rafael Silva
[off-list ref] wrote:
quoted hunk
[...]
Teach "list --porcelain" to do the same and add a "locked" attribute
followed by its reason, thus making both default and porcelain format
consistent. If the locked reason is not available then only "locked"
is shown.
[...]
Signed-off-by: Rafael Silva <redacted>
---
@@ -72,6 +72,36 @@ test_expect_success '"list" all worktrees with locked annotation' '+test_expect_success '"list" all worktrees --porcelain with locked' '+ test_when_finished "rm -rf locked1 locked2 unlocked out actual expect && git worktree prune" &&+ echo "locked" >expect &&+ echo "locked with reason" >>expect &&+ git worktree add --detach locked1 &&+ git worktree add --detach locked2 &&+ # unlocked worktree should not be annotated with "locked"+ git worktree add --detach unlocked &&+ git worktree lock locked1 &&+ git worktree lock locked2 --reason "with reason" &&+ test_when_finished "git worktree unlock locked1 && git worktree unlock locked2" &&
There's a minor problem here. If the second `git worktree lock`
command fails, test_when_finished() will never be invoked, which means
that the first lock won't get cleaned up, thus the worktree won't get
pruned. To fix, you'd want:
git worktree lock locked1 &&
test_when_finished "git worktree unlock locked1" &&
git worktree lock locked2 --reason "with reason" &&
test_when_finished "git worktree unlock locked2" &&
From: Eric Sunshine <hidden> Date: 2021-01-24 08:27:29
On Wed, Jan 20, 2021 at 6:00 AM Phillip Wood [off-list ref] wrote:
On 19/01/2021 21:27, Rafael Silva wrote:
quoted
The porcelain format has a line per attribute. Attributes are listed with a label and value separated by a single space. Boolean attributes (like `bare` and `detached`) are listed as a label only, and are present only+if the value is true. Some attributes (like `locked`) can be listed as a label+only or with a value depending upon whether a reason is available. The first+attribute of a working tree is always `worktree`, an empty line indicates the+end of the record. For example:
I think it would be helpful to document that the reasons are quoted
according core.quotePath.
Good idea.
I'm not sure if it is worth changing this but I wonder if it would be
easier to parse the output if the names of attributes with optional
reasons were always followed by a space even when there is no reason,
otherwise the code that parses the output has to check for the name
followed by a space or newline. A script that only cares if the worktree
is locked can parse the output with
l.starts_with("locked ")
rather than having to do
l.starts_with("locked ") || l == "locked\n"
I see where you're coming from with this suggestion, though my
knee-jerk reaction is that it would be undesirable. Even after mulling
it over for a few days, I still haven't managed to convince myself
that it would be a good idea. There are a couple reasons (at least)
for my negative reaction. The primary reason is that the trailing
space is "invisible", and as such could end up being as confusing as
it is helpful for the simple parsing case (taking into consideration
that people often don't consult documentation). The second reason is
that we're already expecting clients to be able to parse C-style
quoting/escaping of the reason, so asking them to also distinguish
between a single token `locked` and a `locked reason-for-lock` seems
like very, very minor extra complexity. (It also just feels a bit
sloppy to have that trailing space, but that's a quite minor concern.)
From: Eric Sunshine <hidden> Date: 2021-01-24 08:43:38
On Tue, Jan 19, 2021 at 4:28 PM Rafael Silva
[off-list ref] wrote:
quoted hunk
[...]
Let's teach "git worktree list" a --verbose mode that outputs the reason
why the worktrees are being annotated. The reason is a text that can take
virtually any size and appending the text on the default columned format
will make it difficult to extend the command with other annotations and
not fit nicely on the screen. In order to address this shortcoming the
annotation is then moved to the next line indented followed by the reason
If the reason is not available the annotation stays on the same line as
the worktree itself.
[...]
Signed-off-by: Rafael Silva <redacted>
---
@@ -134,6 +134,36 @@ test_expect_success '"list" all worktrees with prunable consistent with "prune"'+test_expect_success '"list" all worktrees --verbose with locked' '+ test_when_finished "rm -rf locked1 locked2 out actual expect && git worktree prune" &&+ git worktree add locked1 --detach &&+ git worktree add locked2 --detach &&+ git worktree lock locked1 &&+ git worktree lock locked2 --reason "with reason" &&+ test_when_finished "git worktree unlock locked1 && git worktree unlock locked2" &&
Same minor problem here as mentioned in my review of [5/7]: If locking
of the second worktree fails then test_when_finished() won't get
invoked, so the first worktree won't get unlocked, thus won't be
pruned. To fix:
git worktree lock locked1 &&
test_when_finished "git worktree unlock locked1" &&
git worktree lock locked2 --reason "with reason" &&
test_when_finished "git worktree unlock locked2" &&
quoted hunk
+ echo "$(git -C locked2 rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >expect &&+ printf "\tlocked: with reason\n" >>expect &&+ git worktree list --verbose >out &&+ grep "/locked1 *[0-9a-f].* locked$" out &&+ sed -n "s/ */ /g;/\/locked2 *[0-9a-f].*$/,/locked: .*$/p" <out >actual &&+ test_cmp actual expect+'
From: Eric Sunshine <hidden> Date: 2021-01-24 08:52:35
On Tue, Jan 19, 2021 at 4:28 PM Rafael Silva
[off-list ref] wrote:
Changes in v3
=============
v3 is 1-patch bigger than v2 and it includes the following changes:
Thanks. I gave v3 a complete read-through, and it is just about perfect.
Aside from a very minor problem with the tests in [5/7] and [7/7] and
the documentation enhancement (`core.quotePath`) suggested by Phillip,
this is really, really close. (In fact, I would be happy to accept v3
as-is; those minor changes could easily be done on top.)
I look forward to giving v4 my Reviewed-by:.
The range-diff seems slightly off. The "libify
should_prune_worktree()" patch existed in v1 and v2, but the
range-diff suggests that it's new with v3. You probably just need a
minor adjustment to your `git format-patch --range-diff` invocation.
From: Rafael Silva <hidden> Date: 2021-01-24 10:29:42
Eric Sunshine writes:
On Tue, Jan 19, 2021 at 4:28 PM Rafael Silva
[off-list ref] wrote:
quoted
In c57b3367be (worktree: teach `list` to annotate locked worktree,
2020-10-11) introduced a new test to ensure locked worktrees are listed
with "locked" annotation. However, the test does not clean up after
itself as "git worktree prune" is not going to remove the locked worktree
in the first place. This not only leaves the test in an unclean state it
also potentially breaks following tests that relies on the
"git worktree list" output.
A couple grammos:
1) Drop "In" from the start of the first sentence.
2) s/relies/rely/
But please do not re-roll just for this. It's good enough as-is.
The patch itself is fine.
Nice catch. I'll be re-rolling the series with the documentation addition
suggested by Phillip and the test fixes suggested by you. So, I'll
include these changes as well.
--
Thanks
Rafael
From: Rafael Silva <hidden> Date: 2021-01-24 10:29:42
Eric Sunshine writes:
On Tue, Jan 19, 2021 at 4:28 PM Rafael Silva
[off-list ref] wrote:
quoted
[...]
Teach "list --porcelain" to do the same and add a "locked" attribute
followed by its reason, thus making both default and porcelain format
consistent. If the locked reason is not available then only "locked"
is shown.
[...]
Signed-off-by: Rafael Silva <redacted>
---
@@ -72,6 +72,36 @@ test_expect_success '"list" all worktrees with locked annotation' '+test_expect_success '"list" all worktrees --porcelain with locked' '+ test_when_finished "rm -rf locked1 locked2 unlocked out actual expect && git worktree prune" &&+ echo "locked" >expect &&+ echo "locked with reason" >>expect &&+ git worktree add --detach locked1 &&+ git worktree add --detach locked2 &&+ # unlocked worktree should not be annotated with "locked"+ git worktree add --detach unlocked &&+ git worktree lock locked1 &&+ git worktree lock locked2 --reason "with reason" &&+ test_when_finished "git worktree unlock locked1 && git worktree unlock locked2" &&
There's a minor problem here. If the second `git worktree lock`
command fails, test_when_finished() will never be invoked, which means
that the first lock won't get cleaned up, thus the worktree won't get
pruned. To fix, you'd want:
git worktree lock locked1 &&
test_when_finished "git worktree unlock locked1" &&
git worktree lock locked2 --reason "with reason" &&
test_when_finished "git worktree unlock locked2" &&
Excellent point. This case didn't occur to me when I was working on v3, I
will make this change in the next revision.
From: Rafael Silva <hidden> Date: 2021-01-24 10:29:42
Eric Sunshine writes:
On Tue, Jan 19, 2021 at 4:28 PM Rafael Silva
[off-list ref] wrote:
quoted
[...]
Let's teach "git worktree list" a --verbose mode that outputs the reason
why the worktrees are being annotated. The reason is a text that can take
virtually any size and appending the text on the default columned format
will make it difficult to extend the command with other annotations and
not fit nicely on the screen. In order to address this shortcoming the
annotation is then moved to the next line indented followed by the reason
If the reason is not available the annotation stays on the same line as
the worktree itself.
[...]
Signed-off-by: Rafael Silva <redacted>
---
@@ -134,6 +134,36 @@ test_expect_success '"list" all worktrees with prunable consistent with "prune"'+test_expect_success '"list" all worktrees --verbose with locked' '+ test_when_finished "rm -rf locked1 locked2 out actual expect && git worktree prune" &&+ git worktree add locked1 --detach &&+ git worktree add locked2 --detach &&+ git worktree lock locked1 &&+ git worktree lock locked2 --reason "with reason" &&+ test_when_finished "git worktree unlock locked1 && git worktree unlock locked2" &&
Same minor problem here as mentioned in my review of [5/7]: If locking
of the second worktree fails then test_when_finished() won't get
invoked, so the first worktree won't get unlocked, thus won't be
pruned. To fix:
git worktree lock locked1 &&
test_when_finished "git worktree unlock locked1" &&
git worktree lock locked2 --reason "with reason" &&
test_when_finished "git worktree unlock locked2" &&
From: Rafael Silva <hidden> Date: 2021-01-27 08:07:17
Add worktree_prune_reason() to allow a caller to discover whether a
worktree is prunable and the reason that it is, much like
worktree_lock_reason() indicates whether a worktree is locked and the
reason for the lock. As with worktree_lock_reason(), retrieve the
prunable reason lazily and cache it in the `worktree` structure.
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva <redacted>
---
worktree.c | 20 ++++++++++++++++++++
worktree.h | 9 +++++++++
2 files changed, 29 insertions(+)
@@ -11,11 +11,13 @@ struct worktree {char*id;char*head_ref;/* NULL if HEAD is broken or detached */char*lock_reason;/* private - use worktree_lock_reason */+char*prune_reason;/* private - use worktree_prune_reason */structobject_idhead_oid;intis_detached;intis_bare;intis_current;intlock_reason_valid;/* private */+intprune_reason_valid;/* private */};/*
@@ -73,6 +75,13 @@ int is_main_worktree(const struct worktree *wt);*/constchar*worktree_lock_reason(structworktree*wt);+/*+*Returnthereasonstringifthegivenworktreeshouldbepruned,otherwise+*NULLifitshouldnotbepruned.`expire`definesagraceperiodtoprune+*theworktreewhenitspathdoesnotexist.+*/+constchar*worktree_prune_reason(structworktree*wt,timestamp_texpire);+/**Returntrueifworktreeentryshouldbepruned,alongwiththereasonfor*pruning.Otherwise,returnfalseandtheworktree'spathin`wtpath`,or
From: Rafael Silva <hidden> Date: 2021-01-27 08:08:10
In c57b3367be (worktree: teach `list` to annotate locked worktree,
2020-10-11) we taught `git worktree list` to annotate working trees that
is locked by appending "locked" text in order to signalize to the user
that a working tree is locked. During the review, there was some
discussion about additional annotations and information that `list`
command could provide to the user that has long been envisioned and
mentioned in [2], [3] and [4].
This patch series addresses some of these changes by teaching
`worktree list` to show "prunable" annotation, adding verbose mode and
extending the --porcelain format with prunable and locked annotation as
follow up from [1]. Additionally, it addresses one shortcoming for porcelain
format to escape any newline characters (LF or CRLF) and quote the lock
reason to prevent breaking the format that is mentioned in [4] and [1] during
the review cycle.
This patch series includes:
1. The first patch moves the should_prune_worktree() machinery to the top-level
worktree.c exposing the function as general API, that will be reference
by should_prune_worktree() wrapper implemented on the second patch.
The original idea was to not only move should_prune_worktree() but also
refactor to accept a "struct worktree" and load the information directly,
which allows simplify the `prune` command by reusing get_worktrees().
However this seems to also require refactoring get_worktrees() itself
to return "non-valid" working trees that can/should be pruned. This is
also mentioned in [5]. Having the wrapper function makes it easier to add
the prunable annotation without touching the get_worktrees() and the
other worktree sub commands. The refactoring can be addressed in a
future patch, if this turns out to be good idea. One possible approach
is to teach get_worktrees() to take additional flags that will tell
whether to return only valid or all worktrees in GIT_DIR/worktrees/
directory and address its own possible shortcoming.
2. Introduces the worktree_prune_reason() to discovery whether a worktree
is prunable along with two new fields in the `worktree` structure. The
function mimics the workree_lock_reason() API.
3. The third patch changes worktree_lock_reason() to be more gentle for
the main working tree to simply returning NULL instead of aborting the
program via assert() macro. This allow us to simplify the code that
checks if the working tree is locked for default and porcelain format.
This changes is also mentioned in [6].
4. Fix t2402 added in [1] to ensure the locked worktree is properly cleaned up.
5. The fourth patch adds the "locked" attribute for the porcelain format
in order to make both default and --porcelain format consistent.
6. The fifth patch introduces "prunable" annotation for both default
and --porcelain format.
7. The sixth patch introduces verbose mode to expand the `list` default
format and show each annotation reason when its available.
Series is built on top of 66e871b664 (The third batch, 2021-01-15).
[1] https://lore.kernel.org/git/20200928154953.30396-1-rafaeloliveira.cs@gmail.com/
[2] https://lore.kernel.org/git/CAPig+cQF6V8HNdMX5AZbmz3_w2WhSfA4SFfNhQqxXBqPXTZL+w@mail.gmail.com/
[3] https://lore.kernel.org/git/CAPig+cSGXqJuaZPhUhOVX5X=LMrjVfv8ye_6ncMUbyKox1i7QA@mail.gmail.com/
[4] https://lore.kernel.org/git/CAPig+cTitWCs5vB=0iXuUyEY22c0gvjXvY1ZtTT90s74ydhE=A@mail.gmail.com/
[5] https://lore.kernel.org/git/CACsJy8ChM99n6skQCv-GmFiod19mnwwH4j-6R+cfZSiVFAxjgA@mail.gmail.com/
[6] https://lore.kernel.org/git/xmqq8sctlgzx.fsf@gitster.c.googlers.com/
Changes in v4
=============
Thanks Eric Sunshine and Phillip Wood for the review and suggestions.
* Added documentation to explain that the lock reason is quoted with
the same rules as described for `core.quotePath`.
* The `worktree unlock` issued in the test cleanup is splitted and
executed after each `worktree lock` to ensure the unlock is only
done after we know each locked command was successful.
* Fix a couple of grammos in the [4/7] commit message.
Changes in v3
=============
v3 is 1-patch bigger than v2 and it includes the changes:
* Dropped CQUOTE_NODQ flag in the the locked reason to return a string
enclosed with double quotes if the text reason contains especial
characters, like newlines.
* fix in t2402 to ensure the locked worktree is properly cleaned up,
is move to its own patch.
* In worktree_prune_reason(), the `path` variable is initialize
with NULL to make the code easier to follow.
* The removal of `is_main_worktree()` before `worktree_lock_reason()`
is moved to the patch that actually changes the API to be more gentle
with the main worktree instead of refactoring the code in the next
patches that adds the annotations.
* Drop the `*reason` in `(reason && *reason)` as we know that
worktree_prune_reason() is either going to return a non-empty string
or NULL which makes the code easier to follow.
* The --verbose test for the locked annotation now tests that "locked"
annotation stays on the same line when there is no locked reason.
* Small documentation updates and make the "git worktree --verbose"
example a little consistent with the worktree path.
Changes in v2
=============
v2 changes considerably from v1 taking into account several comments
suggested by Eric Sunshine and Phillip Wood (thanks guys for the
insightful comments and patience reviewing my patches :) ).
* The biggest change are the way the series is organized. In v1 all the
code was implemented in the fourth patch, all the tests and documentation
updates was added in sixth and seventh patch respectfully, in v2 each
patch introduces the annotations and verbose mode together with theirs
respective test and documentation updates.
* Several rewrite of the commit messages
* In v1 the fifth patch was introducing a new function to escape newline
characters for the "locked" attribute. However, we already have this
feature from the quote.h API. So the new function is dropped and the
changes are squashed into v2's fourth patch.
* The new prunable annotation and locked annotation that was introduced
by [1] was refactor to not poke around the worktree private fields.
* Refactoring of the worktree_prune_reason() to cache the prune_reason
and refactor to early return the use cases following the more common
pattern used on Git's codebase.
* Few documentation rewrites, most notably the `--verbose` and `--expire`
doc sentences for the list command are moved to its own line to clearly
separate the description from the others commands instead of continuing
on the same paragraph.
* The `git unlock <worktree>` used in the test's cleanup is moved to after
we know the `git worktree locked` is executed successfully. This ensures
the unlock doesn't error in the cleanup stage which will make it harder
to debug the tests.
Rafael Silva (7):
worktree: libify should_prune_worktree()
worktree: teach worktree to lazy-load "prunable" reason
worktree: teach worktree_lock_reason() to gently handle main worktree
t2402: ensure locked worktree is properly cleaned up
worktree: teach `list --porcelain` to annotate locked worktree
worktree: teach `list` to annotate prunable worktree
worktree: teach `list` verbose mode
Documentation/git-worktree.txt | 74 ++++++++++++++++++++--
builtin/worktree.c | 110 +++++++++++----------------------
t/t2402-worktree-list.sh | 96 ++++++++++++++++++++++++++++
worktree.c | 91 ++++++++++++++++++++++++++-
worktree.h | 23 +++++++
5 files changed, 314 insertions(+), 80 deletions(-)
Range-diff against v3:
1: fc4252c129 = 1: 66cd83ba42 worktree: libify should_prune_worktree()
2: 664d84155b = 2: 81f4824028 worktree: teach worktree to lazy-load "prunable" reason
3: cf334da2f0 = 3: c62ecf60d6 worktree: teach worktree_lock_reason() to gently handle main worktree
4: ecb11793d9 ! 4: d2ea467927 t2402: ensure locked worktree is properly cleaned up
@@ Metadata
## Commit message ##
t2402: ensure locked worktree is properly cleaned up
- In c57b3367be (worktree: teach `list` to annotate locked worktree,
+ c57b3367be (worktree: teach `list` to annotate locked worktree,
2020-10-11) introduced a new test to ensure locked worktrees are listed
with "locked" annotation. However, the test does not clean up after
itself as "git worktree prune" is not going to remove the locked worktree
in the first place. This not only leaves the test in an unclean state it
- also potentially breaks following tests that relies on the
+ also potentially breaks following tests that rely on the
"git worktree list" output.
Let's fix that by unlocking the worktree before the "prune" command.
5: 1fb68562f6 ! 5: 1b7661a0b3 worktree: teach `list --porcelain` to annotate locked worktree
@@ Commit message
with its value depending whether the value is available or not. Thus
documenting the case of the new "locked" attribute.
+ Helped-by: Phillip Wood [off-list ref]
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva [off-list ref]
@@ Documentation/git-worktree.txt: worktree /path/to/other-linked-worktree
+branch refs/heads/locked-with-reason
+locked reason why is locked
+
++------------
++
++If the lock reason contains "unusual" characters such as newline, they
++are escaped and the entire reason is quoted as explained for the
++configuration variable `core.quotePath` (see linkgit:git-config[1]).
++For Example:
++
++------------
++$ git worktree list --porcelain
++...
++locked "reason\nwhy is locked"
++...
------------
EXAMPLES
@@ t/t2402-worktree-list.sh: test_expect_success '"list" all worktrees with locked
+ # unlocked worktree should not be annotated with "locked"
+ git worktree add --detach unlocked &&
+ git worktree lock locked1 &&
++ test_when_finished "git worktree unlock locked1" &&
+ git worktree lock locked2 --reason "with reason" &&
-+ test_when_finished "git worktree unlock locked1 && git worktree unlock locked2" &&
++ test_when_finished "git worktree unlock locked2" &&
+ git worktree list --porcelain >out &&
+ grep "^locked" out >actual &&
+ test_cmp expect actual
@@ t/t2402-worktree-list.sh: test_expect_success '"list" all worktrees with locked
+ git worktree add --detach locked_lf &&
+ git worktree add --detach locked_crlf &&
+ git worktree lock locked_lf --reason "$(printf "locked\nreason")" &&
++ test_when_finished "git worktree unlock locked_lf" &&
+ git worktree lock locked_crlf --reason "$(printf "locked\r\nreason")" &&
-+ test_when_finished "git worktree unlock locked_lf && git worktree unlock locked_crlf" &&
++ test_when_finished "git worktree unlock locked_crlf" &&
+ git worktree list --porcelain >out &&
+ grep "^locked" out >actual &&
+ test_cmp expect actual
6: 834584f826 = 6: c29ad7ffb1 worktree: teach `list` to annotate prunable worktree
7: 35ad456fbb ! 7: 7938950d04 worktree: teach `list` verbose mode
@@ t/t2402-worktree-list.sh: test_expect_success '"list" all worktrees with prunabl
+ git worktree add locked1 --detach &&
+ git worktree add locked2 --detach &&
+ git worktree lock locked1 &&
++ test_when_finished "git worktree unlock locked1" &&
+ git worktree lock locked2 --reason "with reason" &&
-+ test_when_finished "git worktree unlock locked1 && git worktree unlock locked2" &&
++ test_when_finished "git worktree unlock locked2" &&
+ echo "$(git -C locked2 rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >expect &&
+ printf "\tlocked: with reason\n" >>expect &&
+ git worktree list --verbose >out &&
--
2.30.0.373.geade8fefe8
From: Rafael Silva <hidden> Date: 2021-01-27 08:09:12
The "git worktree list" command shows the absolute path to the worktree,
the commit that is checked out, the name of the branch, and a "locked"
annotation if the worktree is locked, however, it does not indicate
whether the worktree is prunable.
The "prune" command will remove a worktree if it is prunable unless
`--dry-run` option is specified. This could lead to a worktree being
removed without the user realizing before it is too late, in case the
user forgets to pass --dry-run for instance. If the "list" command shows
which worktree is prunable, the user could verify before running
"git worktree prune" and hopefully prevents the working tree to be
removed "accidentally" on the worse case scenario.
Let's teach "git worktree list" to show when a worktree is a prunable
candidate for both default and porcelain format.
In the default format a "prunable" text is appended:
$ git worktree list
/path/to/main aba123 [main]
/path/to/linked 123abc [branch-a]
/path/to/prunable ace127 (detached HEAD) prunable
In the --porcelain format a prunable label is added followed by
its reason:
$ git worktree list --porcelain
...
worktree /path/to/prunable
HEAD abc1234abc1234abc1234abc1234abc1234abc12
detached
prunable gitdir file points to non-existent location
...
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Rafael Silva <redacted>
---
Documentation/git-worktree.txt | 26 ++++++++++++++++++++++++--
builtin/worktree.c | 10 ++++++++++
t/t2402-worktree-list.sh | 32 ++++++++++++++++++++++++++++++++
3 files changed, 66 insertions(+), 2 deletions(-)
@@ -97,8 +97,9 @@ list:: List details of each working tree. The main working tree is listed first, followed by each of the linked working trees. The output details include whether the working tree is bare, the revision currently checked out, the-branch currently checked out (or "detached HEAD" if none), and "locked" if-the worktree is locked.+branch currently checked out (or "detached HEAD" if none), "locked" if+the worktree is locked, "prunable" if the worktree can be pruned by `prune`+command. lock::
@@ -234,6 +235,9 @@ This can also be set up as the default behaviour by using the --expire <time>:: With `prune`, only expire unused working trees older than `<time>`.+++With `list`, annotate missing working trees as prunable if they are+older than `<time>`. --reason <string>:: With `lock`, an explanation why the working tree is locked.
@@ -372,6 +376,19 @@ $ git worktree list /path/to/other-linked-worktree 1234abc (detached HEAD) ------------+The command also shows annotations for each working tree, according to its state.+These annotations are:++ * `locked`, if the working tree is locked.+ * `prunable`, if the working tree can be pruned via `git worktree prune`.++------------+$ git worktree list+/path/to/linked-worktree abcd1234 [master]+/path/to/locked-worktreee acbd5678 (brancha) locked+/path/to/prunable-worktree 5678abc (detached HEAD) prunable+------------+ Porcelain Format ~~~~~~~~~~~~~~~~ The porcelain format has a line per attribute. Attributes are listed with a
@@ -405,6 +422,11 @@ HEAD 3456def3456def3456def3456def3456def3456b branch refs/heads/locked-with-reason locked reason why is locked+worktree /path/to/linked-worktree-prunable+HEAD 1233def1234def1234def1234def1234def1234b+detached+prunable gitdir file points to non-existent location+ ------------ If the lock reason contains "unusual" characters such as newline, they