зеркало из https://github.com/microsoft/git.git
setup_git_directory_1(): avoid changing global state
For historical reasons, Git searches for the .git/ directory (or the .git file) by changing the working directory successively to the parent directory of the current directory, until either anything was found or until a ceiling or a mount point is hit. Further global state may be changed in case a .git/ directory was found. We do have a use case, though, where we would like to find the .git/ directory without having any global state touched, though: when we read the early config e.g. for the pager or for alias expansion. Let's just move all of code that changes any global state out of the function `setup_git_directory_gently_1()` into `setup_git_directory_gently()`. In subsequent patches, we will use the _1() function in a new `discover_git_directory()` function that we will then use for the early config code. Note: the new loop is a *little* tricky, as we have to handle the root directory specially: we cannot simply strip away the last component including the slash, as the root directory only has that slash. To remedy that, we introduce the `min_offset` variable that holds the minimal length of an absolute path, and using that to special-case the root directory, including an early exit before trying to find the parent of the root directory. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Родитель
df380d58ec
Коммит
ce9b8aab5d
235
setup.c
235
setup.c
|
@ -818,21 +818,118 @@ static int canonicalize_ceiling_entry(struct string_list_item *item,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum discovery_result {
|
||||||
|
GIT_DIR_NONE = 0,
|
||||||
|
GIT_DIR_EXPLICIT,
|
||||||
|
GIT_DIR_DISCOVERED,
|
||||||
|
GIT_DIR_BARE,
|
||||||
|
/* these are errors */
|
||||||
|
GIT_DIR_HIT_CEILING = -1,
|
||||||
|
GIT_DIR_HIT_MOUNT_POINT = -2
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We cannot decide in this function whether we are in the work tree or
|
* We cannot decide in this function whether we are in the work tree or
|
||||||
* not, since the config can only be read _after_ this function was called.
|
* not, since the config can only be read _after_ this function was called.
|
||||||
|
*
|
||||||
|
* Also, we avoid changing any global state (such as the current working
|
||||||
|
* directory) to allow early callers.
|
||||||
|
*
|
||||||
|
* The directory where the search should start needs to be passed in via the
|
||||||
|
* `dir` parameter; upon return, the `dir` buffer will contain the path of
|
||||||
|
* the directory where the search ended, and `gitdir` will contain the path of
|
||||||
|
* the discovered .git/ directory, if any. If `gitdir` is not absolute, it
|
||||||
|
* is relative to `dir` (i.e. *not* necessarily the cwd).
|
||||||
*/
|
*/
|
||||||
static const char *setup_git_directory_gently_1(int *nongit_ok)
|
static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
|
||||||
|
struct strbuf *gitdir)
|
||||||
{
|
{
|
||||||
const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);
|
const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);
|
||||||
struct string_list ceiling_dirs = STRING_LIST_INIT_DUP;
|
struct string_list ceiling_dirs = STRING_LIST_INIT_DUP;
|
||||||
static struct strbuf cwd = STRBUF_INIT;
|
const char *gitdirenv;
|
||||||
const char *gitdirenv, *ret;
|
int ceil_offset = -1, min_offset = has_dos_drive_prefix(dir->buf) ? 3 : 1;
|
||||||
char *gitfile;
|
|
||||||
int offset, offset_parent, ceil_offset = -1;
|
|
||||||
dev_t current_device = 0;
|
dev_t current_device = 0;
|
||||||
int one_filesystem = 1;
|
int one_filesystem = 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If GIT_DIR is set explicitly, we're not going
|
||||||
|
* to do any discovery, but we still do repository
|
||||||
|
* validation.
|
||||||
|
*/
|
||||||
|
gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
|
||||||
|
if (gitdirenv) {
|
||||||
|
strbuf_addstr(gitdir, gitdirenv);
|
||||||
|
return GIT_DIR_EXPLICIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (env_ceiling_dirs) {
|
||||||
|
int empty_entry_found = 0;
|
||||||
|
|
||||||
|
string_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);
|
||||||
|
filter_string_list(&ceiling_dirs, 0,
|
||||||
|
canonicalize_ceiling_entry, &empty_entry_found);
|
||||||
|
ceil_offset = longest_ancestor_length(dir->buf, &ceiling_dirs);
|
||||||
|
string_list_clear(&ceiling_dirs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ceil_offset < 0)
|
||||||
|
ceil_offset = min_offset - 2;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test in the following order (relative to the dir):
|
||||||
|
* - .git (file containing "gitdir: <path>")
|
||||||
|
* - .git/
|
||||||
|
* - ./ (bare)
|
||||||
|
* - ../.git
|
||||||
|
* - ../.git/
|
||||||
|
* - ../ (bare)
|
||||||
|
* - ../../.git/
|
||||||
|
* etc.
|
||||||
|
*/
|
||||||
|
one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0);
|
||||||
|
if (one_filesystem)
|
||||||
|
current_device = get_device_or_die(dir->buf, NULL, 0);
|
||||||
|
for (;;) {
|
||||||
|
int offset = dir->len;
|
||||||
|
|
||||||
|
if (offset > min_offset)
|
||||||
|
strbuf_addch(dir, '/');
|
||||||
|
strbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT);
|
||||||
|
gitdirenv = read_gitfile(dir->buf);
|
||||||
|
if (!gitdirenv && is_git_directory(dir->buf))
|
||||||
|
gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;
|
||||||
|
strbuf_setlen(dir, offset);
|
||||||
|
if (gitdirenv) {
|
||||||
|
strbuf_addstr(gitdir, gitdirenv);
|
||||||
|
return GIT_DIR_DISCOVERED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_git_directory(dir->buf)) {
|
||||||
|
strbuf_addstr(gitdir, ".");
|
||||||
|
return GIT_DIR_BARE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset <= min_offset)
|
||||||
|
return GIT_DIR_HIT_CEILING;
|
||||||
|
|
||||||
|
while (--offset > ceil_offset && !is_dir_sep(dir->buf[offset]))
|
||||||
|
; /* continue */
|
||||||
|
if (offset <= ceil_offset)
|
||||||
|
return GIT_DIR_HIT_CEILING;
|
||||||
|
|
||||||
|
strbuf_setlen(dir, offset > min_offset ? offset : min_offset);
|
||||||
|
if (one_filesystem &&
|
||||||
|
current_device != get_device_or_die(dir->buf, NULL, offset))
|
||||||
|
return GIT_DIR_HIT_MOUNT_POINT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *setup_git_directory_gently(int *nongit_ok)
|
||||||
|
{
|
||||||
|
static struct strbuf cwd = STRBUF_INIT;
|
||||||
|
struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT;
|
||||||
|
const char *prefix;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We may have read an incomplete configuration before
|
* We may have read an incomplete configuration before
|
||||||
* setting-up the git directory. If so, clear the cache so
|
* setting-up the git directory. If so, clear the cache so
|
||||||
|
@ -852,100 +949,43 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
|
||||||
|
|
||||||
if (strbuf_getcwd(&cwd))
|
if (strbuf_getcwd(&cwd))
|
||||||
die_errno(_("Unable to read current working directory"));
|
die_errno(_("Unable to read current working directory"));
|
||||||
offset = cwd.len;
|
strbuf_addbuf(&dir, &cwd);
|
||||||
|
|
||||||
/*
|
switch (setup_git_directory_gently_1(&dir, &gitdir)) {
|
||||||
* If GIT_DIR is set explicitly, we're not going
|
case GIT_DIR_NONE:
|
||||||
* to do any discovery, but we still do repository
|
prefix = NULL;
|
||||||
* validation.
|
break;
|
||||||
*/
|
case GIT_DIR_EXPLICIT:
|
||||||
gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
|
prefix = setup_explicit_git_dir(gitdir.buf, &cwd, nongit_ok);
|
||||||
if (gitdirenv)
|
break;
|
||||||
return setup_explicit_git_dir(gitdirenv, &cwd, nongit_ok);
|
case GIT_DIR_DISCOVERED:
|
||||||
|
if (dir.len < cwd.len && chdir(dir.buf))
|
||||||
if (env_ceiling_dirs) {
|
die(_("Cannot change to '%s'"), dir.buf);
|
||||||
int empty_entry_found = 0;
|
prefix = setup_discovered_git_dir(gitdir.buf, &cwd, dir.len,
|
||||||
|
nongit_ok);
|
||||||
string_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);
|
break;
|
||||||
filter_string_list(&ceiling_dirs, 0,
|
case GIT_DIR_BARE:
|
||||||
canonicalize_ceiling_entry, &empty_entry_found);
|
if (dir.len < cwd.len && chdir(dir.buf))
|
||||||
ceil_offset = longest_ancestor_length(cwd.buf, &ceiling_dirs);
|
die(_("Cannot change to '%s'"), dir.buf);
|
||||||
string_list_clear(&ceiling_dirs, 0);
|
prefix = setup_bare_git_dir(&cwd, dir.len, nongit_ok);
|
||||||
|
break;
|
||||||
|
case GIT_DIR_HIT_CEILING:
|
||||||
|
prefix = setup_nongit(cwd.buf, nongit_ok);
|
||||||
|
break;
|
||||||
|
case GIT_DIR_HIT_MOUNT_POINT:
|
||||||
|
if (nongit_ok) {
|
||||||
|
*nongit_ok = 1;
|
||||||
|
strbuf_release(&cwd);
|
||||||
|
strbuf_release(&dir);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
die(_("Not a git repository (or any parent up to mount point %s)\n"
|
||||||
|
"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)."),
|
||||||
|
dir.buf);
|
||||||
|
default:
|
||||||
|
die("BUG: unhandled setup_git_directory_1() result");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ceil_offset < 0 && has_dos_drive_prefix(cwd.buf))
|
|
||||||
ceil_offset = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Test in the following order (relative to the cwd):
|
|
||||||
* - .git (file containing "gitdir: <path>")
|
|
||||||
* - .git/
|
|
||||||
* - ./ (bare)
|
|
||||||
* - ../.git
|
|
||||||
* - ../.git/
|
|
||||||
* - ../ (bare)
|
|
||||||
* - ../../.git/
|
|
||||||
* etc.
|
|
||||||
*/
|
|
||||||
one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0);
|
|
||||||
if (one_filesystem)
|
|
||||||
current_device = get_device_or_die(".", NULL, 0);
|
|
||||||
for (;;) {
|
|
||||||
gitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);
|
|
||||||
if (gitfile)
|
|
||||||
gitdirenv = gitfile = xstrdup(gitfile);
|
|
||||||
else {
|
|
||||||
if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))
|
|
||||||
gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gitdirenv) {
|
|
||||||
ret = setup_discovered_git_dir(gitdirenv,
|
|
||||||
&cwd, offset,
|
|
||||||
nongit_ok);
|
|
||||||
free(gitfile);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
free(gitfile);
|
|
||||||
|
|
||||||
if (is_git_directory("."))
|
|
||||||
return setup_bare_git_dir(&cwd, offset, nongit_ok);
|
|
||||||
|
|
||||||
offset_parent = offset;
|
|
||||||
while (--offset_parent > ceil_offset &&
|
|
||||||
!is_dir_sep(cwd.buf[offset_parent]))
|
|
||||||
; /* continue */
|
|
||||||
if (offset_parent <= ceil_offset)
|
|
||||||
return setup_nongit(cwd.buf, nongit_ok);
|
|
||||||
if (one_filesystem) {
|
|
||||||
dev_t parent_device = get_device_or_die("..", cwd.buf,
|
|
||||||
offset);
|
|
||||||
if (parent_device != current_device) {
|
|
||||||
if (nongit_ok) {
|
|
||||||
if (chdir(cwd.buf))
|
|
||||||
die_errno(_("Cannot come back to cwd"));
|
|
||||||
*nongit_ok = 1;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
strbuf_setlen(&cwd, offset);
|
|
||||||
die(_("Not a git repository (or any parent up to mount point %s)\n"
|
|
||||||
"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)."),
|
|
||||||
cwd.buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (chdir("..")) {
|
|
||||||
strbuf_setlen(&cwd, offset);
|
|
||||||
die_errno(_("Cannot change to '%s/..'"), cwd.buf);
|
|
||||||
}
|
|
||||||
offset = offset_parent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *setup_git_directory_gently(int *nongit_ok)
|
|
||||||
{
|
|
||||||
const char *prefix;
|
|
||||||
|
|
||||||
prefix = setup_git_directory_gently_1(nongit_ok);
|
|
||||||
if (prefix)
|
if (prefix)
|
||||||
setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);
|
setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);
|
||||||
else
|
else
|
||||||
|
@ -954,6 +994,9 @@ const char *setup_git_directory_gently(int *nongit_ok)
|
||||||
startup_info->have_repository = !nongit_ok || !*nongit_ok;
|
startup_info->have_repository = !nongit_ok || !*nongit_ok;
|
||||||
startup_info->prefix = prefix;
|
startup_info->prefix = prefix;
|
||||||
|
|
||||||
|
strbuf_release(&dir);
|
||||||
|
strbuf_release(&gitdir);
|
||||||
|
|
||||||
return prefix;
|
return prefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче