* [PATCH 1/2] Make die_if_checked_out() ignore missing worktree checkouts. @ 2019-10-17 16:28 Peter Jones 2019-10-17 16:28 ` [PATCH 2/2] Make "git branch -d" prune missing worktrees automatically Peter Jones 2019-10-17 16:44 ` [PATCH 1/2] Make die_if_checked_out() ignore missing worktree checkouts SZEDER Gábor 0 siblings, 2 replies; 15+ messages in thread From: Peter Jones @ 2019-10-17 16:28 UTC (permalink / raw) To: git; +Cc: Peter Jones Currently if you do, for example: $ git worktree add path foo And "foo" has already been checked out at some other path, but the user has removed it without pruning, you'll get an error that the branch is already checked out. It isn't meaningfully checked out, the repo's data is just stale and no longer reflects reality. This makes it so that if nothing is present where a worktree is supposedly checked out, we ignore that the worktree exists, and let it get cleaned up the next time worktrees are pruned. (I would prune it instead, but prune isn't available from libgit currently.) Signed-off-by: Peter Jones <pjones@redhat.com> --- branch.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/branch.c b/branch.c index 579494738a7..60322ded953 100644 --- a/branch.c +++ b/branch.c @@ -360,6 +360,9 @@ void die_if_checked_out(const char *branch, int ignore_current_worktree) wt = find_shared_symref("HEAD", branch); if (!wt || (ignore_current_worktree && wt->is_current)) return; + if (access(wt->path, F_OK) < 0 && + (errno == ENOENT || errno == ENOTDIR)) + return; skip_prefix(branch, "refs/heads/", &branch); die(_("'%s' is already checked out at '%s'"), branch, wt->path); -- 2.23.0 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 2/2] Make "git branch -d" prune missing worktrees automatically. 2019-10-17 16:28 [PATCH 1/2] Make die_if_checked_out() ignore missing worktree checkouts Peter Jones @ 2019-10-17 16:28 ` Peter Jones 2019-10-17 17:28 ` Eric Sunshine 2019-10-17 16:44 ` [PATCH 1/2] Make die_if_checked_out() ignore missing worktree checkouts SZEDER Gábor 1 sibling, 1 reply; 15+ messages in thread From: Peter Jones @ 2019-10-17 16:28 UTC (permalink / raw) To: git; +Cc: Peter Jones Currently, if you do: $ git branch zonk origin/master $ git worktree add zonk zonk $ rm -rf zonk $ git branch -d zonk You get the following error: $ git branch -d zonk error: Cannot delete branch 'zonk' checked out at '/home/pjones/devel/kernel.org/git/zonk' It isn't meaningfully checked out, the repo's data is just stale and no longer reflects reality. This makes it so that if nothing is present where a worktree is supposedly checked out, deleting the branch will automatically prune it. Signed-off-by: Peter Jones <pjones@redhat.com> --- builtin/branch.c | 2 +- builtin/worktree.c | 14 ++++++++++++++ worktree.h | 6 ++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/builtin/branch.c b/builtin/branch.c index 2ef214632f0..d611f8183b4 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -236,7 +236,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, if (kinds == FILTER_REFS_BRANCHES) { const struct worktree *wt = find_shared_symref("HEAD", name); - if (wt) { + if (wt && prune_worktree_if_missing(wt) < 0) { error(_("Cannot delete branch '%s' " "checked out at '%s'"), bname.buf, wt->path); diff --git a/builtin/worktree.c b/builtin/worktree.c index 4de44f579af..b3ad915c3c3 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -133,6 +133,20 @@ static int prune_worktree(const char *id, struct strbuf *reason) return 0; } +int prune_worktree_if_missing(const struct worktree *wt) +{ + struct strbuf reason = STRBUF_INIT; + + if (access(wt->path, F_OK) >= 0 || + (errno != ENOENT && errno == ENOTDIR)) { + errno = EEXIST; + return -1; + } + + strbuf_addf(&reason, _("Removing worktrees/%s: worktree directory is not present"), wt->id); + return prune_worktree(wt->id, &reason); +} + static void prune_worktrees(void) { struct strbuf reason = STRBUF_INIT; diff --git a/worktree.h b/worktree.h index caecc7a281c..75762c25752 100644 --- a/worktree.h +++ b/worktree.h @@ -132,4 +132,10 @@ void strbuf_worktree_ref(const struct worktree *wt, const char *worktree_ref(const struct worktree *wt, const char *refname); +/* + * Prune a worktree if it is no longer present at the checked out location. + * Returns < 0 if the checkout is there or if pruning fails. + */ +int prune_worktree_if_missing(const struct worktree *wt); + #endif -- 2.23.0 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH 2/2] Make "git branch -d" prune missing worktrees automatically. 2019-10-17 16:28 ` [PATCH 2/2] Make "git branch -d" prune missing worktrees automatically Peter Jones @ 2019-10-17 17:28 ` Eric Sunshine 2019-10-18 19:43 ` Peter Jones 0 siblings, 1 reply; 15+ messages in thread From: Eric Sunshine @ 2019-10-17 17:28 UTC (permalink / raw) To: Peter Jones; +Cc: Git List On Thu, Oct 17, 2019 at 12:28 PM Peter Jones <pjones@redhat.com> wrote: > Currently, if you do: > > $ git branch zonk origin/master > $ git worktree add zonk zonk > $ rm -rf zonk > $ git branch -d zonk > > You get the following error: > > $ git branch -d zonk > error: Cannot delete branch 'zonk' checked out at '/home/pjones/devel/kernel.org/git/zonk' > > It isn't meaningfully checked out, the repo's data is just stale and no > longer reflects reality. Echoing SEZDER's comment on patch 1/2, this behavior is an intentional design choice and safety feature of the worktree implementation since worktrees may exist on removable media or remote filesystems which might not always be mounted; hence, the presence of commands "git worktree prune" and "git worktree remove". A couple comment regarding this patch... > Signed-off-by: Peter Jones <pjones@redhat.com> > --- > diff --git a/builtin/worktree.c b/builtin/worktree.c > @@ -133,6 +133,20 @@ static int prune_worktree(const char *id, struct strbuf *reason) > +int prune_worktree_if_missing(const struct worktree *wt) > +{ > + struct strbuf reason = STRBUF_INIT; > + > + if (access(wt->path, F_OK) >= 0 || > + (errno != ENOENT && errno == ENOTDIR)) { > + errno = EEXIST; > + return -1; > + } > + > + strbuf_addf(&reason, _("Removing worktrees/%s: worktree directory is not present"), wt->id); > + return prune_worktree(wt->id, &reason); > +} "git worktree" tries to clean up after itself as much as possible. For instance, it is careful to remove the .git/worktrees directory when the last worktree itself is removed (or pruned). So, the caller of this function would also want to call delete_worktrees_dir_if_empty() to follow suit. > diff --git a/worktree.h b/worktree.h > @@ -132,4 +132,10 @@ void strbuf_worktree_ref(const struct worktree *wt, > +/* > + * Prune a worktree if it is no longer present at the checked out location. > + * Returns < 0 if the checkout is there or if pruning fails. > + */ > +int prune_worktree_if_missing(const struct worktree *wt); It's rather ugly that this function is declared in top-level worktree.h whereas the actual implementation is in builtin/worktree.c. I'd expect to see a preparatory patch which moves prune_worktree() (and probably delete_worktrees_dir_if_empty()) to top-level worktree.c. These minor implementation comments aside, before considering this patch series, it would be nice to see a compelling argument as to why this change of behavior, which undercuts a deliberate design decision, is really desirable. ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/2] Make "git branch -d" prune missing worktrees automatically. 2019-10-17 17:28 ` Eric Sunshine @ 2019-10-18 19:43 ` Peter Jones 2019-10-18 19:45 ` [PATCH v2 1/4] libgit: Add a read-only helper to test the worktree lock Peter Jones 2019-11-08 10:14 ` [PATCH 2/2] Make "git branch -d" prune missing worktrees automatically Eric Sunshine 0 siblings, 2 replies; 15+ messages in thread From: Peter Jones @ 2019-10-18 19:43 UTC (permalink / raw) To: Eric Sunshine; +Cc: Git List, SZEDER Gábor On Thu, Oct 17, 2019 at 06:44:26PM +0200, SZEDER Gábor wrote: > > if (!wt || (ignore_current_worktree && wt->is_current)) > > return; > > + if (access(wt->path, F_OK) < 0 && > > + (errno == ENOENT || errno == ENOTDIR)) > > + return; > > I think this check is insuffient: even if the directory of the working > tree is not present, the working tree might still exist, and should > not be ignored (or deleted/pruned in the second patch). > > See the description of 'git worktree lock' for details. Ah, thanks for that, I had not realized "lock" was relevant here as I have never used it. That explains some of what seemed to me like a very strange usage model. On Thu, Oct 17, 2019 at 01:28:09PM -0400, Eric Sunshine wrote: > Echoing SEZDER's comment on patch 1/2, this behavior is an intentional > design choice and safety feature of the worktree implementation since > worktrees may exist on removable media or remote filesystems which > might not always be mounted; hence, the presence of commands "git > worktree prune" and "git worktree remove". Okay, I see that use case now - I hadn't realized there was an intentional design decision here, and honestly that's anything but clear from the *code*. It's surprising, for example, that my patches didn't break a single test case. > A couple comment regarding this patch... Sure... > > diff --git a/builtin/worktree.c b/builtin/worktree.c > > @@ -133,6 +133,20 @@ static int prune_worktree(const char *id, struct strbuf *reason) > > +int prune_worktree_if_missing(const struct worktree *wt) > > +{ > > + struct strbuf reason = STRBUF_INIT; > > + > > + if (access(wt->path, F_OK) >= 0 || > > + (errno != ENOENT && errno == ENOTDIR)) { > > + errno = EEXIST; > > + return -1; > > + } > > + > > + strbuf_addf(&reason, _("Removing worktrees/%s: worktree directory is not present"), wt->id); > > + return prune_worktree(wt->id, &reason); > > +} > > "git worktree" tries to clean up after itself as much as possible. For > instance, it is careful to remove the .git/worktrees directory when > the last worktree itself is removed (or pruned). So, the caller of > this function would also want to call delete_worktrees_dir_if_empty() > to follow suit. Okay, will fix. > > diff --git a/worktree.h b/worktree.h > > @@ -132,4 +132,10 @@ void strbuf_worktree_ref(const struct worktree *wt, > > +/* > > + * Prune a worktree if it is no longer present at the checked out location. > > + * Returns < 0 if the checkout is there or if pruning fails. > > + */ > > +int prune_worktree_if_missing(const struct worktree *wt); > > It's rather ugly that this function is declared in top-level > worktree.h whereas the actual implementation is in builtin/worktree.c. I don't disagree, but I didn't want to move stuff into an exposed API if I didn't have to, and that seemed like an appropriate enough header. I can do it the other way though, no problem. > I'd expect to see a preparatory patch which moves prune_worktree() > (and probably delete_worktrees_dir_if_empty()) to top-level > worktree.c. Sure thing. > These minor implementation comments aside, before considering this > patch series, it would be nice to see a compelling argument as to why > this change of behavior, which undercuts a deliberate design decision, > is really desirable. Okay, so just for clarity, when you say there's a deliberate design decision, which behavior here are you talking about? If you mean making "lock" work, I don't have any issue with that. If you mean not cleaning up when we do other commands, then I don't see why that's a concern - after all, that's exactly what "lock" is for. Assuming it is the "lock" behavior we're talking about, I don't think I actually have any intention of breaking this design decision, just making my workflow (without "lock") nag at me less for what seem like pretty trivial issues. I can easily accommodate "git worktree lock". What bugs me though, is that using worktrees basically means I have to replace fairly regular filesystem activities with worktree commands, and it doesn't seem to be *necessary* in any way. And I'm going to forget. A lot. To me, there doesn't seem to be any reason these need to behave any different: $ git worktree add foo foo $ rm -rf foo vs $ git worktree add foo foo $ git worktree remove foo And in fact the only difference right now, aside from some very minuscule storage requirements that haven't gotten cleaned up, is the first one leaves an artifact that tells it to give me errors later until I run "git worktree prune" myself. I'll send another revision of this patchset as a reply to this mail, which should clear up some of our differences. -- Peter ^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 1/4] libgit: Add a read-only helper to test the worktree lock 2019-10-18 19:43 ` Peter Jones @ 2019-10-18 19:45 ` Peter Jones 2019-10-18 19:45 ` [PATCH v2 2/4] libgit: Expose more worktree functionality Peter Jones ` (3 more replies) 2019-11-08 10:14 ` [PATCH 2/2] Make "git branch -d" prune missing worktrees automatically Eric Sunshine 1 sibling, 4 replies; 15+ messages in thread From: Peter Jones @ 2019-10-18 19:45 UTC (permalink / raw) To: git; +Cc: Eric Sunshine, SZEDER Gábor, Peter Jones Add the function is_worktree_locked(), which is a helper to tell if a worktree is locked without having to be able to modify it. Signed-off-by: Peter Jones <pjones@redhat.com> --- builtin/worktree.c | 2 +- worktree.c | 16 ++++++++++++++++ worktree.h | 5 +++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/builtin/worktree.c b/builtin/worktree.c index 4de44f579af..86305cc1fe1 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -245,7 +245,7 @@ static void validate_worktree_add(const char *path, const struct add_opts *opts) if (!wt) goto done; - locked = !!worktree_lock_reason(wt); + locked = is_worktree_locked(wt); if ((!locked && opts->force) || (locked && opts->force > 1)) { if (delete_git_dir(wt->id)) die(_("unable to re-add worktree '%s'"), path); diff --git a/worktree.c b/worktree.c index 5b4793caa34..4924805c389 100644 --- a/worktree.c +++ b/worktree.c @@ -244,6 +244,22 @@ int is_main_worktree(const struct worktree *wt) return !wt->id; } +int is_worktree_locked(const struct worktree *wt) +{ + struct strbuf path = STRBUF_INIT; + int locked = 0; + + if (wt->lock_reason_valid && wt->lock_reason) + return 1; + + strbuf_addstr(&path, worktree_git_path(wt, "locked")); + if (file_exists(path.buf)) + locked = 1; + + strbuf_release(&path); + return locked; +} + const char *worktree_lock_reason(struct worktree *wt) { assert(!is_main_worktree(wt)); diff --git a/worktree.h b/worktree.h index caecc7a281c..5ff16c414b5 100644 --- a/worktree.h +++ b/worktree.h @@ -56,6 +56,11 @@ struct worktree *find_worktree(struct worktree **list, */ int is_main_worktree(const struct worktree *wt); +/* + * Return true if the given worktree is locked + */ +int is_worktree_locked(const struct worktree *wt); + /* * Return the reason string if the given worktree is locked or NULL * otherwise. -- 2.23.0 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v2 2/4] libgit: Expose more worktree functionality. 2019-10-18 19:45 ` [PATCH v2 1/4] libgit: Add a read-only helper to test the worktree lock Peter Jones @ 2019-10-18 19:45 ` Peter Jones 2019-10-21 1:59 ` Junio C Hamano 2019-10-18 19:45 ` [PATCH v2 3/4] Make die_if_checked_out() prune missing checkouts of unlocked worktrees Peter Jones ` (2 subsequent siblings) 3 siblings, 1 reply; 15+ messages in thread From: Peter Jones @ 2019-10-18 19:45 UTC (permalink / raw) To: git; +Cc: Eric Sunshine, SZEDER Gábor, Peter Jones Add delete_worktrees_dir_if_empty() and prune_worktree() to the public API, so they can be used from more places. Also add a new function, prune_worktree_if_missing(), which prunes unlocked worktrees if they aren't present on the filesystem. Signed-off-by: Peter Jones <pjones@redhat.com> --- builtin/worktree.c | 73 +------------------------------------- worktree.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++ worktree.h | 19 ++++++++++ 3 files changed, 108 insertions(+), 72 deletions(-) diff --git a/builtin/worktree.c b/builtin/worktree.c index 86305cc1fe1..8ff37309be9 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -62,77 +62,6 @@ static int delete_git_dir(const char *id) return ret; } -static void delete_worktrees_dir_if_empty(void) -{ - rmdir(git_path("worktrees")); /* ignore failed removal */ -} - -static int prune_worktree(const char *id, struct strbuf *reason) -{ - struct stat st; - char *path; - int fd; - size_t len; - ssize_t read_result; - - if (!is_directory(git_path("worktrees/%s", id))) { - strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id); - return 1; - } - if (file_exists(git_path("worktrees/%s/locked", id))) - return 0; - if (stat(git_path("worktrees/%s/gitdir", id), &st)) { - strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id); - return 1; - } - fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY); - if (fd < 0) { - strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"), - id, 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, _("Removing worktrees/%s: unable to read gitdir file (%s)"), - id, strerror(errno)); - close(fd); - free(path); - return 1; - } - close(fd); - - if (read_result != len) { - strbuf_addf(reason, - _("Removing worktrees/%s: short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"), - id, (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_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id); - free(path); - return 1; - } - path[len] = '\0'; - if (!file_exists(path)) { - free(path); - if (stat(git_path("worktrees/%s/index", id), &st) || - st.st_mtime <= expire) { - strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id); - return 1; - } else { - return 0; - } - } - free(path); - return 0; -} - static void prune_worktrees(void) { struct strbuf reason = STRBUF_INIT; @@ -144,7 +73,7 @@ static void prune_worktrees(void) if (is_dot_or_dotdot(d->d_name)) continue; strbuf_reset(&reason); - if (!prune_worktree(d->d_name, &reason)) + if (!prune_worktree(d->d_name, &reason, expire)) continue; if (show_only || verbose) printf("%s\n", reason.buf); diff --git a/worktree.c b/worktree.c index 4924805c389..08454a4e65d 100644 --- a/worktree.c +++ b/worktree.c @@ -608,3 +608,91 @@ int other_head_refs(each_ref_fn fn, void *cb_data) free_worktrees(worktrees); return ret; } + +void delete_worktrees_dir_if_empty(void) +{ + rmdir(git_path("worktrees")); /* ignore failed removal */ +} + +int prune_worktree(const char *id, struct strbuf *reason, timestamp_t expire) +{ + struct stat st; + char *path; + int fd; + size_t len; + ssize_t read_result; + + if (!is_directory(git_path("worktrees/%s", id))) { + strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id); + return 1; + } + if (file_exists(git_path("worktrees/%s/locked", id))) + return 0; + if (stat(git_path("worktrees/%s/gitdir", id), &st)) { + strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id); + return 1; + } + fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY); + if (fd < 0) { + strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"), + id, 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, _("Removing worktrees/%s: unable to read gitdir file (%s)"), + id, strerror(errno)); + close(fd); + free(path); + return 1; + } + close(fd); + + if (read_result != len) { + strbuf_addf(reason, + _("Removing worktrees/%s: short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"), + id, (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_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id); + free(path); + return 1; + } + path[len] = '\0'; + if (!file_exists(path)) { + free(path); + if (stat(git_path("worktrees/%s/index", id), &st) || + st.st_mtime <= expire) { + strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id); + return 1; + } else { + return 0; + } + } + free(path); + return 0; +} + +int prune_worktree_if_missing(const struct worktree *wt) +{ + struct strbuf reason = STRBUF_INIT; + int ret; + + if (is_worktree_locked(wt) || + access(wt->path, F_OK) >= 0 || + (errno != ENOENT && errno == ENOTDIR)) { + errno = EEXIST; + return -1; + } + + strbuf_addf(&reason, _("Removing worktrees/%s: worktree directory is not present"), wt->id); + ret = prune_worktree(wt->id, &reason, TIME_MAX); + return ret; +} diff --git a/worktree.h b/worktree.h index 5ff16c414b5..636bbb1c449 100644 --- a/worktree.h +++ b/worktree.h @@ -137,4 +137,23 @@ void strbuf_worktree_ref(const struct worktree *wt, const char *worktree_ref(const struct worktree *wt, const char *refname); +/* + * Clean up the 'worktrees' directory, if necessary. + */ +void delete_worktrees_dir_if_empty(void); + +/* + * Prune a worktree if it's older than expire. + * Returns 0 on success, < 0 on failure. + */ +int prune_worktree(const char *id, struct strbuf *reason, timestamp_t expire); + +/* + * Prune a worktree if it is not locked and is no longer present at the + * checked out location. + * Returns < 0 if the checkout is there, if the worktree is locked, or if + * pruning fails. + */ +int prune_worktree_if_missing(const struct worktree *wt); + #endif -- 2.23.0 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH v2 2/4] libgit: Expose more worktree functionality. 2019-10-18 19:45 ` [PATCH v2 2/4] libgit: Expose more worktree functionality Peter Jones @ 2019-10-21 1:59 ` Junio C Hamano 0 siblings, 0 replies; 15+ messages in thread From: Junio C Hamano @ 2019-10-21 1:59 UTC (permalink / raw) To: Peter Jones; +Cc: git, Eric Sunshine, SZEDER Gábor Peter Jones <pjones@redhat.com> writes: Same comment on the commit title as 1/4; also, we tend not to upcase the first word after the <area>: word and omit the full-stop on the title (see "git shortlog -32 --no-merges" on our project for examples). > Add delete_worktrees_dir_if_empty() and prune_worktree() to the public > API, so they can be used from more places. Also add a new function, > prune_worktree_if_missing(), which prunes unlocked worktrees if they > aren't present on the filesystem. It probably is cleaner to do the "also" part as a separate step, as that allows readers to skip this step without reading it deeply, but let's see how it is done. > @@ -144,7 +73,7 @@ static void prune_worktrees(void) > if (is_dot_or_dotdot(d->d_name)) > continue; > strbuf_reset(&reason); > - if (!prune_worktree(d->d_name, &reason)) > + if (!prune_worktree(d->d_name, &reason, expire)) > continue; > if (show_only || verbose) > printf("%s\n", reason.buf); > diff --git a/worktree.c b/worktree.c > index 4924805c389..08454a4e65d 100644 > --- a/worktree.c > +++ b/worktree.c > @@ -608,3 +608,91 @@ int other_head_refs(each_ref_fn fn, void *cb_data) > +int prune_worktree(const char *id, struct strbuf *reason, timestamp_t expire) This is not a mere code movement, because the original relied on the file-scope static "expire", and the public version wants to give callers control over the expiration value. That is a good change that deserves to be advertised and explained in the proposed log message. > +int prune_worktree_if_missing(const struct worktree *wt) > +{ > + struct strbuf reason = STRBUF_INIT; > + int ret; > + > + if (is_worktree_locked(wt) || > + access(wt->path, F_OK) >= 0 || > + (errno != ENOENT && errno == ENOTDIR)) { > + errno = EEXIST; > + return -1; > + } When access() failed but not because the named path did not exist (i.e. the directory may still exist---it is just this invocation of the process happened to fail to see it---or it may not exist but we cannot see far enough to notice that it does not exist) then we play safe, assume it does exist, and refrain from calling prune_worktree() on it. Which makes sense, but do we need to set errno to EEXIST here? Does prune_worktree() ensure the value left in errno when it returns failure in a similar way to allow the caller of this new helper make effective and reliable use of errno? > + strbuf_addf(&reason, _("Removing worktrees/%s: worktree directory is not present"), wt->id); > + ret = prune_worktree(wt->id, &reason, TIME_MAX); > + return ret; > +} ^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 3/4] Make die_if_checked_out() prune missing checkouts of unlocked worktrees. 2019-10-18 19:45 ` [PATCH v2 1/4] libgit: Add a read-only helper to test the worktree lock Peter Jones 2019-10-18 19:45 ` [PATCH v2 2/4] libgit: Expose more worktree functionality Peter Jones @ 2019-10-18 19:45 ` Peter Jones 2019-10-21 2:09 ` Junio C Hamano 2019-10-18 19:45 ` [PATCH v2 4/4] Make "git branch -d" prune missing worktrees automatically Peter Jones 2019-10-21 1:36 ` [PATCH v2 1/4] libgit: Add a read-only helper to test the worktree lock Junio C Hamano 3 siblings, 1 reply; 15+ messages in thread From: Peter Jones @ 2019-10-18 19:45 UTC (permalink / raw) To: git; +Cc: Eric Sunshine, SZEDER Gábor, Peter Jones Currently if you do, for example: $ git worktree add path foo And "foo" has already been checked out at some other path, but the user has removed it without pruning, and the worktree is not locked, you'll get an error that the branch is already checked out. It isn't meaningfully checked out, the repo's data is just stale and no longer reflects reality. This makes it so that if nothing is present where a worktree is supposedly checked out, and it is not locked, we ignore that the worktree exists, then it should be cleaned up. Signed-off-by: Peter Jones <pjones@redhat.com> --- branch.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/branch.c b/branch.c index 579494738a7..760ef387144 100644 --- a/branch.c +++ b/branch.c @@ -360,6 +360,12 @@ void die_if_checked_out(const char *branch, int ignore_current_worktree) wt = find_shared_symref("HEAD", branch); if (!wt || (ignore_current_worktree && wt->is_current)) return; + + if (prune_worktree_if_missing(wt) >= 0) { + delete_worktrees_dir_if_empty(); + return; + } + skip_prefix(branch, "refs/heads/", &branch); die(_("'%s' is already checked out at '%s'"), branch, wt->path); -- 2.23.0 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH v2 3/4] Make die_if_checked_out() prune missing checkouts of unlocked worktrees. 2019-10-18 19:45 ` [PATCH v2 3/4] Make die_if_checked_out() prune missing checkouts of unlocked worktrees Peter Jones @ 2019-10-21 2:09 ` Junio C Hamano 0 siblings, 0 replies; 15+ messages in thread From: Junio C Hamano @ 2019-10-21 2:09 UTC (permalink / raw) To: Peter Jones; +Cc: git, Eric Sunshine, SZEDER Gábor Peter Jones <pjones@redhat.com> writes: [jc: won't repeat comments on the title] > @@ -360,6 +360,12 @@ void die_if_checked_out(const char *branch, int ignore_current_worktree) > wt = find_shared_symref("HEAD", branch); > if (!wt || (ignore_current_worktree && wt->is_current)) > return; die-if-checked-out is called from callers that expect to be stopped before they do any harm, so it feels dirty to make a side effect like this. If the user tries to check out a branch that used to be checked out in an already removed worktree, doesn't that indicate that an earlier worktree removal was done incorrectly, which is something worth reporting to the user and give the user a chance to think and choose what corrective action(s) need to be taken? For that, instead of automatically losing information like this patch does, it may make more sense to fail the checkout and stop at giving diagnosis (e.g. "our record shows that the branch is checked out in that worktree, but you seem to have lost it. if you forgot to prune it, then here is the command you can give to do so.") without actually touching the filesystem. Thanks. > + > + if (prune_worktree_if_missing(wt) >= 0) { > + delete_worktrees_dir_if_empty(); > + return; > + } > + > skip_prefix(branch, "refs/heads/", &branch); > die(_("'%s' is already checked out at '%s'"), > branch, wt->path); ^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 4/4] Make "git branch -d" prune missing worktrees automatically. 2019-10-18 19:45 ` [PATCH v2 1/4] libgit: Add a read-only helper to test the worktree lock Peter Jones 2019-10-18 19:45 ` [PATCH v2 2/4] libgit: Expose more worktree functionality Peter Jones 2019-10-18 19:45 ` [PATCH v2 3/4] Make die_if_checked_out() prune missing checkouts of unlocked worktrees Peter Jones @ 2019-10-18 19:45 ` Peter Jones 2019-10-21 1:36 ` [PATCH v2 1/4] libgit: Add a read-only helper to test the worktree lock Junio C Hamano 3 siblings, 0 replies; 15+ messages in thread From: Peter Jones @ 2019-10-18 19:45 UTC (permalink / raw) To: git; +Cc: Eric Sunshine, SZEDER Gábor, Peter Jones Currently, if you do: $ git branch zonk origin/master $ git worktree add zonk zonk $ rm -rf zonk $ git branch -d zonk You get the following error: $ git branch -d zonk error: Cannot delete branch 'zonk' checked out at '/home/pjones/devel/kernel.org/git/zonk' It isn't meaningfully checked out, the repo's data is just stale and no longer reflects reality. This makes it so that if nothing is present where a worktree is supposedly checked out, deleting the branch will automatically prune it. Signed-off-by: Peter Jones <pjones@redhat.com> --- builtin/branch.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/builtin/branch.c b/builtin/branch.c index 2ef214632f0..a2a1e89c66b 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -236,13 +236,17 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, if (kinds == FILTER_REFS_BRANCHES) { const struct worktree *wt = find_shared_symref("HEAD", name); - if (wt) { + int rc = -1; + + if (wt && (rc = prune_worktree_if_missing(wt)) < 0) { error(_("Cannot delete branch '%s' " "checked out at '%s'"), bname.buf, wt->path); ret = 1; continue; } + if (rc >= 0) + delete_worktrees_dir_if_empty(); } target = resolve_refdup(name, -- 2.23.0 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH v2 1/4] libgit: Add a read-only helper to test the worktree lock 2019-10-18 19:45 ` [PATCH v2 1/4] libgit: Add a read-only helper to test the worktree lock Peter Jones ` (2 preceding siblings ...) 2019-10-18 19:45 ` [PATCH v2 4/4] Make "git branch -d" prune missing worktrees automatically Peter Jones @ 2019-10-21 1:36 ` Junio C Hamano 3 siblings, 0 replies; 15+ messages in thread From: Junio C Hamano @ 2019-10-21 1:36 UTC (permalink / raw) To: Peter Jones; +Cc: git, Eric Sunshine, SZEDER Gábor Peter Jones <pjones@redhat.com> writes: > Subject: Re: [PATCH v2 1/4] libgit: Add a read-only helper to test the worktree lock Having a word "worktree" somewhere on the title is good, but have it as the "I am changing this area"; "libgit" does not give readers the hint that this is a step about the worktree subsystem. Subject: [PATCH v2 1/4] worktree: add is_worktree_locked() helper When the new helper function is properly named, like yours, there is not much need to explain what it does (i.e. "to test the worktree lock"), so just "worktree: add is_worktree_locked()" is sufficient. > Add the function is_worktree_locked(), which is a helper to tell if a > worktree is locked without having to be able to modify it. I do not see the reason why your proposed title and log message stress the fact that this helper can be used even by callers that are not permitted to modify the worktree (i.e. the emphasis on "read-only"). Asking for worktree_lock_reason() can be done by anybody, but I do not think we particularly advertise it as read-only. Perhaps drop "without having to..."? > - locked = !!worktree_lock_reason(wt); > + locked = is_worktree_locked(wt); > if ((!locked && opts->force) || (locked && opts->force > 1)) { > if (delete_git_dir(wt->id)) > die(_("unable to re-add worktree '%s'"), path); > diff --git a/worktree.c b/worktree.c > index 5b4793caa34..4924805c389 100644 > --- a/worktree.c > +++ b/worktree.c > @@ -244,6 +244,22 @@ int is_main_worktree(const struct worktree *wt) > return !wt->id; > } > > +int is_worktree_locked(const struct worktree *wt) > +{ > + struct strbuf path = STRBUF_INIT; > + int locked = 0; > + > + if (wt->lock_reason_valid && wt->lock_reason) > + return 1; > + > + strbuf_addstr(&path, worktree_git_path(wt, "locked")); > + if (file_exists(path.buf)) > + locked = 1; If you write locked = file_exists(path.buf); here, then readers do not have to scan backwards and find that the variable is initialized to zero, and that no other statement since its initialization touches its value, in order to see what value is returned when file does not exist. Writing the RHS !!file_exists() concisely allows readers to tell that this function returns only 0 or 1 without having to check what file_exists() returns, but that may probably be overkill. > + strbuf_release(&path); > + return locked; > +} I wondered why this is not just #define is_worktree_locked(wt) (!!worktree_lock_reason(wt)) There are a few differences compared to worktree_lock_reason(): - this can be called on the main worktree by mistake and would probably yield "not locked" (but the existing guard is a mere assert() which probably is stripped away in production builds) - this can be used by a process that cannot even read the contents of the locked file for the reason; - because reason is not read, reason or reason_valid fields are not updated, and repeated calls on the same worktree structure would result in repeated lstat() calls. Shouldn't we be advising the callers that the last one as a potential downside? The fact that the new helper is usable even by read-only callers hints that any caching of earlier results is disabled, but it is somewhat a round-about way to say so. As I do not see why being able to take "const struct worktree *", as opposed to non-const version is a huge advantage, for this helper, I wonder if it would make even more sense to introduce one more level to "lock-reason-valid" and allow caching of is_worktree_locked(). Currently, "lock-reason-valid" only tells us "lock-reason may be NULL, but that does not necessarily mean it is not locked---you have to check it" boolean, but it could be instead a tristate: A: lock-reason may be NULL but that is only because we haven't even tried to see if the lock file exists B: NULL-ness of lock-reason reliably tells if the worktree is locked or not because we have tried file_exists(), but if the field has non-NULL value, that is *not* the string we read; if you want to know the reason, you must read the file. C: NULL in lock-reason means it is not locked; non-NULL in lock-reason is what we read form the file. Also, it may make sense to correct the first difference and in a more meaningful way than assert(), given that the reason why this helper is introduced is eventually to perform an destructive action later in the series. Perhaps if (is_main_worktree(wt)) BUG("is-worktree-locked called for the main worktree"); at the front. Thanks. > const char *worktree_lock_reason(struct worktree *wt) > { > assert(!is_main_worktree(wt)); > diff --git a/worktree.h b/worktree.h > index caecc7a281c..5ff16c414b5 100644 > --- a/worktree.h > +++ b/worktree.h > @@ -56,6 +56,11 @@ struct worktree *find_worktree(struct worktree **list, > */ > int is_main_worktree(const struct worktree *wt); > > +/* > + * Return true if the given worktree is locked > + */ > +int is_worktree_locked(const struct worktree *wt); > + > /* > * Return the reason string if the given worktree is locked or NULL > * otherwise. ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/2] Make "git branch -d" prune missing worktrees automatically. 2019-10-18 19:43 ` Peter Jones 2019-10-18 19:45 ` [PATCH v2 1/4] libgit: Add a read-only helper to test the worktree lock Peter Jones @ 2019-11-08 10:14 ` Eric Sunshine 2019-11-08 14:56 ` Phillip Wood 1 sibling, 1 reply; 15+ messages in thread From: Eric Sunshine @ 2019-11-08 10:14 UTC (permalink / raw) To: Peter Jones Cc: Git List, SZEDER Gábor, Nguyễn Thái Ngọc Duy [cc:+duy] On Fri, Oct 18, 2019 at 3:43 PM Peter Jones <pjones@redhat.com> wrote: > On Thu, Oct 17, 2019 at 01:28:09PM -0400, Eric Sunshine wrote: > > Echoing SEZDER's comment on patch 1/2, this behavior is an intentional > > design choice and safety feature of the worktree implementation since > > worktrees may exist on removable media or remote filesystems which > > might not always be mounted; hence, the presence of commands "git > > worktree prune" and "git worktree remove". > > Okay, I see that use case now - I hadn't realized there was an > intentional design decision here, and honestly that's anything but clear > from the *code*. It can indeed sometimes be difficult to get a high-level functional overview by examining code in isolation. In this case, at least, git-worktree documentation tries to be clear about the "why" and "how" of the pruning behavior (which is not to say that the documentation -- or the code -- can't be improved to communicate this better). > It's surprising, for example, that my patches didn't break a single > test case. Tests suites are never perfect, and an attempt to prune a dangling worktree by deleting a branch likely never occurred to the git-worktree implementer(s). > > These minor implementation comments aside, before considering this > > patch series, it would be nice to see a compelling argument as to why > > this change of behavior, which undercuts a deliberate design decision, > > is really desirable. > > Okay, so just for clarity, when you say there's a deliberate design > decision, which behavior here are you talking about? If you mean making > "lock" work, I don't have any issue with that. If you mean not cleaning > up when we do other commands, then I don't see why that's a concern - > after all, that's exactly what "lock" is for. To clarify, I'm talking about Duy's deliberate design decision to model git-worktree auto-pruning after Git's own garbage-collection behavior. That model includes, not only explicit locking, but a grace period before dangling worktree administrative files can be pruned automatically (see the gc.worktreePruneExpire configuration). The point of git-worktree's grace period (just like git-gc's grace period) is to avoid deleting potentially precious information permanently. For instance, the worktree-local "index" file might have some changes staged but not yet committed. Under the existing model, those staged changes are immune from being accidentally deleted permanently until after the grace period expires or until they are thrown away deliberately (say, via "git worktree prune --expire=now"). > Assuming it is the "lock" behavior we're talking about, I don't think I > actually have any intention of breaking this design decision, just > making my workflow (without "lock") nag at me less for what seem like > pretty trivial issues. The ability to lock a worktree is an extra safety measure built atop the grace period mechanism to provide a way to completely override auto-pruning; it is not meant as an alternate or replacement safety mechanism to the grace period, but instead augments it. So, a behavior change which respects only one of those safety mechanisms but not the other is likely flawed. And, importantly, people may already be relying upon this behavior of having an automatic grace period -- without having to place a worktree lock manually -- so changing behavior arbitrarily could break existing workflows and result in data loss. > I can easily accommodate "git worktree lock". What bugs me though, is > that using worktrees basically means I have to replace fairly regular > filesystem activities with worktree commands, and it doesn't seem to be > *necessary* in any way. And I'm going to forget. A lot. > > To me, there doesn't seem to be any reason these need to behave any different: > > $ git worktree add foo foo > $ rm -rf foo > vs > $ git worktree add foo foo > $ git worktree remove foo > > And in fact the only difference right now, aside from some very > minuscule storage requirements that haven't gotten cleaned up, is the > first one leaves an artifact that tells it to give me errors later until > I run "git worktree prune" myself. I understand the pain point, but I also understand Duy's motivation for being very careful about pruning worktree administrative files automatically (so as to avoid data loss, such as changes already staged to a worktree-local "index" file). While the proposed change may address the pain point, it nevertheless creates the possibility of accidental loss which Duy was careful to avoid when designing worktree mechanics. Although annoying, the current behavior gives you the opportunity to avoid that accidental loss by forcing you to take deliberate action to remove the worktree administrative files. Perhaps there is some way to address the pain point without breaking the fundamental promise made by git-worktree about being careful with worktree metadata[*], but the changes proposed by this patch series seem insufficient (even if the patch is reworked to respect worktree locking). I've cc:'d Duy in case he wants to chime in. [*] For instance, perhaps before auto-pruning, it could check whether the index is recording staged changes or conflict information, and only allow auto-pruning if the index is clean. *But* there may be other ways for information to be lost permanently (beyond a dirty "index") which don't occur to me at present, so this has to be considered carefully. ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/2] Make "git branch -d" prune missing worktrees automatically. 2019-11-08 10:14 ` [PATCH 2/2] Make "git branch -d" prune missing worktrees automatically Eric Sunshine @ 2019-11-08 14:56 ` Phillip Wood 2019-11-09 11:34 ` Eric Sunshine 0 siblings, 1 reply; 15+ messages in thread From: Phillip Wood @ 2019-11-08 14:56 UTC (permalink / raw) To: Eric Sunshine, Peter Jones Cc: Git List, SZEDER Gábor, Nguyễn Thái Ngọc Duy On 08/11/2019 10:14, Eric Sunshine wrote: > [cc:+duy] > > On Fri, Oct 18, 2019 at 3:43 PM Peter Jones <pjones@redhat.com> wrote: >> On Thu, Oct 17, 2019 at 01:28:09PM -0400, Eric Sunshine wrote: >>> Echoing SEZDER's comment on patch 1/2, this behavior is an intentional >>> design choice and safety feature of the worktree implementation since >>> worktrees may exist on removable media or remote filesystems which >>> might not always be mounted; hence, the presence of commands "git >>> worktree prune" and "git worktree remove". >> >> Okay, I see that use case now - I hadn't realized there was an >> intentional design decision here, and honestly that's anything but clear >> from the *code*. > > It can indeed sometimes be difficult to get a high-level functional > overview by examining code in isolation. In this case, at least, > git-worktree documentation tries to be clear about the "why" and "how" > of the pruning behavior (which is not to say that the documentation -- > or the code -- can't be improved to communicate this better). > >> It's surprising, for example, that my patches didn't break a single >> test case. > > Tests suites are never perfect, and an attempt to prune a dangling > worktree by deleting a branch likely never occurred to the > git-worktree implementer(s). > >>> These minor implementation comments aside, before considering this >>> patch series, it would be nice to see a compelling argument as to why >>> this change of behavior, which undercuts a deliberate design decision, >>> is really desirable. >> >> Okay, so just for clarity, when you say there's a deliberate design >> decision, which behavior here are you talking about? If you mean making >> "lock" work, I don't have any issue with that. If you mean not cleaning >> up when we do other commands, then I don't see why that's a concern - >> after all, that's exactly what "lock" is for. > > To clarify, I'm talking about Duy's deliberate design decision to > model git-worktree auto-pruning after Git's own garbage-collection > behavior. That model includes, not only explicit locking, but a grace > period before dangling worktree administrative files can be pruned > automatically (see the gc.worktreePruneExpire configuration). > > The point of git-worktree's grace period (just like git-gc's grace > period) is to avoid deleting potentially precious information > permanently. For instance, the worktree-local "index" file might have > some changes staged but not yet committed. Under the existing model, > those staged changes are immune from being accidentally deleted > permanently until after the grace period expires or until they are > thrown away deliberately (say, via "git worktree prune --expire=now"). > >> Assuming it is the "lock" behavior we're talking about, I don't think I >> actually have any intention of breaking this design decision, just >> making my workflow (without "lock") nag at me less for what seem like >> pretty trivial issues. > > The ability to lock a worktree is an extra safety measure built atop > the grace period mechanism to provide a way to completely override > auto-pruning; it is not meant as an alternate or replacement safety > mechanism to the grace period, but instead augments it. So, a behavior > change which respects only one of those safety mechanisms but not the > other is likely flawed. > > And, importantly, people may already be relying upon this behavior of > having an automatic grace period -- without having to place a worktree > lock manually -- so changing behavior arbitrarily could break existing > workflows and result in data loss. > >> I can easily accommodate "git worktree lock". What bugs me though, is >> that using worktrees basically means I have to replace fairly regular >> filesystem activities with worktree commands, and it doesn't seem to be >> *necessary* in any way. And I'm going to forget. A lot. >> >> To me, there doesn't seem to be any reason these need to behave any different: >> >> $ git worktree add foo foo >> $ rm -rf foo >> vs >> $ git worktree add foo foo >> $ git worktree remove foo >> >> And in fact the only difference right now, aside from some very >> minuscule storage requirements that haven't gotten cleaned up, is the >> first one leaves an artifact that tells it to give me errors later until >> I run "git worktree prune" myself. > > I understand the pain point, but I also understand Duy's motivation > for being very careful about pruning worktree administrative files > automatically (so as to avoid data loss, such as changes already > staged to a worktree-local "index" file). While the proposed change > may address the pain point, it nevertheless creates the possibility of > accidental loss which Duy was careful to avoid when designing worktree > mechanics. Although annoying, the current behavior gives you the > opportunity to avoid that accidental loss by forcing you to take > deliberate action to remove the worktree administrative files. > > Perhaps there is some way to address the pain point without breaking > the fundamental promise made by git-worktree about being careful with > worktree metadata[*], but the changes proposed by this patch series > seem insufficient (even if the patch is reworked to respect worktree > locking). I've cc:'d Duy in case he wants to chime in. I agree that we want to preserve the safe guards in the worktree design. I wonder if detaching the HEAD of the missing worktree would solve the problem without losing data. In the case where something wants to checkout the same branch as the missing worktree then I think that is a good solution. I think it should be OK for branch deletion as well. Best Wishes Phillip > [*] For instance, perhaps before auto-pruning, it could check whether > the index is recording staged changes or conflict information, and > only allow auto-pruning if the index is clean. *But* there may be > other ways for information to be lost permanently (beyond a dirty > "index") which don't occur to me at present, so this has to be > considered carefully. > ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/2] Make "git branch -d" prune missing worktrees automatically. 2019-11-08 14:56 ` Phillip Wood @ 2019-11-09 11:34 ` Eric Sunshine 0 siblings, 0 replies; 15+ messages in thread From: Eric Sunshine @ 2019-11-09 11:34 UTC (permalink / raw) To: Phillip Wood Cc: Peter Jones, Git List, SZEDER Gábor, Nguyễn Thái Ngọc Duy On Fri, Nov 8, 2019 at 9:56 AM Phillip Wood <phillip.wood123@gmail.com> wrote: > On 08/11/2019 10:14, Eric Sunshine wrote: > > Perhaps there is some way to address the pain point without breaking > > the fundamental promise made by git-worktree about being careful with > > worktree metadata[*], but the changes proposed by this patch series > > seem insufficient (even if the patch is reworked to respect worktree > > locking). I've cc:'d Duy in case he wants to chime in. > > I agree that we want to preserve the safe guards in the worktree design. > I wonder if detaching the HEAD of the missing worktree would solve the > problem without losing data. In the case where something wants to > checkout the same branch as the missing worktree then I think that is a > good solution. I think it should be OK for branch deletion as well. I would feel very uncomfortable making "automatic HEAD detachment" (decapitation?) the default behavior. Although doing so may (in some fashion) safeguard precious information in .git/worktrees/<id>, it potentially brings its own difficulties. For instance, if someone takes an action which automatically detaches HEAD of a missing worktree which had some branch checked out (and possibly some changes staged in the worktree-specific "index"), and then builds more commits on that branch, then that worktree gets into a state akin to rebased upstream (for which git-rebase documentation devotes an entire section[1], "Recovering From Upstream Rebase"). While a power-user may be able to recover from such a state, allowing the general Git user to get into such a situation by default seems contraindicated. I'm not even convinced that hiding the suggested "auto-detach" behavior behind a configuration variable so power-users can enable it is entirely a good idea either since, while it may eliminate some pain, it also potentially allows abandoned worktree entries to accumulate. [1]: https://git-scm.com/docs/git-rebase#_recovering_from_upstream_rebase ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/2] Make die_if_checked_out() ignore missing worktree checkouts. 2019-10-17 16:28 [PATCH 1/2] Make die_if_checked_out() ignore missing worktree checkouts Peter Jones 2019-10-17 16:28 ` [PATCH 2/2] Make "git branch -d" prune missing worktrees automatically Peter Jones @ 2019-10-17 16:44 ` SZEDER Gábor 1 sibling, 0 replies; 15+ messages in thread From: SZEDER Gábor @ 2019-10-17 16:44 UTC (permalink / raw) To: Peter Jones; +Cc: git On Thu, Oct 17, 2019 at 12:28:25PM -0400, Peter Jones wrote: > Currently if you do, for example: > > $ git worktree add path foo > > And "foo" has already been checked out at some other path, but the user > has removed it without pruning, you'll get an error that the branch is > already checked out. It isn't meaningfully checked out, the repo's > data is just stale and no longer reflects reality. > > This makes it so that if nothing is present where a worktree is > supposedly checked out, we ignore that the worktree exists, and let it > get cleaned up the next time worktrees are pruned. > > (I would prune it instead, but prune isn't available from libgit > currently.) > > Signed-off-by: Peter Jones <pjones@redhat.com> > --- > branch.c | 3 +++ > 1 file changed, 3 insertions(+) > > diff --git a/branch.c b/branch.c > index 579494738a7..60322ded953 100644 > --- a/branch.c > +++ b/branch.c > @@ -360,6 +360,9 @@ void die_if_checked_out(const char *branch, int ignore_current_worktree) > wt = find_shared_symref("HEAD", branch); > if (!wt || (ignore_current_worktree && wt->is_current)) > return; > + if (access(wt->path, F_OK) < 0 && > + (errno == ENOENT || errno == ENOTDIR)) > + return; I think this check is insuffient: even if the directory of the working tree is not present, the working tree might still exist, and should not be ignored (or deleted/pruned in the second patch). See the description of 'git worktree lock' for details. > skip_prefix(branch, "refs/heads/", &branch); > die(_("'%s' is already checked out at '%s'"), > branch, wt->path); > -- > 2.23.0 > ^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2019-11-09 11:35 UTC | newest] Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2019-10-17 16:28 [PATCH 1/2] Make die_if_checked_out() ignore missing worktree checkouts Peter Jones 2019-10-17 16:28 ` [PATCH 2/2] Make "git branch -d" prune missing worktrees automatically Peter Jones 2019-10-17 17:28 ` Eric Sunshine 2019-10-18 19:43 ` Peter Jones 2019-10-18 19:45 ` [PATCH v2 1/4] libgit: Add a read-only helper to test the worktree lock Peter Jones 2019-10-18 19:45 ` [PATCH v2 2/4] libgit: Expose more worktree functionality Peter Jones 2019-10-21 1:59 ` Junio C Hamano 2019-10-18 19:45 ` [PATCH v2 3/4] Make die_if_checked_out() prune missing checkouts of unlocked worktrees Peter Jones 2019-10-21 2:09 ` Junio C Hamano 2019-10-18 19:45 ` [PATCH v2 4/4] Make "git branch -d" prune missing worktrees automatically Peter Jones 2019-10-21 1:36 ` [PATCH v2 1/4] libgit: Add a read-only helper to test the worktree lock Junio C Hamano 2019-11-08 10:14 ` [PATCH 2/2] Make "git branch -d" prune missing worktrees automatically Eric Sunshine 2019-11-08 14:56 ` Phillip Wood 2019-11-09 11:34 ` Eric Sunshine 2019-10-17 16:44 ` [PATCH 1/2] Make die_if_checked_out() ignore missing worktree checkouts SZEDER Gábor
Code repositories for project(s) associated with this public inbox https://80x24.org/mirrors/git.git This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).