From 0f7791476029b73ca9b7dfc0816bfee5f21c0dbb Mon Sep 17 00:00:00 2001 From: Phillip Wood Date: Thu, 26 Mar 2026 14:16:57 +0000 Subject: [PATCH 1/3] worktree: remove "the_repository" from is_current_worktree() The "is_current" member of struct worktree was added in 750e8a60d69 (worktree.c: mark current worktree, 2016-04-22) and was used in 8d9fdd7087d (worktree.c: check whether branch is rebased in another worktree, 2016-04-22) to optionally skip the current worktree when seeing if a branch is already checked out in die_if_checked_out(). To determine if a worktree is "current" is_current_worktree() compares the gitdir of the worktree to the gitdir of "the_repository" and returns true when they match. To get the gitdir of the worktree it calls get_workree_git_dir() which also depends on "the_repository". This means that even if "wt->path" matches "wt->repo->worktree" is_current_worktree(wt) will return false when "wt->repo" is not "the_repository". Consequently die_if_checked_out() will fail to skip such a worktree when checking if a branch is already checked out and may die errounously. Fix this by using the worktree's repository instance instead of "the_repository" when comparing gitdirs. The use of "the_repository" in is_current_wortree() comes from replacing get_git_dir() with repo_get_git_dir() in 246deeac951 (environment: make `get_git_dir()` accept a repository, 2024-09-12). In get_worktree_git_dir() it comes from replacing git_common_path() with repo_common_path() in 07242c2a5af (path: drop `git_common_path()` in favor of `repo_common_path()`, 2025-02-07). In both cases the replacements appear to have been mechanical. Signed-off-by: Phillip Wood Signed-off-by: Junio C Hamano --- worktree.c | 8 ++++---- worktree.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/worktree.c b/worktree.c index e9ff6e6ef2..344ad0c031 100644 --- a/worktree.c +++ b/worktree.c @@ -58,7 +58,7 @@ static void add_head_info(struct worktree *wt) static int is_current_worktree(struct worktree *wt) { - char *git_dir = absolute_pathdup(repo_get_git_dir(the_repository)); + char *git_dir = absolute_pathdup(repo_get_git_dir(wt->repo)); char *wt_git_dir = get_worktree_git_dir(wt); int is_current = !fspathcmp(git_dir, absolute_path(wt_git_dir)); free(wt_git_dir); @@ -78,7 +78,7 @@ struct worktree *get_worktree_from_repository(struct repository *repo) wt->is_bare = !repo->worktree; if (fspathcmp(gitdir, commondir)) wt->id = xstrdup(find_last_dir_sep(gitdir) + 1); - wt->is_current = is_current_worktree(wt); + wt->is_current = true; add_head_info(wt); free(gitdir); @@ -229,9 +229,9 @@ char *get_worktree_git_dir(const struct worktree *wt) if (!wt) return xstrdup(repo_get_git_dir(the_repository)); else if (!wt->id) - return xstrdup(repo_get_common_dir(the_repository)); + return xstrdup(repo_get_common_dir(wt->repo)); else - return repo_common_path(the_repository, "worktrees/%s", wt->id); + return repo_common_path(wt->repo, "worktrees/%s", wt->id); } static struct worktree *find_worktree_by_suffix(struct worktree **list, diff --git a/worktree.h b/worktree.h index e450d1a331..94ae58db97 100644 --- a/worktree.h +++ b/worktree.h @@ -16,7 +16,7 @@ struct worktree { struct object_id head_oid; int is_detached; int is_bare; - int is_current; + int is_current; /* does `path` match `repo->worktree` */ int lock_reason_valid; /* private */ int prune_reason_valid; /* private */ }; From 8bad0e07e1eddff2268bc9be3368c9b5fee47915 Mon Sep 17 00:00:00 2001 From: Phillip Wood Date: Thu, 26 Mar 2026 14:16:58 +0000 Subject: [PATCH 2/3] worktree add: stop reading ".git/HEAD" The function can_use_local_refs() prints a warning if there are no local branches and HEAD is invalid or points to an unborn branch. As part of the warning it prints the contents of ".git/HEAD". In a repository using the reftable backend HEAD is not stored in the filesystem so reading that file is pointless. In a repository using the files backend it is unclear how useful printing it is - it would be better to diagnose the problem for the user. For now, simplify the warning by not printing the file contents and adjust the relevant test case accordingly. Also fixup the test case to use test_grep so that anyone trying to debug a test failure in the future is not met by a wall of silence. Signed-off-by: Phillip Wood Signed-off-by: Junio C Hamano --- builtin/worktree.c | 21 ++------------------- t/t2400-worktree-add.sh | 28 ++++++++++++---------------- 2 files changed, 14 insertions(+), 35 deletions(-) diff --git a/builtin/worktree.c b/builtin/worktree.c index bc2d0d645b..9170b2e898 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -692,25 +692,8 @@ static int can_use_local_refs(const struct add_opts *opts) if (refs_head_ref(get_main_ref_store(the_repository), first_valid_ref, NULL)) { return 1; } else if (refs_for_each_branch_ref(get_main_ref_store(the_repository), first_valid_ref, NULL)) { - if (!opts->quiet) { - struct strbuf path = STRBUF_INIT; - struct strbuf contents = STRBUF_INIT; - char *wt_gitdir = get_worktree_git_dir(NULL); - - strbuf_add_real_path(&path, wt_gitdir); - strbuf_addstr(&path, "/HEAD"); - strbuf_read_file(&contents, path.buf, 64); - strbuf_stripspace(&contents, NULL); - strbuf_strip_suffix(&contents, "\n"); - - warning(_("HEAD points to an invalid (or orphaned) reference.\n" - "HEAD path: '%s'\n" - "HEAD contents: '%s'"), - path.buf, contents.buf); - strbuf_release(&path); - strbuf_release(&contents); - free(wt_gitdir); - } + if (!opts->quiet) + warning(_("HEAD points to an invalid (or orphaned) reference.\n")); return 1; } return 0; diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index 023e1301c8..58b4445cc4 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -987,7 +987,7 @@ test_dwim_orphan () { then test_must_be_empty actual else - grep "$info_text" actual + test_grep "$info_text" actual fi elif [ "$outcome" = "no_infer" ] then @@ -996,39 +996,35 @@ test_dwim_orphan () { then test_must_be_empty actual else - ! grep "$info_text" actual + test_grep ! "$info_text" actual fi elif [ "$outcome" = "fetch_error" ] then test_must_fail git $dashc_args worktree add $args 2>actual && - grep "$fetch_error_text" actual + test_grep "$fetch_error_text" actual elif [ "$outcome" = "fatal_orphan_bad_combo" ] then test_must_fail git $dashc_args worktree add $args 2>actual && if [ $use_quiet -eq 1 ] then - ! grep "$info_text" actual + test_grep ! "$info_text" actual else - grep "$info_text" actual + test_grep "$info_text" actual fi && - grep "$bad_combo_regex" actual + test_grep "$bad_combo_regex" actual elif [ "$outcome" = "warn_bad_head" ] then test_must_fail git $dashc_args worktree add $args 2>actual && if [ $use_quiet -eq 1 ] then - grep "$invalid_ref_regex" actual && - ! grep "$orphan_hint" actual + test_grep "$invalid_ref_regex" actual && + test_grep ! "$orphan_hint" actual else - headpath=$(git $dashc_args rev-parse --path-format=absolute --git-path HEAD) && - headcontents=$(cat "$headpath") && - grep "HEAD points to an invalid (or orphaned) reference" actual && - grep "HEAD path: .$headpath." actual && - grep "HEAD contents: .$headcontents." actual && - grep "$orphan_hint" actual && - ! grep "$info_text" actual + test_grep "HEAD points to an invalid (or orphaned) reference" actual && + test_grep "$orphan_hint" actual && + test_grep ! "$info_text" actual fi && - grep "$invalid_ref_regex" actual + test_grep "$invalid_ref_regex" actual else # Unreachable false From 758086869940c96585f05a0eefe6d2f24fd70630 Mon Sep 17 00:00:00 2001 From: Phillip Wood Date: Thu, 26 Mar 2026 14:16:59 +0000 Subject: [PATCH 3/3] worktree: reject NULL worktree in get_worktree_git_dir() This removes the final dependence on "the_repository" in get_worktree_git_dir(). The last commit removed only caller that passed a NULL worktree. get_worktree_git_dir() has the following callers: - branch.c:prepare_checked_out_branches() which loops over all worktrees. - builtin/fsck.c:cmd_fsck() which loops over all worktrees. - builtin/receive-pack.c:update_worktree() which is called from update() only when "worktree" is non-NULL. - builtin/worktree.c:validate_no_submodules() which is called from check_clean_worktree() and move_worktree(), both of which supply a non-NULL worktree. - reachable.c:add_rebase_files() which loops over all worktrees. - revision.c:add_index_objects_to_pending() which loops over all worktrees. - worktree.c:is_current_worktree() which expects a non-NULL worktree. Signed-off-by: Phillip Wood Signed-off-by: Junio C Hamano --- worktree.c | 2 +- worktree.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/worktree.c b/worktree.c index 344ad0c031..1ed5e8c3cd 100644 --- a/worktree.c +++ b/worktree.c @@ -227,7 +227,7 @@ struct worktree **get_worktrees_without_reading_head(void) char *get_worktree_git_dir(const struct worktree *wt) { if (!wt) - return xstrdup(repo_get_git_dir(the_repository)); + BUG("%s() called with NULL worktree", __func__); else if (!wt->id) return xstrdup(repo_get_common_dir(wt->repo)); else diff --git a/worktree.h b/worktree.h index 94ae58db97..400b614f13 100644 --- a/worktree.h +++ b/worktree.h @@ -51,7 +51,6 @@ int submodule_uses_worktrees(const char *path); /* * Return git dir of the worktree. Note that the path may be relative. - * If wt is NULL, git dir of current worktree is returned. */ char *get_worktree_git_dir(const struct worktree *wt);