Skip to content

Commit 8664fcb

Browse files
committed
Merge branch 'es/worktree-repair-both-moved'
"git worktree repair" learned to deal with the case where both the repository and the worktree moved. * es/worktree-repair-both-moved: worktree: teach `repair` to fix multi-directional breakage
2 parents 45a1770 + cf76bae commit 8664fcb

File tree

4 files changed

+73
-1
lines changed

4 files changed

+73
-1
lines changed

Documentation/git-worktree.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ locate it. Running `repair` within the recently-moved working tree will
143143
reestablish the connection. If multiple linked working trees are moved,
144144
running `repair` from any working tree with each tree's new `<path>` as
145145
an argument, will reestablish the connection to all the specified paths.
146+
+
147+
If both the main working tree and linked working trees have been moved
148+
manually, then running `repair` in the main working tree and specifying the
149+
new `<path>` of each linked working tree will reestablish all connections
150+
in both directions.
146151

147152
unlock::
148153

builtin/worktree.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1052,10 +1052,10 @@ static int repair(int ac, const char **av, const char *prefix)
10521052
int rc = 0;
10531053

10541054
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
1055-
repair_worktrees(report_repair, &rc);
10561055
p = ac > 0 ? av : self;
10571056
for (; *p; p++)
10581057
repair_worktree_at_path(*p, report_repair, &rc);
1058+
repair_worktrees(report_repair, &rc);
10591059
return rc;
10601060
}
10611061

t/t2406-worktree-repair.sh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,16 @@ test_expect_success 'repo not found; .git not file' '
104104
test_i18ngrep ".git is not a file" err
105105
'
106106

107+
test_expect_success 'repo not found; .git not referencing repo' '
108+
test_when_finished "rm -rf side not-a-repo && git worktree prune" &&
109+
git worktree add --detach side &&
110+
sed s,\.git/worktrees/side$,not-a-repo, side/.git >side/.newgit &&
111+
mv side/.newgit side/.git &&
112+
mkdir not-a-repo &&
113+
test_must_fail git worktree repair side 2>err &&
114+
test_i18ngrep ".git file does not reference a repository" err
115+
'
116+
107117
test_expect_success 'repo not found; .git file broken' '
108118
test_when_finished "rm -rf orig moved && git worktree prune" &&
109119
git worktree add --detach orig &&
@@ -176,4 +186,20 @@ test_expect_success 'repair multiple gitdir files' '
176186
test_must_be_empty err
177187
'
178188

189+
test_expect_success 'repair moved main and linked worktrees' '
190+
test_when_finished "rm -rf main side mainmoved sidemoved" &&
191+
test_create_repo main &&
192+
test_commit -C main init &&
193+
git -C main worktree add --detach ../side &&
194+
sed "s,side/\.git$,sidemoved/.git," \
195+
main/.git/worktrees/side/gitdir >expect-gitdir &&
196+
sed "s,main/.git/worktrees/side$,mainmoved/.git/worktrees/side," \
197+
side/.git >expect-gitfile &&
198+
mv main mainmoved &&
199+
mv side sidemoved &&
200+
git -C mainmoved worktree repair ../sidemoved &&
201+
test_cmp expect-gitdir mainmoved/.git/worktrees/side/gitdir &&
202+
test_cmp expect-gitfile sidemoved/.git
203+
'
204+
179205
test_done

worktree.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,42 @@ static int is_main_worktree_path(const char *path)
644644
return !cmp;
645645
}
646646

647+
/*
648+
* If both the main worktree and linked worktree have been moved, then the
649+
* gitfile /path/to/worktree/.git won't point into the repository, thus we
650+
* won't know which <repo>/worktrees/<id>/gitdir to repair. However, we may
651+
* be able to infer the gitdir by manually reading /path/to/worktree/.git,
652+
* extracting the <id>, and checking if <repo>/worktrees/<id> exists.
653+
*/
654+
static char *infer_backlink(const char *gitfile)
655+
{
656+
struct strbuf actual = STRBUF_INIT;
657+
struct strbuf inferred = STRBUF_INIT;
658+
const char *id;
659+
660+
if (strbuf_read_file(&actual, gitfile, 0) < 0)
661+
goto error;
662+
if (!starts_with(actual.buf, "gitdir:"))
663+
goto error;
664+
if (!(id = find_last_dir_sep(actual.buf)))
665+
goto error;
666+
strbuf_trim(&actual);
667+
id++; /* advance past '/' to point at <id> */
668+
if (!*id)
669+
goto error;
670+
strbuf_git_common_path(&inferred, the_repository, "worktrees/%s", id);
671+
if (!is_directory(inferred.buf))
672+
goto error;
673+
674+
strbuf_release(&actual);
675+
return strbuf_detach(&inferred, NULL);
676+
677+
error:
678+
strbuf_release(&actual);
679+
strbuf_release(&inferred);
680+
return NULL;
681+
}
682+
647683
/*
648684
* Repair <repo>/worktrees/<id>/gitdir if missing, corrupt, or not pointing at
649685
* the worktree's path.
@@ -675,6 +711,11 @@ void repair_worktree_at_path(const char *path,
675711
if (err == READ_GITFILE_ERR_NOT_A_FILE) {
676712
fn(1, realdotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);
677713
goto done;
714+
} else if (err == READ_GITFILE_ERR_NOT_A_REPO) {
715+
if (!(backlink = infer_backlink(realdotgit.buf))) {
716+
fn(1, realdotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data);
717+
goto done;
718+
}
678719
} else if (err) {
679720
fn(1, realdotgit.buf, _("unable to locate repository; .git file broken"), cb_data);
680721
goto done;

0 commit comments

Comments
 (0)