{"thread":{"id":"35520","subject":"[PATCH/POC 0/7] Support multiple worktrees","startedAt":"2013-12-11T14:15:26Z","lastAt":"2013-12-23T03:38:31Z","messageCount":49,"participants":["Nguyễn Thái Ngọc Duy","Junio C Hamano","Jonathan Nieder","Duy Nguyen","Ramsay Jones","Torsten Bögershausen","Antoine Pelisse"],"isPatch":true,"patchVersion":1,"patchTotal":7},"messages":[{"id":"231910","messageId":"1386771333-32574-1-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":null,"subject":"[PATCH/POC 0/7] Support multiple worktrees","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-11T14:15:26Z","receivedAt":"2013-12-11T14:15:26Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is what I imagine multi worktree support (aka git-new-workdir)\nlooks like. Basically two variables will control access to the repo:\n$GIT_SUPER_DIR for worktree specific stuff and $GIT_DIR for the rest.\n\nI like the idea of using git_path() to hide path relocation caused by\nGIT_SUPER_DIR, but I may have made some design mistakes here..\nsetup_git_directory() changes are hairy. It's not surprise if I broke\nsomething in there. Not important for a PoC though.\n\nFinal series may be a few patches longer as I only lay the foundation\nin this series.\n\nNguyễn Thái Ngọc Duy (7):\n  Make git_path() beware of file relocation in $GIT_DIR\n  Add new environment variable $GIT_SUPER_DIR\n  setup.c: add split-repo support to .git files\n  setup.c: add split-repo support to is_git_directory()\n  setup.c: reduce cleanup sites in setup_explicit_git_dir()\n  setup.c: add split-repo support to setup_git_directory*\n  init: add --split-repo with the same functionality as git-new-workdir\n\n builtin/init-db.c     |  42 +++++++++++++++\n cache.h               |   5 ++\n environment.c         |  37 ++++++++++++--\n path.c                |  45 ++++++++++++++--\n setup.c               | 139 ++++++++++++++++++++++++++++++++++----------------\n t/t0060-path-utils.sh | 115 +++++++++++++++++++++++++++++++++++++++++\n test-path-utils.c     |   7 +++\n trace.c               |   1 +\n 8 files changed, 339 insertions(+), 52 deletions(-)\n\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"231911","messageId":"1386771333-32574-2-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1386771333-32574-1-git-send-email-pclouds@gmail.com","subject":"[PATCH/POC 1/7] Make git_path() beware of file relocation in $GIT_DIR","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-11T14:15:27Z","receivedAt":"2013-12-11T14:15:27Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"We allow the user to relocate certain paths out of $GIT_DIR via\nenvironment variables, e.g. GIT_OBJECT_DIRECTORY, GIT_INDEX_FILE and\nGIT_GRAFT_FILE. All callers are not supposed to use git_path() or\ngit_pathdup() to get those paths. Instead they must use\nget_object_directory(), get_index_file() and get_graft_file()\nrespectively. This is inconvenient and could be missed in review\n(there's git_path(\"objects/info/alternates\") somewhere in\nsha1_file.c).\n\nThis patch makes git_path() and git_pathdup() understand those\nenvironment variables. So if you set GIT_OBJECT_DIRECTORY to /foo/bar,\ngit_path(\"objects/abc\") should return /tmp/bar/abc. The same is done\nfor the two remaining env variables.\n\nXXX trim consecutive slashes\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h               |  1 +\n environment.c         |  9 +++++++--\n path.c                | 35 ++++++++++++++++++++++++++++++++---\n t/t0060-path-utils.sh | 48 ++++++++++++++++++++++++++++++++++++++++++++++++\n test-path-utils.c     |  7 +++++++\n 5 files changed, 95 insertions(+), 5 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex ce377e1..cdafbd7 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -584,6 +584,7 @@ extern int fsync_object_files;\n extern int core_preload_index;\n extern int core_apply_sparse_checkout;\n extern int precomposed_unicode;\n+extern int git_db_env, git_index_env, git_graft_env;\n \n /*\n  * The character that begins a commented line in user-editable file\ndiff --git a/environment.c b/environment.c\nindex 0a15349..1d74dde 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -81,6 +81,7 @@ static size_t namespace_len;\n \n static const char *git_dir;\n static char *git_object_dir, *git_index_file, *git_graft_file;\n+int git_db_env, git_index_env, git_graft_env;\n \n /*\n  * Repository-local GIT_* environment variables; see cache.h for details.\n@@ -134,15 +135,19 @@ static void setup_git_env(void)\n \tif (!git_object_dir) {\n \t\tgit_object_dir = xmalloc(strlen(git_dir) + 9);\n \t\tsprintf(git_object_dir, \"%s/objects\", git_dir);\n-\t}\n+\t} else\n+\t\tgit_db_env = 1;\n \tgit_index_file = getenv(INDEX_ENVIRONMENT);\n \tif (!git_index_file) {\n \t\tgit_index_file = xmalloc(strlen(git_dir) + 7);\n \t\tsprintf(git_index_file, \"%s/index\", git_dir);\n-\t}\n+\t} else\n+\t\tgit_index_env = 1;\n \tgit_graft_file = getenv(GRAFT_ENVIRONMENT);\n \tif (!git_graft_file)\n \t\tgit_graft_file = git_pathdup(\"info/grafts\");\n+\telse\n+\t\tgit_graft_env = 1;\n \tif (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))\n \t\tread_replace_refs = 0;\n \tnamespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));\ndiff --git a/path.c b/path.c\nindex 24594c4..eda9176 100644\n--- a/path.c\n+++ b/path.c\n@@ -49,10 +49,38 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)\n \treturn cleanup_path(buf);\n }\n \n+static int dir_prefix(const char *buf, const char *dir)\n+{\n+\tint len = strlen(dir);\n+\treturn !strncmp(buf, dir, len) &&\n+\t\t(is_dir_sep(buf[len]) || buf[len] == '\\0');\n+}\n+\n+static void replace_dir(char *buf, int len, const char *newdir)\n+{\n+\tint newlen = strlen(newdir);\n+\tint buflen = strlen(buf);\n+\tmemmove(buf + newlen + 1, buf + len, buflen - len + 1);\n+\tmemcpy(buf, newdir, newlen);\n+\tbuf[newlen] = '/';\n+}\n+\n+static void adjust_git_path(char *buf, int git_dir_len)\n+{\n+\t/* XXX buffer overflow */\n+\tchar *base = buf + git_dir_len;\n+\tif (git_graft_env && !strcmp(base, \"info/grafts\"))\n+\t\tstrcpy(buf, get_graft_file());\n+\telse if (git_index_env && !strcmp(base, \"index\"))\n+\t\tstrcpy(buf, get_index_file());\n+\telse if (git_db_env && dir_prefix(base, \"objects\"))\n+\t\treplace_dir(buf, git_dir_len + 7, get_object_directory());\n+}\n+\n static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)\n {\n \tconst char *git_dir = get_git_dir();\n-\tsize_t len;\n+\tsize_t len, total_len;\n \n \tlen = strlen(git_dir);\n \tif (n < len + 1)\n@@ -60,9 +88,10 @@ static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)\n \tmemcpy(buf, git_dir, len);\n \tif (len && !is_dir_sep(git_dir[len-1]))\n \t\tbuf[len++] = '/';\n-\tlen += vsnprintf(buf + len, n - len, fmt, args);\n-\tif (len >= n)\n+\ttotal_len = len + vsnprintf(buf + len, n - len, fmt, args);\n+\tif (total_len >= n)\n \t\tgoto bad;\n+\tadjust_git_path(buf, len);\n \treturn cleanup_path(buf);\n bad:\n \tstrlcpy(buf, bad_path, n);\ndiff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh\nindex 07c10c8..7ad2730 100755\n--- a/t/t0060-path-utils.sh\n+++ b/t/t0060-path-utils.sh\n@@ -223,4 +223,52 @@ relative_path \"<null>\"\t\t\"<empty>\"\t./\n relative_path \"<null>\"\t\t\"<null>\"\t./\n relative_path \"<null>\"\t\t/foo/a/b\t./\n \n+test_expect_success 'git_path info/grafts without GIT_GRAFT_FILE' '\n+\ttest-path-utils git_path info/grafts >actual1 &&\n+\techo .git/info/grafts >expect1 &&\n+\ttest_cmp expect1 actual1\n+'\n+\n+test_expect_success 'git_path info/grafts with GIT_GRAFT_FILE' '\n+\tGIT_GRAFT_FILE=foo test-path-utils git_path info/grafts >actual2 &&\n+\techo foo >expect2 &&\n+\ttest_cmp expect2 actual2\n+'\n+\n+test_expect_success 'git_path index' '\n+\tGIT_INDEX_FILE=foo test-path-utils git_path index >actual &&\n+\techo foo >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path index/foo' '\n+\tGIT_INDEX_FILE=foo test-path-utils git_path index/foo >actual &&\n+\techo .git/index/foo >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path index2' '\n+\tGIT_INDEX_FILE=foo test-path-utils git_path index2 >actual &&\n+\techo .git/index2 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path objects' '\n+\tGIT_OBJECT_DIRECTORY=foo test-path-utils git_path objects >actual &&\n+\techo foo/ >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path objects/foo' '\n+\tGIT_OBJECT_DIRECTORY=foo test-path-utils git_path objects/foo >actual &&\n+\techo foo//foo >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path objects2' '\n+\tGIT_OBJECT_DIRECTORY=foo test-path-utils git_path objects2 >actual &&\n+\techo .git/objects2 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\ndiff --git a/test-path-utils.c b/test-path-utils.c\nindex 3dd3744..55e4fe0 100644\n--- a/test-path-utils.c\n+++ b/test-path-utils.c\n@@ -135,6 +135,13 @@ int main(int argc, char **argv)\n \t\treturn 0;\n \t}\n \n+\tif (argc == 3 && !strcmp(argv[1], \"git_path\")) {\n+\t\tint nongit_ok = 0;\n+\t\tsetup_git_directory_gently(&nongit_ok);\n+\t\tputs(git_path(\"%s\", argv[2]));\n+\t\treturn 0;\n+\t}\n+\n \tfprintf(stderr, \"%s: unknown function name: %s\\n\", argv[0],\n \t\targv[1] ? argv[1] : \"(there was none)\");\n \treturn 1;\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"231912","messageId":"1386771333-32574-3-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1386771333-32574-1-git-send-email-pclouds@gmail.com","subject":"[PATCH/POC 2/7] Add new environment variable $GIT_SUPER_DIR","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-11T14:15:28Z","receivedAt":"2013-12-11T14:15:28Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is the base for git-new-workdir integration. The git-new-workdir\nscript creates a separate worktree that shares everything except\nworktree-related stuff. The sharing is eanbled by this new env\nvariable.\n\nIn the new worktree, both variables are set at the same time, GIT_DIR\nand GIT_SUPER_DIR. Shared paths are checked at adjust_git_path() and\nrewritten to use $GIT_SUPER_DIR instead of $GIT_DIR. Let's call this\n\"split-repo\" setup to distinguish from $GIT_DIR-only one.\n\nThe \"ln -s\" is avoided because Windows probably does not have the\nsupport, and symlinks don't survive operations that delete the old\nfile, then create a new one.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h               |  2 ++\n environment.c         | 11 +++++++--\n path.c                | 10 ++++++++\n t/t0060-path-utils.sh | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++\n 4 files changed, 88 insertions(+), 2 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex cdafbd7..823582f 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -347,6 +347,7 @@ static inline enum object_type object_type(unsigned int mode)\n \n /* Double-check local_repo_env below if you add to this list. */\n #define GIT_DIR_ENVIRONMENT \"GIT_DIR\"\n+#define GIT_SUPER_DIR_ENVIRONMENT \"GIT_SUPER_DIR\"\n #define GIT_NAMESPACE_ENVIRONMENT \"GIT_NAMESPACE\"\n #define GIT_WORK_TREE_ENVIRONMENT \"GIT_WORK_TREE\"\n #define GIT_PREFIX_ENVIRONMENT \"GIT_PREFIX\"\n@@ -399,6 +400,7 @@ extern int is_inside_git_dir(void);\n extern char *git_work_tree_cfg;\n extern int is_inside_work_tree(void);\n extern const char *get_git_dir(void);\n+extern const char *get_git_super_dir(void);\n extern int is_git_directory(const char *path);\n extern char *get_object_directory(void);\n extern char *get_index_file(void);\ndiff --git a/environment.c b/environment.c\nindex 1d74dde..d5ae7a3 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -79,7 +79,7 @@ static char *work_tree;\n static const char *namespace;\n static size_t namespace_len;\n \n-static const char *git_dir;\n+static const char *git_dir, *git_super_dir;\n static char *git_object_dir, *git_index_file, *git_graft_file;\n int git_db_env, git_index_env, git_graft_env;\n \n@@ -131,10 +131,12 @@ static void setup_git_env(void)\n \t\tgit_dir = DEFAULT_GIT_DIR_ENVIRONMENT;\n \tgitfile = read_gitfile(git_dir);\n \tgit_dir = xstrdup(gitfile ? gitfile : git_dir);\n+\tgit_super_dir = getenv(GIT_SUPER_DIR_ENVIRONMENT);\n \tgit_object_dir = getenv(DB_ENVIRONMENT);\n \tif (!git_object_dir) {\n \t\tgit_object_dir = xmalloc(strlen(git_dir) + 9);\n-\t\tsprintf(git_object_dir, \"%s/objects\", git_dir);\n+\t\tsprintf(git_object_dir, \"%s/objects\",\n+\t\t\tgit_super_dir ? git_super_dir : git_dir);\n \t} else\n \t\tgit_db_env = 1;\n \tgit_index_file = getenv(INDEX_ENVIRONMENT);\n@@ -167,6 +169,11 @@ const char *get_git_dir(void)\n \treturn git_dir;\n }\n \n+const char *get_git_super_dir(void)\n+{\n+\treturn git_super_dir;\n+}\n+\n const char *get_git_namespace(void)\n {\n \tif (!namespace)\ndiff --git a/path.c b/path.c\nindex eda9176..86a7c15 100644\n--- a/path.c\n+++ b/path.c\n@@ -75,6 +75,16 @@ static void adjust_git_path(char *buf, int git_dir_len)\n \t\tstrcpy(buf, get_index_file());\n \telse if (git_db_env && dir_prefix(base, \"objects\"))\n \t\treplace_dir(buf, git_dir_len + 7, get_object_directory());\n+\telse if (get_git_super_dir()) {\n+\t\tif (dir_prefix(base, \"objects\") || dir_prefix(base, \"info\") ||\n+\t\t    dir_prefix(base, \"refs\") ||\n+\t\t    (dir_prefix(base, \"logs\") && strcmp(base, \"logs/HEAD\")) ||\n+\t\t    dir_prefix(base, \"rr-cache\") || dir_prefix(base, \"hooks\") ||\n+\t\t    dir_prefix(base, \"svn\") ||\n+\t\t    !strcmp(base, \"config\") || !strcmp(base, \"packed-refs\") ||\n+\t\t    !strcmp(base, \"shallow\"))\n+\t\t\treplace_dir(buf, git_dir_len, get_git_super_dir());\n+\t}\n }\n \n static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)\ndiff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh\nindex 7ad2730..45f114c 100755\n--- a/t/t0060-path-utils.sh\n+++ b/t/t0060-path-utils.sh\n@@ -271,4 +271,71 @@ test_expect_success 'git_path objects2' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'git_path super index' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path index >actual &&\n+\techo .git/index >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path super HEAD' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path HEAD >actual &&\n+\techo .git/HEAD >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path super objects/*' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path objects/foo >actual &&\n+\techo foo/objects/foo >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path super info/*' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path info/exclude >actual &&\n+\techo foo/info/exclude >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path super logs/refs/heads/master' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path logs/refs/heads/master >actual &&\n+\techo foo/logs/refs/heads/master >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+\n+test_expect_success 'git_path super refs/heads/master' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path refs/heads/master >actual &&\n+\techo foo/refs/heads/master >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path super logs/HEAD' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path logs/HEAD >actual &&\n+\techo .git/logs/HEAD >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path super hooks/me' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path hooks/me >actual &&\n+\techo foo/hooks/me >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path super config' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path config >actual &&\n+\techo foo/config >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path super packed-refs' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path packed-refs >actual &&\n+\techo foo/packed-refs >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path super shallow' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path shallow >actual &&\n+\techo foo/shallow >expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"231913","messageId":"1386771333-32574-4-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1386771333-32574-1-git-send-email-pclouds@gmail.com","subject":"[PATCH/POC 3/7] setup.c: add split-repo support to .git files","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-11T14:15:29Z","receivedAt":"2013-12-11T14:15:29Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"If a .git file contains\n\ngitsuper: <path>\ngitdir: <id>\n\nthen we set GIT_SUPER_DIR to <path> and GIT_DIR to\n$GIT_SUPER_DIR/repos/<id>.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h |  1 +\n setup.c | 40 +++++++++++++++++++++++++++++++++-------\n 2 files changed, 34 insertions(+), 7 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 823582f..f85ee70 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -410,6 +410,7 @@ extern const char *get_git_namespace(void);\n extern const char *strip_namespace(const char *namespaced_ref);\n extern const char *get_git_work_tree(void);\n extern const char *read_gitfile(const char *path);\n+extern const char *read_gitfile_super(const char *path, char **super);\n extern const char *resolve_gitdir(const char *suspect);\n extern void set_git_work_tree(const char *tree);\n \ndiff --git a/setup.c b/setup.c\nindex 5432a31..84362a6 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -281,16 +281,23 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)\n /*\n  * Try to read the location of the git directory from the .git file,\n  * return path to git directory if found.\n+ *\n+ * If \"gitsuper: \" line is found and super is not NULL, *super points\n+ * to the absolute path of the given path. The next line contains the\n+ * repo id.\n  */\n-const char *read_gitfile(const char *path)\n+const char *read_gitfile_super(const char *path, char **super)\n {\n-\tchar *buf;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tchar *buf, *to_free;\n \tchar *dir;\n \tconst char *slash;\n \tstruct stat st;\n \tint fd;\n \tssize_t len;\n \n+\tif (super)\n+\t\t*super = NULL;\n \tif (stat(path, &st))\n \t\treturn NULL;\n \tif (!S_ISREG(st.st_mode))\n@@ -298,12 +305,19 @@ const char *read_gitfile(const char *path)\n \tfd = open(path, O_RDONLY);\n \tif (fd < 0)\n \t\tdie_errno(\"Error opening '%s'\", path);\n-\tbuf = xmalloc(st.st_size + 1);\n+\tto_free = buf = xmalloc(st.st_size + 1);\n \tlen = read_in_full(fd, buf, st.st_size);\n \tclose(fd);\n \tif (len != st.st_size)\n \t\tdie(\"Error reading %s\", path);\n \tbuf[len] = '\\0';\n+\tif (super &&!prefixcmp(buf, \"gitsuper: \")) {\n+\t\tchar *p = strchr(buf, '\\n');\n+\t\t*super = buf + strlen(\"gitsuper: \");\n+\t\t*p = '\\0';\n+\t\tlen -= (p + 1) - buf;\n+\t\tbuf = p + 1;\n+\t}\n \tif (prefixcmp(buf, \"gitdir: \"))\n \t\tdie(\"Invalid gitfile format: %s\", path);\n \twhile (buf[len - 1] == '\\n' || buf[len - 1] == '\\r')\n@@ -312,6 +326,11 @@ const char *read_gitfile(const char *path)\n \t\tdie(\"No path in gitfile: %s\", path);\n \tbuf[len] = '\\0';\n \tdir = buf + 8;\n+\tif (super && *super) {\n+\t\tstrbuf_addf(&sb, \"%s/repos/%s\", *super, dir);\n+\t\tdir = sb.buf;\n+\t\t*super = xstrdup(real_path(*super));\n+\t}\n \n \tif (!is_absolute_path(dir) && (slash = strrchr(path, '/'))) {\n \t\tsize_t pathlen = slash+1 - path;\n@@ -320,18 +339,25 @@ const char *read_gitfile(const char *path)\n \t\tstrncpy(dir, path, pathlen);\n \t\tstrncpy(dir + pathlen, buf + 8, len - 8);\n \t\tdir[dirlen] = '\\0';\n-\t\tfree(buf);\n-\t\tbuf = dir;\n+\t\tfree(to_free);\n+\t\tto_free = buf = dir;\n \t}\n \n-\tif (!is_git_directory(dir))\n+\tif (!is_git_directory_super(dir, super ? *super : dir))\n \t\tdie(\"Not a git repository: %s\", dir);\n \tpath = real_path(dir);\n \n-\tfree(buf);\n+\tfree(to_free);\n+\tstrbuf_release(&sb);\n \treturn path;\n }\n \n+const char *read_gitfile(const char *path)\n+{\n+\treturn read_gitfile_super(path, NULL);\n+}\n+\n+\n static const char *setup_explicit_git_dir(const char *gitdirenv,\n \t\t\t\t\t  char *cwd, int len,\n \t\t\t\t\t  int *nongit_ok)\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"231914","messageId":"1386771333-32574-5-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1386771333-32574-1-git-send-email-pclouds@gmail.com","subject":"[PATCH/POC 4/7] setup.c: add split-repo support to is_git_directory()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-11T14:15:30Z","receivedAt":"2013-12-11T14:15:30Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n setup.c | 19 ++++++++++++++-----\n 1 file changed, 14 insertions(+), 5 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 84362a6..43f56fb 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -182,28 +182,31 @@ void verify_non_filename(const char *prefix, const char *arg)\n  *    a proper \"ref:\", or a regular file HEAD that has a properly\n  *    formatted sha1 object name.\n  */\n-int is_git_directory(const char *suspect)\n+static int is_git_directory_super(const char *suspect, const char *super)\n {\n \tchar path[PATH_MAX];\n \tsize_t len = strlen(suspect);\n+\tsize_t super_len = strlen(super);\n \n-\tif (PATH_MAX <= len + strlen(\"/objects\"))\n+\tif (PATH_MAX <= super_len + strlen(\"/objects\"))\n \t\tdie(\"Too long path: %.*s\", 60, suspect);\n-\tstrcpy(path, suspect);\n+\tstrcpy(path, super);\n \tif (getenv(DB_ENVIRONMENT)) {\n \t\tif (access(getenv(DB_ENVIRONMENT), X_OK))\n \t\t\treturn 0;\n \t}\n \telse {\n-\t\tstrcpy(path + len, \"/objects\");\n+\t\tstrcpy(path + super_len, \"/objects\");\n \t\tif (access(path, X_OK))\n \t\t\treturn 0;\n \t}\n \n-\tstrcpy(path + len, \"/refs\");\n+\tstrcpy(path + super_len, \"/refs\");\n \tif (access(path, X_OK))\n \t\treturn 0;\n \n+\tif (super != suspect)\n+\t\tstrcpy(path, suspect);\n \tstrcpy(path + len, \"/HEAD\");\n \tif (validate_headref(path))\n \t\treturn 0;\n@@ -211,6 +214,12 @@ int is_git_directory(const char *suspect)\n \treturn 1;\n }\n \n+int is_git_directory(const char *suspect)\n+{\n+\treturn is_git_directory_super(suspect, suspect);\n+}\n+\n+\n int is_inside_git_dir(void)\n {\n \tif (inside_git_dir < 0)\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"231915","messageId":"1386771333-32574-6-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1386771333-32574-1-git-send-email-pclouds@gmail.com","subject":"[PATCH/POC 5/7] setup.c: reduce cleanup sites in setup_explicit_git_dir()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-11T14:15:31Z","receivedAt":"2013-12-11T14:15:31Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n setup.c | 20 ++++++++------------\n 1 file changed, 8 insertions(+), 12 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 43f56fb..dfe9d08 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -388,16 +388,13 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,\n \tif (!is_git_directory(gitdirenv)) {\n \t\tif (nongit_ok) {\n \t\t\t*nongit_ok = 1;\n-\t\t\tfree(gitfile);\n-\t\t\treturn NULL;\n+\t\t\tgoto done_null;\n \t\t}\n \t\tdie(\"Not a git repository: '%s'\", gitdirenv);\n \t}\n \n-\tif (check_repository_format_gently(gitdirenv, nongit_ok)) {\n-\t\tfree(gitfile);\n-\t\treturn NULL;\n-\t}\n+\tif (check_repository_format_gently(gitdirenv, nongit_ok))\n+\t\tgoto done_null;\n \n \t/* #3, #7, #11, #15, #19, #23, #27, #31 (see t1510) */\n \tif (work_tree_env)\n@@ -408,8 +405,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,\n \n \t\t/* #18, #26 */\n \t\tset_git_dir(gitdirenv);\n-\t\tfree(gitfile);\n-\t\treturn NULL;\n+\t\tgoto done_null;\n \t}\n \telse if (git_work_tree_cfg) { /* #6, #14 */\n \t\tif (is_absolute_path(git_work_tree_cfg))\n@@ -430,8 +426,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,\n \telse if (!git_env_bool(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, 1)) {\n \t\t/* #16d */\n \t\tset_git_dir(gitdirenv);\n-\t\tfree(gitfile);\n-\t\treturn NULL;\n+\t\tgoto done_null;\n \t}\n \telse /* #2, #10 */\n \t\tset_git_work_tree(\".\");\n@@ -442,8 +437,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,\n \t/* both get_git_work_tree() and cwd are already normalized */\n \tif (!strcmp(cwd, worktree)) { /* cwd == worktree */\n \t\tset_git_dir(gitdirenv);\n-\t\tfree(gitfile);\n-\t\treturn NULL;\n+\t\tgoto done_null;\n \t}\n \n \toffset = dir_inside_of(cwd, worktree);\n@@ -459,6 +453,8 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,\n \n \t/* cwd outside worktree */\n \tset_git_dir(gitdirenv);\n+\n+done_null:\n \tfree(gitfile);\n \treturn NULL;\n }\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"231916","messageId":"1386771333-32574-7-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1386771333-32574-1-git-send-email-pclouds@gmail.com","subject":"[PATCH/POC 6/7] setup.c: add split-repo support to setup_git_directory*","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-11T14:15:32Z","receivedAt":"2013-12-11T14:15:32Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"XXX bare repos probably not worth supporting..\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h       |  1 +\n environment.c | 19 ++++++++++++++++--\n setup.c       | 62 +++++++++++++++++++++++++++++++++++++++--------------------\n trace.c       |  1 +\n 4 files changed, 60 insertions(+), 23 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex f85ee70..4c09223 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -406,6 +406,7 @@ extern char *get_object_directory(void);\n extern char *get_index_file(void);\n extern char *get_graft_file(void);\n extern int set_git_dir(const char *path);\n+extern int set_git_dir_super(const char *path, const char *super);\n extern const char *get_git_namespace(void);\n extern const char *strip_namespace(const char *namespaced_ref);\n extern const char *get_git_work_tree(void);\ndiff --git a/environment.c b/environment.c\nindex d5ae7a3..8152c7e 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -125,13 +125,17 @@ static char *expand_namespace(const char *raw_namespace)\n static void setup_git_env(void)\n {\n \tconst char *gitfile;\n+\tchar *super;\n \n \tgit_dir = getenv(GIT_DIR_ENVIRONMENT);\n \tif (!git_dir)\n \t\tgit_dir = DEFAULT_GIT_DIR_ENVIRONMENT;\n-\tgitfile = read_gitfile(git_dir);\n+\tgitfile = read_gitfile_super(git_dir, &super);\n \tgit_dir = xstrdup(gitfile ? gitfile : git_dir);\n-\tgit_super_dir = getenv(GIT_SUPER_DIR_ENVIRONMENT);\n+\tif (super)\n+\t\tgit_super_dir = xstrdup(super);\n+\telse\n+\t\tgit_super_dir = getenv(GIT_SUPER_DIR_ENVIRONMENT);\n \tgit_object_dir = getenv(DB_ENVIRONMENT);\n \tif (!git_object_dir) {\n \t\tgit_object_dir = xmalloc(strlen(git_dir) + 9);\n@@ -280,6 +284,17 @@ int set_git_dir(const char *path)\n \treturn 0;\n }\n \n+int set_git_dir_super(const char *path, const char *super)\n+{\n+\tif (super != path &&\n+\t    setenv(GIT_SUPER_DIR_ENVIRONMENT, super, 1))\n+\t\treturn error(\"Could not set GIT_SUPER_DIR to '%s'\", super);\n+\tif (setenv(GIT_DIR_ENVIRONMENT, path, 1))\n+\t\treturn error(\"Could not set GIT_DIR to '%s'\", path);\n+\tsetup_git_env();\n+\treturn 0;\n+}\n+\n const char *get_log_output_encoding(void)\n {\n \treturn git_log_output_encoding ? git_log_output_encoding\ndiff --git a/setup.c b/setup.c\nindex dfe9d08..f22381e 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -255,7 +255,8 @@ void setup_work_tree(void)\n \tif (getenv(GIT_WORK_TREE_ENVIRONMENT))\n \t\tsetenv(GIT_WORK_TREE_ENVIRONMENT, \".\", 1);\n \n-\tset_git_dir(remove_leading_path(git_dir, work_tree));\n+\tset_git_dir_super(remove_leading_path(git_dir, work_tree),\n+\t\t\t  get_git_super_dir());\n \tinitialized = 1;\n }\n \n@@ -368,24 +369,28 @@ const char *read_gitfile(const char *path)\n \n \n static const char *setup_explicit_git_dir(const char *gitdirenv,\n+\t\t\t\t\t  const char *super_,\n \t\t\t\t\t  char *cwd, int len,\n \t\t\t\t\t  int *nongit_ok)\n {\n \tconst char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);\n \tconst char *worktree;\n-\tchar *gitfile;\n+\tchar *gitfile, *super = (char *)super_;\n \tint offset;\n \n \tif (PATH_MAX - 40 < strlen(gitdirenv))\n \t\tdie(\"'$%s' too big\", GIT_DIR_ENVIRONMENT);\n \n-\tgitfile = (char*)read_gitfile(gitdirenv);\n+\tgitfile = (char*)read_gitfile_super(gitdirenv, !super ? &super : NULL);\n \tif (gitfile) {\n \t\tgitfile = xstrdup(gitfile);\n \t\tgitdirenv = gitfile;\n-\t}\n+\t\tif (!super)\n+\t\t\tsuper = gitfile;\n+\t} else if (!super)\n+\t\tsuper = (char *)gitdirenv;\n \n-\tif (!is_git_directory(gitdirenv)) {\n+\tif (!is_git_directory_super(gitdirenv, super)) {\n \t\tif (nongit_ok) {\n \t\t\t*nongit_ok = 1;\n \t\t\tgoto done_null;\n@@ -393,7 +398,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,\n \t\tdie(\"Not a git repository: '%s'\", gitdirenv);\n \t}\n \n-\tif (check_repository_format_gently(gitdirenv, nongit_ok))\n+\tif (check_repository_format_gently(super, nongit_ok))\n \t\tgoto done_null;\n \n \t/* #3, #7, #11, #15, #19, #23, #27, #31 (see t1510) */\n@@ -404,7 +409,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,\n \t\t\tdie(\"core.bare and core.worktree do not make sense\");\n \n \t\t/* #18, #26 */\n-\t\tset_git_dir(gitdirenv);\n+\t\tset_git_dir_super(gitdirenv, super);\n \t\tgoto done_null;\n \t}\n \telse if (git_work_tree_cfg) { /* #6, #14 */\n@@ -425,7 +430,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,\n \t}\n \telse if (!git_env_bool(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, 1)) {\n \t\t/* #16d */\n-\t\tset_git_dir(gitdirenv);\n+\t\tset_git_dir_super(gitdirenv, super);\n \t\tgoto done_null;\n \t}\n \telse /* #2, #10 */\n@@ -436,34 +441,39 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,\n \n \t/* both get_git_work_tree() and cwd are already normalized */\n \tif (!strcmp(cwd, worktree)) { /* cwd == worktree */\n-\t\tset_git_dir(gitdirenv);\n+\t\tset_git_dir_super(gitdirenv, super);\n \t\tgoto done_null;\n \t}\n \n \toffset = dir_inside_of(cwd, worktree);\n \tif (offset >= 0) {\t/* cwd inside worktree? */\n-\t\tset_git_dir(real_path(gitdirenv));\n+\t\tset_git_dir_super(real_path(gitdirenv), super);\n \t\tif (chdir(worktree))\n \t\t\tdie_errno(\"Could not chdir to '%s'\", worktree);\n \t\tcwd[len++] = '/';\n \t\tcwd[len] = '\\0';\n \t\tfree(gitfile);\n+\t\tif (super != gitdirenv && super != super_)\n+\t\t\tfree(super);\n \t\treturn cwd + offset;\n \t}\n \n \t/* cwd outside worktree */\n-\tset_git_dir(gitdirenv);\n+\tset_git_dir_super(gitdirenv, super);\n \n done_null:\n \tfree(gitfile);\n+\tif (super != gitdirenv && super != super_)\n+\t\tfree(super);\n \treturn NULL;\n }\n \n static const char *setup_discovered_git_dir(const char *gitdir,\n+\t\t\t\t\t    const char *super,\n \t\t\t\t\t    char *cwd, int offset, int len,\n \t\t\t\t\t    int *nongit_ok)\n {\n-\tif (check_repository_format_gently(gitdir, nongit_ok))\n+\tif (check_repository_format_gently(super, nongit_ok))\n \t\treturn NULL;\n \n \t/* --work-tree is set without --git-dir; use discovered one */\n@@ -472,7 +482,7 @@ static const char *setup_discovered_git_dir(const char *gitdir,\n \t\t\tgitdir = xstrdup(real_path(gitdir));\n \t\tif (chdir(cwd))\n \t\t\tdie_errno(\"Could not come back to cwd\");\n-\t\treturn setup_explicit_git_dir(gitdir, cwd, len, nongit_ok);\n+\t\treturn setup_explicit_git_dir(gitdir, super, cwd, len, nongit_ok);\n \t}\n \n \t/* #16.2, #17.2, #20.2, #21.2, #24, #25, #28, #29 (see t1510) */\n@@ -486,7 +496,7 @@ static const char *setup_discovered_git_dir(const char *gitdir,\n \t/* #0, #1, #5, #8, #9, #12, #13 */\n \tset_git_work_tree(\".\");\n \tif (strcmp(gitdir, DEFAULT_GIT_DIR_ENVIRONMENT))\n-\t\tset_git_dir(gitdir);\n+\t\tset_git_dir_super(gitdir, super);\n \tinside_git_dir = 0;\n \tinside_work_tree = 1;\n \tif (offset == len)\n@@ -516,7 +526,7 @@ static const char *setup_bare_git_dir(char *cwd, int offset, int len, int *nongi\n \t\tgitdir = offset == len ? \".\" : xmemdupz(cwd, offset);\n \t\tif (chdir(cwd))\n \t\t\tdie_errno(\"Could not come back to cwd\");\n-\t\treturn setup_explicit_git_dir(gitdir, cwd, len, nongit_ok);\n+\t\treturn setup_explicit_git_dir(gitdir, NULL, cwd, len, nongit_ok);\n \t}\n \n \tinside_git_dir = 1;\n@@ -596,7 +606,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n \tstatic char cwd[PATH_MAX + 1];\n \tconst char *gitdirenv, *ret;\n-\tchar *gitfile;\n+\tchar *gitfile, *super;\n \tint len, offset, offset_parent, ceil_offset = -1;\n \tdev_t current_device = 0;\n \tint one_filesystem = 1;\n@@ -620,7 +630,9 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \t */\n \tgitdirenv = getenv(GIT_DIR_ENVIRONMENT);\n \tif (gitdirenv)\n-\t\treturn setup_explicit_git_dir(gitdirenv, cwd, len, nongit_ok);\n+\t\treturn setup_explicit_git_dir(gitdirenv,\n+\t\t\t\t\t      getenv(GIT_SUPER_DIR_ENVIRONMENT),\n+\t\t\t\t\t      cwd, len, nongit_ok);\n \n \tif (env_ceiling_dirs) {\n \t\tint empty_entry_found = 0;\n@@ -650,21 +662,29 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \tif (one_filesystem)\n \t\tcurrent_device = get_device_or_die(\".\", NULL, 0);\n \tfor (;;) {\n-\t\tgitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);\n-\t\tif (gitfile)\n+\t\tgitfile = (char*)read_gitfile_super(DEFAULT_GIT_DIR_ENVIRONMENT,\n+\t\t\t\t\t\t    &super);\n+\t\tif (gitfile) {\n \t\t\tgitdirenv = gitfile = xstrdup(gitfile);\n-\t\telse {\n+\t\t\tif (!super)\n+\t\t\t\tsuper = gitfile;\n+\t\t} else {\n \t\t\tif (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))\n \t\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n+\t\t\tsuper = (char *)gitdirenv;\n \t\t}\n \n \t\tif (gitdirenv) {\n-\t\t\tret = setup_discovered_git_dir(gitdirenv,\n+\t\t\tret = setup_discovered_git_dir(gitdirenv, super,\n \t\t\t\t\t\t       cwd, offset, len,\n \t\t\t\t\t\t       nongit_ok);\n+\t\t\tif (super != gitdirenv)\n+\t\t\t\tfree(super);\n \t\t\tfree(gitfile);\n \t\t\treturn ret;\n \t\t}\n+\t\tif (super != gitdirenv)\n+\t\t\tfree(super);\n \t\tfree(gitfile);\n \n \t\tif (is_git_directory(\".\"))\ndiff --git a/trace.c b/trace.c\nindex 3d744d1..53d800b 100644\n--- a/trace.c\n+++ b/trace.c\n@@ -173,6 +173,7 @@ void trace_repo_setup(const char *prefix)\n \t\tprefix = \"(null)\";\n \n \ttrace_printf_key(key, \"setup: git_dir: %s\\n\", quote_crnl(get_git_dir()));\n+\ttrace_printf_key(key, \"setup: git_super_dir: %s\\n\", quote_crnl(get_git_super_dir()));\n \ttrace_printf_key(key, \"setup: worktree: %s\\n\", quote_crnl(git_work_tree));\n \ttrace_printf_key(key, \"setup: cwd: %s\\n\", quote_crnl(cwd));\n \ttrace_printf_key(key, \"setup: prefix: %s\\n\", quote_crnl(prefix));\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"231917","messageId":"1386771333-32574-8-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1386771333-32574-1-git-send-email-pclouds@gmail.com","subject":"[PATCH/POC 7/7] init: add --split-repo with the same functionality as git-new-workdir","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-11T14:15:33Z","receivedAt":"2013-12-11T14:15:33Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"\"git init --split-repo abc\" will create abc/.git file with the content\n\ngitsuper: `git rev-parse --git-dir`\ngitdir: abc\n\nand a new directory in current .git/repos/abc with a valid HEAD.\n\nThis is enough to start checking out and do stuff. We should probably\ntake a branch name and check that branch out though. If that's the\ncase, this feature may better be parked in \"git checkout\" instead of\nhere..\n\nSo far it's not any better than git-new-workdir, except that info from\nall worktrees created this way is centralized in the super repo's\n.git/repos, which makes it possible to ensure what worktree does not\nstep on one another. In particular:\n\n - If a worktree updates a ref, we could check if any other worktrees\n   are also checking out that ref. Detach those worktrees.\n\n - prune/gc should be taught about the extra HEADs in .git/repos. fsck\n   on the super repo should be taught about extra indexes in\n   .git/repos\n\n - ...\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/init-db.c | 42 ++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 42 insertions(+)\n\ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex 78aa387..9c7139a 100644\n--- a/builtin/init-db.c\n+++ b/builtin/init-db.c\n@@ -426,6 +426,38 @@ int init_db(const char *template_dir, unsigned int flags)\n \treturn 0;\n }\n \n+static int create_new_worktree(const char *path)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tconst char *name;\n+\tunsigned char sha1[20];\n+\tFILE *fp;\n+\n+\tfor (name = path + strlen(path) - 1; name > path; name--)\n+\t\tif (is_dir_sep(*name)) {\n+\t\t\tname++;\n+\t\t\tbreak;\n+\t\t}\n+\n+\tstrbuf_addf(&sb, \"%s/.git\", path);\n+\tsafe_create_leading_directories_const(sb.buf);\n+\tfp = fopen(sb.buf, \"w\");\n+\tfprintf(fp, \"gitsuper: %s\\ngitdir: %s\\n\",\n+\t\treal_path(get_git_dir()), name);\n+\tfclose(fp);\n+\tsafe_create_leading_directories_const(git_path(\"repos/%s/HEAD\",\n+\t\t\t\t\t\t       name));\n+\tfp = fopen(git_path(\"repos/%s/HEAD\", name), \"w\");\n+\tget_sha1(\"HEAD\", sha1);\n+\tfprintf(fp, \"%s\\n\", sha1_to_hex(sha1));\n+\tfclose(fp);\n+#if 0\n+\tchdir(path);\n+\tsystem(\"git reset --hard\");\n+#endif\n+\treturn 0;\n+}\n+\n static int guess_repository_type(const char *git_dir)\n {\n \tchar cwd[PATH_MAX];\n@@ -481,6 +513,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)\n \tconst char *work_tree;\n \tconst char *template_dir = NULL;\n \tunsigned int flags = 0;\n+\tint split_repo = 0;\n \tconst struct option init_db_options[] = {\n \t\tOPT_STRING(0, \"template\", &template_dir, N_(\"template-directory\"),\n \t\t\t\tN_(\"directory from which templates will be used\")),\n@@ -491,6 +524,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"specify that the git repository is to be shared amongst several users\"),\n \t\t\tPARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0},\n \t\tOPT_BIT('q', \"quiet\", &flags, N_(\"be quiet\"), INIT_DB_QUIET),\n+\t\tOPT_BOOL(0, \"split-repo\", &split_repo, N_(\"git-new-workdir\")),\n \t\tOPT_STRING(0, \"separate-git-dir\", &real_git_dir, N_(\"gitdir\"),\n \t\t\t   N_(\"separate git dir from working tree\")),\n \t\tOPT_END()\n@@ -498,6 +532,14 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)\n \n \targc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);\n \n+\tif (split_repo) {\n+\t\tif (real_git_dir)\n+\t\t\tdie(_(\"--split-repo and --separate-git-dir are incompatible\"));\n+\t\tif (!argv[0])\n+\t\t\tdie(_(\"--split-repo requires a path\"));\n+\t\treturn create_new_worktree(argv[0]);\n+\t}\n+\n \tif (real_git_dir && !is_absolute_path(real_git_dir))\n \t\treal_git_dir = xstrdup(real_path(real_git_dir));\n \n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"231975","messageId":"xmqqsitwr9wc.fsf@gitster.dls.corp.google.com","threadId":"35520","inReplyTo":"1386771333-32574-2-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH/POC 1/7] Make git_path() beware of file relocation in $GIT_DIR","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-12-13T16:30:27Z","receivedAt":"2013-12-13T16:30:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> +static void adjust_git_path(char *buf, int git_dir_len)\n> +{\n> +\t/* XXX buffer overflow */\n> +\tchar *base = buf + git_dir_len;\n> +\tif (git_graft_env && !strcmp(base, \"info/grafts\"))\n> +\t\tstrcpy(buf, get_graft_file());\n> +\telse if (git_index_env && !strcmp(base, \"index\"))\n> +\t\tstrcpy(buf, get_index_file());\n> +\telse if (git_db_env && dir_prefix(base, \"objects\"))\n> +\t\treplace_dir(buf, git_dir_len + 7, get_object_directory());\n> +}\n> +\n>  static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)\n>  {\n>  \tconst char *git_dir = get_git_dir();\n> -\tsize_t len;\n> +\tsize_t len, total_len;\n>  \n>  \tlen = strlen(git_dir);\n>  \tif (n < len + 1)\n> @@ -60,9 +88,10 @@ static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)\n>  \tmemcpy(buf, git_dir, len);\n>  \tif (len && !is_dir_sep(git_dir[len-1]))\n>  \t\tbuf[len++] = '/';\n> -\tlen += vsnprintf(buf + len, n - len, fmt, args);\n> -\tif (len >= n)\n> +\ttotal_len = len + vsnprintf(buf + len, n - len, fmt, args);\n> +\tif (total_len >= n)\n>  \t\tgoto bad;\n> +\tadjust_git_path(buf, len);\n\nThis is a minor tangent, but this part of the patch made me raise my\neyebrow, wondering what Git-specific path mangler is doing in a\nfunction vsnpath that is named as if it is a lot more generic, until\nI read the change in context.\n\nThe vnspath() is already Git-specific---it is a helper that is used\nto create a path inside the $GIT_DIR directory.\n\nWe probably should do two things to clear things up:\n\n - Right now, path.c has definitions of functions in this order:\n\n    char *mksnpath(char *buf, size_t n, const char *fmt, ...)\n    static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)\n    char *git_snpath(char *buf, size_t n, const char *fmt, ...)\n    char *git_pathdup(const char *fmt, ...)\n    char *mkpathdup(const char *fmt, ...)\n    char *mkpath(const char *fmt, ...)\n    char *git_path(const char *fmt, ...)\n\n   The two functions mkpathdup() and mkpath() are not Git specific\n   at all.  They should be moved up to be grouped together with\n   mksnpath() that is also not Git-specific.\n\n - Rename the static vsnpath() to further clarify that it is Git\n   specific.\n"},{"id":"231977","messageId":"xmqqmwk4r55u.fsf@gitster.dls.corp.google.com","threadId":"35520","inReplyTo":"1386771333-32574-3-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH/POC 2/7] Add new environment variable $GIT_SUPER_DIR","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-12-13T18:12:45Z","receivedAt":"2013-12-13T18:12:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> This is the base for git-new-workdir integration. The git-new-workdir\n> script creates a separate worktree that shares everything except\n> worktree-related stuff. The sharing is eanbled by this new env\n> variable.\n>\n> In the new worktree, both variables are set at the same time, GIT_DIR\n> and GIT_SUPER_DIR. Shared paths are checked at adjust_git_path() and\n> rewritten to use $GIT_SUPER_DIR instead of $GIT_DIR. Let's call this\n> \"split-repo\" setup to distinguish from $GIT_DIR-only one.\n>\n> The \"ln -s\" is avoided because Windows probably does not have the\n> support, and symlinks don't survive operations that delete the old\n> file, then create a new one.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  cache.h               |  2 ++\n>  environment.c         | 11 +++++++--\n>  path.c                | 10 ++++++++\n>  t/t0060-path-utils.sh | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++\n>  4 files changed, 88 insertions(+), 2 deletions(-)\n>\n> diff --git a/cache.h b/cache.h\n> index cdafbd7..823582f 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -347,6 +347,7 @@ static inline enum object_type object_type(unsigned int mode)\n>  \n>  /* Double-check local_repo_env below if you add to this list. */\n>  #define GIT_DIR_ENVIRONMENT \"GIT_DIR\"\n> +#define GIT_SUPER_DIR_ENVIRONMENT \"GIT_SUPER_DIR\"\n>  #define GIT_NAMESPACE_ENVIRONMENT \"GIT_NAMESPACE\"\n>  #define GIT_WORK_TREE_ENVIRONMENT \"GIT_WORK_TREE\"\n>  #define GIT_PREFIX_ENVIRONMENT \"GIT_PREFIX\"\n> @@ -399,6 +400,7 @@ extern int is_inside_git_dir(void);\n>  extern char *git_work_tree_cfg;\n>  extern int is_inside_work_tree(void);\n>  extern const char *get_git_dir(void);\n> +extern const char *get_git_super_dir(void);\n>  extern int is_git_directory(const char *path);\n>  extern char *get_object_directory(void);\n>  extern char *get_index_file(void);\n> diff --git a/environment.c b/environment.c\n> index 1d74dde..d5ae7a3 100644\n> --- a/environment.c\n> +++ b/environment.c\n> @@ -79,7 +79,7 @@ static char *work_tree;\n>  static const char *namespace;\n>  static size_t namespace_len;\n>  \n> -static const char *git_dir;\n> +static const char *git_dir, *git_super_dir;\n>  static char *git_object_dir, *git_index_file, *git_graft_file;\n>  int git_db_env, git_index_env, git_graft_env;\n>  \n> @@ -131,10 +131,12 @@ static void setup_git_env(void)\n>  \t\tgit_dir = DEFAULT_GIT_DIR_ENVIRONMENT;\n>  \tgitfile = read_gitfile(git_dir);\n>  \tgit_dir = xstrdup(gitfile ? gitfile : git_dir);\n> +\tgit_super_dir = getenv(GIT_SUPER_DIR_ENVIRONMENT);\n>  \tgit_object_dir = getenv(DB_ENVIRONMENT);\n>  \tif (!git_object_dir) {\n>  \t\tgit_object_dir = xmalloc(strlen(git_dir) + 9);\n> -\t\tsprintf(git_object_dir, \"%s/objects\", git_dir);\n> +\t\tsprintf(git_object_dir, \"%s/objects\",\n> +\t\t\tgit_super_dir ? git_super_dir : git_dir);\n>  \t} else\n>  \t\tgit_db_env = 1;\n>  \tgit_index_file = getenv(INDEX_ENVIRONMENT);\n> @@ -167,6 +169,11 @@ const char *get_git_dir(void)\n>  \treturn git_dir;\n>  }\n>  \n> +const char *get_git_super_dir(void)\n> +{\n> +\treturn git_super_dir;\n> +}\n> +\n>  const char *get_git_namespace(void)\n>  {\n>  \tif (!namespace)\n> diff --git a/path.c b/path.c\n> index eda9176..86a7c15 100644\n> --- a/path.c\n> +++ b/path.c\n> @@ -75,6 +75,16 @@ static void adjust_git_path(char *buf, int git_dir_len)\n>  \t\tstrcpy(buf, get_index_file());\n>  \telse if (git_db_env && dir_prefix(base, \"objects\"))\n>  \t\treplace_dir(buf, git_dir_len + 7, get_object_directory());\n> +\telse if (get_git_super_dir()) {\n> +\t\tif (dir_prefix(base, \"objects\") || dir_prefix(base, \"info\") ||\n> +\t\t    dir_prefix(base, \"refs\") ||\n> +\t\t    (dir_prefix(base, \"logs\") && strcmp(base, \"logs/HEAD\")) ||\n> +\t\t    dir_prefix(base, \"rr-cache\") || dir_prefix(base, \"hooks\") ||\n> +\t\t    dir_prefix(base, \"svn\") ||\n> +\t\t    !strcmp(base, \"config\") || !strcmp(base, \"packed-refs\") ||\n> +\t\t    !strcmp(base, \"shallow\"))\n> +\t\t\treplace_dir(buf, git_dir_len, get_git_super_dir());\n> +\t}\n\nThis is essentially the list of what are shared across workdirs,\nright?  I wonder if it is a bad idea to make everything under .git\nof the borrowed repository shared by default, with selected\nexceptions.  Granted, not sharing by default is definitely safer\nthan blindly sharing by default, so that alone may be a good\nargument to use a set of known-to-be-safe-to-share paths, like this\ncode does.\n\nDon't we want .git/branches and .git/remotes shared?  After all,\ntheir moral equivalents from .git/config are shared with the code.\n\nThe name \"super\" might need to be rethought, but getting the\nmechanism and the semantics right is the more important first step,\nand I think this is going in a good direction.\n"},{"id":"231978","messageId":"xmqqiousr4bg.fsf@gitster.dls.corp.google.com","threadId":"35520","inReplyTo":"1386771333-32574-4-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH/POC 3/7] setup.c: add split-repo support to .git files","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-12-13T18:30:59Z","receivedAt":"2013-12-13T18:30:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> If a .git file contains\n>\n> gitsuper: <path>\n> gitdir: <id>\n>\n> then we set GIT_SUPER_DIR to <path> and GIT_DIR to\n> $GIT_SUPER_DIR/repos/<id>.\n\nI initially thought: \"what is with that complexity? isn't it just\nthe matter of replacing 'gitdir: <path>' with 'gitsuper: <path>'\nstored in the file .git???\"\n\nUntil I realized that there is nowhere to keep per-workdir data if\nwe only had .git as a pointer, and that is why you have that <id>\nthing.  It would have helped me avoid that confusion if the above\ndescription was followed by:\n\n    The latter, $GIT_SUPER_DIR/repos/<id>, is a directory,\n    underneath which per-work-dir items like index, HEAD, logs/HEAD\n    (what else?) reside.\n\nor something like that.  And $GIT_SUPER_DIR/repos/*/HEAD, especially\nwhen they are detached, plus $GIT_SUPER_DIR/repos/*/index, will work\nas the starting point of object reachability scanning when running\nrepack, fsck, etc.\n\nA few more random thoughts...\n\n - Reusing \"gitdir:\" for this purpose is not advisable; use a\n   different name.  This <id> is used to identify a workdir, so\n   perhaps \"gitworkdir: <id>\" might be a better name;\n\n - Do we want to record where the working tree directory is in\n   $GIT_SUPER_DIR/repos/<id> somewhere?  Would it help to have such\n   a record?\n\n - How would this interact with core.worktree in .git/config of that\n   \"super\" repository?\n"},{"id":"231981","messageId":"20131213204348.GX2311@google.com","threadId":"35520","inReplyTo":"xmqqiousr4bg.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH/POC 3/7] setup.c: add split-repo support to .git files","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2013-12-13T20:43:48Z","receivedAt":"2013-12-13T20:43:48Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Junio C Hamano wrote:\n\n>  - Do we want to record where the working tree directory is in\n>    $GIT_SUPER_DIR/repos/<id> somewhere?  Would it help to have such\n>    a record?\n\nThat could be nice for the purpose of garbage collecting them.  I fear\nthat for users it is too tempting to remove a worktree with \"rm -rf\"\nwithout considering the relationship from the parent repo that might\nbe making walking through all reflogs slower or holding on to objects\nno one cares about any more.\n\nI imagine it would work like this:\n\n 1. At worktree creation time, full path to the working tree directory\n    is stored in $GIT_SUPER_DIR/repos/<id>.\n\n 2. \"git gc\" notices that the worktree is missing and writes a file\n    under $GIT_SUPER_DIR/repos/<id> with a timestamp, saying so.\n\n 3. If the worktree still hasn't existed for a month, \"git gc\" deletes\n    the corresponding $GIT_SUPER_DIR/repos/<id> directory.\n\nProblems:\n\n * What if I move my worktree with \"mv\"?  Then I still need the\n   corresponding $GIT_SUPER_DIR/repos/<id> directory, and nobody told\n   the GIT_SUPER_DIR about it.\n\n * What if my worktree is on removable media (think \"network\n   filesystem\") and has just been temporarily unmounted instead of\n   deleted?\n\nSo maybe it would be nicer to:\n\n  i. When the worktree is on the same filesystem, keep a *hard link* to\n     some file in the worktree (e.g., the .git file).  If the link count\n     goes down, it is safe to remove the $GIT_SUPER_DIR/repos/<id>\n     directory.\n\n ii. When the worktree is on another filesystem, always keep\n     $GIT_SUPER_DIR/repos/<id> unless the user decides to manually\n     remove it.  Provide documentation or a command to list basic\n     information about $GIT_SUPER_DIR/repos directories (path to\n     worktree, what branch they're on, etc).\n\n(i) doesn't require any futureproofing.  As soon as someone wants it,\nthey can implement the check and fall back to (ii) for worktrees\nwithout the magic hard link.\n\n(ii) would benefit from recording the working tree directory as a\npossibly unreliable, human-friendly reminder.\n\n>  - How would this interact with core.worktree in .git/config of that\n>    \"super\" repository?\n\nEek.\n\nThanks,\nJonathan\n"},{"id":"231987","messageId":"CACsJy8DG0yx0J6tnAT-BsHsArCRA2akK-txWv5ee6k4pxPpixQ@mail.gmail.com","threadId":"35520","inReplyTo":"xmqqmwk4r55u.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH/POC 2/7] Add new environment variable $GIT_SUPER_DIR","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T01:11:00Z","receivedAt":"2013-12-14T01:11:00Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Sat, Dec 14, 2013 at 1:12 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> diff --git a/path.c b/path.c\n>> index eda9176..86a7c15 100644\n>> --- a/path.c\n>> +++ b/path.c\n>> @@ -75,6 +75,16 @@ static void adjust_git_path(char *buf, int git_dir_len)\n>>               strcpy(buf, get_index_file());\n>>       else if (git_db_env && dir_prefix(base, \"objects\"))\n>>               replace_dir(buf, git_dir_len + 7, get_object_directory());\n>> +     else if (get_git_super_dir()) {\n>> +             if (dir_prefix(base, \"objects\") || dir_prefix(base, \"info\") ||\n>> +                 dir_prefix(base, \"refs\") ||\n>> +                 (dir_prefix(base, \"logs\") && strcmp(base, \"logs/HEAD\")) ||\n>> +                 dir_prefix(base, \"rr-cache\") || dir_prefix(base, \"hooks\") ||\n>> +                 dir_prefix(base, \"svn\") ||\n>> +                 !strcmp(base, \"config\") || !strcmp(base, \"packed-refs\") ||\n>> +                 !strcmp(base, \"shallow\"))\n>> +                     replace_dir(buf, git_dir_len, get_git_super_dir());\n>> +     }\n>\n> This is essentially the list of what are shared across workdirs,\n> right?  I wonder if it is a bad idea to make everything under .git\n> of the borrowed repository shared by default, with selected\n> exceptions.  Granted, not sharing by default is definitely safer\n> than blindly sharing by default, so that alone may be a good\n> argument to use a set of known-to-be-safe-to-share paths, like this\n> code does.\n\nThe exception list could be equally long (most of them are *_HEAD).\n.git is also used for temporary files with mkstemp, but I think that's\nsafe for sharing. What could break is when people add a new local\n*_HEAD and forget to update the exception list.\n\n> Don't we want .git/branches and .git/remotes shared?  After all,\n> their moral equivalents from .git/config are shared with the code.\n\nYes. I missed them.\n-- \nDuy\n"},{"id":"231988","messageId":"CACsJy8DtPw7_OH+vH3zhrgaeir6GbHSNzbWYtD02ozOnAJYE+w@mail.gmail.com","threadId":"35520","inReplyTo":"20131213204348.GX2311@google.com","subject":"Re: [PATCH/POC 3/7] setup.c: add split-repo support to .git files","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T01:28:26Z","receivedAt":"2013-12-14T01:28:26Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Sat, Dec 14, 2013 at 3:43 AM, Jonathan Nieder <jrnieder@gmail.com> wrote:\n> Junio C Hamano wrote:\n>\n>>  - Do we want to record where the working tree directory is in\n>>    $GIT_SUPER_DIR/repos/<id> somewhere?  Would it help to have such\n>>    a record?\n>\n> That could be nice for the purpose of garbage collecting them.  I fear\n> that for users it is too tempting to remove a worktree with \"rm -rf\"\n> without considering the relationship from the parent repo that might\n> be making walking through all reflogs slower or holding on to objects\n> no one cares about any more.\n>\n> I imagine it would work like this:\n>\n>  1. At worktree creation time, full path to the working tree directory\n>     is stored in $GIT_SUPER_DIR/repos/<id>.\n>\n>  2. \"git gc\" notices that the worktree is missing and writes a file\n>     under $GIT_SUPER_DIR/repos/<id> with a timestamp, saying so.\n>\n>  3. If the worktree still hasn't existed for a month, \"git gc\" deletes\n>     the corresponding $GIT_SUPER_DIR/repos/<id> directory.\n\nI was thinking about doing something like this in \"git prune\" but\nmanually. Your idea sounds nicer.\n\n> Problems:\n>\n>  * What if I move my worktree with \"mv\"?  Then I still need the\n>    corresponding $GIT_SUPER_DIR/repos/<id> directory, and nobody told\n>    the GIT_SUPER_DIR about it.\n\nWe could store $GIT_SUPER_DIR as relative path. That way if you move\nit, you break it. When you fix it, hopefully you remember to fix the\nlink in repos/<id> too\n\nAlternatively, the setup up code could be taught to verify that\n$GIT_SUPER_DIR/repos/id/<backlink> actually points to the current\nworktree. If not warn the user or something\n\n>  * What if my worktree is on removable media (think \"network\n>    filesystem\") and has just been temporarily unmounted instead of\n>    deleted?\n\nOr we keep update a timestamp in repos/<id> to note the last used time\nof this worktree. \"gc\" or \"prune\" would warn about unused repos after\na certain amount of time, do not remove them automatically. This could\nbe iii. to your list below.\n\n> So maybe it would be nicer to:\n>\n>   i. When the worktree is on the same filesystem, keep a *hard link* to\n>      some file in the worktree (e.g., the .git file).  If the link count\n>      goes down, it is safe to remove the $GIT_SUPER_DIR/repos/<id>\n>      directory.\n\nThis can still break with updating by creating a new version, then\nrenaming it. Although I can't think why anybody (or anything) would\nwant to do that on .git file. This does not work on Windows though.\n\n>  ii. When the worktree is on another filesystem, always keep\n>      $GIT_SUPER_DIR/repos/<id> unless the user decides to manually\n>      remove it.  Provide documentation or a command to list basic\n>      information about $GIT_SUPER_DIR/repos directories (path to\n>      worktree, what branch they're on, etc).\n\nAnd on Windows, a new partition means a new drive, so it works there too.\n\n>\n> (i) doesn't require any futureproofing.  As soon as someone wants it,\n> they can implement the check and fall back to (ii) for worktrees\n> without the magic hard link.\n>\n> (ii) would benefit from recording the working tree directory as a\n> possibly unreliable, human-friendly reminder.\n>\n>>  - How would this interact with core.worktree in .git/config of that\n>>    \"super\" repository?\n>\n> Eek.\n\nI'll see if I can ignore core.worktree when $GIT_SUPER_DIR is set. If\nnot, ban this use case :)\n-- \nDuy\n"},{"id":"231998","messageId":"1387018507-21999-1-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1386771333-32574-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 00/21] Support multiple worktrees","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:54:46Z","receivedAt":"2013-12-14T10:54:46Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The UI and behavior are taking shape now. On the UI side, you do\n\n  git checkout --to /somewhere -b newbranch origin/master\n\nwhich will create worktree-only repo at /somewhere. \"git prune --repos\"\ncould be used to remove cruft in .git/repos.\n\nOn the behavior side, you should be able to do everything in\n/somewhere just like in a normal repository. If a ref is updated (from\nany repository) that also happens to be your HEAD, it will be\ndetached. \"git rev-list --all\" is also taught to take repos/.../HEAD\ninto account.\n\nThe structure of repos/XXX is documented in 17/21. Known issues\n\n - naming ($GIT_SUPER_DIR, the name of the shared repo and the\n   dependent one, the reuse of \"gitdir\" in .git files)\n\n - gc --auto support, support for manually pruning .git/repos\n\n - should probably support the new .git format in enter_repo() so that\n   we can push to it\n\n - not sure if we need UI for deleting repositories created with\n   checkout --to, or just \"rm -r\" and let \"gc --auto\" clean\n   things up. The thing about \"rm -r(f)\" is that if .git happens to be\n   a real repo, the user is screwed so I don't really like to\n   encourage doing it.\n\n - more tests\n\n\nNguyễn Thái Ngọc Duy (21):\n  path.c: avoid PATH_MAX as buffer size from get_pathname()\n  path.c: rename vsnpath() to git_vsnpath()\n  path.c: move git_path() closer to similar functions git_pathdup()\n  Make git_path() aware of file relocation in $GIT_DIR\n  reflog: use avoid constructing .lock path with git_path\n  fast-import: use git_path() for accessing .git dir instead of get_git_dir()\n  Add new environment variable $GIT_SUPER_DIR\n  setup.c: refactor path manipulation out of read_gitfile()\n  setup.c: add split-repo support to .git files\n  setup.c: add split-repo support to is_git_directory()\n  setup.c: reduce cleanup sites in setup_explicit_git_dir()\n  environment.c: support super .git file specified by $GIT_DIR\n  setup: support $GIT_SUPER_DIR as well as super .git files\n  checkout: support checking out into a new working directory\n  checkout: clean up half-prepared directories in --to mode\n  setup.c: keep track of the .git file location if read\n  prune: strategies for split repositories\n  refs: adjust reflog path for repos/<id>/HEAD\n  refs: detach split repos' HEAD when the linked ref is updated/deleted\n  refs.c: refactor do_head_ref(... to do_one_ref(\"HEAD\", ...\n  revision: include repos/../HEAD in --all\n\n Documentation/config.txt               |   3 +-\n Documentation/git-checkout.txt         |   6 +\n Documentation/git-prune.txt            |   4 +\n Documentation/git.txt                  |   8 ++\n Documentation/gitrepository-layout.txt |  30 ++++\n builtin/checkout.c                     | 173 ++++++++++++++++++++++\n builtin/prune.c                        |  65 +++++++++\n builtin/reflog.c                       |   2 +-\n builtin/rev-parse.c                    |   6 +\n cache.h                                |   5 +\n environment.c                          |  37 ++++-\n fast-import.c                          |   5 +-\n path.c                                 | 140 ++++++++++++++----\n refs.c                                 |  88 ++++++++++--\n refs.h                                 |   1 +\n revision.c                             |   1 +\n setup.c                                | 253 ++++++++++++++++++++++++---------\n t/t0060-path-utils.sh                  | 133 +++++++++++++++++\n t/t1501-worktree.sh                    |  52 +++++++\n t/t1510-repo-setup.sh                  |   1 +\n test-path-utils.c                      |   7 +\n trace.c                                |   1 +\n 22 files changed, 904 insertions(+), 117 deletions(-)\n\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232002","messageId":"1387018507-21999-2-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 01/21] path.c: avoid PATH_MAX as buffer size from get_pathname()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:54:47Z","receivedAt":"2013-12-14T10:54:47Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"We've been avoiding PATH_MAX whenever possible. This patch avoids\nPATH_MAX in get_pathname() and gives it enough room not to worry about\nreally long paths.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n path.c | 28 ++++++++++++++++------------\n 1 file changed, 16 insertions(+), 12 deletions(-)\n\ndiff --git a/path.c b/path.c\nindex 24594c4..4c1c144 100644\n--- a/path.c\n+++ b/path.c\n@@ -16,10 +16,11 @@ static int get_st_mode_bits(const char *path, int *mode)\n \n static char bad_path[] = \"/bad-path/\";\n \n-static char *get_pathname(void)\n+static char *get_pathname(size_t *len)\n {\n-\tstatic char pathname_array[4][PATH_MAX];\n+\tstatic char pathname_array[4][4096];\n \tstatic int index;\n+\t*len = sizeof(pathname_array[0]);\n \treturn pathname_array[3 & ++index];\n }\n \n@@ -108,24 +109,26 @@ char *mkpath(const char *fmt, ...)\n {\n \tva_list args;\n \tunsigned len;\n-\tchar *pathname = get_pathname();\n+\tsize_t n;\n+\tchar *pathname = get_pathname(&n);\n \n \tva_start(args, fmt);\n-\tlen = vsnprintf(pathname, PATH_MAX, fmt, args);\n+\tlen = vsnprintf(pathname, n, fmt, args);\n \tva_end(args);\n-\tif (len >= PATH_MAX)\n+\tif (len >= n)\n \t\treturn bad_path;\n \treturn cleanup_path(pathname);\n }\n \n char *git_path(const char *fmt, ...)\n {\n-\tchar *pathname = get_pathname();\n+\tsize_t len;\n+\tchar *pathname = get_pathname(&len);\n \tva_list args;\n \tchar *ret;\n \n \tva_start(args, fmt);\n-\tret = vsnpath(pathname, PATH_MAX, fmt, args);\n+\tret = vsnpath(pathname, len, fmt, args);\n \tva_end(args);\n \treturn ret;\n }\n@@ -158,14 +161,15 @@ void home_config_paths(char **global, char **xdg, char *file)\n \n char *git_path_submodule(const char *path, const char *fmt, ...)\n {\n-\tchar *pathname = get_pathname();\n+\tsize_t n;\n+\tchar *pathname = get_pathname(&n);\n \tstruct strbuf buf = STRBUF_INIT;\n \tconst char *git_dir;\n \tva_list args;\n \tunsigned len;\n \n \tlen = strlen(path);\n-\tif (len > PATH_MAX-100)\n+\tif (len > n-100)\n \t\treturn bad_path;\n \n \tstrbuf_addstr(&buf, path);\n@@ -180,7 +184,7 @@ char *git_path_submodule(const char *path, const char *fmt, ...)\n \t}\n \tstrbuf_addch(&buf, '/');\n \n-\tif (buf.len >= PATH_MAX)\n+\tif (buf.len >= n)\n \t\treturn bad_path;\n \tmemcpy(pathname, buf.buf, buf.len + 1);\n \n@@ -188,9 +192,9 @@ char *git_path_submodule(const char *path, const char *fmt, ...)\n \tlen = strlen(pathname);\n \n \tva_start(args, fmt);\n-\tlen += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);\n+\tlen += vsnprintf(pathname + len, n - len, fmt, args);\n \tva_end(args);\n-\tif (len >= PATH_MAX)\n+\tif (len >= n)\n \t\treturn bad_path;\n \treturn cleanup_path(pathname);\n }\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"231999","messageId":"1387018507-21999-3-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 02/21] path.c: rename vsnpath() to git_vsnpath()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:54:48Z","receivedAt":"2013-12-14T10:54:48Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is the underlying implementation of git_path(), git_pathdup() and\ngit_snpath() which will prefix $GIT_DIR in the result string. Put git_\nprefix in front of it to avoid the confusion that this is a generic\npath handling function.#\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n path.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/path.c b/path.c\nindex 4c1c144..06863b7 100644\n--- a/path.c\n+++ b/path.c\n@@ -50,7 +50,7 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)\n \treturn cleanup_path(buf);\n }\n \n-static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)\n+static char *git_vsnpath(char *buf, size_t n, const char *fmt, va_list args)\n {\n \tconst char *git_dir = get_git_dir();\n \tsize_t len;\n@@ -75,7 +75,7 @@ char *git_snpath(char *buf, size_t n, const char *fmt, ...)\n \tchar *ret;\n \tva_list args;\n \tva_start(args, fmt);\n-\tret = vsnpath(buf, n, fmt, args);\n+\tret = git_vsnpath(buf, n, fmt, args);\n \tva_end(args);\n \treturn ret;\n }\n@@ -85,7 +85,7 @@ char *git_pathdup(const char *fmt, ...)\n \tchar path[PATH_MAX], *ret;\n \tva_list args;\n \tva_start(args, fmt);\n-\tret = vsnpath(path, sizeof(path), fmt, args);\n+\tret = git_vsnpath(path, sizeof(path), fmt, args);\n \tva_end(args);\n \treturn xstrdup(ret);\n }\n@@ -128,7 +128,7 @@ char *git_path(const char *fmt, ...)\n \tchar *ret;\n \n \tva_start(args, fmt);\n-\tret = vsnpath(pathname, len, fmt, args);\n+\tret = git_vsnpath(pathname, len, fmt, args);\n \tva_end(args);\n \treturn ret;\n }\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232000","messageId":"1387018507-21999-4-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 03/21] path.c: move git_path() closer to similar functions git_pathdup()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:54:49Z","receivedAt":"2013-12-14T10:54:49Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n path.c | 26 +++++++++++++-------------\n 1 file changed, 13 insertions(+), 13 deletions(-)\n\ndiff --git a/path.c b/path.c\nindex 06863b7..08aa213 100644\n--- a/path.c\n+++ b/path.c\n@@ -80,6 +80,19 @@ char *git_snpath(char *buf, size_t n, const char *fmt, ...)\n \treturn ret;\n }\n \n+char *git_path(const char *fmt, ...)\n+{\n+\tsize_t len;\n+\tchar *pathname = get_pathname(&len);\n+\tva_list args;\n+\tchar *ret;\n+\n+\tva_start(args, fmt);\n+\tret = git_vsnpath(pathname, len, fmt, args);\n+\tva_end(args);\n+\treturn ret;\n+}\n+\n char *git_pathdup(const char *fmt, ...)\n {\n \tchar path[PATH_MAX], *ret;\n@@ -120,19 +133,6 @@ char *mkpath(const char *fmt, ...)\n \treturn cleanup_path(pathname);\n }\n \n-char *git_path(const char *fmt, ...)\n-{\n-\tsize_t len;\n-\tchar *pathname = get_pathname(&len);\n-\tva_list args;\n-\tchar *ret;\n-\n-\tva_start(args, fmt);\n-\tret = git_vsnpath(pathname, len, fmt, args);\n-\tva_end(args);\n-\treturn ret;\n-}\n-\n void home_config_paths(char **global, char **xdg, char *file)\n {\n \tchar *xdg_home = getenv(\"XDG_CONFIG_HOME\");\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232001","messageId":"1387018507-21999-5-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 04/21] Make git_path() aware of file relocation in $GIT_DIR","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:54:50Z","receivedAt":"2013-12-14T10:54:50Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"We allow the user to relocate certain paths out of $GIT_DIR via\nenvironment variables, e.g. GIT_OBJECT_DIRECTORY, GIT_INDEX_FILE and\nGIT_GRAFT_FILE. All callers are not supposed to use git_path() or\ngit_pathdup() to get those paths. Instead they must use\nget_object_directory(), get_index_file() and get_graft_file()\nrespectively. This is inconvenient and could be missed in review\n(there's git_path(\"objects/info/alternates\") somewhere in\nsha1_file.c).\n\nThis patch makes git_path() and git_pathdup() understand those\nenvironment variables. So if you set GIT_OBJECT_DIRECTORY to /foo/bar,\ngit_path(\"objects/abc\") should return /tmp/bar/abc. The same is done\nfor the two remaining env variables.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h               |  1 +\n environment.c         |  9 ++++++--\n path.c                | 57 ++++++++++++++++++++++++++++++++++++++++++++++++---\n t/t0060-path-utils.sh | 54 ++++++++++++++++++++++++++++++++++++++++++++++++\n test-path-utils.c     |  7 +++++++\n 5 files changed, 123 insertions(+), 5 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex ce377e1..cdafbd7 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -584,6 +584,7 @@ extern int fsync_object_files;\n extern int core_preload_index;\n extern int core_apply_sparse_checkout;\n extern int precomposed_unicode;\n+extern int git_db_env, git_index_env, git_graft_env;\n \n /*\n  * The character that begins a commented line in user-editable file\ndiff --git a/environment.c b/environment.c\nindex 0a15349..1d74dde 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -81,6 +81,7 @@ static size_t namespace_len;\n \n static const char *git_dir;\n static char *git_object_dir, *git_index_file, *git_graft_file;\n+int git_db_env, git_index_env, git_graft_env;\n \n /*\n  * Repository-local GIT_* environment variables; see cache.h for details.\n@@ -134,15 +135,19 @@ static void setup_git_env(void)\n \tif (!git_object_dir) {\n \t\tgit_object_dir = xmalloc(strlen(git_dir) + 9);\n \t\tsprintf(git_object_dir, \"%s/objects\", git_dir);\n-\t}\n+\t} else\n+\t\tgit_db_env = 1;\n \tgit_index_file = getenv(INDEX_ENVIRONMENT);\n \tif (!git_index_file) {\n \t\tgit_index_file = xmalloc(strlen(git_dir) + 7);\n \t\tsprintf(git_index_file, \"%s/index\", git_dir);\n-\t}\n+\t} else\n+\t\tgit_index_env = 1;\n \tgit_graft_file = getenv(GRAFT_ENVIRONMENT);\n \tif (!git_graft_file)\n \t\tgit_graft_file = git_pathdup(\"info/grafts\");\n+\telse\n+\t\tgit_graft_env = 1;\n \tif (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))\n \t\tread_replace_refs = 0;\n \tnamespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));\ndiff --git a/path.c b/path.c\nindex 08aa213..93e3ecc 100644\n--- a/path.c\n+++ b/path.c\n@@ -50,10 +50,60 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)\n \treturn cleanup_path(buf);\n }\n \n+static void copy_path(char *dst, size_t n, const char *src)\n+{\n+\tif (strlen(src) < n)\n+\t\tstrcpy(dst, src);\n+\telse\n+\t\tstrlcpy(dst, bad_path, n);\n+}\n+\n+static int dir_prefix(const char *buf, const char *dir)\n+{\n+\tint len = strlen(dir);\n+\treturn !strncmp(buf, dir, len) &&\n+\t\t(is_dir_sep(buf[len]) || buf[len] == '\\0');\n+}\n+\n+/* $buf =~ m|$dir/+$file| but without regex */\n+static int is_dir_file(const char *buf, const char *dir, const char *file)\n+{\n+\tint len = strlen(dir);\n+\tif (strncmp(buf, dir, len) || !is_dir_sep(buf[len]))\n+\t\treturn 0;\n+\twhile (is_dir_sep(buf[len]))\n+\t\tlen++;\n+\treturn !strcmp(buf + len, file);\n+}\n+\n+static void replace_dir(char *buf, size_t n, int len, const char *newdir)\n+{\n+\tint newlen = strlen(newdir);\n+\tint buflen = strlen(buf);\n+\tif (buflen - len + newlen >= n) {\n+\t\tstrlcpy(buf, bad_path, n);\n+\t\treturn;\n+\t}\n+\tmemmove(buf + newlen + 1, buf + len, buflen - len + 1);\n+\tmemcpy(buf, newdir, newlen);\n+\tbuf[newlen] = '/';\n+}\n+\n+static void adjust_git_path(char *buf, size_t n, int git_dir_len)\n+{\n+\tchar *base = buf + git_dir_len;\n+\tif (git_graft_env && is_dir_file(base, \"info\", \"grafts\"))\n+\t\tcopy_path(buf, n, get_graft_file());\n+\telse if (git_index_env && !strcmp(base, \"index\"))\n+\t\tcopy_path(buf, n, get_index_file());\n+\telse if (git_db_env && dir_prefix(base, \"objects\"))\n+\t\treplace_dir(buf, n, git_dir_len + 7, get_object_directory());\n+}\n+\n static char *git_vsnpath(char *buf, size_t n, const char *fmt, va_list args)\n {\n \tconst char *git_dir = get_git_dir();\n-\tsize_t len;\n+\tsize_t len, total_len;\n \n \tlen = strlen(git_dir);\n \tif (n < len + 1)\n@@ -61,9 +111,10 @@ static char *git_vsnpath(char *buf, size_t n, const char *fmt, va_list args)\n \tmemcpy(buf, git_dir, len);\n \tif (len && !is_dir_sep(git_dir[len-1]))\n \t\tbuf[len++] = '/';\n-\tlen += vsnprintf(buf + len, n - len, fmt, args);\n-\tif (len >= n)\n+\ttotal_len = len + vsnprintf(buf + len, n - len, fmt, args);\n+\tif (total_len >= n)\n \t\tgoto bad;\n+\tadjust_git_path(buf, n, len);\n \treturn cleanup_path(buf);\n bad:\n \tstrlcpy(buf, bad_path, n);\ndiff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh\nindex 07c10c8..bce16f5 100755\n--- a/t/t0060-path-utils.sh\n+++ b/t/t0060-path-utils.sh\n@@ -223,4 +223,58 @@ relative_path \"<null>\"\t\t\"<empty>\"\t./\n relative_path \"<null>\"\t\t\"<null>\"\t./\n relative_path \"<null>\"\t\t/foo/a/b\t./\n \n+test_expect_success 'git_path info/grafts without GIT_GRAFT_FILE' '\n+\ttest-path-utils git_path info/grafts >actual1 &&\n+\techo .git/info/grafts >expect1 &&\n+\ttest_cmp expect1 actual1\n+'\n+\n+test_expect_success 'git_path info/grafts with GIT_GRAFT_FILE' '\n+\tGIT_GRAFT_FILE=foo test-path-utils git_path info/grafts >actual2 &&\n+\techo foo >expect2 &&\n+\ttest_cmp expect2 actual2\n+'\n+\n+test_expect_success 'git_path info/////grafts with GIT_GRAFT_FILE' '\n+\tGIT_GRAFT_FILE=foo test-path-utils git_path info/////grafts >actual2 &&\n+\techo foo >expect2 &&\n+\ttest_cmp expect2 actual2\n+'\n+\n+test_expect_success 'git_path index' '\n+\tGIT_INDEX_FILE=foo test-path-utils git_path index >actual &&\n+\techo foo >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path index/foo' '\n+\tGIT_INDEX_FILE=foo test-path-utils git_path index/foo >actual &&\n+\techo .git/index/foo >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path index2' '\n+\tGIT_INDEX_FILE=foo test-path-utils git_path index2 >actual &&\n+\techo .git/index2 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path objects' '\n+\tGIT_OBJECT_DIRECTORY=foo test-path-utils git_path objects >actual &&\n+\techo foo/ >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path objects/foo' '\n+\tGIT_OBJECT_DIRECTORY=foo test-path-utils git_path objects/foo >actual &&\n+\techo foo//foo >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path objects2' '\n+\tGIT_OBJECT_DIRECTORY=foo test-path-utils git_path objects2 >actual &&\n+\techo .git/objects2 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\ndiff --git a/test-path-utils.c b/test-path-utils.c\nindex 3dd3744..55e4fe0 100644\n--- a/test-path-utils.c\n+++ b/test-path-utils.c\n@@ -135,6 +135,13 @@ int main(int argc, char **argv)\n \t\treturn 0;\n \t}\n \n+\tif (argc == 3 && !strcmp(argv[1], \"git_path\")) {\n+\t\tint nongit_ok = 0;\n+\t\tsetup_git_directory_gently(&nongit_ok);\n+\t\tputs(git_path(\"%s\", argv[2]));\n+\t\treturn 0;\n+\t}\n+\n \tfprintf(stderr, \"%s: unknown function name: %s\\n\", argv[0],\n \t\targv[1] ? argv[1] : \"(there was none)\");\n \treturn 1;\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232003","messageId":"1387018507-21999-6-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 05/21] reflog: use avoid constructing .lock path with git_path","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:54:51Z","receivedAt":"2013-12-14T10:54:51Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"git_path() understands the path given to it. Unrecognized paths by\ndefault go to $GIT_SUPER_DIR/repos/<id>/... in split-repo mode. We\nwant the %s.lock file to be at the same place %s file is. Avoid\ngit_path() in this case and use log_file as %s, which should contain\nthe final path of %s file.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/reflog.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/reflog.c b/builtin/reflog.c\nindex 6eb24c8..dc1accf 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -372,7 +372,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,\n \tif (!file_exists(log_file))\n \t\tgoto finish;\n \tif (!cmd->dry_run) {\n-\t\tnewlog_path = git_pathdup(\"logs/%s.lock\", ref);\n+\t\tnewlog_path = mkpathdup(\"%s.lock\", log_file);\n \t\tcb.newlog = fopen(newlog_path, \"w\");\n \t}\n \n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232004","messageId":"1387018507-21999-7-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 06/21] fast-import: use git_path() for accessing .git dir instead of get_git_dir()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:54:52Z","receivedAt":"2013-12-14T10:54:52Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This allows git_path() to redirect info/fast-import to another place\nif needed\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n fast-import.c | 5 +----\n 1 file changed, 1 insertion(+), 4 deletions(-)\n\ndiff --git a/fast-import.c b/fast-import.c\nindex f4d9969..04bba3d 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -3125,12 +3125,9 @@ static void parse_progress(void)\n \n static char* make_fast_import_path(const char *path)\n {\n-\tstruct strbuf abs_path = STRBUF_INIT;\n-\n \tif (!relative_marks_paths || is_absolute_path(path))\n \t\treturn xstrdup(path);\n-\tstrbuf_addf(&abs_path, \"%s/info/fast-import/%s\", get_git_dir(), path);\n-\treturn strbuf_detach(&abs_path, NULL);\n+\treturn xstrdup(git_path(\"info/fast-import/%s\", path));\n }\n \n static void option_import_marks(const char *marks,\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232005","messageId":"1387018507-21999-8-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 07/21] Add new environment variable $GIT_SUPER_DIR","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:54:53Z","receivedAt":"2013-12-14T10:54:53Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"If $GIT_SUPER_DIR is defined, the repository is splitted into two\nplaces:\n\n - HEAD, logs/HEAD, index, other top-level refs and unrecognized files\n   are from $GIT_DIR\n\n - the rest like objects, refs, info, hooks, packed-refs, shallow...\n   are from $GIT_SUPER_DIR\n\nThe redirection is done by git_path(), git_pathdup() and git_snpath()\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git.txt |  8 ++++++\n cache.h               |  2 ++\n environment.c         | 11 +++++--\n path.c                | 28 ++++++++++++++++++\n t/t0060-path-utils.sh | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 126 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex 4448ce2..9f4e9f8 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -764,6 +764,14 @@ Git so take care if using Cogito etc.\n \tan explicit repository directory set via 'GIT_DIR' or on the\n \tcommand line.\n \n+'GIT_SUPER_DIR'::\n+\tIf this variable is set to a path, non-worktree files that are\n+\tnormally in $GIT_DIR will be taken from this path\n+\tinstead. Worktree-specific files such as HEAD or index are\n+\ttaken from $GIT_DIR. This variable has lower precedence than\n+\tother path variables such as GIT_INDEX_FILE,\n+\tGIT_OBJECT_DIRECTORY...\n+\n Git Commits\n ~~~~~~~~~~~\n 'GIT_AUTHOR_NAME'::\ndiff --git a/cache.h b/cache.h\nindex cdafbd7..823582f 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -347,6 +347,7 @@ static inline enum object_type object_type(unsigned int mode)\n \n /* Double-check local_repo_env below if you add to this list. */\n #define GIT_DIR_ENVIRONMENT \"GIT_DIR\"\n+#define GIT_SUPER_DIR_ENVIRONMENT \"GIT_SUPER_DIR\"\n #define GIT_NAMESPACE_ENVIRONMENT \"GIT_NAMESPACE\"\n #define GIT_WORK_TREE_ENVIRONMENT \"GIT_WORK_TREE\"\n #define GIT_PREFIX_ENVIRONMENT \"GIT_PREFIX\"\n@@ -399,6 +400,7 @@ extern int is_inside_git_dir(void);\n extern char *git_work_tree_cfg;\n extern int is_inside_work_tree(void);\n extern const char *get_git_dir(void);\n+extern const char *get_git_super_dir(void);\n extern int is_git_directory(const char *path);\n extern char *get_object_directory(void);\n extern char *get_index_file(void);\ndiff --git a/environment.c b/environment.c\nindex 1d74dde..d5ae7a3 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -79,7 +79,7 @@ static char *work_tree;\n static const char *namespace;\n static size_t namespace_len;\n \n-static const char *git_dir;\n+static const char *git_dir, *git_super_dir;\n static char *git_object_dir, *git_index_file, *git_graft_file;\n int git_db_env, git_index_env, git_graft_env;\n \n@@ -131,10 +131,12 @@ static void setup_git_env(void)\n \t\tgit_dir = DEFAULT_GIT_DIR_ENVIRONMENT;\n \tgitfile = read_gitfile(git_dir);\n \tgit_dir = xstrdup(gitfile ? gitfile : git_dir);\n+\tgit_super_dir = getenv(GIT_SUPER_DIR_ENVIRONMENT);\n \tgit_object_dir = getenv(DB_ENVIRONMENT);\n \tif (!git_object_dir) {\n \t\tgit_object_dir = xmalloc(strlen(git_dir) + 9);\n-\t\tsprintf(git_object_dir, \"%s/objects\", git_dir);\n+\t\tsprintf(git_object_dir, \"%s/objects\",\n+\t\t\tgit_super_dir ? git_super_dir : git_dir);\n \t} else\n \t\tgit_db_env = 1;\n \tgit_index_file = getenv(INDEX_ENVIRONMENT);\n@@ -167,6 +169,11 @@ const char *get_git_dir(void)\n \treturn git_dir;\n }\n \n+const char *get_git_super_dir(void)\n+{\n+\treturn git_super_dir;\n+}\n+\n const char *get_git_namespace(void)\n {\n \tif (!namespace)\ndiff --git a/path.c b/path.c\nindex 93e3ecc..e51fc35 100644\n--- a/path.c\n+++ b/path.c\n@@ -89,8 +89,34 @@ static void replace_dir(char *buf, size_t n, int len, const char *newdir)\n \tbuf[newlen] = '/';\n }\n \n+static void update_super_dir(char *buf, size_t n, int git_dir_len)\n+{\n+\tconst char *super_dir_list[] = {\n+\t\t\"branches\", \"hooks\", \"info\", \"logs\", \"modules\",\n+\t\t\"objects\", \"refs\", \"remotes\", \"rr-cache\", \"svn\", NULL\n+\t};\n+\tconst char *super_top_file_list[] = {\n+\t\t\"config\", \"packed-refs\", \"shallow\", NULL\n+\t};\n+\tchar *base = buf + git_dir_len;\n+\tconst char **p;\n+\tif (is_dir_file(base, \"logs\", \"HEAD\"))\n+\t\treturn;\t/* keep this in $GIT_DIR */\n+\tfor (p = super_dir_list; *p; p++)\n+\t\tif (dir_prefix(base, *p)) {\n+\t\t\treplace_dir(buf, n, git_dir_len, get_git_super_dir());\n+\t\t\treturn;\n+\t\t}\n+\tfor (p = super_top_file_list; *p; p++)\n+\t\tif (!strcmp(base, *p)) {\n+\t\t\treplace_dir(buf, n, git_dir_len, get_git_super_dir());\n+\t\t\treturn;\n+\t\t}\n+}\n+\n static void adjust_git_path(char *buf, size_t n, int git_dir_len)\n {\n+\n \tchar *base = buf + git_dir_len;\n \tif (git_graft_env && is_dir_file(base, \"info\", \"grafts\"))\n \t\tcopy_path(buf, n, get_graft_file());\n@@ -98,6 +124,8 @@ static void adjust_git_path(char *buf, size_t n, int git_dir_len)\n \t\tcopy_path(buf, n, get_index_file());\n \telse if (git_db_env && dir_prefix(base, \"objects\"))\n \t\treplace_dir(buf, n, git_dir_len + 7, get_object_directory());\n+\telse if (get_git_super_dir())\n+\t\tupdate_super_dir(buf, n, git_dir_len);\n }\n \n static char *git_vsnpath(char *buf, size_t n, const char *fmt, va_list args)\ndiff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh\nindex bce16f5..5e798c4 100755\n--- a/t/t0060-path-utils.sh\n+++ b/t/t0060-path-utils.sh\n@@ -277,4 +277,83 @@ test_expect_success 'git_path objects2' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'git_path super index' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path index >actual &&\n+\techo .git/index >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path super HEAD' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path HEAD >actual &&\n+\techo .git/HEAD >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path super objects/*' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path objects/foo >actual &&\n+\techo foo/objects/foo >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path super info/*' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path info/exclude >actual &&\n+\techo foo/info/exclude >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path super remotes/*' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path remotes/foo >actual &&\n+\techo foo/remotes/foo >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path super branches/*' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path branches/foo >actual &&\n+\techo foo/branches/foo >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path super logs/refs/heads/master' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path logs/refs/heads/master >actual &&\n+\techo foo/logs/refs/heads/master >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+\n+test_expect_success 'git_path super refs/heads/master' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path refs/heads/master >actual &&\n+\techo foo/refs/heads/master >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path super logs/HEAD' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path logs/HEAD >actual &&\n+\techo .git/logs/HEAD >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path super hooks/me' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path hooks/me >actual &&\n+\techo foo/hooks/me >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path super config' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path config >actual &&\n+\techo foo/config >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path super packed-refs' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path packed-refs >actual &&\n+\techo foo/packed-refs >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git_path super shallow' '\n+\tGIT_SUPER_DIR=foo test-path-utils git_path shallow >actual &&\n+\techo foo/shallow >expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232006","messageId":"1387018507-21999-9-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 08/21] setup.c: refactor path manipulation out of read_gitfile()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:54:54Z","receivedAt":"2013-12-14T10:54:54Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n setup.c | 44 ++++++++++++++++++++++++--------------------\n 1 file changed, 24 insertions(+), 20 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 5432a31..c040981 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -278,15 +278,31 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)\n \treturn 0;\n }\n \n+static char *path_from_gitfile(const char *path, const char *buf, int len)\n+{\n+\tconst char *slash;\n+\tif (len < 1)\n+\t\tdie(\"No path in gitfile: %s\", path);\n+\n+\tif (!is_absolute_path(buf) && (slash = strrchr(path, '/'))) {\n+\t\tsize_t pathlen = slash+1 - path;\n+\t\tsize_t dirlen = pathlen + len;\n+\t\tchar *p = xmalloc(dirlen + 1);\n+\t\tstrncpy(p, path, pathlen);\n+\t\tstrncpy(p + pathlen, buf, len);\n+\t\tp[dirlen] = '\\0';\n+\t\treturn p;\n+\t} else\n+\t\treturn xmemdupz(buf, len);\n+}\n+\n /*\n  * Try to read the location of the git directory from the .git file,\n  * return path to git directory if found.\n  */\n const char *read_gitfile(const char *path)\n {\n-\tchar *buf;\n-\tchar *dir;\n-\tconst char *slash;\n+\tchar *buf, *dir;\n \tstruct stat st;\n \tint fd;\n \tssize_t len;\n@@ -303,31 +319,19 @@ const char *read_gitfile(const char *path)\n \tclose(fd);\n \tif (len != st.st_size)\n \t\tdie(\"Error reading %s\", path);\n-\tbuf[len] = '\\0';\n-\tif (prefixcmp(buf, \"gitdir: \"))\n-\t\tdie(\"Invalid gitfile format: %s\", path);\n-\twhile (buf[len - 1] == '\\n' || buf[len - 1] == '\\r')\n+\twhile (len > 0 && (buf[len - 1] == '\\n' || buf[len - 1] == '\\r'))\n \t\tlen--;\n-\tif (len < 9)\n-\t\tdie(\"No path in gitfile: %s\", path);\n \tbuf[len] = '\\0';\n-\tdir = buf + 8;\n \n-\tif (!is_absolute_path(dir) && (slash = strrchr(path, '/'))) {\n-\t\tsize_t pathlen = slash+1 - path;\n-\t\tsize_t dirlen = pathlen + len - 8;\n-\t\tdir = xmalloc(dirlen + 1);\n-\t\tstrncpy(dir, path, pathlen);\n-\t\tstrncpy(dir + pathlen, buf + 8, len - 8);\n-\t\tdir[dirlen] = '\\0';\n-\t\tfree(buf);\n-\t\tbuf = dir;\n-\t}\n+\tif (prefixcmp(buf, \"gitdir: \"))\n+\t\tdie(\"Invalid gitfile format: %s\", path);\n+\tdir = path_from_gitfile(path, buf + 8, len - 8);\n \n \tif (!is_git_directory(dir))\n \t\tdie(\"Not a git repository: %s\", dir);\n \tpath = real_path(dir);\n \n+\tfree(dir);\n \tfree(buf);\n \treturn path;\n }\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232007","messageId":"1387018507-21999-10-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 09/21] setup.c: add split-repo support to .git files","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:54:55Z","receivedAt":"2013-12-14T10:54:55Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"If a .git file contains\n\ngitsuper: <path>\ngitdir: <id>\n\nthen we set GIT_SUPER_DIR to <path> and GIT_DIR to\n$GIT_SUPER_DIR/repos/<id>.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h |  1 +\n setup.c | 41 +++++++++++++++++++++++++++++++++++++----\n 2 files changed, 38 insertions(+), 4 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 823582f..f85ee70 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -410,6 +410,7 @@ extern const char *get_git_namespace(void);\n extern const char *strip_namespace(const char *namespaced_ref);\n extern const char *get_git_work_tree(void);\n extern const char *read_gitfile(const char *path);\n+extern const char *read_gitfile_super(const char *path, char **super);\n extern const char *resolve_gitdir(const char *suspect);\n extern void set_git_work_tree(const char *tree);\n \ndiff --git a/setup.c b/setup.c\nindex c040981..e9f0ef6 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -299,14 +299,20 @@ static char *path_from_gitfile(const char *path, const char *buf, int len)\n /*\n  * Try to read the location of the git directory from the .git file,\n  * return path to git directory if found.\n+ *\n+ * If \"gitsuper: \" line is found and super is not NULL, *super points\n+ * to the absolute path of the given path. The next line contains the\n+ * repo id.\n  */\n-const char *read_gitfile(const char *path)\n+const char *read_gitfile_super(const char *path, char **super)\n {\n \tchar *buf, *dir;\n \tstruct stat st;\n \tint fd;\n \tssize_t len;\n \n+\tif (super)\n+\t\t*super = NULL;\n \tif (stat(path, &st))\n \t\treturn NULL;\n \tif (!S_ISREG(st.st_mode))\n@@ -323,9 +329,30 @@ const char *read_gitfile(const char *path)\n \t\tlen--;\n \tbuf[len] = '\\0';\n \n-\tif (prefixcmp(buf, \"gitdir: \"))\n-\t\tdie(\"Invalid gitfile format: %s\", path);\n-\tdir = path_from_gitfile(path, buf + 8, len - 8);\n+\tif (super && !prefixcmp(buf, \"gitsuper: \")) {\n+\t\tchar *p = buf, *end = buf + len;\n+\t\twhile (p < end && *p != '\\n' && *p != '\\r')\n+\t\t\tp++;\n+\t\tif (p == end)\n+\t\t\tdie(\"Invalid gitfile format: %s\", path);\n+\t\t*super = buf + strlen(\"gitsuper: \");\n+\t\t*p = '\\0';\n+\t\t*super = path_from_gitfile(path, *super, p - *super);\n+\t\tp++;\n+\t\twhile (p < end && (*p == '\\n' || *p == '\\r'))\n+\t\t\tp++;\n+\t\tif (prefixcmp(p, \"gitdir: \"))\n+\t\t\tdie(\"Invalid gitfile format: %s\", path);\n+\t\tp += 8;\n+\t\tif (p == end)\n+\t\t\tdie(\"Invalid gitfile format: %s\", path);\n+\t\tdir = xmalloc(strlen(*super) + strlen(\"/repos/\") + (end - p) + 1);\n+\t\tsprintf(dir, \"%s/repos/%s\", *super, p);\n+\t} else {\n+\t\tif (prefixcmp(buf, \"gitdir: \"))\n+\t\t\tdie(\"Invalid gitfile format: %s\", path);\n+\t\tdir = path_from_gitfile(path, buf + 8, len - 8);\n+\t}\n \n \tif (!is_git_directory(dir))\n \t\tdie(\"Not a git repository: %s\", dir);\n@@ -336,6 +363,12 @@ const char *read_gitfile(const char *path)\n \treturn path;\n }\n \n+const char *read_gitfile(const char *path)\n+{\n+\treturn read_gitfile_super(path, NULL);\n+}\n+\n+\n static const char *setup_explicit_git_dir(const char *gitdirenv,\n \t\t\t\t\t  char *cwd, int len,\n \t\t\t\t\t  int *nongit_ok)\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232008","messageId":"1387018507-21999-11-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 10/21] setup.c: add split-repo support to is_git_directory()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:54:56Z","receivedAt":"2013-12-14T10:54:56Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n setup.c | 28 ++++++++++++++++++++++------\n 1 file changed, 22 insertions(+), 6 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex e9f0ef6..01fe89a 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -182,28 +182,38 @@ void verify_non_filename(const char *prefix, const char *arg)\n  *    a proper \"ref:\", or a regular file HEAD that has a properly\n  *    formatted sha1 object name.\n  */\n-int is_git_directory(const char *suspect)\n+static int is_git_directory_super(const char *suspect, const char *super)\n {\n \tchar path[PATH_MAX];\n \tsize_t len = strlen(suspect);\n+\tsize_t super_len;\n+\n+\tif (!super) {\n+\t\tsuper = suspect;\n+\t\tsuper_len = len;\n+\t} else\n+\t\tsuper_len = strlen(super);\n \n-\tif (PATH_MAX <= len + strlen(\"/objects\"))\n+\tif (PATH_MAX <= super_len + strlen(\"/objects\") ||\n+\t    PATH_MAX <= len + strlen(\"/HEAD\"))\n \t\tdie(\"Too long path: %.*s\", 60, suspect);\n-\tstrcpy(path, suspect);\n+\tstrcpy(path, super);\n \tif (getenv(DB_ENVIRONMENT)) {\n \t\tif (access(getenv(DB_ENVIRONMENT), X_OK))\n \t\t\treturn 0;\n \t}\n \telse {\n-\t\tstrcpy(path + len, \"/objects\");\n+\t\tstrcpy(path + super_len, \"/objects\");\n \t\tif (access(path, X_OK))\n \t\t\treturn 0;\n \t}\n \n-\tstrcpy(path + len, \"/refs\");\n+\tstrcpy(path + super_len, \"/refs\");\n \tif (access(path, X_OK))\n \t\treturn 0;\n \n+\tif (super != suspect)\n+\t\tstrcpy(path, suspect);\n \tstrcpy(path + len, \"/HEAD\");\n \tif (validate_headref(path))\n \t\treturn 0;\n@@ -211,6 +221,12 @@ int is_git_directory(const char *suspect)\n \treturn 1;\n }\n \n+int is_git_directory(const char *suspect)\n+{\n+\treturn is_git_directory_super(suspect, NULL);\n+}\n+\n+\n int is_inside_git_dir(void)\n {\n \tif (inside_git_dir < 0)\n@@ -354,7 +370,7 @@ const char *read_gitfile_super(const char *path, char **super)\n \t\tdir = path_from_gitfile(path, buf + 8, len - 8);\n \t}\n \n-\tif (!is_git_directory(dir))\n+\tif (!is_git_directory_super(dir, super ? *super : NULL))\n \t\tdie(\"Not a git repository: %s\", dir);\n \tpath = real_path(dir);\n \n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232009","messageId":"1387018507-21999-12-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 11/21] setup.c: reduce cleanup sites in setup_explicit_git_dir()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:54:57Z","receivedAt":"2013-12-14T10:54:57Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n setup.c | 20 ++++++++------------\n 1 file changed, 8 insertions(+), 12 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 01fe89a..397eecc 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -406,16 +406,13 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,\n \tif (!is_git_directory(gitdirenv)) {\n \t\tif (nongit_ok) {\n \t\t\t*nongit_ok = 1;\n-\t\t\tfree(gitfile);\n-\t\t\treturn NULL;\n+\t\t\tgoto done_null;\n \t\t}\n \t\tdie(\"Not a git repository: '%s'\", gitdirenv);\n \t}\n \n-\tif (check_repository_format_gently(gitdirenv, nongit_ok)) {\n-\t\tfree(gitfile);\n-\t\treturn NULL;\n-\t}\n+\tif (check_repository_format_gently(gitdirenv, nongit_ok))\n+\t\tgoto done_null;\n \n \t/* #3, #7, #11, #15, #19, #23, #27, #31 (see t1510) */\n \tif (work_tree_env)\n@@ -426,8 +423,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,\n \n \t\t/* #18, #26 */\n \t\tset_git_dir(gitdirenv);\n-\t\tfree(gitfile);\n-\t\treturn NULL;\n+\t\tgoto done_null;\n \t}\n \telse if (git_work_tree_cfg) { /* #6, #14 */\n \t\tif (is_absolute_path(git_work_tree_cfg))\n@@ -448,8 +444,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,\n \telse if (!git_env_bool(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, 1)) {\n \t\t/* #16d */\n \t\tset_git_dir(gitdirenv);\n-\t\tfree(gitfile);\n-\t\treturn NULL;\n+\t\tgoto done_null;\n \t}\n \telse /* #2, #10 */\n \t\tset_git_work_tree(\".\");\n@@ -460,8 +455,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,\n \t/* both get_git_work_tree() and cwd are already normalized */\n \tif (!strcmp(cwd, worktree)) { /* cwd == worktree */\n \t\tset_git_dir(gitdirenv);\n-\t\tfree(gitfile);\n-\t\treturn NULL;\n+\t\tgoto done_null;\n \t}\n \n \toffset = dir_inside_of(cwd, worktree);\n@@ -477,6 +471,8 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,\n \n \t/* cwd outside worktree */\n \tset_git_dir(gitdirenv);\n+\n+done_null:\n \tfree(gitfile);\n \treturn NULL;\n }\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232010","messageId":"1387018507-21999-13-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 12/21] environment.c: support super .git file specified by $GIT_DIR","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:54:58Z","receivedAt":"2013-12-14T10:54:58Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n environment.c | 8 ++++++--\n 1 file changed, 6 insertions(+), 2 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex d5ae7a3..cbfa879 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -125,13 +125,17 @@ static char *expand_namespace(const char *raw_namespace)\n static void setup_git_env(void)\n {\n \tconst char *gitfile;\n+\tchar *super;\n \n \tgit_dir = getenv(GIT_DIR_ENVIRONMENT);\n \tif (!git_dir)\n \t\tgit_dir = DEFAULT_GIT_DIR_ENVIRONMENT;\n-\tgitfile = read_gitfile(git_dir);\n+\tgitfile = read_gitfile_super(git_dir, &super);\n \tgit_dir = xstrdup(gitfile ? gitfile : git_dir);\n-\tgit_super_dir = getenv(GIT_SUPER_DIR_ENVIRONMENT);\n+\tif (super)\n+\t\tgit_super_dir = xstrdup(super);\n+\telse\n+\t\tgit_super_dir = getenv(GIT_SUPER_DIR_ENVIRONMENT);\n \tgit_object_dir = getenv(DB_ENVIRONMENT);\n \tif (!git_object_dir) {\n \t\tgit_object_dir = xmalloc(strlen(git_dir) + 9);\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232011","messageId":"1387018507-21999-14-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 13/21] setup: support $GIT_SUPER_DIR as well as super .git files","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:54:59Z","receivedAt":"2013-12-14T10:54:59Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The same rules for git repo setup apply except:\n\n- detect super .git files, set $GIT_SUPER_DIR accordingly\n\n- if $GIT_SUPER_DIR is set (or specified by super .git files), look\n  for non-worktree stuff in this directory instead of $GIT_DIR. This\n  mostly affects is_git_directory() and check_repository_format()\n\n- the worktree setting precedence goes from lower to higher:\n  core.worktree, GIT_SUPER_DIR then GIT_WORK_TREE\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/config.txt               |  3 +-\n Documentation/gitrepository-layout.txt | 10 ++++\n builtin/rev-parse.c                    |  6 +++\n cache.h                                |  1 +\n environment.c                          | 11 +++++\n setup.c                                | 89 ++++++++++++++++++++++++----------\n t/t1501-worktree.sh                    | 52 ++++++++++++++++++++\n t/t1510-repo-setup.sh                  |  1 +\n trace.c                                |  1 +\n 9 files changed, 147 insertions(+), 27 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex a405806..df19aa8 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -381,7 +381,8 @@ false), while all other repositories are assumed to be bare (bare\n \n core.worktree::\n \tSet the path to the root of the working tree.\n-\tThis can be overridden by the GIT_WORK_TREE environment\n+\tThis can be overridden by the GIT_WORK_TREE\n+\tor GIT_SUPER_DIR environment\n \tvariable and the '--work-tree' command line option.\n \tThe value can be an absolute path or relative to the path to\n \tthe .git directory, which is either specified by --git-dir\ndiff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt\nindex aa03882..7ce31d4 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -29,6 +29,10 @@ containing superproject to `git checkout` a branch that does not\n have the submodule.  The `checkout` has to remove the entire\n submodule working tree, without losing the submodule repository.\n \n+If the text file contains `gitsuper: <path>`, this is a \"working\n+directory only\" repository, attached to another repository and share\n+everything with the attached repository except HEAD and the index.\n+\n These things may exist in a Git repository.\n \n objects::\n@@ -214,6 +218,12 @@ shallow::\n modules::\n \tContains the git-repositories of the submodules.\n \n+repos/<id>::\n+\tIf a repository's .git is a file contains two lines `gitsuper:\n+\t<path>` and `repo: <id>`. The directory `<path>/repos/<id>`\n+\tcontains the real non-shared part of .git directory of the\n+\trepository in question (e.g. HEAD or index).\n+\n SEE ALSO\n --------\n linkgit:git-init[1],\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex 1d9ecaf..f5f766a 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -725,6 +725,12 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n \t\t\t\tprintf(\"%s%s.git\\n\", cwd, len && cwd[len-1] != '/' ? \"/\" : \"\");\n \t\t\t\tcontinue;\n \t\t\t}\n+\t\t\tif (!strcmp(arg, \"--git-super-dir\")) {\n+\t\t\t\tconst char *gitdir = getenv(GIT_SUPER_DIR_ENVIRONMENT);\n+\t\t\t\tif (gitdir)\n+\t\t\t\t\tputs(gitdir);\n+\t\t\t\tcontinue;\n+\t\t\t}\n \t\t\tif (!strcmp(arg, \"--resolve-git-dir\")) {\n \t\t\t\tconst char *gitdir = resolve_gitdir(argv[i+1]);\n \t\t\t\tif (!gitdir)\ndiff --git a/cache.h b/cache.h\nindex f85ee70..4c09223 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -406,6 +406,7 @@ extern char *get_object_directory(void);\n extern char *get_index_file(void);\n extern char *get_graft_file(void);\n extern int set_git_dir(const char *path);\n+extern int set_git_dir_super(const char *path, const char *super);\n extern const char *get_git_namespace(void);\n extern const char *strip_namespace(const char *namespaced_ref);\n extern const char *get_git_work_tree(void);\ndiff --git a/environment.c b/environment.c\nindex cbfa879..5cbbe11 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -284,6 +284,17 @@ int set_git_dir(const char *path)\n \treturn 0;\n }\n \n+int set_git_dir_super(const char *path, const char *super)\n+{\n+\tif (super && super != path &&\n+\t    setenv(GIT_SUPER_DIR_ENVIRONMENT, super, 1))\n+\t\treturn error(\"Could not set GIT_SUPER_DIR to '%s'\", super);\n+\tif (setenv(GIT_DIR_ENVIRONMENT, path, 1))\n+\t\treturn error(\"Could not set GIT_DIR to '%s'\", path);\n+\tsetup_git_env();\n+\treturn 0;\n+}\n+\n const char *get_log_output_encoding(void)\n {\n \treturn git_log_output_encoding ? git_log_output_encoding\ndiff --git a/setup.c b/setup.c\nindex 397eecc..7ccb1f8 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -243,7 +243,8 @@ int is_inside_work_tree(void)\n \n void setup_work_tree(void)\n {\n-\tconst char *work_tree, *git_dir;\n+\tconst char *work_tree, *git_dir, *git_super_dir;\n+\tchar *super_new = NULL, *super_relative = NULL;\n \tstatic int initialized = 0;\n \n \tif (initialized)\n@@ -252,6 +253,9 @@ void setup_work_tree(void)\n \tgit_dir = get_git_dir();\n \tif (!is_absolute_path(git_dir))\n \t\tgit_dir = real_path(get_git_dir());\n+\tgit_super_dir = get_git_super_dir();\n+\tif (git_super_dir && !is_absolute_path(git_super_dir))\n+\t\tgit_super_dir = super_new = xstrdup(real_path(get_git_super_dir()));\n \tif (!work_tree || chdir(work_tree))\n \t\tdie(\"This operation must be run in a work tree\");\n \n@@ -262,8 +266,16 @@ void setup_work_tree(void)\n \tif (getenv(GIT_WORK_TREE_ENVIRONMENT))\n \t\tsetenv(GIT_WORK_TREE_ENVIRONMENT, \".\", 1);\n \n-\tset_git_dir(remove_leading_path(git_dir, work_tree));\n+\tif (git_super_dir) {\n+\t\tsuper_relative = xstrdup(remove_leading_path(git_super_dir, work_tree));\n+\t\tgit_super_dir = super_relative;\n+\t}\n+\n+\tset_git_dir_super(remove_leading_path(git_dir, work_tree),\n+\t\t\t  git_super_dir);\n \tinitialized = 1;\n+\tfree(super_new);\n+\tfree(super_relative);\n }\n \n static int check_repository_format_gently(const char *gitdir, int *nongit_ok)\n@@ -386,24 +398,30 @@ const char *read_gitfile(const char *path)\n \n \n static const char *setup_explicit_git_dir(const char *gitdirenv,\n+\t\t\t\t\t  const char *super,\n \t\t\t\t\t  char *cwd, int len,\n \t\t\t\t\t  int *nongit_ok)\n {\n \tconst char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);\n \tconst char *worktree;\n-\tchar *gitfile;\n+\tchar *gitfile, *super_new = NULL;\n \tint offset;\n \n \tif (PATH_MAX - 40 < strlen(gitdirenv))\n \t\tdie(\"'$%s' too big\", GIT_DIR_ENVIRONMENT);\n \n-\tgitfile = (char*)read_gitfile(gitdirenv);\n+\tif (super)\n+\t\tgitfile = (char*)read_gitfile(gitdirenv);\n+\telse {\n+\t\tgitfile = (char*)read_gitfile_super(gitdirenv, &super_new);\n+\t\tsuper = super_new;\n+\t}\n \tif (gitfile) {\n \t\tgitfile = xstrdup(gitfile);\n \t\tgitdirenv = gitfile;\n \t}\n \n-\tif (!is_git_directory(gitdirenv)) {\n+\tif (!is_git_directory_super(gitdirenv, super)) {\n \t\tif (nongit_ok) {\n \t\t\t*nongit_ok = 1;\n \t\t\tgoto done_null;\n@@ -411,7 +429,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,\n \t\tdie(\"Not a git repository: '%s'\", gitdirenv);\n \t}\n \n-\tif (check_repository_format_gently(gitdirenv, nongit_ok))\n+\tif (check_repository_format_gently(super ? super : gitdirenv, nongit_ok))\n \t\tgoto done_null;\n \n \t/* #3, #7, #11, #15, #19, #23, #27, #31 (see t1510) */\n@@ -422,10 +440,10 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,\n \t\t\tdie(\"core.bare and core.worktree do not make sense\");\n \n \t\t/* #18, #26 */\n-\t\tset_git_dir(gitdirenv);\n+\t\tset_git_dir_super(gitdirenv, super);\n \t\tgoto done_null;\n \t}\n-\telse if (git_work_tree_cfg) { /* #6, #14 */\n+\telse if (git_work_tree_cfg && !super) { /* #6, #14 */\n \t\tif (is_absolute_path(git_work_tree_cfg))\n \t\t\tset_git_work_tree(git_work_tree_cfg);\n \t\telse {\n@@ -443,7 +461,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,\n \t}\n \telse if (!git_env_bool(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, 1)) {\n \t\t/* #16d */\n-\t\tset_git_dir(gitdirenv);\n+\t\tset_git_dir_super(gitdirenv, super);\n \t\tgoto done_null;\n \t}\n \telse /* #2, #10 */\n@@ -454,43 +472,47 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,\n \n \t/* both get_git_work_tree() and cwd are already normalized */\n \tif (!strcmp(cwd, worktree)) { /* cwd == worktree */\n-\t\tset_git_dir(gitdirenv);\n+\t\tset_git_dir_super(gitdirenv, super);\n \t\tgoto done_null;\n \t}\n \n \toffset = dir_inside_of(cwd, worktree);\n \tif (offset >= 0) {\t/* cwd inside worktree? */\n-\t\tset_git_dir(real_path(gitdirenv));\n+\t\tset_git_dir_super(real_path(gitdirenv), super);\n \t\tif (chdir(worktree))\n \t\t\tdie_errno(\"Could not chdir to '%s'\", worktree);\n \t\tcwd[len++] = '/';\n \t\tcwd[len] = '\\0';\n \t\tfree(gitfile);\n+\t\tfree(super_new);\n \t\treturn cwd + offset;\n \t}\n \n \t/* cwd outside worktree */\n-\tset_git_dir(gitdirenv);\n+\tset_git_dir_super(gitdirenv, super);\n \n done_null:\n \tfree(gitfile);\n+\tfree(super_new);\n \treturn NULL;\n }\n \n static const char *setup_discovered_git_dir(const char *gitdir,\n+\t\t\t\t\t    const char *super,\n \t\t\t\t\t    char *cwd, int offset, int len,\n \t\t\t\t\t    int *nongit_ok)\n {\n-\tif (check_repository_format_gently(gitdir, nongit_ok))\n+\tif (check_repository_format_gently(super ? super : gitdir, nongit_ok))\n \t\treturn NULL;\n \n \t/* --work-tree is set without --git-dir; use discovered one */\n-\tif (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) {\n+\tif (getenv(GIT_WORK_TREE_ENVIRONMENT) ||\n+\t    (git_work_tree_cfg && !super)) {\n \t\tif (offset != len && !is_absolute_path(gitdir))\n \t\t\tgitdir = xstrdup(real_path(gitdir));\n \t\tif (chdir(cwd))\n \t\t\tdie_errno(\"Could not come back to cwd\");\n-\t\treturn setup_explicit_git_dir(gitdir, cwd, len, nongit_ok);\n+\t\treturn setup_explicit_git_dir(gitdir, super, cwd, len, nongit_ok);\n \t}\n \n \t/* #16.2, #17.2, #20.2, #21.2, #24, #25, #28, #29 (see t1510) */\n@@ -503,8 +525,8 @@ static const char *setup_discovered_git_dir(const char *gitdir,\n \n \t/* #0, #1, #5, #8, #9, #12, #13 */\n \tset_git_work_tree(\".\");\n-\tif (strcmp(gitdir, DEFAULT_GIT_DIR_ENVIRONMENT))\n-\t\tset_git_dir(gitdir);\n+\tif (strcmp(gitdir, DEFAULT_GIT_DIR_ENVIRONMENT) || super)\n+\t\tset_git_dir_super(gitdir, super);\n \tinside_git_dir = 0;\n \tinside_work_tree = 1;\n \tif (offset == len)\n@@ -534,7 +556,7 @@ static const char *setup_bare_git_dir(char *cwd, int offset, int len, int *nongi\n \t\tgitdir = offset == len ? \".\" : xmemdupz(cwd, offset);\n \t\tif (chdir(cwd))\n \t\t\tdie_errno(\"Could not come back to cwd\");\n-\t\treturn setup_explicit_git_dir(gitdir, cwd, len, nongit_ok);\n+\t\treturn setup_explicit_git_dir(gitdir, NULL, cwd, len, nongit_ok);\n \t}\n \n \tinside_git_dir = 1;\n@@ -613,8 +635,8 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \tconst char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);\n \tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n \tstatic char cwd[PATH_MAX + 1];\n-\tconst char *gitdirenv, *ret;\n-\tchar *gitfile;\n+\tconst char *gitdirenv, *ret, *super;\n+\tchar *gitfile, *super_new = NULL;\n \tint len, offset, offset_parent, ceil_offset = -1;\n \tdev_t current_device = 0;\n \tint one_filesystem = 1;\n@@ -637,8 +659,10 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \t * validation.\n \t */\n \tgitdirenv = getenv(GIT_DIR_ENVIRONMENT);\n+\tsuper = getenv(GIT_SUPER_DIR_ENVIRONMENT);\n \tif (gitdirenv)\n-\t\treturn setup_explicit_git_dir(gitdirenv, cwd, len, nongit_ok);\n+\t\treturn setup_explicit_git_dir(gitdirenv, super,\n+\t\t\t\t\t      cwd, len, nongit_ok);\n \n \tif (env_ceiling_dirs) {\n \t\tint empty_entry_found = 0;\n@@ -668,25 +692,38 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \tif (one_filesystem)\n \t\tcurrent_device = get_device_or_die(\".\", NULL, 0);\n \tfor (;;) {\n-\t\tgitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);\n+\t\tif (super)\n+\t\t\tgitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);\n+\t\telse {\n+\t\t\tgitfile = (char*)read_gitfile_super(DEFAULT_GIT_DIR_ENVIRONMENT,\n+\t\t\t\t\t\t\t    &super_new);\n+\t\t\tsuper = super_new;\n+\t\t}\n \t\tif (gitfile)\n \t\t\tgitdirenv = gitfile = xstrdup(gitfile);\n \t\telse {\n-\t\t\tif (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))\n+\t\t\tif (is_git_directory_super(DEFAULT_GIT_DIR_ENVIRONMENT, super))\n \t\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n \t\t}\n \n \t\tif (gitdirenv) {\n-\t\t\tret = setup_discovered_git_dir(gitdirenv,\n+\t\t\tret = setup_discovered_git_dir(gitdirenv, super,\n \t\t\t\t\t\t       cwd, offset, len,\n \t\t\t\t\t\t       nongit_ok);\n \t\t\tfree(gitfile);\n+\t\t\tfree(super_new);\n \t\t\treturn ret;\n \t\t}\n \t\tfree(gitfile);\n \n-\t\tif (is_git_directory(\".\"))\n-\t\t\treturn setup_bare_git_dir(cwd, offset, len, nongit_ok);\n+\t\tif (is_git_directory(\".\")) {\n+\t\t\tret = setup_bare_git_dir(cwd, offset, len, nongit_ok);\n+\t\t\tfree(super_new);\n+\t\t\treturn ret;\n+\t\t}\n+\n+\t\tfree(super_new);\n+\t\tsuper_new = NULL;\n \n \t\toffset_parent = offset;\n \t\twhile (--offset_parent > ceil_offset && cwd[offset_parent] != '/');\ndiff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh\nindex 8f36aa9..dcd8d16 100755\n--- a/t/t1501-worktree.sh\n+++ b/t/t1501-worktree.sh\n@@ -346,4 +346,56 @@ test_expect_success 'relative $GIT_WORK_TREE and git subprocesses' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'Split repo setup' '\n+\tmkdir work &&\n+\tmkdir -p repo.git/repos/foo &&\n+\tcp repo.git/HEAD repo.git/index repo.git/repos/foo\n+'\n+\n+test_expect_success 'GIT_SUPER_DIR set' '\n+\tunset GIT_WORK_TREE GIT_DIR GIT_CONFIG &&\n+\tGIT_SUPER_DIR=repo.git GIT_DIR=repo.git/repos/foo git rev-parse --git-super-dir >actual &&\n+\techo repo.git >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'GIT_DIR set (1)' '\n+\t(\n+\t\tcat <<EOF >gitfile &&\n+gitsuper: repo.git\n+gitdir: foo\n+EOF\n+\t\tcd work &&\n+\t\tGIT_DIR=../gitfile git rev-parse --git-super-dir >actual &&\n+\t\techo ../repo.git >expect &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'GIT_DIR set (2)' '\n+\t(\n+\t\tcat <<EOF >gitfile &&\n+gitsuper: $TRASH_DIRECTORY/repo.git\n+gitdir: foo\n+EOF\n+\t\tcd work &&\n+\t\tGIT_DIR=../gitfile git rev-parse --git-super-dir >actual &&\n+\t\techo \"$TRASH_DIRECTORY\"/repo.git >expect &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'Auto discovery' '\n+\t(\n+\t\tcat <<EOF >.git &&\n+gitsuper: repo.git\n+gitdir: foo\n+EOF\n+\t\tcd work &&\n+\t\tgit rev-parse --git-super-dir >actual &&\n+\t\techo repo.git >expect &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n test_done\ndiff --git a/t/t1510-repo-setup.sh b/t/t1510-repo-setup.sh\nindex cf2ee78..713f4d7 100755\n--- a/t/t1510-repo-setup.sh\n+++ b/t/t1510-repo-setup.sh\n@@ -106,6 +106,7 @@ setup_env () {\n expect () {\n \tcat >\"$1/expected\" <<-EOF\n \tsetup: git_dir: $2\n+\tsetup: git_super_dir: (null)\n \tsetup: worktree: $3\n \tsetup: cwd: $4\n \tsetup: prefix: $5\ndiff --git a/trace.c b/trace.c\nindex 3d744d1..53d800b 100644\n--- a/trace.c\n+++ b/trace.c\n@@ -173,6 +173,7 @@ void trace_repo_setup(const char *prefix)\n \t\tprefix = \"(null)\";\n \n \ttrace_printf_key(key, \"setup: git_dir: %s\\n\", quote_crnl(get_git_dir()));\n+\ttrace_printf_key(key, \"setup: git_super_dir: %s\\n\", quote_crnl(get_git_super_dir()));\n \ttrace_printf_key(key, \"setup: worktree: %s\\n\", quote_crnl(git_work_tree));\n \ttrace_printf_key(key, \"setup: cwd: %s\\n\", quote_crnl(cwd));\n \ttrace_printf_key(key, \"setup: prefix: %s\\n\", quote_crnl(prefix));\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232012","messageId":"1387018507-21999-15-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 14/21] checkout: support checking out into a new working directory","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:55:00Z","receivedAt":"2013-12-14T10:55:00Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-checkout.txt         |  6 +++\n Documentation/gitrepository-layout.txt |  3 +-\n builtin/checkout.c                     | 94 ++++++++++++++++++++++++++++++++++\n path.c                                 |  3 +-\n 4 files changed, 104 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex 91294f8..06076c8 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -225,6 +225,12 @@ This means that you can use `git checkout -p` to selectively discard\n edits from your current working tree. See the ``Interactive Mode''\n section of linkgit:git-add[1] to learn how to operate the `--patch` mode.\n \n+--to=<path>::\n+\tCheck out a new branch in a separate working directory at\n+\t`<path>`. A new repository is initialized at `<path>` that\n+\tshares everything with the current repository except working\n+\tdirectory specific files.\n+\n <branch>::\n \tBranch to checkout; if it refers to a branch (i.e., a name that,\n \twhen prepended with \"refs/heads/\", is a valid ref), then that\ndiff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt\nindex 7ce31d4..3c6149e 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -222,7 +222,8 @@ repos/<id>::\n \tIf a repository's .git is a file contains two lines `gitsuper:\n \t<path>` and `repo: <id>`. The directory `<path>/repos/<id>`\n \tcontains the real non-shared part of .git directory of the\n-\trepository in question (e.g. HEAD or index).\n+\trepository in question (e.g. HEAD or index). Such .git files\n+\tare created by `git checkout --to`.\n \n SEE ALSO\n --------\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 904fd71..95a1a61 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -48,6 +48,9 @@ struct checkout_opts {\n \tconst char *prefix;\n \tstruct pathspec pathspec;\n \tstruct tree *source_tree;\n+\n+\tconst char *new_worktree;\n+\tconst char **saved_argv;\n };\n \n static int post_checkout_hook(struct commit *old, struct commit *new,\n@@ -250,6 +253,9 @@ static int checkout_paths(const struct checkout_opts *opts,\n \t\tdie(_(\"Cannot update paths and switch to branch '%s' at the same time.\"),\n \t\t    opts->new_branch);\n \n+\tif (opts->new_worktree)\n+\t\tdie(_(\"'%s' cannot be used with updating paths\"), \"--to\");\n+\n \tif (opts->patch_mode)\n \t\treturn run_add_interactive(revision, \"--patch=checkout\",\n \t\t\t\t\t   &opts->pathspec);\n@@ -808,6 +814,82 @@ static int switch_branches(const struct checkout_opts *opts,\n \treturn ret || writeout_error;\n }\n \n+static void write_to_file(const char *path, const char *fmt, ...)\n+{\n+\tva_list params;\n+\tFILE *fp = fopen(path, \"w\");\n+\tif (!fp)\n+\t\tdie_errno(_(\"could not create file '%s'\"), path);\n+\tva_start(params, fmt);\n+\tvfprintf(fp, fmt, params);\n+\tva_end(params);\n+\tfclose(fp);\n+}\n+\n+static int checkout_new_worktree(const struct checkout_opts *opts,\n+\t\t\t\t struct branch_info *new)\n+{\n+\tstruct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tconst char *path = opts->new_worktree;\n+\tstruct stat st;\n+\tconst char *name;\n+\tstruct child_process cp;\n+\tint counter = 0, len;\n+\n+\tif (!new->commit)\n+\t\tdie(_(\"no branch specified\"));\n+\n+\tlen = strlen(path);\n+\tif (!len || is_dir_sep(path[len - 1]))\n+\t\tdie(_(\"'--to' argument '%s' cannot end with a slash\"), path);\n+\n+\tfor (name = path + len - 1; name > path; name--)\n+\t\tif (is_dir_sep(*name)) {\n+\t\t\tname++;\n+\t\t\tbreak;\n+\t\t}\n+\tstrbuf_addstr(&sb_repo, git_path(\"repos/%s\", name));\n+\tlen = sb_repo.len;\n+\tif (safe_create_leading_directories_const(sb_repo.buf))\n+\t\tdie_errno(_(\"could not create leading directories of '%s'\"),\n+\t\t\t  sb_repo.buf);\n+\twhile (!stat(sb_repo.buf, &st)) {\n+\t\tcounter++;\n+\t\tstrbuf_setlen(&sb_repo, len);\n+\t\tstrbuf_addf(&sb_repo, \"%d\", counter);\n+\t}\n+\tname = sb_repo.buf + len - strlen(name);\n+\tif (mkdir(sb_repo.buf, 0777))\n+\t\tdie_errno(_(\"could not create directory of '%s'\"), sb_repo.buf);\n+\n+\tstrbuf_addf(&sb_git, \"%s/.git\", path);\n+\tif (safe_create_leading_directories_const(sb_git.buf))\n+\t\tdie_errno(_(\"could not create leading directories of '%s'\"),\n+\t\t\t  sb_git.buf);\n+\n+\twrite_to_file(sb_git.buf, \"gitsuper: %s\\ngitdir: %s\\n\",\n+\t\t      real_path(get_git_dir()), name);\n+\tstrbuf_addf(&sb, \"%s/HEAD\", sb_repo.buf);\n+\twrite_to_file(sb.buf, \"%s\\n\", sha1_to_hex(new->commit->object.sha1));\n+\n+\tif (!opts->quiet)\n+\t\tfprintf_ln(stderr, _(\"Enter %s (identifier %s)\"), path, name);\n+\n+\t/*\n+\t * Rerun checkout in the new worktree. This way is safer than\n+\t * set_git_dir() because a path relative to cwd could have\n+\t * been cached somewhere undetected.\n+\t */\n+\tsetenv(\"GIT_CHECKOUT_NEW_WORKTREE\", \"1\", 1);\n+\tsetenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1);\n+\tsetenv(GIT_WORK_TREE_ENVIRONMENT, path, 1);\n+\tmemset(&cp, 0, sizeof(cp));\n+\tcp.git_cmd = 1;\n+\tcp.argv = opts->saved_argv;\n+\treturn run_command(&cp);\n+}\n+\n static int git_checkout_config(const char *var, const char *value, void *cb)\n {\n \tif (!strcmp(var, \"diff.ignoresubmodules\")) {\n@@ -1069,6 +1151,9 @@ static int checkout_branch(struct checkout_opts *opts,\n \t\tdie(_(\"Cannot switch branch to a non-commit '%s'\"),\n \t\t    new->name);\n \n+\tif (opts->new_worktree)\n+\t\treturn checkout_new_worktree(opts, new);\n+\n \tif (!new->commit && opts->new_branch) {\n \t\tunsigned char rev[20];\n \t\tint flag;\n@@ -1111,6 +1196,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\t\t N_(\"do not limit pathspecs to sparse entries only\")),\n \t\tOPT_HIDDEN_BOOL(0, \"guess\", &dwim_new_local_branch,\n \t\t\t\tN_(\"second guess 'git checkout no-such-branch'\")),\n+\t\tOPT_STRING(0, \"to\", &opts.new_worktree, N_(\"path\"),\n+\t\t\t   N_(\"check a branch out in a separate working directory\")),\n \t\tOPT_END(),\n \t};\n \n@@ -1119,6 +1206,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \topts.overwrite_ignore = 1;\n \topts.prefix = prefix;\n \n+\topts.saved_argv = xmalloc(sizeof(const char *) * (argc + 2));\n+\tmemcpy(opts.saved_argv, argv, sizeof(const char *) * (argc + 1));\n+\n \tgitmodules_config();\n \tgit_config(git_checkout_config, &opts);\n \n@@ -1127,6 +1217,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, options, checkout_usage,\n \t\t\t     PARSE_OPT_KEEP_DASHDASH);\n \n+\tif (getenv(\"GIT_CHECKOUT_NEW_WORKTREE\"))\n+\t\t/* recursive execution from checkout_new_worktree() */\n+\t\topts.new_worktree = NULL;\n+\n \tif (conflict_style) {\n \t\topts.merge = 1; /* implied */\n \t\tgit_xmerge_config(\"merge.conflictstyle\", conflict_style, NULL);\ndiff --git a/path.c b/path.c\nindex e51fc35..1a2478a 100644\n--- a/path.c\n+++ b/path.c\n@@ -93,7 +93,8 @@ static void update_super_dir(char *buf, size_t n, int git_dir_len)\n {\n \tconst char *super_dir_list[] = {\n \t\t\"branches\", \"hooks\", \"info\", \"logs\", \"modules\",\n-\t\t\"objects\", \"refs\", \"remotes\", \"rr-cache\", \"svn\", NULL\n+\t\t\"objects\", \"refs\", \"remotes\", \"repos\", \"rr-cache\",\n+\t\t\"svn\", NULL\n \t};\n \tconst char *super_top_file_list[] = {\n \t\t\"config\", \"packed-refs\", \"shallow\", NULL\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232013","messageId":"1387018507-21999-16-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 15/21] checkout: clean up half-prepared directories in --to mode","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:55:01Z","receivedAt":"2013-12-14T10:55:01Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/checkout.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 47 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 95a1a61..6353557 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -20,6 +20,7 @@\n #include \"resolve-undo.h\"\n #include \"submodule.h\"\n #include \"argv-array.h\"\n+#include \"sigchain.h\"\n \n static const char * const checkout_usage[] = {\n \tN_(\"git checkout [options] <branch>\"),\n@@ -826,6 +827,35 @@ static void write_to_file(const char *path, const char *fmt, ...)\n \tfclose(fp);\n }\n \n+static const char *junk_work_tree;\n+static const char *junk_git_dir;\n+static int is_junk;\n+static pid_t junk_pid;\n+\n+static void remove_junk(void)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tif (!is_junk || getpid() != junk_pid)\n+\t\treturn;\n+\tif (junk_git_dir) {\n+\t\tstrbuf_addstr(&sb, junk_git_dir);\n+\t\tremove_dir_recursively(&sb, 0);\n+\t\tstrbuf_reset(&sb);\n+\t}\n+\tif (junk_work_tree) {\n+\t\tstrbuf_addstr(&sb, junk_work_tree);\n+\t\tremove_dir_recursively(&sb, 0);\n+\t\tstrbuf_reset(&sb);\n+\t}\n+}\n+\n+static void remove_junk_on_signal(int signo)\n+{\n+\tremove_junk();\n+\tsigchain_pop(signo);\n+\traise(signo);\n+}\n+\n static int checkout_new_worktree(const struct checkout_opts *opts,\n \t\t\t\t struct branch_info *new)\n {\n@@ -835,7 +865,7 @@ static int checkout_new_worktree(const struct checkout_opts *opts,\n \tstruct stat st;\n \tconst char *name;\n \tstruct child_process cp;\n-\tint counter = 0, len;\n+\tint counter = 0, len, ret;\n \n \tif (!new->commit)\n \t\tdie(_(\"no branch specified\"));\n@@ -860,13 +890,21 @@ static int checkout_new_worktree(const struct checkout_opts *opts,\n \t\tstrbuf_addf(&sb_repo, \"%d\", counter);\n \t}\n \tname = sb_repo.buf + len - strlen(name);\n+\n+\tjunk_pid = getpid();\n+\tatexit(remove_junk);\n+\tsigchain_push_common(remove_junk_on_signal);\n+\n \tif (mkdir(sb_repo.buf, 0777))\n \t\tdie_errno(_(\"could not create directory of '%s'\"), sb_repo.buf);\n+\tjunk_git_dir = sb_repo.buf;\n+\tis_junk = 1;\n \n \tstrbuf_addf(&sb_git, \"%s/.git\", path);\n \tif (safe_create_leading_directories_const(sb_git.buf))\n \t\tdie_errno(_(\"could not create leading directories of '%s'\"),\n \t\t\t  sb_git.buf);\n+\tjunk_work_tree = path;\n \n \twrite_to_file(sb_git.buf, \"gitsuper: %s\\ngitdir: %s\\n\",\n \t\t      real_path(get_git_dir()), name);\n@@ -887,7 +925,14 @@ static int checkout_new_worktree(const struct checkout_opts *opts,\n \tmemset(&cp, 0, sizeof(cp));\n \tcp.git_cmd = 1;\n \tcp.argv = opts->saved_argv;\n-\treturn run_command(&cp);\n+\tret = run_command(&cp);\n+\tif (!ret)\n+\t\tis_junk = 0;\n+\tstrbuf_release(&sb);\n+\tstrbuf_release(&sb_repo);\n+\tstrbuf_release(&sb_git);\n+\treturn ret;\n+\n }\n \n static int git_checkout_config(const char *var, const char *value, void *cb)\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232014","messageId":"1387018507-21999-17-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 16/21] setup.c: keep track of the .git file location if read","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:55:02Z","receivedAt":"2013-12-14T10:55:02Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h       |  2 +-\n environment.c |  2 +-\n setup.c       | 44 ++++++++++++++++++++++++++++++--------------\n 3 files changed, 32 insertions(+), 16 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 4c09223..e036a7d 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -411,7 +411,7 @@ extern const char *get_git_namespace(void);\n extern const char *strip_namespace(const char *namespaced_ref);\n extern const char *get_git_work_tree(void);\n extern const char *read_gitfile(const char *path);\n-extern const char *read_gitfile_super(const char *path, char **super);\n+extern const char *read_gitfile_super(const char *path, char **super, char **gitfile);\n extern const char *resolve_gitdir(const char *suspect);\n extern void set_git_work_tree(const char *tree);\n \ndiff --git a/environment.c b/environment.c\nindex 5cbbe11..61aff23 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -130,7 +130,7 @@ static void setup_git_env(void)\n \tgit_dir = getenv(GIT_DIR_ENVIRONMENT);\n \tif (!git_dir)\n \t\tgit_dir = DEFAULT_GIT_DIR_ENVIRONMENT;\n-\tgitfile = read_gitfile_super(git_dir, &super);\n+\tgitfile = read_gitfile_super(git_dir, &super, NULL);\n \tgit_dir = xstrdup(gitfile ? gitfile : git_dir);\n \tif (super)\n \t\tgit_super_dir = xstrdup(super);\ndiff --git a/setup.c b/setup.c\nindex 7ccb1f8..ac7d90c 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -332,7 +332,7 @@ static char *path_from_gitfile(const char *path, const char *buf, int len)\n  * to the absolute path of the given path. The next line contains the\n  * repo id.\n  */\n-const char *read_gitfile_super(const char *path, char **super)\n+const char *read_gitfile_super(const char *path, char **super, char **gitfile)\n {\n \tchar *buf, *dir;\n \tstruct stat st;\n@@ -384,6 +384,10 @@ const char *read_gitfile_super(const char *path, char **super)\n \n \tif (!is_git_directory_super(dir, super ? *super : NULL))\n \t\tdie(\"Not a git repository: %s\", dir);\n+\n+\tif (gitfile)\n+\t\t*gitfile = xstrdup(real_path(path));\n+\n \tpath = real_path(dir);\n \n \tfree(dir);\n@@ -393,14 +397,15 @@ const char *read_gitfile_super(const char *path, char **super)\n \n const char *read_gitfile(const char *path)\n {\n-\treturn read_gitfile_super(path, NULL);\n+\treturn read_gitfile_super(path, NULL, NULL);\n }\n \n \n static const char *setup_explicit_git_dir(const char *gitdirenv,\n \t\t\t\t\t  const char *super,\n \t\t\t\t\t  char *cwd, int len,\n-\t\t\t\t\t  int *nongit_ok)\n+\t\t\t\t\t  int *nongit_ok,\n+\t\t\t\t\t  char **gitfile_path)\n {\n \tconst char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);\n \tconst char *worktree;\n@@ -413,7 +418,8 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,\n \tif (super)\n \t\tgitfile = (char*)read_gitfile(gitdirenv);\n \telse {\n-\t\tgitfile = (char*)read_gitfile_super(gitdirenv, &super_new);\n+\t\tgitfile = (char*)read_gitfile_super(gitdirenv, &super_new,\n+\t\t\t\t\t\t    gitfile_path);\n \t\tsuper = super_new;\n \t}\n \tif (gitfile) {\n@@ -500,7 +506,8 @@ done_null:\n static const char *setup_discovered_git_dir(const char *gitdir,\n \t\t\t\t\t    const char *super,\n \t\t\t\t\t    char *cwd, int offset, int len,\n-\t\t\t\t\t    int *nongit_ok)\n+\t\t\t\t\t    int *nongit_ok,\n+\t\t\t\t\t    char **gitfile_path)\n {\n \tif (check_repository_format_gently(super ? super : gitdir, nongit_ok))\n \t\treturn NULL;\n@@ -512,7 +519,8 @@ static const char *setup_discovered_git_dir(const char *gitdir,\n \t\t\tgitdir = xstrdup(real_path(gitdir));\n \t\tif (chdir(cwd))\n \t\t\tdie_errno(\"Could not come back to cwd\");\n-\t\treturn setup_explicit_git_dir(gitdir, super, cwd, len, nongit_ok);\n+\t\treturn setup_explicit_git_dir(gitdir, super, cwd, len,\n+\t\t\t\t\t      nongit_ok, gitfile_path);\n \t}\n \n \t/* #16.2, #17.2, #20.2, #21.2, #24, #25, #28, #29 (see t1510) */\n@@ -540,7 +548,8 @@ static const char *setup_discovered_git_dir(const char *gitdir,\n }\n \n /* #16.1, #17.1, #20.1, #21.1, #22.1 (see t1510) */\n-static const char *setup_bare_git_dir(char *cwd, int offset, int len, int *nongit_ok)\n+static const char *setup_bare_git_dir(char *cwd, int offset, int len,\n+\t\t\t\t      int *nongit_ok, char **gitfile_path)\n {\n \tint root_len;\n \n@@ -556,7 +565,8 @@ static const char *setup_bare_git_dir(char *cwd, int offset, int len, int *nongi\n \t\tgitdir = offset == len ? \".\" : xmemdupz(cwd, offset);\n \t\tif (chdir(cwd))\n \t\t\tdie_errno(\"Could not come back to cwd\");\n-\t\treturn setup_explicit_git_dir(gitdir, NULL, cwd, len, nongit_ok);\n+\t\treturn setup_explicit_git_dir(gitdir, NULL, cwd, len,\n+\t\t\t\t\t      nongit_ok, gitfile_path);\n \t}\n \n \tinside_git_dir = 1;\n@@ -630,7 +640,8 @@ static int canonicalize_ceiling_entry(struct string_list_item *item,\n  * We cannot decide in this function whether we are in the work tree or\n  * not, since the config can only be read _after_ this function was called.\n  */\n-static const char *setup_git_directory_gently_1(int *nongit_ok)\n+static const char *setup_git_directory_gently_1(int *nongit_ok,\n+\t\t\t\t\t\tchar **gitfile_path)\n {\n \tconst char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);\n \tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n@@ -662,7 +673,8 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \tsuper = getenv(GIT_SUPER_DIR_ENVIRONMENT);\n \tif (gitdirenv)\n \t\treturn setup_explicit_git_dir(gitdirenv, super,\n-\t\t\t\t\t      cwd, len, nongit_ok);\n+\t\t\t\t\t      cwd, len, nongit_ok,\n+\t\t\t\t\t      gitfile_path);\n \n \tif (env_ceiling_dirs) {\n \t\tint empty_entry_found = 0;\n@@ -696,7 +708,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \t\t\tgitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);\n \t\telse {\n \t\t\tgitfile = (char*)read_gitfile_super(DEFAULT_GIT_DIR_ENVIRONMENT,\n-\t\t\t\t\t\t\t    &super_new);\n+\t\t\t\t\t\t\t    &super_new, gitfile_path);\n \t\t\tsuper = super_new;\n \t\t}\n \t\tif (gitfile)\n@@ -709,7 +721,8 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \t\tif (gitdirenv) {\n \t\t\tret = setup_discovered_git_dir(gitdirenv, super,\n \t\t\t\t\t\t       cwd, offset, len,\n-\t\t\t\t\t\t       nongit_ok);\n+\t\t\t\t\t\t       nongit_ok,\n+\t\t\t\t\t\t       gitfile_path);\n \t\t\tfree(gitfile);\n \t\t\tfree(super_new);\n \t\t\treturn ret;\n@@ -717,7 +730,8 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \t\tfree(gitfile);\n \n \t\tif (is_git_directory(\".\")) {\n-\t\t\tret = setup_bare_git_dir(cwd, offset, len, nongit_ok);\n+\t\t\tret = setup_bare_git_dir(cwd, offset, len,\n+\t\t\t\t\t\t nongit_ok, gitfile_path);\n \t\t\tfree(super_new);\n \t\t\treturn ret;\n \t\t}\n@@ -754,8 +768,9 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n const char *setup_git_directory_gently(int *nongit_ok)\n {\n \tconst char *prefix;\n+\tchar *gitfile = NULL;\n \n-\tprefix = setup_git_directory_gently_1(nongit_ok);\n+\tprefix = setup_git_directory_gently_1(nongit_ok, &gitfile);\n \tif (prefix)\n \t\tsetenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);\n \telse\n@@ -765,6 +780,7 @@ const char *setup_git_directory_gently(int *nongit_ok)\n \t\tstartup_info->have_repository = !nongit_ok || !*nongit_ok;\n \t\tstartup_info->prefix = prefix;\n \t}\n+\tfree(gitfile);\n \treturn prefix;\n }\n \n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232015","messageId":"1387018507-21999-18-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 17/21] prune: strategies for split repositories","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:55:03Z","receivedAt":"2013-12-14T10:55:03Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"alias REPO=$GIT_SUPER_DIR/repos/<id>\n\n - split repos are supposed to update mtime of $REPO/gitdir\n\n - split repos are supposed to keep its location in $REPO/gitdir up to\n   date\n\n - \"git checkout --to\" is supposed to create $REPO/locked if the new\n   repo is on a different partition than the shared one.\n\n - \"git checkout --to\" is supposed to make hardlink named $REPO/link\n   pointing to the .git file\n\nThe pruning rules are:\n\n - if $REPO/locked exists, repos/<id> is not supposed to be pruned.\n\n - if $REPO/locked exists and $REPO/gitdir's mtimer is older than a\n   really long limit, warn about old unused repo.\n\n - if $REPO/link exists and its link count is 1, repos/<id> is to be\n   pruned.\n\n - if $REPO/gitdir's mtime is older than a limit, and it points to\n   nowhere, repos/<id> is to be pruned. Warn about it some time before\n   the prune time.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-prune.txt            |  4 +++\n Documentation/gitrepository-layout.txt | 19 ++++++++++\n builtin/checkout.c                     | 36 ++++++++++++++++++-\n builtin/prune.c                        | 65 ++++++++++++++++++++++++++++++++++\n setup.c                                | 13 +++++++\n 5 files changed, 136 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt\nindex bf82410..55a9341 100644\n--- a/Documentation/git-prune.txt\n+++ b/Documentation/git-prune.txt\n@@ -46,6 +46,10 @@ OPTIONS\n --expire <time>::\n \tOnly expire loose objects older than <time>.\n \n+--repos::\n+\tPrune directories in $GIT_DIR/repos other than `<time>` given\n+\tby `--expire` (or default 6 months)\n+\n <head>...::\n \tIn addition to objects\n \treachable from any of our references, keep objects\ndiff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt\nindex 3c6149e..a8a0426 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -225,6 +225,25 @@ repos/<id>::\n \trepository in question (e.g. HEAD or index). Such .git files\n \tare created by `git checkout --to`.\n \n+repos/<id>/gitdir::\n+\tA text file containing the absolute path back to the .git file\n+\tthat points to here. This is used to check if the linked\n+\trepository has been manually removed and there is no need to\n+\tkeep this directory any more. mtime of this file should be\n+\tupdated every time the linked repository is accessed.\n+\n+repos/<id>/locked::\n+\tIf this file exists, the linked repository may be on a\n+\tportable device and not available. It does not mean that the\n+\tlinked repository is gone and `repos/<id>` could be\n+\tremoved. The file's content contains a reason string on why\n+\tthe repository is locked.\n+\n+repos/<id>/link::\n+\tIf this file exists, it is a hard link to the linked .git\n+\tfile. It is used to detect if the linked repository is\n+\tmanually removed.\n+\n SEE ALSO\n --------\n linkgit:git-init[1],\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 6353557..aece20d 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -856,6 +856,17 @@ static void remove_junk_on_signal(int signo)\n \traise(signo);\n }\n \n+static dev_t get_device_or_die(const char *path)\n+{\n+\tstruct stat buf;\n+\tif (stat(path, &buf))\n+\t\tdie_errno(\"failed to stat '%s'\", path);\n+\t/* Ah Windows! Make different drives different \"partitions\" */\n+\tif (buf.st_dev == 0 && has_dos_drive_prefix(\"c:\\\\\"))\n+\t\tbuf.st_dev = toupper(real_path(path)[0]);\n+\treturn buf.st_dev;\n+}\n+\n static int checkout_new_worktree(const struct checkout_opts *opts,\n \t\t\t\t struct branch_info *new)\n {\n@@ -865,7 +876,7 @@ static int checkout_new_worktree(const struct checkout_opts *opts,\n \tstruct stat st;\n \tconst char *name;\n \tstruct child_process cp;\n-\tint counter = 0, len, ret;\n+\tint counter = 0, len, keep_locked = 0, ret;\n \n \tif (!new->commit)\n \t\tdie(_(\"no branch specified\"));\n@@ -900,17 +911,35 @@ static int checkout_new_worktree(const struct checkout_opts *opts,\n \tjunk_git_dir = sb_repo.buf;\n \tis_junk = 1;\n \n+\tstrbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n+\twrite_to_file(sb.buf, \"initializing\\n\");\n+\n \tstrbuf_addf(&sb_git, \"%s/.git\", path);\n \tif (safe_create_leading_directories_const(sb_git.buf))\n \t\tdie_errno(_(\"could not create leading directories of '%s'\"),\n \t\t\t  sb_git.buf);\n \tjunk_work_tree = path;\n \n+\tstrbuf_reset(&sb);\n+\tstrbuf_addf(&sb, \"%s/gitdir\", sb_repo.buf);\n+\twrite_to_file(sb.buf, \"%s\\n\", real_path(sb_git.buf));\n \twrite_to_file(sb_git.buf, \"gitsuper: %s\\ngitdir: %s\\n\",\n \t\t      real_path(get_git_dir()), name);\n+\tstrbuf_reset(&sb);\n \tstrbuf_addf(&sb, \"%s/HEAD\", sb_repo.buf);\n \twrite_to_file(sb.buf, \"%s\\n\", sha1_to_hex(new->commit->object.sha1));\n \n+\tif (get_device_or_die(path) != get_device_or_die(get_git_dir())) {\n+\t\tstrbuf_reset(&sb);\n+\t\tstrbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n+\t\twrite_to_file(sb.buf, \"located on a different file system\\n\");\n+\t\tkeep_locked = 1;\n+\t} else {\n+\t\tstrbuf_reset(&sb);\n+\t\tstrbuf_addf(&sb, \"%s/link\", sb_repo.buf);\n+\t\tlink(sb_git.buf, sb.buf); /* it's ok to fail */\n+\t}\n+\n \tif (!opts->quiet)\n \t\tfprintf_ln(stderr, _(\"Enter %s (identifier %s)\"), path, name);\n \n@@ -928,6 +957,11 @@ static int checkout_new_worktree(const struct checkout_opts *opts,\n \tret = run_command(&cp);\n \tif (!ret)\n \t\tis_junk = 0;\n+\tif (!keep_locked) {\n+\t\tstrbuf_reset(&sb);\n+\t\tstrbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n+\t\tunlink_or_warn(sb.buf);\n+\t}\n \tstrbuf_release(&sb);\n \tstrbuf_release(&sb_repo);\n \tstrbuf_release(&sb_git);\ndiff --git a/builtin/prune.c b/builtin/prune.c\nindex 6366917..16eb99c 100644\n--- a/builtin/prune.c\n+++ b/builtin/prune.c\n@@ -102,6 +102,62 @@ static void prune_object_dir(const char *path)\n \t}\n }\n \n+static const char *prune_repo_dir(const char *id)\n+{\n+\tstruct stat st;\n+\tchar *path;\n+\tint fd, len;\n+\tif (file_exists(git_path(\"repos/%s/locked\", id)))\n+\t\treturn NULL;\n+\tif (!stat(git_path(\"repos/%s/link\", id), &st) && st.st_nlink == 1)\n+\t\treturn _(\"hard link count is 1\");\n+\tif (stat(git_path(\"repos/%s/gitdir\", id), &st))\n+\t\treturn \"gitdir does not exist\";\n+\tif (st.st_mtime > expire)\n+\t\treturn NULL;\n+\tfd = open(git_path(\"repos/%s/gitdir\", id), O_RDONLY);\n+\tlen = st.st_size;\n+\tpath = xmalloc(len + 1);\n+\tread_in_full(fd, path, len);\n+\tclose(fd);\n+\twhile (path[len - 1] == '\\n' || path[len - 1] == '\\r')\n+\t\tlen--;\n+\tpath[len] = '\\0';\n+\tif (!file_exists(path)) {\n+\t\tfree(path);\n+\t\treturn \"gitdir points to non-existing file\";\n+\t}\n+\tfree(path);\n+\treturn NULL;\n+}\n+\n+static void prune_repos_dir(void)\n+{\n+\tconst char *reason;\n+\tDIR *dir = opendir(git_path(\"repos\"));\n+\tstruct dirent *d;\n+\tint removed = 0;\n+\tif (!dir)\n+\t\treturn;\n+\twhile ((d = readdir(dir)) != NULL) {\n+\t\tif (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n+\t\t\tcontinue;\n+\t\tif ((reason = prune_repo_dir(d->d_name)) != NULL) {\n+\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\t\tprintf(_(\"Removing repos/%s: %s\\n\"), d->d_name, reason);\n+\t\t\tif (show_only)\n+\t\t\t\tcontinue;\n+\t\t\tstrbuf_addstr(&sb, git_path(\"repos/%s\", d->d_name));\n+\t\t\tremove_dir_recursively(&sb, 0);\n+\t\t\tstrbuf_release(&sb);\n+\t\t\tremoved = 1;\n+\t\t}\n+\t}\n+\tclosedir(dir);\n+\tif (removed)\n+\t\trmdir(git_path(\"repos\"));\n+}\n+\n /*\n  * Write errors (particularly out of space) can result in\n  * failed temporary packs (and more rarely indexes and other\n@@ -128,10 +184,12 @@ int cmd_prune(int argc, const char **argv, const char *prefix)\n {\n \tstruct rev_info revs;\n \tstruct progress *progress = NULL;\n+\tint prune_repos = 0;\n \tconst struct option options[] = {\n \t\tOPT__DRY_RUN(&show_only, N_(\"do not remove, show only\")),\n \t\tOPT__VERBOSE(&verbose, N_(\"report pruned objects\")),\n \t\tOPT_BOOL(0, \"progress\", &show_progress, N_(\"show progress\")),\n+\t\tOPT_BOOL(0, \"repos\", &prune_repos, N_(\"prune .git/repos/\")),\n \t\tOPT_EXPIRY_DATE(0, \"expire\", &expire,\n \t\t\t\tN_(\"expire objects older than <time>\")),\n \t\tOPT_END()\n@@ -156,6 +214,13 @@ int cmd_prune(int argc, const char **argv, const char *prefix)\n \t\t\tdie(\"unrecognized argument: %s\", name);\n \t}\n \n+\tif (prune_repos) {\n+\t\tif (expire == ULONG_MAX)\n+\t\t\texpire = time(NULL) - 6 * 30 * 24 * 3600; /* 6 months */\n+\t\tprune_repos_dir();\n+\t\treturn 0;\n+\t}\n+\n \tif (show_progress == -1)\n \t\tshow_progress = isatty(2);\n \tif (show_progress)\ndiff --git a/setup.c b/setup.c\nindex ac7d90c..95a3f40 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -765,6 +765,17 @@ static const char *setup_git_directory_gently_1(int *nongit_ok,\n \t}\n }\n \n+static int update_super_gitdir(char *path)\n+{\n+\tFILE *fp = fopen(git_path(\"gitdir\"), \"w\");\n+\tif (!fp)\n+\t\treturn error(_(\"unable to update %s: %s\"),\n+\t\t\t     git_path(\"gitdir\"), strerror(errno));\n+\tfprintf(stderr, \"%s\\n\", path);\n+\tfclose(fp);\n+\treturn 0;\n+}\n+\n const char *setup_git_directory_gently(int *nongit_ok)\n {\n \tconst char *prefix;\n@@ -780,6 +791,8 @@ const char *setup_git_directory_gently(int *nongit_ok)\n \t\tstartup_info->have_repository = !nongit_ok || !*nongit_ok;\n \t\tstartup_info->prefix = prefix;\n \t}\n+\tif (get_git_super_dir() && gitfile)\n+\t\tupdate_super_gitdir(gitfile);\n \tfree(gitfile);\n \treturn prefix;\n }\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232016","messageId":"1387018507-21999-19-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 18/21] refs: adjust reflog path for repos/<id>/HEAD","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:55:04Z","receivedAt":"2013-12-14T10:55:04Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n refs.c | 15 +++++++++++++--\n 1 file changed, 13 insertions(+), 2 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 5e5a382..3691ef5 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2676,13 +2676,24 @@ static int copy_msg(char *buf, const char *msg)\n int log_ref_setup(const char *refname, char *logfile, int bufsize)\n {\n \tint logfd, oflags = O_APPEND | O_WRONLY;\n+\tconst char *p = NULL;\n \n-\tgit_snpath(logfile, bufsize, \"logs/%s\", refname);\n+\t/*\n+\t * reflog of repos/XXX/HEAD is repos/XXX/logs/HEAD, not\n+\t * refs/repos/XXX/HEAD\n+\t */\n+\tif (!strncmp(refname, \"repos/\", 6) &&\n+\t    (p = strchr(refname + 6, '/')) != 0)\n+\t\tgit_snpath(logfile, bufsize, \"%.*s/logs/%s\",\n+\t\t\t   (int)(p - refname), refname, p + 1);\n+\telse\n+\t\tgit_snpath(logfile, bufsize, \"logs/%s\", refname);\n \tif (log_all_ref_updates &&\n \t    (!prefixcmp(refname, \"refs/heads/\") ||\n \t     !prefixcmp(refname, \"refs/remotes/\") ||\n \t     !prefixcmp(refname, \"refs/notes/\") ||\n-\t     !strcmp(refname, \"HEAD\"))) {\n+\t     !strcmp(refname, \"HEAD\") ||\n+\t     (p && !strcmp(p + 1, \"HEAD\")))) {\n \t\tif (safe_create_leading_directories(logfile) < 0)\n \t\t\treturn error(\"unable to create directory for %s\",\n \t\t\t\t     logfile);\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232017","messageId":"1387018507-21999-20-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 19/21] refs: detach split repos' HEAD when the linked ref is updated/deleted","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:55:05Z","receivedAt":"2013-12-14T10:55:05Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is effective when a ref is updated from the super repository as\nwell as all linked repositories because \"repos\" directory is shared\nbetween all repos.\n\nWe could even forbid a ref update if it's some repo's HEAD. But I'd\nrather see a generic, permanent ref locking mechanism in place first\nand make use of it.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n refs.c | 37 +++++++++++++++++++++++++++++++++++++\n 1 file changed, 37 insertions(+)\n\ndiff --git a/refs.c b/refs.c\nindex 3691ef5..d856b1a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2462,6 +2462,41 @@ static int repack_without_ref(const char *refname)\n \treturn repack_without_refs(&refname, 1);\n }\n \n+static void detach_repos_refs(const char *refname, const unsigned char *sha1)\n+{\n+\tstruct dirent *d;\n+\tDIR *dir;\n+\n+\tif (!strncmp(refname, \"repos/\", 6) ||\n+\t    !strchr(refname, '/') ||\n+\t    (dir = opendir(git_path(\"repos\"))) == NULL)\n+\t\treturn;\n+\n+\twhile ((d = readdir(dir)) != NULL) {\n+\t\tstruct strbuf sb_ref = STRBUF_INIT;\n+\t\tunsigned char ref_sha1[20];\n+\t\tconst char *ref;\n+\t\tint flags;\n+\n+\t\tif (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n+\t\t\tcontinue;\n+\t\tstrbuf_addf(&sb_ref, \"repos/%s/HEAD\", d->d_name);\n+\t\tref = resolve_ref_unsafe(sb_ref.buf, ref_sha1, 1, &flags);\n+\t\tif (flags & REF_ISSYMREF && ref && !strcmp(ref, refname)) {\n+\t\t\tstruct strbuf msg = STRBUF_INIT;\n+\t\t\tstrbuf_addf(&msg, \"repos: detach from %s by %s\",\n+\t\t\t\t    ref, real_path(get_git_dir()));\n+\t\t\tupdate_ref(msg.buf, sb_ref.buf, sha1, NULL,\n+\t\t\t\t   REF_NODEREF, DIE_ON_ERR);\n+\t\t\tstrbuf_release(&msg);\n+\t\t\twarning(_(\"detach HEAD of linked repository %s from %s\"),\n+\t\t\t\td->d_name, refname);\n+\t\t}\n+\t\tstrbuf_release(&sb_ref);\n+\t}\n+\tclosedir(dir);\n+}\n+\n static int delete_ref_loose(struct ref_lock *lock, int flag)\n {\n \tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n@@ -2485,6 +2520,7 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)\n \tlock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);\n \tif (!lock)\n \t\treturn 1;\n+\tdetach_repos_refs(lock->ref_name, lock->old_sha1);\n \tret |= delete_ref_loose(lock, flag);\n \n \t/* removing the loose one could have resurrected an earlier\n@@ -2790,6 +2826,7 @@ int write_ref_sha1(struct ref_lock *lock,\n \t\tunlock_ref(lock);\n \t\treturn -1;\n \t}\n+\tdetach_repos_refs(lock->ref_name, lock->old_sha1);\n \tif (write_in_full(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 ||\n \t    write_in_full(lock->lock_fd, &term, 1) != 1\n \t\t|| close_ref(lock) < 0) {\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232018","messageId":"1387018507-21999-21-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 20/21] refs.c: refactor do_head_ref(... to do_one_head_ref(\"HEAD\", ...","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:55:06Z","receivedAt":"2013-12-14T10:55:06Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n refs.c | 15 ++++++++-------\n 1 file changed, 8 insertions(+), 7 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex d856b1a..8ef1cb0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1721,32 +1721,33 @@ static int do_for_each_ref(struct ref_cache *refs, const char *base,\n \treturn do_for_each_entry(refs, base, do_one_ref, &data);\n }\n \n-static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)\n+static int do_one_head_ref(const char *ref, const char *submodule,\n+\t\t      each_ref_fn fn, void *cb_data)\n {\n \tunsigned char sha1[20];\n \tint flag;\n \n \tif (submodule) {\n-\t\tif (resolve_gitlink_ref(submodule, \"HEAD\", sha1) == 0)\n-\t\t\treturn fn(\"HEAD\", sha1, 0, cb_data);\n+\t\tif (resolve_gitlink_ref(submodule, ref, sha1) == 0)\n+\t\t\treturn fn(ref, sha1, 0, cb_data);\n \n \t\treturn 0;\n \t}\n \n-\tif (!read_ref_full(\"HEAD\", sha1, 1, &flag))\n-\t\treturn fn(\"HEAD\", sha1, flag, cb_data);\n+\tif (!read_ref_full(ref, sha1, 1, &flag))\n+\t\treturn fn(ref, sha1, flag, cb_data);\n \n \treturn 0;\n }\n \n int head_ref(each_ref_fn fn, void *cb_data)\n {\n-\treturn do_head_ref(NULL, fn, cb_data);\n+\treturn do_one_head_ref(\"HEAD\", NULL, fn, cb_data);\n }\n \n int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n {\n-\treturn do_head_ref(submodule, fn, cb_data);\n+\treturn do_one_head_ref(\"HEAD\", submodule, fn, cb_data);\n }\n \n int for_each_ref(each_ref_fn fn, void *cb_data)\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232019","messageId":"1387018507-21999-22-git-send-email-pclouds@gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 21/21] revision: include repos/../HEAD in --all","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-14T10:55:07Z","receivedAt":"2013-12-14T10:55:07Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This makes sure repack won't drop detached HEADS from $GIT_DIR/repos/\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n refs.c     | 21 +++++++++++++++++++++\n refs.h     |  1 +\n revision.c |  1 +\n 3 files changed, 23 insertions(+)\n\ndiff --git a/refs.c b/refs.c\nindex 8ef1cb0..a9c8651 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1740,6 +1740,27 @@ static int do_one_head_ref(const char *ref, const char *submodule,\n \treturn 0;\n }\n \n+int repos_head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n+{\n+\tDIR *dir = opendir(git_path(\"repos\"));\n+\tint ret = 0;\n+\tstruct dirent *d;\n+\n+\tif (!dir)\n+\t\treturn ret;\n+\n+\twhile (!ret && (d = readdir(dir)) != NULL) {\n+\t\tstruct strbuf sb_ref = STRBUF_INIT;\n+\t\tif (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n+\t\t\tcontinue;\n+\t\tstrbuf_addf(&sb_ref, \"repos/%s/HEAD\", d->d_name);\n+\t\tret = do_one_head_ref(sb_ref.buf, submodule, fn, cb_data);\n+\t\tstrbuf_release(&sb_ref);\n+\t}\n+\tclosedir(dir);\n+\treturn ret;\n+}\n+\n int head_ref(each_ref_fn fn, void *cb_data)\n {\n \treturn do_one_head_ref(\"HEAD\", NULL, fn, cb_data);\ndiff --git a/refs.h b/refs.h\nindex 87a1a79..05686e4 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -70,6 +70,7 @@ extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *);\n extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, void *);\n \n extern int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);\n+extern int repos_head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);\n extern int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);\n extern int for_each_ref_in_submodule(const char *submodule, const char *prefix,\n \t\teach_ref_fn fn, void *cb_data);\ndiff --git a/revision.c b/revision.c\nindex 05d2d77..c4150ed 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2004,6 +2004,7 @@ static int handle_revision_pseudo_opt(const char *submodule,\n \tif (!strcmp(arg, \"--all\")) {\n \t\thandle_refs(submodule, revs, *flags, for_each_ref_submodule);\n \t\thandle_refs(submodule, revs, *flags, head_ref_submodule);\n+\t\thandle_refs(submodule, revs, *flags, repos_head_ref_submodule);\n \t\tclear_ref_exclusion(&revs->ref_excludes);\n \t} else if (!strcmp(arg, \"--branches\")) {\n \t\thandle_refs(submodule, revs, *flags, for_each_branch_ref_submodule);\n-- \n1.8.5.1.77.g42c48fa\n"},{"id":"232030","messageId":"7viourgqvb.fsf@alter.siamese.dyndns.org","threadId":"35520","inReplyTo":"CACsJy8DG0yx0J6tnAT-BsHsArCRA2akK-txWv5ee6k4pxPpixQ@mail.gmail.com","subject":"Re: [PATCH/POC 2/7] Add new environment variable $GIT_SUPER_DIR","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-12-14T19:43:52Z","receivedAt":"2013-12-14T19:43:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Duy Nguyen <pclouds@gmail.com> writes:\n\n> The exception list could be equally long (most of them are *_HEAD).\n> .git is also used for temporary files with mkstemp, but I think that's\n> safe for sharing. What could break is when people add a new local\n> *_HEAD and forget to update the exception list.\n\nSensible; it may be worth having the above to describe why we choose\nto go that \"share only the selected ones\" route in one of the log\nmessage and/or in-code comment.\n\nThanks.\n"},{"id":"232032","messageId":"52ACBE2B.3040909@ramsay1.demon.co.uk","threadId":"35520","inReplyTo":"1387018507-21999-3-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v2 02/21] path.c: rename vsnpath() to git_vsnpath()","fromName":"Ramsay Jones","fromEmail":"ramsay@ramsay1.demon.co.uk","sentAt":"2013-12-14T20:23:07Z","receivedAt":"2013-12-14T20:23:07Z","isPatch":true,"sender":{"key":"ramsay@ramsayjones.plus.com","avatar":"https://avatars.githubusercontent.com/u/33702710?v=4"},"body":"On 14/12/13 10:54, Nguyễn Thái Ngọc Duy wrote:\n> This is the underlying implementation of git_path(), git_pathdup() and\n> git_snpath() which will prefix $GIT_DIR in the result string. Put git_\n> prefix in front of it to avoid the confusion that this is a generic\n> path handling function.#\n> \n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  path.c | 8 ++++----\n>  1 file changed, 4 insertions(+), 4 deletions(-)\n> \n> diff --git a/path.c b/path.c\n> index 4c1c144..06863b7 100644\n> --- a/path.c\n> +++ b/path.c\n> @@ -50,7 +50,7 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)\n>  \treturn cleanup_path(buf);\n>  }\n>  \n> -static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)\n> +static char *git_vsnpath(char *buf, size_t n, const char *fmt, va_list args)\n\n:-D I renamed this _from_ git_vsnpath() in commit 5b3b8fa2 (\"path.c: Remove the\n'git_' prefix from a file scope function\", 04-09-2012), because ... well it's a\nfile scope function! (i.e. the git_ prefix implies greater than file scope).\nI'm not very good at naming things, so ...\n\nATB,\nRamsay Jones\n"},{"id":"232038","messageId":"CACsJy8BdLt8ZUU2z4LAgTcfXUjruFaOFsvrv6dSziZVizAopGA@mail.gmail.com","threadId":"35520","inReplyTo":"52ACBE2B.3040909@ramsay1.demon.co.uk","subject":"Re: [PATCH v2 02/21] path.c: rename vsnpath() to git_vsnpath()","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-15T02:25:30Z","receivedAt":"2013-12-15T02:25:30Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Sun, Dec 15, 2013 at 3:23 AM, Ramsay Jones\n<ramsay@ramsay1.demon.co.uk> wrote:\n> On 14/12/13 10:54, Nguyễn Thái Ngọc Duy wrote:\n>> This is the underlying implementation of git_path(), git_pathdup() and\n>> git_snpath() which will prefix $GIT_DIR in the result string. Put git_\n>> prefix in front of it to avoid the confusion that this is a generic\n>> path handling function.#\n>>\n>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>> ---\n>>  path.c | 8 ++++----\n>>  1 file changed, 4 insertions(+), 4 deletions(-)\n>>\n>> diff --git a/path.c b/path.c\n>> index 4c1c144..06863b7 100644\n>> --- a/path.c\n>> +++ b/path.c\n>> @@ -50,7 +50,7 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)\n>>       return cleanup_path(buf);\n>>  }\n>>\n>> -static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)\n>> +static char *git_vsnpath(char *buf, size_t n, const char *fmt, va_list args)\n>\n> :-D I renamed this _from_ git_vsnpath() in commit 5b3b8fa2 (\"path.c: Remove the\n> 'git_' prefix from a file scope function\", 04-09-2012), because ... well it's a\n> file scope function! (i.e. the git_ prefix implies greater than file scope).\n> I'm not very good at naming things, so ...\n\nmaybe gitdir_vsnpath() then to avoid the global scope prefix git_?\n-- \nDuy\n"},{"id":"232039","messageId":"CACsJy8C4FXbR7JtUiRLH=PuDDR8CLzwMeADR6u8JzFEOOj43AA@mail.gmail.com","threadId":"35520","inReplyTo":"1387018507-21999-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v2 00/21] Support multiple worktrees","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-15T02:29:05Z","receivedAt":"2013-12-15T02:29:05Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Sat, Dec 14, 2013 at 5:54 PM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> Known issues\n\nScripts that expand \"$GIT_DIR/objects\" and are not aware about the new\nenv variable. I introduced \"test-path-utils --git-path\" to test\ngit_path(). I could move it to \"git rev-parse --git-path\" for use in\nscripts, but there'll be more changes. git-new-workdir's symlink\napproach shines here.\n-- \nDuy\n"},{"id":"232040","messageId":"52AD69D4.30605@web.de","threadId":"35520","inReplyTo":"1387018507-21999-2-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v2 01/21] path.c: avoid PATH_MAX as buffer size from get_pathname()","fromName":"Torsten Bögershausen","fromEmail":"tboegi@web.de","sentAt":"2013-12-15T08:35:32Z","receivedAt":"2013-12-15T08:35:32Z","isPatch":true,"sender":{"key":"tboegi@web.de","avatar":"https://avatars.githubusercontent.com/u/7138363?v=4"},"body":"On 2013-12-14 11.54, Nguyễn Thái Ngọc Duy wrote:\n> We've been avoiding PATH_MAX whenever possible. This patch avoids\n> PATH_MAX in get_pathname() and gives it enough room not to worry about\n> really long paths.\n> \n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  path.c | 28 ++++++++++++++++------------\n>  1 file changed, 16 insertions(+), 12 deletions(-)\n> \n> diff --git a/path.c b/path.c\n> index 24594c4..4c1c144 100644\n> --- a/path.c\n> +++ b/path.c\n> @@ -16,10 +16,11 @@ static int get_st_mode_bits(const char *path, int *mode)\n>  \n>  static char bad_path[] = \"/bad-path/\";\n>  \n> -static char *get_pathname(void)\n> +static char *get_pathname(size_t *len)\n>  {\n> -\tstatic char pathname_array[4][PATH_MAX];\n> +\tstatic char pathname_array[4][4096];\nThe PATH_MAX doesn't seem to be that bad:\nhttp://pubs.opengroup.org/onlinepubs/009695399/basedefs/limits.h.html\nOr do we have a an OS where PATH_MAX does not work ?\n\nWindows uses MAX_PATH=260 PATH_MAX=259\n<http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx>\n\nWhich means that the current implementation of git can not use path names longer than\n259 (260 including the trailing \\0)\n(Please correct me if this is wrong)\n\nWhich means that the code working with the buffers must make sure to stay within range,\nand not to write beyond the buffers.\n\nIf we really want to go away from PATH_MAX, is a hard-coded value of 4096 so attractive ?\nBecause we can either\n\na) Re-define PATH_MAX in git-compat-util.h\nb) Use an own  #define in git-compat-util.h, like e.g. GIT_PATH_MAX\nc) Change the code to use a \"strbuf\" which can grow on demand.\n\nI would prefer c) over b) over a)\n\n\n\n\n>  \tstatic int index;\n> +\t*len = sizeof(pathname_array[0]);\n>  \treturn pathname_array[3 & ++index];\n>  }\n>  \n> @@ -108,24 +109,26 @@ char *mkpath(const char *fmt, ...)\n>  {\n>  \tva_list args;\n>  \tunsigned len;\n> -\tchar *pathname = get_pathname();\n> +\tsize_t n;\n> +\tchar *pathname = get_pathname(&n);\n>  \n>  \tva_start(args, fmt);\n> -\tlen = vsnprintf(pathname, PATH_MAX, fmt, args);\n> +\tlen = vsnprintf(pathname, n, fmt, args);\nRenaming \"n\" into something like \"max\" or \"max_len\" could\nmake this line 5% easier to read.\n(And similar at some places below)\n/Torsten\n"},{"id":"232041","messageId":"CACsJy8CVQfWdTY_aoH6uMTp0cgTnvF09tC7iLD7CjEL9d9=bzw@mail.gmail.com","threadId":"35520","inReplyTo":"52AD69D4.30605@web.de","subject":"Re: [PATCH v2 01/21] path.c: avoid PATH_MAX as buffer size from get_pathname()","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-15T09:02:33Z","receivedAt":"2013-12-15T09:02:33Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Sun, Dec 15, 2013 at 3:35 PM, Torsten Bögershausen <tboegi@web.de> wrote:\n> If we really want to go away from PATH_MAX, is a hard-coded value of 4096 so attractive ?\n> Because we can either\n>\n> a) Re-define PATH_MAX in git-compat-util.h\n> b) Use an own  #define in git-compat-util.h, like e.g. GIT_PATH_MAX\n> c) Change the code to use a \"strbuf\" which can grow on demand.\n>\n> I would prefer c) over b) over a)\n\nLooking at the code again, c) looks feasible after updating\ngit_snpath() to accept a strbuf instead of buffer/size pair.\n-- \nDuy\n"},{"id":"232042","messageId":"CALWbr2w-ibuoV5x3GGjJ3Lazeumdj7LhvnK+zfpWvyr10nc4Fw@mail.gmail.com","threadId":"35520","inReplyTo":"CACsJy8CVQfWdTY_aoH6uMTp0cgTnvF09tC7iLD7CjEL9d9=bzw@mail.gmail.com","subject":"Re: [PATCH v2 01/21] path.c: avoid PATH_MAX as buffer size from get_pathname()","fromName":"Antoine Pelisse","fromEmail":"apelisse@gmail.com","sentAt":"2013-12-15T09:33:34Z","receivedAt":"2013-12-15T09:33:34Z","isPatch":true,"sender":{"key":"apelisse@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1929644?v=4"},"body":"On Sun, Dec 15, 2013 at 10:02 AM, Duy Nguyen <pclouds@gmail.com> wrote:\n> On Sun, Dec 15, 2013 at 3:35 PM, Torsten Bögershausen <tboegi@web.de> wrote:\n>> If we really want to go away from PATH_MAX, is a hard-coded value of 4096 so attractive ?\n>> Because we can either\n>>\n>> a) Re-define PATH_MAX in git-compat-util.h\n>> b) Use an own  #define in git-compat-util.h, like e.g. GIT_PATH_MAX\n>> c) Change the code to use a \"strbuf\" which can grow on demand.\n>>\n>> I would prefer c) over b) over a)\n>\n> Looking at the code again, c) looks feasible after updating\n> git_snpath() to accept a strbuf instead of buffer/size pair.\n\nI agree that this is probably the way to go.\nWe are not trying to avoid PATH_MAX, but static-size array that can overflow.\n\nAntoine,\n"},{"id":"232044","messageId":"52AE1B5E.6020506@ramsay1.demon.co.uk","threadId":"35520","inReplyTo":"CACsJy8BdLt8ZUU2z4LAgTcfXUjruFaOFsvrv6dSziZVizAopGA@mail.gmail.com","subject":"Re: [PATCH v2 02/21] path.c: rename vsnpath() to git_vsnpath()","fromName":"Ramsay Jones","fromEmail":"ramsay@ramsay1.demon.co.uk","sentAt":"2013-12-15T21:13:02Z","receivedAt":"2013-12-15T21:13:02Z","isPatch":true,"sender":{"key":"ramsay@ramsayjones.plus.com","avatar":"https://avatars.githubusercontent.com/u/33702710?v=4"},"body":"On 15/12/13 02:25, Duy Nguyen wrote:\n> On Sun, Dec 15, 2013 at 3:23 AM, Ramsay Jones\n> <ramsay@ramsay1.demon.co.uk> wrote:\n>> On 14/12/13 10:54, Nguyễn Thái Ngọc Duy wrote:\n>>> This is the underlying implementation of git_path(), git_pathdup() and\n>>> git_snpath() which will prefix $GIT_DIR in the result string. Put git_\n>>> prefix in front of it to avoid the confusion that this is a generic\n>>> path handling function.#\n>>>\n>>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>>> ---\n>>>  path.c | 8 ++++----\n>>>  1 file changed, 4 insertions(+), 4 deletions(-)\n>>>\n>>> diff --git a/path.c b/path.c\n>>> index 4c1c144..06863b7 100644\n>>> --- a/path.c\n>>> +++ b/path.c\n>>> @@ -50,7 +50,7 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)\n>>>       return cleanup_path(buf);\n>>>  }\n>>>\n>>> -static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)\n>>> +static char *git_vsnpath(char *buf, size_t n, const char *fmt, va_list args)\n>>\n>> :-D I renamed this _from_ git_vsnpath() in commit 5b3b8fa2 (\"path.c: Remove the\n>> 'git_' prefix from a file scope function\", 04-09-2012), because ... well it's a\n>> file scope function! (i.e. the git_ prefix implies greater than file scope).\n>> I'm not very good at naming things, so ...\n> \n> maybe gitdir_vsnpath() then to avoid the global scope prefix git_?\n\nSounds fine to me (but then so does vsnpath ;-) ).\n\nATB,\nRamsay Jones\n"},{"id":"232046","messageId":"CACsJy8AJT5XXyJvVi_6Gjrn=AqA1M9KWY_62p=d3CPjg7nXFWw@mail.gmail.com","threadId":"35520","inReplyTo":"52AE1B5E.6020506@ramsay1.demon.co.uk","subject":"Re: [PATCH v2 02/21] path.c: rename vsnpath() to git_vsnpath()","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-16T07:21:08Z","receivedAt":"2013-12-16T07:21:08Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Mon, Dec 16, 2013 at 4:13 AM, Ramsay Jones\n<ramsay@ramsay1.demon.co.uk> wrote:\n>>>> -static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)\n>>>> +static char *git_vsnpath(char *buf, size_t n, const char *fmt, va_list args)\n>>>\n>>> :-D I renamed this _from_ git_vsnpath() in commit 5b3b8fa2 (\"path.c: Remove the\n>>> 'git_' prefix from a file scope function\", 04-09-2012), because ... well it's a\n>>> file scope function! (i.e. the git_ prefix implies greater than file scope).\n>>> I'm not very good at naming things, so ...\n>>\n>> maybe gitdir_vsnpath() then to avoid the global scope prefix git_?\n>\n> Sounds fine to me (but then so does vsnpath ;-) ).\n\nOK I go with this. I think it makes sense\n\nvsnpath -> do_git_path\n\nits three callers are\n\ngit_vsnpath -> strbuf_git_path (it's updated to take strbuf)\ngit_path\ngit_pathdup\n\nIn the end all these functions have \"git_path\" in them :)\n-- \nDuy\n"},{"id":"232055","messageId":"20131216171147.GA28604@google.com","threadId":"35520","inReplyTo":"CACsJy8AJT5XXyJvVi_6Gjrn=AqA1M9KWY_62p=d3CPjg7nXFWw@mail.gmail.com","subject":"Re: [PATCH v2 02/21] path.c: rename vsnpath() to git_vsnpath()","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2013-12-16T17:11:47Z","receivedAt":"2013-12-16T17:11:47Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Duy Nguyen wrote:\n>>> Ramsay Jones wrote:\n\n>>>> :-D I renamed this _from_ git_vsnpath() in commit 5b3b8fa2 (\"path.c: Remove the\n>>>> 'git_' prefix from a file scope function\", 04-09-2012), because ... well it's a\n>>>> file scope function! (i.e. the git_ prefix implies greater than file scope).\n>>>> I'm not very good at naming things, so ...\n[...]\n> OK I go with this. I think it makes sense\n>\n> vsnpath -> do_git_path\n\nI think this renaming would be still losing clarity --- it loses the\ninformation that this is the vsnprintf-like variant of git_path.\n\nDo we actually have a convention that functions with git_ prefix\nshould be global?  If git_path were not global, would it have to be\nrenamed?  If git_vsnpath should be renamed to avoid mistaking it for\ngit's replacement of a hypothetical standard library vsnpath function,\nshouldn't git_path, git_pathdup, etc be renamed for the same reason as\nwell?\n\n> its three callers are\n>\n> git_vsnpath -> strbuf_git_path (it's updated to take strbuf)\n> git_path\n> git_pathdup\n\nYeah, independently of everything else, a strbuf variant sounds nice.\n\nThanks,\nJonathan\n"},{"id":"232080","messageId":"xmqqfvpso8ko.fsf@gitster.dls.corp.google.com","threadId":"35520","inReplyTo":"20131216171147.GA28604@google.com","subject":"Re: [PATCH v2 02/21] path.c: rename vsnpath() to git_vsnpath()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-12-16T20:16:23Z","receivedAt":"2013-12-16T20:16:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> Duy Nguyen wrote:\n>>>> Ramsay Jones wrote:\n>\n>>>>> :-D I renamed this _from_ git_vsnpath() in commit 5b3b8fa2 (\"path.c: Remove the\n>>>>> 'git_' prefix from a file scope function\", 04-09-2012), because ... well it's a\n>>>>> file scope function! (i.e. the git_ prefix implies greater than file scope).\n>>>>> I'm not very good at naming things, so ...\n> [...]\n>> OK I go with this. I think it makes sense\n>>\n>> vsnpath -> do_git_path\n>\n> I think this renaming would be still losing clarity --- it loses the\n> information that this is the vsnprintf-like variant of git_path.\n>\n> Do we actually have a convention that functions with git_ prefix\n> should be global?  If git_path were not global, would it have to be\n> renamed?  If git_vsnpath should be renamed to avoid mistaking it for\n> git's replacement of a hypothetical standard library vsnpath function,\n> shouldn't git_path, git_pathdup, etc be renamed for the same reason as\n> well?\n>\n>> its three callers are\n>>\n>> git_vsnpath -> strbuf_git_path (it's updated to take strbuf)\n>> git_path\n>> git_pathdup\n>\n> Yeah, independently of everything else, a strbuf variant sounds nice.\n\nOK, then strbuf_vsnpath_in_gitdir() perhaps?\n"},{"id":"232087","messageId":"52AF85CB.90107@ramsay1.demon.co.uk","threadId":"35520","inReplyTo":"20131216171147.GA28604@google.com","subject":"Re: [PATCH v2 02/21] path.c: rename vsnpath() to git_vsnpath()","fromName":"Ramsay Jones","fromEmail":"ramsay@ramsay1.demon.co.uk","sentAt":"2013-12-16T22:59:23Z","receivedAt":"2013-12-16T22:59:23Z","isPatch":true,"sender":{"key":"ramsay@ramsayjones.plus.com","avatar":"https://avatars.githubusercontent.com/u/33702710?v=4"},"body":"On 16/12/13 17:11, Jonathan Nieder wrote:\n> Duy Nguyen wrote:\n>>>> Ramsay Jones wrote:\n> \n>>>>> :-D I renamed this _from_ git_vsnpath() in commit 5b3b8fa2 (\"path.c: Remove the\n>>>>> 'git_' prefix from a file scope function\", 04-09-2012), because ... well it's a\n>>>>> file scope function! (i.e. the git_ prefix implies greater than file scope).\n>>>>> I'm not very good at naming things, so ...\n> [...]\n>> OK I go with this. I think it makes sense\n>>\n>> vsnpath -> do_git_path\n> \n> I think this renaming would be still losing clarity --- it loses the\n> information that this is the vsnprintf-like variant of git_path.\n> \n> Do we actually have a convention that functions with git_ prefix\n> should be global?\n\nHmm, probably not no. However, any symbol that starts with git_ positively\nscreams global symbol to me. Maybe, I just need to close my ears. ;-)\n\nI didn't intend to provoke so much discussion. I was simply pointing out\nthat this symbol had already been renamed, in the exact opposite direction,\nonce before. Please just ignore me.\n\nATB,\nRamsay Jones\n"},{"id":"232359","messageId":"CACsJy8AWCRLqnQK5SV4yPRjU-3JX9BwgbrRvpBtJngdjad6Eew@mail.gmail.com","threadId":"35520","inReplyTo":"20131213204348.GX2311@google.com","subject":"Re: [PATCH/POC 3/7] setup.c: add split-repo support to .git files","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2013-12-23T03:38:31Z","receivedAt":"2013-12-23T03:38:31Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Sat, Dec 14, 2013 at 3:43 AM, Jonathan Nieder <jrnieder@gmail.com> wrote:\n> Problems:\n>\n>  * What if I move my worktree with \"mv\"?  Then I still need the\n>    corresponding $GIT_SUPER_DIR/repos/<id> directory, and nobody told\n>    the GIT_SUPER_DIR about it.\n>\n>  * What if my worktree is on removable media (think \"network\n>    filesystem\") and has just been temporarily unmounted instead of\n>    deleted?\n>\n> So maybe it would be nicer to:\n>\n>   i. When the worktree is on the same filesystem, keep a *hard link* to\n>      some file in the worktree (e.g., the .git file).  If the link count\n>      goes down, it is safe to remove the $GIT_SUPER_DIR/repos/<id>\n>      directory.\n\nLink count goes down to 1 if I move the worktree to a different\nfilesystem and it's not safe to remove $GIT_SUPER_DIR/repos/<id> in\nthis case, I think.\n\n>  ii. When the worktree is on another filesystem, always keep\n>      $GIT_SUPER_DIR/repos/<id> unless the user decides to manually\n>      remove it.  Provide documentation or a command to list basic\n>      information about $GIT_SUPER_DIR/repos directories (path to\n>      worktree, what branch they're on, etc).\n>\n> (i) doesn't require any futureproofing.  As soon as someone wants it,\n> they can implement the check and fall back to (ii) for worktrees\n> without the magic hard link.\n>\n> (ii) would benefit from recording the working tree directory as a\n> possibly unreliable, human-friendly reminder.\n-- \nDuy\n"}]}