[PATCH/RFC] introduce GIT_WORK_TREE environment variable

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



GIT_WORK_TREE can be used with GIT_DIR to specify the working tree.
As for GIT_DIR there is also the option `git
--work-tree=GIT_WORK_TREE` which overrides the environment variable
and a config setting core.worktree which is used as default value.

setup_git_directory_gently() is rewritten and does the
following now:

  find the repository directory ($GIT_DIR, ".git" in parent
  directories, ".")

  read the configuration (core.bare and core.worktree are used)

  if core.bare is not set assume the repository is not bare if a
  working tree is specified or guess based on the name of the
  repository directory

  for a bare repository:
    set GIT_DIR if it is not set already and stop (this wont change
    the directory even if the repository was found as .git in a
    parent directory)

  for a non-bare repository:
    if GIT_DIR is specified:
      use GIT_WORK_TREE, core.worktree or "." as working tree
    if the repository was found as .git in a parent directory:
      use the parent directory of the .git directory as working tree
    if the repository was found in ".":
      use "." as working tree

    set inside_git_dir and inside_working_tree based on getcwd() and
    prefixcmp()

is_bare_repository() is also changed to return true if the working
directory is outside of the working tree.

Signed-off-by: Matthias Lederhofer <matled@xxxxxxx>
---
This patch implements the outline for setup_git_directory_gently()
from <20070318211836.GA12456@xxxxxxxxxxxx>.

setup_git_directory_gently() reads the whole configuration and gives
it to git_default_config() too.  I can't think of any downsides to do
this, if this should not be done setup_git_directory_gently() could
also do a git_config_from_file(${GIT_CONFIG:-$GIT_DIR/.config}) and
only get the value of core.bare and core.worktree.
---
 Documentation/config.txt |    4 +
 Documentation/git.txt    |   17 ++++-
 cache.h                  |    2 +
 environment.c            |   11 +++-
 git.c                    |   12 +++-
 setup.c                  |  178 +++++++++++++++++++++++++++++++++++----------
 t/test-lib.sh            |    1 +
 7 files changed, 181 insertions(+), 44 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index cf1e040..52b1aa9 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -162,6 +162,10 @@ 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::
+	Path to the working tree when GIT_DIR is set.  This can be
+	overriden by GIT_WORK_TREE.
+
 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.txt b/Documentation/git.txt
index 31397dc..5ead10f 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
 -----------
@@ -83,6 +84,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 be used only
+	in combination with $GIT_DIR or '--git-dir'.  If GIT_DIR is
+	set but this option is not git will assume that the current
+	directory is also the working tree.
+	This can also be controlled by setting the GIT_WORK_TREE
+	environment variable.
+
 --bare::
 	Same as --git-dir=`pwd`.
 
@@ -327,6 +336,12 @@ 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 be used only
+	in combination with $GIT_DIR or '--git-dir'.  If GIT_DIR is
+	set but this option is not git will assume that the current
+	directory is also the working tree.
+
 git Commits
 ~~~~~~~~~~~
 'GIT_AUTHOR_NAME'::
diff --git a/cache.h b/cache.h
index 384b260..df47925 100644
--- a/cache.h
+++ b/cache.h
@@ -144,6 +144,7 @@ enum object_type {
 };
 
 #define GIT_DIR_ENVIRONMENT "GIT_DIR"
+#define GIT_WORKING_TREE_ENVIRONMENT "GIT_WORK_TREE"
 #define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
 #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
 #define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
@@ -164,6 +165,7 @@ extern char *get_graft_file(void);
 
 #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
 
+extern int inside_working_tree;
 extern const char **get_pathspec(const char *prefix, const char **pathspec);
 extern const char *setup_git_directory_gently(int *);
 extern const char *setup_git_directory(void);
diff --git a/environment.c b/environment.c
index 2231659..8db684a 100644
--- a/environment.c
+++ b/environment.c
@@ -60,8 +60,15 @@ static void setup_git_env(void)
 int is_bare_repository(void)
 {
 	const char *dir, *s;
-	if (0 <= is_bare_repository_cfg)
-		return is_bare_repository_cfg;
+	/* definitely bare */
+	if (is_bare_repository_cfg == 1)
+		return 1;
+	/* bare if cwd is outside of the working tree */
+	if (inside_working_tree >= 0)
+		return !inside_working_tree;
+	/* configuration says it is not bare */
+	if (is_bare_repository_cfg == 0)
+		return 0;
 
 	dir = get_git_dir();
 	if (!strcmp(dir, DEFAULT_GIT_DIR_ENVIRONMENT))
diff --git a/git.c b/git.c
index 5b1bc2a..9c282cd 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)
 {
@@ -68,6 +68,16 @@ static int handle_options(const char*** argv, int* argc)
 			(*argc)--;
 		} 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_WORKING_TREE_ENVIRONMENT, (*argv)[1], 1);
+			(*argv)++;
+			(*argc)--;
+		} else if (!prefixcmp(cmd, "--work-tree=")) {
+			setenv(GIT_WORKING_TREE_ENVIRONMENT, cmd + 11, 1);
 		} else if (!strcmp(cmd, "--bare")) {
 			static char git_dir[PATH_MAX+1];
 			setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 1);
diff --git a/setup.c b/setup.c
index a45ea83..ddbe852 100644
--- a/setup.c
+++ b/setup.c
@@ -192,67 +192,165 @@ int is_inside_git_dir(void)
 	return inside_git_dir;
 }
 
