diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index 2a240f53ba..70437c815f 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -157,7 +157,7 @@ will reestablish the connection. If multiple linked worktrees are moved, running `repair` from any worktree with each tree's new `` as an argument, will reestablish the connection to all the specified paths. + -If both the main worktree and linked worktrees have been moved manually, +If both the main worktree and linked worktrees have been moved or copied manually, then running `repair` in the main worktree and specifying the new `` of each linked worktree will reestablish all connections in both directions. diff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh index edbf502ec5..7686e60f6a 100755 --- a/t/t2406-worktree-repair.sh +++ b/t/t2406-worktree-repair.sh @@ -197,4 +197,23 @@ test_expect_success 'repair moved main and linked worktrees' ' test_cmp expect-gitfile sidemoved/.git ' +test_expect_success 'repair copied main and linked worktrees' ' + test_when_finished "rm -rf orig dup" && + mkdir -p orig && + git -C orig init main && + test_commit -C orig/main nothing && + git -C orig/main worktree add ../linked && + cp orig/main/.git/worktrees/linked/gitdir orig/main.expect && + cp orig/linked/.git orig/linked.expect && + cp -R orig dup && + sed "s,orig/linked/\.git$,dup/linked/.git," orig/main.expect >dup/main.expect && + sed "s,orig/main/\.git/worktrees/linked$,dup/main/.git/worktrees/linked," \ + orig/linked.expect >dup/linked.expect && + git -C dup/main worktree repair ../linked && + test_cmp orig/main.expect orig/main/.git/worktrees/linked/gitdir && + test_cmp orig/linked.expect orig/linked/.git && + test_cmp dup/main.expect dup/main/.git/worktrees/linked/gitdir && + test_cmp dup/linked.expect dup/linked/.git +' + test_done diff --git a/worktree.c b/worktree.c index f3c4c8ec54..28cc2ac707 100644 --- a/worktree.c +++ b/worktree.c @@ -683,6 +683,7 @@ void repair_worktree_at_path(const char *path, struct strbuf gitdir = STRBUF_INIT; struct strbuf olddotgit = STRBUF_INIT; char *backlink = NULL; + char *inferred_backlink = NULL; const char *repair = NULL; int err; @@ -698,12 +699,24 @@ void repair_worktree_at_path(const char *path, goto done; } + inferred_backlink = infer_backlink(realdotgit.buf); backlink = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err)); 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))) { + if (inferred_backlink) { + /* + * Worktree's .git file does not point at a repository + * but we found a .git/worktrees/ in this + * repository with the same as recorded in the + * worktree's .git file so make the worktree point at + * the discovered .git/worktrees/. (Note: backlink + * is already NULL, so no need to free it first.) + */ + backlink = inferred_backlink; + inferred_backlink = NULL; + } else { fn(1, realdotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data); goto done; } @@ -712,6 +725,30 @@ void repair_worktree_at_path(const char *path, goto done; } + /* + * If we got this far, either the worktree's .git file pointed at a + * valid repository (i.e. read_gitfile_gently() returned success) or + * the .git file did not point at a repository but we were able to + * infer a suitable new value for the .git file by locating a + * .git/worktrees/ in *this* repository corresponding to the + * recorded in the worktree's .git file. + * + * However, if, at this point, inferred_backlink is non-NULL (i.e. we + * found a suitable .git/worktrees/ in *this* repository) *and* the + * worktree's .git file points at a valid repository *and* those two + * paths differ, then that indicates that the user probably *copied* + * the main and linked worktrees to a new location as a unit rather + * than *moving* them. Thus, the copied worktree's .git file actually + * points at the .git/worktrees/ in the *original* repository, not + * in the "copy" repository. In this case, point the "copy" worktree's + * .git file at the "copy" repository. + */ + if (inferred_backlink && fspathcmp(backlink, inferred_backlink)) { + free(backlink); + backlink = inferred_backlink; + inferred_backlink = NULL; + } + strbuf_addf(&gitdir, "%s/gitdir", backlink); if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0) repair = _("gitdir unreadable"); @@ -727,6 +764,7 @@ void repair_worktree_at_path(const char *path, } done: free(backlink); + free(inferred_backlink); strbuf_release(&olddotgit); strbuf_release(&gitdir); strbuf_release(&realdotgit);