git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
From: Johannes Schindelin <johannes.schindelin@gmx.de>
To: git@vger.kernel.org
Cc: Junio C Hamano <gitster@pobox.com>, Jeff King <peff@peff.net>,
	Duy Nguyen <pclouds@gmail.com>
Subject: [PATCH v2 3/9] setup_git_directory(): avoid changing global state during discovery
Date: Fri, 3 Mar 2017 03:04:11 +0100 (CET)	[thread overview]
Message-ID: <49f66f2b723af38a58c17bc8ae413bcee69e2a2f.1488506615.git.johannes.schindelin@gmx.de> (raw)
In-Reply-To: <cover.1488506615.git.johannes.schindelin@gmx.de>

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, depending on the actual type of
discovery, e.g. the global variable `repository_format_precious_objects`
is set in the `check_repository_format_gently()` function (which is a
bit surprising, given the function name).

We do have a use case 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 rename the function `setup_git_directory_gently_1()` to
`discover_git_directory()` and move all code that changes any global
state back into `setup_git_directory_gently()`.

In subsequent patches, we will export the `discover_git_directory()`
function and make use of it.

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>
---
 setup.c | 187 ++++++++++++++++++++++++++++++++++++++--------------------------
 1 file changed, 112 insertions(+), 75 deletions(-)

diff --git a/setup.c b/setup.c
index 89a0cef9231..edac3c27dc1 100644
--- a/setup.c
+++ b/setup.c
@@ -816,50 +816,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. This path may be relative against
+ * `dir` (i.e. *not* necessarily the cwd).
  */
-static const char *setup_git_directory_gently_1(int *nongit_ok)
+static enum discovery_result discover_git_directory(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;
@@ -867,15 +866,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: <path>")
 	 * - .git/
 	 * - ./ (bare)
@@ -887,62 +886,100 @@ 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) {
-			ret = setup_discovered_git_dir(gitdirenv,
-						       &cwd, offset,
-						       nongit_ok);
-			free(gitfile);
-			return ret;
+			strbuf_addstr(gitdir, gitdirenv);
+			return GIT_DIR_DISCOVERED;
 		}
-		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(dir->buf[offset_parent]));
-		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);
+		if (is_git_directory(dir->buf)) {
+			strbuf_addstr(gitdir, ".");
+			return GIT_DIR_BARE;
 		}
