Skip to content

Commit bdd1f3e

Browse files
sunshinecogitster
authored andcommitted
worktree: teach "repair" to fix worktree back-links to main worktree
The .git file in a linked worktree is a "gitfile" which points back to the .git/worktrees/<id> entry in the main worktree or bare repository. If a worktree's .git file is deleted or becomes corrupted or outdated, then the linked worktree won't know how to find the repository or any of its own administrative files (such as 'index', 'HEAD', etc.). An easy way for the .git file to become outdated is for the user to move the main worktree or bare repository. Although it is possible to manually update each linked worktree's .git file to reflect the new repository location, doing so requires a level of knowledge about worktree internals beyond what a user should be expected to know offhand. Therefore, teach "git worktree repair" how to repair broken or outdated worktree .git files automatically. (For this to work, the command must be invoked from within the main worktree or bare repository, or from within a worktree which has not become disconnected from the repository -- such as one which was created after the repository was moved.) Signed-off-by: Eric Sunshine <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent e8e1ff2 commit bdd1f3e

File tree

5 files changed

+175
-1
lines changed

5 files changed

+175
-1
lines changed

Documentation/git-worktree.txt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,10 @@ with `--reason`.
9898
move::
9999

100100
Move a working tree to a new location. Note that the main working tree
101-
or linked working trees containing submodules cannot be moved.
101+
or linked working trees containing submodules cannot be moved with this
102+
command. (The `git worktree repair` command, however, can reestablish
103+
the connection with linked working trees if you move the main working
104+
tree manually.)
102105

103106
prune::
104107

@@ -115,6 +118,11 @@ repair::
115118

116119
Repair working tree administrative files, if possible, if they have
117120
become corrupted or outdated due to external factors.
121+
+
122+
For instance, if the main working tree (or bare repository) is moved,
123+
linked working trees will be unable to locate it. Running `repair` in
124+
the main working tree will reestablish the connection from linked
125+
working trees back to the main working tree.
118126

119127
unlock::
120128

builtin/worktree.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,6 +1030,17 @@ static int remove_worktree(int ac, const char **av, const char *prefix)
10301030
return ret;
10311031
}
10321032

