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 v3 0/5] worktree repair: avoid breaking unrelated .git file and gitdir
Date: Sat, 17 Oct 2026 12:54:57 +0000 [thread overview]
Message-ID: <pull.2225.v3.git.1792241702.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.
v3
==
Make the separator check in get_worktree_id() more lenient.
* [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
v2
==
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
==
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 | 57 ++++++++++++--
worktree.c | 153 ++++++++++++++++++++++++-------------
2 files changed, 152 insertions(+), 58 deletions(-)
base-commit: c51408889c3b372814bc20b9431c3b74d0d268eb
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2225%2Fyoichi%2Fworktree-repair-keep-unrelated-gitfile-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2225/yoichi/worktree-repair-keep-unrelated-gitfile-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/2225
Range-diff vs v2:
1: 21b2878aa5 = 1: e336afa029 worktree repair: align .git file handling with repair_gitfile()
2: 30ac7acad3 = 2: 86021f31af worktree repair: centralize inferred backlink selection
3: 2cc7b817ee = 3: 7087e829bb worktree repair: use read_gitfile_raw() in infer_backlink()
4: 8c80b9ae05 = 4: e35285a4ea worktree repair: reduce .git file reads
5: 015602168c ! 5: f1d5c7b5f7 worktree repair: avoid breaking unrelated .git file and gitdir
@@ t/t2406-worktree-repair.sh: test_expect_success 'repair .git file from bare.git'
+ test_cmp expect corrupt/.git &&
+ test_grep "unrelated .git file" err
+'
++
++test_expect_success 'repair moved worktree whose .git file has extra separators' '
++ test_when_finished "rm -rf side sidemoved && git worktree prune" &&
++ git worktree add --detach side &&
++ mv side sidemoved &&
++ cat sidemoved/.git >expect &&
++ sed s,/,//,g sidemoved/.git | sed s,$,//, >sidemoved/.newgit &&
++ mv sidemoved/.newgit sidemoved/.git &&
++ git worktree repair sidemoved 2>err &&
++ test_cmp expect sidemoved/.git &&
++ test_grep "gitdir incorrect" err
++'
++
++test_expect_success WINDOWS 'repair moved worktree whose .git file uses backslash' '
++ test_when_finished "rm -rf side sidemoved && git worktree prune" &&
++ git worktree add --detach side &&
++ mv side sidemoved &&
++ cat sidemoved/.git >expect &&
++ sed s,\/,\\\\,g sidemoved/.git >sidemoved/.newgit &&
++ mv sidemoved/.newgit sidemoved/.git &&
++ git worktree repair sidemoved 2>err &&
++ test_cmp expect sidemoved/.git &&
++ test_grep "gitdir incorrect" err
++'
+
test_expect_success 'invalid worktree path' '
test_must_fail git worktree repair /notvalid >out 2>err &&
@@ t/t2406-worktree-repair.sh: test_expect_success 'repo not found; .git not refere
## worktree.c ##
@@ worktree.c: int other_head_refs(struct repository *repo,
- static const char *get_worktree_id(const char *dotgit_contents)
+ return ret;
+ }
+
+-static const char *get_worktree_id(const char *dotgit_contents)
++static int get_worktree_id(struct strbuf *worktree_id,
++ const char *dotgit_contents)
{
- const char *slash = find_last_dir_sep(dotgit_contents);
+- const char *slash = find_last_dir_sep(dotgit_contents);
- if (!slash)
-+ const char *prefix = "/worktrees";
+- return "";
+- return slash + 1;
++ const char *id_end;
++ const char *id;
++ const char *prefix_end;
++ const char *prefix = "worktrees";
+ int prefixlen = strlen(prefix);
-+ if (!slash ||
-+ slash - dotgit_contents < prefixlen ||
-+ strncmp(slash - prefixlen, prefix, prefixlen))
- return "";
- return slash + 1;
++
++ id_end = dotgit_contents + strlen(dotgit_contents);
++
++ /* skip trailing separators */
++ while (dotgit_contents < id_end && is_dir_sep(*(id_end - 1)))
++ id_end--;
++ if (id_end == dotgit_contents)
++ return 1;
++
++ id = id_end;
++
++ /* find the last separator */
++ while (dotgit_contents < id && !is_dir_sep(*(id - 1)))
++ id--;
++ if (id == dotgit_contents || id == id_end)
++ return 1;
++
++ prefix_end = id - 1;
++
++ /* skip repeated separators */
++ while (dotgit_contents < prefix_end && is_dir_sep(*(prefix_end - 1)))
++ prefix_end--;
++
++ /* check for "/worktrees" */
++ if (prefix_end - dotgit_contents < prefixlen + 1 ||
++ strncmp(prefix_end - prefixlen, prefix, prefixlen) ||
++ !is_dir_sep(*(prefix_end - prefixlen - 1)))
++ return 1;
++
++ strbuf_add(worktree_id, id, id_end - id);
++ return 0;
}
+
+ /*
+@@ worktree.c: static void repair_gitfile(struct worktree *wt,
+ struct strbuf repo = STRBUF_INIT;
+ struct strbuf backlink = STRBUF_INIT;
+ struct strbuf contents = STRBUF_INIT;
++ struct strbuf worktree_id = STRBUF_INIT;
+ const char *dotgit_contents = NULL;
+ const char *repair = NULL;
+ char *path = NULL;
@@ worktree.c: static void repair_gitfile(struct worktree *wt,
if (err == READ_GITFILE_ERR_NOT_A_FILE ||
err == READ_GITFILE_ERR_IS_A_DIR)
@@ worktree.c: static void repair_gitfile(struct worktree *wt,
- else if (err || !is_git_directory(backlink.buf))
+ else if (err)
repair = _(".git file broken");
-+ else if (strcmp(get_worktree_id(dotgit_contents), wt->id))
++ else if (get_worktree_id(&worktree_id, dotgit_contents) ||
++ strcmp(worktree_id.buf, wt->id))
+ fn(1, wt->path, _("unrelated .git file"), cb_data);
else if (fspathcmp(backlink.buf, repo.buf))
repair = _(".git file incorrect");
else if (use_relative_paths == is_absolute_path(dotgit_contents))
+@@ worktree.c: done:
+ strbuf_release(&gitdir);
+ strbuf_release(&backlink);
+ strbuf_release(&contents);
++ strbuf_release(&worktree_id);
+ }
+
+ static void repair_noop(int iserr UNUSED,
@@ worktree.c: static ssize_t infer_backlink(struct repository *repo,
- if (!*id)
+ const char *dotgit_contents,
+ struct strbuf *inferred)
+ {
+- const char *id;
++ struct strbuf worktree_id = STRBUF_INIT;
+
+- id = get_worktree_id(dotgit_contents);
+- if (!*id)
++ if (get_worktree_id(&worktree_id, dotgit_contents))
goto error;
- repo_common_path_replace(repo, inferred, "worktrees/%s", id);
+- repo_common_path_replace(repo, inferred, "worktrees/%s", id);
- if (!is_directory(inferred->buf))
++ repo_common_path_replace(repo, inferred,
++ "worktrees/%s", worktree_id.buf);
+ if (!is_git_directory(inferred->buf))
goto error;
++ strbuf_release(&worktree_id);
return inferred->len;
+ error:
+ strbuf_reset(inferred); /* clear invalid path */
++ strbuf_release(&worktree_id);
+ return -1;
+ }
+
@@ worktree.c: void repair_worktree_at_path(struct repository *repo,
cb_data);
goto done;
--
gitgitgadget
next prev parent reply other threads:[~2026-10-17 12:55 UTC|newest]
Thread overview: 23+ 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 ` [PATCH v2 0/5] " Yoichi NAKAYAMA via GitGitGadget
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-15 18:03 ` Junio C Hamano
2026-10-17 2:40 ` Yoichi NAKAYAMA
2026-10-14 22:46 ` [PATCH v2 0/5] " Junio C Hamano
2026-10-15 13:41 ` Yoichi NAKAYAMA
2026-10-17 12:54 ` Yoichi NAKAYAMA via GitGitGadget [this message]
2026-10-17 12:54 ` [PATCH v3 1/5] worktree repair: align .git file handling with repair_gitfile() Yoichi NAKAYAMA via GitGitGadget
2026-10-17 12:54 ` [PATCH v3 2/5] worktree repair: centralize inferred backlink selection Yoichi NAKAYAMA via GitGitGadget
2026-10-17 12:55 ` [PATCH v3 3/5] worktree repair: use read_gitfile_raw() in infer_backlink() Yoichi NAKAYAMA via GitGitGadget
2026-10-17 12:55 ` [PATCH v3 4/5] worktree repair: reduce .git file reads Yoichi NAKAYAMA via GitGitGadget
2026-10-17 12:55 ` [PATCH v3 5/5] worktree repair: avoid breaking unrelated .git file and gitdir Yoichi NAKAYAMA via GitGitGadget
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.v3.git.1792241702.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox