Skip to content

Commit 1cd25e3

Browse files
committed
worktree repair: detect relative path in .git file correctly
Given a state in which the cross-references between the worktree and the repository (specifically worktree/id/gitdir in the main repository and the .git file in the worktree) are recorded using absolute paths, setting 'worktree.useRelativePaths=true' and running 'git worktree repair' within the main worktree converts them to relative paths. Conversely, given a state in which the cross-references are recorded using relative paths, one would expect that setting 'worktree.useRelativePaths=false' and running 'git worktree repair' would convert them to absolute paths. However, they remain as relative paths. This is because we incorrectly use read_gitfile_gently(), which always returns an absolute path. To fix this, introduce read_gitfile_raw(), which is almost identical to read_gitfile_gently(), but skips checking the existence of the referenced repository and returns the path as-is from the .git file. Signed-off-by: Yoichi NAKAYAMA <yoichi.nakayama@gmail.com>
1 parent dea0ea3 commit 1cd25e3

4 files changed

Lines changed: 88 additions & 50 deletions

File tree

setup.c

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -962,16 +962,48 @@ void read_gitfile_error_die(int error_code, const char *path)
962962
* cases).
963963
*/
964964
const char *read_gitfile_gently(const char *path, int *return_error_code)
965+
{
966+
int error_code = 0;
967+
const char *slash;
968+
struct strbuf contents = STRBUF_INIT;
969+
static struct strbuf realpath = STRBUF_INIT;
970+
971+
error_code = read_gitfile_raw(&contents, path);
972+
if (error_code)
973+
goto cleanup_return;
974+
975+
if (!is_absolute_path(contents.buf) && (slash = strrchr(path, '/'))) {
976+
size_t pathlen = slash+1 - path;
977+
char *dir = xstrfmt("%.*s%s", (int)pathlen, path, contents.buf);
978+
strbuf_reset(&contents);
979+
strbuf_addstr(&contents, dir);
980+
free(dir);
981+
}
982+
if (!is_git_directory(contents.buf)) {
983+
error_code = READ_GITFILE_ERR_NOT_A_REPO;
984+
goto cleanup_return;
985+
}
986+
987+
strbuf_realpath(&realpath, contents.buf, 1);
988+
989+
cleanup_return:
990+
if (return_error_code)
991+
*return_error_code = error_code;
992+
else if (error_code)
993+
read_gitfile_error_die(error_code, path);
994+
995+
strbuf_release(&contents);
996+
return error_code ? NULL : realpath.buf;
997+
}
998+
999+
int read_gitfile_raw(struct strbuf *contents, const char *path)
9651000
{
9661001
const int max_file_size = 1 << 20; /* 1MB */
9671002
int error_code = 0;
9681003
char *buf = NULL;
969-
char *dir = NULL;
970-
const char *slash;
9711004
struct stat st;
9721005
int fd;
9731006
ssize_t len;
974-
static struct strbuf realpath = STRBUF_INIT;
9751007

9761008
if (stat(path, &st)) {
9771009
if (errno == ENOENT || errno == ENOTDIR)
@@ -1014,32 +1046,11 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)
10141046
error_code = READ_GITFILE_ERR_NO_PATH;
10151047
goto cleanup_return;
10161048
}
1017-
buf[len] = '\0';
1018-
dir = buf + 8;
1019-
1020-
if (!is_absolute_path(dir) && (slash = strrchr(path, '/'))) {
1021-
size_t pathlen = slash+1 - path;
1022-
dir = xstrfmt("%.*s%.*s", (int)pathlen, path,
1023-
(int)(len - 8), buf + 8);
1024-
free(buf);
1025-
buf = dir;
1026-
}
1027-
if (!is_git_directory(dir)) {
1028-
error_code = READ_GITFILE_ERR_NOT_A_REPO;
1029-
goto cleanup_return;
1030-
}
1031-
1032-
strbuf_realpath(&realpath, dir, 1);
1033-
path = realpath.buf;
1049+
strbuf_add(contents, buf+8, len-8);
10341050

10351051
cleanup_return:
1036-
if (return_error_code)
1037-
*return_error_code = error_code;
1038-
else if (error_code)
1039-
read_gitfile_error_die(error_code, path);
1040-
10411052
free(buf);
1042-
return error_code ? NULL : path;
1053+
return error_code;
10431054
}
10441055

10451056
static void apply_gitdir_and_environment(struct repository *repo, const char *path)

setup.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ int is_nonbare_repository_dir(struct strbuf *path);
4040
#define READ_GITFILE_ERR_IS_A_DIR 10
4141
void read_gitfile_error_die(int error_code, const char *path);
4242
const char *read_gitfile_gently(const char *path, int *return_error_code);
43+
int read_gitfile_raw(struct strbuf *contents, const char *path);
4344
#define read_gitfile(path) read_gitfile_gently((path), NULL)
4445
const char *resolve_gitdir_gently(const char *suspect, int *return_error_code);
4546
#define resolve_gitdir(path) resolve_gitdir_gently((path), NULL)

t/t2406-worktree-repair.sh

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -228,30 +228,60 @@ test_expect_success 'repair worktree with relative path with missing gitfile' '
228228
test_cmp expect wt/.git
229229
'
230230

231-
test_expect_success 'repair absolute worktree to use relative paths' '
232-
test_when_finished "rm -rf main side sidemoved" &&
231+
test_expect_success 'repair absolute to relative from side worktree' '
232+
test_when_finished "rm -rf main side" &&
233233
test_create_repo main &&
234234
test_commit -C main init &&
235235
git -C main worktree add --detach ../side &&
236-
echo "../../../../sidemoved/.git" >expect-gitdir &&
236+
echo "../../../../side/.git" >expect-gitdir &&
237237
echo "gitdir: ../main/.git/worktrees/side" >expect-gitfile &&
238-
mv side sidemoved &&
239-
git -C main worktree repair --relative-paths ../sidemoved &&
238+
git -C main worktree repair --relative-paths ../side 2>main/err &&
239+
test_grep "gitdir absolute/relative path mismatch" main/err &&
240240
test_cmp expect-gitdir main/.git/worktrees/side/gitdir &&
241-
test_cmp expect-gitfile sidemoved/.git
241+
test_cmp expect-gitfile side/.git
242242
'
243243

244-
test_expect_success 'repair relative worktree to use absolute paths' '
245-
test_when_finished "rm -rf main side sidemoved" &&
244+
test_expect_success 'repair relative to absolute from side worktree' '
245+
test_when_finished "rm -rf main side" &&
246246
test_create_repo main &&
247247
test_commit -C main init &&
248248
git -C main worktree add --relative-paths --detach ../side &&
249-
echo "$(pwd)/sidemoved/.git" >expect-gitdir &&
249+
echo "$(pwd)/side/.git" >expect-gitdir &&
250250
echo "gitdir: $(pwd)/main/.git/worktrees/side" >expect-gitfile &&
251-
mv side sidemoved &&
252-
git -C main worktree repair ../sidemoved &&
251+
git -C main worktree repair ../side 2>main/err &&
252+
test_grep "gitdir absolute/relative path mismatch" main/err &&
253253
test_cmp expect-gitdir main/.git/worktrees/side/gitdir &&
254-
test_cmp expect-gitfile sidemoved/.git
254+
test_cmp expect-gitfile side/.git
255+
'
256+
257+
test_expect_success 'repair absolute to relative from main worktree' '
258+
test_when_finished "rm -rf main side" &&
259+
test_create_repo main &&
260+
git -C main config worktree.useRelativePaths false &&
261+
test_commit -C main init &&
262+
git -C main worktree add --detach ../side &&
263+
echo "../../../../side/.git" >expect-gitdir &&
264+
echo "gitdir: ../main/.git/worktrees/side" >expect-gitfile &&
265+
git -C main config worktree.useRelativePaths true &&
266+
git -C main worktree repair 2>main/err &&
267+
test_grep ".git file absolute/relative path mismatch" main/err &&
268+
test_cmp expect-gitdir main/.git/worktrees/side/gitdir &&
269+
test_cmp expect-gitfile side/.git
270+
'
271+
272+
test_expect_success 'repair relative to absolute from main worktree' '
273+
test_when_finished "rm -rf main side" &&
274+
test_create_repo main &&
275+
git -C main config worktree.useRelativePaths true &&
276+
test_commit -C main init &&
277+
git -C main worktree add --detach ../side &&
278+
echo "$(pwd)/side/.git" >expect-gitdir &&
279+
echo "gitdir: $(pwd)/main/.git/worktrees/side" >expect-gitfile &&
280+
git -C main config worktree.useRelativePaths false &&
281+
git -C main worktree repair 2>main/err &&
282+
test_grep ".git file absolute/relative path mismatch" main/err &&
283+
test_cmp expect-gitdir main/.git/worktrees/side/gitdir &&
284+
test_cmp expect-gitfile side/.git
255285
'
256286

