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>

Johannes Schindelin committed Mar 13, 2017 at 21:10 UTC ce9b8aab5d9a40a84b4868fa890654900ab2d4cc
1 file changed +118 -75
setup.c
+118 -75
@@ -818,50 +818,49 @@ static int canonicalize_ceiling_entry(struct string_list_item *item,
818 }
819 }
820
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 +
831 /*
832 * We cannot decide in this function whether we are in the work tree or
833 * 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).
843 */
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)
846 {
847 const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);
848 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;
851 dev_t current_device = 0;
852 int one_filesystem = 1;
853
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 -
854 /*
855 * If GIT_DIR is set explicitly, we're not going
856 * to do any discovery, but we still do repository
857 * validation.
858 */
859 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 + }
864
865 if (env_ceiling_dirs) {
866 int empty_entry_found = 0;
@@ -869,15 +868,15 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
868 string_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);
869 filter_string_list(&ceiling_dirs, 0,
870 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);
872 string_list_clear(&ceiling_dirs, 0);
873 }
874
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;
877
878 /*
880 - * Test in the following order (relative to the cwd):
879 + * Test in the following order (relative to the dir):
880 * - .git (file containing "gitdir: <path>")
881 * - .git/
882 * - ./ (bare)
@@ -889,63 +888,104 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
888 */
889 one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0);
890 if (one_filesystem)
892 - current_device = get_device_or_die(".", NULL, 0);
891 + current_device = get_device_or_die(dir->buf, NULL, 0);
892 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;
905 }
906
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;
910 }
909 - free(gitfile);
911
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;
914
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]))
916 ; /* 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;
924 }
925 }
926
927 const char *setup_git_directory_gently(int *nongit_ok)
928 {
929 + static struct strbuf cwd = STRBUF_INIT;
930 + struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT;
931 const char *prefix;
932
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 +
989 if (prefix)
990 setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);
991 else
@@ -954,6 +994,9 @@ const char *setup_git_directory_gently(int *nongit_ok)
994 startup_info->have_repository = !nongit_ok || !*nongit_ok;
995 startup_info->prefix = prefix;
996
997 + strbuf_release(&dir);
998 + strbuf_release(&gitdir);
999 +
1000 return prefix;
1001 }
1002