All of lore.kernel.org
 help / color / mirror / Atom feed
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

  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 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.