+static char *git_work_tree;
+
+static int git_setup_config(const char *var, const char *value)
+{
+	if (git_work_tree && !strcmp(var, "core.worktree")) {
+		strlcpy(git_work_tree, value, PATH_MAX);
+	}
+	return git_default_config(var, value);
+}
+
+int inside_working_tree = -1;
+
 const char *setup_git_directory_gently(int *nongit_ok)
 {
 	static char cwd[PATH_MAX+1];
+	char worktree[PATH_MAX+1], gitdir[PATH_MAX+1];
 	const char *gitdirenv;
-	int len, offset;
+	const char *gitwt = NULL;
+	int len;
+	int dotgit = 0;
+	int wt_rel_cwd = 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))
-			return NULL;
-		if (nongit_ok) {
-			*nongit_ok = 1;
-			return NULL;
+		if (!is_git_directory(gitdirenv)) {
+			if (nongit_ok) {
+				*nongit_ok = 1;
+				return NULL;
+			}
+			die("Not a git repository: '%s'", gitdirenv);
 		}
-		die("Not a git repository: '%s'", gitdirenv);
-	}
-
-	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;
-		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;
+	} else {
+		int offset;
+		/* keep 5 bytes for /.git and 40 for files in .git directory */
+		if (!getcwd(cwd, sizeof(cwd) - 5 - 40) || cwd[0] != '/')
+			die("Unable to read current working directory");
+		offset = strlen(cwd);
+		for (;;) {
+			strcat(cwd, "/.git");
+			if (is_git_directory(cwd)) {
+				dotgit = 1;
+				setenv(GIT_DIR_ENVIRONMENT, cwd, 1);
+				cwd[offset] = '\0';
+				strcpy(worktree, cwd);
+				gitwt = worktree;
+				break;
+			}
+			if (offset == 0) {
+				if (is_git_directory(".")) {
+					setenv(GIT_DIR_ENVIRONMENT, ".", 1);
+					strcpy(worktree, ".");
+					gitwt = worktree;
+					break;
 				}
 				if (nongit_ok) {
-					if (chdir(cwd))
-						die("Cannot come back to cwd");
 					*nongit_ok = 1;
 					return NULL;
 				}
 				die("Not a git repository");
 			}
-		} while (cwd[--offset] != '/');
+			cwd[offset] = '\0';
+			while (cwd[offset] != '/')
+				--offset;
+			cwd[offset] = '\0';
+		}
+		gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
+	}
+
+	if (!gitwt)
+		gitwt = getenv(GIT_WORKING_TREE_ENVIRONMENT);
+	if (gitwt && gitwt[0] != '/')
+		wt_rel_cwd = 1;
+	if (!gitwt) {
+		git_work_tree = worktree;
+		worktree[0] = '\0';
+	}
+	git_config(git_setup_config);
+	if (!gitwt) {
+		git_work_tree = NULL;
+		if (worktree[0])
+			gitwt = worktree;
 	}
 
-	if (offset == len)
+	/*
+	 * try to figure out if this is a bare repository:
+	 * if core.bare is undefined and a worktree is specified we go on and
+	 * try the specified worktree, otherwise use is_bare_repository() to
+	 * decide
+	 */
+	if (!(is_bare_repository_cfg == -1 && gitwt) && is_bare_repository())
+		return NULL;
+
+	if (!gitwt) {
+		wt_rel_cwd = 1;
+		strcpy(worktree, ".");
+		gitwt = worktree;
+	}
+
+	/* run getcwd in cwd, GIT_DIR and GIT_WORK_TREE */
+	if (!getcwd(cwd, sizeof(cwd)-1) || cwd[0] != '/')
+		die("Unable to read current working directory");
+	if (chdir(gitdirenv))
+		die("Cannot change directory to '%s'", gitdirenv);
+	if (!getcwd(gitdir, sizeof(gitdir)-1) || gitdir[0] != '/')
+		die("Unable to read current working directory");
+	/* gitwt is a relative path from the old cwd, go back first */
+	if (wt_rel_cwd && chdir(cwd))
+		die("Cannot come back to cwd");
+	if (chdir(gitwt)) {
+		if (wt_rel_cwd || gitwt[0] == '/')
+			die("Cannot change directory to working tree '%s'",
+				gitwt);
+		else
+			die("Cannot change directory to working tree '%s'"
+				" from $GIT_DIR", gitwt);
+	}
+	if (!getcwd(worktree, sizeof(worktree)-1) || worktree[0] != '/')
+		die("Unable to read current working directory");
+
+	len = strlen(cwd);
+	cwd[len] = '/';
+	cwd[len+1] = '\0';
+
+	len = strlen(worktree);
+	worktree[len] = '/';
+	worktree[len+1] = '\0';
+	inside_working_tree = !prefixcmp(cwd, worktree);
+
+	len = strlen(gitdir);
+	gitdir[len] = '/';
+	gitdir[len+1] = '\0';
+	/*
+	 * if we are inside worktree, worktree is below gitdir and
+	 * worktree != gitdir then we are not really in gitdir
+	 */
+	if (inside_working_tree && !prefixcmp(worktree, gitdir) &&
+	    strcmp(worktree, gitdir)) {
+		inside_git_dir = 0;
+	} else {
+		inside_git_dir = !prefixcmp(cwd, gitdir);
+	}
+	gitdir[len] = '\0';
+
+	if (!inside_working_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 (dotgit)
+		unsetenv(GIT_DIR_ENVIRONMENT);
+	else if (gitdirenv[0] != '/')
+		setenv(GIT_DIR_ENVIRONMENT, gitdir, 1);
+
+	if (!strcmp(cwd, worktree))
+		return NULL;
+	return cwd+strlen(worktree);
 }
 
 int git_config_perm(const char *var, const char *value)
diff --git a/t/test-lib.sh b/t/test-lib.sh
index c075474..77c6d23 100755
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -25,6 +25,7 @@ GIT_COMMITTER_EMAIL=committer@xxxxxxxxxxx
 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
-- 
1.5.1.rc2.621.g1fee7

-
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]