Skip to content

Commit ce9b8aa

Browse files
dschogitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent df380d5 commit ce9b8aa

File tree

1 file changed

+118
-75
lines changed

1 file changed

+118
-75
lines changed

setup.c

Lines changed: 118 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -818,66 +818,65 @@ static int canonicalize_ceiling_entry(struct string_list_item *item,
818818
}
819819
}
820820

821+
enum discovery_result {
822+
GIT_DIR_NONE = 0,
823+
GIT_DIR_EXPLICIT,
824+
GIT_DIR_DISCOVERED,
825+
GIT_DIR_BARE,
826+
/* these are errors */
827+
GIT_DIR_HIT_CEILING = -1,
828+
GIT_DIR_HIT_MOUNT_POINT = -2
829+
};
830+
821831
/*
822832
* We cannot decide in this function whether we are in the work tree or
823833
* not, since the config can only be read _after_ this function was called.
834+
*
835+
* Also, we avoid changing any global state (such as the current working
836+
* directory) to allow early callers.
837+
*
838+
* The directory where the search should start needs to be passed in via the
839+
* `dir` parameter; upon return, the `dir` buffer will contain the path of
840+
* the directory where the search ended, and `gitdir` will contain the path of
841+
* the discovered .git/ directory, if any. If `gitdir` is not absolute, it
842+
* is relative to `dir` (i.e. *not* necessarily the cwd).
824843
*/
825-
static const char *setup_git_directory_gently_1(int *nongit_ok)
844+
static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
845+
struct strbuf *gitdir)
826846
{
827847
const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);
828848
struct string_list ceiling_dirs = STRING_LIST_INIT_DUP;
829-
static struct strbuf cwd = STRBUF_INIT;
830-
const char *gitdirenv, *ret;
831-
char *gitfile;
832-
int offset, offset_parent, ceil_offset = -1;
849+
const char *gitdirenv;
850+
int ceil_offset = -1, min_offset = has_dos_drive_prefix(dir->buf) ? 3 : 1;
833851
dev_t current_device = 0;
834852
int one_filesystem = 1;
835853

836-
/*
837-
* We may have read an incomplete configuration before
838-
* setting-up the git directory. If so, clear the cache so
839-
* that the next queries to the configuration reload complete
840-
* configuration (including the per-repo config file that we
841-
* ignored previously).
842-
*/
843-
git_config_clear();
844-
845-
/*
846-
* Let's assume that we are in a git repository.
847-
* If it turns out later that we are somewhere else, the value will be
848-
* updated accordingly.
849-
*/
850-
if (nongit_ok)
851-
*nongit_ok = 0;
852-
853-
if (strbuf_getcwd(&cwd))
854-
die_errno(_("Unable to read current working directory"));
855-
offset = cwd.len;
856-
857854
/*
858855
* If GIT_DIR is set explicitly, we're not going
859856
* to do any discovery, but we still do repository
860857
* validation.
861858
*/
862859
gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
863-
if (gitdirenv)
864-
return setup_explicit_git_dir(gitdirenv, &cwd, nongit_ok);
860+
if (gitdirenv) {
861+
strbuf_addstr(gitdir, gitdirenv);
862+
return GIT_DIR_EXPLICIT;
863+
}
865864

866865
if (env_ceiling_dirs) {
867866
int empty_entry_found = 0;
868867

869868
string_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);
870869
filter_string_list(&ceiling_dirs, 0,
871870
canonicalize_ceiling_entry, &empty_entry_found);
872-
ceil_offset = longest_ancestor_length(cwd.buf, &ceiling_dirs);
871+
ceil_offset = longest_ancestor_length(dir->buf, &ceiling_dirs);
873872
string_list_clear(&ceiling_dirs, 0);
874873
}
875874

876-
if (ceil_offset < 0 && has_dos_drive_prefix(cwd.buf))
877-
ceil_offset = 1;
875+
if (ceil_offset < 0)
876+
ceil_offset = min_offset - 2;
878877

879878
/*
880-
* Test in the following order (relative to the cwd):
879+
* Test in the following order (relative to the dir):
881880
* - .git (file containing "gitdir: <path>")
882881
* - .git/
883882
* - ./ (bare)
@@ -889,63 +888,104 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
889888
*/
890889
one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0);
891890
if (one_filesystem)
892-
current_device = get_device_or_die(".", NULL, 0);
891+
current_device = get_device_or_die(dir->buf, NULL, 0);
893892
for (;;) {
894-
gitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);
895-
if (gitfile)
896-
gitdirenv = gitfile = xstrdup(gitfile);
897-
else {
898-
if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))
899-
gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;
893+
int offset = dir->len;
894+
895+
if (offset > min_offset)
896+
strbuf_addch(dir, '/');
897+
strbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT);
898+
gitdirenv = read_gitfile(dir->buf);
899+
if (!gitdirenv && is_git_directory(dir->buf))
900+
gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;
901+
strbuf_setlen(dir, offset);
902+
if (gitdirenv) {
903+
strbuf_addstr(gitdir, gitdirenv);
904+
return GIT_DIR_DISCOVERED;
900905
}
901906

