From 6c1e654437b7d3fff8bb8315d61afa0e930d6776 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 7 Mar 2017 15:32:32 +0100 Subject: setup_git_directory(): use is_dir_sep() helper It is okay in practice to test for forward slashes in the output of getcwd(), because we go out of our way to convert backslashes to forward slashes in getcwd()'s output on Windows. Still, the correct way to test for a dir separator is by using the helper function we introduced for that very purpose. It also serves as a good documentation what the code tries to do (not "how"). Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- setup.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'setup.c') diff --git a/setup.c b/setup.c index 967f289f1e..4a15b10567 100644 --- a/setup.c +++ b/setup.c @@ -910,7 +910,9 @@ static const char *setup_git_directory_gently_1(int *nongit_ok) return setup_bare_git_dir(&cwd, offset, nongit_ok); offset_parent = offset; - while (--offset_parent > ceil_offset && cwd.buf[offset_parent] != '/'); + 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) { -- cgit v1.2.3 From df380d58ece2a745e4283ef4de8dfeea560546bb Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 13 Mar 2017 21:09:44 +0100 Subject: setup: prepare setup_discovered_git_dir() for the root directory Currently, the offset parameter (indicating what part of the cwd parameter corresponds to the current directory after discovering the .git/ directory) is set to 0 when we are running in the root directory. However, in the next patches we will avoid changing the current working directory while searching for the .git/ directory, meaning that the offset corresponding to the root directory will have to be 1 to reflect that this directory is characterized by the path "/" (and not ""). So let's make sure that setup_discovered_git_directory() only tries to append the trailing slash to non-root directories. Note: the setup_bare_git_directory() does not need a corresponding change, as it does not want to return a prefix. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- setup.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'setup.c') diff --git a/setup.c b/setup.c index 4a15b10567..20a1f0f870 100644 --- a/setup.c +++ b/setup.c @@ -721,8 +721,10 @@ static const char *setup_discovered_git_dir(const char *gitdir, if (offset == cwd->len) return NULL; - /* Make "offset" point to past the '/', and add a '/' at the end */ - offset++; + /* Make "offset" point past the '/' (already the case for root dirs) */ + if (offset != offset_1st_component(cwd->buf)) + offset++; + /* Add a '/' at the end */ strbuf_addch(cwd, '/'); return cwd->buf + offset; } -- cgit v1.2.3 From ce9b8aab5d9a40a84b4868fa890654900ab2d4cc Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 13 Mar 2017 21:10:42 +0100 Subject: 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 Signed-off-by: Junio C Hamano --- setup.c | 193 +++++++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 118 insertions(+), 75 deletions(-) (limited to 'setup.c') diff --git a/setup.c b/setup.c index 20a1f0f870..27a31de33f 100644 --- a/setup.c +++ b/setup.c @@ -818,50 +818,49 @@ 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 * 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); struct string_list ceiling_dirs = STRING_LIST_INIT_DUP; - static struct strbuf cwd = STRBUF_INIT; - const char *gitdirenv, *ret; - char *gitfile; - int offset, offset_parent, ceil_offset = -1; + const char *gitdirenv; + int ceil_offset = -1, min_offset = has_dos_drive_prefix(dir->buf) ? 3 : 1; dev_t current_device = 0; int one_filesystem = 1; - /* - * We may have read an incomplete configuration before - * setting-up the git directory. If so, clear the cache so - * that the next queries to the configuration reload complete - * configuration (including the per-repo config file that we - * ignored previously). - */ - git_config_clear(); - - /* - * Let's assume that we are in a git repository. - * If it turns out later that we are somewhere else, the value will be - * updated accordingly. - */ - if (nongit_ok) - *nongit_ok = 0; - - if (strbuf_getcwd(&cwd)) - die_errno(_("Unable to read current working directory")); - offset = cwd.len; - /* * 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) - return setup_explicit_git_dir(gitdirenv, &cwd, nongit_ok); + if (gitdirenv) { + strbuf_addstr(gitdir, gitdirenv); + return GIT_DIR_EXPLICIT; + } if (env_ceiling_dirs) { int empty_entry_found = 0; @@ -869,15 +868,15 @@ static const char *setup_git_directory_gently_1(int *nongit_ok) 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(cwd.buf, &ceiling_dirs); + ceil_offset = longest_ancestor_length(dir->buf, &ceiling_dirs); string_list_clear(&ceiling_dirs, 0); } - if (ceil_offset < 0 && has_dos_drive_prefix(cwd.buf)) - ceil_offset = 1; + if (ceil_offset < 0) + ceil_offset = min_offset - 2; /* - * Test in the following order (relative to the cwd): + * Test in the following order (relative to the dir): * - .git (file containing "gitdir: ") * - .git/ * - ./ (bare) @@ -889,63 +888,104 @@ static const char *setup_git_directory_gently_1(int *nongit_ok) */ one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0); if (one_filesystem) - current_device = get_device_or_die(".", NULL, 0); + current_device = get_device_or_die(dir->buf, 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; + 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 (gitdirenv) { - ret = setup_discovered_git_dir(gitdirenv, - &cwd, offset, - nongit_ok); - free(gitfile); - return ret; + if (is_git_directory(dir->buf)) { + strbuf_addstr(gitdir, "."); + return GIT_DIR_BARE; } - free(gitfile); - if (is_git_directory(".")) - return setup_bare_git_dir(&cwd, offset, nongit_ok); + if (offset <= min_offset) + return GIT_DIR_HIT_CEILING; - offset_parent = offset; - while (--offset_parent > ceil_offset && - !is_dir_sep(cwd.buf[offset_parent])) + while (--offset > ceil_offset && !is_dir_sep(dir->buf[offset])) ; /* 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; + 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; - prefix = setup_git_directory_gently_1(nongit_ok); + /* + * We may have read an incomplete configuration before + * setting-up the git directory. If so, clear the cache so + * that the next queries to the configuration reload complete + * configuration (including the per-repo config file that we + * ignored previously). + */ + git_config_clear(); + + /* + * Let's assume that we are in a git repository. + * If it turns out later that we are somewhere else, the value will be + * updated accordingly. + */ + if (nongit_ok) + *nongit_ok = 0; + + if (strbuf_getcwd(&cwd)) + die_errno(_("Unable to read current working directory")); + strbuf_addbuf(&dir, &cwd); + + switch (setup_git_directory_gently_1(&dir, &gitdir)) { + case GIT_DIR_NONE: + prefix = NULL; + break; + case GIT_DIR_EXPLICIT: + prefix = setup_explicit_git_dir(gitdir.buf, &cwd, nongit_ok); + break; + case GIT_DIR_DISCOVERED: + if (dir.len < cwd.len && chdir(dir.buf)) + die(_("Cannot change to '%s'"), dir.buf); + prefix = setup_discovered_git_dir(gitdir.buf, &cwd, dir.len, + nongit_ok); + break; + case GIT_DIR_BARE: + if (dir.len < cwd.len && chdir(dir.buf)) + die(_("Cannot change to '%s'"), dir.buf); + 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 (prefix) setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1); else @@ -954,6 +994,9 @@ const char *setup_git_directory_gently(int *nongit_ok) startup_info->have_repository = !nongit_ok || !*nongit_ok; startup_info->prefix = prefix; + strbuf_release(&dir); + strbuf_release(&gitdir); + return prefix; } -- cgit v1.2.3 From 16ac8b8db6ec7400719db6b5c76edaab33c47606 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 13 Mar 2017 21:10:45 +0100 Subject: setup: introduce the discover_git_directory() function We modified the setup_git_directory_gently_1() function earlier to make it possible to discover the GIT_DIR without changing global state. However, it is still a bit cumbersome to use if you only need to figure out the (possibly absolute) path of the .git/ directory. Let's just provide a convenient wrapper function with an easier signature that *just* discovers the .git/ directory. We will use it in a subsequent patch to fix the early config. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- setup.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) (limited to 'setup.c') diff --git a/setup.c b/setup.c index 27a31de33f..d08730d94d 100644 --- a/setup.c +++ b/setup.c @@ -924,6 +924,49 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir, } } +const char *discover_git_directory(struct strbuf *gitdir) +{ + struct strbuf dir = STRBUF_INIT, err = STRBUF_INIT; + size_t gitdir_offset = gitdir->len, cwd_len; + struct repository_format candidate; + + if (strbuf_getcwd(&dir)) + return NULL; + + cwd_len = dir.len; + if (setup_git_directory_gently_1(&dir, gitdir) <= 0) { + strbuf_release(&dir); + return NULL; + } + + /* + * The returned gitdir is relative to dir, and if dir does not reflect + * the current working directory, we simply make the gitdir absolute. + */ + if (dir.len < cwd_len && !is_absolute_path(gitdir->buf + gitdir_offset)) { + /* Avoid a trailing "/." */ + if (!strcmp(".", gitdir->buf + gitdir_offset)) + strbuf_setlen(gitdir, gitdir_offset); + else + strbuf_addch(&dir, '/'); + strbuf_insert(gitdir, gitdir_offset, dir.buf, dir.len); + } + + strbuf_reset(&dir); + strbuf_addf(&dir, "%s/config", gitdir->buf + gitdir_offset); + read_repository_format(&candidate, dir.buf); + strbuf_release(&dir); + + if (verify_repository_format(&candidate, &err) < 0) { + warning("ignoring git dir '%s': %s", + gitdir->buf + gitdir_offset, err.buf); + strbuf_release(&err); + return NULL; + } + + return gitdir->buf + gitdir_offset; +} + const char *setup_git_directory_gently(int *nongit_ok) { static struct strbuf cwd = STRBUF_INIT; -- cgit v1.2.3 From 01017dce5469660191f926e35a3d9e88cbcb8537 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 13 Mar 2017 21:11:22 +0100 Subject: setup_git_directory_gently_1(): avoid die()ing This function now has a new caller in addition to setup_git_directory(): the newly introduced discover_git_directory(). That function wants to discover the current .git/ directory, and in case of a corrupted one simply pretend that there is none to be found. Example: if a stale .git file exists in the parent directory, and the user calls `git -p init`, we want Git to simply *not* read any repository config for the pager (instead of aborting with a message that the .git file is corrupt). Let's actually pretend that there was no GIT_DIR to be found in that case when being called from discover_git_directory(), but keep the previous behavior (i.e. to die()) for the setup_git_directory() case. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- setup.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) (limited to 'setup.c') diff --git a/setup.c b/setup.c index d08730d94d..f26094e9ad 100644 --- a/setup.c +++ b/setup.c @@ -825,7 +825,8 @@ enum discovery_result { GIT_DIR_BARE, /* these are errors */ GIT_DIR_HIT_CEILING = -1, - GIT_DIR_HIT_MOUNT_POINT = -2 + GIT_DIR_HIT_MOUNT_POINT = -2, + GIT_DIR_INVALID_GITFILE = -3 }; /* @@ -842,7 +843,8 @@ enum discovery_result { * is relative to `dir` (i.e. *not* necessarily the cwd). */ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir, - struct strbuf *gitdir) + struct strbuf *gitdir, + int die_on_error) { const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT); struct string_list ceiling_dirs = STRING_LIST_INIT_DUP; @@ -890,14 +892,21 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir, if (one_filesystem) current_device = get_device_or_die(dir->buf, NULL, 0); for (;;) { - int offset = dir->len; + int offset = dir->len, error_code = 0; 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; + gitdirenv = read_gitfile_gently(dir->buf, die_on_error ? + NULL : &error_code); + if (!gitdirenv) { + if (die_on_error || + error_code == READ_GITFILE_ERR_NOT_A_FILE) { + if (is_git_directory(dir->buf)) + gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT; + } else if (error_code != READ_GITFILE_ERR_STAT_FAILED) + return GIT_DIR_INVALID_GITFILE; + } strbuf_setlen(dir, offset); if (gitdirenv) { strbuf_addstr(gitdir, gitdirenv); @@ -934,7 +943,7 @@ const char *discover_git_directory(struct strbuf *gitdir) return NULL; cwd_len = dir.len; - if (setup_git_directory_gently_1(&dir, gitdir) <= 0) { + if (setup_git_directory_gently_1(&dir, gitdir, 0) <= 0) { strbuf_release(&dir); return NULL; } @@ -994,7 +1003,7 @@ const char *setup_git_directory_gently(int *nongit_ok) die_errno(_("Unable to read current working directory")); strbuf_addbuf(&dir, &cwd); - switch (setup_git_directory_gently_1(&dir, &gitdir)) { + switch (setup_git_directory_gently_1(&dir, &gitdir, 1)) { case GIT_DIR_NONE: prefix = NULL; break; -- cgit v1.2.3 From 5c4003ca3f0e9ac6d3c8aa3e387ff843bd440411 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 13 Mar 2017 21:12:18 +0100 Subject: setup.c: mention unresolved problems During the review of the `early-config` patch series, two issues have been identified that have been with us forever. Mark the identified problems for later so that we do not forget them. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- setup.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'setup.c') diff --git a/setup.c b/setup.c index f26094e9ad..98b8dee8b8 100644 --- a/setup.c +++ b/setup.c @@ -531,6 +531,7 @@ const char *read_gitfile_gently(const char *path, int *return_error_code) ssize_t len; if (stat(path, &st)) { + /* NEEDSWORK: discern between ENOENT vs other errors */ error_code = READ_GITFILE_ERR_STAT_FAILED; goto cleanup_return; } @@ -902,6 +903,7 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir, if (!gitdirenv) { if (die_on_error || error_code == READ_GITFILE_ERR_NOT_A_FILE) { + /* NEEDSWORK: fail if .git is not file nor dir */ if (is_git_directory(dir->buf)) gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT; } else if (error_code != READ_GITFILE_ERR_STAT_FAILED) -- cgit v1.2.3