worktree: skip reading HEAD when repairing worktrees

When calling `git init --separate-git-dir=<new-path>` on a preexisting repository, we move the Git directory of that repository to the new path specified by the user. If there are worktrees present in the repository, we need to repair the worktrees so that their gitlinks point to the new location of the repository. This repair logic will load repositories via `get_worktrees()`, which will enumerate up and initialize all worktrees. Part of initialization is logic that we resolve their respective worktree HEADs, even though that information may not actually be needed in the end by all callers. Although not a problem presently with the file-based reference backend, it will become a problem with the upcoming reftable backend. In the context of git-init(1) we do not have a fully-initialized repository set up via `setup_git_directory()` or friends. Consequently, we do not know about the repository format when `repair_worktrees()` is called, and properly setting up all parts of the repositroy in `init_db()` before we try to repair worktrees is not an easy task. With the introduction of the reftable backend, we would ultimately try to look up the worktree HEADs before we have figured out the reference format, which does not work. We do not require the worktree HEADs at all to repair worktrees. So let's fix this issue by skipping over the step that reads them. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Dec 29, 2023 at 08:26 UTC 465a22b338a05a5ffeeffc6bd9174ef5d9a090a0
1 file changed +23 -8
worktree.c
+23 -8
@@ -51,7 +51,7 @@ static void add_head_info(struct worktree *wt)
51 /**
52 * get the main worktree
53 */
54 -static struct worktree *get_main_worktree(void)
54 +static struct worktree *get_main_worktree(int skip_reading_head)
55 {
56 struct worktree *worktree = NULL;
57 struct strbuf worktree_path = STRBUF_INIT;
@@ -70,11 +70,13 @@ static struct worktree *get_main_worktree(void)
70 */
71 worktree->is_bare = (is_bare_repository_cfg == 1) ||
72 is_bare_repository();
73 - add_head_info(worktree);
73 + if (!skip_reading_head)
74 + add_head_info(worktree);
75 return worktree;
76 }
77
77 -static struct worktree *get_linked_worktree(const char *id)
78 +static struct worktree *get_linked_worktree(const char *id,
79 + int skip_reading_head)
80 {
81 struct worktree *worktree = NULL;
82 struct strbuf path = STRBUF_INIT;
@@ -93,7 +95,8 @@ static struct worktree *get_linked_worktree(const char *id)
95 CALLOC_ARRAY(worktree, 1);
96 worktree->path = strbuf_detach(&worktree_path, NULL);
97 worktree->id = xstrdup(id);
96 - add_head_info(worktree);
98 + if (!skip_reading_head)
99 + add_head_info(worktree);
100
101 done:
102 strbuf_release(&path);
@@ -118,7 +121,14 @@ static void mark_current_worktree(struct worktree **worktrees)
121 free(git_dir);
122 }
123
121 -struct worktree **get_worktrees(void)
124 +/*
125 + * NEEDSWORK: This function exists so that we can look up metadata of a
126 + * worktree without trying to access any of its internals like the refdb. It
127 + * would be preferable to instead have a corruption-tolerant function for
128 + * retrieving worktree metadata that could be used when the worktree is known
129 + * to not be in a healthy state, e.g. when creating or repairing it.
130 + */
131 +static struct worktree **get_worktrees_internal(int skip_reading_head)
132 {
133 struct worktree **list = NULL;
134 struct strbuf path = STRBUF_INIT;
@@ -128,7 +138,7 @@ struct worktree **get_worktrees(void)
138
139 ALLOC_ARRAY(list, alloc);
140
131 - list[counter++] = get_main_worktree();
141 + list[counter++] = get_main_worktree(skip_reading_head);
142
143 strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
144 dir = opendir(path.buf);
@@ -137,7 +147,7 @@ struct worktree **get_worktrees(void)
147 while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) {
148 struct worktree *linked = NULL;
149
140 - if ((linked = get_linked_worktree(d->d_name))) {
150 + if ((linked = get_linked_worktree(d->d_name, skip_reading_head))) {
151 ALLOC_GROW(list, counter + 1, alloc);
152 list[counter++] = linked;
153 }
@@ -151,6 +161,11 @@ struct worktree **get_worktrees(void)
161 return list;
162 }
163
164 +struct worktree **get_worktrees(void)
165 +{
166 + return get_worktrees_internal(0);
167 +}
168 +
169 const char *get_worktree_git_dir(const struct worktree *wt)
170 {
171 if (!wt)
@@ -591,7 +606,7 @@ static void repair_noop(int iserr UNUSED,
606
607 void repair_worktrees(worktree_repair_fn fn, void *cb_data)
608 {
594 - struct worktree **worktrees = get_worktrees();
609 + struct worktree **worktrees = get_worktrees_internal(1);
610 struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
611
612 if (!fn)