summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/git-worktree.txt5
-rw-r--r--builtin/worktree.c2
-rwxr-xr-xt/t2406-worktree-repair.sh26
-rw-r--r--worktree.c41
4 files changed, 73 insertions, 1 deletions
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index af06128cc9..02a706c4c0 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -143,6 +143,11 @@ locate it. Running `repair` within the recently-moved working tree will
reestablish the connection. If multiple linked working trees are moved,
running `repair` from any working tree with each tree's new `<path>` as
an argument, will reestablish the connection to all the specified paths.
++
+If both the main working tree and linked working trees have been moved
+manually, then running `repair` in the main working tree and specifying the
+new `<path>` of each linked working tree will reestablish all connections
+in both directions.
unlock::
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 197fd24a55..71287b2da6 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -1052,10 +1052,10 @@ static int repair(int ac, const char **av, const char *prefix)
int rc = 0;
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
- repair_worktrees(report_repair, &rc);
p = ac > 0 ? av : self;
for (; *p; p++)
repair_worktree_at_path(*p, report_repair, &rc);
+ repair_worktrees(report_repair, &rc);
return rc;
}
diff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh
index 1fe468bfe8..f73741886b 100755
--- a/t/t2406-worktree-repair.sh
+++ b/t/t2406-worktree-repair.sh
@@ -104,6 +104,16 @@ test_expect_success 'repo not found; .git not file' '
test_i18ngrep ".git is not a file" err
'
+test_expect_success 'repo not found; .git not referencing repo' '
+ test_when_finished "rm -rf side not-a-repo && git worktree prune" &&
+ git worktree add --detach side &&
+ sed s,\.git/worktrees/side$,not-a-repo, side/.git >side/.newgit &&
+ mv side/.newgit side/.git &&
+ mkdir not-a-repo &&
+ test_must_fail git worktree repair side 2>err &&
+ test_i18ngrep ".git file does not reference a repository" err
+'
+
test_expect_success 'repo not found; .git file broken' '
test_when_finished "rm -rf orig moved && git worktree prune" &&
git worktree add --detach orig &&
@@ -176,4 +186,20 @@ test_expect_success 'repair multiple gitdir files' '
test_must_be_empty err
'
+test_expect_success 'repair moved main and linked worktrees' '
+ test_when_finished "rm -rf main side mainmoved sidemoved" &&
+ test_create_repo main &&
+ test_commit -C main init &&
+ git -C main worktree add --detach ../side &&
+ sed "s,side/\.git$,sidemoved/.git," \
+ main/.git/worktrees/side/gitdir >expect-gitdir &&
+ sed "s,main/.git/worktrees/side$,mainmoved/.git/worktrees/side," \
+ side/.git >expect-gitfile &&
+ mv main mainmoved &&
+ mv side sidemoved &&
+ git -C mainmoved worktree repair ../sidemoved &&
+ test_cmp expect-gitdir mainmoved/.git/worktrees/side/gitdir &&
+ test_cmp expect-gitfile sidemoved/.git
+'
+
test_done
diff --git a/worktree.c b/worktree.c
index f84ceae87d..821b233479 100644
--- a/worktree.c
+++ b/worktree.c
@@ -645,6 +645,42 @@ static int is_main_worktree_path(const char *path)
}
/*
+ * If both the main worktree and linked worktree have been moved, then the
+ * gitfile /path/to/worktree/.git won't point into the repository, thus we
+ * won't know which <repo>/worktrees/<id>/gitdir to repair. However, we may
+ * be able to infer the gitdir by manually reading /path/to/worktree/.git,
+ * extracting the <id>, and checking if <repo>/worktrees/<id> exists.
+ */
+static char *infer_backlink(const char *gitfile)
+{
+ struct strbuf actual = STRBUF_INIT;
+ struct strbuf inferred = STRBUF_INIT;
+ const char *id;
+
+ if (strbuf_read_file(&actual, gitfile, 0) < 0)
+ goto error;
+ if (!starts_with(actual.buf, "gitdir:"))
+ goto error;
+ if (!(id = find_last_dir_sep(actual.buf)))
+ goto error;
+ strbuf_trim(&actual);
+ id++; /* advance past '/' to point at <id> */
+ if (!*id)
+ goto error;
+ strbuf_git_common_path(&inferred, the_repository, "worktrees/%s", id);
+ if (!is_directory(inferred.buf))
+ goto error;
+
+ strbuf_release(&actual);
+ return strbuf_detach(&inferred, NULL);
+
+error:
+ strbuf_release(&actual);
+ strbuf_release(&inferred);
+ return NULL;
+}
+
+/*
* Repair <repo>/worktrees/<id>/gitdir if missing, corrupt, or not pointing at
* the worktree's path.
*/
@@ -675,6 +711,11 @@ void repair_worktree_at_path(const char *path,
if (err == READ_GITFILE_ERR_NOT_A_FILE) {
fn(1, realdotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);
goto done;
+ } else if (err == READ_GITFILE_ERR_NOT_A_REPO) {
+ if (!(backlink = infer_backlink(realdotgit.buf))) {
+ fn(1, realdotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data);
+ goto done;
+ }
} else if (err) {
fn(1, realdotgit.buf, _("unable to locate repository; .git file broken"), cb_data);
goto done;