setup: introduce explicit repository discovery

When setting up the global repository we intermix repository discovery and repository configuration: we repeatedly call `set_git_work_tree()` and `apply_and_export_relative_gitdir()` until we're happy with the result. The result of this is then a partially-configured repository that we use for further setup. This process is quite hard to follow, as it's never quite clear which parts of the repository have been configured already and which haven't. Furthermore, it means that the repository configuration is distributed across many different places instead of having it neatly contained in a single location. Ultimately, this is the reason that we cannot use a central function like `repo_init()`. Refactor the logic so that we stop partially-configuring a repository and instead populate a new `struct repo_discovery`. This allow us to essentially split repository setup into two phases: - The first phase only figures out parameters required to configure the repository. - The second phase then takes these parameters and applies them to the repository. Like this, we'll never end up with a partially-configured repository and can eventually extend `repo_init()` to handle the full initialization for us. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Jul 7, 2026 at 09:21 UTC d66975fa06823493a3343431a36ebffbe6253424
1 file changed +98 -57
setup.c
+98 -57
@@ -1090,14 +1090,47 @@ static void apply_and_export_relative_gitdir(struct repository *repo, const char
1090 strbuf_release(&realpath);
1091 }
1092
1093 -static const char *setup_explicit_git_dir(struct repository *repo,
1094 - const char *gitdirenv,
1095 - struct strbuf *cwd,
1096 - struct repository_format *repo_fmt,
1097 - int *nongit_ok)
1093 +struct repo_discovery {
1094 + char *gitdir;
1095 + char *worktree;
1096 +};
1097 +
1098 +#define REPO_DISCOVERY_INIT { 0 }
1099 +
1100 +static void repo_discovery_release(struct repo_discovery *r)
1101 +{
1102 + free(r->gitdir);
1103 + free(r->worktree);
1104 +}
1105 +
1106 +static void repo_discovery_set_gitdir(struct repo_discovery *r,
1107 + const char *gitdir,
1108 + int make_realpath)
1109 +{
1110 + free(r->gitdir);
1111 + if (make_realpath) {
1112 + struct strbuf realpath = STRBUF_INIT;
1113 + strbuf_realpath(&realpath, gitdir, 1);
1114 + r->gitdir = strbuf_detach(&realpath, NULL);
1115 + } else {
1116 + r->gitdir = xstrdup(gitdir);
1117 + }
1118 +}
1119 +
1120 +static void repo_discovery_set_worktree(struct repo_discovery *r,
1121 + const char *worktree)
1122 +{
1123 + free(r->worktree);
1124 + r->worktree = real_pathdup(worktree, 1);
1125 +}
1126 +
1127 +static const char *repo_discover_explicit_gitdir(struct repo_discovery *discovery,
1128 + const char *gitdirenv,
1129 + struct strbuf *cwd,
1130 + struct repository_format *repo_fmt,
1131 + int *nongit_ok)
1132 {
1133 const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
1100 - const char *worktree;
1134 char *gitfile;
1135 int offset;
1136
@@ -1133,15 +1166,15 @@ static const char *setup_explicit_git_dir(struct repository *repo,
1166 * we have to explicitly unset the configuration.
1167 */
1168 FREE_AND_NULL(repo_fmt->work_tree);
1136 - set_git_work_tree(repo, work_tree_env);
1169 + repo_discovery_set_worktree(discovery, work_tree_env);
1170 } else if (repo_fmt->is_bare > 0) {
1171 /* #18, #26 */
1139 - apply_and_export_relative_gitdir(repo, gitdirenv, 0);
1172 + repo_discovery_set_gitdir(discovery, gitdirenv, 0);
1173 free(gitfile);
1174 return NULL;
1175 } else if (repo_fmt->work_tree) { /* #6, #14 */
1176 if (is_absolute_path(repo_fmt->work_tree)) {
1144 - set_git_work_tree(repo, repo_fmt->work_tree);
1177 + repo_discovery_set_worktree(discovery, repo_fmt->work_tree);
1178 } else {
1179 char *core_worktree;
1180 if (chdir(gitdirenv))
@@ -1151,49 +1184,46 @@ static const char *setup_explicit_git_dir(struct repository *repo,
1184 core_worktree = xgetcwd();
1185 if (chdir(cwd->buf))
1186 die_errno(_("cannot come back to cwd"));
1154 - set_git_work_tree(repo, core_worktree);
1187 + repo_discovery_set_worktree(discovery, core_worktree);
1188 free(core_worktree);
1189 }
1190 } else if (!git_env_bool(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, 1)) {
1191 /* #16d */
1159 - apply_and_export_relative_gitdir(repo, gitdirenv, 0);
1192 + repo_discovery_set_gitdir(discovery, gitdirenv, 0);
1193 free(gitfile);
1194 return NULL;
1195 } else { /* #2, #10 */
1163 - set_git_work_tree(repo, ".");
1196 + repo_discovery_set_worktree(discovery, ".");
1197 }
1198
1166 - /* set_git_work_tree() must have been called by now */
1167 - worktree = repo_get_work_tree(repo);
1168 -
1169 - /* both repo_get_work_tree() and cwd are already normalized */
1170 - if (!strcmp(cwd->buf, worktree)) { /* cwd == worktree */
1171 - apply_and_export_relative_gitdir(repo, gitdirenv, 0);
1199 + /* both the worktree and cwd are already normalized */
1200 + if (!strcmp(cwd->buf, discovery->worktree)) { /* cwd == worktree */
1201 + repo_discovery_set_gitdir(discovery, gitdirenv, 0);
1202 free(gitfile);
1203 return NULL;
1204 }
1205
1176 - offset = dir_inside_of(cwd->buf, worktree);
1177 - if (offset >= 0) { /* cwd inside worktree? */
1178 - apply_and_export_relative_gitdir(repo, gitdirenv, 1);
1179 - if (chdir(worktree))
1180 - die_errno(_("cannot chdir to '%s'"), worktree);
1206 + offset = dir_inside_of(cwd->buf, discovery->worktree);
1207 + if (offset >= 0) { /* cwd inside discovery->worktree? */
1208 + repo_discovery_set_gitdir(discovery, gitdirenv, 1);
1209 + if (chdir(discovery->worktree))
1210 + die_errno(_("cannot chdir to '%s'"), discovery->worktree);
1211 strbuf_addch(cwd, '/');
1212 free(gitfile);
1213 return cwd->buf + offset;
1214 }
1215
1216 /* cwd outside worktree */
1187 - apply_and_export_relative_gitdir(repo, gitdirenv, 0);
1217 + repo_discovery_set_gitdir(discovery, gitdirenv, 0);
1218 free(gitfile);
1219 return NULL;
1220 }
1221
1192 -static const char *setup_discovered_git_dir(struct repository *repo,
1193 - const char *gitdir,
1194 - struct strbuf *cwd, int offset,
1195 - struct repository_format *repo_fmt,
1196 - int *nongit_ok)
1222 +static const char *repo_discover_implicit_gitdir(struct repo_discovery *discovery,
1223 + const char *gitdir,
1224 + struct strbuf *cwd, int offset,
1225 + struct repository_format *repo_fmt,
1226 + int *nongit_ok)
1227 {
1228 if (read_and_verify_repository_format(repo_fmt, gitdir, nongit_ok))
1229 return NULL;
@@ -1207,23 +1237,24 @@ static const char *setup_discovered_git_dir(struct repository *repo,
1237 gitdir = to_free = real_pathdup(gitdir, 1);
1238 if (chdir(cwd->buf))
1239 die_errno(_("cannot come back to cwd"));
1210 - ret = setup_explicit_git_dir(repo, gitdir, cwd, repo_fmt, nongit_ok);
1240 + ret = repo_discover_explicit_gitdir(discovery, gitdir, cwd,
1241 + repo_fmt, nongit_ok);
1242 free(to_free);
1243 return ret;
1244 }
1245
1246 /* #16.2, #17.2, #20.2, #21.2, #24, #25, #28, #29 (see t1510) */
1247 if (repo_fmt->is_bare > 0) {
1217 - apply_and_export_relative_gitdir(repo, gitdir, (offset != cwd->len));
1248 + repo_discovery_set_gitdir(discovery, gitdir, (offset != cwd->len));
1249 if (chdir(cwd->buf))
1250 die_errno(_("cannot come back to cwd"));
1251 return NULL;
1252 }
1253
1254 /* #0, #1, #5, #8, #9, #12, #13 */
1224 - set_git_work_tree(repo, ".");
1255 + repo_discovery_set_worktree(discovery, ".");
1256 if (strcmp(gitdir, DEFAULT_GIT_DIR_ENVIRONMENT))
1226 - apply_and_export_relative_gitdir(repo, gitdir, 0);
1257 + repo_discovery_set_gitdir(discovery, gitdir, 0);
1258 if (offset >= cwd->len)
1259 return NULL;
1260
@@ -1236,10 +1267,10 @@ static const char *setup_discovered_git_dir(struct repository *repo,
1267 }
1268
1269 /* #16.1, #17.1, #20.1, #21.1, #22.1 (see t1510) */
1239 -static const char *setup_bare_git_dir(struct repository *repo,
1240 - struct strbuf *cwd, int offset,
1241 - struct repository_format *repo_fmt,
1242 - int *nongit_ok)
1270 +static const char *repo_discover_bare_gitdir(struct repo_discovery *discovery,
1271 + struct strbuf *cwd, int offset,
1272 + struct repository_format *repo_fmt,
1273 + int *nongit_ok)
1274 {
1275 int root_len;
1276
@@ -1255,7 +1286,8 @@ static const char *setup_bare_git_dir(struct repository *repo,
1286 gitdir = offset == cwd->len ? "." : xmemdupz(cwd->buf, offset);
1287 if (chdir(cwd->buf))
1288 die_errno(_("cannot come back to cwd"));
1258 - return setup_explicit_git_dir(repo, gitdir, cwd, repo_fmt, nongit_ok);
1289 + return repo_discover_explicit_gitdir(discovery, gitdir, cwd,
1290 + repo_fmt, nongit_ok);
1291 }
1292
1293 if (offset != cwd->len) {
@@ -1263,10 +1295,10 @@ static const char *setup_bare_git_dir(struct repository *repo,
1295 die_errno(_("cannot come back to cwd"));
1296 root_len = offset_1st_component(cwd->buf);
1297 strbuf_setlen(cwd, offset > root_len ? offset : root_len);
1266 - apply_and_export_relative_gitdir(repo, cwd->buf, 0);
1298 + repo_discovery_set_gitdir(discovery, cwd->buf, 0);
1299 }
1300 else
1269 - apply_and_export_relative_gitdir(repo, ".", 0);
1301 + repo_discovery_set_gitdir(discovery, ".", 0);
1302 return NULL;
1303 }
1304
@@ -1525,10 +1557,10 @@ static int is_implicit_bare_repo(const char *path)
1557 * the discovered .git/ directory, if any. If `gitdir` is not absolute, it
1558 * is relative to `dir` (i.e. *not* necessarily the cwd).
1559 */
1528 -static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
1529 - struct strbuf *gitdir,
1530 - struct strbuf *report,
1531 - int die_on_error)
1560 +static enum discovery_result repo_discovery_find_dir(struct strbuf *dir,
1561 + struct strbuf *gitdir,
1562 + struct strbuf *report,
1563 + int die_on_error)
1564 {
1565 const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);
1566 struct string_list ceiling_dirs = STRING_LIST_INIT_DUP;
@@ -1695,7 +1727,7 @@ enum discovery_result discover_git_directory_reason(struct strbuf *commondir,
1727 return GIT_DIR_CWD_FAILURE;
1728
1729 cwd_len = dir.len;
1698 - result = setup_git_directory_gently_1(&dir, gitdir, NULL, 0);
1730 + result = repo_discovery_find_dir(&dir, gitdir, NULL, 0);
1731 if (result <= 0) {
1732 strbuf_release(&dir);
1733 return result;
@@ -1902,6 +1934,7 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
1934 {
1935 static struct strbuf cwd = STRBUF_INIT;
1936 struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT, report = STRBUF_INIT;
1937 + struct repo_discovery discovery = REPO_DISCOVERY_INIT;
1938 const char *prefix = NULL;
1939 struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
1940
@@ -1926,20 +1959,22 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
1959 die_errno(_("Unable to read current working directory"));
1960 strbuf_addbuf(&dir, &cwd);
1961
1929 - switch (setup_git_directory_gently_1(&dir, &gitdir, &report, 1)) {
1962 + switch (repo_discovery_find_dir(&dir, &gitdir, &report, 1)) {
1963 case GIT_DIR_EXPLICIT:
1931 - prefix = setup_explicit_git_dir(repo, gitdir.buf, &cwd, &repo_fmt, nongit_ok);
1964 + prefix = repo_discover_explicit_gitdir(&discovery, gitdir.buf, &cwd,
1965 + &repo_fmt, nongit_ok);
1966 break;
1967 case GIT_DIR_DISCOVERED:
1968 if (dir.len < cwd.len && chdir(dir.buf))
1969 die(_("cannot change to '%s'"), dir.buf);
1936 - prefix = setup_discovered_git_dir(repo, gitdir.buf, &cwd, dir.len,
1937 - &repo_fmt, nongit_ok);
1970 + prefix = repo_discover_implicit_gitdir(&discovery, gitdir.buf, &cwd, dir.len,
1971 + &repo_fmt, nongit_ok);
1972 break;
1973 case GIT_DIR_BARE:
1974 if (dir.len < cwd.len && chdir(dir.buf))
1975 die(_("cannot change to '%s'"), dir.buf);
1942 - prefix = setup_bare_git_dir(repo, &cwd, dir.len, &repo_fmt, nongit_ok);
1976 + prefix = repo_discover_bare_gitdir(&discovery, &cwd, dir.len,
1977 + &repo_fmt, nongit_ok);
1978 break;
1979 case GIT_DIR_HIT_CEILING:
1980 if (!nongit_ok)
@@ -1980,13 +2015,13 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
2015 case GIT_DIR_CWD_FAILURE:
2016 case GIT_DIR_INVALID_FORMAT:
2017 /*
1983 - * As a safeguard against setup_git_directory_gently_1 returning
2018 + * As a safeguard against repo_discovery_find_dir returning
2019 * these values, fallthrough to BUG. Otherwise it is possible to
2020 * set startup_info->have_repository to 1 when we did nothing to
2021 * find a repository.
2022 */
2023 default:
1989 - BUG("unhandled setup_git_directory_gently_1() result");
2024 + BUG("unhandled repo_discovery_find_dir() result");
2025 }
2026
2027 /*
@@ -2005,10 +2040,10 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
2040 startup_info->have_repository = 1;
2041
2042 /*
2008 - * Not all paths through the setup code will call 'apply_and_export_relative_gitdir()' (which
2009 - * directly sets up the environment) so in order to guarantee that the
2010 - * environment is in a consistent state after setup, explicitly setup
2011 - * the environment if we have a repository.
2043 + * Not all paths through the setup code will have recorded a gitdir
2044 + * above, so in order to guarantee that the environment is in a
2045 + * consistent state after setup, explicitly set up the gitdir and
2046 + * environment if we have a repository.
2047 *
2048 * NEEDSWORK: currently we allow bogus GIT_DIR values to be set in some
2049 * code paths so we also need to explicitly setup the environment if
@@ -2019,7 +2054,12 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
2054 startup_info->have_repository ||
2055 /* GIT_DIR_EXPLICIT */
2056 getenv(GIT_DIR_ENVIRONMENT)) {
2022 - if (!repo->gitdir) {
2057 + if (discovery.worktree)
2058 + set_git_work_tree(repo, discovery.worktree);
2059 +
2060 + if (discovery.gitdir) {
2061 + apply_and_export_relative_gitdir(repo, discovery.gitdir, 0);
2062 + } else {
2063 const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
2064 if (!gitdir)
2065 gitdir = DEFAULT_GIT_DIR_ENVIRONMENT;
@@ -2074,6 +2114,7 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
2114
2115 setup_original_cwd(repo);
2116
2117 + repo_discovery_release(&discovery);
2118 strbuf_release(&dir);
2119 strbuf_release(&gitdir);
2120 strbuf_release(&report);