{"thread":{"id":"7454","subject":"[PATCH/RFC] introduce GIT_WORK_TREE environment variable","startedAt":"2007-03-28T14:15:05Z","lastAt":"2007-04-11T01:29:27Z","messageCount":12,"participants":["Matthias Lederhofer","Johannes Sixt","Dana How","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"38253","messageId":"20070328141505.GA16600@moooo.ath.cx","threadId":"7454","inReplyTo":null,"subject":"[PATCH/RFC] introduce GIT_WORK_TREE environment variable","fromName":"Matthias Lederhofer","fromEmail":"matled@gmx.net","sentAt":"2007-03-28T14:15:05Z","receivedAt":"2007-03-28T14:15:05Z","isPatch":true,"sender":{"key":"matled@gmx.net","avatar":null},"body":"GIT_WORK_TREE can be used with GIT_DIR to specify the working tree.\nAs for GIT_DIR there is also the option `git\n--work-tree=GIT_WORK_TREE` which overrides the environment variable\nand a config setting core.worktree which is used as default value.\n\nsetup_git_directory_gently() is rewritten and does the\nfollowing now:\n\n  find the repository directory ($GIT_DIR, \".git\" in parent\n  directories, \".\")\n\n  read the configuration (core.bare and core.worktree are used)\n\n  if core.bare is not set assume the repository is not bare if a\n  working tree is specified or guess based on the name of the\n  repository directory\n\n  for a bare repository:\n    set GIT_DIR if it is not set already and stop (this wont change\n    the directory even if the repository was found as .git in a\n    parent directory)\n\n  for a non-bare repository:\n    if GIT_DIR is specified:\n      use GIT_WORK_TREE, core.worktree or \".\" as working tree\n    if the repository was found as .git in a parent directory:\n      use the parent directory of the .git directory as working tree\n    if the repository was found in \".\":\n      use \".\" as working tree\n\n    set inside_git_dir and inside_working_tree based on getcwd() and\n    prefixcmp()\n\nis_bare_repository() is also changed to return true if the working\ndirectory is outside of the working tree.\n\nSigned-off-by: Matthias Lederhofer <matled@gmx.net>\n---\nThis patch implements the outline for setup_git_directory_gently()\nfrom <20070318211836.GA12456@moooo.ath.cx>.\n\nsetup_git_directory_gently() reads the whole configuration and gives\nit to git_default_config() too.  I can't think of any downsides to do\nthis, if this should not be done setup_git_directory_gently() could\nalso do a git_config_from_file(${GIT_CONFIG:-$GIT_DIR/.config}) and\nonly get the value of core.bare and core.worktree.\n---\n Documentation/config.txt |    4 +\n Documentation/git.txt    |   17 ++++-\n cache.h                  |    2 +\n environment.c            |   11 +++-\n git.c                    |   12 +++-\n setup.c                  |  178 +++++++++++++++++++++++++++++++++++----------\n t/test-lib.sh            |    1 +\n 7 files changed, 181 insertions(+), 44 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex cf1e040..52b1aa9 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -162,6 +162,10 @@ repository that ends in \"/.git\" is assumed to be not bare (bare =\n false), while all other repositories are assumed to be bare (bare\n = true).\n \n+core.worktree::\n+\tPath to the working tree when GIT_DIR is set.  This can be\n+\toverriden by GIT_WORK_TREE.\n+\n core.logAllRefUpdates::\n \tUpdates to a ref <ref> is logged to the file\n \t\"$GIT_DIR/logs/<ref>\", by appending the new and old\ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex 31397dc..5ead10f 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -10,7 +10,8 @@ SYNOPSIS\n --------\n [verse]\n 'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate]\n-    [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]\n+    [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]\n+    [--help] COMMAND [ARGS]\n \n DESCRIPTION\n -----------\n@@ -83,6 +84,14 @@ OPTIONS\n \tSet the path to the repository. This can also be controlled by\n \tsetting the GIT_DIR environment variable.\n \n+--work-tree=<path>::\n+\tSet the path to the working tree.  The value will be used only\n+\tin combination with $GIT_DIR or '--git-dir'.  If GIT_DIR is\n+\tset but this option is not git will assume that the current\n+\tdirectory is also the working tree.\n+\tThis can also be controlled by setting the GIT_WORK_TREE\n+\tenvironment variable.\n+\n --bare::\n \tSame as --git-dir=`pwd`.\n \n@@ -327,6 +336,12 @@ git so take care if using Cogito etc.\n \tspecifies a path to use instead of the default `.git`\n \tfor the base of the repository.\n \n+'GIT_WORK_TREE'::\n+\tSet the path to the working tree.  The value will be used only\n+\tin combination with $GIT_DIR or '--git-dir'.  If GIT_DIR is\n+\tset but this option is not git will assume that the current\n+\tdirectory is also the working tree.\n+\n git Commits\n ~~~~~~~~~~~\n 'GIT_AUTHOR_NAME'::\ndiff --git a/cache.h b/cache.h\nindex 384b260..df47925 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -144,6 +144,7 @@ enum object_type {\n };\n \n #define GIT_DIR_ENVIRONMENT \"GIT_DIR\"\n+#define GIT_WORKING_TREE_ENVIRONMENT \"GIT_WORK_TREE\"\n #define DEFAULT_GIT_DIR_ENVIRONMENT \".git\"\n #define DB_ENVIRONMENT \"GIT_OBJECT_DIRECTORY\"\n #define INDEX_ENVIRONMENT \"GIT_INDEX_FILE\"\n@@ -164,6 +165,7 @@ extern char *get_graft_file(void);\n \n #define ALTERNATE_DB_ENVIRONMENT \"GIT_ALTERNATE_OBJECT_DIRECTORIES\"\n \n+extern int inside_working_tree;\n extern const char **get_pathspec(const char *prefix, const char **pathspec);\n extern const char *setup_git_directory_gently(int *);\n extern const char *setup_git_directory(void);\ndiff --git a/environment.c b/environment.c\nindex 2231659..8db684a 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -60,8 +60,15 @@ static void setup_git_env(void)\n int is_bare_repository(void)\n {\n \tconst char *dir, *s;\n-\tif (0 <= is_bare_repository_cfg)\n-\t\treturn is_bare_repository_cfg;\n+\t/* definitely bare */\n+\tif (is_bare_repository_cfg == 1)\n+\t\treturn 1;\n+\t/* bare if cwd is outside of the working tree */\n+\tif (inside_working_tree >= 0)\n+\t\treturn !inside_working_tree;\n+\t/* configuration says it is not bare */\n+\tif (is_bare_repository_cfg == 0)\n+\t\treturn 0;\n \n \tdir = get_git_dir();\n \tif (!strcmp(dir, DEFAULT_GIT_DIR_ENVIRONMENT))\ndiff --git a/git.c b/git.c\nindex 5b1bc2a..9c282cd 100644\n--- a/git.c\n+++ b/git.c\n@@ -4,7 +4,7 @@\n #include \"quote.h\"\n \n const char git_usage_string[] =\n-\t\"git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]\";\n+\t\"git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]\";\n \n static void prepend_to_path(const char *dir, int len)\n {\n@@ -68,6 +68,16 @@ static int handle_options(const char*** argv, int* argc)\n \t\t\t(*argc)--;\n \t\t} else if (!prefixcmp(cmd, \"--git-dir=\")) {\n \t\t\tsetenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);\n+\t\t} else if (!strcmp(cmd, \"--work-tree\")) {\n+\t\t\tif (*argc < 2) {\n+\t\t\t\tfprintf(stderr, \"No directory given for --work-tree.\\n\" );\n+\t\t\t\tusage(git_usage_string);\n+\t\t\t}\n+\t\t\tsetenv(GIT_WORKING_TREE_ENVIRONMENT, (*argv)[1], 1);\n+\t\t\t(*argv)++;\n+\t\t\t(*argc)--;\n+\t\t} else if (!prefixcmp(cmd, \"--work-tree=\")) {\n+\t\t\tsetenv(GIT_WORKING_TREE_ENVIRONMENT, cmd + 11, 1);\n \t\t} else if (!strcmp(cmd, \"--bare\")) {\n \t\t\tstatic char git_dir[PATH_MAX+1];\n \t\t\tsetenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 1);\ndiff --git a/setup.c b/setup.c\nindex a45ea83..ddbe852 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -192,67 +192,165 @@ int is_inside_git_dir(void)\n \treturn inside_git_dir;\n }\n \n+static char *git_work_tree;\n+\n+static int git_setup_config(const char *var, const char *value)\n+{\n+\tif (git_work_tree && !strcmp(var, \"core.worktree\")) {\n+\t\tstrlcpy(git_work_tree, value, PATH_MAX);\n+\t}\n+\treturn git_default_config(var, value);\n+}\n+\n+int inside_working_tree = -1;\n+\n const char *setup_git_directory_gently(int *nongit_ok)\n {\n \tstatic char cwd[PATH_MAX+1];\n+\tchar worktree[PATH_MAX+1], gitdir[PATH_MAX+1];\n \tconst char *gitdirenv;\n-\tint len, offset;\n+\tconst char *gitwt = NULL;\n+\tint len;\n+\tint dotgit = 0;\n+\tint wt_rel_cwd = 0;\n \n-\t/*\n-\t * If GIT_DIR is set explicitly, we're not going\n-\t * to do any discovery, but we still do repository\n-\t * validation.\n-\t */\n \tgitdirenv = getenv(GIT_DIR_ENVIRONMENT);\n \tif (gitdirenv) {\n \t\tif (PATH_MAX - 40 < strlen(gitdirenv))\n \t\t\tdie(\"'$%s' too big\", GIT_DIR_ENVIRONMENT);\n-\t\tif (is_git_directory(gitdirenv))\n-\t\t\treturn NULL;\n-\t\tif (nongit_ok) {\n-\t\t\t*nongit_ok = 1;\n-\t\t\treturn NULL;\n+\t\tif (!is_git_directory(gitdirenv)) {\n+\t\t\tif (nongit_ok) {\n+\t\t\t\t*nongit_ok = 1;\n+\t\t\t\treturn NULL;\n+\t\t\t}\n+\t\t\tdie(\"Not a git repository: '%s'\", gitdirenv);\n \t\t}\n-\t\tdie(\"Not a git repository: '%s'\", gitdirenv);\n-\t}\n-\n-\tif (!getcwd(cwd, sizeof(cwd)-1) || cwd[0] != '/')\n-\t\tdie(\"Unable to read current working directory\");\n-\n-\toffset = len = strlen(cwd);\n-\tfor (;;) {\n-\t\tif (is_git_directory(\".git\"))\n-\t\t\tbreak;\n-\t\tchdir(\"..\");\n-\t\tdo {\n-\t\t\tif (!offset) {\n-\t\t\t\tif (is_git_directory(cwd)) {\n-\t\t\t\t\tif (chdir(cwd))\n-\t\t\t\t\t\tdie(\"Cannot come back to cwd\");\n-\t\t\t\t\tsetenv(GIT_DIR_ENVIRONMENT, cwd, 1);\n-\t\t\t\t\tinside_git_dir = 1;\n-\t\t\t\t\treturn NULL;\n+\t} else {\n+\t\tint offset;\n+\t\t/* keep 5 bytes for /.git and 40 for files in .git directory */\n+\t\tif (!getcwd(cwd, sizeof(cwd) - 5 - 40) || cwd[0] != '/')\n+\t\t\tdie(\"Unable to read current working directory\");\n+\t\toffset = strlen(cwd);\n+\t\tfor (;;) {\n+\t\t\tstrcat(cwd, \"/.git\");\n+\t\t\tif (is_git_directory(cwd)) {\n+\t\t\t\tdotgit = 1;\n+\t\t\t\tsetenv(GIT_DIR_ENVIRONMENT, cwd, 1);\n+\t\t\t\tcwd[offset] = '\\0';\n+\t\t\t\tstrcpy(worktree, cwd);\n+\t\t\t\tgitwt = worktree;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tif (offset == 0) {\n+\t\t\t\tif (is_git_directory(\".\")) {\n+\t\t\t\t\tsetenv(GIT_DIR_ENVIRONMENT, \".\", 1);\n+\t\t\t\t\tstrcpy(worktree, \".\");\n+\t\t\t\t\tgitwt = worktree;\n+\t\t\t\t\tbreak;\n \t\t\t\t}\n \t\t\t\tif (nongit_ok) {\n-\t\t\t\t\tif (chdir(cwd))\n-\t\t\t\t\t\tdie(\"Cannot come back to cwd\");\n \t\t\t\t\t*nongit_ok = 1;\n \t\t\t\t\treturn NULL;\n \t\t\t\t}\n \t\t\t\tdie(\"Not a git repository\");\n \t\t\t}\n-\t\t} while (cwd[--offset] != '/');\n+\t\t\tcwd[offset] = '\\0';\n+\t\t\twhile (cwd[offset] != '/')\n+\t\t\t\t--offset;\n+\t\t\tcwd[offset] = '\\0';\n+\t\t}\n+\t\tgitdirenv = getenv(GIT_DIR_ENVIRONMENT);\n+\t}\n+\n+\tif (!gitwt)\n+\t\tgitwt = getenv(GIT_WORKING_TREE_ENVIRONMENT);\n+\tif (gitwt && gitwt[0] != '/')\n+\t\twt_rel_cwd = 1;\n+\tif (!gitwt) {\n+\t\tgit_work_tree = worktree;\n+\t\tworktree[0] = '\\0';\n+\t}\n+\tgit_config(git_setup_config);\n+\tif (!gitwt) {\n+\t\tgit_work_tree = NULL;\n+\t\tif (worktree[0])\n+\t\t\tgitwt = worktree;\n \t}\n \n-\tif (offset == len)\n+\t/*\n+\t * try to figure out if this is a bare repository:\n+\t * if core.bare is undefined and a worktree is specified we go on and\n+\t * try the specified worktree, otherwise use is_bare_repository() to\n+\t * decide\n+\t */\n+\tif (!(is_bare_repository_cfg == -1 && gitwt) && is_bare_repository())\n+\t\treturn NULL;\n+\n+\tif (!gitwt) {\n+\t\twt_rel_cwd = 1;\n+\t\tstrcpy(worktree, \".\");\n+\t\tgitwt = worktree;\n+\t}\n+\n+\t/* run getcwd in cwd, GIT_DIR and GIT_WORK_TREE */\n+\tif (!getcwd(cwd, sizeof(cwd)-1) || cwd[0] != '/')\n+\t\tdie(\"Unable to read current working directory\");\n+\tif (chdir(gitdirenv))\n+\t\tdie(\"Cannot change directory to '%s'\", gitdirenv);\n+\tif (!getcwd(gitdir, sizeof(gitdir)-1) || gitdir[0] != '/')\n+\t\tdie(\"Unable to read current working directory\");\n+\t/* gitwt is a relative path from the old cwd, go back first */\n+\tif (wt_rel_cwd && chdir(cwd))\n+\t\tdie(\"Cannot come back to cwd\");\n+\tif (chdir(gitwt)) {\n+\t\tif (wt_rel_cwd || gitwt[0] == '/')\n+\t\t\tdie(\"Cannot change directory to working tree '%s'\",\n+\t\t\t\tgitwt);\n+\t\telse\n+\t\t\tdie(\"Cannot change directory to working tree '%s'\"\n+\t\t\t\t\" from $GIT_DIR\", gitwt);\n+\t}\n+\tif (!getcwd(worktree, sizeof(worktree)-1) || worktree[0] != '/')\n+\t\tdie(\"Unable to read current working directory\");\n+\n+\tlen = strlen(cwd);\n+\tcwd[len] = '/';\n+\tcwd[len+1] = '\\0';\n+\n+\tlen = strlen(worktree);\n+\tworktree[len] = '/';\n+\tworktree[len+1] = '\\0';\n+\tinside_working_tree = !prefixcmp(cwd, worktree);\n+\n+\tlen = strlen(gitdir);\n+\tgitdir[len] = '/';\n+\tgitdir[len+1] = '\\0';\n+\t/*\n+\t * if we are inside worktree, worktree is below gitdir and\n+\t * worktree != gitdir then we are not really in gitdir\n+\t */\n+\tif (inside_working_tree && !prefixcmp(worktree, gitdir) &&\n+\t    strcmp(worktree, gitdir)) {\n+\t\tinside_git_dir = 0;\n+\t} else {\n+\t\tinside_git_dir = !prefixcmp(cwd, gitdir);\n+\t}\n+\tgitdir[len] = '\\0';\n+\n+\tif (!inside_working_tree) {\n+\t\tif (chdir(cwd))\n+\t\t\tdie(\"Cannot come back to cwd\");\n \t\treturn NULL;\n+\t}\n \n-\t/* Make \"offset\" point to past the '/', and add a '/' at the end */\n-\toffset++;\n-\tcwd[len++] = '/';\n-\tcwd[len] = 0;\n-\tinside_git_dir = !prefixcmp(cwd + offset, \".git/\");\n-\treturn cwd + offset;\n+\tif (dotgit)\n+\t\tunsetenv(GIT_DIR_ENVIRONMENT);\n+\telse if (gitdirenv[0] != '/')\n+\t\tsetenv(GIT_DIR_ENVIRONMENT, gitdir, 1);\n+\n+\tif (!strcmp(cwd, worktree))\n+\t\treturn NULL;\n+\treturn cwd+strlen(worktree);\n }\n \n int git_config_perm(const char *var, const char *value)\ndiff --git a/t/test-lib.sh b/t/test-lib.sh\nindex c075474..77c6d23 100755\n--- a/t/test-lib.sh\n+++ b/t/test-lib.sh\n@@ -25,6 +25,7 @@ GIT_COMMITTER_EMAIL=committer@example.com\n GIT_COMMITTER_NAME='C O Mitter'\n unset GIT_DIFF_OPTS\n unset GIT_DIR\n+unset GIT_WORK_TREE\n unset GIT_EXTERNAL_DIFF\n unset GIT_INDEX_FILE\n unset GIT_OBJECT_DIRECTORY\n-- \n1.5.1.rc2.621.g1fee7\n"},{"id":"38256","messageId":"460A8D9F.75323F65@eudaptics.com","threadId":"7454","inReplyTo":"20070328141505.GA16600@moooo.ath.cx","subject":"Re: [PATCH/RFC] introduce GIT_WORK_TREE environment variable","fromName":"Johannes Sixt","fromEmail":"j.sixt@eudaptics.com","sentAt":"2007-03-28T15:45:35Z","receivedAt":"2007-03-28T15:45:35Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Matthias Lederhofer wrote:\n> diff --git a/t/test-lib.sh b/t/test-lib.sh\n> index c075474..77c6d23 100755\n> --- a/t/test-lib.sh\n> +++ b/t/test-lib.sh\n> @@ -25,6 +25,7 @@ GIT_COMMITTER_EMAIL=committer@example.com\n>  GIT_COMMITTER_NAME='C O Mitter'\n>  unset GIT_DIFF_OPTS\n>  unset GIT_DIR\n> +unset GIT_WORK_TREE\n>  unset GIT_EXTERNAL_DIFF\n>  unset GIT_INDEX_FILE\n>  unset GIT_OBJECT_DIRECTORY\n\nconnect.c:git_connect() unsets some environment variables, most\nprominently GIT_DIR; I think it should also unset GIT_WORK_TREE.\n\n-- Hannes\n"},{"id":"38291","messageId":"20070329082202.GA4974@moooo.ath.cx","threadId":"7454","inReplyTo":"20070328141505.GA16600@moooo.ath.cx","subject":"Re: [PATCH/RFC] introduce GIT_WORK_TREE environment variable","fromName":"Matthias Lederhofer","fromEmail":"matled@gmx.net","sentAt":"2007-03-29T08:22:03Z","receivedAt":"2007-03-29T08:22:03Z","isPatch":true,"sender":{"key":"matled@gmx.net","avatar":null},"body":"Matthias Lederhofer <matled@gmx.net> wrote:\n> +\tgit_config(git_setup_config);\n\nWhen $GIT_DIR was not set and the repository is found as a .git\ndirectory $GIT_DIR will be the full path to the .git directory when\ncalling git_config().  git_config() calls git_path() which calls\nget_git_dir() which calls getenv(\"GIT_DIR\").\nI'm not sure this is defined behaviour at all:\n    \n    char *foo = getenv(\"FOO\");\n    unsetenv(\"FOO\");\n\nDoes foo still point to the old content of the FOO environment\nvariable?  If it does get_git_dir() will always return the full path\nto the repository directory.\n\nI can think of two ways to solve this (in case this is a problem):\n1. Add a function to environment.c which will cause setup_git_env to\n   be called again (either directly or by setting all the pointers to\n   NULL again).\n2. Use git_config_from_file() instead of git_config().  This will\n   probably duplicate code from git_config().\n"},{"id":"38618","messageId":"20070404140853.GA31372@moooo.ath.cx","threadId":"7454","inReplyTo":"20070328141505.GA16600@moooo.ath.cx","subject":"Re: [PATCH/RFC] introduce GIT_WORK_TREE environment variable","fromName":"Matthias Lederhofer","fromEmail":"matled@gmx.net","sentAt":"2007-04-04T14:08:53Z","receivedAt":"2007-04-04T14:08:53Z","isPatch":true,"sender":{"key":"matled@gmx.net","avatar":null},"body":"As 1.5.1 is released now: any comments on this patch?\n"},{"id":"38636","messageId":"56b7f5510704040959n17795c1aj5792fef712bae1b8@mail.gmail.com","threadId":"7454","inReplyTo":"20070404140853.GA31372@moooo.ath.cx","subject":"Re: [PATCH/RFC] introduce GIT_WORK_TREE environment variable","fromName":"Dana How","fromEmail":"danahow@gmail.com","sentAt":"2007-04-04T16:59:57Z","receivedAt":"2007-04-04T16:59:57Z","isPatch":true,"sender":{"key":"danahow@gmail.com","avatar":null},"body":"On 4/4/07, Matthias Lederhofer <matled@gmx.net> wrote:\n> As 1.5.1 is released now: any comments on this patch?\n\nDitto for \"Handling large repositories\".\nI guess I should resubmit it against 1.5.1.\n\nThanks,\n-- \nDana L. How  danahow@gmail.com  +1 650 804 5991 cell\n"},{"id":"38643","messageId":"7vslbg2d2t.fsf@assigned-by-dhcp.cox.net","threadId":"7454","inReplyTo":"20070404140853.GA31372@moooo.ath.cx","subject":"Re: [PATCH/RFC] introduce GIT_WORK_TREE environment variable","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-04-04T18:45:30Z","receivedAt":"2007-04-04T18:45:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Matthias Lederhofer <matled@gmx.net> writes:\n\n> As 1.5.1 is released now: any comments on this patch?\n\nMy impression was that you already received a couple responses\nwhich were not yet answered.\n"},{"id":"38649","messageId":"20070404200948.GA22782@moooo.ath.cx","threadId":"7454","inReplyTo":"20070328141505.GA16600@moooo.ath.cx","subject":"[PATCH] export setup_git_env()","fromName":"Matthias Lederhofer","fromEmail":"matled@gmx.net","sentAt":"2007-04-04T20:09:48Z","receivedAt":"2007-04-04T20:09:48Z","isPatch":true,"sender":{"key":"matled@gmx.net","avatar":null},"body":"After changing environment variables used by environment.c\nwe need to call getenv() again.  setup_git_env() does this\nand can now be used from other files too.\n\nSigned-off-by: Matthias Lederhofer <matled@gmx.net>\n---\n cache.h       |    1 +\n environment.c |    2 +-\n 2 files changed, 2 insertions(+), 1 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 384b260..7b49258 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -154,6 +154,7 @@ enum object_type {\n #define EXEC_PATH_ENVIRONMENT \"GIT_EXEC_PATH\"\n \n extern int is_bare_repository_cfg;\n+extern void setup_git_env(void);\n extern int is_bare_repository(void);\n extern int is_inside_git_dir(void);\n extern const char *get_git_dir(void);\ndiff --git a/environment.c b/environment.c\nindex 2231659..713a011 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -35,7 +35,7 @@ int auto_crlf = 0;\t/* 1: both ways, -1: only when adding git objects */\n static const char *git_dir;\n static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file;\n \n-static void setup_git_env(void)\n+void setup_git_env(void)\n {\n \tgit_dir = getenv(GIT_DIR_ENVIRONMENT);\n \tif (!git_dir)\n-- \n1.5.1.4.g446af-dirty\n"},{"id":"38650","messageId":"20070404201313.GB22782@moooo.ath.cx","threadId":"7454","inReplyTo":"20070328141505.GA16600@moooo.ath.cx","subject":"[PATCH(amend)] introduce GIT_WORK_TREE environment variable","fromName":"Matthias Lederhofer","fromEmail":"matled@gmx.net","sentAt":"2007-04-04T20:13:13Z","receivedAt":"2007-04-04T20:13:13Z","isPatch":true,"sender":{"key":"matled@gmx.net","avatar":null},"body":"GIT_WORK_TREE can be used with GIT_DIR to specify the working tree.\nAs for GIT_DIR there is also the option `git\n--work-tree=GIT_WORK_TREE` which overrides the environment\nvariable and a config setting core.worktree which is used as\ndefault value.\n\nsetup_git_directory_gently() is rewritten and does the\nfollowing now:\n\n  find the repository directory ($GIT_DIR, \".git\" in parent\n  directories, \".\")\n\n  read the configuration (core.bare and core.worktree are used)\n\n  if core.bare is not set assume the repository is not bare if a\n  working tree is specified or guess based on the name of the\n  repository directory\n\n  for a bare repository:\n    set GIT_DIR if it is not set already and stop (this wont change\n    the directory even if the repository was found as .git in a\n    parent directory)\n\n  for a non-bare repository:\n    if GIT_DIR is specified:\n      use GIT_WORK_TREE, core.worktree or \".\" as working tree\n    if the repository was found as .git in a parent directory:\n      use the parent directory of the .git directory as working tree\n    if the repository was found in \".\":\n      use \".\" as working tree\n\n    set inside_git_dir and inside_working_tree based on getcwd() and\n    prefixcmp()\n\nis_bare_repository() is also changed to return true if the working\ndirectory is outside of the working tree.\n\nSigned-off-by: Matthias Lederhofer <matled@gmx.net>\n---\nChanges:\n    - unsetenv(GIT_WORKING_TREE_ENVIRONMENT) in git_connect(), noted\n      by Johannes Sixt\n      I also looked at the other places using GIT_DIR_ENVIRONMENT to\n      see if I missed something similar\n    - call setup_git_env() from setup_git_directory_gently() if needed\n---\n Documentation/config.txt |    4 +\n Documentation/git.txt    |   17 ++++-\n cache.h                  |    2 +\n connect.c                |    1 +\n environment.c            |   11 +++-\n git.c                    |   12 +++-\n setup.c                  |  181 ++++++++++++++++++++++++++++++++++++----------\n t/test-lib.sh            |    1 +\n 8 files changed, 185 insertions(+), 44 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex cf1e040..52b1aa9 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -162,6 +162,10 @@ repository that ends in \"/.git\" is assumed to be not bare (bare =\n false), while all other repositories are assumed to be bare (bare\n = true).\n \n+core.worktree::\n+\tPath to the working tree when GIT_DIR is set.  This can be\n+\toverriden by GIT_WORK_TREE.\n+\n core.logAllRefUpdates::\n \tUpdates to a ref <ref> is logged to the file\n \t\"$GIT_DIR/logs/<ref>\", by appending the new and old\ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex 9defc33..b37f9fd 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -10,7 +10,8 @@ SYNOPSIS\n --------\n [verse]\n 'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate]\n-    [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]\n+    [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]\n+    [--help] COMMAND [ARGS]\n \n DESCRIPTION\n -----------\n@@ -89,6 +90,14 @@ OPTIONS\n \tSet the path to the repository. This can also be controlled by\n \tsetting the GIT_DIR environment variable.\n \n+--work-tree=<path>::\n+\tSet the path to the working tree.  The value will be used only\n+\tin combination with $GIT_DIR or '--git-dir'.  If GIT_DIR is\n+\tset but this option is not git will assume that the current\n+\tdirectory is also the working tree.\n+\tThis can also be controlled by setting the GIT_WORK_TREE\n+\tenvironment variable.\n+\n --bare::\n \tSame as --git-dir=`pwd`.\n \n@@ -333,6 +342,12 @@ git so take care if using Cogito etc.\n \tspecifies a path to use instead of the default `.git`\n \tfor the base of the repository.\n \n+'GIT_WORK_TREE'::\n+\tSet the path to the working tree.  The value will be used only\n+\tin combination with $GIT_DIR or '--git-dir'.  If GIT_DIR is\n+\tset but this option is not git will assume that the current\n+\tdirectory is also the working tree.\n+\n git Commits\n ~~~~~~~~~~~\n 'GIT_AUTHOR_NAME'::\ndiff --git a/cache.h b/cache.h\nindex 7b49258..e163a76 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -144,6 +144,7 @@ enum object_type {\n };\n \n #define GIT_DIR_ENVIRONMENT \"GIT_DIR\"\n+#define GIT_WORKING_TREE_ENVIRONMENT \"GIT_WORK_TREE\"\n #define DEFAULT_GIT_DIR_ENVIRONMENT \".git\"\n #define DB_ENVIRONMENT \"GIT_OBJECT_DIRECTORY\"\n #define INDEX_ENVIRONMENT \"GIT_INDEX_FILE\"\n@@ -165,6 +166,7 @@ extern char *get_graft_file(void);\n \n #define ALTERNATE_DB_ENVIRONMENT \"GIT_ALTERNATE_OBJECT_DIRECTORIES\"\n \n+extern int inside_working_tree;\n extern const char **get_pathspec(const char *prefix, const char **pathspec);\n extern const char *setup_git_directory_gently(int *);\n extern const char *setup_git_directory(void);\ndiff --git a/connect.c b/connect.c\nindex da89c9c..ff2c459 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -773,6 +773,7 @@ pid_t git_connect(int fd[2], char *url, const char *prog)\n \t\t\tunsetenv(ALTERNATE_DB_ENVIRONMENT);\n \t\t\tunsetenv(DB_ENVIRONMENT);\n \t\t\tunsetenv(GIT_DIR_ENVIRONMENT);\n+\t\t\tunsetenv(GIT_WORKING_TREE_ENVIRONMENT);\n \t\t\tunsetenv(GRAFT_ENVIRONMENT);\n \t\t\tunsetenv(INDEX_ENVIRONMENT);\n \t\t\texeclp(\"sh\", \"sh\", \"-c\", command, NULL);\ndiff --git a/environment.c b/environment.c\nindex 713a011..769d409 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -60,8 +60,15 @@ void setup_git_env(void)\n int is_bare_repository(void)\n {\n \tconst char *dir, *s;\n-\tif (0 <= is_bare_repository_cfg)\n-\t\treturn is_bare_repository_cfg;\n+\t/* definitely bare */\n+\tif (is_bare_repository_cfg == 1)\n+\t\treturn 1;\n+\t/* bare if cwd is outside of the working tree */\n+\tif (inside_working_tree >= 0)\n+\t\treturn !inside_working_tree;\n+\t/* configuration says it is not bare */\n+\tif (is_bare_repository_cfg == 0)\n+\t\treturn 0;\n \n \tdir = get_git_dir();\n \tif (!strcmp(dir, DEFAULT_GIT_DIR_ENVIRONMENT))\ndiff --git a/git.c b/git.c\nindex 5b1bc2a..9c282cd 100644\n--- a/git.c\n+++ b/git.c\n@@ -4,7 +4,7 @@\n #include \"quote.h\"\n \n const char git_usage_string[] =\n-\t\"git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]\";\n+\t\"git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]\";\n \n static void prepend_to_path(const char *dir, int len)\n {\n@@ -68,6 +68,16 @@ static int handle_options(const char*** argv, int* argc)\n \t\t\t(*argc)--;\n \t\t} else if (!prefixcmp(cmd, \"--git-dir=\")) {\n \t\t\tsetenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);\n+\t\t} else if (!strcmp(cmd, \"--work-tree\")) {\n+\t\t\tif (*argc < 2) {\n+\t\t\t\tfprintf(stderr, \"No directory given for --work-tree.\\n\" );\n+\t\t\t\tusage(git_usage_string);\n+\t\t\t}\n+\t\t\tsetenv(GIT_WORKING_TREE_ENVIRONMENT, (*argv)[1], 1);\n+\t\t\t(*argv)++;\n+\t\t\t(*argc)--;\n+\t\t} else if (!prefixcmp(cmd, \"--work-tree=\")) {\n+\t\t\tsetenv(GIT_WORKING_TREE_ENVIRONMENT, cmd + 11, 1);\n \t\t} else if (!strcmp(cmd, \"--bare\")) {\n \t\t\tstatic char git_dir[PATH_MAX+1];\n \t\t\tsetenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 1);\ndiff --git a/setup.c b/setup.c\nindex a45ea83..794edcf 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -192,67 +192,168 @@ int is_inside_git_dir(void)\n \treturn inside_git_dir;\n }\n \n+static char *git_work_tree;\n+\n+static int git_setup_config(const char *var, const char *value)\n+{\n+\tif (git_work_tree && !strcmp(var, \"core.worktree\")) {\n+\t\tstrlcpy(git_work_tree, value, PATH_MAX);\n+\t}\n+\treturn git_default_config(var, value);\n+}\n+\n+int inside_working_tree = -1;\n+\n const char *setup_git_directory_gently(int *nongit_ok)\n {\n \tstatic char cwd[PATH_MAX+1];\n+\tchar worktree[PATH_MAX+1], gitdir[PATH_MAX+1];\n \tconst char *gitdirenv;\n-\tint len, offset;\n+\tconst char *gitwt = NULL;\n+\tint len;\n+\tint dotgit = 0;\n+\tint wt_rel_cwd = 0;\n \n-\t/*\n-\t * If GIT_DIR is set explicitly, we're not going\n-\t * to do any discovery, but we still do repository\n-\t * validation.\n-\t */\n \tgitdirenv = getenv(GIT_DIR_ENVIRONMENT);\n \tif (gitdirenv) {\n \t\tif (PATH_MAX - 40 < strlen(gitdirenv))\n \t\t\tdie(\"'$%s' too big\", GIT_DIR_ENVIRONMENT);\n-\t\tif (is_git_directory(gitdirenv))\n-\t\t\treturn NULL;\n-\t\tif (nongit_ok) {\n-\t\t\t*nongit_ok = 1;\n-\t\t\treturn NULL;\n+\t\tif (!is_git_directory(gitdirenv)) {\n+\t\t\tif (nongit_ok) {\n+\t\t\t\t*nongit_ok = 1;\n+\t\t\t\treturn NULL;\n+\t\t\t}\n+\t\t\tdie(\"Not a git repository: '%s'\", gitdirenv);\n \t\t}\n-\t\tdie(\"Not a git repository: '%s'\", gitdirenv);\n-\t}\n-\n-\tif (!getcwd(cwd, sizeof(cwd)-1) || cwd[0] != '/')\n-\t\tdie(\"Unable to read current working directory\");\n-\n-\toffset = len = strlen(cwd);\n-\tfor (;;) {\n-\t\tif (is_git_directory(\".git\"))\n-\t\t\tbreak;\n-\t\tchdir(\"..\");\n-\t\tdo {\n-\t\t\tif (!offset) {\n-\t\t\t\tif (is_git_directory(cwd)) {\n-\t\t\t\t\tif (chdir(cwd))\n-\t\t\t\t\t\tdie(\"Cannot come back to cwd\");\n-\t\t\t\t\tsetenv(GIT_DIR_ENVIRONMENT, cwd, 1);\n-\t\t\t\t\tinside_git_dir = 1;\n-\t\t\t\t\treturn NULL;\n+\t} else {\n+\t\tint offset;\n+\t\t/* keep 5 bytes for /.git and 40 for files in .git directory */\n+\t\tif (!getcwd(cwd, sizeof(cwd) - 5 - 40) || cwd[0] != '/')\n+\t\t\tdie(\"Unable to read current working directory\");\n+\t\toffset = strlen(cwd);\n+\t\tfor (;;) {\n+\t\t\tstrcat(cwd, \"/.git\");\n+\t\t\tif (is_git_directory(cwd)) {\n+\t\t\t\tdotgit = 1;\n+\t\t\t\tsetenv(GIT_DIR_ENVIRONMENT, cwd, 1);\n+\t\t\t\tcwd[offset] = '\\0';\n+\t\t\t\tstrcpy(worktree, cwd);\n+\t\t\t\tgitwt = worktree;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tif (offset == 0) {\n+\t\t\t\tif (is_git_directory(\".\")) {\n+\t\t\t\t\tsetenv(GIT_DIR_ENVIRONMENT, \".\", 1);\n+\t\t\t\t\tstrcpy(worktree, \".\");\n+\t\t\t\t\tgitwt = worktree;\n+\t\t\t\t\tbreak;\n \t\t\t\t}\n \t\t\t\tif (nongit_ok) {\n-\t\t\t\t\tif (chdir(cwd))\n-\t\t\t\t\t\tdie(\"Cannot come back to cwd\");\n \t\t\t\t\t*nongit_ok = 1;\n \t\t\t\t\treturn NULL;\n \t\t\t\t}\n \t\t\t\tdie(\"Not a git repository\");\n \t\t\t}\n-\t\t} while (cwd[--offset] != '/');\n+\t\t\tcwd[offset] = '\\0';\n+\t\t\twhile (cwd[offset] != '/')\n+\t\t\t\t--offset;\n+\t\t\tcwd[offset] = '\\0';\n+\t\t}\n+\t\tgitdirenv = getenv(GIT_DIR_ENVIRONMENT);\n+\t}\n+\n+\tif (!gitwt)\n+\t\tgitwt = getenv(GIT_WORKING_TREE_ENVIRONMENT);\n+\tif (gitwt && gitwt[0] != '/')\n+\t\twt_rel_cwd = 1;\n+\tif (!gitwt) {\n+\t\tgit_work_tree = worktree;\n+\t\tworktree[0] = '\\0';\n+\t}\n+\tgit_config(git_setup_config);\n+\tif (!gitwt) {\n+\t\tgit_work_tree = NULL;\n+\t\tif (worktree[0])\n+\t\t\tgitwt = worktree;\n \t}\n \n-\tif (offset == len)\n+\t/*\n+\t * try to figure out if this is a bare repository:\n+\t * if core.bare is undefined and a worktree is specified we go on and\n+\t * try the specified worktree, otherwise use is_bare_repository() to\n+\t * decide\n+\t */\n+\tif (!(is_bare_repository_cfg == -1 && gitwt) && is_bare_repository())\n \t\treturn NULL;\n \n-\t/* Make \"offset\" point to past the '/', and add a '/' at the end */\n-\toffset++;\n-\tcwd[len++] = '/';\n-\tcwd[len] = 0;\n-\tinside_git_dir = !prefixcmp(cwd + offset, \".git/\");\n-\treturn cwd + offset;\n+\tif (!gitwt) {\n+\t\twt_rel_cwd = 1;\n+\t\tstrcpy(worktree, \".\");\n+\t\tgitwt = worktree;\n+\t}\n+\n+\t/* run getcwd in cwd, GIT_DIR and GIT_WORK_TREE */\n+\tif (!getcwd(cwd, sizeof(cwd)-1) || cwd[0] != '/')\n+\t\tdie(\"Unable to read current working directory\");\n+\tif (chdir(gitdirenv))\n+\t\tdie(\"Cannot change directory to '%s'\", gitdirenv);\n+\tif (!getcwd(gitdir, sizeof(gitdir)-1) || gitdir[0] != '/')\n+\t\tdie(\"Unable to read current working directory\");\n+\t/* gitwt is a relative path from the old cwd, go back first */\n+\tif (wt_rel_cwd && chdir(cwd))\n+\t\tdie(\"Cannot come back to cwd\");\n+\tif (chdir(gitwt)) {\n+\t\tif (wt_rel_cwd || gitwt[0] == '/')\n+\t\t\tdie(\"Cannot change directory to working tree '%s'\",\n+\t\t\t\tgitwt);\n+\t\telse\n+\t\t\tdie(\"Cannot change directory to working tree '%s'\"\n+\t\t\t\t\" from $GIT_DIR\", gitwt);\n+\t}\n+\tif (!getcwd(worktree, sizeof(worktree)-1) || worktree[0] != '/')\n+\t\tdie(\"Unable to read current working directory\");\n+\n+\tlen = strlen(cwd);\n+\tcwd[len] = '/';\n+\tcwd[len+1] = '\\0';\n+\n+\tlen = strlen(worktree);\n+\tworktree[len] = '/';\n+\tworktree[len+1] = '\\0';\n+\tinside_working_tree = !prefixcmp(cwd, worktree);\n+\n+\tlen = strlen(gitdir);\n+\tgitdir[len] = '/';\n+\tgitdir[len+1] = '\\0';\n+\t/*\n+\t * if we are inside worktree, worktree is below gitdir and\n+\t * worktree != gitdir then we are not really in gitdir\n+\t */\n+\tif (inside_working_tree && !prefixcmp(worktree, gitdir) &&\n+\t    strcmp(worktree, gitdir)) {\n+\t\tinside_git_dir = 0;\n+\t} else {\n+\t\tinside_git_dir = !prefixcmp(cwd, gitdir);\n+\t}\n+\tgitdir[len] = '\\0';\n+\n+\tif (!inside_working_tree) {\n+\t\tif (chdir(cwd))\n+\t\t\tdie(\"Cannot come back to cwd\");\n+\t\treturn NULL;\n+\t}\n+\n+\tif (dotgit) {\n+\t\tunsetenv(GIT_DIR_ENVIRONMENT);\n+\t\tsetup_git_env();\n+\t} else if (gitdirenv[0] != '/') {\n+\t\tsetenv(GIT_DIR_ENVIRONMENT, gitdir, 1);\n+\t\tsetup_git_env();\n+\t}\n+\n+\tif (!strcmp(cwd, worktree))\n+\t\treturn NULL;\n+\treturn cwd+strlen(worktree);\n }\n \n int git_config_perm(const char *var, const char *value)\ndiff --git a/t/test-lib.sh b/t/test-lib.sh\nindex c075474..77c6d23 100755\n--- a/t/test-lib.sh\n+++ b/t/test-lib.sh\n@@ -25,6 +25,7 @@ GIT_COMMITTER_EMAIL=committer@example.com\n GIT_COMMITTER_NAME='C O Mitter'\n unset GIT_DIFF_OPTS\n unset GIT_DIR\n+unset GIT_WORK_TREE\n unset GIT_EXTERNAL_DIFF\n unset GIT_INDEX_FILE\n unset GIT_OBJECT_DIRECTORY\n-- \n1.5.1.4.g446af-dirty\n"},{"id":"38654","messageId":"20070404210439.GA24245@moooo.ath.cx","threadId":"7454","inReplyTo":"7vslbg2d2t.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH/RFC] introduce GIT_WORK_TREE environment variable","fromName":"Matthias Lederhofer","fromEmail":"matled@gmx.net","sentAt":"2007-04-04T21:04:39Z","receivedAt":"2007-04-04T21:04:39Z","isPatch":true,"sender":{"key":"matled@gmx.net","avatar":null},"body":"Junio C Hamano <junkio@cox.net> wrote:\n> My impression was that you already received a couple responses\n> which were not yet answered.\n\nI just fixed the two things noted in reply to the patch.  I was\nwaiting for feedback if it is desired to read the whole configuration\nfrom setup_git_directory_gently() and perhaps a comment on the rewrite\nof it.\n"},{"id":"38662","messageId":"7vslbfydiv.fsf@assigned-by-dhcp.cox.net","threadId":"7454","inReplyTo":"20070404201313.GB22782@moooo.ath.cx","subject":"Re: [PATCH(amend)] introduce GIT_WORK_TREE environment variable","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-04-04T22:34:48Z","receivedAt":"2007-04-04T22:34:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Matthias Lederhofer <matled@gmx.net> writes:\n\n> GIT_WORK_TREE can be used with GIT_DIR to specify the working tree.\n> As for GIT_DIR there is also the option `git\n> --work-tree=GIT_WORK_TREE` which overrides the environment\n> variable and a config setting core.worktree which is used as\n> default value.\n\nI haven't followed the latest code to see if it does what it\nclaims to in the log message (although I looked at the last\nround briefly).\n\nMost of my comments below are not objections but questions.\n\n> setup_git_directory_gently() is rewritten and does the\n> following now:\n>\n>   find the repository directory ($GIT_DIR, \".git\" in parent\n>   directories, \".\")\n>\n>   read the configuration (core.bare and core.worktree are used)\n>\n>   if core.bare is not set assume the repository is not bare if a\n>   working tree is specified or guess based on the name of the\n>   repository directory\n\nSounds sane so far.\n\n>   for a bare repository:\n>     set GIT_DIR if it is not set already and stop (this wont change\n>     the directory even if the repository was found as .git in a\n>     parent directory)\n\nIf you have a normal non-bare layout repository and have\ncore.bare set (perhaps by mistake), currently we chdir(2) up to\nthe working tree root, but the new code doesn't.  Is it a\nproblem in practice?\n\nIf you have a truly bare repository (perhaps a one that is\nserving the general public over gitweb), and you use GIT_DIR and\nGIT_WORK_TREE to have a working tree elsewhere so that you can\nhack on it, running a git command from a subdirectory would not\nchdir(2) up to the root of the working tree.  I suspect that the\ncallers of setup_git_directory() would expect to see the same\n\"cd up to the root and return the prefix\" behaviour.  Is this a\nproblem in practice?\n\n>   for a non-bare repository:\n>     if GIT_DIR is specified:\n>       use GIT_WORK_TREE, core.worktree or \".\" as working tree\n>     if the repository was found as .git in a parent directory:\n>       use the parent directory of the .git directory as working tree\n\nSounds sane so far.\n\n>     if the repository was found in \".\":\n>       use \".\" as working tree\n\nI am not sure about this.\n\nIs there ever a case that the repository (i.e. the directory\nthat has objects/, refs/, and HEAD) can also be a sane working\ntree root?  Wouldn't it be saner to say this case is without any\nworking tree and fail commands that require a working tree?\n\n>     set inside_git_dir and inside_working_tree based on getcwd() and\n>     prefixcmp()\n\n> is_bare_repository() is also changed to return true if the working\n> directory is outside of the working tree.\n\nWhat does this mean from operational perspective?  Suppose you\nare using GIT_DIR and GIT_WORK_TREE to have a working tree that\nis separate from your working area, and get interrupted and go\nsomewhere else (say \"pushd /var/tmp\").  Does this suddenly allow\n\"git fetch\" into the repository to update the current branch\ntip?  That does not sound right, but it might not be a good use\ncase to begin with.  I dunno, but I think is_bare_repository\nshould mean \"I am treating this repository as a bare\nrepository\".  That means I do not want to have \"no-working-tree\"\nsemantics applied to the repository operation, regardless of\nwhere my $cwd happens to be, once I say GIT_WORK_TREE to name\nwhich working tree I am using to work with that repository.\n\nWhat problem are you solving with this is_bare_repository()\nthing?  Could it be that you are working around problems with\nthe current callers that behave inappropriately, based on\nis_bare_repository(), when they should really be checking\ninside_work_tree() instead, perhaps?\n\n>     - call setup_git_env() from setup_git_directory_gently() if needed\n\nCalling setup_git_env() again would leak memory for\ngit_object_dir and friends, but I have a bigger worry about this\nchange.\n\nIf calling setup_git_env() explicitly after resetting GIT_DIR\nand stuff in this code makes any difference, doesn't that mean\nsomebody else already called a function in environment.c (say,\nget_refs_directory()) and also have already _acted_ on it\n(e.g. calling get_packed_refs() which populates the list of refs\nbased on the old value of \"$GIT_DIR/packed-refs\")?  Calling the\nfunction again would not undo/redo that, so maybe the calling\nsequence into setup_git_env() needs to be made safer.\n\nI think the only thing you care about in your \"where is the repo\nand where is the worktree\" codepath are get_git_dir() and\nis_bare_repository().  As a side effect of calling these\nfunctions for your own purpose, you later have to call\nsetup_git_env() again to clean up, which is fine.  I would feel\nbetter if there is an assert in setup_git_env that catches the\ncase where it is called for the second time even though the\nfirst caller was something other than this repository/worktree\ndiscovery code.\n\n> diff --git a/environment.c b/environment.c\n> index 713a011..769d409 100644\n> --- a/environment.c\n> +++ b/environment.c\n> @@ -60,8 +60,15 @@ void setup_git_env(void)\n>  int is_bare_repository(void)\n>  {\n>  \tconst char *dir, *s;\n> -\tif (0 <= is_bare_repository_cfg)\n> -\t\treturn is_bare_repository_cfg;\n> +\t/* definitely bare */\n> +\tif (is_bare_repository_cfg == 1)\n> +\t\treturn 1;\n> +\t/* bare if cwd is outside of the working tree */\n> +\tif (inside_working_tree >= 0)\n> +\t\treturn !inside_working_tree;\n> +\t/* configuration says it is not bare */\n> +\tif (is_bare_repository_cfg == 0)\n> +\t\treturn 0;\n>  \n>  \tdir = get_git_dir();\n>  \tif (!strcmp(dir, DEFAULT_GIT_DIR_ENVIRONMENT))\n\nFor example, calling this function from programs before calling\nthe repository/worktree discovery is an error, isn't it?  Can we\nhave a safety here to catch such a programming error?\n\n> diff --git a/setup.c b/setup.c\n> index a45ea83..794edcf 100644\n> --- a/setup.c\n> +++ b/setup.c\n> @@ -192,67 +192,168 @@ int is_inside_git_dir(void)\n>  \treturn inside_git_dir;\n>  }\n>  \n> +static char *git_work_tree;\n> +\n> +static int git_setup_config(const char *var, const char *value)\n> +{\n> +\tif (git_work_tree && !strcmp(var, \"core.worktree\")) {\n> +\t\tstrlcpy(git_work_tree, value, PATH_MAX);\n> +\t}\n> +\treturn git_default_config(var, value);\n> +}\n> +\n\nOther config functions do not pass already handled variables to\ngit_default_config().  You probably would care about core.bare\nin your own code, so falling back to git_default_config() is\nfine, although it may look wasteful.\n"},{"id":"38790","messageId":"20070406132109.GA5682@moooo.ath.cx","threadId":"7454","inReplyTo":"7vslbfydiv.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH(amend)] introduce GIT_WORK_TREE environment variable","fromName":"Matthias Lederhofer","fromEmail":"matled@gmx.net","sentAt":"2007-04-06T13:21:09Z","receivedAt":"2007-04-06T13:21:09Z","isPatch":true,"sender":{"key":"matled@gmx.net","avatar":null},"body":"Junio C Hamano <junkio@cox.net> wrote:\n> Matthias Lederhofer <matled@gmx.net> writes:\n> \n> > is_bare_repository() is also changed to return true if the working\n> > directory is outside of the working tree.\n> \n> What does this mean from operational perspective?  Suppose you\n> are using GIT_DIR and GIT_WORK_TREE to have a working tree that\n> is separate from your working area, and get interrupted and go\n> somewhere else (say \"pushd /var/tmp\").  Does this suddenly allow\n> \"git fetch\" into the repository to update the current branch\n> tip?  That does not sound right, but it might not be a good use\n> case to begin with.  I dunno, but I think is_bare_repository\n> should mean \"I am treating this repository as a bare\n> repository\".  That means I do not want to have \"no-working-tree\"\n> semantics applied to the repository operation, regardless of\n> where my $cwd happens to be, once I say GIT_WORK_TREE to name\n> which working tree I am using to work with that repository.\n> \n> What problem are you solving with this is_bare_repository()\n> thing?  Could it be that you are working around problems with\n> the current callers that behave inappropriately, based on\n> is_bare_repository(), when they should really be checking\n> inside_work_tree() instead, perhaps?\n\nRight, I am using is_bare_repository() as check for \"has this\nrepository a usable working tree\".\n\nCurrently:\n\n    The core.bare setting is ignored during setup which leads to\n    things like that:\n        \n        % git status\n        fatal: git-status cannot be used without a working tree.\n        % git runstatus\n        # On branch master\n        [..]\n\n    So one command honors core.bare and another doesn't because even\n    though runstatus has the NOT_BARE flag handle_internal_command()\n    has not read the core.bare setting and just uses name based\n    guessing.\n\n    is_bare_repository() is used for two different checks at the\n    moment:\n    - should the user be protected from doing things with strange\n      effects in repositories with a working tree (e.g. git fetch\n      updating HEAD)\n    - is there a working tree to be used (e.g. git add)\n\nMy patch:\n\n    core.bare = true means that the repository may never be used with\n    a working tree.\n\n    is_bare_repository() checks for a usable working tree, this means\n    it may return true for non-bare repositories when the cwd is not\n    inside the working tree.  This allows a fetch to update HEAD when\n    you're outside the working tree.\n\nSo the answer (you seem to suggest) to this problem is to split\nis_bare_repository() into two functions, which sounds sane to me:\nis_bare_repository():\n    Check if it is allowed to do things which might cause problems if\n    there is someone using this repository with a working tree.  This\n    does not mean that there is a working tree available to use.\ninside_work_tree():\n    Check if there is a usable working tree (cwd is below the working\n    tree).\n\nWith splitting is_bare_repository() it is no longer abused to check\nfor a working tree.  This raises the question what 'bare' is actually\nsupposed to mean:\n(1) git may do operations on the repository which have strange effects\n    if I'm using the repository with a working tree (e.g. update HEAD\n    during a fetch)\n(2) git may do (1) but also disallows to use the repository with a\nworking tree\n\nWith (1) it would be ok to let a user work with a repository that has\ncore.bare = true if she really wants to (e.g. by setting GIT_WORK_TREE\nexplicitly, details to be figured out later).\nWith (2) git should disallow the use of a working tree if core.bare = true.\n\nOne option to solve this is to make core.bare have three possible\nvalues, so the user can decide if he wants (1) or (2).\n    core.bare = true: no working tree allowed\n    core.bare = false: don't allow git-fetch to update HEAD etc.\n    core.bare = mixed: allow both\nIn the third case is_bare_repository() would be true and\ninside_work_tree() would change depending on cwd and GIT_WORK_TREE\nsetting.  This would allow git fetch to update the current branch even\nwhile inside the working tree.\n\nBut I'm not really sure about this yet, any comments?\n\n\n> >   for a bare repository:\n> >     set GIT_DIR if it is not set already and stop (this wont change\n> >     the directory even if the repository was found as .git in a\n> >     parent directory)\n> \n> If you have a normal non-bare layout repository and have\n> core.bare set (perhaps by mistake), currently we chdir(2) up to\n> the working tree root, but the new code doesn't.  Is it a\n> problem in practice?\n\nIf you refer to the user: see above.\n\nFor git itself: The caller can only note a difference if it tries to\nfigure out if the repository is bare or not before calling\nsetup_gdg (setup_git_directory_gently) or using something else than\nis_bare_repository().  After calling setup_gdg everything is the same\nas for any other bare repository with the current code: GIT_DIR is\nexported and cwd has not been changed.\n\n> If you have a truly bare repository (perhaps a one that is\n> serving the general public over gitweb), and you use GIT_DIR and\n> GIT_WORK_TREE to have a working tree elsewhere so that you can\n> hack on it, running a git command from a subdirectory would not\n> chdir(2) up to the root of the working tree.  I suspect that the\n> callers of setup_git_directory() would expect to see the same\n> \"cd up to the root and return the prefix\" behaviour.  Is this a\n> problem in practice?\n\nCallers of setup_git_directory() should not check if GIT_WORK_TREE is\nset and if the cwd is below this directory (which is required to use\nthe tree) but use is_bare_repository() and inside_work_tree().  The\nformer should return true, the latter should return false, so the\ncaller clearly knows that there is no working tree to use and\ntherefore there is especially no prefix.\n\n\n> >     if the repository was found in \".\":\n> >       use \".\" as working tree\n> \n> I am not sure about this.\n> \n> Is there ever a case that the repository (i.e. the directory\n> that has objects/, refs/, and HEAD) can also be a sane working\n> tree root?  Wouldn't it be saner to say this case is without any\n> working tree and fail commands that require a working tree?\n\nRight, I can't think of any case where this helps.  I think I tried to\nfollow the old code but missed that if the directory is named .git it\nis found as .git directory in .. and if it is named otherwise it is\nbare because its name is not ending in /.git.\n\n> >     - call setup_git_env() from setup_git_directory_gently() if needed\n> \n> Calling setup_git_env() again would leak memory for\n> git_object_dir and friends, but I have a bigger worry about this\n> change.\n> \n> If calling setup_git_env() explicitly after resetting GIT_DIR\n> and stuff in this code makes any difference, doesn't that mean\n> somebody else already called a function in environment.c (say,\n> get_refs_directory()) and also have already _acted_ on it\n> (e.g. calling get_packed_refs() which populates the list of refs\n> based on the old value of \"$GIT_DIR/packed-refs\")?  Calling the\n> function again would not undo/redo that, so maybe the calling\n> sequence into setup_git_env() needs to be made safer.\n> \n> I think the only thing you care about in your \"where is the repo\n> and where is the worktree\" codepath are get_git_dir() and\n> is_bare_repository().  As a side effect of calling these\n> functions for your own purpose, you later have to call\n> setup_git_env() again to clean up, which is fine.  I would feel\n> better if there is an assert in setup_git_env that catches the\n> case where it is called for the second time even though the\n> first caller was something other than this repository/worktree\n> discovery code.\n\nYou mean for the third time (first: git_get_dir() from git_config(),\nsecond: setup_gdg, third: someone who should not call setup_git_env)?\n\nI think we could also work around to use setup_git_env() at all: the\nguessing part of is_bare_repository() could be moved out of\nis_bare_repository() and there could be an additional git_config()\nfunction taking the path to the repository as argument.\n\n> > diff --git a/environment.c b/environment.c\n> > index 713a011..769d409 100644\n> > --- a/environment.c\n> > +++ b/environment.c\n> > @@ -60,8 +60,15 @@ void setup_git_env(void)\n> >  int is_bare_repository(void)\n> >  {\n> >  \tconst char *dir, *s;\n> > -\tif (0 <= is_bare_repository_cfg)\n> > -\t\treturn is_bare_repository_cfg;\n> > +\t/* definitely bare */\n> > +\tif (is_bare_repository_cfg == 1)\n> > +\t\treturn 1;\n> > +\t/* bare if cwd is outside of the working tree */\n> > +\tif (inside_working_tree >= 0)\n> > +\t\treturn !inside_working_tree;\n> > +\t/* configuration says it is not bare */\n> > +\tif (is_bare_repository_cfg == 0)\n> > +\t\treturn 0;\n> >  \n> >  \tdir = get_git_dir();\n> >  \tif (!strcmp(dir, DEFAULT_GIT_DIR_ENVIRONMENT))\n> \n> For example, calling this function from programs before calling\n> the repository/worktree discovery is an error, isn't it?  Can we\n> have a safety here to catch such a programming error?\n\nRight, before setup_gdg is_bare_repository() will only guess based on\nthe name, with core.bare being used by setup_gdg this guessing should\nnot be used if there is more accurate information after calling\nsetup_gdg.\n\nYou mean something like assert(inside_work_tree >= 0); to check if\nsetup_gdg was called yet?  I didn't see such checks in the git source\nyet but I like it, it is better to have an error message pointing at\nthe bug instead of strange behaviour which may result in a bug.\n\n> > diff --git a/setup.c b/setup.c\n> > index a45ea83..794edcf 100644\n> > --- a/setup.c\n> > +++ b/setup.c\n> > @@ -192,67 +192,168 @@ int is_inside_git_dir(void)\n> >  \treturn inside_git_dir;\n> >  }\n> >  \n> > +static char *git_work_tree;\n> > +\n> > +static int git_setup_config(const char *var, const char *value)\n> > +{\n> > +\tif (git_work_tree && !strcmp(var, \"core.worktree\")) {\n> > +\t\tstrlcpy(git_work_tree, value, PATH_MAX);\n> > +\t}\n> > +\treturn git_default_config(var, value);\n> > +}\n> > +\n> \n> Other config functions do not pass already handled variables to\n> git_default_config().  You probably would care about core.bare\n> in your own code, so falling back to git_default_config() is\n> fine, although it may look wasteful.\n\nRight, this should be:\n+\tif (!strcmp(var, \"core.worktree\")) {\n+\t\tif (git_work_tree)\n+\t\t\tstrlcpy(git_work_tree, value, PATH_MAX);\n+\t\treturn 0;\n+\t}\n\nsetup_gdg has to be called before calling git_config(), I think we\nshould do one of\n(1) setup_gdg calls git_default_config()\n    we can probably get rid of all those\n      git_config(git_default_config);\n    and\n      return git_default_config(var, value);\n(2) setup_gdg extracts only core.{bare,worktree} and does not call\n    git_default_config()\n\nI'm in favor of the first one: all commands can rely on the default\nconfiguration values to be loaded and I don't see a disadvantage yet.\n"},{"id":"39082","messageId":"7vzm5fr954.fsf@assigned-by-dhcp.cox.net","threadId":"7454","inReplyTo":"20070406132109.GA5682@moooo.ath.cx","subject":"Re: [PATCH(amend)] introduce GIT_WORK_TREE environment variable","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-04-11T01:29:27Z","receivedAt":"2007-04-11T01:29:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Matthias Lederhofer <matled@gmx.net> writes:\n\n> With splitting is_bare_repository() it is no longer abused to check\n> for a working tree.  This raises the question what 'bare' is actually\n> supposed to mean:\n> (1) git may do operations on the repository which have strange effects\n>     if I'm using the repository with a working tree (e.g. update HEAD\n>     during a fetch)\n> (2) git may do (1) but also disallows to use the repository with a\n> working tree\n>\n> With (1) it would be ok to let a user work with a repository that has\n> core.bare = true if she really wants to (e.g. by setting GIT_WORK_TREE\n> explicitly, details to be figured out later).\n> With (2) git should disallow the use of a working tree if core.bare = true.\n\nWell, core.bare was designed without any support for something\nlike GIT_WORK_TREE in mind, so I do not think we should be tied\ntoo much to whatever the current implementation happens to do.\n\nIf you are using GIT_WORK_TREE and GIT_DIR to name two\ndirectories that are not connected in the usual way\n(i.e. $GIT_DIR != $GIT_WORK_TREE/.git) to work in, I *think*\nyour intention is \"I want to checkout, diff, commit and all the\nusual git operations in GIT_WORK_TREE to grow branches in\nGIT_DIR\".  I do not think core.bare should even be looked at in\nthis case.\n\nA quick \"git grep is_bare\" reveals that:\n\n - \"git-branch foo $commit\" refuses if the repository is not\n   bare and foo is the current branch.  The intention here is if\n   we allow it then it makes the HEAD and index+working tree out\n   of sync, so this clearly is about \"does it have a working\n   tree, and is it used with one\"?\n\n - \"git gc\" runs pack-refs only if the repository is not bare.\n   This is to prevent public repositories from getting\n   pack-ref-pruned (which would make older dumb clients not to\n   be able to fetch from it).  This _is_ about is_bare; it does\n   not matter if somebody else happens to use it with a working\n   tree.\n\n - reflog update code in refs.c defaults log_all_ref_updates\n   value to true when !is-bare; the intent is if the repository\n   is used to actively build history with working tree we would\n   want the reflog, otherwise if it is primarily used to track\n   other repositories (either pushing into it, or fetching from\n   elsewhere) reflog is often clutter.  So this is not about\n   bare, but \"does it have a working tree?\".\n\n - \"git ls-files\" refuses if \"is-bare\" or \"is-inside-git-dir\" is\n   true.  The intention is we want ls-files to be run inside the\n   working tree.\n\n - Any other commands that says NOT_BARE does the same as\n   ls-files, and the error message is \"must be run in a work\n   tree\".\n\n> One option to solve this is to make core.bare have three possible\n> values, so the user can decide if he wants (1) or (2).\n>     core.bare = true: no working tree allowed\n>     core.bare = false: don't allow git-fetch to update HEAD etc.\n>     core.bare = mixed: allow both\n> In the third case is_bare_repository() would be true and\n> inside_work_tree() would change depending on cwd and GIT_WORK_TREE\n> setting.  This would allow git fetch to update the current branch even\n> while inside the working tree.\n\nI am not sure if you even want the \"no working tree allowed\"\ncase. Doesn't it directly contradict with the purpose of the\nwhole GIT_WORK_TREE idea?\n\nI did not look at the Porcelain-ish scripts, but for example in\nthe case of git-fetch, the reason it does not allow updating the\ncurrent branch is the same as \"git-branch\" above.\n\nSo I suspect that most of the existing use of is_bare is just an\nimplementation detail of \"do we have a working tree?\", and it\nhas been a good implementation primarily because we did not even\nallow doing something like GIT_WORK_TREE to begin with.  They\nneed to be adjusted to work well with GIT_WORK_TREE.\n\n>> I think the only thing you care about in your \"where is the repo\n>> and where is the worktree\" codepath are get_git_dir() and\n>> is_bare_repository().  As a side effect of calling these\n>> functions for your own purpose, you later have to call\n>> setup_git_env() again to clean up, which is fine.  I would feel\n>> better if there is an assert in setup_git_env that catches the\n>> case where it is called for the second time even though the\n>> first caller was something other than this repository/worktree\n>> discovery code.\n>\n> You mean for the third time (first: git_get_dir() from git_config(),\n> second: setup_gdg, third: someone who should not call setup_git_env)?\n\nI am more worried about a case where\n\n (1) a program calls some function A (perhaps in environment.c);\n\n (2) both of us are forgetting right now that A ends up calling\n    setup_git_env();\n\n (3) the result from A is used to make decision by the calling\n     program, and...\n\n (4) then the program calls setup_git_directory().\n\nCurrently the damange is limited as A cannot be outside\nenvironment.c because setup_git_env() is static there, but\nwhatever the program decides in step (3) may need to be rolled\nback and recomputed if setup_git_directory() ends up moving\nGIT_DIR and friends around.\n\n> You mean something like assert(inside_work_tree >= 0); to check if\n> setup_gdg was called yet?  I didn't see such checks in the git source\n> yet but I like it, it is better to have an error message pointing at\n> the bug instead of strange behaviour which may result in a bug.\n\nYes, but I would rather have something stroner than assert()\nthat cannot be compiled away.  An explicit if (...) die(\"Gaah\").\n"}]}