{"thread":{"id":"36838","subject":"[PATCH v5] Add an explicit GIT_DIR to the list of excludes","startedAt":"2014-06-04T20:15:09Z","lastAt":"2014-06-14T19:16:33Z","messageCount":10,"participants":["Pasha Bolokhov","Duy Nguyen","Johannes Sixt","Junio C Hamano"],"isPatch":true,"patchVersion":5,"patchTotal":null},"messages":[{"id":"243338","messageId":"1401912909-29654-1-git-send-email-pasha.bolokhov@gmail.com","threadId":"36838","inReplyTo":null,"subject":"[PATCH v5] Add an explicit GIT_DIR to the list of excludes","fromName":"Pasha Bolokhov","fromEmail":"pasha.bolokhov@gmail.com","sentAt":"2014-06-04T20:15:09Z","receivedAt":"2014-06-04T20:15:09Z","isPatch":true,"sender":{"key":"pasha.bolokhov@gmail.com","avatar":null},"body":"When an explicit '--git-dir' option points to a directory inside\nthe work tree, git treats it as if it were any other directory.\nIn particular, 'git status' lists it as untracked, while 'git add -A'\nstages the metadata directory entirely\n\nAdd GIT_DIR to the list of excludes in a dedicated function\nadd_git_dir_exclude(), while checking that GIT_DIR is not just '.git'\nor its basename is not '.git', in which cases it would be ignored\nby default, and that GIT_DIR is inside GIT_WORK_TREE. Function\nadd_git_dir_exclude() is invoked at the beginning of\ndir.c:setup_standard_excludes()\n\nAlthough an analogous comparison of any given path against '.git'\nis done in treat_path(), this does not seem to be the right place\nto compare against GIT_DIR. Instead, the excludes provide an\neffective mechanism of ignoring a file/directory, and adding GIT_DIR\nas an exclude is equivalent to putting it into '.gitignore'. Function\nsetup_standard_excludes() was chosen because that is the place where\nthe excludes are initialized by the commands that are concerned about\nexcludes\n\nSigned-off-by: Pasha Bolokhov <pasha.bolokhov@gmail.com>\n---\nbasename() is not needed anymore, as we are excluding a \"full\" path GIT_DIR\nbut relative to GIT_WORK_TREE. Now, a full path ensures that nothing\nelse gets excluded (i.e. files with the same basename), and this behaviour\nis checked in tests\n\nDocs (git-grep, git-ls-files, gitignore) will be updated in the\nsubsequent version, wanted to check right now if you agree with code.\nDocs kind of depend on what we decide about the code\n\n Documentation/technical/api-directory-listing.txt |   4 +-\n dir.c                                             |  33 ++++\n t/t2205-add-gitdir.sh                             | 187 ++++++++++++++++++++++\n 3 files changed, 222 insertions(+), 2 deletions(-)\n create mode 100755 t/t2205-add-gitdir.sh\n\ndiff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt\nindex 7f8e78d..fd4a178 100644\n--- a/Documentation/technical/api-directory-listing.txt\n+++ b/Documentation/technical/api-directory-listing.txt\n@@ -90,8 +90,8 @@ marked. If you to exclude files, make sure you have loaded index first.\n   `add_exclude()`.\n \n * To add patterns from a file (e.g. `.git/info/exclude`), call\n-  `add_excludes_from_file()` , and/or set `dir.exclude_per_dir`.  A\n-  short-hand function `setup_standard_excludes()` can be used to set\n+  `add_excludes_from_file()` , and/or set `dir.exclude_per_dir`.  The\n+  short-hand function `setup_standard_excludes()` must be used to set\n   up the standard set of exclude settings.\n \n * Set options described in the Data Structure section above.\ndiff --git a/dir.c b/dir.c\nindex eb6f581..300ce1c 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1604,11 +1604,44 @@ int remove_dir_recursively(struct strbuf *path, int flag)\n \treturn remove_dir_recurse(path, flag, NULL);\n }\n \n+static void add_git_dir_exclude(struct dir_struct *dir)\n+{\n+\tconst char *r_git, *gitdir = get_git_dir();\n+\tchar *n_git;\n+\tint len;\n+\n+\tr_git = real_path(absolute_path(gitdir));\n+\tn_git = xmalloc(strlen(r_git) + 1 + 1);\n+\tnormalize_path_copy(n_git, r_git);\n+\tlen = strlen(n_git);\n+\n+\t/* only add it if GIT_DIR does not end with '.git' or '/.git' */\n+\tif (len < 4 || strcmp(n_git + len - 4, \".git\") ||\n+\t    (len > 4 && n_git[len - 5] != '/')) {\n+\t\tconst char *worktree = get_git_work_tree();\n+\n+\t\tif (!worktree ||\n+\t\t    dir_inside_of(n_git, worktree) >= 0) {\n+\t\t\tstruct exclude_list *el = add_exclude_list(dir, EXC_CMDL,\n+\t\t\t\t\t\t\t\"GIT_DIR setup\");\n+\t\t\tchar *reldir = worktree ? n_git + strlen(worktree) : n_git;\n+\n+\t\t\t/* append a trailing slash to exclude directories only */\n+\t\t\tn_git[len] = '/';\n+\t\t\tn_git[len + 1] = '\\0';\n+\t\t\tadd_exclude(reldir, \"\", 0, el, 0);\n+\t\t}\n+\t}\n+\tfree(n_git);\n+}\n+\n void setup_standard_excludes(struct dir_struct *dir)\n {\n \tconst char *path;\n \tchar *xdg_path;\n \n+\tadd_git_dir_exclude(dir);\n+\n \tdir->exclude_per_dir = \".gitignore\";\n \tpath = git_path(\"info/exclude\");\n \tif (!excludes_file) {\ndiff --git a/t/t2205-add-gitdir.sh b/t/t2205-add-gitdir.sh\nnew file mode 100755\nindex 0000000..721970e\n--- /dev/null\n+++ b/t/t2205-add-gitdir.sh\n@@ -0,0 +1,187 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2014 Pasha Bolokhov\n+#\n+\n+test_description='alternative repository path specified by --git-dir is ignored by add and status'\n+\n+. ./test-lib.sh\n+\n+#\n+# Create a tree:\n+#\n+#\trepo-inside/  repo-outside/\n+#\n+#\n+# repo-inside:\n+# \ta  b  c  d  dir1/ dir2/ [meta/]\n+#\n+# repo-inside/dir1:\n+# \te  f  g  h  meta/  ssubdir/\n+#\n+# repo-inside/dir1/meta:\n+# \taa\n+#\n+# repo-inside/dir1/ssubdir:\n+# \tmeta/\n+#\n+# repo-inside/dir1/ssubdir/meta:\n+# \taaa\n+#\n+# repo-inside/dir2:\n+#\tmeta\n+#\n+#\n+#\n+# repo-outside:\n+#\texternal/  tree/\n+#\n+# repo-outside/external:\n+#\t[meta/]\n+#\n+# repo-outside/tree:\n+#\tn  o  p  q  meta/  sub/\n+#\n+# repo-outside/tree/meta:\n+#\tbb\n+#\n+# repo-outside/tree/sub:\n+#\tmeta/\n+#\n+# repo-outside/tree/sub/meta:\n+#\tbbb\n+#\n+#\n+# (both of the above [meta/] denote the actual repositories)\n+#\n+\n+#\n+# First set of tests (in \"repo-inside/\"):\n+# ---------------------------------------\n+#\n+# Name the repository \"meta\" and see whether or not \"git status\" includes or\n+# ignores directories named \"meta\". Directory \"meta\" at the top level of\n+# \"repo-inside/\"is the repository and appears upon the first \"git init\"\n+#\n+#\n+# Second set of tests (in \"repo-outside/\"):\n+# -----------------------------------------\n+#\n+# Put the work tree into \"tree/\" and repository into \"external/meta\"\n+# (the latter directory appears upon the corresponding \"git init\").\n+# The work tree again contains directories named \"meta\", but those ones are\n+# tested not to be ignored now\n+#\n+\n+test_expect_success \"setup\" '\n+\tmkdir repo-inside/ &&\n+\t(\n+\t\tcd repo-inside/ &&\n+\t\tfor f in a b c d\n+\t\tdo\n+\t\t\techo \"DATA\" >\"$f\" || exit 1\n+\t\tdone &&\n+\t\tmkdir dir1 dir1/meta &&\n+\t\tmkdir dir1/ssubdir dir1/ssubdir/meta &&\n+\t\tfor f in e f g h\n+\t\tdo\n+\t\t\techo \"MORE DATA\" >\"dir1/$f\" || exit 1\n+\t\tdone &&\n+\t\techo \"EVEN more Data\" >dir1/meta/aa &&\n+\t\techo \"Data and BAIT\" >dir1/ssubdir/meta/aaa &&\n+\t\tmkdir dir2\n+\t\techo \"Not a Metadata File\" >dir2/meta\n+\t\tgit --git-dir=meta init\n+\t) &&\n+\tmkdir repo-outside/ repo-outside/external repo-outside/tree &&\n+\t(\n+\t\tcd repo-outside/tree &&\n+\t\tfor f in n o p q\n+\t\tdo\n+\t\t\techo \"Literal Data\" >\"$f\" || exit 1\n+\t\tdone &&\n+\t\tmkdir meta sub sub/meta &&\n+\t\techo \"Sample data\" >meta/bb &&\n+\t\techo \"Stream of data\" >sub/meta/bbb &&\n+\t\tgit --git-dir=../external/meta init\n+\t)\n+'\n+\n+\n+#\n+# The first set of tests (the repository is inside the work tree)\n+#\n+test_expect_success \"'git status' ignores the repository directory\" '\n+\t(\n+\t\tcd repo-inside &&\n+\t\tgit --git-dir=meta --work-tree=. status --porcelain --untracked=all |\n+\t\t\tgrep meta | sort >status.actual.2 &&\n+\t\tcat >status.expect.2 <<-\\EOF &&\n+\t\t?? dir1/meta/aa\n+\t\t?? dir1/ssubdir/meta/aaa\n+\t\t?? dir2/meta\n+\t\tEOF\n+\t\ttest_cmp status.actual.2 status.expect.2\n+\t)\n+'\n+\n+test_expect_success \"'git add -A' ignores the repository directory\" '\n+\t(\n+\t\tcd repo-inside &&\n+\t\tgit --git-dir=meta --work-tree=. add -A &&\n+\t\tgit --git-dir=meta --work-tree=. status --porcelain | grep meta | sort >status.actual.3 &&\n+\t\tcat >status.expect.3 <<-\\EOF &&\n+\t\tA  dir1/meta/aa\n+\t\tA  dir1/ssubdir/meta/aaa\n+\t\tA  dir2/meta\n+\t\tEOF\n+\t\ttest_cmp status.actual.3 status.expect.3\n+\t)\n+'\n+\n+test_expect_success \"'git grep --exclude-standard' ignores the repository directory\" '\n+\t(\n+\t\tcd repo-inside &&\n+\t\ttest_might_fail git --git-dir=meta \\\n+\t\t\tgrep --no-index --exclude-standard BAIT >grep.actual.4 &&\n+\t\tcat >grep.expect.4 <<-\\EOF &&\n+\t\tdir1/ssubdir/meta/aaa:Data and BAIT\n+\t\tEOF\n+\t\ttest_cmp grep.actual.4 grep.expect.4\n+\t)\n+'\n+\n+#\n+# The second set of tests (the repository is outside of the work tree)\n+#\n+test_expect_success \"'git status' acknowledges directories 'meta' \\\n+if repo is not within work tree\" '\n+\ttest_might_fail rm -rf meta/ &&\n+\t(\n+\t\tcd repo-outside/tree &&\n+\t\tgit --git-dir=../external/meta init &&\n+\t\tgit --git-dir=../external/meta --work-tree=. status --porcelain --untracked=all |\n+\t\t\tgrep meta | sort >status.actual.5 &&\n+\t\tcat >status.expect.5 <<-\\EOF &&\n+\t\t?? meta/bb\n+\t\t?? sub/meta/bbb\n+\t\tEOF\n+\t\ttest_cmp status.actual.5 status.expect.5\n+\t)\n+'\n+\n+test_expect_success \"'git add -A' adds 'meta' if the repo is outside the work tree\" '\n+\t(\n+\t\tcd repo-outside/tree &&\n+\t\tgit --git-dir=../external/meta --work-tree=. add -A &&\n+\t\tgit --git-dir=../external/meta --work-tree=. status --porcelain --untracked=all |\n+\t\t\tgrep meta | sort >status.actual.6 &&\n+\t\tcat >status.expect.6 <<-\\EOF &&\n+\t\tA  meta/bb\n+\t\tA  sub/meta/bbb\n+\t\tEOF\n+\t\ttest_cmp status.actual.6 status.expect.6\n+\t)\n+'\n+\n+test_done\n-- \n1.9.1\n"},{"id":"243603","messageId":"CACsJy8AyvkA20mD283=hWp7WFJXBu3-ViXHuS4jtMzz2hpBi2Q@mail.gmail.com","threadId":"36838","inReplyTo":"1401912909-29654-1-git-send-email-pasha.bolokhov@gmail.com","subject":"Re: [PATCH v5] Add an explicit GIT_DIR to the list of excludes","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-06-08T10:17:58Z","receivedAt":"2014-06-08T10:17:58Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Jun 5, 2014 at 3:15 AM, Pasha Bolokhov <pasha.bolokhov@gmail.com> wrote:\n> +       /* only add it if GIT_DIR does not end with '.git' or '/.git' */\n> +       if (len < 4 || strcmp(n_git + len - 4, \".git\") ||\n> +           (len > 4 && n_git[len - 5] != '/')) {\n\nHmm.. should we exclude \"foobar.git\" as well?\n-- \nDuy\n"},{"id":"243696","messageId":"CAKpPgveXen9sQefo8dva1Uy4YOyroT6cPXLzgUvxj93Zqw13zw@mail.gmail.com","threadId":"36838","inReplyTo":"CACsJy8AyvkA20mD283=hWp7WFJXBu3-ViXHuS4jtMzz2hpBi2Q@mail.gmail.com","subject":"Re: [PATCH v5] Add an explicit GIT_DIR to the list of excludes","fromName":"Pasha Bolokhov","fromEmail":"pasha.bolokhov@gmail.com","sentAt":"2014-06-10T03:18:12Z","receivedAt":"2014-06-10T03:18:12Z","isPatch":true,"sender":{"key":"pasha.bolokhov@gmail.com","avatar":null},"body":"> On Thu, Jun 5, 2014 at 3:15 AM, Pasha Bolokhov <pasha.bolokhov@gmail.com> wrote:\n>> +       /* only add it if GIT_DIR does not end with '.git' or '/.git' */\n>> +       if (len < 4 || strcmp(n_git + len - 4, \".git\") ||\n>> +           (len > 4 && n_git[len - 5] != '/')) {\n>\n> Hmm.. should we exclude \"foobar.git\" as well?\n\n    Why wouldn't we? Everything that has basename \".git\" is hard-wired\nto be excluded, but everything else, including \"foobar.git\" should be\nadded to the excludes manually... How is it better than just \"foobar\"?\n"},{"id":"243899","messageId":"CACsJy8Cgu792Tamej3+_9w=Krg8nezYYA6k604gikebHG=yVUg@mail.gmail.com","threadId":"36838","inReplyTo":"CAKpPgveXen9sQefo8dva1Uy4YOyroT6cPXLzgUvxj93Zqw13zw@mail.gmail.com","subject":"Re: [PATCH v5] Add an explicit GIT_DIR to the list of excludes","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-06-11T10:48:31Z","receivedAt":"2014-06-11T10:48:31Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Tue, Jun 10, 2014 at 10:18 AM, Pasha Bolokhov\n<pasha.bolokhov@gmail.com> wrote:\n>> On Thu, Jun 5, 2014 at 3:15 AM, Pasha Bolokhov <pasha.bolokhov@gmail.com> wrote:\n>>> +       /* only add it if GIT_DIR does not end with '.git' or '/.git' */\n>>> +       if (len < 4 || strcmp(n_git + len - 4, \".git\") ||\n>>> +           (len > 4 && n_git[len - 5] != '/')) {\n>>\n>> Hmm.. should we exclude \"foobar.git\" as well?\n>\n>     Why wouldn't we? Everything that has basename \".git\" is hard-wired\n> to be excluded, but everything else, including \"foobar.git\" should be\n> added to the excludes manually... How is it better than just \"foobar\"?\n\nYes everything except \".git\" should be excluded. And you do exactly\nthat. I misread the code (probably better to write \"if (!(<basename is\n\".git>))\", maybe)\n-- \nDuy\n"},{"id":"243930","messageId":"1402529308-3940-1-git-send-email-pasha.bolokhov@gmail.com","threadId":"36838","inReplyTo":"CACsJy8Cgu792Tamej3+_9w=Krg8nezYYA6k604gikebHG=yVUg@mail.gmail.com","subject":"[PATCH v6] Add an explicit GIT_DIR to the list of excludes","fromName":"Pasha Bolokhov","fromEmail":"pasha.bolokhov@gmail.com","sentAt":"2014-06-11T23:28:28Z","receivedAt":"2014-06-11T23:28:28Z","isPatch":true,"sender":{"key":"pasha.bolokhov@gmail.com","avatar":null},"body":"When an explicit '--git-dir' option points to a directory inside\nthe work tree, git treats it as if it were any other directory.\nIn particular, 'git status' lists it as untracked, while 'git add -A'\nstages the metadata directory entirely\n\nAdd GIT_DIR to the list of excludes in a dedicated function\nadd_git_dir_exclude(), while checking that GIT_DIR is not just '.git'\nor its basename is not '.git', in which cases it would be ignored\nby default, and that GIT_DIR is inside GIT_WORK_TREE. Function\nadd_git_dir_exclude() is invoked at the beginning of\ndir.c:setup_standard_excludes()\n\nAlthough an analogous comparison of any given path against '.git'\nis done in treat_path(), this does not seem to be the right place\nto compare against GIT_DIR. Instead, the excludes provide an\neffective mechanism of ignoring a file/directory, and adding GIT_DIR\nas an exclude is equivalent to putting it into '.gitignore'. Function\nsetup_standard_excludes() was chosen because that is the place where\nthe excludes are initialized by the commands that are concerned about\nexcludes\n\nSigned-off-by: Pasha Bolokhov <pasha.bolokhov@gmail.com>\n---\nNegate the 'basename(GIT_DIR) != \".git\"' check for better readability\nin add_git_dir_exclude()\n\n Documentation/technical/api-directory-listing.txt |   4 +-\n dir.c                                             |  33 ++++\n t/t2205-add-gitdir.sh                             | 187 ++++++++++++++++++++++\n 3 files changed, 222 insertions(+), 2 deletions(-)\n create mode 100755 t/t2205-add-gitdir.sh\n\ndiff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt\nindex 7f8e78d..fd4a178 100644\n--- a/Documentation/technical/api-directory-listing.txt\n+++ b/Documentation/technical/api-directory-listing.txt\n@@ -90,8 +90,8 @@ marked. If you to exclude files, make sure you have loaded index first.\n   `add_exclude()`.\n \n * To add patterns from a file (e.g. `.git/info/exclude`), call\n-  `add_excludes_from_file()` , and/or set `dir.exclude_per_dir`.  A\n-  short-hand function `setup_standard_excludes()` can be used to set\n+  `add_excludes_from_file()` , and/or set `dir.exclude_per_dir`.  The\n+  short-hand function `setup_standard_excludes()` must be used to set\n   up the standard set of exclude settings.\n \n * Set options described in the Data Structure section above.\ndiff --git a/dir.c b/dir.c\nindex 797805d..3f57e34 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1608,11 +1608,44 @@ int remove_dir_recursively(struct strbuf *path, int flag)\n \treturn remove_dir_recurse(path, flag, NULL);\n }\n \n+static void add_git_dir_exclude(struct dir_struct *dir)\n+{\n+\tconst char *r_git, *gitdir = get_git_dir();\n+\tchar *n_git;\n+\tint len;\n+\n+\tr_git = real_path(absolute_path(gitdir));\n+\tn_git = xmalloc(strlen(r_git) + 1 + 1);\n+\tnormalize_path_copy(n_git, r_git);\n+\tlen = strlen(n_git);\n+\n+\t/* only add it if GIT_DIR does not end with '.git' or '/.git' */\n+\tif (!(len >= 4 && !strcmp(n_git + len - 4, \".git\") &&\n+\t      (len == 4 || n_git[len - 5] == '/'))) {\n+\t\tconst char *worktree = get_git_work_tree();\n+\n+\t\tif (!worktree ||\n+\t\t    dir_inside_of(n_git, worktree) >= 0) {\n+\t\t\tstruct exclude_list *el = add_exclude_list(dir, EXC_CMDL,\n+\t\t\t\t\t\t\t\"GIT_DIR setup\");\n+\t\t\tchar *reldir = worktree ? n_git + strlen(worktree) : n_git;\n+\n+\t\t\t/* append a trailing slash to exclude directories only */\n+\t\t\tn_git[len] = '/';\n+\t\t\tn_git[len + 1] = '\\0';\n+\t\t\tadd_exclude(reldir, \"\", 0, el, 0);\n+\t\t}\n+\t}\n+\tfree(n_git);\n+}\n+\n void setup_standard_excludes(struct dir_struct *dir)\n {\n \tconst char *path;\n \tchar *xdg_path;\n \n+\tadd_git_dir_exclude(dir);\n+\n \tdir->exclude_per_dir = \".gitignore\";\n \tpath = git_path(\"info/exclude\");\n \tif (!excludes_file) {\ndiff --git a/t/t2205-add-gitdir.sh b/t/t2205-add-gitdir.sh\nnew file mode 100755\nindex 0000000..721970e\n--- /dev/null\n+++ b/t/t2205-add-gitdir.sh\n@@ -0,0 +1,187 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2014 Pasha Bolokhov\n+#\n+\n+test_description='alternative repository path specified by --git-dir is ignored by add and status'\n+\n+. ./test-lib.sh\n+\n+#\n+# Create a tree:\n+#\n+#\trepo-inside/  repo-outside/\n+#\n+#\n+# repo-inside:\n+# \ta  b  c  d  dir1/ dir2/ [meta/]\n+#\n+# repo-inside/dir1:\n+# \te  f  g  h  meta/  ssubdir/\n+#\n+# repo-inside/dir1/meta:\n+# \taa\n+#\n+# repo-inside/dir1/ssubdir:\n+# \tmeta/\n+#\n+# repo-inside/dir1/ssubdir/meta:\n+# \taaa\n+#\n+# repo-inside/dir2:\n+#\tmeta\n+#\n+#\n+#\n+# repo-outside:\n+#\texternal/  tree/\n+#\n+# repo-outside/external:\n+#\t[meta/]\n+#\n+# repo-outside/tree:\n+#\tn  o  p  q  meta/  sub/\n+#\n+# repo-outside/tree/meta:\n+#\tbb\n+#\n+# repo-outside/tree/sub:\n+#\tmeta/\n+#\n+# repo-outside/tree/sub/meta:\n+#\tbbb\n+#\n+#\n+# (both of the above [meta/] denote the actual repositories)\n+#\n+\n+#\n+# First set of tests (in \"repo-inside/\"):\n+# ---------------------------------------\n+#\n+# Name the repository \"meta\" and see whether or not \"git status\" includes or\n+# ignores directories named \"meta\". Directory \"meta\" at the top level of\n+# \"repo-inside/\"is the repository and appears upon the first \"git init\"\n+#\n+#\n+# Second set of tests (in \"repo-outside/\"):\n+# -----------------------------------------\n+#\n+# Put the work tree into \"tree/\" and repository into \"external/meta\"\n+# (the latter directory appears upon the corresponding \"git init\").\n+# The work tree again contains directories named \"meta\", but those ones are\n+# tested not to be ignored now\n+#\n+\n+test_expect_success \"setup\" '\n+\tmkdir repo-inside/ &&\n+\t(\n+\t\tcd repo-inside/ &&\n+\t\tfor f in a b c d\n+\t\tdo\n+\t\t\techo \"DATA\" >\"$f\" || exit 1\n+\t\tdone &&\n+\t\tmkdir dir1 dir1/meta &&\n+\t\tmkdir dir1/ssubdir dir1/ssubdir/meta &&\n+\t\tfor f in e f g h\n+\t\tdo\n+\t\t\techo \"MORE DATA\" >\"dir1/$f\" || exit 1\n+\t\tdone &&\n+\t\techo \"EVEN more Data\" >dir1/meta/aa &&\n+\t\techo \"Data and BAIT\" >dir1/ssubdir/meta/aaa &&\n+\t\tmkdir dir2\n+\t\techo \"Not a Metadata File\" >dir2/meta\n+\t\tgit --git-dir=meta init\n+\t) &&\n+\tmkdir repo-outside/ repo-outside/external repo-outside/tree &&\n+\t(\n+\t\tcd repo-outside/tree &&\n+\t\tfor f in n o p q\n+\t\tdo\n+\t\t\techo \"Literal Data\" >\"$f\" || exit 1\n+\t\tdone &&\n+\t\tmkdir meta sub sub/meta &&\n+\t\techo \"Sample data\" >meta/bb &&\n+\t\techo \"Stream of data\" >sub/meta/bbb &&\n+\t\tgit --git-dir=../external/meta init\n+\t)\n+'\n+\n+\n+#\n+# The first set of tests (the repository is inside the work tree)\n+#\n+test_expect_success \"'git status' ignores the repository directory\" '\n+\t(\n+\t\tcd repo-inside &&\n+\t\tgit --git-dir=meta --work-tree=. status --porcelain --untracked=all |\n+\t\t\tgrep meta | sort >status.actual.2 &&\n+\t\tcat >status.expect.2 <<-\\EOF &&\n+\t\t?? dir1/meta/aa\n+\t\t?? dir1/ssubdir/meta/aaa\n+\t\t?? dir2/meta\n+\t\tEOF\n+\t\ttest_cmp status.actual.2 status.expect.2\n+\t)\n+'\n+\n+test_expect_success \"'git add -A' ignores the repository directory\" '\n+\t(\n+\t\tcd repo-inside &&\n+\t\tgit --git-dir=meta --work-tree=. add -A &&\n+\t\tgit --git-dir=meta --work-tree=. status --porcelain | grep meta | sort >status.actual.3 &&\n+\t\tcat >status.expect.3 <<-\\EOF &&\n+\t\tA  dir1/meta/aa\n+\t\tA  dir1/ssubdir/meta/aaa\n+\t\tA  dir2/meta\n+\t\tEOF\n+\t\ttest_cmp status.actual.3 status.expect.3\n+\t)\n+'\n+\n+test_expect_success \"'git grep --exclude-standard' ignores the repository directory\" '\n+\t(\n+\t\tcd repo-inside &&\n+\t\ttest_might_fail git --git-dir=meta \\\n+\t\t\tgrep --no-index --exclude-standard BAIT >grep.actual.4 &&\n+\t\tcat >grep.expect.4 <<-\\EOF &&\n+\t\tdir1/ssubdir/meta/aaa:Data and BAIT\n+\t\tEOF\n+\t\ttest_cmp grep.actual.4 grep.expect.4\n+\t)\n+'\n+\n+#\n+# The second set of tests (the repository is outside of the work tree)\n+#\n+test_expect_success \"'git status' acknowledges directories 'meta' \\\n+if repo is not within work tree\" '\n+\ttest_might_fail rm -rf meta/ &&\n+\t(\n+\t\tcd repo-outside/tree &&\n+\t\tgit --git-dir=../external/meta init &&\n+\t\tgit --git-dir=../external/meta --work-tree=. status --porcelain --untracked=all |\n+\t\t\tgrep meta | sort >status.actual.5 &&\n+\t\tcat >status.expect.5 <<-\\EOF &&\n+\t\t?? meta/bb\n+\t\t?? sub/meta/bbb\n+\t\tEOF\n+\t\ttest_cmp status.actual.5 status.expect.5\n+\t)\n+'\n+\n+test_expect_success \"'git add -A' adds 'meta' if the repo is outside the work tree\" '\n+\t(\n+\t\tcd repo-outside/tree &&\n+\t\tgit --git-dir=../external/meta --work-tree=. add -A &&\n+\t\tgit --git-dir=../external/meta --work-tree=. status --porcelain --untracked=all |\n+\t\t\tgrep meta | sort >status.actual.6 &&\n+\t\tcat >status.expect.6 <<-\\EOF &&\n+\t\tA  meta/bb\n+\t\tA  sub/meta/bbb\n+\t\tEOF\n+\t\ttest_cmp status.actual.6 status.expect.6\n+\t)\n+'\n+\n+test_done\n-- \n1.9.1\n"},{"id":"243933","messageId":"53994667.90209@kdbg.org","threadId":"36838","inReplyTo":"1402529308-3940-1-git-send-email-pasha.bolokhov@gmail.com","subject":"Re: [PATCH v6] Add an explicit GIT_DIR to the list of excludes","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2014-06-12T06:19:19Z","receivedAt":"2014-06-12T06:19:19Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 12.06.2014 01:28, schrieb Pasha Bolokhov:\n> +test_expect_success \"setup\" '\n> +\tmkdir repo-inside/ &&\n> +\t(\n> +\t\tcd repo-inside/ &&\n> +\t\tfor f in a b c d\n> +\t\tdo\n> +\t\t\techo \"DATA\" >\"$f\" || exit 1\n> +\t\tdone &&\n> +\t\tmkdir dir1 dir1/meta &&\n> +\t\tmkdir dir1/ssubdir dir1/ssubdir/meta &&\n> +\t\tfor f in e f g h\n> +\t\tdo\n> +\t\t\techo \"MORE DATA\" >\"dir1/$f\" || exit 1\n> +\t\tdone &&\n> +\t\techo \"EVEN more Data\" >dir1/meta/aa &&\n> +\t\techo \"Data and BAIT\" >dir1/ssubdir/meta/aaa &&\n> +\t\tmkdir dir2\n> +\t\techo \"Not a Metadata File\" >dir2/meta\n\n&&-chain broken twice.\n\nWe already use 'mkdir -p' elsewhere; you can use it here as well to\ncontract several mkdir invocations.\n\n> +\t\tgit --git-dir=meta init\n> +\t) &&\n> +\tmkdir repo-outside/ repo-outside/external repo-outside/tree &&\n> +\t(\n> +\t\tcd repo-outside/tree &&\n> +\t\tfor f in n o p q\n> +\t\tdo\n> +\t\t\techo \"Literal Data\" >\"$f\" || exit 1\n> +\t\tdone &&\n> +\t\tmkdir meta sub sub/meta &&\n> +\t\techo \"Sample data\" >meta/bb &&\n> +\t\techo \"Stream of data\" >sub/meta/bbb &&\n> +\t\tgit --git-dir=../external/meta init\n> +\t)\n> +'\n> +\n> +\n> +#\n> +# The first set of tests (the repository is inside the work tree)\n> +#\n> +test_expect_success \"'git status' ignores the repository directory\" '\n> +\t(\n> +\t\tcd repo-inside &&\n> +\t\tgit --git-dir=meta --work-tree=. status --porcelain --untracked=all |\n> +\t\t\tgrep meta | sort >status.actual.2 &&\n\nPlease do not place a git invocation in a pipline such that it is not\nthe last command; its exist status would be ignored:\n\n\t\tgit --git-dir=meta --work-tree=. status --porcelain --untracked=all\n>status.actual.2+ &&\n\t\tgrep meta status.actual.2+ | sort >status.actual.2 &&\n\nThere are more cases like this later in the patch.\n\n> +\t\tcat >status.expect.2 <<-\\EOF &&\n> +\t\t?? dir1/meta/aa\n> +\t\t?? dir1/ssubdir/meta/aaa\n> +\t\t?? dir2/meta\n> +\t\tEOF\n> +\t\ttest_cmp status.actual.2 status.expect.2\n\nIt is customary to call the files 'expect' and 'actual'. Furthermore,\nswap the order so that in case of a failure the diff shows how the\nactual text was changed from the expected text:\n\n\t\ttest_cmp status.expect.2 status.actual.2\n\n> +\t)\n> +'\n> +\n> +test_expect_success \"'git add -A' ignores the repository directory\" '\n> +\t(\n> +\t\tcd repo-inside &&\n> +\t\tgit --git-dir=meta --work-tree=. add -A &&\n> +\t\tgit --git-dir=meta --work-tree=. status --porcelain | grep meta | sort >status.actual.3 &&\n> +\t\tcat >status.expect.3 <<-\\EOF &&\n> +\t\tA  dir1/meta/aa\n> +\t\tA  dir1/ssubdir/meta/aaa\n> +\t\tA  dir2/meta\n> +\t\tEOF\n> +\t\ttest_cmp status.actual.3 status.expect.3\n> +\t)\n> +'\n> +\n> +test_expect_success \"'git grep --exclude-standard' ignores the repository directory\" '\n> +\t(\n> +\t\tcd repo-inside &&\n> +\t\ttest_might_fail git --git-dir=meta \\\n> +\t\t\tgrep --no-index --exclude-standard BAIT >grep.actual.4 &&\n> +\t\tcat >grep.expect.4 <<-\\EOF &&\n> +\t\tdir1/ssubdir/meta/aaa:Data and BAIT\n> +\t\tEOF\n> +\t\ttest_cmp grep.actual.4 grep.expect.4\n> +\t)\n> +'\n> +\n> +#\n> +# The second set of tests (the repository is outside of the work tree)\n> +#\n> +test_expect_success \"'git status' acknowledges directories 'meta' \\\n> +if repo is not within work tree\" '\n> +\ttest_might_fail rm -rf meta/ &&\n\nHow might this fail? Only if permissions are wrong, and then we do want\nthis to fail.\n\nMoreover, test_*_fail helpers are intended to be used only with git\ncommands; we don't expect system commands to fail in unexpected ways.\n\n> +\t(\n> +\t\tcd repo-outside/tree &&\n> +\t\tgit --git-dir=../external/meta init &&\n> +\t\tgit --git-dir=../external/meta --work-tree=. status --porcelain --untracked=all |\n> +\t\t\tgrep meta | sort >status.actual.5 &&\n> +\t\tcat >status.expect.5 <<-\\EOF &&\n> +\t\t?? meta/bb\n> +\t\t?? sub/meta/bbb\n> +\t\tEOF\n> +\t\ttest_cmp status.actual.5 status.expect.5\n> +\t)\n> +'\n> +\n> +test_expect_success \"'git add -A' adds 'meta' if the repo is outside the work tree\" '\n> +\t(\n> +\t\tcd repo-outside/tree &&\n> +\t\tgit --git-dir=../external/meta --work-tree=. add -A &&\n> +\t\tgit --git-dir=../external/meta --work-tree=. status --porcelain --untracked=all |\n> +\t\t\tgrep meta | sort >status.actual.6 &&\n> +\t\tcat >status.expect.6 <<-\\EOF &&\n> +\t\tA  meta/bb\n> +\t\tA  sub/meta/bbb\n> +\t\tEOF\n> +\t\ttest_cmp status.actual.6 status.expect.6\n> +\t)\n> +'\n> +\n> +test_done\n> \n"},{"id":"244143","messageId":"CAKpPgvf=OGRHFv81x2b_PVqzALbSWHaJvejyy5w7aS_P+QFA6w@mail.gmail.com","threadId":"36838","inReplyTo":"53994667.90209@kdbg.org","subject":"Re: [PATCH v6] Add an explicit GIT_DIR to the list of excludes","fromName":"Pasha Bolokhov","fromEmail":"pasha.bolokhov@gmail.com","sentAt":"2014-06-13T16:10:22Z","receivedAt":"2014-06-13T16:10:22Z","isPatch":true,"sender":{"key":"pasha.bolokhov@gmail.com","avatar":null},"body":">> +             test_cmp status.actual.2 status.expect.2\n>\n> It is customary to call the files 'expect' and 'actual'. Furthermore,\n> swap the order so that in case of a failure the diff shows how the\n> actual text was changed from the expected text:\n>\n>                 test_cmp status.expect.2 status.actual.2\n\nSo, is naming the files \"status.expect.2\" instead of just\n\"expect\"/\"actual\" ok or not?\nThose prefixes \"status\" etc just help sorting out where the problem\nlies that causes the test to fail. But let me know if this is too\n\"detailed\"\n\n>\n> Moreover, test_*_fail helpers are intended to be used only with git\n> commands; we don't expect system commands to fail in unexpected ways.\n\nOk, no problem, will change that. The only thing, I saw this in other\ntests, so decided to use it too. Those tests use \"test_mighf_fail rm\"\nand \"ls\" seemingly without invocation of git\n"},{"id":"244145","messageId":"1402677123-6865-1-git-send-email-pasha.bolokhov@gmail.com","threadId":"36838","inReplyTo":"53994667.90209@kdbg.org","subject":"[PATCH v7] Add an explicit GIT_DIR to the list of excludes","fromName":"Pasha Bolokhov","fromEmail":"pasha.bolokhov@gmail.com","sentAt":"2014-06-13T16:32:03Z","receivedAt":"2014-06-13T16:32:03Z","isPatch":true,"sender":{"key":"pasha.bolokhov@gmail.com","avatar":null},"body":"When an explicit '--git-dir' option points to a directory inside\nthe work tree, git treats it as if it were any other directory.\nIn particular, 'git status' lists it as untracked, while 'git add -A'\nstages the metadata directory entirely\n\nAdd GIT_DIR to the list of excludes in a dedicated function\nadd_git_dir_exclude(), while checking that GIT_DIR is not just '.git'\nor its basename is not '.git', in which cases it would be ignored\nby default, and that GIT_DIR is inside GIT_WORK_TREE. Function\nadd_git_dir_exclude() is invoked at the beginning of\ndir.c:setup_standard_excludes()\n\nAlthough an analogous comparison of any given path against '.git'\nis done in treat_path(), this does not seem to be the right place\nto compare against GIT_DIR. Instead, the excludes provide an\neffective mechanism of ignoring a file/directory, and adding GIT_DIR\nas an exclude is equivalent to putting it into '.gitignore'. Function\nsetup_standard_excludes() was chosen because that is the place where\nthe excludes are initialized by the commands that are concerned about\nexcludes\n\nSigned-off-by: Pasha Bolokhov <pasha.bolokhov@gmail.com>\n---\nBrush up the test -- make sure 'git' is the last command in pipelines,\ncompact mkdirs into 'mkdir -p', remove unnecessary 'test_might_fail'\n\n Documentation/technical/api-directory-listing.txt |   4 +-\n dir.c                                             |  33 ++++\n t/t2205-add-gitdir.sh                             | 187 ++++++++++++++++++++++\n 3 files changed, 222 insertions(+), 2 deletions(-)\n create mode 100755 t/t2205-add-gitdir.sh\n\ndiff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt\nindex 7f8e78d..fd4a178 100644\n--- a/Documentation/technical/api-directory-listing.txt\n+++ b/Documentation/technical/api-directory-listing.txt\n@@ -90,8 +90,8 @@ marked. If you to exclude files, make sure you have loaded index first.\n   `add_exclude()`.\n \n * To add patterns from a file (e.g. `.git/info/exclude`), call\n-  `add_excludes_from_file()` , and/or set `dir.exclude_per_dir`.  A\n-  short-hand function `setup_standard_excludes()` can be used to set\n+  `add_excludes_from_file()` , and/or set `dir.exclude_per_dir`.  The\n+  short-hand function `setup_standard_excludes()` must be used to set\n   up the standard set of exclude settings.\n \n * Set options described in the Data Structure section above.\ndiff --git a/dir.c b/dir.c\nindex 797805d..3f57e34 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1608,11 +1608,44 @@ int remove_dir_recursively(struct strbuf *path, int flag)\n \treturn remove_dir_recurse(path, flag, NULL);\n }\n \n+static void add_git_dir_exclude(struct dir_struct *dir)\n+{\n+\tconst char *r_git, *gitdir = get_git_dir();\n+\tchar *n_git;\n+\tint len;\n+\n+\tr_git = real_path(absolute_path(gitdir));\n+\tn_git = xmalloc(strlen(r_git) + 1 + 1);\n+\tnormalize_path_copy(n_git, r_git);\n+\tlen = strlen(n_git);\n+\n+\t/* only add it if GIT_DIR does not end with '.git' or '/.git' */\n+\tif (!(len >= 4 && !strcmp(n_git + len - 4, \".git\") &&\n+\t      (len == 4 || n_git[len - 5] == '/'))) {\n+\t\tconst char *worktree = get_git_work_tree();\n+\n+\t\tif (!worktree ||\n+\t\t    dir_inside_of(n_git, worktree) >= 0) {\n+\t\t\tstruct exclude_list *el = add_exclude_list(dir, EXC_CMDL,\n+\t\t\t\t\t\t\t\"GIT_DIR setup\");\n+\t\t\tchar *reldir = worktree ? n_git + strlen(worktree) : n_git;\n+\n+\t\t\t/* append a trailing slash to exclude directories only */\n+\t\t\tn_git[len] = '/';\n+\t\t\tn_git[len + 1] = '\\0';\n+\t\t\tadd_exclude(reldir, \"\", 0, el, 0);\n+\t\t}\n+\t}\n+\tfree(n_git);\n+}\n+\n void setup_standard_excludes(struct dir_struct *dir)\n {\n \tconst char *path;\n \tchar *xdg_path;\n \n+\tadd_git_dir_exclude(dir);\n+\n \tdir->exclude_per_dir = \".gitignore\";\n \tpath = git_path(\"info/exclude\");\n \tif (!excludes_file) {\ndiff --git a/t/t2205-add-gitdir.sh b/t/t2205-add-gitdir.sh\nnew file mode 100755\nindex 0000000..92b925f\n--- /dev/null\n+++ b/t/t2205-add-gitdir.sh\n@@ -0,0 +1,187 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2014 Pasha Bolokhov\n+#\n+\n+test_description='alternative repository path specified by --git-dir is ignored by add and status'\n+\n+. ./test-lib.sh\n+\n+#\n+# Create a tree:\n+#\n+#\trepo-inside/  repo-outside/\n+#\n+#\n+# repo-inside:\n+# \ta  b  c  d  dir1/ dir2/ [meta/]\n+#\n+# repo-inside/dir1:\n+# \te  f  g  h  meta/  ssubdir/\n+#\n+# repo-inside/dir1/meta:\n+# \taa\n+#\n+# repo-inside/dir1/ssubdir:\n+# \tmeta/\n+#\n+# repo-inside/dir1/ssubdir/meta:\n+# \taaa\n+#\n+# repo-inside/dir2:\n+#\tmeta\n+#\n+#\n+#\n+# repo-outside:\n+#\texternal/  tree/\n+#\n+# repo-outside/external:\n+#\t[meta/]\n+#\n+# repo-outside/tree:\n+#\tn  o  p  q  meta/  sub/\n+#\n+# repo-outside/tree/meta:\n+#\tbb\n+#\n+# repo-outside/tree/sub:\n+#\tmeta/\n+#\n+# repo-outside/tree/sub/meta:\n+#\tbbb\n+#\n+#\n+# (both of the above [meta/] denote the actual repositories)\n+#\n+\n+#\n+# First set of tests (in \"repo-inside/\"):\n+# ---------------------------------------\n+#\n+# Name the repository \"meta\" and see whether or not \"git status\" includes or\n+# ignores directories named \"meta\". Directory \"meta\" at the top level of\n+# \"repo-inside/\"is the repository and appears upon the first \"git init\"\n+#\n+#\n+# Second set of tests (in \"repo-outside/\"):\n+# -----------------------------------------\n+#\n+# Put the work tree into \"tree/\" and repository into \"external/meta\"\n+# (the latter directory appears upon the corresponding \"git init\").\n+# The work tree again contains directories named \"meta\", but those ones are\n+# tested not to be ignored now\n+#\n+\n+test_expect_success \"setup\" '\n+\tmkdir repo-inside/ &&\n+\t(\n+\t\tcd repo-inside/ &&\n+\t\tfor f in a b c d\n+\t\tdo\n+\t\t\techo \"DATA\" >\"$f\" || exit 1\n+\t\tdone &&\n+\t\tmkdir -p dir1/meta dir1/ssubdir/meta &&\n+\t\tfor f in e f g h\n+\t\tdo\n+\t\t\techo \"MORE DATA\" >\"dir1/$f\" || exit 1\n+\t\tdone &&\n+\t\techo \"EVEN more Data\" >dir1/meta/aa &&\n+\t\techo \"Data and BAIT\" >dir1/ssubdir/meta/aaa &&\n+\t\tmkdir dir2 &&\n+\t\techo \"Not a Metadata File\" >dir2/meta &&\n+\t\tgit --git-dir=meta init\n+\t) &&\n+\tmkdir repo-outside/ repo-outside/external repo-outside/tree &&\n+\t(\n+\t\tcd repo-outside/tree &&\n+\t\tfor f in n o p q\n+\t\tdo\n+\t\t\techo \"Literal Data\" >\"$f\" || exit 1\n+\t\tdone &&\n+\t\tmkdir -p meta sub/meta &&\n+\t\techo \"Sample data\" >meta/bb &&\n+\t\techo \"Stream of data\" >sub/meta/bbb &&\n+\t\tgit --git-dir=../external/meta init\n+\t)\n+'\n+\n+\n+#\n+# The first set of tests (the repository is inside the work tree)\n+#\n+test_expect_success \"'git status' ignores the repository directory\" '\n+\t(\n+\t\tcd repo-inside &&\n+\t\tgit --git-dir=meta --work-tree=. status --porcelain --untracked=all >status.actual.2+ &&\n+\t\tgrep meta status.actual.2+ | sort >status.actual.2 &&\n+\t\tcat >status.expect.2 <<-\\EOF &&\n+\t\t?? dir1/meta/aa\n+\t\t?? dir1/ssubdir/meta/aaa\n+\t\t?? dir2/meta\n+\t\tEOF\n+\t\ttest_cmp status.expect.2 status.actual.2\n+\t)\n+'\n+\n+test_expect_success \"'git add -A' ignores the repository directory\" '\n+\t(\n+\t\tcd repo-inside &&\n+\t\tgit --git-dir=meta --work-tree=. add -A &&\n+\t\tgit --git-dir=meta --work-tree=. status --porcelain >status.actual.3+ &&\n+\t\tgrep meta status.actual.3+ | sort >status.actual.3 &&\n+\t\tcat >status.expect.3 <<-\\EOF &&\n+\t\tA  dir1/meta/aa\n+\t\tA  dir1/ssubdir/meta/aaa\n+\t\tA  dir2/meta\n+\t\tEOF\n+\t\ttest_cmp status.expect.3 status.actual.3\n+\t)\n+'\n+\n+test_expect_success \"'git grep --exclude-standard' ignores the repository directory\" '\n+\t(\n+\t\tcd repo-inside &&\n+\t\ttest_might_fail git --git-dir=meta \\\n+\t\t\tgrep --no-index --exclude-standard BAIT >grep.actual.4 &&\n+\t\tcat >grep.expect.4 <<-\\EOF &&\n+\t\tdir1/ssubdir/meta/aaa:Data and BAIT\n+\t\tEOF\n+\t\ttest_cmp grep.expect.4 grep.actual.4\n+\t)\n+'\n+\n+#\n+# The second set of tests (the repository is outside of the work tree)\n+#\n+test_expect_success \"'git status' acknowledges directories 'meta' \\\n+if repo is not within work tree\" '\n+\trm -rf meta/ &&\n+\t(\n+\t\tcd repo-outside/tree &&\n+\t\tgit --git-dir=../external/meta init &&\n+\t\tgit --git-dir=../external/meta --work-tree=. status --porcelain --untracked=all >status.actual.5+ &&\n+\t\tgrep meta status.actual.5+ | sort >status.actual.5 &&\n+\t\tcat >status.expect.5 <<-\\EOF &&\n+\t\t?? meta/bb\n+\t\t?? sub/meta/bbb\n+\t\tEOF\n+\t\ttest_cmp status.expect.5 status.actual.5\n+\t)\n+'\n+\n+test_expect_success \"'git add -A' adds 'meta' if the repo is outside the work tree\" '\n+\t(\n+\t\tcd repo-outside/tree &&\n+\t\tgit --git-dir=../external/meta --work-tree=. add -A &&\n+\t\tgit --git-dir=../external/meta --work-tree=. status --porcelain --untracked=all >status.actual.6+ &&\n+\t\tgrep meta status.actual.6+ | sort >status.actual.6 &&\n+\t\tcat >status.expect.6 <<-\\EOF &&\n+\t\tA  meta/bb\n+\t\tA  sub/meta/bbb\n+\t\tEOF\n+\t\ttest_cmp status.expect.6 status.actual.6\n+\t)\n+'\n+\n+test_done\n-- \n1.9.1\n"},{"id":"244148","messageId":"xmqqfvj84uue.fsf@gitster.dls.corp.google.com","threadId":"36838","inReplyTo":"CAKpPgvf=OGRHFv81x2b_PVqzALbSWHaJvejyy5w7aS_P+QFA6w@mail.gmail.com","subject":"Re: [PATCH v6] Add an explicit GIT_DIR to the list of excludes","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-06-13T16:50:33Z","receivedAt":"2014-06-13T16:50:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Pasha Bolokhov <pasha.bolokhov@gmail.com> writes:\n\n>>> +             test_cmp status.actual.2 status.expect.2\n>>\n>> It is customary to call the files 'expect' and 'actual'. Furthermore,\n>> swap the order so that in case of a failure the diff shows how the\n>> actual text was changed from the expected text:\n>>\n>>                 test_cmp status.expect.2 status.actual.2\n>\n> So, is naming the files \"status.expect.2\" instead of just\n> \"expect\"/\"actual\" ok or not?\n\nUnless there is a compelling reason not to, just use expect/actual,\nwithout \"status.\" or \".<number>\".  When debugging, people pass the\n\"-i\" (and often \"-v\") to the script which would stop at the first\nbreakage.  If they can inspect the result in \"actual\", that is far\nmore pleasant than having to know that it is status.actual.43 they\nhave to inspect while ignoring clutters in status.actual.{1-42} that\nhave nothing to do with the breakage they are dealing with.\n\n> Ok, no problem, will change that. The only thing, I saw this in other\n> tests, so decided to use it too. Those tests use \"test_mighf_fail rm\"\n\n\"might_fail\" and \"must_fail\" are totally different beasts.\n\"must_fail\" is about controlled failure exit (i.e. no segfaulting\nallowed) and we avoid using it on non-git things.\n\nWhen you run clean-up commands without knowing if there is anythning\nto clean-up, e.g. you may or may not have successfully created a\njunk file and you want to make sure that file does not exist.  It\nhappens that \"rm junk\" has a convenient option \"-f\" to make it not\nbarf upon missing \"junk\", so you can do so with \"rm -f junk\" instead\nof saying \"test_might_fail rm junk\", but \"might_fail\" is designed to\nbe useful for commands that do not have such a convenient option.\n"},{"id":"244195","messageId":"1402773393-3445-1-git-send-email-pasha.bolokhov@gmail.com","threadId":"36838","inReplyTo":"xmqqfvj84uue.fsf@gitster.dls.corp.google.com","subject":"[PATCH v8] Add an explicit GIT_DIR to the list of excludes","fromName":"Pasha Bolokhov","fromEmail":"pasha.bolokhov@gmail.com","sentAt":"2014-06-14T19:16:33Z","receivedAt":"2014-06-14T19:16:33Z","isPatch":true,"sender":{"key":"pasha.bolokhov@gmail.com","avatar":null},"body":"When an explicit '--git-dir' option points to a directory inside\nthe work tree, git treats it as if it were any other directory.\nIn particular, 'git status' lists it as untracked, while 'git add -A'\nstages the metadata directory entirely\n\nAdd GIT_DIR to the list of excludes in a dedicated function\nadd_git_dir_exclude(), while checking that GIT_DIR is not just '.git'\nor its basename is not '.git', in which cases it would be ignored\nby default, and that GIT_DIR is inside GIT_WORK_TREE. Function\nadd_git_dir_exclude() is invoked at the beginning of\ndir.c:setup_standard_excludes()\n\nAlthough an analogous comparison of any given path against '.git'\nis done in treat_path(), this does not seem to be the right place\nto compare against GIT_DIR. Instead, the excludes provide an\neffective mechanism of ignoring a file/directory, and adding GIT_DIR\nas an exclude is equivalent to putting it into '.gitignore'. Function\nsetup_standard_excludes() was chosen because that is the place where\nthe excludes are initialized by the commands that are concerned about\nexcludes\n\nSigned-off-by: Pasha Bolokhov <pasha.bolokhov@gmail.com>\n---\nRename output files in the test into just 'expect' and 'actual'.\nWill add documentation on the standard excludes in the later revision.\n\n Documentation/technical/api-directory-listing.txt |   4 +-\n dir.c                                             |  33 ++++\n t/t2205-add-gitdir.sh                             | 187 ++++++++++++++++++++++\n 3 files changed, 222 insertions(+), 2 deletions(-)\n create mode 100755 t/t2205-add-gitdir.sh\n\ndiff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt\nindex 7f8e78d..fd4a178 100644\n--- a/Documentation/technical/api-directory-listing.txt\n+++ b/Documentation/technical/api-directory-listing.txt\n@@ -90,8 +90,8 @@ marked. If you to exclude files, make sure you have loaded index first.\n   `add_exclude()`.\n \n * To add patterns from a file (e.g. `.git/info/exclude`), call\n-  `add_excludes_from_file()` , and/or set `dir.exclude_per_dir`.  A\n-  short-hand function `setup_standard_excludes()` can be used to set\n+  `add_excludes_from_file()` , and/or set `dir.exclude_per_dir`.  The\n+  short-hand function `setup_standard_excludes()` must be used to set\n   up the standard set of exclude settings.\n \n * Set options described in the Data Structure section above.\ndiff --git a/dir.c b/dir.c\nindex 797805d..3f57e34 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1608,11 +1608,44 @@ int remove_dir_recursively(struct strbuf *path, int flag)\n \treturn remove_dir_recurse(path, flag, NULL);\n }\n \n+static void add_git_dir_exclude(struct dir_struct *dir)\n+{\n+\tconst char *r_git, *gitdir = get_git_dir();\n+\tchar *n_git;\n+\tint len;\n+\n+\tr_git = real_path(absolute_path(gitdir));\n+\tn_git = xmalloc(strlen(r_git) + 1 + 1);\n+\tnormalize_path_copy(n_git, r_git);\n+\tlen = strlen(n_git);\n+\n+\t/* only add it if GIT_DIR does not end with '.git' or '/.git' */\n+\tif (!(len >= 4 && !strcmp(n_git + len - 4, \".git\") &&\n+\t      (len == 4 || n_git[len - 5] == '/'))) {\n+\t\tconst char *worktree = get_git_work_tree();\n+\n+\t\tif (!worktree ||\n+\t\t    dir_inside_of(n_git, worktree) >= 0) {\n+\t\t\tstruct exclude_list *el = add_exclude_list(dir, EXC_CMDL,\n+\t\t\t\t\t\t\t\"GIT_DIR setup\");\n+\t\t\tchar *reldir = worktree ? n_git + strlen(worktree) : n_git;\n+\n+\t\t\t/* append a trailing slash to exclude directories only */\n+\t\t\tn_git[len] = '/';\n+\t\t\tn_git[len + 1] = '\\0';\n+\t\t\tadd_exclude(reldir, \"\", 0, el, 0);\n+\t\t}\n+\t}\n+\tfree(n_git);\n+}\n+\n void setup_standard_excludes(struct dir_struct *dir)\n {\n \tconst char *path;\n \tchar *xdg_path;\n \n+\tadd_git_dir_exclude(dir);\n+\n \tdir->exclude_per_dir = \".gitignore\";\n \tpath = git_path(\"info/exclude\");\n \tif (!excludes_file) {\ndiff --git a/t/t2205-add-gitdir.sh b/t/t2205-add-gitdir.sh\nnew file mode 100755\nindex 0000000..c48a845\n--- /dev/null\n+++ b/t/t2205-add-gitdir.sh\n@@ -0,0 +1,187 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2014 Pasha Bolokhov\n+#\n+\n+test_description='alternative repository path specified by --git-dir is ignored by add and status'\n+\n+. ./test-lib.sh\n+\n+#\n+# Create a tree:\n+#\n+#\trepo-inside/  repo-outside/\n+#\n+#\n+# repo-inside:\n+# \ta  b  c  d  dir1/ dir2/ [meta/]\n+#\n+# repo-inside/dir1:\n+# \te  f  g  h  meta/  ssubdir/\n+#\n+# repo-inside/dir1/meta:\n+# \taa\n+#\n+# repo-inside/dir1/ssubdir:\n+# \tmeta/\n+#\n+# repo-inside/dir1/ssubdir/meta:\n+# \taaa\n+#\n+# repo-inside/dir2:\n+#\tmeta\n+#\n+#\n+#\n+# repo-outside:\n+#\texternal/  tree/\n+#\n+# repo-outside/external:\n+#\t[meta/]\n+#\n+# repo-outside/tree:\n+#\tn  o  p  q  meta/  sub/\n+#\n+# repo-outside/tree/meta:\n+#\tbb\n+#\n+# repo-outside/tree/sub:\n+#\tmeta/\n+#\n+# repo-outside/tree/sub/meta:\n+#\tbbb\n+#\n+#\n+# (both of the above [meta/] denote the actual repositories)\n+#\n+\n+#\n+# First set of tests (in \"repo-inside/\"):\n+# ---------------------------------------\n+#\n+# Name the repository \"meta\" and see whether or not \"git status\" includes or\n+# ignores directories named \"meta\". Directory \"meta\" at the top level of\n+# \"repo-inside/\"is the repository and appears upon the first \"git init\"\n+#\n+#\n+# Second set of tests (in \"repo-outside/\"):\n+# -----------------------------------------\n+#\n+# Put the work tree into \"tree/\" and repository into \"external/meta\"\n+# (the latter directory appears upon the corresponding \"git init\").\n+# The work tree again contains directories named \"meta\", but those ones are\n+# tested not to be ignored now\n+#\n+\n+test_expect_success \"setup\" '\n+\tmkdir repo-inside/ &&\n+\t(\n+\t\tcd repo-inside/ &&\n+\t\tfor f in a b c d\n+\t\tdo\n+\t\t\techo \"DATA\" >\"$f\" || exit 1\n+\t\tdone &&\n+\t\tmkdir -p dir1/meta dir1/ssubdir/meta &&\n+\t\tfor f in e f g h\n+\t\tdo\n+\t\t\techo \"MORE DATA\" >\"dir1/$f\" || exit 1\n+\t\tdone &&\n+\t\techo \"EVEN more Data\" >dir1/meta/aa &&\n+\t\techo \"Data and BAIT\" >dir1/ssubdir/meta/aaa &&\n+\t\tmkdir dir2 &&\n+\t\techo \"Not a Metadata File\" >dir2/meta &&\n+\t\tgit --git-dir=meta init\n+\t) &&\n+\tmkdir repo-outside/ repo-outside/external repo-outside/tree &&\n+\t(\n+\t\tcd repo-outside/tree &&\n+\t\tfor f in n o p q\n+\t\tdo\n+\t\t\techo \"Literal Data\" >\"$f\" || exit 1\n+\t\tdone &&\n+\t\tmkdir -p meta sub/meta &&\n+\t\techo \"Sample data\" >meta/bb &&\n+\t\techo \"Stream of data\" >sub/meta/bbb &&\n+\t\tgit --git-dir=../external/meta init\n+\t)\n+'\n+\n+\n+#\n+# The first set of tests (the repository is inside the work tree)\n+#\n+test_expect_success \"'git status' ignores the repository directory\" '\n+\t(\n+\t\tcd repo-inside &&\n+\t\tgit --git-dir=meta --work-tree=. status --porcelain --untracked=all >actual+ &&\n+\t\tgrep meta actual+ | sort >actual &&\n+\t\tcat >expect <<-\\EOF &&\n+\t\t?? dir1/meta/aa\n+\t\t?? dir1/ssubdir/meta/aaa\n+\t\t?? dir2/meta\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success \"'git add -A' ignores the repository directory\" '\n+\t(\n+\t\tcd repo-inside &&\n+\t\tgit --git-dir=meta --work-tree=. add -A &&\n+\t\tgit --git-dir=meta --work-tree=. status --porcelain >actual+ &&\n+\t\tgrep meta actual+ | sort >actual &&\n+\t\tcat >expect <<-\\EOF &&\n+\t\tA  dir1/meta/aa\n+\t\tA  dir1/ssubdir/meta/aaa\n+\t\tA  dir2/meta\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success \"'git grep --exclude-standard' ignores the repository directory\" '\n+\t(\n+\t\tcd repo-inside &&\n+\t\ttest_might_fail git --git-dir=meta \\\n+\t\t\tgrep --no-index --exclude-standard BAIT >actual &&\n+\t\tcat >expect <<-\\EOF &&\n+\t\tdir1/ssubdir/meta/aaa:Data and BAIT\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+#\n+# The second set of tests (the repository is outside of the work tree)\n+#\n+test_expect_success \"'git status' acknowledges directories 'meta' \\\n+if repo is not within work tree\" '\n+\trm -rf meta/ &&\n+\t(\n+\t\tcd repo-outside/tree &&\n+\t\tgit --git-dir=../external/meta init &&\n+\t\tgit --git-dir=../external/meta --work-tree=. status --porcelain --untracked=all >actual+ &&\n+\t\tgrep meta actual+ | sort >actual &&\n+\t\tcat >expect <<-\\EOF &&\n+\t\t?? meta/bb\n+\t\t?? sub/meta/bbb\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success \"'git add -A' adds 'meta' if the repo is outside the work tree\" '\n+\t(\n+\t\tcd repo-outside/tree &&\n+\t\tgit --git-dir=../external/meta --work-tree=. add -A &&\n+\t\tgit --git-dir=../external/meta --work-tree=. status --porcelain --untracked=all >actual+ &&\n+\t\tgrep meta actual+ | sort >actual &&\n+\t\tcat >expect <<-\\EOF &&\n+\t\tA  meta/bb\n+\t\tA  sub/meta/bbb\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_done\n-- \n1.9.1\n"}]}