Skip to content

Commit 920147e

Browse files
committed
worktree repair: avoid breaking unrelated .git file and gitdir
Currently, `repair_gitfile()` does not verify whether the worktree ID recorded in the .git file matches the worktree being repaired, which can result in the corruption of an unrelated .git file. For instance, if two worktree directories are swapped without using 'git worktree move', running 'git worktree repair' in the main worktree accidentally swaps the links between their .git files and gitdirs. `repair_worktree_at_path()` proceeds even if it fails to infer the gitdir path. This can result in the corruption of an unrelated gitdir. For instance, if we copied a linked worktree to a new location X, running 'git worktree repair X' in a working tree which does not belong to the original repository can accidentally overwrite the gitdir in the original repository (the scope of impact should be limited to the repository where the command was executed). Resolve these issues by validating the worktree ID and stopping the repair when the ID does not match or the gitdir path cannot be inferred. Signed-off-by: Yoichi NAKAYAMA <yoichi.nakayama@gmail.com>
1 parent dc7ebb4 commit 920147e

2 files changed

Lines changed: 41 additions & 10 deletions

File tree

‎t/t2406-worktree-repair.sh‎

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,12 @@ test_expect_success 'repair missing .git file' '
5656
'
5757

5858
test_expect_success 'repair bogus .git file' '
59-
test_corrupt_gitfile "echo \"gitdir: /nowhere\" >corrupt/.git" \
59+
test_corrupt_gitfile "echo \"contents not started with gitdir:\" >corrupt/.git" \
6060
".git file broken"
6161
'
6262

63-
test_expect_success 'repair incorrect .git file' '
64-
test_when_finished "rm -rf other && git worktree prune" &&
65-
test_create_repo other &&
66-
other=$(git -C other rev-parse --absolute-git-dir) &&
67-
test_corrupt_gitfile "echo \"gitdir: $other\" >corrupt/.git" \
63+
test_expect_success 'repair unlinked .git file' '
64+
test_corrupt_gitfile "echo \"gitdir: /nowhere/worktrees/corrupt\" >corrupt/.git" \
6865
".git file incorrect"
6966
'
7067

@@ -89,6 +86,18 @@ test_expect_success 'repair .git file from bare.git' '
8986
test_cmp expect actual
9087
'
9188

89+
test_expect_success 'skip unrelated .git file' '
90+
test_when_finished "rm -rf corrupt other && git worktree prune" &&
91+
git worktree add --detach corrupt &&
92+
rm -rf corrupt &&
93+
git worktree add --detach other &&
94+
mv other corrupt &&
95+
cat corrupt/.git >expect &&
96+
test_must_fail git worktree repair 2>err &&
97+
test_cmp expect corrupt/.git &&
98+
test_grep "unrelated .git file" err
99+
'
100+
92101
test_expect_success 'invalid worktree path' '
93102
test_must_fail git worktree repair /notvalid >out 2>err &&
94103
test_must_be_empty out &&
@@ -113,6 +122,18 @@ test_expect_success 'repo not found; .git not referencing repo' '
113122
test_grep ".git file does not reference a repository" err
114123
'
115124

125+
test_expect_success 'repo not found; .git not for worktree' '
126+
test_when_finished "rm -rf side other-repo && git worktree prune" &&
127+
test_create_repo other-repo &&
128+
git worktree add --detach side &&
129+
cat .git/worktrees/side/gitdir >expect &&
130+
cp -R side other-repo/side &&
131+
test_must_fail git -C other-repo worktree repair side >out 2>err &&
132+
test_cmp expect .git/worktrees/side/gitdir &&
133+
test_must_be_empty out &&
134+
test_grep ".git file is not for a linked worktree" err
135+
'
136+
116137
test_expect_success 'repo not found; .git file broken' '
117138
test_when_finished "rm -rf orig moved && git worktree prune" &&
118139
git worktree add --detach orig &&

‎worktree.c‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,11 @@ int other_head_refs(struct repository *repo,
640640
static const char *get_worktree_id(const char *dotgit_contents)
641641
{
642642
const char *slash = find_last_dir_sep(dotgit_contents);
643-
if (!slash)
643+
const char *prefix = "/worktrees";
644+
int prefixlen = strlen(prefix);
645+
if (!slash ||
646+
slash - dotgit_contents < prefixlen ||
647+
strncmp(slash - prefixlen, prefix, prefixlen))
644648
return "";
645649
return slash + 1;
646650
}
@@ -692,8 +696,10 @@ static void repair_gitfile(struct worktree *wt,
692696
if (err == READ_GITFILE_ERR_NOT_A_FILE ||
693697
err == READ_GITFILE_ERR_IS_A_DIR)
694698
fn(1, wt->path, _(".git is not a file"), cb_data);
695-
else if (err || !is_git_directory(backlink.buf))
699+
else if (err)
696700
repair = _(".git file broken");
701+
else if (strcmp(get_worktree_id(dotgit_contents), wt->id))
702+
fn(1, wt->path, _("unrelated .git file"), cb_data);
697703
else if (fspathcmp(backlink.buf, repo.buf))
698704
repair = _(".git file incorrect");
699705
else if (use_relative_paths == is_absolute_path(dotgit_contents))
@@ -815,7 +821,7 @@ static ssize_t infer_backlink(struct repository *repo,
815821
if (!*id)
816822
goto error;
817823
repo_common_path_replace(repo, inferred, "worktrees/%s", id);
818-
if (!is_directory(inferred->buf))
824+
if (!is_git_directory(inferred->buf))
819825
goto error;
820826

821827
return inferred->len;
@@ -882,6 +888,10 @@ void repair_worktree_at_path(struct repository *repo,
882888
fn(1, dotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data);
883889
goto done;
884890
}
891+
if (!inferred_backlink.len) {
892+
fn(1, dotgit.buf, _("unable to locate repository; .git file is not for a linked worktree"), cb_data);
893+
goto done;
894+
}
885895

886896
/*
887897
* If we got this far, either the worktree's .git file pointed at a
@@ -899,7 +909,7 @@ void repair_worktree_at_path(struct repository *repo,
899909
* in the *original* repository, not in the "copy" repository).
900910
* Therefore, we prioritize inferred_backlink over backlink.
901911
*/
902-
if (inferred_backlink.len && fspathcmp(backlink.buf, inferred_backlink.buf))
912+
if (fspathcmp(backlink.buf, inferred_backlink.buf))
903913
strbuf_swap(&backlink, &inferred_backlink);
904914

905915
strbuf_addf(&gitdir, "%s/gitdir", backlink.buf);

0 commit comments

Comments
 (0)