Commits

Anonymous committed f3bb8b4 Merge

Merge branch 'nd/setup'

* nd/setup: (47 commits)
setup_work_tree: adjust relative $GIT_WORK_TREE after moving cwd
git.txt: correct where --work-tree path is relative to
Revert "Documentation: always respect core.worktree if set"
t0001: test git init when run via an alias
Remove all logic from get_git_work_tree()
setup: rework setup_explicit_git_dir()
setup: clean up setup_discovered_git_dir()
t1020-subdirectory: test alias expansion in a subdirectory
setup: clean up setup_bare_git_dir()
setup: limit get_git_work_tree()'s to explicit setup case only
Use git_config_early() instead of git_config() during repo setup
Add git_config_early()
git-rev-parse.txt: clarify --git-dir
t1510: setup case #31
t1510: setup case #30
t1510: setup case #29
t1510: setup case #28
t1510: setup case #27
t1510: setup case #26
t1510: setup case #25
...

Comments (0)

Files changed (17)

 /test-sha1
 /test-sigchain
 /test-string-pool
+/test-subprocess
 /test-svn-fe
 /test-treap
 /common-cmds.h

Documentation/config.txt

 = true).
 
 core.worktree::
-	Set the path to the root of the work tree.
+	Set the path to the working tree.  The value will not be
+	used in combination with repositories found automatically in
+	a .git directory (i.e. $GIT_DIR is not set).
 	This can be overridden by the GIT_WORK_TREE environment
 	variable and the '--work-tree' command line option. It can be
-	an absolute path or a relative path to the .git directory,
-	either specified by --git-dir or GIT_DIR, or automatically
-	discovered.
-	If --git-dir or GIT_DIR are specified but none of
+	an absolute path or relative path to the directory specified by
+	--git-dir or GIT_DIR.
+	Note: If --git-dir or GIT_DIR are specified but none of
 	--work-tree, GIT_WORK_TREE and core.worktree is specified,
-	the current working directory is regarded as the root of the
-	work tree.
-+
-Note that this variable is honored even when set in a configuration
-file in a ".git" subdirectory of a directory, and its value differs
-from the latter directory (e.g. "/path/to/.git/config" has
-core.worktree set to "/different/path"), which is most likely a
-misconfiguration.  Running git commands in "/path/to" directory will
-still use "/different/path" as the root of the work tree and can cause
-great confusion to the users.
+	the current working directory is regarded as the top directory
+	of your working tree.
 
 core.logAllRefUpdates::
 	Enable the reflog. Updates to a ref <ref> is logged to the file

Documentation/git-rev-parse.txt

 	directory (typically a sequence of "../", or an empty string).
 
 --git-dir::
-	Show `$GIT_DIR` if defined else show the path to the .git directory.
+	Show `$GIT_DIR` if defined. Otherwise show the path to
+	the .git directory, relative to the current directory.
++
+If `$GIT_DIR` is not defined and the current directory
+is not detected to lie in a git repository or work tree
+print a message to stderr and exit with nonzero status.
 
 --is-inside-git-dir::
 	When the current working directory is below the repository

Documentation/git.txt

 	This can also be controlled by setting the GIT_WORK_TREE
 	environment variable and the core.worktree configuration
 	variable. It can be an absolute path or relative path to
-	the directory specified by --git-dir or GIT_DIR.
+	current working directory.
 	Note: If --git-dir or GIT_DIR are specified but none of
 	--work-tree, GIT_WORK_TREE and core.worktree is specified,
 	the current working directory is regarded as the top directory
 TEST_PROGRAMS_NEED_X += test-sha1
 TEST_PROGRAMS_NEED_X += test-sigchain
 TEST_PROGRAMS_NEED_X += test-string-pool
+TEST_PROGRAMS_NEED_X += test-subprocess
 TEST_PROGRAMS_NEED_X += test-svn-fe
 TEST_PROGRAMS_NEED_X += test-treap
 TEST_PROGRAMS_NEED_X += test-index-version

builtin/init-db.c

 int cmd_init_db(int argc, const char **argv, const char *prefix)
 {
 	const char *git_dir;
+	const char *work_tree;
 	const char *template_dir = NULL;
 	unsigned int flags = 0;
 	const struct option init_db_options[] = {
 	 * without --bare.  Catch the error early.
 	 */
 	git_dir = getenv(GIT_DIR_ENVIRONMENT);
-	if ((!git_dir || is_bare_repository_cfg == 1)
-	    && getenv(GIT_WORK_TREE_ENVIRONMENT))
+	work_tree = getenv(GIT_WORK_TREE_ENVIRONMENT);
+	if ((!git_dir || is_bare_repository_cfg == 1) && work_tree)
 		die("%s (or --work-tree=<directory>) not allowed without "
 		    "specifying %s (or --git-dir=<directory>)",
 		    GIT_WORK_TREE_ENVIRONMENT,
 			if (!getcwd(git_work_tree_cfg, PATH_MAX))
 				die_errno ("Cannot access current working directory");
 		}
+		if (work_tree)
+			set_git_work_tree(make_absolute_path(work_tree));
+		else
+			set_git_work_tree(git_work_tree_cfg);
 		if (access(get_git_work_tree(), X_OK))
 			die_errno ("Cannot access work tree '%s'",
 				   get_git_work_tree());
 	}
+	else {
+		if (work_tree)
+			set_git_work_tree(make_absolute_path(work_tree));
+	}
 
 	set_git_dir(make_absolute_path(git_dir));
 
 extern int git_config_parse_environment(void);
 extern int git_config_from_parameters(config_fn_t fn, void *data);
 extern int git_config(config_fn_t fn, void *);
+extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
 extern int git_parse_ulong(const char *, unsigned long *);
 extern int git_config_int(const char *, const char *);
 extern unsigned long git_config_ulong(const char *, const char *);
 extern void trace_printf(const char *format, ...);
 __attribute__((format (printf, 2, 3)))
 extern void trace_argv_printf(const char **argv, const char *format, ...);
+extern void trace_repo_setup(const char *prefix);
 
 /* convert.c */
 /* returns 1 if *dst was used */
 	return 0;
 }
 
-int git_config(config_fn_t fn, void *data)
+int git_config_early(config_fn_t fn, void *data, const char *repo_config)
 {
 	int ret = 0, found = 0;
-	char *repo_config = NULL;
 	const char *home = NULL;
 
 	/* Setting $GIT_CONFIG makes git read _only_ the given config file. */
 		free(user_config);
 	}
 
-	repo_config = git_pathdup("config");
-	if (!access(repo_config, R_OK)) {
+	if (repo_config && !access(repo_config, R_OK)) {
 		ret += git_config_from_file(fn, repo_config, data);
 		found += 1;
 	}
-	free(repo_config);
 
 	ret += git_config_from_parameters(fn, data);
 	if (config_parameters)
 	return ret == 0 ? found : ret;
 }
 
+int git_config(config_fn_t fn, void *data)
+{
+	char *repo_config = NULL;
+	int ret;
+
+	repo_config = git_pathdup("config");
+	ret = git_config_early(fn, data, repo_config);
+	if (repo_config)
+		free(repo_config);
+	return ret;
+}
+
 /*
  * Find all the stuff for git_config_set() below.
  */
  */
 void set_git_work_tree(const char *new_work_tree)
 {
-	if (is_bare_repository_cfg >= 0)
-		die("cannot set work tree after initialization");
+	if (git_work_tree_initialized) {
+		new_work_tree = make_absolute_path(new_work_tree);
+		if (strcmp(new_work_tree, work_tree))
+			die("internal error: work tree has already been set\n"
+			    "Current worktree: %s\nNew worktree: %s",
+			    work_tree, new_work_tree);
+		return;
+	}
 	git_work_tree_initialized = 1;
-	free(work_tree);
 	work_tree = xstrdup(make_absolute_path(new_work_tree));
-	is_bare_repository_cfg = 0;
 }
 
 const char *get_git_work_tree(void)
 {
-	if (!git_work_tree_initialized) {
-		work_tree = getenv(GIT_WORK_TREE_ENVIRONMENT);
-		/* core.bare = true overrides implicit and config work tree */
-		if (!work_tree && is_bare_repository_cfg < 1) {
-			work_tree = git_work_tree_cfg;
-			/* make_absolute_path also normalizes the path */
-			if (work_tree && !is_absolute_path(work_tree))
-				work_tree = xstrdup(make_absolute_path(git_path("%s", work_tree)));
-		} else if (work_tree)
-			work_tree = xstrdup(make_absolute_path(work_tree));
-		git_work_tree_initialized = 1;
-		if (work_tree)
-			is_bare_repository_cfg = 0;
-	}
 	return work_tree;
 }
 
 			use_pager = check_pager_config(p->cmd);
 		if (use_pager == -1 && p->option & USE_PAGER)
 			use_pager = 1;
+
+		if ((p->option & (RUN_SETUP | RUN_SETUP_GENTLY)) &&
+		    startup_info->have_repository) /* get_git_dir() may set up repo, avoid that */
+			trace_repo_setup(prefix);
 	}
 	commit_pager_choice();
 
 	return inside_work_tree;
 }
 
