@matheusbsbs / matheus-bagliano / commits / bdd1f3e4da
Fork 0

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 <sunshine@sunshineco.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Eric Sunshine committed Aug 31, 2020 at 02:57 UTC bdd1f3e4da716012a52194cdfbc9c80dfdce87d4
5 files changed +175 -1
Documentation/git-worktree.txt
+9 -1
@@ -98,7 +98,10 @@ with `--reason`.
98 move::
99
100 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.)
105
106 prune::
107
@@ -115,6 +118,11 @@ repair::
118
119 Repair working tree administrative files, if possible, if they have
120 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.
126
127 unlock::
128
builtin/worktree.c
+12
@@ -1030,6 +1030,17 @@ static int remove_worktree(int ac, const char **av, const char *prefix)
1030 return ret;
1031 }
1032
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 +
1044 static int repair(int ac, const char **av, const char *prefix)
1045 {
1046 struct option options[] = {
@@ -1040,6 +1051,7 @@ static int repair(int ac, const char **av, const char *prefix)
1051 ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
1052 if (ac)
1053 usage_with_options(worktree_usage, options);
1054 + repair_worktrees(report_repair, &rc);
1055 return rc;
1056 }
1057
t/t2406-worktree-repair.sh
+82
@@ -8,4 +8,86 @@ test_expect_success setup '
8 test_commit init
9 '
10
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 +
93 test_done
worktree.c
+61
@@ -571,3 +571,64 @@ int other_head_refs(each_ref_fn fn, void *cb_data)
571 free_worktrees(worktrees);
572 return ret;
573 }
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
+11
@@ -89,6 +89,17 @@ int validate_worktree(const struct worktree *wt,
89 void update_worktree_location(struct worktree *wt,
90 const char *path_);
91
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 +
103 /*
104 * Free up the memory for worktree(s)
105 */