257287
test_done

worktree.c

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,8 @@ static void repair_gitfile(struct worktree *wt,
649649
struct strbuf gitdir = STRBUF_INIT;
650650
struct strbuf repo = STRBUF_INIT;
651651
struct strbuf backlink = STRBUF_INIT;
652-
char *dotgit_contents = NULL;
652+
struct strbuf contents = STRBUF_INIT;
653+
const char *dotgit_contents = NULL;
653654
const char *repair = NULL;
654655
char *path = NULL;
655656
int err;
@@ -667,7 +668,9 @@ static void repair_gitfile(struct worktree *wt,
667668
strbuf_realpath(&repo, path, 1);
668669
strbuf_addf(&dotgit, "%s/.git", wt->path);
669670
strbuf_addf(&gitdir, "%s/gitdir", repo.buf);
670-
dotgit_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
671+
err = read_gitfile_raw(&contents, dotgit.buf);
672+
if (!err)
673+
dotgit_contents = contents.buf;
671674

672675
if (dotgit_contents) {
673676
if (is_absolute_path(dotgit_contents)) {
@@ -681,7 +684,7 @@ static void repair_gitfile(struct worktree *wt,
681684
if (err == READ_GITFILE_ERR_NOT_A_FILE ||
682685
err == READ_GITFILE_ERR_IS_A_DIR)
683686
fn(1, wt->path, _(".git is not a file"), cb_data);
684-
else if (err)
687+
else if (err || !is_git_directory(backlink.buf))
685688
repair = _(".git file broken");
686689
else if (fspathcmp(backlink.buf, repo.buf))
687690
repair = _(".git file incorrect");
@@ -695,12 +698,12 @@ static void repair_gitfile(struct worktree *wt,
695698
}
696699

697700
done:
698-
free(dotgit_contents);
699701
free(path);
700702
strbuf_release(&repo);
701703
strbuf_release(&dotgit);
702704
strbuf_release(&gitdir);
703705
strbuf_release(&backlink);
706+
strbuf_release(&contents);
704707
}
705708

706709
static void repair_noop(int iserr UNUSED,
@@ -857,14 +860,7 @@ void repair_worktree_at_path(struct repository *repo,
857860
strbuf_realpath_forgiving(&inferred_backlink, inferred_backlink.buf, 0);
858861
dotgit_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
859862
if (dotgit_contents) {
860-
if (is_absolute_path(dotgit_contents)) {
861-
strbuf_addstr(&backlink, dotgit_contents);
862-
} else {
863-
strbuf_addbuf(&backlink, &dotgit);
864-
strbuf_strip_suffix(&backlink, ".git");
865-
strbuf_addstr(&backlink, dotgit_contents);
866-
strbuf_realpath_forgiving(&backlink, backlink.buf, 0);
867-
}
863+
strbuf_addstr(&backlink, dotgit_contents);
868864
} else if (err == READ_GITFILE_ERR_NOT_A_FILE ||
869865
err == READ_GITFILE_ERR_IS_A_DIR) {
870866
fn(1, dotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);

0 commit comments

Comments
 (0)