-		offset = offset_parent;
+
+		if (offset <= min_offset)
+			return GIT_DIR_HIT_CEILING;
+
+		while (--offset > ceil_offset && !is_dir_sep(dir->buf[offset]));
+		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)
 {
+	struct strbuf cwd = STRBUF_INIT, 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 (discover_git_directory(&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;
+			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 discover_git_directory() result");
+	}
+
 	if (prefix)
 		setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);
 	else
-- 
2.12.0.windows.1.3.g8a117c48243



  parent reply	other threads:[~2017-03-03  2:04 UTC|newest]

Thread overview: 123+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-12-08 15:35 [PATCH/RFC 0/7] Pie-in-the-sky attempt to fix the early config Johannes Schindelin
2016-12-08 15:36 ` [PATCH/RFC 1/7] Make read_early_config() reusable Johannes Schindelin
2016-12-08 15:36 ` [PATCH/RFC 2/7] read_early_config(): avoid .git/config hack when unneeded Johannes Schindelin
2016-12-08 15:36 ` [PATCH/RFC 3/7] Mark builtins that create .git/ directories Johannes Schindelin
2016-12-08 15:36 ` [PATCH/RFC 4/7] read_early_config(): special-case `init` and `clone` Johannes Schindelin
2016-12-08 15:36 ` [PATCH/RFC 5/7] read_early_config(): really discover .git/ Johannes Schindelin
2016-12-08 15:36 ` [PATCH/RFC 6/7] WIP read_config_early(): respect ceiling directories Johannes Schindelin
2016-12-08 15:36 ` [PATCH/RFC 7/7] WIP: read_early_config(): add tests Johannes Schindelin
2016-12-08 17:26 ` [PATCH/RFC 0/7] Pie-in-the-sky attempt to fix the early config Jeff King
2016-12-09 17:28   ` Johannes Schindelin
2016-12-09 17:55     ` Jeff King
2016-12-09 12:42 ` Duy Nguyen
2016-12-09 16:52   ` Johannes Schindelin
2017-03-03  2:03 ` [PATCH v2 0/9] Fix " Johannes Schindelin
2017-03-03  2:04   ` [PATCH v2 1/9] t7006: replace dubious test Johannes Schindelin
2017-03-03  3:36     ` Jeff King
2017-03-03 11:10       ` Johannes Schindelin
2017-03-03  2:04   ` [PATCH v2 2/9] setup_git_directory(): use is_dir_sep() helper Johannes Schindelin
2017-03-03  3:37     ` Jeff King
2017-03-03 11:16       ` Johannes Schindelin
2017-03-03 11:26         ` Jeff King
2017-03-03 15:35           ` Johannes Schindelin
2017-03-03  2:04   ` Johannes Schindelin [this message]
2017-03-03  4:24     ` [PATCH v2 3/9] setup_git_directory(): avoid changing global state during discovery Jeff King
2017-03-03 13:54       ` Johannes Schindelin
2017-03-03  2:04   ` [PATCH v2 4/9] Export the discover_git_directory() function Johannes Schindelin
2017-03-03  4:45     ` Jeff King
2017-03-03 14:49       ` Johannes Schindelin
2017-03-03  2:04   ` [PATCH v2 5/9] Make read_early_config() reusable Johannes Schindelin
2017-03-03  4:46     ` Jeff King
2017-03-03 14:11       ` Johannes Schindelin
2017-03-03  2:04   ` [PATCH v2 6/9] read_early_config(): special-case builtins that create a repository Johannes Schindelin
2017-03-03  4:51     ` Jeff King
2017-03-03 15:11       ` Johannes Schindelin
2017-03-03  2:04   ` [PATCH v2 7/9] read_early_config(): avoid .git/config hack when unneeded Johannes Schindelin
2017-03-03  4:51     ` Jeff King
2017-03-03  2:04   ` [PATCH v2 8/9] read_early_config(): really discover .git/ Johannes Schindelin
2017-03-03  5:06     ` Jeff King
2017-03-03 15:26       ` Johannes Schindelin
2017-03-03  2:04   ` [PATCH v2 9/9] Test read_early_config() Johannes Schindelin
2017-03-03  5:07     ` Jeff King
2017-03-03 15:04       ` Johannes Schindelin
2017-03-03  5:14   ` [PATCH v2 0/9] Fix the early config Jeff King
2017-03-03 15:31     ` Johannes Schindelin
2017-03-03 17:31   ` [PATCH v3 " Johannes Schindelin
2017-03-03 17:32     ` [PATCH v3 1/9] t7006: replace dubious test Johannes Schindelin
2017-03-03 17:32     ` [PATCH v3 2/9] setup_git_directory(): use is_dir_sep() helper Johannes Schindelin
2017-03-03 17:32     ` [PATCH v3 3/9] Prepare setup_discovered_git_directory() the root directory Johannes Schindelin
2017-03-03 17:32     ` [PATCH v3 4/9] setup_git_directory_1(): avoid changing global state Johannes Schindelin
2017-03-03 17:33     ` [PATCH v3 5/9] Export the discover_git_directory() function Johannes Schindelin
2017-03-03 17:33     ` [PATCH v3 6/9] Make read_early_config() reusable Johannes Schindelin
2017-03-03 17:33     ` [PATCH v3 7/9] read_early_config(): avoid .git/config hack when unneeded Johannes Schindelin
2017-03-03 17:33     ` [PATCH v3 8/9] read_early_config(): really discover .git/ Johannes Schindelin
2017-03-03 17:33     ` [PATCH v3 9/9] Test read_early_config() Johannes Schindelin
2017-03-03 21:35     ` [PATCH v3 0/9] Fix the early config Junio C Hamano
2017-03-07 11:55       ` Johannes Schindelin
2017-03-07 15:18       ` Johannes Schindelin
2017-03-04  7:39     ` Jeff King
2017-03-05  3:36       ` Junio C Hamano
2017-03-07 14:31       ` Johannes Schindelin
2017-03-08  7:30         ` Jeff King
2017-03-08 16:18           ` Johannes Schindelin
2017-03-08 16:29             ` Jeff King
2017-03-08 17:09           ` Junio C Hamano
2017-03-08 17:42             ` Jeff King
2017-03-08 22:43               ` Junio C Hamano
2017-03-09 11:51                 ` Johannes Schindelin
2017-03-09 12:16                   ` Jeff King
2017-03-10 16:39                     ` Junio C Hamano
2017-03-07 14:32     ` [PATCH v4 00/10] " Johannes Schindelin
2017-03-07 14:32       ` [PATCH v4 01/10] t7006: replace dubious test Johannes Schindelin
2017-03-07 14:32       ` [PATCH v4 02/10] setup_git_directory(): use is_dir_sep() helper Johannes Schindelin
2017-03-07 14:32       ` [PATCH v4 03/10] Prepare setup_discovered_git_directory() the root directory Johannes Schindelin
2017-03-07 14:32       ` [PATCH v4 04/10] setup_git_directory_1(): avoid changing global state Johannes Schindelin
2017-03-07 23:24         ` Junio C Hamano
2017-03-07 23:35         ` Brandon Williams
2017-03-08  0:57           ` Johannes Schindelin
2017-03-08  2:10             ` Brandon Williams
2017-03-07 14:33       ` [PATCH v4 05/10] Introduce the discover_git_directory() function Johannes Schindelin
2017-03-07 14:33       ` [PATCH v4 06/10] Make read_early_config() reusable Johannes Schindelin
2017-03-07 14:33       ` [PATCH v4 07/10] read_early_config(): avoid .git/config hack when unneeded Johannes Schindelin
2017-03-07 14:33       ` [PATCH v4 08/10] read_early_config(): really discover .git/ Johannes Schindelin
2017-03-07 14:33       ` [PATCH v4 09/10] Test read_early_config() Johannes Schindelin
2017-03-07 14:33       ` [PATCH v4 10/10] setup_git_directory_gently_1(): avoid die()ing Johannes Schindelin
2017-03-09 22:23       ` [PATCH v5 00/11] Fix the early config Johannes Schindelin
2017-03-09 22:23         ` [PATCH v5 01/11] t7006: replace dubious test Johannes Schindelin
2017-03-09 22:23         ` [PATCH v5 02/11] setup_git_directory(): use is_dir_sep() helper Johannes Schindelin
2017-03-09 22:23         ` [PATCH v5 03/11] Prepare setup_discovered_git_directory() the root directory Johannes Schindelin
2017-03-09 22:24         ` [PATCH v5 04/11] setup_git_directory_1(): avoid changing global state Johannes Schindelin
2017-03-10 19:34           ` Junio C Hamano
2017-03-09 22:24         ` [PATCH v5 05/11] Introduce the discover_git_directory() function Johannes Schindelin
2017-03-09 22:24         ` [PATCH v5 06/11] Make read_early_config() reusable Johannes Schindelin
2017-03-09 22:24         ` [PATCH v5 07/11] read_early_config(): avoid .git/config hack when unneeded Johannes Schindelin
2017-03-09 22:25         ` [PATCH v5 08/11] read_early_config(): really discover .git/ Johannes Schindelin
2017-03-09 22:25         ` [PATCH v5 09/11] Test read_early_config() Johannes Schindelin
2017-03-10 19:02           ` Junio C Hamano
2017-03-13 17:19             ` Johannes Schindelin
2017-03-13 17:32               ` Junio C Hamano
2017-03-09 22:25         ` [PATCH v5 10/11] setup_git_directory_gently_1(): avoid die()ing Johannes Schindelin
2017-03-10 18:58           ` Junio C Hamano
2017-03-13 19:38             ` Johannes Schindelin
2017-03-13 19:47               ` Junio C Hamano
2017-03-13 20:20                 ` Junio C Hamano
2017-03-13 21:46                   ` Johannes Schindelin
2017-03-13 23:31                     ` Junio C Hamano
2017-03-09 22:25         ` [PATCH v5 11/11] t1309: document cases where we would want early config not to die() Johannes Schindelin
2017-03-13 20:09         ` [PATCH v6 00/12] Fix the early config Johannes Schindelin
2017-03-13 20:09           ` [PATCH v6 01/12] t7006: replace dubious test Johannes Schindelin
2017-03-13 20:09           ` [PATCH v6 02/12] setup_git_directory(): use is_dir_sep() helper Johannes Schindelin
2017-03-13 20:09           ` [PATCH v6 03/12] Prepare setup_discovered_git_directory() the root directory Johannes Schindelin
2017-03-13 20:34             ` Junio C Hamano
2017-03-13 21:44               ` Johannes Schindelin
2017-03-13 20:10           ` [PATCH v6 04/12] setup_git_directory_1(): avoid changing global state Johannes Schindelin
2017-03-13 20:10           ` [PATCH v6 05/12] Introduce the discover_git_directory() function Johannes Schindelin
2017-03-13 20:11           ` [PATCH v6 06/12] Make read_early_config() reusable Johannes Schindelin
2017-03-13 20:11           ` [PATCH v6 07/12] read_early_config(): avoid .git/config hack when unneeded Johannes Schindelin
2017-03-13 20:11           ` [PATCH v6 08/12] read_early_config(): really discover .git/ Johannes Schindelin
2017-03-13 20:11           ` [PATCH v6 09/12] Add t1309 to test read_early_config() Johannes Schindelin
2017-03-13 20:11           ` [PATCH v6 10/12] setup_git_directory_gently_1(): avoid die()ing Johannes Schindelin
2017-03-13 20:11           ` [PATCH v6 11/12] t1309: document cases where we would want early config not to die() Johannes Schindelin
2017-03-13 20:12           ` [PATCH v6 12/12] setup.c: mention unresolved problems Johannes Schindelin
2017-03-13 22:31           ` [PATCH v6 00/12] Fix the early config Junio C Hamano
2017-03-14 18:01             ` Jeff King

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: http://vger.kernel.org/majordomo-info.html

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=49f66f2b723af38a58c17bc8ae413bcee69e2a2f.1488506615.git.johannes.schindelin@gmx.de \
    --to=johannes.schindelin@gmx.de \
    --cc=git@vger.kernel.org \
    --cc=gitster@pobox.com \
    --cc=pclouds@gmail.com \
    --cc=peff@peff.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).