From a29a8b7574ab34026252691933f462eddc59146a Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Tue, 19 Jan 2021 22:27:33 +0100 Subject: 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. 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 Signed-off-by: Rafael Silva Reviewed-by: Eric Sunshine Signed-off-by: Junio C Hamano --- builtin/worktree.c | 75 +----------------------------------------------------- 1 file changed, 1 insertion(+), 74 deletions(-) (limited to 'builtin') diff --git a/builtin/worktree.c b/builtin/worktree.c index 71287b2da6..dd886d5029 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -67,79 +67,6 @@ static void delete_worktrees_dir_if_empty(void) rmdir(git_path("worktrees")); /* ignore failed removal */ } -/* - * 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. - */ -static int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath) -{ - struct stat st; - char *path; - int fd; - size_t len; - ssize_t read_result; - - *wtpath = NULL; - if (!is_directory(git_path("worktrees/%s", id))) { - strbuf_addstr(reason, _("not a valid directory")); - return 1; - } - if (file_exists(git_path("worktrees/%s/locked", id))) - return 0; - if (stat(git_path("worktrees/%s/gitdir", id), &st)) { - strbuf_addstr(reason, _("gitdir file does not exist")); - return 1; - } - fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY); - if (fd < 0) { - strbuf_addf(reason, _("unable to read gitdir file (%s)"), - strerror(errno)); - return 1; - } - len = xsize_t(st.st_size); - path = xmallocz(len); - - read_result = read_in_full(fd, path, len); - if (read_result < 0) { - strbuf_addf(reason, _("unable to read gitdir file (%s)"), - strerror(errno)); - close(fd); - free(path); - return 1; - } - close(fd); - - if (read_result != len) { - strbuf_addf(reason, - _("short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"), - (uintmax_t)len, (uintmax_t)read_result); - free(path); - return 1; - } - while (len && (path[len - 1] == '\n' || path[len - 1] == '\r')) - len--; - if (!len) { - strbuf_addstr(reason, _("invalid gitdir file")); - free(path); - return 1; - } - path[len] = '\0'; - if (!file_exists(path)) { - if (stat(git_path("worktrees/%s/index", id), &st) || - st.st_mtime <= expire) { - strbuf_addstr(reason, _("gitdir file points to non-existent location")); - free(path); - return 1; - } else { - *wtpath = path; - return 0; - } - } - *wtpath = path; - return 0; -} - static void prune_worktree(const char *id, const char *reason) { if (show_only || verbose) @@ -195,7 +122,7 @@ static void prune_worktrees(void) if (is_dot_or_dotdot(d->d_name)) continue; strbuf_reset(&reason); - if (should_prune_worktree(d->d_name, &reason, &path)) + if (should_prune_worktree(d->d_name, &reason, &path, expire)) prune_worktree(d->d_name, reason.buf); else if (path) string_list_append(&kept, path)->util = xstrdup(d->d_name); -- cgit v1.2.3 From eb36135af7b03fbaab2d3091fa7f5c62a164ff43 Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Tue, 19 Jan 2021 22:27:35 +0100 Subject: worktree: teach worktree_lock_reason() to gently handle main worktree 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 Signed-off-by: Rafael Silva Reviewed-by: Eric Sunshine Signed-off-by: Junio C Hamano --- builtin/worktree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'builtin') diff --git a/builtin/worktree.c b/builtin/worktree.c index dd886d5029..df90a5acca 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -604,7 +604,7 @@ 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); -- cgit v1.2.3 From 862c723d18ddc6be183abc450a9fb220ba4efb0b Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Wed, 27 Jan 2021 09:03:08 +0100 Subject: worktree: teach `list --porcelain` to annotate locked worktree 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: Phillip Wood Helped-by: Eric Sunshine Signed-off-by: Rafael Silva Reviewed-by: Eric Sunshine Signed-off-by: Junio C Hamano --- builtin/worktree.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'builtin') diff --git a/builtin/worktree.c b/builtin/worktree.c index df90a5acca..98177f91d4 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -12,6 +12,7 @@ #include "submodule.h" #include "utf8.h" #include "worktree.h" +#include "quote.h" static const char * const worktree_usage[] = { N_("git worktree add [] []"), @@ -569,6 +570,8 @@ static int add(int ac, const char **av, const char *prefix) static void show_worktree_porcelain(struct worktree *wt) { + const char *reason; + printf("worktree %s\n", wt->path); if (wt->is_bare) printf("bare\n"); @@ -579,6 +582,16 @@ static void show_worktree_porcelain(struct worktree *wt) else if (wt->head_ref) printf("branch %s\n", wt->head_ref); } + + reason = worktree_lock_reason(wt); + if (reason && *reason) { + struct strbuf sb = STRBUF_INIT; + quote_c_style(reason, &sb, NULL, 0); + printf("locked %s\n", sb.buf); + strbuf_release(&sb); + } else if (reason) + printf("locked\n"); + printf("\n"); } -- cgit v1.2.3 From 9b19a58f66946c5022fbd1dac6384ca3c86b08ff Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Wed, 27 Jan 2021 09:03:09 +0100 Subject: worktree: teach `list` to annotate prunable worktree 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 Signed-off-by: Rafael Silva Reviewed-by: Eric Sunshine Signed-off-by: Junio C Hamano --- builtin/worktree.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'builtin') diff --git a/builtin/worktree.c b/builtin/worktree.c index 98177f91d4..20944c266e 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -592,6 +592,10 @@ static void show_worktree_porcelain(struct worktree *wt) } else if (reason) printf("locked\n"); + reason = worktree_prune_reason(wt, expire); + if (reason) + printf("prunable %s\n", reason); + printf("\n"); } @@ -620,6 +624,9 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len) if (worktree_lock_reason(wt)) strbuf_addstr(&sb, " locked"); + if (worktree_prune_reason(wt, expire)) + strbuf_addstr(&sb, " prunable"); + printf("%s\n", sb.buf); strbuf_release(&sb); } @@ -663,9 +670,12 @@ static int list(int ac, const char **av, const char *prefix) struct option options[] = { OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")), + OPT_EXPIRY_DATE(0, "expire", &expire, + N_("add 'prunable' annotation to worktrees older than