1033+
static void report_repair(int iserr, const char *path, const char *msg, void *cb_data)
1034+
{
1035+
if (!iserr) {
1036+
printf_ln(_("repair: %s: %s"), msg, path);
1037+
} else {
1038+
int *exit_status = (int *)cb_data;
1039+
fprintf_ln(stderr, _("error: %s: %s"), msg, path);
1040+
*exit_status = 1;
1041+
}
1042+
}
1043+
10331044
static int repair(int ac, const char **av, const char *prefix)
10341045
{
10351046
struct option options[] = {
@@ -1040,6 +1051,7 @@ static int repair(int ac, const char **av, const char *prefix)
10401051
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
10411052
if (ac)
10421053
usage_with_options(worktree_usage, options);
1054+
repair_worktrees(report_repair, &rc);
10431055
return rc;
10441056
}
10451057

t/t2406-worktree-repair.sh

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,86 @@ test_expect_success setup '
88
test_commit init
99
'
1010

11+
test_expect_success 'skip missing worktree' '
12+
test_when_finished "git worktree prune" &&
13+
git worktree add --detach missing &&
14+
rm -rf missing &&
15+
git worktree repair >out 2>err &&
16+
test_must_be_empty out &&
17+
test_must_be_empty err
18+
'
19+
20+
test_expect_success 'worktree path not directory' '
21+
test_when_finished "git worktree prune" &&
22+
git worktree add --detach notdir &&
23+
rm -rf notdir &&
24+
>notdir &&
25+
test_must_fail git worktree repair >out 2>err &&
26+
test_must_be_empty out &&
27+
test_i18ngrep "not a directory" err
28+
'
29+
30+
test_expect_success "don't clobber .git repo" '
31+
test_when_finished "rm -rf repo && git worktree prune" &&
32+
git worktree add --detach repo &&
33+
rm -rf repo &&
34+
test_create_repo repo &&
35+
test_must_fail git worktree repair >out 2>err &&
36+
test_must_be_empty out &&
37+
test_i18ngrep ".git is not a file" err
38+
'
39+
40+
test_corrupt_gitfile () {
41+
butcher=$1 &&
42+
problem=$2 &&
43+
repairdir=${3:-.} &&
44+
test_when_finished 'rm -rf corrupt && git worktree prune' &&
45+
git worktree add --detach corrupt &&
46+
git -C corrupt rev-parse --absolute-git-dir >expect &&
47+
eval "$butcher" &&
48+
git -C "$repairdir" worktree repair >out 2>err &&
49+
test_i18ngrep "$problem" out &&
50+
test_must_be_empty err &&
51+
git -C corrupt rev-parse --absolute-git-dir >actual &&
52+
test_cmp expect actual
53+
}
54+
55+
test_expect_success 'repair missing .git file' '
56+
test_corrupt_gitfile "rm -f corrupt/.git" ".git file broken"
57+
'
58+
59+
test_expect_success 'repair bogus .git file' '
60+
test_corrupt_gitfile "echo \"gitdir: /nowhere\" >corrupt/.git" \
61+
".git file broken"
62+
'
63+
64+
test_expect_success 'repair incorrect .git file' '
65+
test_when_finished "rm -rf other && git worktree prune" &&
66+
test_create_repo other &&
67+
other=$(git -C other rev-parse --absolute-git-dir) &&
68+
test_corrupt_gitfile "echo \"gitdir: $other\" >corrupt/.git" \
69+
".git file incorrect"
70+
'
71+
72+
test_expect_success 'repair .git file from main/.git' '
73+
test_corrupt_gitfile "rm -f corrupt/.git" ".git file broken" .git
74+
'
75+
76+
test_expect_success 'repair .git file from linked worktree' '
77+
test_when_finished "rm -rf other && git worktree prune" &&
78+
git worktree add --detach other &&
79+
test_corrupt_gitfile "rm -f corrupt/.git" ".git file broken" other
80+
'
81+
82+
test_expect_success 'repair .git file from bare.git' '
83+
test_when_finished "rm -rf bare.git corrupt && git worktree prune" &&
84+
git clone --bare . bare.git &&
85+
git -C bare.git worktree add --detach ../corrupt &&
86+
git -C corrupt rev-parse --absolute-git-dir >expect &&
87+
rm -f corrupt/.git &&
88+
git -C bare.git worktree repair &&
89+
git -C corrupt rev-parse --absolute-git-dir >actual &&
90+
test_cmp expect actual
91+
'
92+
1193
test_done

worktree.c

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,3 +571,64 @@ int other_head_refs(each_ref_fn fn, void *cb_data)
571571
free_worktrees(worktrees);
572572
return ret;
573573
}
574+
575+
/*
576+
* Repair worktree's /path/to/worktree/.git file if missing, corrupt, or not
577+
* pointing at <repo>/worktrees/<id>.
578+
*/
579+
static void repair_gitfile(struct worktree *wt,
580+
worktree_repair_fn fn, void *cb_data)
581+
{
582+
struct strbuf dotgit = STRBUF_INIT;
583+
struct strbuf repo = STRBUF_INIT;
584+
char *backlink;
585+
const char *repair = NULL;
586+
int err;
587+
588+
/* missing worktree can't be repaired */
589+
if (!file_exists(wt->path))
590+
return;
591+
592+
if (!is_directory(wt->path)) {
593+
fn(1, wt->path, _("not a directory"), cb_data);
594+
return;
595+
}
596+
597+
strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1);
598+
strbuf_addf(&dotgit, "%s/.git", wt->path);
599+
backlink = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
600+
601+
if (err == READ_GITFILE_ERR_NOT_A_FILE)
602+
fn(1, wt->path, _(".git is not a file"), cb_data);
603+
else if (err)
604+
repair = _(".git file broken");
605+
else if (fspathcmp(backlink, repo.buf))
606+
repair = _(".git file incorrect");
607+
608+
if (repair) {
609+
fn(0, wt->path, repair, cb_data);
610+
write_file(dotgit.buf, "gitdir: %s", repo.buf);
611+
}
612+
613+
free(backlink);
614+
strbuf_release(&repo);
615+
strbuf_release(&dotgit);
616+
}
617+
618+
static void repair_noop(int iserr, const char *path, const char *msg,
619+
void *cb_data)
620+
{
621+
/* nothing */
622+
}
623+
624+
void repair_worktrees(worktree_repair_fn fn, void *cb_data)
625+
{
626+
struct worktree **worktrees = get_worktrees();
627+
struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
628+
629+
if (!fn)
630+
fn = repair_noop;
631+
for (; *wt; wt++)
632+
repair_gitfile(*wt, fn, cb_data);
633+
free_worktrees(worktrees);
634+
}

worktree.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,17 @@ int validate_worktree(const struct worktree *wt,
8989
void update_worktree_location(struct worktree *wt,
9090
const char *path_);
9191

92+
typedef void (* worktree_repair_fn)(int iserr, const char *path,
93+
const char *msg, void *cb_data);
94+
95+
/*
96+
* Visit each registered linked worktree and repair corruptions. For each
97+
* repair made or error encountered while attempting a repair, the callback
98+
* function, if non-NULL, is called with the path of the worktree and a
99+
* description of the repair or error, along with the callback user-data.
100+
*/
101+
void repair_worktrees(worktree_repair_fn, void *cb_data);
102+
92103
/*
93104
* Free up the memory for worktree(s)
94105
*/

0 commit comments

Comments
 (0)