diff options
-rw-r--r-- | Documentation/git-worktree.txt | 10 | ||||
-rw-r--r-- | builtin/worktree.c | 12 | ||||
-rwxr-xr-x | t/t2406-worktree-repair.sh | 82 | ||||
-rw-r--r-- | worktree.c | 61 | ||||
-rw-r--r-- | worktree.h | 11 |
5 files changed, 175 insertions, 1 deletions
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index ae432d39a8..34fe47cecd 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -98,7 +98,10 @@ with `--reason`. move:: Move a working tree to a new location. Note that the main working tree -or linked working trees containing submodules cannot be moved. +or linked working trees containing submodules cannot be moved with this +command. (The `git worktree repair` command, however, can reestablish +the connection with linked working trees if you move the main working +tree manually.) prune:: @@ -115,6 +118,11 @@ repair:: Repair working tree administrative files, if possible, if they have become corrupted or outdated due to external factors. ++ +For instance, if the main working tree (or bare repository) is moved, +linked working trees will be unable to locate it. Running `repair` in +the main working tree will reestablish the connection from linked +working trees back to the main working tree. unlock:: diff --git a/builtin/worktree.c b/builtin/worktree.c index 88af412d4f..68b0032428 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -1030,6 +1030,17 @@ static int remove_worktree(int ac, const char **av, const char *prefix) return ret; } +static void report_repair(int iserr, const char *path, const char *msg, void *cb_data) +{ + if (!iserr) { + printf_ln(_("repair: %s: %s"), msg, path); + } else { + int *exit_status = (int *)cb_data; + fprintf_ln(stderr, _("error: %s: %s"), msg, path); + *exit_status = 1; + } +} + static int repair(int ac, const char **av, const char *prefix) { struct option options[] = { @@ -1040,6 +1051,7 @@ static int repair(int ac, const char **av, const char *prefix) ac = parse_options(ac, av, prefix, options, worktree_usage, 0); if (ac) usage_with_options(worktree_usage, options); + repair_worktrees(report_repair, &rc); return rc; } diff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh index cc679e1a21..ef59cdce95 100755 --- a/t/t2406-worktree-repair.sh +++ b/t/t2406-worktree-repair.sh @@ -8,4 +8,86 @@ test_expect_success setup ' test_commit init ' +test_expect_success 'skip missing worktree' ' + test_when_finished "git worktree prune" && + git worktree add --detach missing && + rm -rf missing && + git worktree repair >out 2>err && + test_must_be_empty out && + test_must_be_empty err +' + +test_expect_success 'worktree path not directory' ' + test_when_finished "git worktree prune" && + git worktree add --detach notdir && + rm -rf notdir && + >notdir && + test_must_fail git worktree repair >out 2>err && + test_must_be_empty out && + test_i18ngrep "not a directory" err +' + +test_expect_success "don't clobber .git repo" ' + test_when_finished "rm -rf repo && git worktree prune" && + git worktree add --detach repo && + rm -rf repo && + test_create_repo repo && + test_must_fail git worktree repair >out 2>err && + test_must_be_empty out && + test_i18ngrep ".git is not a file" err +' + +test_corrupt_gitfile () { + butcher=$1 && + problem=$2 && + repairdir=${3:-.} && + test_when_finished 'rm -rf corrupt && git worktree prune' && + git worktree add --detach corrupt && + git -C corrupt rev-parse --absolute-git-dir >expect && + eval "$butcher" && + git -C "$repairdir" worktree repair >out 2>err && + test_i18ngrep "$problem" out && + test_must_be_empty err && + git -C corrupt rev-parse --absolute-git-dir >actual && + test_cmp expect actual +} + +test_expect_success 'repair missing .git file' ' + test_corrupt_gitfile "rm -f corrupt/.git" ".git file broken" +' + +test_expect_success 'repair bogus .git file' ' + test_corrupt_gitfile "echo \"gitdir: /nowhere\" >corrupt/.git" \ + ".git file broken" +' + +test_expect_success 'repair incorrect .git file' ' + test_when_finished "rm -rf other && git worktree prune" && + test_create_repo other && + other=$(git -C other rev-parse --absolute-git-dir) && + test_corrupt_gitfile "echo \"gitdir: $other\" >corrupt/.git" \ + ".git file incorrect" +' + +test_expect_success 'repair .git file from main/.git' ' + test_corrupt_gitfile "rm -f corrupt/.git" ".git file broken" .git +' + +test_expect_success 'repair .git file from linked worktree' ' + test_when_finished "rm -rf other && git worktree prune" && + git worktree add --detach other && + test_corrupt_gitfile "rm -f corrupt/.git" ".git file broken" other +' + +test_expect_success 'repair .git file from bare.git' ' + test_when_finished "rm -rf bare.git corrupt && git worktree prune" && + git clone --bare . bare.git && + git -C bare.git worktree add --detach ../corrupt && + git -C corrupt rev-parse --absolute-git-dir >expect && + rm -f corrupt/.git && + git -C bare.git worktree repair && + git -C corrupt rev-parse --absolute-git-dir >actual && + test_cmp expect actual +' + test_done diff --git a/worktree.c b/worktree.c index 62217b4a6b..3ad93cc4aa 100644 --- a/worktree.c +++ b/worktree.c @@ -571,3 +571,64 @@ int other_head_refs(each_ref_fn fn, void *cb_data) free_worktrees(worktrees); return ret; } + +/* + * Repair worktree's /path/to/worktree/.git file if missing, corrupt, or not + * pointing at <repo>/worktrees/<id>. + */ +static void repair_gitfile(struct worktree *wt, + worktree_repair_fn fn, void *cb_data) +{ + struct strbuf dotgit = STRBUF_INIT; + struct strbuf repo = STRBUF_INIT; + char *backlink; + const char *repair = NULL; + int err; + + /* missing worktree can't be repaired */ + if (!file_exists(wt->path)) + return; + + if (!is_directory(wt->path)) { + fn(1, wt->path, _("not a directory"), cb_data); + return; + } + + strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1); + strbuf_addf(&dotgit, "%s/.git", wt->path); + backlink = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err)); + + if (err == READ_GITFILE_ERR_NOT_A_FILE) + fn(1, wt->path, _(".git is not a file"), cb_data); + else if (err) + repair = _(".git file broken"); + else if (fspathcmp(backlink, repo.buf)) + repair = _(".git file incorrect"); + + if (repair) { + fn(0, wt->path, repair, cb_data); + write_file(dotgit.buf, "gitdir: %s", repo.buf); + } + + free(backlink); + strbuf_release(&repo); + strbuf_release(&dotgit); +} + +static void repair_noop(int iserr, const char *path, const char *msg, + void *cb_data) +{ + /* nothing */ +} + +void repair_worktrees(worktree_repair_fn fn, void *cb_data) +{ + struct worktree **worktrees = get_worktrees(); + struct worktree **wt = worktrees + 1; /* +1 skips main worktree */ + + if (!fn) + fn = repair_noop; + for (; *wt; wt++) + repair_gitfile(*wt, fn, cb_data); + free_worktrees(worktrees); +} diff --git a/worktree.h b/worktree.h index 516744c433..4fcb01348c 100644 --- a/worktree.h +++ b/worktree.h @@ -89,6 +89,17 @@ int validate_worktree(const struct worktree *wt, void update_worktree_location(struct worktree *wt, const char *path_); +typedef void (* worktree_repair_fn)(int iserr, const char *path, + const char *msg, void *cb_data); + +/* + * Visit each registered linked worktree and repair corruptions. For each + * repair made or error encountered while attempting a repair, the callback + * function, if non-NULL, is called with the path of the worktree and a + * description of the repair or error, along with the callback user-data. + */ +void repair_worktrees(worktree_repair_fn, void *cb_data); + /* * Free up the memory for worktree(s) */ |