summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar Junio C Hamano <gitster@pobox.com>2007-07-01 13:10:42 -0700
committerLibravatar Junio C Hamano <gitster@pobox.com>2007-07-01 13:10:42 -0700
commit0305b636542c8c137ed7c82fee90db8d3621118c (patch)
tree3a2a5e7a2b814b2f96c5edd681768706b9b3a048
parentMerge branch 'fl/config' (diff)
parentfilter-branch: always export GIT_DIR if it is set (diff)
downloadtgif-0305b636542c8c137ed7c82fee90db8d3621118c.tar.xz
Merge branch 'ei/worktree+filter'
* ei/worktree+filter: filter-branch: always export GIT_DIR if it is set setup_git_directory: fix segfault if repository is found in cwd test GIT_WORK_TREE extend rev-parse test for --is-inside-work-tree Use new semantics of is_bare/inside_git_dir/inside_work_tree introduce GIT_WORK_TREE to specify the work tree test git rev-parse rev-parse: introduce --is-bare-repository rev-parse: document --is-inside-git-dir
-rw-r--r--Documentation/config.txt7
-rw-r--r--Documentation/git-rev-parse.txt11
-rw-r--r--Documentation/git.txt18
-rw-r--r--builtin-ls-files.c2
-rw-r--r--builtin-rev-parse.c10
-rw-r--r--cache.h2
-rw-r--r--connect.c1
-rw-r--r--git-filter-branch.sh3
-rwxr-xr-xgit-sh-setup.sh8
-rwxr-xr-xgit-svn.perl3
-rw-r--r--git.c35
-rw-r--r--setup.c218
-rwxr-xr-xt/t1500-rev-parse.sh77
-rwxr-xr-xt/t1501-worktree.sh92
-rw-r--r--t/test-lib.sh1
15 files changed, 406 insertions, 82 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt
index a2057d9d24..3dc17a6d78 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -172,6 +172,13 @@ repository that ends in "/.git" is assumed to be not bare (bare =
false), while all other repositories are assumed to be bare (bare
= true).
+core.worktree::
+ 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 overriden by the GIT_WORK_TREE environment
+ variable and the '--work-tree' command line option.
+
core.logAllRefUpdates::
Updates to a ref <ref> is logged to the file
"$GIT_DIR/logs/<ref>", by appending the new and old
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 87771b832b..eea9c9cfe9 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -90,8 +90,15 @@ OPTIONS
Show `$GIT_DIR` if defined else show the path to the .git directory.
--is-inside-git-dir::
- Return "true" if we are in the git directory, otherwise "false".
- Some commands require to be run in a working directory.
+ When the current working directory is below the repository
+ directory print "true", otherwise "false".
+
+--is-inside-work-tree::
+ When the current working directory is inside the work tree of the
+ repository print "true", otherwise "false".
+
+--is-bare-repository::
+ When the repository is bare print "true", otherwise "false".
--short, --short=number::
Instead of outputting the full SHA1 values of object names try to
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 826914837b..2cc0b214d2 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -10,7 +10,8 @@ SYNOPSIS
--------
[verse]
'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate]
- [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]
+ [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]
+ [--help] COMMAND [ARGS]
DESCRIPTION
-----------
@@ -103,6 +104,14 @@ OPTIONS
Set the path to the repository. This can also be controlled by
setting the GIT_DIR environment variable.
+--work-tree=<path>::
+ 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 also be controlled by setting the GIT_WORK_TREE
+ environment variable and the core.worktree configuration
+ variable.
+
--bare::
Same as --git-dir=`pwd`.
@@ -347,6 +356,13 @@ git so take care if using Cogito etc.
specifies a path to use instead of the default `.git`
for the base of the repository.
+'GIT_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 also be controlled by the '--work-tree' command line
+ option and the core.worktree configuration variable.
+
git Commits
~~~~~~~~~~~
'GIT_AUTHOR_NAME'::
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
index 5398a41415..61577ea13f 100644
--- a/builtin-ls-files.c
+++ b/builtin-ls-files.c
@@ -470,7 +470,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
}
if (require_work_tree &&
- (is_bare_repository() || is_inside_git_dir()))
+ (!is_inside_work_tree() || is_inside_git_dir()))
die("This operation must be run in a work tree");
pathspec = get_pathspec(prefix, argv + i);
diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c
index 37addb25fa..497903a85a 100644
--- a/builtin-rev-parse.c
+++ b/builtin-rev-parse.c
@@ -352,6 +352,16 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
: "false");
continue;
}
+ if (!strcmp(arg, "--is-inside-work-tree")) {
+ printf("%s\n", is_inside_work_tree() ? "true"
+ : "false");
+ continue;
+ }
+ if (!strcmp(arg, "--is-bare-repository")) {
+ printf("%s\n", is_bare_repository() ? "true"
+ : "false");
+ continue;
+ }
if (!prefixcmp(arg, "--since=")) {
show_datestring("--max-age=", arg+8);
continue;
diff --git a/cache.h b/cache.h
index 0525c4ee55..dcadfef929 100644
--- a/cache.h
+++ b/cache.h
@@ -192,6 +192,7 @@ enum object_type {
};
#define GIT_DIR_ENVIRONMENT "GIT_DIR"
+#define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
#define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
#define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
#define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
@@ -207,6 +208,7 @@ enum object_type {
extern int is_bare_repository_cfg;
extern int is_bare_repository(void);
extern int is_inside_git_dir(void);
+extern int is_inside_work_tree(void);
extern const char *get_git_dir(void);
extern char *get_object_directory(void);
extern char *get_refs_directory(void);
diff --git a/connect.c b/connect.c
index a5afd2a536..65e79edc77 100644
--- a/connect.c
+++ b/connect.c
@@ -587,6 +587,7 @@ pid_t git_connect(int fd[2], char *url, const char *prog, int flags)
unsetenv(ALTERNATE_DB_ENVIRONMENT);
unsetenv(DB_ENVIRONMENT);
unsetenv(GIT_DIR_ENVIRONMENT);
+ unsetenv(GIT_WORK_TREE_ENVIRONMENT);
unsetenv(GRAFT_ENVIRONMENT);
unsetenv(INDEX_ENVIRONMENT);
execlp("sh", "sh", "-c", command, NULL);
diff --git a/git-filter-branch.sh b/git-filter-branch.sh
index 8fa5ce6467..a2fcebc1c6 100644
--- a/git-filter-branch.sh
+++ b/git-filter-branch.sh
@@ -312,9 +312,10 @@ case "$GIT_DIR" in
/*)
;;
*)
- export GIT_DIR="$(pwd)/../../$GIT_DIR"
+ GIT_DIR="$(pwd)/../../$GIT_DIR"
;;
esac
+export GIT_DIR GIT_WORK_TREE=.
export GIT_INDEX_FILE="$(pwd)/../index"
git-read-tree # seed the index file
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index f24c7f2d23..0de49e8459 100755
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -29,11 +29,7 @@ set_reflog_action() {
}
is_bare_repository () {
- git-config --bool --get core.bare ||
- case "$GIT_DIR" in
- .git | */.git) echo false ;;
- *) echo true ;;
- esac
+ git-rev-parse --is-bare-repository
}
cd_to_toplevel () {
@@ -48,7 +44,7 @@ cd_to_toplevel () {
}
require_work_tree () {
- test $(is_bare_repository) = false &&
+ test $(git-rev-parse --is-inside-work-tree) = true &&
test $(git-rev-parse --is-inside-git-dir) = false ||
die "fatal: $0 cannot be used without a working tree."
}
diff --git a/git-svn.perl b/git-svn.perl
index 98687f54d7..03d5e2d979 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -596,8 +596,7 @@ sub post_fetch_checkout {
my $index = $ENV{GIT_INDEX_FILE} || "$ENV{GIT_DIR}/index";
return if -f $index;
- chomp(my $bare = `git config --bool --get core.bare`);
- return if $bare eq 'true';
+ return if command_oneline(qw/rev-parse --is-inside-work-tree/) eq 'false';
return if command_oneline(qw/rev-parse --is-inside-git-dir/) eq 'true';
command_noisy(qw/read-tree -m -u -v HEAD HEAD/);
print STDERR "Checked out HEAD:\n ",
diff --git a/git.c b/git.c
index cfec5d70ee..b6bf5ad5ec 100644
--- a/git.c
+++ b/git.c
@@ -4,7 +4,7 @@
#include "quote.h"
const char git_usage_string[] =
- "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]";
+ "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]";
static void prepend_to_path(const char *dir, int len)
{
@@ -69,6 +69,16 @@ static int handle_options(const char*** argv, int* argc)
handled++;
} else if (!prefixcmp(cmd, "--git-dir=")) {
setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);
+ } else if (!strcmp(cmd, "--work-tree")) {
+ if (*argc < 2) {
+ fprintf(stderr, "No directory given for --work-tree.\n" );
+ usage(git_usage_string);
+ }
+ setenv(GIT_WORK_TREE_ENVIRONMENT, (*argv)[1], 1);
+ (*argv)++;
+ (*argc)--;
+ } else if (!prefixcmp(cmd, "--work-tree=")) {
+ setenv(GIT_WORK_TREE_ENVIRONMENT, cmd + 12, 1);
} else if (!strcmp(cmd, "--bare")) {
static char git_dir[PATH_MAX+1];
setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 1);
@@ -214,7 +224,7 @@ const char git_version_string[] = GIT_VERSION;
* require working tree to be present -- anything uses this needs
* RUN_SETUP for reading from the configuration file.
*/
-#define NOT_BARE (1<<2)
+#define NEED_WORK_TREE (1<<2)
struct cmd_struct {
const char *cmd;
@@ -233,10 +243,9 @@ static int run_command(struct cmd_struct *p, int argc, const char **argv)
prefix = setup_git_directory();
if (p->option & USE_PAGER)
setup_pager();
- if (p->option & NOT_BARE) {
- if (is_bare_repository() || is_inside_git_dir())
- die("%s must be run in a work tree", p->cmd);
- }
+ if ((p->option & NEED_WORK_TREE) &&
+ (!is_inside_work_tree() || is_inside_git_dir()))
+ die("%s must be run in a work tree", p->cmd);
trace_argv_printf(argv, argc, "trace: built-in: git");
status = p->fn(argc, argv, prefix);
@@ -264,7 +273,7 @@ static void handle_internal_command(int argc, const char **argv)
{
const char *cmd = argv[0];
static struct cmd_struct commands[] = {
- { "add", cmd_add, RUN_SETUP | NOT_BARE },
+ { "add", cmd_add, RUN_SETUP | NEED_WORK_TREE },
{ "annotate", cmd_annotate, RUN_SETUP | USE_PAGER },
{ "apply", cmd_apply },
{ "archive", cmd_archive },
@@ -274,9 +283,9 @@ static void handle_internal_command(int argc, const char **argv)
{ "cat-file", cmd_cat_file, RUN_SETUP },
{ "checkout-index", cmd_checkout_index, RUN_SETUP },
{ "check-ref-format", cmd_check_ref_format },
- { "check-attr", cmd_check_attr, RUN_SETUP | NOT_BARE },
+ { "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE },
{ "cherry", cmd_cherry, RUN_SETUP },
- { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NOT_BARE },
+ { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
{ "config", cmd_config },
{ "count-objects", cmd_count_objects, RUN_SETUP },
@@ -304,7 +313,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "mailsplit", cmd_mailsplit },
{ "merge-base", cmd_merge_base, RUN_SETUP },
{ "merge-file", cmd_merge_file },
- { "mv", cmd_mv, RUN_SETUP | NOT_BARE },
+ { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
{ "name-rev", cmd_name_rev, RUN_SETUP },
{ "pack-objects", cmd_pack_objects, RUN_SETUP },
{ "pickaxe", cmd_blame, RUN_SETUP | USE_PAGER },
@@ -317,9 +326,9 @@ static void handle_internal_command(int argc, const char **argv)
{ "rerere", cmd_rerere, RUN_SETUP },
{ "rev-list", cmd_rev_list, RUN_SETUP },
{ "rev-parse", cmd_rev_parse, RUN_SETUP },
- { "revert", cmd_revert, RUN_SETUP | NOT_BARE },
- { "rm", cmd_rm, RUN_SETUP | NOT_BARE },
- { "runstatus", cmd_runstatus, RUN_SETUP | NOT_BARE },
+ { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
+ { "rm", cmd_rm, RUN_SETUP | NEED_WORK_TREE },
+ { "runstatus", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE },
{ "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
{ "show-branch", cmd_show_branch, RUN_SETUP },
{ "show", cmd_show, RUN_SETUP | USE_PAGER },
diff --git a/setup.c b/setup.c
index 14f62c42e3..01f74d4644 100644
--- a/setup.c
+++ b/setup.c
@@ -95,7 +95,7 @@ void verify_non_filename(const char *prefix, const char *arg)
const char *name;
struct stat st;
- if (is_inside_git_dir())
+ if (!is_inside_work_tree() || is_inside_git_dir())
return;
if (*arg == '-')
return; /* flag */
@@ -174,41 +174,96 @@ static int inside_git_dir = -1;
int is_inside_git_dir(void)
{
- if (inside_git_dir < 0) {
- char buffer[1024];
-
- if (is_bare_repository())
- return (inside_git_dir = 1);
- if (getcwd(buffer, sizeof(buffer))) {
- const char *git_dir = get_git_dir(), *cwd = buffer;
- while (*git_dir && *git_dir == *cwd) {
- git_dir++;
- cwd++;
- }
- inside_git_dir = !*git_dir;
- } else
- inside_git_dir = 0;
+ if (inside_git_dir >= 0)
+ return inside_git_dir;
+ die("BUG: is_inside_git_dir called before setup_git_directory");
+}
+
+static int inside_work_tree = -1;
+
+int is_inside_work_tree(void)
+{
+ if (inside_git_dir >= 0)
+ return inside_work_tree;
+ die("BUG: is_inside_work_tree called before setup_git_directory");
+}
+
+static char *gitworktree_config;
+
+static int git_setup_config(const char *var, const char *value)
+{
+ if (!strcmp(var, "core.worktree")) {
+ if (gitworktree_config)
+ strlcpy(gitworktree_config, value, PATH_MAX);
+ return 0;
}
- return inside_git_dir;
+ return git_default_config(var, value);
}
const char *setup_git_directory_gently(int *nongit_ok)
{
static char cwd[PATH_MAX+1];
- const char *gitdirenv;
- int len, offset;
+ char worktree[PATH_MAX+1], gitdir[PATH_MAX+1];
+ const char *gitdirenv, *gitworktree;
+ int wt_rel_gitdir = 0;
- /*
- * 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) {
- if (PATH_MAX - 40 < strlen(gitdirenv))
- die("'$%s' too big", GIT_DIR_ENVIRONMENT);
- if (is_git_directory(gitdirenv))
+ if (!gitdirenv) {
+ int len, offset;
+
+ if (!getcwd(cwd, sizeof(cwd)-1) || cwd[0] != '/')
+ die("Unable to read current working directory");
+
+ offset = len = strlen(cwd);
+ for (;;) {
+ if (is_git_directory(".git"))
+ break;
+ if (offset == 0) {
+ offset = -1;
+ break;
+ }
+ chdir("..");
+ while (cwd[--offset] != '/')
+ ; /* do nothing */
+ }
+
+ if (offset >= 0) {
+ inside_work_tree = 1;
+ git_config(git_default_config);
+ if (offset == len) {
+ inside_git_dir = 0;
+ return NULL;
+ }
+
+ cwd[len++] = '/';
+ cwd[len] = '\0';
+ inside_git_dir = !prefixcmp(cwd + offset + 1, ".git/");
+ return cwd + offset + 1;
+ }
+
+ if (chdir(cwd))
+ die("Cannot come back to cwd");
+ if (!is_git_directory(".")) {
+ if (nongit_ok) {
+ *nongit_ok = 1;
+ return NULL;
+ }
+ die("Not a git repository");
+ }
+ setenv(GIT_DIR_ENVIRONMENT, cwd, 1);
+ gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
+ if (!gitdirenv)
+ die("getenv after setenv failed");
+ }
+
+ if (PATH_MAX - 40 < strlen(gitdirenv)) {
+ if (nongit_ok) {
+ *nongit_ok = 1;
return NULL;
+ }
+ die("$%s too big", GIT_DIR_ENVIRONMENT);
+ }
+ if (!is_git_directory(gitdirenv)) {
if (nongit_ok) {
*nongit_ok = 1;
return NULL;
@@ -218,41 +273,92 @@ const char *setup_git_directory_gently(int *nongit_ok)
if (!getcwd(cwd, sizeof(cwd)-1) || cwd[0] != '/')
die("Unable to read current working directory");
+ if (chdir(gitdirenv)) {
+ if (nongit_ok) {
+ *nongit_ok = 1;
+ return NULL;
+ }
+ die("Cannot change directory to $%s '%s'",
+ GIT_DIR_ENVIRONMENT, gitdirenv);
+ }
+ if (!getcwd(gitdir, sizeof(gitdir)-1) || gitdir[0] != '/')
+ die("Unable to read current working directory");
+ if (chdir(cwd))
+ die("Cannot come back to cwd");
- offset = len = strlen(cwd);
- for (;;) {
- if (is_git_directory(".git"))
- break;
- chdir("..");
- do {
- if (!offset) {
- if (is_git_directory(cwd)) {
- if (chdir(cwd))
- die("Cannot come back to cwd");
- setenv(GIT_DIR_ENVIRONMENT, cwd, 1);
- inside_git_dir = 1;
- return NULL;
- }
- if (nongit_ok) {
- if (chdir(cwd))
- die("Cannot come back to cwd");
- *nongit_ok = 1;
- return NULL;
- }
- die("Not a git repository");
+ /*
+ * In case there is a work tree we may change the directory,
+ * therefore make GIT_DIR an absolute path.
+ */
+ if (gitdirenv[0] != '/') {
+ setenv(GIT_DIR_ENVIRONMENT, gitdir, 1);
+ gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
+ if (!gitdirenv)
+ die("getenv after setenv failed");
+ if (PATH_MAX - 40 < strlen(gitdirenv)) {
+ if (nongit_ok) {
+ *nongit_ok = 1;
+ return NULL;
}
- } while (cwd[--offset] != '/');
+ die("$%s too big after expansion to absolute path",
+ GIT_DIR_ENVIRONMENT);
+ }
+ }
+
+ strcat(cwd, "/");
+ strcat(gitdir, "/");
+ inside_git_dir = !prefixcmp(cwd, gitdir);
+
+ gitworktree = getenv(GIT_WORK_TREE_ENVIRONMENT);
+ if (!gitworktree) {
+ gitworktree_config = worktree;
+ worktree[0] = '\0';
+ }
+ git_config(git_setup_config);
+ if (!gitworktree) {
+ gitworktree_config = NULL;
+ if (worktree[0])
+ gitworktree = worktree;
+ if (gitworktree && gitworktree[0] != '/')
+ wt_rel_gitdir = 1;
+ }
+
+ if (wt_rel_gitdir && chdir(gitdirenv))
+ die("Cannot change directory to $%s '%s'",
+ GIT_DIR_ENVIRONMENT, gitdirenv);
+ if (gitworktree && chdir(gitworktree)) {
+ if (nongit_ok) {
+ if (wt_rel_gitdir && chdir(cwd))
+ die("Cannot come back to cwd");
+ *nongit_ok = 1;
+ return NULL;
+ }
+ if (wt_rel_gitdir)
+ die("Cannot change directory to working tree '%s'"
+ " from $%s", gitworktree, GIT_DIR_ENVIRONMENT);
+ else
+ die("Cannot change directory to working tree '%s'",
+ gitworktree);
}
+ if (!getcwd(worktree, sizeof(worktree)-1) || worktree[0] != '/')
+ die("Unable to read current working directory");
+ strcat(worktree, "/");
+ inside_work_tree = !prefixcmp(cwd, worktree);
- if (offset == len)
+ if (gitworktree && inside_work_tree && !prefixcmp(worktree, gitdir) &&
+ strcmp(worktree, gitdir)) {
+ inside_git_dir = 0;
+ }
+
+ if (!inside_work_tree) {
+ if (chdir(cwd))
+ die("Cannot come back to cwd");
return NULL;
+ }
- /* Make "offset" point to past the '/', and add a '/' at the end */
- offset++;
- cwd[len++] = '/';
- cwd[len] = 0;
- inside_git_dir = !prefixcmp(cwd + offset, ".git/");
- return cwd + offset;
+ if (!strcmp(cwd, worktree))
+ return NULL;
+ return cwd+strlen(worktree);
}
int git_config_perm(const char *var, const char *value)
diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh
new file mode 100755
index 0000000000..ec4996637d
--- /dev/null
+++ b/t/t1500-rev-parse.sh
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+test_description='test git rev-parse'
+. ./test-lib.sh
+
+test_rev_parse() {
+ name=$1
+ shift
+
+ test_expect_success "$name: is-bare-repository" \
+ "test '$1' = \"\$(git rev-parse --is-bare-repository)\""
+ shift
+ [ $# -eq 0 ] && return
+
+ test_expect_success "$name: is-inside-git-dir" \
+ "test '$1' = \"\$(git rev-parse --is-inside-git-dir)\""
+ shift
+ [ $# -eq 0 ] && return
+
+ test_expect_success "$name: is-inside-work-tree" \
+ "test '$1' = \"\$(git rev-parse --is-inside-work-tree)\""
+ shift
+ [ $# -eq 0 ] && return
+
+ test_expect_success "$name: prefix" \
+ "test '$1' = \"\$(git rev-parse --show-prefix)\""
+ shift
+ [ $# -eq 0 ] && return
+}
+
+test_rev_parse toplevel false false true ''
+
+cd .git || exit 1
+test_rev_parse .git/ false true true .git/
+cd objects || exit 1
+test_rev_parse .git/objects/ false true true .git/objects/
+cd ../.. || exit 1
+
+mkdir -p sub/dir || exit 1
+cd sub/dir || exit 1
+test_rev_parse subdirectory false false true sub/dir/
+cd ../.. || exit 1
+
+git config core.bare true
+test_rev_parse 'core.bare = true' true false true
+
+git config --unset core.bare
+test_rev_parse 'core.bare undefined' false false true
+
+mkdir work || exit 1
+cd work || exit 1
+export GIT_DIR=../.git
+export GIT_CONFIG="$GIT_DIR"/config
+
+git config core.bare false
+test_rev_parse 'GIT_DIR=../.git, core.bare = false' false false true ''
+
+git config core.bare true
+test_rev_parse 'GIT_DIR=../.git, core.bare = true' true false true ''
+
+git config --unset core.bare
+test_rev_parse 'GIT_DIR=../.git, core.bare undefined' false false true ''
+
+mv ../.git ../repo.git || exit 1
+export GIT_DIR=../repo.git
+export GIT_CONFIG="$GIT_DIR"/config
+
+git config core.bare false
+test_rev_parse 'GIT_DIR=../repo.git, core.bare = false' false false true ''
+
+git config core.bare true
+test_rev_parse 'GIT_DIR=../repo.git, core.bare = true' true false true ''
+
+git config --unset core.bare
+test_rev_parse 'GIT_DIR=../repo.git, core.bare undefined' true false true ''
+
+test_done
diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh
new file mode 100755
index 0000000000..aadeeab9ab
--- /dev/null
+++ b/t/t1501-worktree.sh
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+test_description='test separate work tree'
+. ./test-lib.sh
+
+test_rev_parse() {
+ name=$1
+ shift
+
+ test_expect_success "$name: is-bare-repository" \
+ "test '$1' = \"\$(git rev-parse --is-bare-repository)\""
+ shift
+ [ $# -eq 0 ] && return
+
+ test_expect_success "$name: is-inside-git-dir" \
+ "test '$1' = \"\$(git rev-parse --is-inside-git-dir)\""
+ shift
+ [ $# -eq 0 ] && return
+
+ test_expect_success "$name: is-inside-work-tree" \
+ "test '$1' = \"\$(git rev-parse --is-inside-work-tree)\""
+ shift
+ [ $# -eq 0 ] && return
+
+ test_expect_success "$name: prefix" \
+ "test '$1' = \"\$(git rev-parse --show-prefix)\""
+ shift
+ [ $# -eq 0 ] && return
+}
+
+mkdir -p work/sub/dir || exit 1
+mv .git repo.git || exit 1
+
+say "core.worktree = relative path"
+export GIT_DIR=repo.git
+export GIT_CONFIG=$GIT_DIR/config
+unset GIT_WORK_TREE
+git config core.worktree ../work
+test_rev_parse 'outside' false false false
+cd work || exit 1
+export GIT_DIR=../repo.git
+export GIT_CONFIG=$GIT_DIR/config
+test_rev_parse 'inside' false false true ''
+cd sub/dir || exit 1
+export GIT_DIR=../../../repo.git
+export GIT_CONFIG=$GIT_DIR/config
+test_rev_parse 'subdirectory' false false true sub/dir/
+cd ../../.. || exit 1
+
+say "core.worktree = absolute path"
+export GIT_DIR=$(pwd)/repo.git
+export GIT_CONFIG=$GIT_DIR/config
+git config core.worktree "$(pwd)/work"
+test_rev_parse 'outside' false false false
+cd work || exit 1
+test_rev_parse 'inside' false false true ''
+cd sub/dir || exit 1
+test_rev_parse 'subdirectory' false false true sub/dir/
+cd ../../.. || exit 1
+
+say "GIT_WORK_TREE=relative path (override core.worktree)"
+export GIT_DIR=$(pwd)/repo.git
+export GIT_CONFIG=$GIT_DIR/config
+git config core.worktree non-existent
+export GIT_WORK_TREE=work
+test_rev_parse 'outside' false false false
+cd work || exit 1
+export GIT_WORK_TREE=.
+test_rev_parse 'inside' false false true ''
+cd sub/dir || exit 1
+export GIT_WORK_TREE=../..
+test_rev_parse 'subdirectory' false false true sub/dir/
+cd ../../.. || exit 1
+
+mv work repo.git/work
+
+say "GIT_WORK_TREE=absolute path, work tree below git dir"
+export GIT_DIR=$(pwd)/repo.git
+export GIT_CONFIG=$GIT_DIR/config
+export GIT_WORK_TREE=$(pwd)/repo.git/work
+test_rev_parse 'outside' false false false
+cd repo.git || exit 1
+test_rev_parse 'in repo.git' false true false
+cd objects || exit 1
+test_rev_parse 'in repo.git/objects' false true false
+cd ../work || exit 1
+test_rev_parse 'in repo.git/work' false false true ''
+cd sub/dir || exit 1
+test_rev_parse 'in repo.git/sub/dir' false false true sub/dir/
+cd ../../../.. || exit 1
+
+test_done
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 8bf4cf49a2..78d7e87e86 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -26,6 +26,7 @@ GIT_COMMITTER_EMAIL=committer@example.com
GIT_COMMITTER_NAME='C O Mitter'
unset GIT_DIFF_OPTS
unset GIT_DIR
+unset GIT_WORK_TREE
unset GIT_EXTERNAL_DIFF
unset GIT_INDEX_FILE
unset GIT_OBJECT_DIRECTORY