From: "Yoichi NAKAYAMA via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Eric Sunshine <sunshine@sunshineco.com>,
Yoichi NAKAYAMA <yoichi.nakayama@gmail.com>,
Yoichi NAKAYAMA <yoichi.nakayama@gmail.com>
Subject: [PATCH v2 0/5] worktree repair: avoid breaking unrelated .git file and gitdir
Date: Wed, 14 Oct 2026 21:20:31 +0000 [thread overview]
Message-ID: <pull.2225.v2.git.1792012836.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2225.git.1789269613.gitgitgadget@gmail.com>
'git worktree repair' does not sufficiently validate the cross-references
between a linked working tree and its administrative data before repairing
them. This can cause the repair to modify the wrong .git file or gitdir in
certain situations.
v2 (0156021)
============
Split the first commit of v1 to make the changes easier to understand. Fold
long lines.
* [1/5] align .git file handling with repair_gitfile()
* [2/5] centralize inferred backlink selection
* [3/5] use read_gitfile_raw() in infer_backlink()
* [4/5] reduce .git file reads
* [5/5] avoid breaking unrelated .git file and gitdir
v1 (99aa341)
============
This series first refactors the code to read the .git file once and extract
the worktree ID, then uses that information to validate the repair target
before modifying the cross-references.
* [1/2] Refactor the code without changing functionality before making the
fix
* [2/2] Validate the worktree ID and inferred gitdir path before repairing
Yoichi NAKAYAMA (5):
worktree repair: align .git file handling with repair_gitfile()
worktree repair: centralize inferred backlink selection
worktree repair: use read_gitfile_raw() in infer_backlink()
worktree repair: reduce .git file reads
worktree repair: avoid breaking unrelated .git file and gitdir
t/t2406-worktree-repair.sh | 33 +++++++++--
worktree.c | 113 +++++++++++++++++++++----------------
2 files changed, 91 insertions(+), 55 deletions(-)
base-commit: 6de20f6092dcf9bdb1c8efe03db4b70c82b423dd
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2225%2Fyoichi%2Fworktree-repair-keep-unrelated-gitfile-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2225/yoichi/worktree-repair-keep-unrelated-gitfile-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/2225
Range-diff vs v1:
-: ---------- > 1: 21b2878aa5 worktree repair: align .git file handling with repair_gitfile()
1: dc7ebb427b ! 2: 30ac7acad3 worktree repair: refactor and reduce .git file reads
@@ Metadata
Author: Yoichi NAKAYAMA <yoichi.nakayama@gmail.com>
## Commit message ##
- worktree repair: refactor and reduce .git file reads
+ worktree repair: centralize inferred backlink selection
- Remove the file reading and trimming logic from `infer_backlink()`,
- and instead read the .git file once in its caller,
- `repair_worktree_at_path()`, using `read_gitfile_raw()`. Since
- `read_gitfile_gently()` is replaced with `read_gitfile_raw()`, restore
- the logic for constructing the absolute path and replace the
- READ_GITFILE_ERR_NOT_A_REPO handling with a check using
- `is_git_directory()`. Simplify the logic for prioritizing
- 'inferred_backlink' over 'backlink'.
-
- Extract `get_worktree_id()` to get the worktree ID from the contents
- of the .git file. We are going to modify and use this function in
- subsequent commits.
+ Reorder the processing steps to centralize the logic for preferring
+ the inferred_backlink.
Signed-off-by: Yoichi NAKAYAMA <yoichi.nakayama@gmail.com>
## worktree.c ##
-@@ worktree.c: int other_head_refs(struct repository *repo,
- return ret;
- }
-
-+static const char *get_worktree_id(const char *dotgit_contents)
-+{
-+ const char *slash = find_last_dir_sep(dotgit_contents);
-+ if (!slash)
-+ return "";
-+ return slash + 1;
-+}
-+
- /*
- * Repair worktree's /path/to/worktree/.git file if missing, corrupt, or not
- * pointing at <repo>/worktrees/<id>.
-@@ worktree.c: static int is_main_worktree_path(struct repository *repo, const char *path)
- * Returns -1 on failure and strbuf.len on success.
- */
- static ssize_t infer_backlink(struct repository *repo,
-- const char *gitfile,
-+ const char *dotgit_contents,
- struct strbuf *inferred)
- {
-- struct strbuf actual = STRBUF_INIT;
- const char *id;
-
-- if (strbuf_read_file(&actual, gitfile, 0) < 0)
-- goto error;
-- if (!starts_with(actual.buf, "gitdir:"))
-- goto error;
-- if (!(id = find_last_dir_sep(actual.buf)))
-- goto error;
-- strbuf_trim(&actual);
-- id++; /* advance past '/' to point at <id> */
-+ id = get_worktree_id(dotgit_contents);
- if (!*id)
- goto error;
- repo_common_path_replace(repo, inferred, "worktrees/%s", id);
- if (!is_directory(inferred->buf))
- goto error;
-
-- strbuf_release(&actual);
- return inferred->len;
- error:
-- strbuf_release(&actual);
- strbuf_reset(inferred); /* clear invalid path */
- return -1;
- }
@@ worktree.c: void repair_worktree_at_path(struct repository *repo,
- struct strbuf inferred_backlink = STRBUF_INIT;
struct strbuf gitdir = STRBUF_INIT;
struct strbuf olddotgit = STRBUF_INIT;
-- char *dotgit_contents = NULL;
-+ struct strbuf contents = STRBUF_INIT;
+ struct strbuf contents = STRBUF_INIT;
+ const char *dotgit_contents = NULL;
const char *repair = NULL;
int err;
@@ worktree.c: void repair_worktree_at_path(struct repository *repo,
- infer_backlink(repo, dotgit.buf, &inferred_backlink);
- strbuf_realpath_forgiving(&inferred_backlink, inferred_backlink.buf, 0);
-- dotgit_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
-- if (dotgit_contents) {
-- strbuf_addstr(&backlink, dotgit_contents);
-- } else if (err == READ_GITFILE_ERR_NOT_A_FILE ||
-- err == READ_GITFILE_ERR_IS_A_DIR) {
-+ err = read_gitfile_raw(&contents, dotgit.buf);
-+ if (err == READ_GITFILE_ERR_NOT_A_FILE ||
-+ err == READ_GITFILE_ERR_IS_A_DIR) {
- fn(1, dotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);
+ err = read_gitfile_raw(&contents, dotgit.buf);
+- if (!err) {
+- const char *dotgit_contents = contents.buf;
+- if (is_absolute_path(dotgit_contents)) {
+- strbuf_addstr(&backlink, dotgit_contents);
+- } else {
+- strbuf_addbuf(&backlink, &dotgit);
+- strbuf_strip_suffix(&backlink, ".git");
+- strbuf_addstr(&backlink, dotgit_contents);
+- strbuf_realpath_forgiving(&backlink, backlink.buf, 0);
+- }
+- }
+ if (err == READ_GITFILE_ERR_NOT_A_FILE ||
+ err == READ_GITFILE_ERR_IS_A_DIR) {
+ fn(1, dotgit.buf,
+ _("unable to locate repository; .git is not a file"),
+ cb_data);
goto done;
-- } else if (err == READ_GITFILE_ERR_NOT_A_REPO) {
+- } else if (!err && !is_git_directory(backlink.buf)) {
+- strbuf_release(&backlink);
- if (inferred_backlink.len) {
- /*
- * Worktree's .git file does not point at a repository
@@ worktree.c: void repair_worktree_at_path(struct repository *repo,
- */
- strbuf_swap(&backlink, &inferred_backlink);
- } else {
-- fn(1, dotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data);
+- fn(1, dotgit.buf,
+- _("unable to locate repository; .git file does not reference a repository"),
+- cb_data);
- goto done;
- }
-- } else {
-+ } else if (err) {
- fn(1, dotgit.buf, _("unable to locate repository; .git file broken"), cb_data);
+ } else if (err) {
+ fn(1, dotgit.buf,
+ _("unable to locate repository; .git file broken"),
+@@ worktree.c: void repair_worktree_at_path(struct repository *repo,
goto done;
}
+ dotgit_contents = contents.buf;
-+ infer_backlink(repo, dotgit_contents, &inferred_backlink);
++ infer_backlink(repo, dotgit.buf, &inferred_backlink);
+ strbuf_realpath_forgiving(&inferred_backlink, inferred_backlink.buf, 0);
+
+ if (is_absolute_path(dotgit_contents)) {
@@ worktree.c: void repair_worktree_at_path(struct repository *repo,
+ }
+
+ if (!is_git_directory(backlink.buf) && !inferred_backlink.len) {
-+ fn(1, dotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data);
++ fn(1, dotgit.buf,
++ _("unable to locate repository; .git file does not reference a repository"),
++ cb_data);
+ goto done;
+ }
+
@@ worktree.c: void repair_worktree_at_path(struct repository *repo,
*/
if (inferred_backlink.len && fspathcmp(backlink.buf, inferred_backlink.buf))
strbuf_swap(&backlink, &inferred_backlink);
-@@ worktree.c: void repair_worktree_at_path(struct repository *repo,
- gitdir.buf, use_relative_paths);
- }
- done:
-- free(dotgit_contents);
- strbuf_release(&olddotgit);
- strbuf_release(&backlink);
- strbuf_release(&inferred_backlink);
- strbuf_release(&gitdir);
- strbuf_release(&dotgit);
-+ strbuf_release(&contents);
- }
-
- int should_prune_worktree(struct repository *repo,
-: ---------- > 3: 2cc7b817ee worktree repair: use read_gitfile_raw() in infer_backlink()
-: ---------- > 4: 8c80b9ae05 worktree repair: reduce .git file reads
2: 99aa34135c ! 5: 015602168c worktree repair: avoid breaking unrelated .git file and gitdir
@@ worktree.c: static ssize_t infer_backlink(struct repository *repo,
return inferred->len;
@@ worktree.c: void repair_worktree_at_path(struct repository *repo,
- fn(1, dotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data);
+ cb_data);
goto done;
}
+ if (!inferred_backlink.len) {
-+ fn(1, dotgit.buf, _("unable to locate repository; .git file is not for a linked worktree"), cb_data);
++ fn(1, dotgit.buf,
++ _("unable to locate repository; .git file is not for a linked worktree"),
++ cb_data);
+ goto done;
+ }
--
gitgitgadget
next prev parent reply other threads:[~2026-10-14 21:20 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-13 3:20 [PATCH 0/2] worktree repair: avoid breaking unrelated .git file and gitdir Yoichi NAKAYAMA via GitGitGadget
2026-09-13 3:20 ` [PATCH 1/2] worktree repair: refactor and reduce .git file reads Yoichi NAKAYAMA via GitGitGadget
2026-10-08 20:55 ` Junio C Hamano
2026-10-11 16:16 ` Yoichi NAKAYAMA
2026-10-12 19:57 ` Yoichi NAKAYAMA
2026-09-13 3:20 ` [PATCH 2/2] worktree repair: avoid breaking unrelated .git file and gitdir Yoichi NAKAYAMA via GitGitGadget
2026-10-08 3:50 ` [PATCH 0/2] " Yoichi NAKAYAMA
2026-10-14 21:20 ` Yoichi NAKAYAMA via GitGitGadget [this message]
2026-10-14 21:20 ` [PATCH v2 1/5] worktree repair: align .git file handling with repair_gitfile() Yoichi NAKAYAMA via GitGitGadget
2026-10-14 21:20 ` [PATCH v2 2/5] worktree repair: centralize inferred backlink selection Yoichi NAKAYAMA via GitGitGadget
2026-10-14 21:20 ` [PATCH v2 3/5] worktree repair: use read_gitfile_raw() in infer_backlink() Yoichi NAKAYAMA via GitGitGadget
2026-10-14 21:20 ` [PATCH v2 4/5] worktree repair: reduce .git file reads Yoichi NAKAYAMA via GitGitGadget
2026-10-14 21:20 ` [PATCH v2 5/5] worktree repair: avoid breaking unrelated .git file and gitdir Yoichi NAKAYAMA via GitGitGadget
2026-10-14 22:46 ` [PATCH v2 0/5] " Junio C Hamano
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=pull.2225.v2.git.1792012836.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=git@vger.kernel.org \
--cc=sunshine@sunshineco.com \
--cc=yoichi.nakayama@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.