-/*
- * set_work_tree() is only ever called if you set GIT_DIR explicitly.
- * The old behaviour (which we retain here) is to set the work tree root
- * to the cwd, unless overridden by the config, the command line, or
- * GIT_WORK_TREE.
- */
-static const char *set_work_tree(const char *dir)
-{
-	char buffer[PATH_MAX + 1];
-
-	if (!getcwd(buffer, sizeof(buffer)))
-		die ("Could not get the current working directory");
-	git_work_tree_cfg = xstrdup(buffer);
-	inside_work_tree = 1;
-
-	return NULL;
-}
-
 void setup_work_tree(void)
 {
 	const char *work_tree, *git_dir;
 		git_dir = make_absolute_path(git_dir);
 	if (!work_tree || chdir(work_tree))
 		die("This operation must be run in a work tree");
+
+	/*
+	 * Make sure subsequent git processes find correct worktree
+	 * if $GIT_WORK_TREE is set relative
+	 */
+	if (getenv(GIT_WORK_TREE_ENVIRONMENT))
+		setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1);
+
 	set_git_dir(make_relative_path(git_dir, work_tree));
 	initialized = 1;
 }
 
-static int check_repository_format_gently(int *nongit_ok)
+static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 {
-	git_config(check_repository_format_version, NULL);
+	char repo_config[PATH_MAX+1];
+
+	/*
+	 * git_config() can't be used here because it calls git_pathdup()
+	 * to get $GIT_CONFIG/config. That call will make setup_git_env()
+	 * set git_dir to ".git".
+	 *
+	 * We are in gitdir setup, no git dir has been found useable yet.
+	 * Use a gentler version of git_config() to check if this repo
+	 * is a good one.
+	 */
+	snprintf(repo_config, PATH_MAX, "%s/config", gitdir);
+	git_config_early(check_repository_format_version, NULL, repo_config);
 	if (GIT_REPO_VERSION < repository_format_version) {
 		if (!nongit_ok)
 			die ("Expected git repo version <= %d, found %d",
 }
 
 static const char *setup_explicit_git_dir(const char *gitdirenv,
-				const char *work_tree_env, int *nongit_ok)
+					  char *cwd, int len,
+					  int *nongit_ok)
 {
-	static char buffer[1024 + 1];
-	const char *retval;
+	const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
+	const char *worktree;
+	char *gitfile;
 
 	if (PATH_MAX - 40 < strlen(gitdirenv))
 		die("'$%s' too big", GIT_DIR_ENVIRONMENT);
+
+	gitfile = (char*)read_gitfile_gently(gitdirenv);
+	if (gitfile) {
+		gitfile = xstrdup(gitfile);
+		gitdirenv = gitfile;
+	}
+
 	if (!is_git_directory(gitdirenv)) {
 		if (nongit_ok) {
 			*nongit_ok = 1;
+			free(gitfile);
 			return NULL;
 		}
 		die("Not a git repository: '%s'", gitdirenv);
 	}
-	if (!work_tree_env) {
-		retval = set_work_tree(gitdirenv);
-		/* config may override worktree */
-		if (check_repository_format_gently(nongit_ok))
-			return NULL;
-		return retval;
+
+	if (check_repository_format_gently(gitdirenv, nongit_ok)) {
+		free(gitfile);
+		return NULL;
 	}
-	if (check_repository_format_gently(nongit_ok))
+
+	/* #3, #7, #11, #15, #19, #23, #27, #31 (see t1510) */
+	if (work_tree_env)
+		set_git_work_tree(work_tree_env);
+	else if (is_bare_repository_cfg > 0) {
+		if (git_work_tree_cfg) /* #22.2, #30 */
+			die("core.bare and core.worktree do not make sense");
+
+		/* #18, #26 */
+		set_git_dir(gitdirenv);
+		free(gitfile);
 		return NULL;
-	retval = get_relative_cwd(buffer, sizeof(buffer) - 1,
-			get_git_work_tree());
-	if (!retval || !*retval)
+	}
+	else if (git_work_tree_cfg) { /* #6, #14 */
+		if (is_absolute_path(git_work_tree_cfg))
+			set_git_work_tree(git_work_tree_cfg);
+		else {
+			char core_worktree[PATH_MAX];
+			if (chdir(gitdirenv))
+				die_errno("Could not chdir to '%s'", gitdirenv);
+			if (chdir(git_work_tree_cfg))
+				die_errno("Could not chdir to '%s'", git_work_tree_cfg);
+			if (!getcwd(core_worktree, PATH_MAX))
+				die_errno("Could not get directory '%s'", git_work_tree_cfg);
+			if (chdir(cwd))
+				die_errno("Could not come back to cwd");
+			set_git_work_tree(core_worktree);
+		}
+	}
+	else /* #2, #10 */
+		set_git_work_tree(".");
+
+	/* set_git_work_tree() must have been called by now */
+	worktree = get_git_work_tree();
+
+	/* both get_git_work_tree() and cwd are already normalized */
+	if (!strcmp(cwd, worktree)) { /* cwd == worktree */
+		set_git_dir(gitdirenv);
+		free(gitfile);
 		return NULL;
-	set_git_dir(make_absolute_path(gitdirenv));
-	if (chdir(work_tree_env) < 0)
-		die_errno ("Could not chdir to '%s'", work_tree_env);
-	strcat(buffer, "/");
-	return retval;
-}
+	}
 
-static int cwd_contains_git_dir(const char **gitfile_dirp)
-{
-	const char *gitfile_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT);
-	*gitfile_dirp = gitfile_dir;
-	if (gitfile_dir) {
-		if (set_git_dir(gitfile_dir))
-			die("Repository setup failed");
-		return 1;
+	if (!prefixcmp(cwd, worktree) &&
+	    cwd[strlen(worktree)] == '/') { /* cwd inside worktree */
+		set_git_dir(make_absolute_path(gitdirenv));
+		if (chdir(worktree))
+			die_errno("Could not chdir to '%s'", worktree);
+		cwd[len++] = '/';
+		cwd[len] = '\0';
+		free(gitfile);
+		return cwd + strlen(worktree) + 1;
 	}
-	return is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT);
+
+	/* cwd outside worktree */
+	set_git_dir(gitdirenv);
+	free(gitfile);
+	return NULL;
 }
 
-static const char *setup_discovered_git_dir(const char *work_tree_env,
-		int offset, int len, char *cwd, int *nongit_ok)
+static const char *setup_discovered_git_dir(const char *gitdir,
+					    char *cwd, int offset, int len,
+					    int *nongit_ok)
 {
-	int root_len;
+	if (check_repository_format_gently(gitdir, nongit_ok))
+		return NULL;
 
-	inside_git_dir = 0;
-	if (!work_tree_env)
-		inside_work_tree = 1;
-	root_len = offset_1st_component(cwd);
-	git_work_tree_cfg = xstrndup(cwd, offset > root_len ? offset : root_len);
-	if (check_repository_format_gently(nongit_ok))
+	/* #16.2, #17.2, #20.2, #21.2, #24, #25, #28, #29 (see t1510) */
+	if (is_bare_repository_cfg > 0) {
+		set_git_dir(offset == len ? gitdir : make_absolute_path(gitdir));
+		if (chdir(cwd))
+			die_errno("Could not come back to cwd");
 		return NULL;
+	}
+
+	/* #0, #1, #5, #8, #9, #12, #13 */
+	set_git_work_tree(".");
+	if (strcmp(gitdir, DEFAULT_GIT_DIR_ENVIRONMENT))
+		set_git_dir(gitdir);
+	inside_git_dir = 0;
+	inside_work_tree = 1;
 	if (offset == len)
 		return NULL;
 
 	return cwd + offset;
 }
 
-static const char *setup_bare_git_dir(const char *work_tree_env,
-		int offset, int len, char *cwd, int *nongit_ok)
+/* #16.1, #17.1, #20.1, #21.1, #22.1 (see t1510) */
+static const char *setup_bare_git_dir(char *cwd, int offset, int len, int *nongit_ok)
 {
 	int root_len;
 
+	if (check_repository_format_gently(".", nongit_ok))
+		return NULL;
+
 	inside_git_dir = 1;
-	if (!work_tree_env)
-		inside_work_tree = 0;
+	inside_work_tree = 0;
 	if (offset != len) {
 		if (chdir(cwd))
 			die_errno("Cannot come back to cwd");
 		root_len = offset_1st_component(cwd);
 		cwd[offset > root_len ? offset : root_len] = '\0';
 		set_git_dir(cwd);
-	} else
+	}
+	else
 		set_git_dir(".");
-	check_repository_format_gently(nongit_ok);
 	return NULL;
 }
 
  */
 static const char *setup_git_directory_gently_1(int *nongit_ok)
 {
-	const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
 	const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);
 	static char cwd[PATH_MAX+1];
-	const char *gitdirenv;
-	const char *gitfile_dir;
+	const char *gitdirenv, *ret;
+	char *gitfile;
 	int len, offset, ceil_offset;
 	dev_t current_device = 0;
 	int one_filesystem = 1;
 	if (nongit_ok)
 		*nongit_ok = 0;
 