902-
if (gitdirenv) {
903-
ret = setup_discovered_git_dir(gitdirenv,
904-
&cwd, offset,
905-
nongit_ok);
906-
free(gitfile);
907-
return ret;
907+
if (is_git_directory(dir->buf)) {
908+
strbuf_addstr(gitdir, ".");
909+
return GIT_DIR_BARE;
908910
}
909-
free(gitfile);
910911

911-
if (is_git_directory("."))
912-
return setup_bare_git_dir(&cwd, offset, nongit_ok);
912+
if (offset <= min_offset)
913+
return GIT_DIR_HIT_CEILING;
913914

914-
offset_parent = offset;
915-
while (--offset_parent > ceil_offset &&
916-
!is_dir_sep(cwd.buf[offset_parent]))
915+
while (--offset > ceil_offset && !is_dir_sep(dir->buf[offset]))
917916
; /* continue */
918-
if (offset_parent <= ceil_offset)
919-
return setup_nongit(cwd.buf, nongit_ok);
920-
if (one_filesystem) {
921-
dev_t parent_device = get_device_or_die("..", cwd.buf,
922-
offset);
923-
if (parent_device != current_device) {
924-
if (nongit_ok) {
925-
if (chdir(cwd.buf))
926-
die_errno(_("Cannot come back to cwd"));
927-
*nongit_ok = 1;
928-
return NULL;
929-
}
930-
strbuf_setlen(&cwd, offset);
931-
die(_("Not a git repository (or any parent up to mount point %s)\n"
932-
"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)."),
933-
cwd.buf);
934-
}
935-
}
936-
if (chdir("..")) {
937-
strbuf_setlen(&cwd, offset);
938-
die_errno(_("Cannot change to '%s/..'"), cwd.buf);
939-
}
940-
offset = offset_parent;
917+
if (offset <= ceil_offset)
918+
return GIT_DIR_HIT_CEILING;
919+
920+
strbuf_setlen(dir, offset > min_offset ? offset : min_offset);
921+
if (one_filesystem &&
922+
current_device != get_device_or_die(dir->buf, NULL, offset))
923+
return GIT_DIR_HIT_MOUNT_POINT;
941924
}
942925
}
943926

944927
const char *setup_git_directory_gently(int *nongit_ok)
945928
{
929+
static struct strbuf cwd = STRBUF_INIT;
930+
struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT;
946931
const char *prefix;
947932

948-
prefix = setup_git_directory_gently_1(nongit_ok);
933+
/*
934+
* We may have read an incomplete configuration before
935+
* setting-up the git directory. If so, clear the cache so
936+
* that the next queries to the configuration reload complete
937+
* configuration (including the per-repo config file that we
938+
* ignored previously).
939+
*/
940+
git_config_clear();
941+
942+
/*
943+
* Let's assume that we are in a git repository.
944+
* If it turns out later that we are somewhere else, the value will be
945+
* updated accordingly.
946+
*/
947+
if (nongit_ok)
948+
*nongit_ok = 0;
949+
950+
if (strbuf_getcwd(&cwd))
951+
die_errno(_("Unable to read current working directory"));
952+
strbuf_addbuf(&dir, &cwd);
953+
954+
switch (setup_git_directory_gently_1(&dir, &gitdir)) {
955+
case GIT_DIR_NONE:
956+
prefix = NULL;
957+
break;
958+
case GIT_DIR_EXPLICIT:
959+
prefix = setup_explicit_git_dir(gitdir.buf, &cwd, nongit_ok);
960+
break;
961+
case GIT_DIR_DISCOVERED:
962+
if (dir.len < cwd.len && chdir(dir.buf))
963+
die(_("Cannot change to '%s'"), dir.buf);
964+
prefix = setup_discovered_git_dir(gitdir.buf, &cwd, dir.len,
965+
nongit_ok);
966+
break;
967+
case GIT_DIR_BARE:
968+
if (dir.len < cwd.len && chdir(dir.buf))
969+
die(_("Cannot change to '%s'"), dir.buf);
970+
prefix = setup_bare_git_dir(&cwd, dir.len, nongit_ok);
971+
break;
972+
case GIT_DIR_HIT_CEILING:
973+
prefix = setup_nongit(cwd.buf, nongit_ok);
974+
break;
975+
case GIT_DIR_HIT_MOUNT_POINT:
976+
if (nongit_ok) {
977+
*nongit_ok = 1;
978+
strbuf_release(&cwd);
979+
strbuf_release(&dir);
980+
return NULL;
981+
}
982+
die(_("Not a git repository (or any parent up to mount point %s)\n"
983+
"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)."),
984+
dir.buf);
985+
default:
986+
die("BUG: unhandled setup_git_directory_1() result");
987+
}
988+
949989
if (prefix)
950990
setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);
951991
else
@@ -954,6 +994,9 @@ const char *setup_git_directory_gently(int *nongit_ok)
954994
startup_info->have_repository = !nongit_ok || !*nongit_ok;
955995
startup_info->prefix = prefix;
956996

997+
strbuf_release(&dir);
998+
strbuf_release(&gitdir);
999+
9571000
return prefix;
9581001
}
9591002

0 commit comments

Comments
 (0)