+	if (!getcwd(cwd, sizeof(cwd)-1))
+		die_errno("Unable to read current working directory");
+	offset = len = strlen(cwd);
+
 	/*
 	 * If GIT_DIR is set explicitly, we're not going
 	 * to do any discovery, but we still do repository
 	 */
 	gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
 	if (gitdirenv)
-		return setup_explicit_git_dir(gitdirenv, work_tree_env, nongit_ok);
-
-	if (!getcwd(cwd, sizeof(cwd)-1))
-		die_errno("Unable to read current working directory");
+		return setup_explicit_git_dir(gitdirenv, cwd, len, nongit_ok);
 
 	ceil_offset = longest_ancestor_length(cwd, env_ceiling_dirs);
 	if (ceil_offset < 0 && has_dos_drive_prefix(cwd))
 	 * - ../../.git/
 	 *   etc.
 	 */
-	offset = len = strlen(cwd);
 	one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0);
 	if (one_filesystem)
 		current_device = get_device_or_die(".", NULL);
 	for (;;) {
-		if (cwd_contains_git_dir(&gitfile_dir))
-			return setup_discovered_git_dir(work_tree_env, offset,
-							len, cwd, nongit_ok);
+		gitfile = (char*)read_gitfile_gently(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, len,
+						       nongit_ok);
+			free(gitfile);
+			return ret;
+		}
+		free(gitfile);
+
 		if (is_git_directory("."))
-			return setup_bare_git_dir(work_tree_env, offset,
-							len, cwd, nongit_ok);
+			return setup_bare_git_dir(cwd, offset, len, nongit_ok);
+
 		while (--offset > ceil_offset && cwd[offset] != '/');
 		if (offset <= ceil_offset)
 			return setup_nongit(cwd, nongit_ok);
 
 int check_repository_format(void)
 {
-	return check_repository_format_gently(NULL);
+	return check_repository_format_gently(get_git_dir(), NULL);
 }
 
 /*
  */
 const char *setup_git_directory(void)
 {
-	const char *retval = setup_git_directory_gently(NULL);
-
-	/* If the work tree is not the default one, recompute prefix */
-	if (inside_work_tree < 0) {
-		static char buffer[PATH_MAX + 1];
-		char *rel;
-		if (retval && chdir(retval))
-			die_errno ("Could not jump back into original cwd");
-		rel = get_relative_cwd(buffer, PATH_MAX, get_git_work_tree());
-		if (rel && *rel && chdir(get_git_work_tree()))
-			die_errno ("Could not jump to working directory");
-		return rel && *rel ? strcat(rel, "/") : NULL;
-	}
-
-	return retval;
+	return setup_git_directory_gently(NULL);
 }
 	check_config plain/.git false unset
 '
 
+test_expect_success 'plain nested in bare' '
+	(
+		unset GIT_DIR GIT_WORK_TREE &&
+		git init --bare bare-ancestor.git &&
+		cd bare-ancestor.git &&
+		mkdir plain-nested &&
+		cd plain-nested &&
+		git init
+	) &&
+	check_config bare-ancestor.git/plain-nested/.git false unset
+'
+
+test_expect_success 'plain through aliased command, outside any git repo' '
+	(
+		unset GIT_DIR GIT_WORK_TREE GIT_CONFIG_NOGLOBAL &&
+		HOME=$(pwd)/alias-config &&
+		export HOME &&
+		mkdir alias-config &&
+		echo "[alias] aliasedinit = init" >alias-config/.gitconfig &&
+
+		GIT_CEILING_DIRECTORIES=$(pwd) &&
+		export GIT_CEILING_DIRECTORIES &&
+
+		mkdir plain-aliased &&
+		cd plain-aliased &&
+		git aliasedinit
+	) &&
+	check_config plain-aliased/.git false unset
+'
+
+test_expect_failure 'plain nested through aliased command' '
+	(
+		unset GIT_DIR GIT_WORK_TREE &&
+		git init plain-ancestor-aliased &&
+		cd plain-ancestor-aliased &&
+		echo "[alias] aliasedinit = init" >>.git/config &&
+		mkdir plain-nested &&
+		cd plain-nested &&
+		git aliasedinit
+	) &&
+	check_config plain-ancestor-aliased/plain-nested/.git false unset
+'
+
+test_expect_failure 'plain nested in bare through aliased command' '
+	(
+		unset GIT_DIR GIT_WORK_TREE &&
+		git init --bare bare-ancestor-aliased.git &&
+		cd bare-ancestor-aliased.git &&
+		echo "[alias] aliasedinit = init" >>config &&
+		mkdir plain-nested &&
+		cd plain-nested &&
+		git aliasedinit
+	) &&
+	check_config bare-ancestor-aliased.git/plain-nested/.git false unset
+'
+
 test_expect_success 'plain with GIT_WORK_TREE' '
 	if (
 		sane_unset GIT_DIR &&

t/t1020-subdirectory.sh

 	)
 '
 
+test_expect_success 'alias expansion' '
+	(
+		git config alias.ss status &&
+		cd dir &&
+		git status &&
+		git ss
+	)
+'
 test_expect_success 'no file/rev ambiguity check inside .git' '
 	git commit -a -m 1 &&
 	(

t/t1501-worktree.sh

 	git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file
 '
 
+test_expect_success 'relative $GIT_WORK_TREE and git subprocesses' '
+	GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work \
+	test-subprocess --setup-work-tree rev-parse --show-toplevel >actual &&
+	echo "$TRASH_DIRECTORY/repo.git/work" >expected &&
+	test_cmp expected actual
+'
+
 test_done

t/t1510-repo-setup.sh

+#!/bin/sh
+
+test_description='Tests of cwd/prefix/worktree/gitdir setup in all cases'
+
+. ./test-lib.sh
+
+#
+# A few rules for repo setup:
+#
+# 1. GIT_DIR is relative to user's cwd. --git-dir is equivalent to
+#    GIT_DIR.
+#
+# 2. .git file is relative to parent directory. .git file is basically
+#    symlink in disguise. The directory where .git file points to will
+#    become new git_dir.
+#
+# 3. core.worktree is relative to git_dir.
+#
+# 4. GIT_WORK_TREE is relative to user's cwd. --work-tree is
+#    equivalent to GIT_WORK_TREE.
+#
+# 5. GIT_WORK_TREE/core.worktree is only effective if GIT_DIR is set
+#    Uneffective worktree settings should be warned.
+#
+# 6. Effective GIT_WORK_TREE overrides core.worktree and core.bare
+#
+# 7. Effective core.worktree conflicts with core.bare
+#
+# 8. If GIT_DIR is set but neither worktree nor bare setting is given,
+#    original cwd becomes worktree.
+#
+# 9. If .git discovery is done inside a repo, the repo becomes a bare
+#    repo. .git discovery is performed if GIT_DIR is not set.
+#
+# 10. If no worktree is available, cwd remains unchanged, prefix is
+#     NULL.
+#
+# 11. When user's cwd is outside worktree, cwd remains unchanged,
+#     prefix is NULL.
+#
+
+test_repo() {
+	(
+	cd "$1" &&
+	if test -n "$2"; then GIT_DIR="$2" && export GIT_DIR; fi &&
+	if test -n "$3"; then GIT_WORK_TREE="$3" && export GIT_WORK_TREE; fi &&
+	rm -f trace &&
+	GIT_TRACE="`pwd`/trace" git symbolic-ref HEAD >/dev/null &&
+	grep '^setup: ' trace >result &&
+	test_cmp expected result
+	)
+}
+
+# Bit 0 = GIT_WORK_TREE
+# Bit 1 = GIT_DIR
+# Bit 2 = core.worktree
+# Bit 3 = .git is a file
+# Bit 4 = bare repo
+# Case# = encoding of the above 5 bits
+
+#
+# Case #0
+#
+############################################################
+#
+# Input:
+#
+#  - GIT_WORK_TREE is not set
+#  - GIT_DIR is not set
+#  - core.worktree is not set
+#  - .git is a directory
+#  - core.bare is not set, cwd is outside .git
+#
+# Output:
+#
+#  - worktree is .git's parent directory
+#  - cwd is at worktree root dir
+#  - prefix is calculated
+#  - git_dir is set to ".git"
+#  - cwd can't be outside worktree
+
+test_expect_success '#0: setup' '
+	unset GIT_DIR GIT_WORK_TREE &&
+	mkdir 0 0/sub &&
+	cd 0 && git init && cd ..
+'
+
+test_expect_success '#0: at root' '
+	cat >0/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/0
+setup: cwd: $TRASH_DIRECTORY/0
+setup: prefix: (null)
+EOF
+	test_repo 0
+'
+
+test_expect_success '#0: in subdir' '
+	cat >0/sub/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/0
+setup: cwd: $TRASH_DIRECTORY/0
+setup: prefix: sub/
+EOF
+	test_repo 0/sub
+'
+
+#
+# case #1
+#
+############################################################
+#
+# Input:
+#
+#  - GIT_WORK_TREE is set
+#  - GIT_DIR is not set
+#  - core.worktree is not set
+#  - .git is a directory
+#  - core.bare is not set, cwd is outside .git
+#
+# Output:
+#
+# GIT_WORK_TREE is ignored -> #0
+
+test_expect_success '#1: setup' '
+	unset GIT_DIR GIT_WORK_TREE &&
+	mkdir 1 1/sub 1.wt 1.wt/sub 1/wt 1/wt/sub &&
+	cd 1 &&
+	git init &&
+	GIT_WORK_TREE=non-existent &&
+	export GIT_WORK_TREE &&
+	cd ..
+'
+
+test_expect_success '#1: at root' '
+	cat >1/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/1
+setup: cwd: $TRASH_DIRECTORY/1
+setup: prefix: (null)
+EOF
+	test_repo 1
+'
+
+test_expect_success '#1: in subdir' '
+	cat >1/sub/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/1
+setup: cwd: $TRASH_DIRECTORY/1
+setup: prefix: sub/
+EOF
+	test_repo 1/sub
+'
+
+#
+# case #2
+#
+############################################################
+#
+# Input:
+#
+#  - GIT_WORK_TREE is not set
+#  - GIT_DIR is set
+#  - core.worktree is not set
+#  - .git is a directory
+#  - core.bare is not set, cwd is outside .git
+#
+# Output:
+#
+#  - worktree is at original cwd
+#  - cwd is unchanged
+#  - prefix is NULL
+#  - git_dir is set to $GIT_DIR
+#  - cwd can't be outside worktree
+
+test_expect_success '#2: setup' '
+	unset GIT_DIR GIT_WORK_TREE &&
+	mkdir 2 2/sub &&
+	cd 2 && git init && cd ..
+'
+
+test_expect_success '#2: at root' '
+	cat >2/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/2/.git
+setup: worktree: $TRASH_DIRECTORY/2
+setup: cwd: $TRASH_DIRECTORY/2
+setup: prefix: (null)
+EOF
+	test_repo 2 "$TRASH_DIRECTORY/2/.git"
+'
+
+test_expect_success '#2: in subdir' '
+	cat >2/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/2/.git
+setup: worktree: $TRASH_DIRECTORY/2/sub
+setup: cwd: $TRASH_DIRECTORY/2/sub
+setup: prefix: (null)
+EOF
+	test_repo 2/sub "$TRASH_DIRECTORY/2/.git"
+'
+
+test_expect_success '#2: relative GIT_DIR at root' '
+	cat >2/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/2
+setup: cwd: $TRASH_DIRECTORY/2
+setup: prefix: (null)
+EOF
+	test_repo 2 .git
+'
+
+test_expect_success '#2: relative GIT_DIR in subdir' '
+	cat >2/sub/expected <<EOF &&
+setup: git_dir: ../.git
+setup: worktree: $TRASH_DIRECTORY/2/sub
+setup: cwd: $TRASH_DIRECTORY/2/sub
+setup: prefix: (null)
+EOF
+	test_repo 2/sub ../.git
+'
+
+#
+# case #3
+#
+############################################################
+#
+# Input:
+#
+#  - GIT_WORK_TREE is set
+#  - GIT_DIR is set
+#  - core.worktree is not set
+#  - .git is a directory
+#  - core.bare is not set, cwd is outside .git
+#
+# Output:
+#
+#  - worktree is set to $GIT_WORK_TREE
+#  - cwd is at worktree root
+#  - prefix is calculated
+#  - git_dir is set to $GIT_DIR
+#  - cwd can be outside worktree
+
+test_expect_success '#3: setup' '
+	unset GIT_DIR GIT_WORK_TREE &&
+	mkdir 3 3/sub 3/sub/sub 3.wt 3.wt/sub 3/wt 3/wt/sub &&
+	cd 3 && git init && cd ..
+'
+
+test_expect_success '#3: GIT_DIR(rel), GIT_WORK_TREE=root at root' '
+	cat >3/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/3
+setup: cwd: $TRASH_DIRECTORY/3
+setup: prefix: (null)
+EOF
+	test_repo 3 .git "$TRASH_DIRECTORY/3"
+'
+
+test_expect_success '#3: GIT_DIR(rel), GIT_WORK_TREE=root(rel) at root' '
+	cat >3/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/3
+setup: cwd: $TRASH_DIRECTORY/3
+setup: prefix: (null)
+EOF
+	test_repo 3 .git .
+'
+
+test_expect_success '#3: GIT_DIR, GIT_WORK_TREE=root at root' '
+	cat >3/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/3/.git
+setup: worktree: $TRASH_DIRECTORY/3
+setup: cwd: $TRASH_DIRECTORY/3
+setup: prefix: (null)
+EOF
+	test_repo 3 "$TRASH_DIRECTORY/3/.git" "$TRASH_DIRECTORY/3"
+'
+
+test_expect_success '#3: GIT_DIR, GIT_WORK_TREE=root(rel) at root' '
+	cat >3/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/3/.git
+setup: worktree: $TRASH_DIRECTORY/3
+setup: cwd: $TRASH_DIRECTORY/3
+setup: prefix: (null)
+EOF
+	test_repo 3 "$TRASH_DIRECTORY/3/.git" .
+'
+
+test_expect_success '#3: GIT_DIR(rel), GIT_WORKTREE=root in subdir' '
+	cat >3/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/3/.git
+setup: worktree: $TRASH_DIRECTORY/3
+setup: cwd: $TRASH_DIRECTORY/3
+setup: prefix: sub/sub/
+EOF
+	test_repo 3/sub/sub ../../.git "$TRASH_DIRECTORY/3"
+'
+
+test_expect_success '#3: GIT_DIR(rel), GIT_WORKTREE=root(rel) in subdir' '
+	cat >3/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/3/.git
+setup: worktree: $TRASH_DIRECTORY/3
+setup: cwd: $TRASH_DIRECTORY/3
+setup: prefix: sub/sub/
+EOF
+	test_repo 3/sub/sub ../../.git ../..
+'
+
+test_expect_success '#3: GIT_DIR, GIT_WORKTREE=root in subdir' '
+	cat >3/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/3/.git
+setup: worktree: $TRASH_DIRECTORY/3
+setup: cwd: $TRASH_DIRECTORY/3
+setup: prefix: sub/
+EOF
+	test_repo 3/sub "$TRASH_DIRECTORY/3/.git" "$TRASH_DIRECTORY/3"
+'
+
+test_expect_success '#3: GIT_DIR, GIT_WORKTREE=root(rel) in subdir' '
+	cat >3/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/3/.git
+setup: worktree: $TRASH_DIRECTORY/3
+setup: cwd: $TRASH_DIRECTORY/3
+setup: prefix: sub/sub/
+EOF
+	test_repo 3/sub/sub "$TRASH_DIRECTORY/3/.git" ../..
+'
+
+test_expect_success '#3: GIT_DIR(rel), GIT_WORK_TREE=wt at root' '
+	cat >3/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/3/wt
+setup: cwd: $TRASH_DIRECTORY/3
+setup: prefix: (null)
+EOF
+	test_repo 3 .git "$TRASH_DIRECTORY/3/wt"
+'
+
+test_expect_success '#3: GIT_DIR(rel), GIT_WORK_TREE=wt(rel) at root' '
+	cat >3/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/3/wt
+setup: cwd: $TRASH_DIRECTORY/3
+setup: prefix: (null)
+EOF
+	test_repo 3 .git wt
+'
+
+test_expect_success '#3: GIT_DIR, GIT_WORK_TREE=wt(rel) at root' '
+	cat >3/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/3/.git
+setup: worktree: $TRASH_DIRECTORY/3/wt
+setup: cwd: $TRASH_DIRECTORY/3
+setup: prefix: (null)
+EOF
+	test_repo 3 "$TRASH_DIRECTORY/3/.git" wt
+'
+
+test_expect_success '#3: GIT_DIR, GIT_WORK_TREE=wt at root' '
+	cat >3/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/3/.git
+setup: worktree: $TRASH_DIRECTORY/3/wt
+setup: cwd: $TRASH_DIRECTORY/3
+setup: prefix: (null)
+EOF
+	test_repo 3 "$TRASH_DIRECTORY/3/.git" "$TRASH_DIRECTORY/3/wt"
+'
+
+test_expect_success '#3: GIT_DIR(rel), GIT_WORK_TREE=wt in subdir' '
+	cat >3/sub/sub/expected <<EOF &&
+setup: git_dir: ../../.git
+setup: worktree: $TRASH_DIRECTORY/3/wt
+setup: cwd: $TRASH_DIRECTORY/3/sub/sub
+setup: prefix: (null)
+EOF
+	test_repo 3/sub/sub ../../.git "$TRASH_DIRECTORY/3/wt"
+'
+
+test_expect_success '#3: GIT_DIR(rel), GIT_WORK_TREE=wt(rel) in subdir' '
+	cat >3/sub/sub/expected <<EOF &&
+setup: git_dir: ../../.git
+setup: worktree: $TRASH_DIRECTORY/3/wt
+setup: cwd: $TRASH_DIRECTORY/3/sub/sub
+setup: prefix: (null)
+EOF
+	test_repo 3/sub/sub ../../.git ../../wt
+'
+
+test_expect_success '#3: GIT_DIR, GIT_WORK_TREE=wt(rel) in subdir' '
+	cat >3/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/3/.git
+setup: worktree: $TRASH_DIRECTORY/3/wt
+setup: cwd: $TRASH_DIRECTORY/3/sub/sub
+setup: prefix: (null)
+EOF
+	test_repo 3/sub/sub "$TRASH_DIRECTORY/3/.git" ../../wt
+'
+
+test_expect_success '#3: GIT_DIR, GIT_WORK_TREE=wt in subdir' '
+	cat >3/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/3/.git
+setup: worktree: $TRASH_DIRECTORY/3/wt
+setup: cwd: $TRASH_DIRECTORY/3/sub/sub
+setup: prefix: (null)
+EOF
+	test_repo 3/sub/sub "$TRASH_DIRECTORY/3/.git" "$TRASH_DIRECTORY/3/wt"
+'
+
+test_expect_success '#3: GIT_DIR(rel), GIT_WORK_TREE=.. at root' '
+	cat >3/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/3/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 3/
+EOF
+	test_repo 3 .git "$TRASH_DIRECTORY"
+'
+
+test_expect_success '#3: GIT_DIR(rel), GIT_WORK_TREE=..(rel) at root' '
+	cat >3/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/3/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 3/
+EOF
+	test_repo 3 .git ..
+'
+
+test_expect_success '#3: GIT_DIR, GIT_WORK_TREE=..(rel) at root' '
+	cat >3/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/3/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 3/
+EOF
+	test_repo 3 "$TRASH_DIRECTORY/3/.git" ..
+'
+
+test_expect_success '#3: GIT_DIR, GIT_WORK_TREE=.. at root' '
+	cat >3/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/3/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 3/
+EOF
+	test_repo 3 "$TRASH_DIRECTORY/3/.git" "$TRASH_DIRECTORY"
+'
+
+test_expect_success '#3: GIT_DIR(rel), GIT_WORK_TREE=.. in subdir' '
+	cat >3/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/3/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 3/sub/sub/
+EOF
+	test_repo 3/sub/sub ../../.git "$TRASH_DIRECTORY"
+'
+
+test_expect_success '#3: GIT_DIR(rel), GIT_WORK_TREE=..(rel) in subdir' '
+	cat >3/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/3/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 3/sub/sub/
+EOF
+	test_repo 3/sub/sub ../../.git ../../..
+'
+
+test_expect_success '#3: GIT_DIR, GIT_WORK_TREE=..(rel) in subdir' '
+	cat >3/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/3/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 3/sub/sub/
+EOF
+	test_repo 3/sub/sub "$TRASH_DIRECTORY/3/.git" ../../../
+'
+
+test_expect_success '#3: GIT_DIR, GIT_WORK_TREE=.. in subdir' '
+	cat >3/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/3/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 3/sub/sub/
+EOF
+	test_repo 3/sub/sub "$TRASH_DIRECTORY/3/.git" "$TRASH_DIRECTORY"
+'
+
+#
+# case #4
+#
+############################################################
+#
+# Input:
+#
+#  - GIT_WORK_TREE is not set
+#  - GIT_DIR is not set
+#  - core.worktree is set
+#  - .git is a directory
+#  - core.bare is not set, cwd is outside .git
+#
+# Output:
+#
+# core.worktree is ignored -> #0
+
+test_expect_success '#4: setup' '
+	unset GIT_DIR GIT_WORK_TREE &&
+	mkdir 4 4/sub &&
+	cd 4 &&
+	git init &&
+	git config core.worktree non-existent &&
+	cd ..
+'
+
+test_expect_success '#4: at root' '
+	cat >4/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/4
+setup: cwd: $TRASH_DIRECTORY/4
+setup: prefix: (null)
+EOF
+	test_repo 4
+'
+
+test_expect_success '#4: in subdir' '
+	cat >4/sub/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/4
+setup: cwd: $TRASH_DIRECTORY/4
+setup: prefix: sub/
+EOF
+	test_repo 4/sub
+'
+
+#
+# case #5
+#
+############################################################
+#
+# Input:
+#
+#  - GIT_WORK_TREE is set
+#  - GIT_DIR is not set
+#  - core.worktree is set
+#  - .git is a directory
+#  - core.bare is not set, cwd is outside .git
+#
+# Output:
+#
+# GIT_WORK_TREE/core.worktree are ignored -> #0
+
+test_expect_success '#5: setup' '
+	unset GIT_DIR GIT_WORK_TREE &&
+	mkdir 5 5/sub &&
+	cd 5 &&
+	git init &&
+	git config core.worktree non-existent &&
+	GIT_WORK_TREE=non-existent-too &&
+	export GIT_WORK_TREE &&
+	cd ..
+'
+
+test_expect_success '#5: at root' '
+	cat >5/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/5
+setup: cwd: $TRASH_DIRECTORY/5
+setup: prefix: (null)
+EOF
+	test_repo 5
+'
+
+test_expect_success '#5: in subdir' '
+	cat >5/sub/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/5
+setup: cwd: $TRASH_DIRECTORY/5
+setup: prefix: sub/
+EOF
+	test_repo 5/sub
+'
+
+#
+# case #6
+#
+############################################################
+#
+# Input:
+#
+#  - GIT_WORK_TREE is not set
+#  - GIT_DIR is set
+#  - core.worktree is set
+#  - .git is a directory
+#  - core.bare is not set, cwd is outside .git
+#
+# Output:
+#
+#  - worktree is at core.worktree
+#  - cwd is at worktree root
+#  - prefix is calculated
+#  - git_dir is at $GIT_DIR
+#  - cwd can be outside worktree
+
+test_expect_success '#6: setup' '
+	unset GIT_DIR GIT_WORK_TREE &&
+	mkdir 6 6/sub 6/sub/sub 6.wt 6.wt/sub 6/wt 6/wt/sub &&
+	cd 6 && git init && cd ..
+'
+
+test_expect_success '#6: GIT_DIR(rel), core.worktree=.. at root' '
+	cat >6/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/6
+setup: cwd: $TRASH_DIRECTORY/6
+setup: prefix: (null)
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree "$TRASH_DIRECTORY/6" &&
+	test_repo 6 .git
+'
+
+test_expect_success '#6: GIT_DIR(rel), core.worktree=..(rel) at root' '
+	cat >6/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/6
+setup: cwd: $TRASH_DIRECTORY/6
+setup: prefix: (null)
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree .. &&
+	test_repo 6 .git
+'
+
+test_expect_success '#6: GIT_DIR, core.worktree=.. at root' '
+	cat >6/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/6/.git
+setup: worktree: $TRASH_DIRECTORY/6
+setup: cwd: $TRASH_DIRECTORY/6
+setup: prefix: (null)
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree "$TRASH_DIRECTORY/6" &&
+	test_repo 6 "$TRASH_DIRECTORY/6/.git"
+'
+
+test_expect_success '#6: GIT_DIR, core.worktree=..(rel) at root' '
+	cat >6/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/6/.git
+setup: worktree: $TRASH_DIRECTORY/6
+setup: cwd: $TRASH_DIRECTORY/6
+setup: prefix: (null)
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree .. &&
+	test_repo 6 "$TRASH_DIRECTORY/6/.git"
+'
+
+test_expect_success '#6: GIT_DIR(rel), core.worktree=.. in subdir' '
+	cat >6/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/6/.git
+setup: worktree: $TRASH_DIRECTORY/6
+setup: cwd: $TRASH_DIRECTORY/6
+setup: prefix: sub/sub/
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree "$TRASH_DIRECTORY/6" &&
+	test_repo 6/sub/sub ../../.git
+'
+
+test_expect_success '#6: GIT_DIR(rel), core.worktree=..(rel) in subdir' '
+	cat >6/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/6/.git
+setup: worktree: $TRASH_DIRECTORY/6
+setup: cwd: $TRASH_DIRECTORY/6
+setup: prefix: sub/sub/
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree .. &&
+	test_repo 6/sub/sub ../../.git
+'
+
+test_expect_success '#6: GIT_DIR, core.worktree=.. in subdir' '
+	cat >6/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/6/.git
+setup: worktree: $TRASH_DIRECTORY/6
+setup: cwd: $TRASH_DIRECTORY/6
+setup: prefix: sub/
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree "$TRASH_DIRECTORY/6" &&
+	test_repo 6/sub "$TRASH_DIRECTORY/6/.git"
+'
+
+test_expect_success '#6: GIT_DIR, core.worktree=..(rel) in subdir' '
+	cat >6/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/6/.git
+setup: worktree: $TRASH_DIRECTORY/6
+setup: cwd: $TRASH_DIRECTORY/6
+setup: prefix: sub/sub/
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree .. &&
+	test_repo 6/sub/sub "$TRASH_DIRECTORY/6/.git"
+'
+
+test_expect_success '#6: GIT_DIR(rel), core.worktree=../wt at root' '
+	cat >6/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/6/wt
+setup: cwd: $TRASH_DIRECTORY/6
+setup: prefix: (null)
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree "$TRASH_DIRECTORY/6/wt" &&
+	test_repo 6 .git
+'
+
+test_expect_success '#6: GIT_DIR(rel), core.worktree=../wt(rel) at root' '
+	cat >6/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/6/wt
+setup: cwd: $TRASH_DIRECTORY/6
+setup: prefix: (null)
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree ../wt &&
+	test_repo 6 .git
+'
+
+test_expect_success '#6: GIT_DIR, core.worktree=../wt(rel) at root' '
+	cat >6/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/6/.git
+setup: worktree: $TRASH_DIRECTORY/6/wt
+setup: cwd: $TRASH_DIRECTORY/6
+setup: prefix: (null)
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree ../wt &&
+	test_repo 6 "$TRASH_DIRECTORY/6/.git"
+'
+
+test_expect_success '#6: GIT_DIR, core.worktree=../wt at root' '
+	cat >6/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/6/.git
+setup: worktree: $TRASH_DIRECTORY/6/wt
+setup: cwd: $TRASH_DIRECTORY/6
+setup: prefix: (null)
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree "$TRASH_DIRECTORY/6/wt" &&
+	test_repo 6 "$TRASH_DIRECTORY/6/.git"
+'
+
+test_expect_success '#6: GIT_DIR(rel), core.worktree=../wt in subdir' '
+	cat >6/sub/sub/expected <<EOF &&
+setup: git_dir: ../../.git
+setup: worktree: $TRASH_DIRECTORY/6/wt
+setup: cwd: $TRASH_DIRECTORY/6/sub/sub
+setup: prefix: (null)
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree "$TRASH_DIRECTORY/6/wt" &&
+	test_repo 6/sub/sub ../../.git
+'
+
+test_expect_success '#6: GIT_DIR(rel), core.worktree=../wt(rel) in subdir' '
+	cat >6/sub/sub/expected <<EOF &&
+setup: git_dir: ../../.git
+setup: worktree: $TRASH_DIRECTORY/6/wt
+setup: cwd: $TRASH_DIRECTORY/6/sub/sub
+setup: prefix: (null)
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree ../wt &&
+	test_repo 6/sub/sub ../../.git
+'
+
+test_expect_success '#6: GIT_DIR, core.worktree=../wt(rel) in subdir' '
+	cat >6/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/6/.git
+setup: worktree: $TRASH_DIRECTORY/6/wt
+setup: cwd: $TRASH_DIRECTORY/6/sub/sub
+setup: prefix: (null)
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree ../wt &&
+	test_repo 6/sub/sub "$TRASH_DIRECTORY/6/.git"
+'
+
+test_expect_success '#6: GIT_DIR, core.worktree=../wt in subdir' '
+	cat >6/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/6/.git
+setup: worktree: $TRASH_DIRECTORY/6/wt
+setup: cwd: $TRASH_DIRECTORY/6/sub/sub
+setup: prefix: (null)
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree "$TRASH_DIRECTORY/6/wt" &&
+	test_repo 6/sub/sub "$TRASH_DIRECTORY/6/.git"
+'
+
+test_expect_success '#6: GIT_DIR(rel), core.worktree=../.. at root' '
+	cat >6/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/6/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 6/
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree "$TRASH_DIRECTORY" &&
+	test_repo 6 .git
+'
+
+test_expect_success '#6: GIT_DIR(rel), core.worktree=../..(rel) at root' '
+	cat >6/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/6/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 6/
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree ../../ &&
+	test_repo 6 .git
+'
+
+test_expect_success '#6: GIT_DIR, core.worktree=../..(rel) at root' '
+	cat >6/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/6/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 6/
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree ../../ &&
+	test_repo 6 "$TRASH_DIRECTORY/6/.git"
+'
+
+test_expect_success '#6: GIT_DIR, core.worktree=../.. at root' '
+	cat >6/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/6/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 6/
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree "$TRASH_DIRECTORY" &&
+	test_repo 6 "$TRASH_DIRECTORY/6/.git"
+'
+
+test_expect_success '#6: GIT_DIR(rel), core.worktree=../.. in subdir' '
+	cat >6/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/6/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 6/sub/sub/
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree "$TRASH_DIRECTORY" &&
+	test_repo 6/sub/sub ../../.git
+'
+
+test_expect_success '#6: GIT_DIR(rel), core.worktree=../..(rel) in subdir' '
+	cat >6/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/6/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 6/sub/sub/
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree ../.. &&
+	test_repo 6/sub/sub ../../.git
+'
+
+test_expect_success '#6: GIT_DIR, core.worktree=../..(rel) in subdir' '
+	cat >6/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/6/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 6/sub/sub/
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree ../.. &&
+	test_repo 6/sub/sub "$TRASH_DIRECTORY/6/.git"
+'
+
+test_expect_success '#6: GIT_DIR, core.worktree=../.. in subdir' '
+	cat >6/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/6/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 6/sub/sub/
+EOF
+	git config --file="$TRASH_DIRECTORY/6/.git/config" core.worktree "$TRASH_DIRECTORY" &&
+	test_repo 6/sub/sub "$TRASH_DIRECTORY/6/.git"
+'
+
+#
+# case #7
+#
+############################################################
+#
+# Input:
+#
+#  - GIT_WORK_TREE is set
+#  - GIT_DIR is set
+#  - core.worktree is set
+#  - .git is a directory
+#  - core.bare is not set, cwd is outside .git
+#
+# Output:
+#
+# core.worktree is overridden by GIT_WORK_TREE -> #3
+
+test_expect_success '#7: setup' '
+	unset GIT_DIR GIT_WORK_TREE &&
+	mkdir 7 7/sub 7/sub/sub 7.wt 7.wt/sub 7/wt 7/wt/sub &&
+	cd 7 &&
+	git init &&
+	git config core.worktree non-existent &&
+	cd ..
+'
+
+test_expect_success '#7: GIT_DIR(rel), GIT_WORK_TREE=root at root' '
+	cat >7/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/7
+setup: cwd: $TRASH_DIRECTORY/7
+setup: prefix: (null)
+EOF
+	test_repo 7 .git "$TRASH_DIRECTORY/7"
+'
+
+test_expect_success '#7: GIT_DIR(rel), GIT_WORK_TREE=root(rel) at root' '
+	cat >7/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/7
+setup: cwd: $TRASH_DIRECTORY/7
+setup: prefix: (null)
+EOF
+	test_repo 7 .git .
+'
+
+test_expect_success '#7: GIT_DIR, GIT_WORK_TREE=root at root' '
+	cat >7/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/7/.git
+setup: worktree: $TRASH_DIRECTORY/7
+setup: cwd: $TRASH_DIRECTORY/7
+setup: prefix: (null)
+EOF
+	test_repo 7 "$TRASH_DIRECTORY/7/.git" "$TRASH_DIRECTORY/7"
+'
+
+test_expect_success '#7: GIT_DIR, GIT_WORK_TREE=root(rel) at root' '
+	cat >7/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/7/.git
+setup: worktree: $TRASH_DIRECTORY/7
+setup: cwd: $TRASH_DIRECTORY/7
+setup: prefix: (null)
+EOF
+	test_repo 7 "$TRASH_DIRECTORY/7/.git" .
+'
+
+test_expect_success '#7: GIT_DIR(rel), GIT_WORKTREE=root in subdir' '
+	cat >7/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/7/.git
+setup: worktree: $TRASH_DIRECTORY/7
+setup: cwd: $TRASH_DIRECTORY/7
+setup: prefix: sub/sub/
+EOF
+	test_repo 7/sub/sub ../../.git "$TRASH_DIRECTORY/7"
+'
+
+test_expect_success '#7: GIT_DIR(rel), GIT_WORKTREE=root(rel) in subdir' '
+	cat >7/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/7/.git
+setup: worktree: $TRASH_DIRECTORY/7
+setup: cwd: $TRASH_DIRECTORY/7
+setup: prefix: sub/sub/
+EOF
+	test_repo 7/sub/sub ../../.git ../..
+'
+
+test_expect_success '#7: GIT_DIR, GIT_WORKTREE=root in subdir' '
+	cat >7/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/7/.git
+setup: worktree: $TRASH_DIRECTORY/7
+setup: cwd: $TRASH_DIRECTORY/7
+setup: prefix: sub/
+EOF
+	test_repo 7/sub "$TRASH_DIRECTORY/7/.git" "$TRASH_DIRECTORY/7"
+'
+
+test_expect_success '#7: GIT_DIR, GIT_WORKTREE=root(rel) in subdir' '
+	cat >7/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/7/.git
+setup: worktree: $TRASH_DIRECTORY/7
+setup: cwd: $TRASH_DIRECTORY/7
+setup: prefix: sub/sub/
+EOF
+	test_repo 7/sub/sub "$TRASH_DIRECTORY/7/.git" ../..
+'
+
+test_expect_success '#7: GIT_DIR(rel), GIT_WORK_TREE=wt at root' '
+	cat >7/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/7/wt
+setup: cwd: $TRASH_DIRECTORY/7
+setup: prefix: (null)
+EOF
+	test_repo 7 .git "$TRASH_DIRECTORY/7/wt"
+'
+
+test_expect_success '#7: GIT_DIR(rel), GIT_WORK_TREE=wt(rel) at root' '
+	cat >7/expected <<EOF &&
+setup: git_dir: .git
+setup: worktree: $TRASH_DIRECTORY/7/wt
+setup: cwd: $TRASH_DIRECTORY/7
+setup: prefix: (null)
+EOF
+	test_repo 7 .git wt
+'
+
+test_expect_success '#7: GIT_DIR, GIT_WORK_TREE=wt(rel) at root' '
+	cat >7/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/7/.git
+setup: worktree: $TRASH_DIRECTORY/7/wt
+setup: cwd: $TRASH_DIRECTORY/7
+setup: prefix: (null)
+EOF
+	test_repo 7 "$TRASH_DIRECTORY/7/.git" wt
+'
+
+test_expect_success '#7: GIT_DIR, GIT_WORK_TREE=wt at root' '
+	cat >7/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/7/.git
+setup: worktree: $TRASH_DIRECTORY/7/wt
+setup: cwd: $TRASH_DIRECTORY/7
+setup: prefix: (null)
+EOF
+	test_repo 7 "$TRASH_DIRECTORY/7/.git" "$TRASH_DIRECTORY/7/wt"
+'
+
+test_expect_success '#7: GIT_DIR(rel), GIT_WORK_TREE=wt in subdir' '
+	cat >7/sub/sub/expected <<EOF &&
+setup: git_dir: ../../.git
+setup: worktree: $TRASH_DIRECTORY/7/wt
+setup: cwd: $TRASH_DIRECTORY/7/sub/sub
+setup: prefix: (null)
+EOF
+	test_repo 7/sub/sub ../../.git "$TRASH_DIRECTORY/7/wt"
+'
+
+test_expect_success '#7: GIT_DIR(rel), GIT_WORK_TREE=wt(rel) in subdir' '
+	cat >7/sub/sub/expected <<EOF &&
+setup: git_dir: ../../.git
+setup: worktree: $TRASH_DIRECTORY/7/wt
+setup: cwd: $TRASH_DIRECTORY/7/sub/sub
+setup: prefix: (null)
+EOF
+	test_repo 7/sub/sub ../../.git ../../wt
+'
+
+test_expect_success '#7: GIT_DIR, GIT_WORK_TREE=wt(rel) in subdir' '
+	cat >7/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/7/.git
+setup: worktree: $TRASH_DIRECTORY/7/wt
+setup: cwd: $TRASH_DIRECTORY/7/sub/sub
+setup: prefix: (null)
+EOF
+	test_repo 7/sub/sub "$TRASH_DIRECTORY/7/.git" ../../wt
+'
+
+test_expect_success '#7: GIT_DIR, GIT_WORK_TREE=wt in subdir' '
+	cat >7/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/7/.git
+setup: worktree: $TRASH_DIRECTORY/7/wt
+setup: cwd: $TRASH_DIRECTORY/7/sub/sub
+setup: prefix: (null)
+EOF
+	test_repo 7/sub/sub "$TRASH_DIRECTORY/7/.git" "$TRASH_DIRECTORY/7/wt"
+'
+
+test_expect_success '#7: GIT_DIR(rel), GIT_WORK_TREE=.. at root' '
+	cat >7/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/7/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 7/
+EOF
+	test_repo 7 .git "$TRASH_DIRECTORY"
+'
+
+test_expect_success '#7: GIT_DIR(rel), GIT_WORK_TREE=..(rel) at root' '
+	cat >7/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/7/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 7/
+EOF
+	test_repo 7 .git ..
+'
+
+test_expect_success '#7: GIT_DIR, GIT_WORK_TREE=..(rel) at root' '
+	cat >7/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/7/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 7/
+EOF
+	test_repo 7 "$TRASH_DIRECTORY/7/.git" ..
+'
+
+test_expect_success '#7: GIT_DIR, GIT_WORK_TREE=.. at root' '
+	cat >7/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/7/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 7/
+EOF
+	test_repo 7 "$TRASH_DIRECTORY/7/.git" "$TRASH_DIRECTORY"
+'
+
+test_expect_success '#7: GIT_DIR(rel), GIT_WORK_TREE=.. in subdir' '
+	cat >7/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/7/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 7/sub/sub/
+EOF
+	test_repo 7/sub/sub ../../.git "$TRASH_DIRECTORY"
+'
+
+test_expect_success '#7: GIT_DIR(rel), GIT_WORK_TREE=..(rel) in subdir' '
+	cat >7/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/7/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 7/sub/sub/
+EOF
+	test_repo 7/sub/sub ../../.git ../../..
+'
+
+test_expect_success '#7: GIT_DIR, GIT_WORK_TREE=..(rel) in subdir' '
+	cat >7/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/7/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 7/sub/sub/
+EOF
+	test_repo 7/sub/sub "$TRASH_DIRECTORY/7/.git" ../../../
+'
+
+test_expect_success '#7: GIT_DIR, GIT_WORK_TREE=.. in subdir' '
+	cat >7/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/7/.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 7/sub/sub/
+EOF
+	test_repo 7/sub/sub "$TRASH_DIRECTORY/7/.git" "$TRASH_DIRECTORY"
+'
+
+#
+# case #8
+#
+############################################################
+#
+# Input:
+#
+#  - GIT_WORK_TREE is not set
+#  - GIT_DIR is not set
+#  - core.worktree is not set
+#  - .git is a file
+#  - core.bare is not set, cwd is outside .git
+#
+# Output:
+#
+# #0 except that git_dir is set by .git file
+
+test_expect_success '#8: setup' '
+	unset GIT_DIR GIT_WORK_TREE &&
+	mkdir 8 8/sub &&
+	cd 8 &&
+	git init &&
+	mv .git ../8.git &&
+	echo gitdir: ../8.git >.git &&
+	cd ..
+'
+
+test_expect_success '#8: at root' '
+	cat >8/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/8.git
+setup: worktree: $TRASH_DIRECTORY/8
+setup: cwd: $TRASH_DIRECTORY/8
+setup: prefix: (null)
+EOF
+	test_repo 8
+'
+
+test_expect_success '#8: in subdir' '
+	cat >8/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/8.git
+setup: worktree: $TRASH_DIRECTORY/8
+setup: cwd: $TRASH_DIRECTORY/8
+setup: prefix: sub/
+EOF
+	test_repo 8/sub
+'
+
+#
+# case #9
+#
+############################################################
+#
+# Input:
+#
+#  - GIT_WORK_TREE is set
+#  - GIT_DIR is not set
+#  - core.worktree is not set
+#  - .git is a file
+#  - core.bare is not set, cwd is outside .git
+#
+# Output:
+#
+# #1 except that git_dir is set by .git file
+
+test_expect_success '#9: setup' '
+	unset GIT_DIR GIT_WORK_TREE &&
+	mkdir 9 9/sub 9.wt 9.wt/sub 9/wt 9/wt/sub &&
+	cd 9 &&
+	git init &&
+	mv .git ../9.git &&
+	echo gitdir: ../9.git >.git &&
+	GIT_WORK_TREE=non-existent &&
+	export GIT_WORK_TREE &&
+	cd ..
+'
+
+test_expect_success '#9: at root' '
+	cat >9/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/9.git
+setup: worktree: $TRASH_DIRECTORY/9
+setup: cwd: $TRASH_DIRECTORY/9
+setup: prefix: (null)
+EOF
+	test_repo 9
+'
+
+test_expect_success '#9: in subdir' '
+	cat >9/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/9.git
+setup: worktree: $TRASH_DIRECTORY/9
+setup: cwd: $TRASH_DIRECTORY/9
+setup: prefix: sub/
+EOF
+	test_repo 9/sub
+'
+
+#
+# case #10
+#
+############################################################
+#
+# Input:
+#
+#  - GIT_WORK_TREE is not set
+#  - GIT_DIR is set
+#  - core.worktree is not set
+#  - .git is a file
+#  - core.bare is not set, cwd is outside .git
+#
+# Output:
+#
+# #2 except that git_dir is set by .git file
+
+test_expect_success '#10: setup' '
+	unset GIT_DIR GIT_WORK_TREE &&
+	mkdir 10 10/sub &&
+	cd 10 &&
+	git init &&
+	mv .git ../10.git &&
+	echo gitdir: ../10.git >.git &&
+	cd ..
+'
+
+test_expect_success '#10: at root' '
+	cat >10/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/10.git
+setup: worktree: $TRASH_DIRECTORY/10
+setup: cwd: $TRASH_DIRECTORY/10
+setup: prefix: (null)
+EOF
+	test_repo 10 "$TRASH_DIRECTORY/10/.git"
+'
+
+test_expect_success '#10: in subdir' '
+	cat >10/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/10.git
+setup: worktree: $TRASH_DIRECTORY/10/sub
+setup: cwd: $TRASH_DIRECTORY/10/sub
+setup: prefix: (null)
+EOF
+	test_repo 10/sub "$TRASH_DIRECTORY/10/.git"
+'
+
+test_expect_success '#10: relative GIT_DIR at root' '
+	cat >10/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/10.git
+setup: worktree: $TRASH_DIRECTORY/10
+setup: cwd: $TRASH_DIRECTORY/10
+setup: prefix: (null)
+EOF
+	test_repo 10 .git
+'
+
+test_expect_success '#10: relative GIT_DIR in subdir' '
+	cat >10/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/10.git
+setup: worktree: $TRASH_DIRECTORY/10/sub
+setup: cwd: $TRASH_DIRECTORY/10/sub
+setup: prefix: (null)
+EOF
+	test_repo 10/sub ../.git
+'
+
+#
+# case #11
+#
+############################################################
+#
+# Input:
+#
+#  - GIT_WORK_TREE is set
+#  - GIT_DIR is set
+#  - core.worktree is not set
+#  - .git is a file
+#  - core.bare is not set, cwd is outside .git
+#
+# Output:
+#
+# #3 except that git_dir is set by .git file
+
+test_expect_success '#11: setup' '
+	unset GIT_DIR GIT_WORK_TREE &&
+	mkdir 11 11/sub 11/sub/sub 11.wt 11.wt/sub 11/wt 11/wt/sub &&
+	cd 11 &&
+	git init &&
+	mv .git ../11.git &&
+	echo gitdir: ../11.git >.git &&
+	cd ..
+'
+
+test_expect_success '#11: GIT_DIR(rel), GIT_WORK_TREE=root at root' '
+	cat >11/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY/11
+setup: cwd: $TRASH_DIRECTORY/11
+setup: prefix: (null)
+EOF
+	test_repo 11 .git "$TRASH_DIRECTORY/11"
+'
+
+test_expect_success '#11: GIT_DIR(rel), GIT_WORK_TREE=root(rel) at root' '
+	cat >11/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY/11
+setup: cwd: $TRASH_DIRECTORY/11
+setup: prefix: (null)
+EOF
+	test_repo 11 .git .
+'
+
+test_expect_success '#11: GIT_DIR, GIT_WORK_TREE=root at root' '
+	cat >11/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY/11
+setup: cwd: $TRASH_DIRECTORY/11
+setup: prefix: (null)
+EOF
+	test_repo 11 "$TRASH_DIRECTORY/11/.git" "$TRASH_DIRECTORY/11"
+'
+
+test_expect_success '#11: GIT_DIR, GIT_WORK_TREE=root(rel) at root' '
+	cat >11/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY/11
+setup: cwd: $TRASH_DIRECTORY/11
+setup: prefix: (null)
+EOF
+	test_repo 11 "$TRASH_DIRECTORY/11/.git" .
+'
+
+test_expect_success '#11: GIT_DIR(rel), GIT_WORKTREE=root in subdir' '
+	cat >11/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY/11
+setup: cwd: $TRASH_DIRECTORY/11
+setup: prefix: sub/sub/
+EOF
+	test_repo 11/sub/sub ../../.git "$TRASH_DIRECTORY/11"
+'
+
+test_expect_success '#11: GIT_DIR(rel), GIT_WORKTREE=root(rel) in subdir' '
+	cat >11/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY/11
+setup: cwd: $TRASH_DIRECTORY/11
+setup: prefix: sub/sub/
+EOF
+	test_repo 11/sub/sub ../../.git ../..
+'
+
+test_expect_success '#11: GIT_DIR, GIT_WORKTREE=root in subdir' '
+	cat >11/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY/11
+setup: cwd: $TRASH_DIRECTORY/11
+setup: prefix: sub/
+EOF
+	test_repo 11/sub "$TRASH_DIRECTORY/11/.git" "$TRASH_DIRECTORY/11"
+'
+
+test_expect_success '#11: GIT_DIR, GIT_WORKTREE=root(rel) in subdir' '
+	cat >11/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY/11
+setup: cwd: $TRASH_DIRECTORY/11
+setup: prefix: sub/sub/
+EOF
+	test_repo 11/sub/sub "$TRASH_DIRECTORY/11/.git" ../..
+'
+
+test_expect_success '#11: GIT_DIR(rel), GIT_WORK_TREE=wt at root' '
+	cat >11/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY/11/wt
+setup: cwd: $TRASH_DIRECTORY/11
+setup: prefix: (null)
+EOF
+	test_repo 11 .git "$TRASH_DIRECTORY/11/wt"
+'
+
+test_expect_success '#11: GIT_DIR(rel), GIT_WORK_TREE=wt(rel) at root' '
+	cat >11/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY/11/wt
+setup: cwd: $TRASH_DIRECTORY/11
+setup: prefix: (null)
+EOF
+	test_repo 11 .git wt
+'
+
+test_expect_success '#11: GIT_DIR, GIT_WORK_TREE=wt(rel) at root' '
+	cat >11/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY/11/wt
+setup: cwd: $TRASH_DIRECTORY/11
+setup: prefix: (null)
+EOF
+	test_repo 11 "$TRASH_DIRECTORY/11/.git" wt
+'
+
+test_expect_success '#11: GIT_DIR, GIT_WORK_TREE=wt at root' '
+	cat >11/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY/11/wt
+setup: cwd: $TRASH_DIRECTORY/11
+setup: prefix: (null)
+EOF
+	test_repo 11 "$TRASH_DIRECTORY/11/.git" "$TRASH_DIRECTORY/11/wt"
+'
+
+test_expect_success '#11: GIT_DIR(rel), GIT_WORK_TREE=wt in subdir' '
+	cat >11/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY/11/wt
+setup: cwd: $TRASH_DIRECTORY/11/sub/sub
+setup: prefix: (null)
+EOF
+	test_repo 11/sub/sub ../../.git "$TRASH_DIRECTORY/11/wt"
+'
+
+test_expect_success '#11: GIT_DIR(rel), GIT_WORK_TREE=wt(rel) in subdir' '
+	cat >11/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY/11/wt
+setup: cwd: $TRASH_DIRECTORY/11/sub/sub
+setup: prefix: (null)
+EOF
+	test_repo 11/sub/sub ../../.git ../../wt
+'
+
+test_expect_success '#11: GIT_DIR, GIT_WORK_TREE=wt(rel) in subdir' '
+	cat >11/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY/11/wt
+setup: cwd: $TRASH_DIRECTORY/11/sub/sub
+setup: prefix: (null)
+EOF
+	test_repo 11/sub/sub "$TRASH_DIRECTORY/11/.git" ../../wt
+'
+
+test_expect_success '#11: GIT_DIR, GIT_WORK_TREE=wt in subdir' '
+	cat >11/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY/11/wt
+setup: cwd: $TRASH_DIRECTORY/11/sub/sub
+setup: prefix: (null)
+EOF
+	test_repo 11/sub/sub "$TRASH_DIRECTORY/11/.git" "$TRASH_DIRECTORY/11/wt"
+'
+
+test_expect_success '#11: GIT_DIR(rel), GIT_WORK_TREE=.. at root' '
+	cat >11/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 11/
+EOF
+	test_repo 11 .git "$TRASH_DIRECTORY"
+'
+
+test_expect_success '#11: GIT_DIR(rel), GIT_WORK_TREE=..(rel) at root' '
+	cat >11/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 11/
+EOF
+	test_repo 11 .git ..
+'
+
+test_expect_success '#11: GIT_DIR, GIT_WORK_TREE=..(rel) at root' '
+	cat >11/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 11/
+EOF
+	test_repo 11 "$TRASH_DIRECTORY/11/.git" ..
+'
+
+test_expect_success '#11: GIT_DIR, GIT_WORK_TREE=.. at root' '
+	cat >11/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 11/
+EOF
+	test_repo 11 "$TRASH_DIRECTORY/11/.git" "$TRASH_DIRECTORY"
+'
+
+test_expect_success '#11: GIT_DIR(rel), GIT_WORK_TREE=.. in subdir' '
+	cat >11/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 11/sub/sub/
+EOF
+	test_repo 11/sub/sub ../../.git "$TRASH_DIRECTORY"
+'
+
+test_expect_success '#11: GIT_DIR(rel), GIT_WORK_TREE=..(rel) in subdir' '
+	cat >11/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 11/sub/sub/
+EOF
+	test_repo 11/sub/sub ../../.git ../../..
+'
+
+test_expect_success '#11: GIT_DIR, GIT_WORK_TREE=..(rel) in subdir' '
+	cat >11/sub/sub/expected <<EOF &&
+setup: git_dir: $TRASH_DIRECTORY/11.git
+setup: worktree: $TRASH_DIRECTORY
+setup: cwd: $TRASH_DIRECTORY
+setup: prefix: 11/sub/sub/
+EOF
+	test_repo 11/sub/sub "$TRASH_DIRECTORY/11/.git" ../../../
+'
+
+test_expect_success '#11: GIT_DIR, GIT_WORK_TREE=.. in subdir' '