{"thread":{"id":"38074","subject":"[PATCH 00/34] nd/multiple-work-trees reroll","startedAt":"2014-11-30T08:24:25Z","lastAt":"2014-12-03T15:54:21Z","messageCount":45,"participants":["Nguyễn Thái Ngọc Duy","Mark Levedahl","Duy Nguyen","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":34},"messages":[{"id":"252683","messageId":"1417335899-27307-1-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":null,"subject":"[PATCH 00/34] nd/multiple-work-trees reroll","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:25Z","receivedAt":"2014-11-30T08:24:25Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is rebased so the diff below (against the version on Junio's\nrepo) is only approximate. Changes include test fixes for Windows\nport, $GIT_COMMON_DIR and $GIT_DIR/modules problems with submodules.\nPatch 03/34 is rewritten to touch less in refs.c to reduce conflicts.\nA lot of changes there are just revert.\n\ndiff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt\nindex 2b30a92..7173b38 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -248,9 +248,7 @@ commondir::\n \tincomplete without the repository pointed by \"commondir\".\n \n modules::\n-\tContains the git-repositories of the submodules. This\n-\tdirectory is ignored if $GIT_COMMON_DIR is set and\n-\t\"$GIT_COMMON_DIR/modules\" will be used instead.\n+\tContains the git-repositories of the submodules.\n \n worktrees::\n \tContains worktree specific information of linked\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 01d0f2f..e70e66c 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -865,7 +865,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n \n \tif (!new->commit)\n \t\tdie(_(\"no branch specified\"));\n-\tif (file_exists(path))\n+\tif (file_exists(path) && !is_empty_dir(path))\n \t\tdie(_(\"'%s' already exists\"), path);\n \n \tlen = strlen(path);\n@@ -1207,7 +1207,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \tif (new->path && !force_detach && !*new_branch) {\n \t\tunsigned char sha1[20];\n \t\tint flag;\n-\t\tchar *head_ref = resolve_refdup(\"HEAD\", sha1, 0, &flag);\n+\t\tchar *head_ref = resolve_refdup(\"HEAD\", 0, sha1, &flag);\n \t\tif (head_ref &&\n \t\t    (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))\n \t\t\tcheck_linked_checkouts(new);\ndiff --git a/environment.c b/environment.c\nindex d5b0788..8351007 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -101,6 +101,7 @@ const char * const local_repo_env[] = {\n \tNO_REPLACE_OBJECTS_ENVIRONMENT,\n \tGIT_PREFIX_ENVIRONMENT,\n \tGIT_SHALLOW_FILE_ENVIRONMENT,\n+\tGIT_COMMON_DIR_ENVIRONMENT,\n \tNULL\n };\n \ndiff --git a/path.c b/path.c\nindex 35d498e..a5c51a3 100644\n--- a/path.c\n+++ b/path.c\n@@ -92,7 +92,7 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)\n }\n \n static const char *common_list[] = {\n-\t\"/branches\", \"/hooks\", \"/info\", \"!/logs\", \"/lost-found\", \"/modules\",\n+\t\"/branches\", \"/hooks\", \"/info\", \"!/logs\", \"/lost-found\",\n \t\"/objects\", \"/refs\", \"/remotes\", \"/worktrees\", \"/rr-cache\", \"/svn\",\n \t\"config\", \"!gc.pid\", \"packed-refs\", \"shallow\",\n \tNULL\ndiff --git a/refs.c b/refs.c\nindex 3cefbd3..f7e48b0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1398,14 +1398,16 @@ static const char *handle_missing_loose_ref(const char *refname,\n }\n \n /* This function needs to return a meaningful errno on failure */\n-const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)\n+static const char *resolve_ref_unsafe_1(const char *refname,\n+\t\t\t\t\tunsigned char *sha1,\n+\t\t\t\t\tint reading,\n+\t\t\t\t\tint *flags,\n+\t\t\t\t\tstruct strbuf *sb_path)\n {\n-\tstruct strbuf sb_path = STRBUF_INIT;\n \tint depth = MAXDEPTH;\n \tssize_t len;\n \tchar buffer[256];\n \tstatic char refname_buffer[256];\n-\tconst char *ret;\n \n \tif (flag)\n \t\t*flag = 0;\n@@ -1423,12 +1425,12 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \n \t\tif (--depth < 0) {\n \t\t\terrno = ELOOP;\n-\t\t\tgoto fail;\n+\t\t\treturn NULL;\n \t\t}\n \n-\t\tstrbuf_reset(&sb_path);\n-\t\tstrbuf_git_path(&sb_path, \"%s\", refname);\n-\t\tpath = sb_path.buf;\n+\t\tstrbuf_reset(sb_path);\n+\t\tstrbuf_git_path(sb_path, \"%s\", refname);\n+\t\tpath = sb_path->buf;\n \n \t\t/*\n \t\t * We might have to loop back here to avoid a race\n@@ -1442,11 +1444,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \tstat_ref:\n \t\tif (lstat(path, &st) < 0) {\n \t\t\tif (errno == ENOENT)\n-\t\t\t\tret = handle_missing_loose_ref(refname, sha1,\n-\t\t\t\t\t\t\t       reading, flag);\n+\t\t\t\treturn handle_missing_loose_ref(refname, sha1,\n+\t\t\t\t\t\t\t\treading, flag);\n \t\t\telse\n-\t\t\t\tret = NULL;\n-\t\t\tgoto done;\n+\t\t\t\treturn NULL;\n \t\t}\n \n \t\t/* Follow \"normalized\" - ie \"refs/..\" symlinks by hand */\n@@ -1457,7 +1458,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \t\t\t\t\t/* inconsistent with lstat; retry */\n \t\t\t\t\tgoto stat_ref;\n \t\t\t\telse\n-\t\t\t\t\tgoto fail;\n+\t\t\t\t\treturn NULL;\n \t\t\t}\n \t\t\tbuffer[len] = 0;\n \t\t\tif (starts_with(buffer, \"refs/\") &&\n@@ -1473,7 +1474,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \t\t/* Is it a directory? */\n \t\tif (S_ISDIR(st.st_mode)) {\n \t\t\terrno = EISDIR;\n-\t\t\tgoto fail;\n+\t\t\treturn NULL;\n \t\t}\n \n \t\t/*\n@@ -1486,15 +1487,14 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \t\t\t\t/* inconsistent with lstat; retry */\n \t\t\t\tgoto stat_ref;\n \t\t\telse\n-\t\t\t\tgoto fail;\n+\t\t\t\treturn NULL;\n \t\t}\n-\n \t\tlen = read_in_full(fd, buffer, sizeof(buffer)-1);\n \t\tif (len < 0) {\n \t\t\tint save_errno = errno;\n \t\t\tclose(fd);\n \t\t\terrno = save_errno;\n-\t\t\tgoto fail;\n+\t\t\treturn NULL;\n \t\t}\n \t\tclose(fd);\n \t\twhile (len && isspace(buffer[len-1]))\n@@ -1514,10 +1514,9 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \t\t\t\tif (flag)\n \t\t\t\t\t*flag |= REF_ISBROKEN;\n \t\t\t\terrno = EINVAL;\n-\t\t\t\tgoto fail;\n+\t\t\t\treturn NULL;\n \t\t\t}\n-\t\t\tret = refname;\n-\t\t\tgoto done;\n+\t\t\treturn refname;\n \t\t}\n \t\tif (flag)\n \t\t\t*flag |= REF_ISSYMREF;\n@@ -1528,13 +1527,17 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \t\t\tif (flag)\n \t\t\t\t*flag |= REF_ISBROKEN;\n \t\t\terrno = EINVAL;\n-\t\t\tgoto fail;\n+\t\t\treturn NULL;\n \t\t}\n \t\trefname = strcpy(refname_buffer, buf);\n \t}\n-fail:\n-\tret = NULL;\n-done:\n+}\n+\n+const char *resolve_ref_unsafe(const char *refname, int reading,\n+\t\t\t       unsigned char *sha1, int *flags)\n+{\n+\tstruct strbuf sb_path = STRBUF_INIT;\n+\tconst char *ret = resolve_ref_unsafe_1(refname, reading,, sha1, flags, &sb_path);\n \tstrbuf_release(&sb_path);\n \treturn ret;\n }\n@@ -2844,62 +2847,66 @@ static int copy_msg(char *buf, const char *msg)\n }\n \n /* This function must set a meaningful errno on failure */\n-int log_ref_setup(const char *refname, struct strbuf *logfile)\n+int log_ref_setup(const char *refname, struct strbuf *sb_logfile)\n {\n \tint logfd, oflags = O_APPEND | O_WRONLY;\n+\tchar *logfile;\n \n-\tstrbuf_git_path(logfile, \"logs/%s\", refname);\n+\tstrbuf_git_path(sb_logfile, \"logs/%s\", refname);\n+\tlogfile = sb_logfile->buf;\n+\t/* make sure the rest of the function can't change \"logfile\" */\n+\tsb_logfile = NULL;\n \tif (log_all_ref_updates &&\n \t    (starts_with(refname, \"refs/heads/\") ||\n \t     starts_with(refname, \"refs/remotes/\") ||\n \t     starts_with(refname, \"refs/notes/\") ||\n \t     !strcmp(refname, \"HEAD\"))) {\n-\t\tif (safe_create_leading_directories(logfile->buf) < 0) {\n+\t\tif (safe_create_leading_directories(logfile) < 0) {\n \t\t\tint save_errno = errno;\n-\t\t\terror(\"unable to create directory for %s\", logfile->buf);\n+\t\t\terror(\"unable to create directory for %s\", logfile);\n \t\t\terrno = save_errno;\n \t\t\treturn -1;\n \t\t}\n \t\toflags |= O_CREAT;\n \t}\n \n-\tlogfd = open(logfile->buf, oflags, 0666);\n+\tlogfd = open(logfile, oflags, 0666);\n \tif (logfd < 0) {\n \t\tif (!(oflags & O_CREAT) && errno == ENOENT)\n \t\t\treturn 0;\n \n \t\tif ((oflags & O_CREAT) && errno == EISDIR) {\n-\t\t\tif (remove_empty_directories(logfile->buf)) {\n+\t\t\tif (remove_empty_directories(logfile)) {\n \t\t\t\tint save_errno = errno;\n \t\t\t\terror(\"There are still logs under '%s'\",\n-\t\t\t\t      logfile->buf);\n+\t\t\t\t      logfile);\n \t\t\t\terrno = save_errno;\n \t\t\t\treturn -1;\n \t\t\t}\n-\t\t\tlogfd = open(logfile->buf, oflags, 0666);\n+\t\t\tlogfd = open(logfile, oflags, 0666);\n \t\t}\n \n \t\tif (logfd < 0) {\n \t\t\tint save_errno = errno;\n-\t\t\terror(\"Unable to append to %s: %s\", logfile->buf,\n+\t\t\terror(\"Unable to append to %s: %s\", logfile,\n \t\t\t      strerror(errno));\n \t\t\terrno = save_errno;\n \t\t\treturn -1;\n \t\t}\n \t}\n \n-\tadjust_shared_perm(logfile->buf);\n+\tadjust_shared_perm(logfile);\n \tclose(logfd);\n \treturn 0;\n }\n \n-static int log_ref_write(const char *refname, const unsigned char *old_sha1,\n-\t\t\t const unsigned char *new_sha1, const char *msg)\n+static int log_ref_write_1(const char *refname, const unsigned char *old_sha1,\n+\t\t\t   const unsigned char *new_sha1, const char *msg,\n+\t\t\t   struct strbuf *sb_log_file)\n {\n \tint logfd, result, written, oflags = O_APPEND | O_WRONLY;\n \tunsigned maxlen, len;\n \tint msglen;\n-\tstruct strbuf sb_log_file = STRBUF_INIT;\n \tconst char *log_file;\n \tchar *logrec;\n \tconst char *committer;\n@@ -2907,14 +2914,16 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,\n \tif (log_all_ref_updates < 0)\n \t\tlog_all_ref_updates = !is_bare_repository();\n \n-\tresult = log_ref_setup(refname, &sb_log_file);\n+\tresult = log_ref_setup(refname, sb_log_file);\n \tif (result)\n-\t\tgoto done;\n-\tlog_file = sb_log_file.buf;\n+\t\treturn result;\n+\tlog_file = sb_log_file->buf;\n+\t/* make sure the rest of the function can't change \"log_file\" */\n+\tsb_log_file = NULL;\n \n \tlogfd = open(log_file, oflags);\n \tif (logfd < 0)\n-\t\tgoto done;\n+\t\treturn 0;\n \tmsglen = msg ? strlen(msg) : 0;\n \tcommitter = git_committer_info(0);\n \tmaxlen = strlen(committer) + msglen + 100;\n@@ -2932,19 +2941,24 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,\n \t\tclose(logfd);\n \t\terror(\"Unable to append to %s\", log_file);\n \t\terrno = save_errno;\n-\t\tresult = -1;\n-\t\tgoto done;\n+\t\treturn -1;\n \t}\n \tif (close(logfd)) {\n \t\tint save_errno = errno;\n \t\terror(\"Unable to append to %s\", log_file);\n \t\terrno = save_errno;\n-\t\tresult = -1;\n-\t\tgoto done;\n+\t\treturn -1;\n \t}\n-done:\n-\tstrbuf_release(&sb_log_file);\n-\treturn result;\n+\treturn 0;\n+}\n+\n+static int log_ref_write(const char *refname, const unsigned char *old_sha1,\n+\t\t\t const unsigned char *new_sha1, const char *msg)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tint ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb);\n+\tstrbuf_release(&sb);\n+\treturn ret;\n }\n \n int is_branch(const char *refname)\ndiff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh\nindex e6ac7a4..4df7a2f 100755\n--- a/t/t1501-worktree.sh\n+++ b/t/t1501-worktree.sh\n@@ -366,7 +366,7 @@ test_expect_success 'GIT_DIR set (1)' '\n \n test_expect_success 'GIT_DIR set (2)' '\n \techo \"gitdir: repo.git/repos/foo\" >gitfile &&\n-\techo \"$TRASH_DIRECTORY/repo.git\" >repo.git/repos/foo/commondir &&\n+\techo \"$(pwd)/repo.git\" >repo.git/repos/foo/commondir &&\n \t(\n \t\tcd work &&\n \t\tGIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&\ndiff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh\nindex eddd325..915b506 100755\n--- a/t/t2025-checkout-to.sh\n+++ b/t/t2025-checkout-to.sh\n@@ -13,10 +13,15 @@ test_expect_success 'checkout --to not updating paths' '\n '\n \n test_expect_success 'checkout --to an existing worktree' '\n-\tmkdir existing &&\n+\tmkdir -p existing/subtree &&\n \ttest_must_fail git checkout --detach --to existing master\n '\n \n+test_expect_success 'checkout --to an existing empty worktree' '\n+\tmkdir existing_empty &&\n+\tgit checkout --detach --to existing_empty master\n+'\n+\n test_expect_success 'checkout --to refuses to checkout locked branch' '\n \ttest_must_fail git checkout --to zere master &&\n \t! test -d zere &&\ndiff --git a/t/t2026-prune-linked-checkouts.sh b/t/t2026-prune-linked-checkouts.sh\nindex 3622800..170aefe 100755\n--- a/t/t2026-prune-linked-checkouts.sh\n+++ b/t/t2026-prune-linked-checkouts.sh\n@@ -57,7 +57,7 @@ test_expect_success 'prune directories with invalid gitdir' '\n test_expect_success 'prune directories with gitdir pointing to nowhere' '\n \tmkdir -p .git/worktrees/def/abc &&\n \t: >.git/worktrees/def/def &&\n-\techo \"$TRASH_DIRECTORY\"/nowhere >.git/worktrees/def/gitdir &&\n+\techo \"$(pwd)\"/nowhere >.git/worktrees/def/gitdir &&\n \tgit prune --worktrees --verbose >actual &&\n \ttest_i18ngrep \"Removing worktrees/def: gitdir file points to non-existent location\" actual &&\n \t! test -d .git/worktrees/def &&\n@@ -76,7 +76,7 @@ test_expect_success 'not prune recent checkouts' '\n \ttest_when_finished rm -r .git/worktrees\n \tmkdir zz &&\n \tmkdir -p .git/worktrees/jlm &&\n-\techo \"$TRASH_DIRECTORY\"/zz >.git/worktrees/jlm/gitdir &&\n+\techo \"$(pwd)\"/zz >.git/worktrees/jlm/gitdir &&\n \tgit prune --worktrees --verbose --expire=2.days.ago &&\n \ttest -d .git/worktrees/jlm\n '\ndiff --git a/t/t7410-submodule-checkout-to.sh b/t/t7410-submodule-checkout-to.sh\nnew file mode 100755\nindex 0000000..8f30aed\n--- /dev/null\n+++ b/t/t7410-submodule-checkout-to.sh\n@@ -0,0 +1,50 @@\n+#!/bin/sh\n+\n+test_description='Combination of submodules and multiple workdirs'\n+\n+. ./test-lib.sh\n+\n+base_path=$(pwd -P)\n+\n+test_expect_success 'setup: make origin' \\\n+    'mkdir -p origin/sub && ( cd origin/sub && git init &&\n+\techo file1 >file1 &&\n+\tgit add file1 &&\n+\tgit commit -m file1 ) &&\n+    mkdir -p origin/main && ( cd origin/main && git init &&\n+\tgit submodule add ../sub &&\n+\tgit commit -m \"add sub\" ) &&\n+    ( cd origin/sub &&\n+\techo file1updated >file1 &&\n+\tgit add file1 &&\n+\tgit commit -m \"file1 updated\" ) &&\n+    ( cd origin/main/sub && git pull ) &&\n+    ( cd origin/main &&\n+\tgit add sub &&\n+\tgit commit -m \"sub updated\" )'\n+\n+test_expect_success 'setup: clone' \\\n+    'mkdir clone && ( cd clone &&\n+\tgit clone --recursive \"$base_path/origin/main\")'\n+\n+rev1_hash_main=$(git --git-dir=origin/main/.git show --pretty=format:%h -q \"HEAD~1\")\n+rev1_hash_sub=$(git --git-dir=origin/sub/.git show --pretty=format:%h -q \"HEAD~1\")\n+\n+test_expect_success 'checkout main' \\\n+    'mkdir default_checkout &&\n+    (cd clone/main &&\n+\tgit checkout --to \"$base_path/default_checkout/main\" \"$rev1_hash_main\")'\n+\n+test_expect_failure 'can see submodule diffs just after checkout' \\\n+    '(cd default_checkout/main && git diff --submodule master\"^!\" | grep \"file1 updated\")'\n+\n+test_expect_success 'checkout main and initialize independed clones' \\\n+    'mkdir fully_cloned_submodule &&\n+    (cd clone/main &&\n+\tgit checkout --to \"$base_path/fully_cloned_submodule/main\" \"$rev1_hash_main\") &&\n+    (cd fully_cloned_submodule/main && git submodule update)'\n+\n+test_expect_success 'can see submodule diffs after independed cloning' \\\n+    '(cd fully_cloned_submodule/main && git diff --submodule master\"^!\" | grep \"file1 updated\")'\n+\n+test_done\n"},{"id":"252684","messageId":"1417335899-27307-2-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 01/34] path.c: make get_pathname() return strbuf instead of static buffer","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:26Z","receivedAt":"2014-11-30T08:24:26Z","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 makes\nget_pathname() return a strbuf and updates the callers to take\nadvantage of this. The code is simplified as we no longer need to\nworry about buffer overflow.\n\nvsnpath() behavior is changed slightly: previously it always clears\nthe buffer before writing, now it just appends. Fortunately this is a\nstatic function and all of its callers prepare the buffer properly:\ngit_path() gets the buffer from get_pathname() which resets the\nbuffer, the remaining call sites start with STRBUF_INIT'd buffer.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n path.c | 120 ++++++++++++++++++++++++++++-------------------------------------\n 1 file changed, 51 insertions(+), 69 deletions(-)\n\ndiff --git a/path.c b/path.c\nindex f68df0c..015c0e4 100644\n--- a/path.c\n+++ b/path.c\n@@ -16,11 +16,15 @@ 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 struct strbuf *get_pathname(void)\n {\n-\tstatic char pathname_array[4][PATH_MAX];\n+\tstatic struct strbuf pathname_array[4] = {\n+\t\tSTRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n+\t};\n \tstatic int index;\n-\treturn pathname_array[3 & ++index];\n+\tstruct strbuf *sb = &pathname_array[3 & ++index];\n+\tstrbuf_reset(sb);\n+\treturn sb;\n }\n \n static char *cleanup_path(char *path)\n@@ -34,6 +38,13 @@ static char *cleanup_path(char *path)\n \treturn path;\n }\n \n+static void strbuf_cleanup_path(struct strbuf *sb)\n+{\n+\tchar *path = cleanup_path(sb->buf);\n+\tif (path > sb->buf)\n+\t\tstrbuf_remove(sb, 0, path - sb->buf);\n+}\n+\n char *mksnpath(char *buf, size_t n, const char *fmt, ...)\n {\n \tva_list args;\n@@ -49,85 +60,70 @@ 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 void vsnpath(struct strbuf *buf, const char *fmt, va_list args)\n {\n \tconst char *git_dir = get_git_dir();\n-\tsize_t len;\n-\n-\tlen = strlen(git_dir);\n-\tif (n < len + 1)\n-\t\tgoto bad;\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-\t\tgoto bad;\n-\treturn cleanup_path(buf);\n-bad:\n-\tstrlcpy(buf, bad_path, n);\n-\treturn buf;\n+\tstrbuf_addstr(buf, git_dir);\n+\tif (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))\n+\t\tstrbuf_addch(buf, '/');\n+\tstrbuf_vaddf(buf, fmt, args);\n+\tstrbuf_cleanup_path(buf);\n }\n \n char *git_snpath(char *buf, size_t n, const char *fmt, ...)\n {\n-\tchar *ret;\n+\tstruct strbuf sb = STRBUF_INIT;\n \tva_list args;\n \tva_start(args, fmt);\n-\tret = vsnpath(buf, n, fmt, args);\n+\tvsnpath(&sb, fmt, args);\n \tva_end(args);\n-\treturn ret;\n+\tif (sb.len >= n)\n+\t\tstrlcpy(buf, bad_path, n);\n+\telse\n+\t\tmemcpy(buf, sb.buf, sb.len + 1);\n+\tstrbuf_release(&sb);\n+\treturn buf;\n }\n \n char *git_pathdup(const char *fmt, ...)\n {\n-\tchar path[PATH_MAX], *ret;\n+\tstruct strbuf path = STRBUF_INIT;\n \tva_list args;\n \tva_start(args, fmt);\n-\tret = vsnpath(path, sizeof(path), fmt, args);\n+\tvsnpath(&path, fmt, args);\n \tva_end(args);\n-\treturn xstrdup(ret);\n+\treturn strbuf_detach(&path, NULL);\n }\n \n char *mkpathdup(const char *fmt, ...)\n {\n-\tchar *path;\n \tstruct strbuf sb = STRBUF_INIT;\n \tva_list args;\n-\n \tva_start(args, fmt);\n \tstrbuf_vaddf(&sb, fmt, args);\n \tva_end(args);\n-\tpath = xstrdup(cleanup_path(sb.buf));\n-\n-\tstrbuf_release(&sb);\n-\treturn path;\n+\tstrbuf_cleanup_path(&sb);\n+\treturn strbuf_detach(&sb, NULL);\n }\n \n char *mkpath(const char *fmt, ...)\n {\n \tva_list args;\n-\tunsigned len;\n-\tchar *pathname = get_pathname();\n-\n+\tstruct strbuf *pathname = get_pathname();\n \tva_start(args, fmt);\n-\tlen = vsnprintf(pathname, PATH_MAX, fmt, args);\n+\tstrbuf_vaddf(pathname, fmt, args);\n \tva_end(args);\n-\tif (len >= PATH_MAX)\n-\t\treturn bad_path;\n-\treturn cleanup_path(pathname);\n+\treturn cleanup_path(pathname->buf);\n }\n \n char *git_path(const char *fmt, ...)\n {\n-\tchar *pathname = get_pathname();\n+\tstruct strbuf *pathname = get_pathname();\n \tva_list args;\n-\tchar *ret;\n-\n \tva_start(args, fmt);\n-\tret = vsnpath(pathname, PATH_MAX, fmt, args);\n+\tvsnpath(pathname, fmt, args);\n \tva_end(args);\n-\treturn ret;\n+\treturn pathname->buf;\n }\n \n void home_config_paths(char **global, char **xdg, char *file)\n@@ -160,41 +156,27 @@ 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-\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct strbuf *buf = get_pathname();\n \tconst char *git_dir;\n \tva_list args;\n-\tunsigned len;\n-\n-\tlen = strlen(path);\n-\tif (len > PATH_MAX-100)\n-\t\treturn bad_path;\n \n-\tstrbuf_addstr(&buf, path);\n-\tif (len && path[len-1] != '/')\n-\t\tstrbuf_addch(&buf, '/');\n-\tstrbuf_addstr(&buf, \".git\");\n+\tstrbuf_addstr(buf, path);\n+\tif (buf->len && buf->buf[buf->len - 1] != '/')\n+\t\tstrbuf_addch(buf, '/');\n+\tstrbuf_addstr(buf, \".git\");\n \n-\tgit_dir = read_gitfile(buf.buf);\n+\tgit_dir = read_gitfile(buf->buf);\n \tif (git_dir) {\n-\t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addstr(&buf, git_dir);\n+\t\tstrbuf_reset(buf);\n+\t\tstrbuf_addstr(buf, git_dir);\n \t}\n-\tstrbuf_addch(&buf, '/');\n-\n-\tif (buf.len >= PATH_MAX)\n-\t\treturn bad_path;\n-\tmemcpy(pathname, buf.buf, buf.len + 1);\n-\n-\tstrbuf_release(&buf);\n-\tlen = strlen(pathname);\n+\tstrbuf_addch(buf, '/');\n \n \tva_start(args, fmt);\n-\tlen += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);\n+\tstrbuf_vaddf(buf, fmt, args);\n \tva_end(args);\n-\tif (len >= PATH_MAX)\n-\t\treturn bad_path;\n-\treturn cleanup_path(pathname);\n+\tstrbuf_cleanup_path(buf);\n+\treturn buf->buf;\n }\n \n int validate_headref(const char *path)\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252685","messageId":"1417335899-27307-3-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 02/34] path.c: make get_pathname() call sites return const char *","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:27Z","receivedAt":"2014-11-30T08:24:27Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Before the previous commit, get_pathname returns an array of PATH_MAX\nlength. Even if git_path() and similar functions does not use the\nwhole array, git_path() caller can, in theory.\n\nAfter the commit, get_pathname() may return a buffer that has just\nenough room for the returned string and git_path() caller should never\nwrite beyond that.\n\nMake git_path(), mkpath() and git_path_submodule() return a const\nbuffer to make sure callers do not write in it at all.\n\nThis could have been part of the previous commit, but the \"const\"\nconversion is too much distraction from the core changes in path.c.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/checkout.c     | 2 +-\n builtin/clone.c        | 9 +++++----\n builtin/fetch.c        | 5 +++--\n builtin/fsck.c         | 4 ++--\n builtin/receive-pack.c | 2 +-\n builtin/remote.c       | 2 +-\n builtin/repack.c       | 8 +++++---\n cache.h                | 6 +++---\n fast-import.c          | 2 +-\n notes-merge.c          | 6 +++---\n path.c                 | 6 +++---\n refs.c                 | 8 ++++----\n run-command.c          | 4 ++--\n run-command.h          | 2 +-\n sha1_file.c            | 2 +-\n 15 files changed, 36 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 5410dac..c600ec1 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -589,7 +589,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\tif (opts->new_branch_log && !log_all_ref_updates) {\n \t\t\t\tint temp;\n \t\t\t\tchar log_file[PATH_MAX];\n-\t\t\t\tchar *ref_name = mkpath(\"refs/heads/%s\", opts->new_orphan_branch);\n+\t\t\t\tconst char *ref_name = mkpath(\"refs/heads/%s\", opts->new_orphan_branch);\n \n \t\t\t\ttemp = log_all_ref_updates;\n \t\t\t\tlog_all_ref_updates = 1;\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex d5e7532..a9af3f2 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -290,16 +290,17 @@ static void copy_alternates(struct strbuf *src, struct strbuf *dst,\n \tstruct strbuf line = STRBUF_INIT;\n \n \twhile (strbuf_getline(&line, in, '\\n') != EOF) {\n-\t\tchar *abs_path, abs_buf[PATH_MAX];\n+\t\tchar *abs_path;\n \t\tif (!line.len || line.buf[0] == '#')\n \t\t\tcontinue;\n \t\tif (is_absolute_path(line.buf)) {\n \t\t\tadd_to_alternates_file(line.buf);\n \t\t\tcontinue;\n \t\t}\n-\t\tabs_path = mkpath(\"%s/objects/%s\", src_repo, line.buf);\n-\t\tnormalize_path_copy(abs_buf, abs_path);\n-\t\tadd_to_alternates_file(abs_buf);\n+\t\tabs_path = mkpathdup(\"%s/objects/%s\", src_repo, line.buf);\n+\t\tnormalize_path_copy(abs_path, abs_path);\n+\t\tadd_to_alternates_file(abs_path);\n+\t\tfree(abs_path);\n \t}\n \tstrbuf_release(&line);\n \tfclose(in);\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 7b84d35..cb54936 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -587,7 +587,8 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,\n \tstruct strbuf note = STRBUF_INIT;\n \tconst char *what, *kind;\n \tstruct ref *rm;\n-\tchar *url, *filename = dry_run ? \"/dev/null\" : git_path(\"FETCH_HEAD\");\n+\tchar *url;\n+\tconst char *filename = dry_run ? \"/dev/null\" : git_path(\"FETCH_HEAD\");\n \tint want_status;\n \n \tfp = fopen(filename, \"a\");\n@@ -821,7 +822,7 @@ static void check_not_current_branch(struct ref *ref_map)\n \n static int truncate_fetch_head(void)\n {\n-\tchar *filename = git_path(\"FETCH_HEAD\");\n+\tconst char *filename = git_path(\"FETCH_HEAD\");\n \tFILE *fp = fopen(filename, \"w\");\n \n \tif (!fp)\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex a27515a..b92aeff 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -225,12 +225,12 @@ static void check_unreachable_object(struct object *obj)\n \t\t\tprintf(\"dangling %s %s\\n\", typename(obj->type),\n \t\t\t       sha1_to_hex(obj->sha1));\n \t\tif (write_lost_and_found) {\n-\t\t\tchar *filename = git_path(\"lost-found/%s/%s\",\n+\t\t\tconst char *filename = git_path(\"lost-found/%s/%s\",\n \t\t\t\tobj->type == OBJ_COMMIT ? \"commit\" : \"other\",\n \t\t\t\tsha1_to_hex(obj->sha1));\n \t\t\tFILE *f;\n \n-\t\t\tif (safe_create_leading_directories(filename)) {\n+\t\t\tif (safe_create_leading_directories_const(filename)) {\n \t\t\t\terror(\"Could not create lost-found\");\n \t\t\t\treturn;\n \t\t\t}\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 32fc540..3b8f420 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -869,7 +869,7 @@ static void run_update_post_hook(struct command *commands)\n \tint argc;\n \tconst char **argv;\n \tstruct child_process proc = CHILD_PROCESS_INIT;\n-\tchar *hook;\n+\tconst char *hook;\n \n \thook = find_hook(\"post-update\");\n \tfor (argc = 0, cmd = commands; cmd; cmd = cmd->next) {\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 7f28f92..4ce396f 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -582,7 +582,7 @@ static int migrate_file(struct remote *remote)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tint i;\n-\tchar *path = NULL;\n+\tconst char *path = NULL;\n \n \tstrbuf_addf(&buf, \"remote.%s.url\", remote->name);\n \tfor (i = 0; i < remote->url_nr; i++)\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 2845620..9c04e1d 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -284,7 +284,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)\n \tfailed = 0;\n \tfor_each_string_list_item(item, &names) {\n \t\tfor (ext = 0; ext < ARRAY_SIZE(exts); ext++) {\n-\t\t\tchar *fname, *fname_old;\n+\t\t\tconst char *fname_old;\n+\t\t\tchar *fname;\n \t\t\tfname = mkpathdup(\"%s/pack-%s%s\", packdir,\n \t\t\t\t\t\titem->string, exts[ext].name);\n \t\t\tif (!file_exists(fname)) {\n@@ -312,7 +313,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)\n \tif (failed) {\n \t\tstruct string_list rollback_failure = STRING_LIST_INIT_DUP;\n \t\tfor_each_string_list_item(item, &rollback) {\n-\t\t\tchar *fname, *fname_old;\n+\t\t\tconst char *fname_old;\n+\t\t\tchar *fname;\n \t\t\tfname = mkpathdup(\"%s/%s\", packdir, item->string);\n \t\t\tfname_old = mkpath(\"%s/old-%s\", packdir, item->string);\n \t\t\tif (rename(fname_old, fname))\n@@ -365,7 +367,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)\n \t/* Remove the \"old-\" files */\n \tfor_each_string_list_item(item, &names) {\n \t\tfor (ext = 0; ext < ARRAY_SIZE(exts); ext++) {\n-\t\t\tchar *fname;\n+\t\t\tconst char *fname;\n \t\t\tfname = mkpath(\"%s/old-%s%s\",\n \t\t\t\t\tpackdir,\n \t\t\t\t\titem->string,\ndiff --git a/cache.h b/cache.h\nindex 99ed096..dbee0a3 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -687,9 +687,9 @@ extern char *mkpathdup(const char *fmt, ...)\n \t__attribute__((format (printf, 1, 2)));\n \n /* Return a statically allocated filename matching the sha1 signature */\n-extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n-extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n-extern char *git_path_submodule(const char *path, const char *fmt, ...)\n+extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n+extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n+extern const char *git_path_submodule(const char *path, const char *fmt, ...)\n \t__attribute__((format (printf, 2, 3)));\n \n /*\ndiff --git a/fast-import.c b/fast-import.c\nindex d0bd285..3018130 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -405,7 +405,7 @@ static void dump_marks_helper(FILE *, uintmax_t, struct mark_set *);\n \n static void write_crash_report(const char *err)\n {\n-\tchar *loc = git_path(\"fast_import_crash_%\"PRIuMAX, (uintmax_t) getpid());\n+\tconst char *loc = git_path(\"fast_import_crash_%\"PRIuMAX, (uintmax_t) getpid());\n \tFILE *rpt = fopen(loc, \"w\");\n \tstruct branch *b;\n \tunsigned long lu;\ndiff --git a/notes-merge.c b/notes-merge.c\nindex 7eb9d7a..f39e906 100644\n--- a/notes-merge.c\n+++ b/notes-merge.c\n@@ -280,7 +280,7 @@ static void check_notes_merge_worktree(struct notes_merge_options *o)\n \t\t\t\t    \"(%s exists).\", git_path(\"NOTES_MERGE_*\"));\n \t\t}\n \n-\t\tif (safe_create_leading_directories(git_path(\n+\t\tif (safe_create_leading_directories_const(git_path(\n \t\t\t\tNOTES_MERGE_WORKTREE \"/.test\")))\n \t\t\tdie_errno(\"unable to create directory %s\",\n \t\t\t\t  git_path(NOTES_MERGE_WORKTREE));\n@@ -295,8 +295,8 @@ static void write_buf_to_worktree(const unsigned char *obj,\n \t\t\t\t  const char *buf, unsigned long size)\n {\n \tint fd;\n-\tchar *path = git_path(NOTES_MERGE_WORKTREE \"/%s\", sha1_to_hex(obj));\n-\tif (safe_create_leading_directories(path))\n+\tconst char *path = git_path(NOTES_MERGE_WORKTREE \"/%s\", sha1_to_hex(obj));\n+\tif (safe_create_leading_directories_const(path))\n \t\tdie_errno(\"unable to create directory for '%s'\", path);\n \tif (file_exists(path))\n \t\tdie(\"found existing file at '%s'\", path);\ndiff --git a/path.c b/path.c\nindex 015c0e4..a7ceea2 100644\n--- a/path.c\n+++ b/path.c\n@@ -106,7 +106,7 @@ char *mkpathdup(const char *fmt, ...)\n \treturn strbuf_detach(&sb, NULL);\n }\n \n-char *mkpath(const char *fmt, ...)\n+const char *mkpath(const char *fmt, ...)\n {\n \tva_list args;\n \tstruct strbuf *pathname = get_pathname();\n@@ -116,7 +116,7 @@ char *mkpath(const char *fmt, ...)\n \treturn cleanup_path(pathname->buf);\n }\n \n-char *git_path(const char *fmt, ...)\n+const char *git_path(const char *fmt, ...)\n {\n \tstruct strbuf *pathname = get_pathname();\n \tva_list args;\n@@ -154,7 +154,7 @@ void home_config_paths(char **global, char **xdg, char *file)\n \tfree(to_free);\n }\n \n-char *git_path_submodule(const char *path, const char *fmt, ...)\n+const char *git_path_submodule(const char *path, const char *fmt, ...)\n {\n \tstruct strbuf *buf = get_pathname();\n \tconst char *git_dir;\ndiff --git a/refs.c b/refs.c\nindex 5ff457e..23617e0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1351,7 +1351,7 @@ static int resolve_gitlink_ref_recursive(struct ref_cache *refs,\n {\n \tint fd, len;\n \tchar buffer[128], *p;\n-\tchar *path;\n+\tconst char *path;\n \n \tif (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN)\n \t\treturn -1;\n@@ -2229,7 +2229,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\t\t\t\t    const struct string_list *skip,\n \t\t\t\t\t    int flags, int *type_p)\n {\n-\tchar *ref_file;\n+\tconst char *ref_file;\n \tconst char *orig_refname = refname;\n \tstruct ref_lock *lock;\n \tint last_errno = 0;\n@@ -2303,7 +2303,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\tlock->force_write = 1;\n \n  retry:\n-\tswitch (safe_create_leading_directories(ref_file)) {\n+\tswitch (safe_create_leading_directories_const(ref_file)) {\n \tcase SCLD_OK:\n \t\tbreak; /* success */\n \tcase SCLD_VANISHED:\n@@ -2743,7 +2743,7 @@ static int rename_tmp_log(const char *newrefname)\n \tint attempts_remaining = 4;\n \n  retry:\n-\tswitch (safe_create_leading_directories(git_path(\"logs/%s\", newrefname))) {\n+\tswitch (safe_create_leading_directories_const(git_path(\"logs/%s\", newrefname))) {\n \tcase SCLD_OK:\n \t\tbreak; /* success */\n \tcase SCLD_VANISHED:\ndiff --git a/run-command.c b/run-command.c\nindex a476999..c4a62aa 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -794,9 +794,9 @@ int finish_async(struct async *async)\n #endif\n }\n \n-char *find_hook(const char *name)\n+const char *find_hook(const char *name)\n {\n-\tchar *path = git_path(\"hooks/%s\", name);\n+\tconst char *path = git_path(\"hooks/%s\", name);\n \tif (access(path, X_OK) < 0)\n \t\tpath = NULL;\n \ndiff --git a/run-command.h b/run-command.h\nindex 2137315..892892d 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -52,7 +52,7 @@ int start_command(struct child_process *);\n int finish_command(struct child_process *);\n int run_command(struct child_process *);\n \n-extern char *find_hook(const char *name);\n+extern const char *find_hook(const char *name);\n LAST_ARG_MUST_BE_NULL\n extern int run_hook_le(const char *const *env, const char *name, ...);\n extern int run_hook_ve(const char *const *env, const char *name, va_list args);\ndiff --git a/sha1_file.c b/sha1_file.c\nindex d7f1838..1ed74f9 100644\n--- a/sha1_file.c\n+++ b/sha1_file.c\n@@ -405,7 +405,7 @@ void add_to_alternates_file(const char *reference)\n {\n \tstruct lock_file *lock = xcalloc(1, sizeof(struct lock_file));\n \tint fd = hold_lock_file_for_append(lock, git_path(\"objects/info/alternates\"), LOCK_DIE_ON_ERROR);\n-\tchar *alt = mkpath(\"%s\\n\", reference);\n+\tconst char *alt = mkpath(\"%s\\n\", reference);\n \twrite_or_die(fd, alt, strlen(alt));\n \tif (commit_lock_file(lock))\n \t\tdie(\"could not close alternates file\");\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252686","messageId":"1417335899-27307-4-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 03/34] git_snpath(): retire and replace with strbuf_git_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:28Z","receivedAt":"2014-11-30T08:24:28Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"In the previous patch, git_snpath() is modified to allocate a new\nstrbuf buffer because vsnpath() needs that. But that makes it\nawkward because git_snpath() receives a pre-allocated buffer from\noutside and has to copy data back. Rename it to strbuf_git_path()\nand make it receive strbuf directly.\n\nUsing git_path() in update_refs_for_switch() which used to call\ngit_snpath() is safe because that function and all of its callers do\nnot keep any pointer to the round-robin buffer pool allocated by\nget_pathname().\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/checkout.c | 13 ++++++++-----\n cache.h            |  4 ++--\n path.c             | 11 ++---------\n refs.c             | 51 ++++++++++++++++++++++++++++++++++++++++++---------\n refs.h             |  2 +-\n 5 files changed, 55 insertions(+), 26 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex c600ec1..195aca7 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -588,18 +588,21 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\tif (opts->new_orphan_branch) {\n \t\t\tif (opts->new_branch_log && !log_all_ref_updates) {\n \t\t\t\tint temp;\n-\t\t\t\tchar log_file[PATH_MAX];\n-\t\t\t\tconst char *ref_name = mkpath(\"refs/heads/%s\", opts->new_orphan_branch);\n+\t\t\t\tstruct strbuf log_file = STRBUF_INIT;\n+\t\t\t\tint ret;\n+\t\t\t\tconst char *ref_name;\n \n+\t\t\t\tref_name = mkpath(\"refs/heads/%s\", opts->new_orphan_branch);\n \t\t\t\ttemp = log_all_ref_updates;\n \t\t\t\tlog_all_ref_updates = 1;\n-\t\t\t\tif (log_ref_setup(ref_name, log_file, sizeof(log_file))) {\n+\t\t\t\tret = log_ref_setup(ref_name, &log_file);\n+\t\t\t\tlog_all_ref_updates = temp;\n+\t\t\t\tstrbuf_release(&log_file);\n+\t\t\t\tif (ret) {\n \t\t\t\t\tfprintf(stderr, _(\"Can not do reflog for '%s'\\n\"),\n \t\t\t\t\t    opts->new_orphan_branch);\n-\t\t\t\t\tlog_all_ref_updates = temp;\n \t\t\t\t\treturn;\n \t\t\t\t}\n-\t\t\t\tlog_all_ref_updates = temp;\n \t\t\t}\n \t\t}\n \t\telse\ndiff --git a/cache.h b/cache.h\nindex dbee0a3..56643cf 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -679,8 +679,8 @@ extern int check_repository_format(void);\n \n extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)\n \t__attribute__((format (printf, 3, 4)));\n-extern char *git_snpath(char *buf, size_t n, const char *fmt, ...)\n-\t__attribute__((format (printf, 3, 4)));\n+extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)\n+\t__attribute__((format (printf, 2, 3)));\n extern char *git_pathdup(const char *fmt, ...)\n \t__attribute__((format (printf, 1, 2)));\n extern char *mkpathdup(const char *fmt, ...)\ndiff --git a/path.c b/path.c\nindex a7ceea2..47753aa 100644\n--- a/path.c\n+++ b/path.c\n@@ -70,19 +70,12 @@ static void vsnpath(struct strbuf *buf, const char *fmt, va_list args)\n \tstrbuf_cleanup_path(buf);\n }\n \n-char *git_snpath(char *buf, size_t n, const char *fmt, ...)\n+void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)\n {\n-\tstruct strbuf sb = STRBUF_INIT;\n \tva_list args;\n \tva_start(args, fmt);\n-\tvsnpath(&sb, fmt, args);\n+\tvsnpath(sb, fmt, args);\n \tva_end(args);\n-\tif (sb.len >= n)\n-\t\tstrlcpy(buf, bad_path, n);\n-\telse\n-\t\tmemcpy(buf, sb.buf, sb.len + 1);\n-\tstrbuf_release(&sb);\n-\treturn buf;\n }\n \n char *git_pathdup(const char *fmt, ...)\ndiff --git a/refs.c b/refs.c\nindex 23617e0..5ed991b 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1444,7 +1444,11 @@ static int resolve_missing_loose_ref(const char *refname,\n }\n \n /* This function needs to return a meaningful errno on failure */\n-const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned char *sha1, int *flags)\n+static const char *resolve_ref_unsafe_1(const char *refname,\n+\t\t\t\t\tint resolve_flags,\n+\t\t\t\t\tunsigned char *sha1,\n+\t\t\t\t\tint *flags,\n+\t\t\t\t\tstruct strbuf *sb_path)\n {\n \tint depth = MAXDEPTH;\n \tssize_t len;\n@@ -1475,7 +1479,7 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned\n \t\tbad_name = 1;\n \t}\n \tfor (;;) {\n-\t\tchar path[PATH_MAX];\n+\t\tconst char *path;\n \t\tstruct stat st;\n \t\tchar *buf;\n \t\tint fd;\n@@ -1485,7 +1489,9 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned\n \t\t\treturn NULL;\n \t\t}\n \n-\t\tgit_snpath(path, sizeof(path), \"%s\", refname);\n+\t\tstrbuf_reset(sb_path);\n+\t\tstrbuf_git_path(sb_path, \"%s\", refname);\n+\t\tpath = sb_path->buf;\n \n \t\t/*\n \t\t * We might have to loop back here to avoid a race\n@@ -1612,6 +1618,16 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned\n \t}\n }\n \n+const char *resolve_ref_unsafe(const char *refname, int resolve_flags,\n+\t\t\t       unsigned char *sha1, int *flags)\n+{\n+\tstruct strbuf sb_path = STRBUF_INIT;\n+\tconst char *ret = resolve_ref_unsafe_1(refname, resolve_flags,\n+\t\t\t\t\t       sha1, flags, &sb_path);\n+\tstrbuf_release(&sb_path);\n+\treturn ret;\n+}\n+\n char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags)\n {\n \tconst char *ret = resolve_ref_unsafe(ref, resolve_flags, sha1, flags);\n@@ -2941,11 +2957,15 @@ static int copy_msg(char *buf, const char *msg)\n }\n \n /* This function must set a meaningful errno on failure */\n-int log_ref_setup(const char *refname, char *logfile, int bufsize)\n+int log_ref_setup(const char *refname, struct strbuf *sb_logfile)\n {\n \tint logfd, oflags = O_APPEND | O_WRONLY;\n+\tchar *logfile;\n \n-\tgit_snpath(logfile, bufsize, \"logs/%s\", refname);\n+\tstrbuf_git_path(sb_logfile, \"logs/%s\", refname);\n+\tlogfile = sb_logfile->buf;\n+\t/* make sure the rest of the function can't change \"logfile\" */\n+\tsb_logfile = NULL;\n \tif (log_all_ref_updates &&\n \t    (starts_with(refname, \"refs/heads/\") ||\n \t     starts_with(refname, \"refs/remotes/\") ||\n@@ -2990,22 +3010,26 @@ int log_ref_setup(const char *refname, char *logfile, int bufsize)\n \treturn 0;\n }\n \n-static int log_ref_write(const char *refname, const unsigned char *old_sha1,\n-\t\t\t const unsigned char *new_sha1, const char *msg)\n+static int log_ref_write_1(const char *refname, const unsigned char *old_sha1,\n+\t\t\t   const unsigned char *new_sha1, const char *msg,\n+\t\t\t   struct strbuf *sb_log_file)\n {\n \tint logfd, result, written, oflags = O_APPEND | O_WRONLY;\n \tunsigned maxlen, len;\n \tint msglen;\n-\tchar log_file[PATH_MAX];\n+\tconst char *log_file;\n \tchar *logrec;\n \tconst char *committer;\n \n \tif (log_all_ref_updates < 0)\n \t\tlog_all_ref_updates = !is_bare_repository();\n \n-\tresult = log_ref_setup(refname, log_file, sizeof(log_file));\n+\tresult = log_ref_setup(refname, sb_log_file);\n \tif (result)\n \t\treturn result;\n+\tlog_file = sb_log_file->buf;\n+\t/* make sure the rest of the function can't change \"log_file\" */\n+\tsb_log_file = NULL;\n \n \tlogfd = open(log_file, oflags);\n \tif (logfd < 0)\n@@ -3038,6 +3062,15 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,\n \treturn 0;\n }\n \n+static int log_ref_write(const char *refname, const unsigned char *old_sha1,\n+\t\t\t const unsigned char *new_sha1, const char *msg)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tint ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb);\n+\tstrbuf_release(&sb);\n+\treturn ret;\n+}\n+\n int is_branch(const char *refname)\n {\n \treturn !strcmp(refname, \"HEAD\") || starts_with(refname, \"refs/heads/\");\ndiff --git a/refs.h b/refs.h\nindex 2bc3556..1f88220 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -210,7 +210,7 @@ extern void unlock_ref(struct ref_lock *lock);\n /*\n  * Setup reflog before using. Set errno to something meaningful on failure.\n  */\n-int log_ref_setup(const char *refname, char *logfile, int bufsize);\n+int log_ref_setup(const char *refname, struct strbuf *logfile);\n \n /** Reads log for the value of ref during at_time. **/\n extern int read_ref_at(const char *refname, unsigned int flags,\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252687","messageId":"1417335899-27307-5-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 04/34] path.c: rename vsnpath() to do_git_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:29Z","receivedAt":"2014-11-30T08:24:29Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The name vsnpath() gives an impression that this is general path\nhandling function. It's not. This is the underlying implementation of\ngit_path(), git_pathdup() and strbuf_git_path() which will prefix\n$GIT_DIR in the result string.\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 47753aa..6991103 100644\n--- a/path.c\n+++ b/path.c\n@@ -60,7 +60,7 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)\n \treturn cleanup_path(buf);\n }\n \n-static void vsnpath(struct strbuf *buf, const char *fmt, va_list args)\n+static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)\n {\n \tconst char *git_dir = get_git_dir();\n \tstrbuf_addstr(buf, git_dir);\n@@ -74,7 +74,7 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)\n {\n \tva_list args;\n \tva_start(args, fmt);\n-\tvsnpath(sb, fmt, args);\n+\tdo_git_path(sb, fmt, args);\n \tva_end(args);\n }\n \n@@ -83,7 +83,7 @@ char *git_pathdup(const char *fmt, ...)\n \tstruct strbuf path = STRBUF_INIT;\n \tva_list args;\n \tva_start(args, fmt);\n-\tvsnpath(&path, fmt, args);\n+\tdo_git_path(&path, fmt, args);\n \tva_end(args);\n \treturn strbuf_detach(&path, NULL);\n }\n@@ -114,7 +114,7 @@ const char *git_path(const char *fmt, ...)\n \tstruct strbuf *pathname = get_pathname();\n \tva_list args;\n \tva_start(args, fmt);\n-\tvsnpath(pathname, fmt, args);\n+\tdo_git_path(pathname, fmt, args);\n \tva_end(args);\n \treturn pathname->buf;\n }\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252688","messageId":"1417335899-27307-6-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 05/34] path.c: group git_path(), git_pathdup() and strbuf_git_path() together","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:30Z","receivedAt":"2014-11-30T08:24: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 path.c | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/path.c b/path.c\nindex 6991103..df0f75b 100644\n--- a/path.c\n+++ b/path.c\n@@ -78,6 +78,16 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)\n \tva_end(args);\n }\n \n+const char *git_path(const char *fmt, ...)\n+{\n+\tstruct strbuf *pathname = get_pathname();\n+\tva_list args;\n+\tva_start(args, fmt);\n+\tdo_git_path(pathname, fmt, args);\n+\tva_end(args);\n+\treturn pathname->buf;\n+}\n+\n char *git_pathdup(const char *fmt, ...)\n {\n \tstruct strbuf path = STRBUF_INIT;\n@@ -109,16 +119,6 @@ const char *mkpath(const char *fmt, ...)\n \treturn cleanup_path(pathname->buf);\n }\n \n-const char *git_path(const char *fmt, ...)\n-{\n-\tstruct strbuf *pathname = get_pathname();\n-\tva_list args;\n-\tva_start(args, fmt);\n-\tdo_git_path(pathname, fmt, args);\n-\tva_end(args);\n-\treturn pathname->buf;\n-}\n-\n void home_config_paths(char **global, char **xdg, char *file)\n {\n \tchar *xdg_home = getenv(\"XDG_CONFIG_HOME\");\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252689","messageId":"1417335899-27307-7-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 06/34] git_path(): be aware of file relocation in $GIT_DIR","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:31Z","receivedAt":"2014-11-30T08:24:31Z","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. 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 (for\nexample, 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 /foo/bar/abc. The same is done\nfor the two remaining env variables.\n\n\"git rev-parse --git-path\" is the wrapper for script use.\n\nThis patch kinda reverts a0279e1 (setup_git_env: use git_pathdup\ninstead of xmalloc + sprintf - 2014-06-19) because using git_pathdup\nhere would result in infinite recursion:\n\n  setup_git_env() -> git_pathdup(\"objects\") -> .. -> adjust_git_path()\n  -> get_object_directory() -> oops, git_object_directory is NOT set\n  yet -> setup_git_env()\n\nI wanted to make git_pathdup_literal() that skips adjust_git_path().\nBut that won't work because later on when $GIT_COMMON_DIR is\nintroduced, git_pathdup_literal(\"objects\") needs adjust_git_path() to\nreplace $GIT_DIR with $GIT_COMMON_DIR.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-rev-parse.txt |  7 ++++++\n builtin/rev-parse.c             |  7 ++++++\n cache.h                         |  1 +\n environment.c                   | 19 +++++++++++-----\n path.c                          | 49 +++++++++++++++++++++++++++++++++++++++--\n t/t0060-path-utils.sh           | 19 ++++++++++++++++\n 6 files changed, 95 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt\nindex d6de42f..2103bbf 100644\n--- a/Documentation/git-rev-parse.txt\n+++ b/Documentation/git-rev-parse.txt\n@@ -233,6 +233,13 @@ print a message to stderr and exit with nonzero status.\n \trepository.  If <path> is a gitfile then the resolved path\n \tto the real repository is printed.\n \n+--git-path <path>::\n+\tResolve \"$GIT_DIR/<path>\" and takes other path relocation\n+\tvariables such as $GIT_OBJECT_DIRECTORY,\n+\t$GIT_INDEX_FILE... into account. For example, if\n+\t$GIT_OBJECT_DIRECTORY is set to /foo/bar then \"git rev-parse\n+\t--git-path objects/abc\" returns /foo/bar/abc.\n+\n --show-cdup::\n \tWhen the command is invoked from a subdirectory, show the\n \tpath of the top-level directory relative to the current\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex 35d3c43..4cd44aa 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -533,6 +533,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n \tfor (i = 1; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n \n+\t\tif (!strcmp(arg, \"--git-path\")) {\n+\t\t\tif (!argv[i + 1])\n+\t\t\t\tdie(\"--git-path requires an argument\");\n+\t\t\tputs(git_path(\"%s\", argv[i + 1]));\n+\t\t\ti++;\n+\t\t\tcontinue;\n+\t\t}\n \t\tif (as_is) {\n \t\t\tif (show_file(arg, output_prefix) && as_is < 2)\n \t\t\t\tverify_filename(prefix, arg, 0);\ndiff --git a/cache.h b/cache.h\nindex 56643cf..7da2ffb 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -617,6 +617,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 565f652..fee12a6 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -83,6 +83,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@@ -124,10 +125,18 @@ static char *expand_namespace(const char *raw_namespace)\n \treturn strbuf_detach(&buf, NULL);\n }\n \n-static char *git_path_from_env(const char *envvar, const char *path)\n+static char *git_path_from_env(const char *envvar, const char *path,\n+\t\t\t       int *fromenv)\n {\n \tconst char *value = getenv(envvar);\n-\treturn value ? xstrdup(value) : git_pathdup(\"%s\", path);\n+\tif (!value) {\n+\t\tchar *buf = xmalloc(strlen(git_dir) + strlen(path) + 2);\n+\t\tsprintf(buf, \"%s/%s\", git_dir, path);\n+\t\treturn buf;\n+\t}\n+\tif (fromenv)\n+\t\t*fromenv = 1;\n+\treturn xstrdup(value);\n }\n \n static void setup_git_env(void)\n@@ -140,9 +149,9 @@ 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_object_dir = git_path_from_env(DB_ENVIRONMENT, \"objects\");\n-\tgit_index_file = git_path_from_env(INDEX_ENVIRONMENT, \"index\");\n-\tgit_graft_file = git_path_from_env(GRAFT_ENVIRONMENT, \"info/grafts\");\n+\tgit_object_dir = git_path_from_env(DB_ENVIRONMENT, \"objects\", &git_db_env);\n+\tgit_index_file = git_path_from_env(INDEX_ENVIRONMENT, \"index\", &git_index_env);\n+\tgit_graft_file = git_path_from_env(GRAFT_ENVIRONMENT, \"info/grafts\", &git_graft_env);\n \tif (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))\n \t\tcheck_replace_refs = 0;\n \tnamespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));\ndiff --git a/path.c b/path.c\nindex df0f75b..4910783 100644\n--- a/path.c\n+++ b/path.c\n@@ -60,13 +60,58 @@ 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+/* $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(struct strbuf *buf, int len, const char *newdir)\n+{\n+\tint newlen = strlen(newdir);\n+\tint need_sep = (buf->buf[len] && !is_dir_sep(buf->buf[len])) &&\n+\t\t!is_dir_sep(newdir[newlen - 1]);\n+\tif (need_sep)\n+\t\tlen--;\t /* keep one char, to be replaced with '/'  */\n+\tstrbuf_splice(buf, 0, len, newdir, newlen);\n+\tif (need_sep)\n+\t\tbuf->buf[newlen] = '/';\n+}\n+\n+static void adjust_git_path(struct strbuf *buf, int git_dir_len)\n+{\n+\tconst char *base = buf->buf + git_dir_len;\n+\tif (git_graft_env && is_dir_file(base, \"info\", \"grafts\"))\n+\t\tstrbuf_splice(buf, 0, buf->len,\n+\t\t\t      get_graft_file(), strlen(get_graft_file()));\n+\telse if (git_index_env && !strcmp(base, \"index\"))\n+\t\tstrbuf_splice(buf, 0, buf->len,\n+\t\t\t      get_index_file(), strlen(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 void do_git_path(struct strbuf *buf, const char *fmt, va_list args)\n {\n-\tconst char *git_dir = get_git_dir();\n-\tstrbuf_addstr(buf, git_dir);\n+\tint gitdir_len;\n+\tstrbuf_addstr(buf, get_git_dir());\n \tif (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))\n \t\tstrbuf_addch(buf, '/');\n+\tgitdir_len = buf->len;\n \tstrbuf_vaddf(buf, fmt, args);\n+\tadjust_git_path(buf, gitdir_len);\n \tstrbuf_cleanup_path(buf);\n }\n \ndiff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh\nindex c0143a0..33d2818 100755\n--- a/t/t0060-path-utils.sh\n+++ b/t/t0060-path-utils.sh\n@@ -19,6 +19,14 @@ relative_path() {\n \t\"test \\\"\\$(test-path-utils relative_path '$1' '$2')\\\" = '$expected'\"\n }\n \n+test_git_path() {\n+\ttest_expect_success \"git-path $1 $2 => $3\" \"\n+\t\t$1 git rev-parse --git-path $2 >actual &&\n+\t\techo $3 >expect &&\n+\t\ttest_cmp expect actual\n+\t\"\n+}\n+\n # On Windows, we are using MSYS's bash, which mangles the paths.\n # Absolute paths are anchored at the MSYS installation directory,\n # which means that the path / accounts for this many characters:\n@@ -244,4 +252,15 @@ 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_git_path A=B                info/grafts .git/info/grafts\n+test_git_path GIT_GRAFT_FILE=foo info/grafts foo\n+test_git_path GIT_GRAFT_FILE=foo info/////grafts foo\n+test_git_path GIT_INDEX_FILE=foo index foo\n+test_git_path GIT_INDEX_FILE=foo index/foo .git/index/foo\n+test_git_path GIT_INDEX_FILE=foo index2 .git/index2\n+test_expect_success 'setup fake objects directory foo' 'mkdir foo'\n+test_git_path GIT_OBJECT_DIRECTORY=foo objects foo\n+test_git_path GIT_OBJECT_DIRECTORY=foo objects/foo foo/foo\n+test_git_path GIT_OBJECT_DIRECTORY=foo objects2 .git/objects2\n+\n test_done\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252690","messageId":"1417335899-27307-8-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 07/34] *.sh: respect $GIT_INDEX_FILE","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:32Z","receivedAt":"2014-11-30T08:24:32Z","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 git-pull.sh  | 2 +-\n git-stash.sh | 2 +-\n 2 files changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/git-pull.sh b/git-pull.sh\nindex 4d4fc77..ad44226 100755\n--- a/git-pull.sh\n+++ b/git-pull.sh\n@@ -240,7 +240,7 @@ test true = \"$rebase\" && {\n \tif ! git rev-parse -q --verify HEAD >/dev/null\n \tthen\n \t\t# On an unborn branch\n-\t\tif test -f \"$GIT_DIR/index\"\n+\t\tif test -f \"$(git rev-parse --git-path index)\"\n \t\tthen\n \t\t\tdie \"$(gettext \"updating an unborn branch with changes added to the index\")\"\n \t\tfi\ndiff --git a/git-stash.sh b/git-stash.sh\nindex d4cf818..b6d4b06 100755\n--- a/git-stash.sh\n+++ b/git-stash.sh\n@@ -20,7 +20,7 @@ require_work_tree\n cd_to_toplevel\n \n TMP=\"$GIT_DIR/.git-stash.$$\"\n-TMPindex=${GIT_INDEX_FILE-\"$GIT_DIR/index\"}.stash.$$\n+TMPindex=${GIT_INDEX_FILE-\"$(git rev-parse --git-path index)\"}.stash.$$\n trap 'rm -f \"$TMP-\"* \"$TMPindex\"' 0\n \n ref_stash=refs/stash\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252691","messageId":"1417335899-27307-9-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 08/34] reflog: avoid constructing .lock path with git_path","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:33Z","receivedAt":"2014-11-30T08:24:33Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Among pathnames in $GIT_DIR, e.g. \"index\" or \"packed-refs\", we want to\nautomatically and silently map some of them to the $GIT_DIR of the\nrepository we are borrowing from via $GIT_COMMON_DIR mechanism.  When\nwe formulate the pathname for its lockfile, we want it to be in the\nsame location as its final destination.  \"index\" is not shared and\nneeds to remain in the borrowing repository, while \"packed-refs\" is\nshared and needs to go to the borrowed repository.\n\ngit_path() could be taught about the \".lock\" suffix and map\n\"index.lock\" and \"packed-refs.lock\" the same way their basenames are\nmapped, but instead the caller can help by asking where the basename\n(e.g. \"index\") is mapped to git_path() and then appending \".lock\"\nafter the mapping is done.\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 2d85d26..fb07c66 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 (!reflog_exists(ref))\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-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252694","messageId":"1417335899-27307-10-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 09/34] 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":"2014-11-30T08:24:34Z","receivedAt":"2014-11-30T08:24:34Z","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 3018130..1b50923 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -3112,12 +3112,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-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252692","messageId":"1417335899-27307-11-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 10/34] commit: use SEQ_DIR instead of hardcoding \"sequencer\"","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:35Z","receivedAt":"2014-11-30T08:24:35Z","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/commit.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex e108c53..3f02686 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -170,7 +170,7 @@ static void determine_whence(struct wt_status *s)\n \t\twhence = FROM_MERGE;\n \telse if (file_exists(git_path(\"CHERRY_PICK_HEAD\"))) {\n \t\twhence = FROM_CHERRY_PICK;\n-\t\tif (file_exists(git_path(\"sequencer\")))\n+\t\tif (file_exists(git_path(SEQ_DIR)))\n \t\t\tsequencer_in_use = 1;\n \t}\n \telse\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252693","messageId":"1417335899-27307-12-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 11/34] $GIT_COMMON_DIR: a new environment variable","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:36Z","receivedAt":"2014-11-30T08:24:36Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This variable is intended to support multiple working directories\nattached to a repository. Such a repository may have a main working\ndirectory, created by either \"git init\" or \"git clone\" and one or more\nlinked working directories. These working directories and the main\nrepository share the same repository directory.\n\nIn linked working directories, $GIT_COMMON_DIR must be defined to point\nto the real repository directory and $GIT_DIR points to an unused\nsubdirectory inside $GIT_COMMON_DIR. File locations inside the\nrepository are reorganized from the linked worktree view point:\n\n - worktree-specific such as HEAD, logs/HEAD, index, other top-level\n   refs and unrecognized files are from $GIT_DIR.\n\n - the rest like objects, refs, info, hooks, packed-refs, shallow...\n   are from $GIT_COMMON_DIR (except info/sparse-checkout, but that's\n   a separate patch)\n\nScripts are supposed to retrieve paths in $GIT_DIR with \"git rev-parse\n--git-path\", which will take care of \"$GIT_DIR vs $GIT_COMMON_DIR\"\nbusiness.\n\nThe redirection is done by git_path(), git_pathdup() and\nstrbuf_git_path(). The selected list of paths goes to $GIT_COMMON_DIR,\nnot the other way around in case a developer adds a new\nworktree-specific file and it's accidentally promoted to be shared\nacross repositories (this includes unknown files added by third party\ncommands)\n\nThe list of known files that belong to $GIT_DIR are:\n\nADD_EDIT.patch BISECT_ANCESTORS_OK BISECT_EXPECTED_REV BISECT_LOG\nBISECT_NAMES CHERRY_PICK_HEAD COMMIT_MSG FETCH_HEAD HEAD MERGE_HEAD\nMERGE_MODE MERGE_RR NOTES_EDITMSG NOTES_MERGE_WORKTREE ORIG_HEAD\nREVERT_HEAD SQUASH_MSG TAG_EDITMSG fast_import_crash_* logs/HEAD\nnext-index-* rebase-apply rebase-merge rsync-refs-* sequencer/*\nshallow_*\n\nPath mapping is NOT done for git_path_submodule(). Multi-checkouts are\nnot supported as submodules.\n\nHelped-by: Jens Lehmann <Jens.Lehmann@web.de>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git.txt                  |  8 ++++++\n Documentation/gitrepository-layout.txt | 45 +++++++++++++++++++++++++---------\n cache.h                                |  4 ++-\n environment.c                          | 29 ++++++++++++++++------\n path.c                                 | 34 +++++++++++++++++++++++++\n t/t0060-path-utils.sh                  | 16 ++++++++++++\n 6 files changed, 117 insertions(+), 19 deletions(-)\n\ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex afb48d3..96354af 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -809,6 +809,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_COMMON_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. See linkgit:gitrepository-layout[5] for\n+\tdetails. This variable has lower precedence than other path\n+\tvariables such as GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY...\n+\n Git Commits\n ~~~~~~~~~~~\n 'GIT_AUTHOR_NAME'::\ndiff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt\nindex 79653f3..2b5966a 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -46,6 +46,9 @@ of incomplete object store is not suitable to be published for\n use with dumb transports but otherwise is OK as long as\n `objects/info/alternates` points at the object stores it\n borrows from.\n++\n+This directory is ignored if $GIT_COMMON_DIR is set and\n+\"$GIT_COMMON_DIR/objects\" will be used instead.\n \n objects/[0-9a-f][0-9a-f]::\n \tA newly created object is stored in its own file.\n@@ -92,7 +95,8 @@ refs::\n \tReferences are stored in subdirectories of this\n \tdirectory.  The 'git prune' command knows to preserve\n \tobjects reachable from refs found in this directory and\n-\tits subdirectories.\n+\tits subdirectories. This directory is ignored if $GIT_COMMON_DIR\n+\tis set and \"$GIT_COMMON_DIR/refs\" will be used instead.\n \n refs/heads/`name`::\n \trecords tip-of-the-tree commit objects of branch `name`\n@@ -114,7 +118,8 @@ refs/replace/`<obj-sha1>`::\n packed-refs::\n \trecords the same information as refs/heads/, refs/tags/,\n \tand friends record in a more efficient way.  See\n-\tlinkgit:git-pack-refs[1].\n+\tlinkgit:git-pack-refs[1]. This file is ignored if $GIT_COMMON_DIR\n+\tis set and \"$GIT_COMMON_DIR/packed-refs\" will be used instead.\n \n HEAD::\n \tA symref (see glossary) to the `refs/heads/` namespace\n@@ -133,6 +138,11 @@ being a symref to point at the current branch.  Such a state\n is often called 'detached HEAD.'  See linkgit:git-checkout[1]\n for details.\n \n+config::\n+\tRepository specific configuration file. This file is ignored\n+\tif $GIT_COMMON_DIR is set and \"$GIT_COMMON_DIR/config\" will be\n+\tused instead.\n+\n branches::\n \tA slightly deprecated way to store shorthands to be used\n \tto specify a URL to 'git fetch', 'git pull' and 'git push'.\n@@ -140,7 +150,10 @@ branches::\n \t'name' can be given to these commands in place of\n \t'repository' argument.  See the REMOTES section in\n \tlinkgit:git-fetch[1] for details.  This mechanism is legacy\n-\tand not likely to be found in modern repositories.\n+\tand not likely to be found in modern repositories. This\n+\tdirectory is ignored if $GIT_COMMON_DIR is set and\n+\t\"$GIT_COMMON_DIR/branches\" will be used instead.\n+\n \n hooks::\n \tHooks are customization scripts used by various Git\n@@ -149,7 +162,9 @@ hooks::\n \tdefault.  To enable, the `.sample` suffix has to be\n \tremoved from the filename by renaming.\n \tRead linkgit:githooks[5] for more details about\n-\teach hook.\n+\teach hook. This directory is ignored if $GIT_COMMON_DIR is set\n+\tand \"$GIT_COMMON_DIR/hooks\" will be used instead.\n+\n \n index::\n \tThe current index file for the repository.  It is\n@@ -161,7 +176,8 @@ sharedindex.<SHA-1>::\n \n info::\n \tAdditional information about the repository is recorded\n-\tin this directory.\n+\tin this directory. This directory is ignored if $GIT_COMMON_DIR\n+\tis set and \"$GIT_COMMON_DIR/index\" will be used instead.\n \n info/refs::\n \tThis file helps dumb transports discover what refs are\n@@ -201,12 +217,15 @@ remotes::\n \twhen interacting with remote repositories via 'git fetch',\n \t'git pull' and 'git push' commands.  See the REMOTES section\n \tin linkgit:git-fetch[1] for details.  This mechanism is legacy\n-\tand not likely to be found in modern repositories.\n+\tand not likely to be found in modern repositories. This\n+\tdirectory is ignored if $GIT_COMMON_DIR is set and\n+\t\"$GIT_COMMON_DIR/remotes\" will be used instead.\n \n logs::\n-\tRecords of changes made to refs are stored in this\n-\tdirectory.  See linkgit:git-update-ref[1]\n-\tfor more information.\n+\tRecords of changes made to refs are stored in this directory.\n+\tSee linkgit:git-update-ref[1] for more information. This\n+\tdirectory is ignored if $GIT_COMMON_DIR is set and\n+\t\"$GIT_COMMON_DIR/logs\" will be used instead.\n \n logs/refs/heads/`name`::\n \tRecords all changes made to the branch tip named `name`.\n@@ -217,10 +236,14 @@ logs/refs/tags/`name`::\n shallow::\n \tThis is similar to `info/grafts` but is internally used\n \tand maintained by shallow clone mechanism.  See `--depth`\n-\toption to linkgit:git-clone[1] and linkgit:git-fetch[1].\n+\toption to linkgit:git-clone[1] and linkgit:git-fetch[1]. This\n+\tfile is ignored if $GIT_COMMON_DIR is set and\n+\t\"$GIT_COMMON_DIR/shallow\" will be used instead.\n \n modules::\n-\tContains the git-repositories of the submodules.\n+\tContains the git-repositories of the submodules. This\n+\tdirectory is ignored if $GIT_COMMON_DIR is set and\n+\t\"$GIT_COMMON_DIR/modules\" will be used instead.\n \n SEE ALSO\n --------\ndiff --git a/cache.h b/cache.h\nindex 7da2ffb..bce1a7d 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -377,6 +377,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_COMMON_DIR_ENVIRONMENT \"GIT_COMMON_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@@ -430,6 +431,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_common_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@@ -617,7 +619,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+extern int git_db_env, git_index_env, git_graft_env, git_common_dir_env;\n \n /*\n  * The character that begins a commented line in user-editable file\ndiff --git a/environment.c b/environment.c\nindex fee12a6..050f160 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -81,9 +81,9 @@ 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_common_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+int git_db_env, git_index_env, git_graft_env, git_common_dir_env;\n \n /*\n  * Repository-local GIT_* environment variables; see cache.h for details.\n@@ -101,6 +101,7 @@ const char * const local_repo_env[] = {\n \tNO_REPLACE_OBJECTS_ENVIRONMENT,\n \tGIT_PREFIX_ENVIRONMENT,\n \tGIT_SHALLOW_FILE_ENVIRONMENT,\n+\tGIT_COMMON_DIR_ENVIRONMENT,\n \tNULL\n };\n \n@@ -125,8 +126,8 @@ static char *expand_namespace(const char *raw_namespace)\n \treturn strbuf_detach(&buf, NULL);\n }\n \n-static char *git_path_from_env(const char *envvar, const char *path,\n-\t\t\t       int *fromenv)\n+static char *git_path_from_env(const char *envvar, const char *git_dir,\n+\t\t\t       const char *path, int *fromenv)\n {\n \tconst char *value = getenv(envvar);\n \tif (!value) {\n@@ -149,9 +150,18 @@ 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_object_dir = git_path_from_env(DB_ENVIRONMENT, \"objects\", &git_db_env);\n-\tgit_index_file = git_path_from_env(INDEX_ENVIRONMENT, \"index\", &git_index_env);\n-\tgit_graft_file = git_path_from_env(GRAFT_ENVIRONMENT, \"info/grafts\", &git_graft_env);\n+\tgit_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);\n+\tif (git_common_dir) {\n+\t\tgit_common_dir_env = 1;\n+\t\tgit_common_dir = xstrdup(git_common_dir);\n+\t} else\n+\t\tgit_common_dir = git_dir;\n+\tgit_object_dir = git_path_from_env(DB_ENVIRONMENT, git_common_dir,\n+\t\t\t\t\t   \"objects\", &git_db_env);\n+\tgit_index_file = git_path_from_env(INDEX_ENVIRONMENT, git_dir,\n+\t\t\t\t\t   \"index\", &git_index_env);\n+\tgit_graft_file = git_path_from_env(GRAFT_ENVIRONMENT, git_common_dir,\n+\t\t\t\t\t   \"info/grafts\", &git_graft_env);\n \tif (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))\n \t\tcheck_replace_refs = 0;\n \tnamespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));\n@@ -174,6 +184,11 @@ const char *get_git_dir(void)\n \treturn git_dir;\n }\n \n+const char *get_git_common_dir(void)\n+{\n+\treturn git_common_dir;\n+}\n+\n const char *get_git_namespace(void)\n {\n \tif (!namespace)\ndiff --git a/path.c b/path.c\nindex 4910783..94db501 100644\n--- a/path.c\n+++ b/path.c\n@@ -90,6 +90,38 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)\n \t\tbuf->buf[newlen] = '/';\n }\n \n+static const char *common_list[] = {\n+\t\"/branches\", \"/hooks\", \"/info\", \"/logs\", \"/lost-found\", \"/modules\",\n+\t\"/objects\", \"/refs\", \"/remotes\", \"/rr-cache\", \"/svn\",\n+\t\"config\", \"gc.pid\", \"packed-refs\", \"shallow\",\n+\tNULL\n+};\n+\n+static void update_common_dir(struct strbuf *buf, int git_dir_len)\n+{\n+\tchar *base = buf->buf + git_dir_len;\n+\tconst char **p;\n+\n+\tif (is_dir_file(base, \"logs\", \"HEAD\"))\n+\t\treturn;\t/* keep this in $GIT_DIR */\n+\tfor (p = common_list; *p; p++) {\n+\t\tconst char *path = *p;\n+\t\tint is_dir = 0;\n+\t\tif (*path == '/') {\n+\t\t\tpath++;\n+\t\t\tis_dir = 1;\n+\t\t}\n+\t\tif (is_dir && dir_prefix(base, path)) {\n+\t\t\treplace_dir(buf, git_dir_len, get_git_common_dir());\n+\t\t\treturn;\n+\t\t}\n+\t\tif (!is_dir && !strcmp(base, path)) {\n+\t\t\treplace_dir(buf, git_dir_len, get_git_common_dir());\n+\t\t\treturn;\n+\t\t}\n+\t}\n+}\n+\n static void adjust_git_path(struct strbuf *buf, int git_dir_len)\n {\n \tconst char *base = buf->buf + git_dir_len;\n@@ -101,6 +133,8 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)\n \t\t\t      get_index_file(), strlen(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 (git_common_dir_env)\n+\t\tupdate_common_dir(buf, git_dir_len);\n }\n \n static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)\ndiff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh\nindex 33d2818..f5d6f80 100755\n--- a/t/t0060-path-utils.sh\n+++ b/t/t0060-path-utils.sh\n@@ -262,5 +262,21 @@ test_expect_success 'setup fake objects directory foo' 'mkdir foo'\n test_git_path GIT_OBJECT_DIRECTORY=foo objects foo\n test_git_path GIT_OBJECT_DIRECTORY=foo objects/foo foo/foo\n test_git_path GIT_OBJECT_DIRECTORY=foo objects2 .git/objects2\n+test_expect_success 'setup common repository' 'git --git-dir=bar init'\n+test_git_path GIT_COMMON_DIR=bar index                    .git/index\n+test_git_path GIT_COMMON_DIR=bar HEAD                     .git/HEAD\n+test_git_path GIT_COMMON_DIR=bar logs/HEAD                .git/logs/HEAD\n+test_git_path GIT_COMMON_DIR=bar objects                  bar/objects\n+test_git_path GIT_COMMON_DIR=bar objects/bar              bar/objects/bar\n+test_git_path GIT_COMMON_DIR=bar info/exclude             bar/info/exclude\n+test_git_path GIT_COMMON_DIR=bar info/grafts              bar/info/grafts\n+test_git_path GIT_COMMON_DIR=bar remotes/bar              bar/remotes/bar\n+test_git_path GIT_COMMON_DIR=bar branches/bar             bar/branches/bar\n+test_git_path GIT_COMMON_DIR=bar logs/refs/heads/master   bar/logs/refs/heads/master\n+test_git_path GIT_COMMON_DIR=bar refs/heads/master        bar/refs/heads/master\n+test_git_path GIT_COMMON_DIR=bar hooks/me                 bar/hooks/me\n+test_git_path GIT_COMMON_DIR=bar config                   bar/config\n+test_git_path GIT_COMMON_DIR=bar packed-refs              bar/packed-refs\n+test_git_path GIT_COMMON_DIR=bar shallow                  bar/shallow\n \n test_done\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252695","messageId":"1417335899-27307-13-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 12/34] git-sh-setup.sh: use rev-parse --git-path to get $GIT_DIR/objects","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:37Z","receivedAt":"2014-11-30T08:24:37Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"If $GIT_COMMON_DIR is set, $GIT_OBJECT_DIRECTORY should be\n$GIT_COMMON_DIR/objects, not $GIT_DIR/objects. Just let rev-parse\n--git-path handle it.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n git-sh-setup.sh | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/git-sh-setup.sh b/git-sh-setup.sh\nindex d968760..94a5c6d 100644\n--- a/git-sh-setup.sh\n+++ b/git-sh-setup.sh\n@@ -344,7 +344,7 @@ git_dir_init () {\n \t\techo >&2 \"Unable to determine absolute path of git directory\"\n \t\texit 1\n \t}\n-\t: ${GIT_OBJECT_DIRECTORY=\"$GIT_DIR/objects\"}\n+\t: ${GIT_OBJECT_DIRECTORY=\"$(git rev-parse --git-path objects)\"}\n }\n \n if test -z \"$NONGIT_OK\"\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252696","messageId":"1417335899-27307-14-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 13/34] *.sh: avoid hardcoding $GIT_DIR/hooks/...","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:38Z","receivedAt":"2014-11-30T08:24:38Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"If $GIT_COMMON_DIR is set, it should be $GIT_COMMON_DIR/hooks/, not\n$GIT_DIR/hooks/. Just let rev-parse --git-path handle it.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n git-am.sh                              | 22 +++++++++++-----------\n git-rebase--interactive.sh             |  6 +++---\n git-rebase--merge.sh                   |  6 ++----\n git-rebase.sh                          |  4 ++--\n templates/hooks--applypatch-msg.sample |  4 ++--\n templates/hooks--pre-applypatch.sample |  4 ++--\n 6 files changed, 22 insertions(+), 24 deletions(-)\n\ndiff --git a/git-am.sh b/git-am.sh\nindex ee61a77..66803d1 100755\n--- a/git-am.sh\n+++ b/git-am.sh\n@@ -810,10 +810,10 @@ To restore the original branch and stop patching run \\\"\\$cmdline --abort\\\".\"\n \t\tcontinue\n \tfi\n \n-\tif test -x \"$GIT_DIR\"/hooks/applypatch-msg\n+\thook=\"$(git rev-parse --git-path hooks/applypatch-msg)\"\n+\tif test -x \"$hook\"\n \tthen\n-\t\t\"$GIT_DIR\"/hooks/applypatch-msg \"$dotest/final-commit\" ||\n-\t\tstop_here $this\n+\t\t\"$hook\" \"$dotest/final-commit\" || stop_here $this\n \tfi\n \n \tif test -f \"$dotest/final-commit\"\n@@ -887,9 +887,10 @@ did you forget to use 'git add'?\"\n \t\tstop_here_user_resolve $this\n \tfi\n \n-\tif test -x \"$GIT_DIR\"/hooks/pre-applypatch\n+\thook=\"$(git rev-parse --git-path hooks/pre-applypatch)\"\n+\tif test -x \"$hook\"\n \tthen\n-\t\t\"$GIT_DIR\"/hooks/pre-applypatch || stop_here $this\n+\t\t\"$hook\" || stop_here $this\n \tfi\n \n \ttree=$(git write-tree) &&\n@@ -916,18 +917,17 @@ did you forget to use 'git add'?\"\n \t\techo \"$(cat \"$dotest/original-commit\") $commit\" >> \"$dotest/rewritten\"\n \tfi\n \n-\tif test -x \"$GIT_DIR\"/hooks/post-applypatch\n-\tthen\n-\t\t\"$GIT_DIR\"/hooks/post-applypatch\n-\tfi\n+\thook=\"$(git rev-parse --git-path hooks/post-applypatch)\"\n+\ttest -x \"$hook\" && \"$hook\"\n \n \tgo_next\n done\n \n if test -s \"$dotest\"/rewritten; then\n     git notes copy --for-rewrite=rebase < \"$dotest\"/rewritten\n-    if test -x \"$GIT_DIR\"/hooks/post-rewrite; then\n-\t\"$GIT_DIR\"/hooks/post-rewrite rebase < \"$dotest\"/rewritten\n+    hook=\"$(git rev-parse --git-path hooks/post-rewrite)\"\n+    if test -x \"$hook\"; then\n+\t\"$hook\" rebase < \"$dotest\"/rewritten\n     fi\n fi\n \ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex b64dd28..b32f797 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -642,9 +642,9 @@ do_next () {\n \t\tgit notes copy --for-rewrite=rebase < \"$rewritten_list\" ||\n \t\ttrue # we don't care if this copying failed\n \t} &&\n-\tif test -x \"$GIT_DIR\"/hooks/post-rewrite &&\n-\t\ttest -s \"$rewritten_list\"; then\n-\t\t\"$GIT_DIR\"/hooks/post-rewrite rebase < \"$rewritten_list\"\n+\thook=\"$(git rev-parse --git-path hooks/post-rewrite)\"\n+\tif test -x \"$hook\" && test -s \"$rewritten_list\"; then\n+\t\t\"$hook\" rebase < \"$rewritten_list\"\n \t\ttrue # we don't care if this hook failed\n \tfi &&\n \twarn \"Successfully rebased and updated $head_name.\"\ndiff --git a/git-rebase--merge.sh b/git-rebase--merge.sh\nindex d3fb67d..2cc2a6d 100644\n--- a/git-rebase--merge.sh\n+++ b/git-rebase--merge.sh\n@@ -94,10 +94,8 @@ finish_rb_merge () {\n \tif test -s \"$state_dir\"/rewritten\n \tthen\n \t\tgit notes copy --for-rewrite=rebase <\"$state_dir\"/rewritten\n-\t\tif test -x \"$GIT_DIR\"/hooks/post-rewrite\n-\t\tthen\n-\t\t\t\"$GIT_DIR\"/hooks/post-rewrite rebase <\"$state_dir\"/rewritten\n-\t\tfi\n+\t\thook=\"$(git rev-parse --git-path hooks/post-rewrite)\"\n+\t\ttest -x \"$hook\" && \"$hook\" rebase <\"$state_dir\"/rewritten\n \tfi\n \tsay All done.\n }\ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex 55da9db..fb935a0 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -202,9 +202,9 @@ run_specific_rebase () {\n \n run_pre_rebase_hook () {\n \tif test -z \"$ok_to_skip_pre_rebase\" &&\n-\t   test -x \"$GIT_DIR/hooks/pre-rebase\"\n+\t   test -x \"$(git rev-parse --git-path hooks/pre-rebase)\"\n \tthen\n-\t\t\"$GIT_DIR/hooks/pre-rebase\" ${1+\"$@\"} ||\n+\t\t\"$(git rev-parse --git-path hooks/pre-rebase)\" ${1+\"$@\"} ||\n \t\tdie \"$(gettext \"The pre-rebase hook refused to rebase.\")\"\n \tfi\n }\ndiff --git a/templates/hooks--applypatch-msg.sample b/templates/hooks--applypatch-msg.sample\nindex 8b2a2fe..a5d7b84 100755\n--- a/templates/hooks--applypatch-msg.sample\n+++ b/templates/hooks--applypatch-msg.sample\n@@ -10,6 +10,6 @@\n # To enable this hook, rename this file to \"applypatch-msg\".\n \n . git-sh-setup\n-test -x \"$GIT_DIR/hooks/commit-msg\" &&\n-\texec \"$GIT_DIR/hooks/commit-msg\" ${1+\"$@\"}\n+commitmsg=\"$(git rev-parse --git-path hooks/commit-msg)\"\n+test -x \"$commitmsg\" && exec \"$commitmsg\" ${1+\"$@\"}\n :\ndiff --git a/templates/hooks--pre-applypatch.sample b/templates/hooks--pre-applypatch.sample\nindex b1f187c..4142082 100755\n--- a/templates/hooks--pre-applypatch.sample\n+++ b/templates/hooks--pre-applypatch.sample\n@@ -9,6 +9,6 @@\n # To enable this hook, rename this file to \"pre-applypatch\".\n \n . git-sh-setup\n-test -x \"$GIT_DIR/hooks/pre-commit\" &&\n-\texec \"$GIT_DIR/hooks/pre-commit\" ${1+\"$@\"}\n+precommit=\"$(git rev-parse --git-path hooks/pre-commit)\"\n+test -x \"$precommit\" && exec \"$precommit\" ${1+\"$@\"}\n :\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252697","messageId":"1417335899-27307-15-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 14/34] git-stash: avoid hardcoding $GIT_DIR/logs/....","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:39Z","receivedAt":"2014-11-30T08:24:39Z","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 git-stash.sh | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/git-stash.sh b/git-stash.sh\nindex b6d4b06..6846b18 100755\n--- a/git-stash.sh\n+++ b/git-stash.sh\n@@ -184,7 +184,7 @@ store_stash () {\n \tfi\n \n \t# Make sure the reflog for stash is kept.\n-\t: >>\"$GIT_DIR/logs/$ref_stash\"\n+\t: >>\"$(git rev-parse --git-path logs/$ref_stash)\"\n \tgit update-ref -m \"$stash_msg\" $ref_stash $w_commit\n \tret=$?\n \ttest $ret != 0 && test -z $quiet &&\n@@ -259,7 +259,7 @@ save_stash () {\n \t\tsay \"$(gettext \"No local changes to save\")\"\n \t\texit 0\n \tfi\n-\ttest -f \"$GIT_DIR/logs/$ref_stash\" ||\n+\ttest -f \"$(git rev-parse --git-path logs/$ref_stash)\" ||\n \t\tclear_stash || die \"$(gettext \"Cannot initialize stash\")\"\n \n \tcreate_stash \"$stash_msg\" $untracked\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252698","messageId":"1417335899-27307-16-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 15/34] setup.c: convert is_git_directory() to use strbuf","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:40Z","receivedAt":"2014-11-30T08:24:40Z","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 | 37 +++++++++++++++++++++----------------\n 1 file changed, 21 insertions(+), 16 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 979b13f..afd6742 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -238,31 +238,36 @@ void verify_non_filename(const char *prefix, const char *arg)\n  */\n int is_git_directory(const char *suspect)\n {\n-\tchar path[PATH_MAX];\n-\tsize_t len = strlen(suspect);\n+\tstruct strbuf path = STRBUF_INIT;\n+\tint ret = 0;\n+\tsize_t len;\n \n-\tif (PATH_MAX <= len + strlen(\"/objects\"))\n-\t\tdie(\"Too long path: %.*s\", 60, suspect);\n-\tstrcpy(path, suspect);\n+\tstrbuf_addstr(&path, suspect);\n+\tlen = path.len;\n \tif (getenv(DB_ENVIRONMENT)) {\n \t\tif (access(getenv(DB_ENVIRONMENT), X_OK))\n-\t\t\treturn 0;\n+\t\t\tgoto done;\n \t}\n \telse {\n-\t\tstrcpy(path + len, \"/objects\");\n-\t\tif (access(path, X_OK))\n-\t\t\treturn 0;\n+\t\tstrbuf_addstr(&path, \"/objects\");\n+\t\tif (access(path.buf, X_OK))\n+\t\t\tgoto done;\n \t}\n \n-\tstrcpy(path + len, \"/refs\");\n-\tif (access(path, X_OK))\n-\t\treturn 0;\n+\tstrbuf_setlen(&path, len);\n+\tstrbuf_addstr(&path, \"/refs\");\n+\tif (access(path.buf, X_OK))\n+\t\tgoto done;\n \n-\tstrcpy(path + len, \"/HEAD\");\n-\tif (validate_headref(path))\n-\t\treturn 0;\n+\tstrbuf_setlen(&path, len);\n+\tstrbuf_addstr(&path, \"/HEAD\");\n+\tif (validate_headref(path.buf))\n+\t\tgoto done;\n \n-\treturn 1;\n+\tret = 1;\n+done:\n+\tstrbuf_release(&path);\n+\treturn ret;\n }\n \n int is_inside_git_dir(void)\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252699","messageId":"1417335899-27307-17-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 16/34] setup.c: detect $GIT_COMMON_DIR in is_git_directory()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:41Z","receivedAt":"2014-11-30T08:24:41Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"If the file \"$GIT_DIR/commondir\" exists, it contains the value of\n$GIT_COMMON_DIR.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/gitrepository-layout.txt |  7 ++++++\n setup.c                                | 43 +++++++++++++++++++++++++++++-----\n 2 files changed, 44 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt\nindex 2b5966a..2dc5667 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -240,6 +240,13 @@ shallow::\n \tfile is ignored if $GIT_COMMON_DIR is set and\n \t\"$GIT_COMMON_DIR/shallow\" will be used instead.\n \n+commondir::\n+\tIf this file exists, $GIT_COMMON_DIR (see linkgit:git[1]) will\n+\tbe set to the path specified in this file if it is not\n+\texplicitly set. If the specified path is relative, it is\n+\trelative to $GIT_DIR. The repository with commondir is\n+\tincomplete without the repository pointed by \"commondir\".\n+\n modules::\n \tContains the git-repositories of the submodules. This\n \tdirectory is ignored if $GIT_COMMON_DIR is set and\ndiff --git a/setup.c b/setup.c\nindex afd6742..6c52f75 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -224,6 +224,33 @@ void verify_non_filename(const char *prefix, const char *arg)\n \t    \"'git <command> [<revision>...] -- [<file>...]'\", arg);\n }\n \n+static void get_common_dir(struct strbuf *sb, const char *gitdir)\n+{\n+\tstruct strbuf data = STRBUF_INIT;\n+\tstruct strbuf path = STRBUF_INIT;\n+\tconst char *git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);\n+\tif (git_common_dir) {\n+\t\tstrbuf_addstr(sb, git_common_dir);\n+\t\treturn;\n+\t}\n+\tstrbuf_addf(&path, \"%s/commondir\", gitdir);\n+\tif (file_exists(path.buf)) {\n+\t\tif (strbuf_read_file(&data, path.buf, 0) <= 0)\n+\t\t\tdie_errno(_(\"failed to read %s\"), path.buf);\n+\t\twhile (data.len && (data.buf[data.len - 1] == '\\n' ||\n+\t\t\t\t    data.buf[data.len - 1] == '\\r'))\n+\t\t\tdata.len--;\n+\t\tdata.buf[data.len] = '\\0';\n+\t\tstrbuf_reset(&path);\n+\t\tif (!is_absolute_path(data.buf))\n+\t\t\tstrbuf_addf(&path, \"%s/\", gitdir);\n+\t\tstrbuf_addbuf(&path, &data);\n+\t\tstrbuf_addstr(sb, real_path(path.buf));\n+\t} else\n+\t\tstrbuf_addstr(sb, gitdir);\n+\tstrbuf_release(&data);\n+\tstrbuf_release(&path);\n+}\n \n /*\n  * Test if it looks like we're at a git directory.\n@@ -242,13 +269,22 @@ int is_git_directory(const char *suspect)\n \tint ret = 0;\n \tsize_t len;\n \n-\tstrbuf_addstr(&path, suspect);\n+\t/* Check worktree-related signatures */\n+\tstrbuf_addf(&path, \"%s/HEAD\", suspect);\n+\tif (validate_headref(path.buf))\n+\t\tgoto done;\n+\n+\tstrbuf_reset(&path);\n+\tget_common_dir(&path, suspect);\n \tlen = path.len;\n+\n+\t/* Check non-worktree-related signatures */\n \tif (getenv(DB_ENVIRONMENT)) {\n \t\tif (access(getenv(DB_ENVIRONMENT), X_OK))\n \t\t\tgoto done;\n \t}\n \telse {\n+\t\tstrbuf_setlen(&path, len);\n \t\tstrbuf_addstr(&path, \"/objects\");\n \t\tif (access(path.buf, X_OK))\n \t\t\tgoto done;\n@@ -259,11 +295,6 @@ int is_git_directory(const char *suspect)\n \tif (access(path.buf, X_OK))\n \t\tgoto done;\n \n-\tstrbuf_setlen(&path, len);\n-\tstrbuf_addstr(&path, \"/HEAD\");\n-\tif (validate_headref(path.buf))\n-\t\tgoto done;\n-\n \tret = 1;\n done:\n \tstrbuf_release(&path);\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252700","messageId":"1417335899-27307-18-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 17/34] setup.c: convert check_repository_format_gently to use strbuf","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:42Z","receivedAt":"2014-11-30T08:24:42Z","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 | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 6c52f75..00a23e6 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -342,7 +342,9 @@ void setup_work_tree(void)\n \n static int check_repository_format_gently(const char *gitdir, int *nongit_ok)\n {\n-\tchar repo_config[PATH_MAX+1];\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tconst char *repo_config;\n+\tint ret = 0;\n \n \t/*\n \t * git_config() can't be used here because it calls git_pathdup()\n@@ -353,7 +355,8 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)\n \t * Use a gentler version of git_config() to check if this repo\n \t * is a good one.\n \t */\n-\tsnprintf(repo_config, PATH_MAX, \"%s/config\", gitdir);\n+\tstrbuf_addf(&sb, \"%s/config\", gitdir);\n+\trepo_config = sb.buf;\n \tgit_config_early(check_repository_format_version, NULL, repo_config);\n \tif (GIT_REPO_VERSION < repository_format_version) {\n \t\tif (!nongit_ok)\n@@ -363,9 +366,10 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)\n \t\t\tGIT_REPO_VERSION, repository_format_version);\n \t\twarning(\"Please upgrade Git\");\n \t\t*nongit_ok = -1;\n-\t\treturn -1;\n+\t\tret = -1;\n \t}\n-\treturn 0;\n+\tstrbuf_release(&sb);\n+\treturn ret;\n }\n \n /*\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252701","messageId":"1417335899-27307-19-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 18/34] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:43Z","receivedAt":"2014-11-30T08:24:43Z","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 | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 00a23e6..1d4f1aa 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -346,6 +346,10 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)\n \tconst char *repo_config;\n \tint ret = 0;\n \n+\tget_common_dir(&sb, gitdir);\n+\tstrbuf_addstr(&sb, \"/config\");\n+\trepo_config = sb.buf;\n+\n \t/*\n \t * git_config() can't be used here because it calls git_pathdup()\n \t * to get $GIT_CONFIG/config. That call will make setup_git_env()\n@@ -355,8 +359,6 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)\n \t * Use a gentler version of git_config() to check if this repo\n \t * is a good one.\n \t */\n-\tstrbuf_addf(&sb, \"%s/config\", gitdir);\n-\trepo_config = sb.buf;\n \tgit_config_early(check_repository_format_version, NULL, repo_config);\n \tif (GIT_REPO_VERSION < repository_format_version) {\n \t\tif (!nongit_ok)\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252702","messageId":"1417335899-27307-20-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 19/34] setup.c: support multi-checkout repo setup","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:44Z","receivedAt":"2014-11-30T08:24:44Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The repo setup procedure is updated to detect $GIT_DIR/commondir and\nset $GIT_COMMON_DIR properly.\n\nThe core.worktree is ignored when $GIT_COMMON_DIR is set. This is\nbecause the config file is shared in multi-checkout setup, but\ncheckout directories _are_ different. Making core.worktree effective\nin all checkouts mean it's back to a single checkout.\n\nHelped-by: Johannes Sixt <j6t@kdbg.org>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/config.txt        |  2 ++\n Documentation/git-rev-parse.txt |  3 ++\n builtin/rev-parse.c             |  4 +++\n cache.h                         |  1 +\n environment.c                   |  8 ++---\n setup.c                         | 33 +++++++++++++-----\n t/t1501-worktree.sh             | 76 +++++++++++++++++++++++++++++++++++++++++\n t/t1510-repo-setup.sh           |  1 +\n trace.c                         |  1 +\n 9 files changed, 115 insertions(+), 14 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 9220725..e1623ec 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -393,6 +393,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+\tIf GIT_COMMON_DIR environment variable is set, core.worktree\n+\tis ignored and not used for determining the root of working tree.\n \tThis can be overridden by the GIT_WORK_TREE environment\n \tvariable and the '--work-tree' command-line option.\n \tThe value can be an absolute path or relative to the path to\ndiff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt\nindex 2103bbf..97fc703 100644\n--- a/Documentation/git-rev-parse.txt\n+++ b/Documentation/git-rev-parse.txt\n@@ -216,6 +216,9 @@ If `$GIT_DIR` is not defined and the current directory\n is not detected to lie in a Git repository or work tree\n print a message to stderr and exit with nonzero status.\n \n+--git-common-dir::\n+\tShow `$GIT_COMMON_DIR` if defined, else `$GIT_DIR`.\n+\n --is-inside-git-dir::\n \tWhen the current working directory is below the repository\n \tdirectory print \"true\", otherwise \"false\".\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex 4cd44aa..5799f35 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -762,6 +762,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n \t\t\t\tfree(cwd);\n \t\t\t\tcontinue;\n \t\t\t}\n+\t\t\tif (!strcmp(arg, \"--git-common-dir\")) {\n+\t\t\t\tputs(get_git_common_dir());\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 = argv[++i];\n \t\t\t\tif (!gitdir)\ndiff --git a/cache.h b/cache.h\nindex bce1a7d..9dc6ae0 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -437,6 +437,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 get_common_dir(struct strbuf *sb, const char *gitdir);\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 050f160..8351007 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -142,6 +142,7 @@ static char *git_path_from_env(const char *envvar, const char *git_dir,\n \n static void setup_git_env(void)\n {\n+\tstruct strbuf sb = STRBUF_INIT;\n \tconst char *gitfile;\n \tconst char *shallow_file;\n \n@@ -150,12 +151,9 @@ 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_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);\n-\tif (git_common_dir) {\n+\tif (get_common_dir(&sb, git_dir))\n \t\tgit_common_dir_env = 1;\n-\t\tgit_common_dir = xstrdup(git_common_dir);\n-\t} else\n-\t\tgit_common_dir = git_dir;\n+\tgit_common_dir = strbuf_detach(&sb, NULL);\n \tgit_object_dir = git_path_from_env(DB_ENVIRONMENT, git_common_dir,\n \t\t\t\t\t   \"objects\", &git_db_env);\n \tgit_index_file = git_path_from_env(INDEX_ENVIRONMENT, git_dir,\ndiff --git a/setup.c b/setup.c\nindex 1d4f1aa..b99bdd3 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -224,14 +224,15 @@ void verify_non_filename(const char *prefix, const char *arg)\n \t    \"'git <command> [<revision>...] -- [<file>...]'\", arg);\n }\n \n-static void get_common_dir(struct strbuf *sb, const char *gitdir)\n+int get_common_dir(struct strbuf *sb, const char *gitdir)\n {\n \tstruct strbuf data = STRBUF_INIT;\n \tstruct strbuf path = STRBUF_INIT;\n \tconst char *git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);\n+\tint ret = 0;\n \tif (git_common_dir) {\n \t\tstrbuf_addstr(sb, git_common_dir);\n-\t\treturn;\n+\t\treturn 1;\n \t}\n \tstrbuf_addf(&path, \"%s/commondir\", gitdir);\n \tif (file_exists(path.buf)) {\n@@ -246,10 +247,12 @@ static void get_common_dir(struct strbuf *sb, const char *gitdir)\n \t\t\tstrbuf_addf(&path, \"%s/\", gitdir);\n \t\tstrbuf_addbuf(&path, &data);\n \t\tstrbuf_addstr(sb, real_path(path.buf));\n+\t\tret = 1;\n \t} else\n \t\tstrbuf_addstr(sb, gitdir);\n \tstrbuf_release(&data);\n \tstrbuf_release(&path);\n+\treturn ret;\n }\n \n /*\n@@ -340,13 +343,26 @@ void setup_work_tree(void)\n \tinitialized = 1;\n }\n \n+static int check_repo_format(const char *var, const char *value, void *cb)\n+{\n+\tif (strcmp(var, \"core.repositoryformatversion\") == 0)\n+\t\trepository_format_version = git_config_int(var, value);\n+\telse if (strcmp(var, \"core.sharedrepository\") == 0)\n+\t\tshared_repository = git_config_perm(var, value);\n+\treturn 0;\n+}\n+\n static int check_repository_format_gently(const char *gitdir, int *nongit_ok)\n {\n \tstruct strbuf sb = STRBUF_INIT;\n \tconst char *repo_config;\n+\tconfig_fn_t fn;\n \tint ret = 0;\n \n-\tget_common_dir(&sb, gitdir);\n+\tif (get_common_dir(&sb, gitdir))\n+\t\tfn = check_repo_format;\n+\telse\n+\t\tfn = check_repository_format_version;\n \tstrbuf_addstr(&sb, \"/config\");\n \trepo_config = sb.buf;\n \n@@ -359,7 +375,7 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)\n \t * Use a gentler version of git_config() to check if this repo\n \t * is a good one.\n \t */\n-\tgit_config_early(check_repository_format_version, NULL, repo_config);\n+\tgit_config_early(fn, NULL, repo_config);\n \tif (GIT_REPO_VERSION < repository_format_version) {\n \t\tif (!nongit_ok)\n \t\t\tdie (\"Expected git repo version <= %d, found %d\",\n@@ -841,11 +857,10 @@ int git_config_perm(const char *var, const char *value)\n \n int check_repository_format_version(const char *var, const char *value, void *cb)\n {\n-\tif (strcmp(var, \"core.repositoryformatversion\") == 0)\n-\t\trepository_format_version = git_config_int(var, value);\n-\telse if (strcmp(var, \"core.sharedrepository\") == 0)\n-\t\tshared_repository = git_config_perm(var, value);\n-\telse if (strcmp(var, \"core.bare\") == 0) {\n+\tint ret = check_repo_format(var, value, cb);\n+\tif (ret)\n+\t\treturn ret;\n+\tif (strcmp(var, \"core.bare\") == 0) {\n \t\tis_bare_repository_cfg = git_config_bool(var, value);\n \t\tif (is_bare_repository_cfg == 1)\n \t\t\tinside_work_tree = -1;\ndiff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh\nindex 8f36aa9..4df7a2f 100755\n--- a/t/t1501-worktree.sh\n+++ b/t/t1501-worktree.sh\n@@ -346,4 +346,80 @@ test_expect_success 'relative $GIT_WORK_TREE and git subprocesses' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'Multi-worktree setup' '\n+\tmkdir work &&\n+\tmkdir -p repo.git/repos/foo &&\n+\tcp repo.git/HEAD repo.git/index repo.git/repos/foo &&\n+\tsane_unset GIT_DIR GIT_CONFIG GIT_WORK_TREE\n+'\n+\n+test_expect_success 'GIT_DIR set (1)' '\n+\techo \"gitdir: repo.git/repos/foo\" >gitfile &&\n+\techo ../.. >repo.git/repos/foo/commondir &&\n+\t(\n+\t\tcd work &&\n+\t\tGIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&\n+\t\ttest-path-utils real_path \"$TRASH_DIRECTORY/repo.git\" >expect &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'GIT_DIR set (2)' '\n+\techo \"gitdir: repo.git/repos/foo\" >gitfile &&\n+\techo \"$(pwd)/repo.git\" >repo.git/repos/foo/commondir &&\n+\t(\n+\t\tcd work &&\n+\t\tGIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&\n+\t\ttest-path-utils real_path \"$TRASH_DIRECTORY/repo.git\" >expect &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'Auto discovery' '\n+\techo \"gitdir: repo.git/repos/foo\" >.git &&\n+\techo ../.. >repo.git/repos/foo/commondir &&\n+\t(\n+\t\tcd work &&\n+\t\tgit rev-parse --git-common-dir >actual &&\n+\t\ttest-path-utils real_path \"$TRASH_DIRECTORY/repo.git\" >expect &&\n+\t\ttest_cmp expect actual &&\n+\t\techo haha >data1 &&\n+\t\tgit add data1 &&\n+\t\tgit ls-files --full-name :/ | grep data1 >actual &&\n+\t\techo work/data1 >expect &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success '$GIT_DIR/common overrides core.worktree' '\n+\tmkdir elsewhere &&\n+\tgit --git-dir=repo.git config core.worktree \"$TRASH_DIRECTORY/elsewhere\" &&\n+\techo \"gitdir: repo.git/repos/foo\" >.git &&\n+\techo ../.. >repo.git/repos/foo/commondir &&\n+\t(\n+\t\tcd work &&\n+\t\tgit rev-parse --git-common-dir >actual &&\n+\t\ttest-path-utils real_path \"$TRASH_DIRECTORY/repo.git\" >expect &&\n+\t\ttest_cmp expect actual &&\n+\t\techo haha >data2 &&\n+\t\tgit add data2 &&\n+\t\tgit ls-files --full-name :/ | grep data2 >actual &&\n+\t\techo work/data2 >expect &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success '$GIT_WORK_TREE overrides $GIT_DIR/common' '\n+\techo \"gitdir: repo.git/repos/foo\" >.git &&\n+\techo ../.. >repo.git/repos/foo/commondir &&\n+\t(\n+\t\tcd work &&\n+\t\techo haha >data3 &&\n+\t\tgit --git-dir=../.git --work-tree=. add data3 &&\n+\t\tgit ls-files --full-name -- :/ | grep data3 >actual &&\n+\t\techo data3 >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 e1b2a99..33c1a58 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_common_dir: $2\n \tsetup: worktree: $3\n \tsetup: cwd: $4\n \tsetup: prefix: $5\ndiff --git a/trace.c b/trace.c\nindex 4778608..bc3f53d 100644\n--- a/trace.c\n+++ b/trace.c\n@@ -312,6 +312,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_common_dir: %s\\n\", quote_crnl(get_git_common_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-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252703","messageId":"1417335899-27307-21-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 20/34] wrapper.c: wrapper to open a file, fprintf then close","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:45Z","receivedAt":"2014-11-30T08:24:45Z","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 wrapper.c | 31 +++++++++++++++++++++++++++++++\n 2 files changed, 33 insertions(+)\n\ndiff --git a/cache.h b/cache.h\nindex 9dc6ae0..f8e75a1 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1499,6 +1499,8 @@ static inline ssize_t write_str_in_full(int fd, const char *str)\n {\n \treturn write_in_full(fd, str, strlen(str));\n }\n+__attribute__((format (printf,3,4)))\n+extern int write_file(const char *path, int fatal, const char *fmt, ...);\n \n /* pager.c */\n extern void setup_pager(void);\ndiff --git a/wrapper.c b/wrapper.c\nindex 007ec0d..a2dff8e 100644\n--- a/wrapper.c\n+++ b/wrapper.c\n@@ -550,3 +550,34 @@ char *xgetcwd(void)\n \t\tdie_errno(_(\"unable to get current working directory\"));\n \treturn strbuf_detach(&sb, NULL);\n }\n+\n+int write_file(const char *path, int fatal, const char *fmt, ...)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tva_list params;\n+\tint fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);\n+\tif (fd < 0) {\n+\t\tif (fatal)\n+\t\t\tdie_errno(_(\"could not open %s for writing\"), path);\n+\t\treturn -1;\n+\t}\n+\tva_start(params, fmt);\n+\tstrbuf_vaddf(&sb, fmt, params);\n+\tva_end(params);\n+\tif (write_in_full(fd, sb.buf, sb.len) != sb.len) {\n+\t\tint err = errno;\n+\t\tclose(fd);\n+\t\tstrbuf_release(&sb);\n+\t\terrno = err;\n+\t\tif (fatal)\n+\t\t\tdie_errno(_(\"could not write to %s\"), path);\n+\t\treturn -1;\n+\t}\n+\tstrbuf_release(&sb);\n+\tif (close(fd)) {\n+\t\tif (fatal)\n+\t\t\tdie_errno(_(\"could not close %s\"), path);\n+\t\treturn -1;\n+\t}\n+\treturn 0;\n+}\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252704","messageId":"1417335899-27307-22-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 21/34] use new wrapper write_file() for simple file writing","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:46Z","receivedAt":"2014-11-30T08:24:46Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This fixes common problems in these code about error handling,\nforgetting to close the file handle after fprintf() fails, or not\nprinting out the error string..\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/branch.c  |  4 +---\n builtin/init-db.c |  7 +------\n daemon.c          | 11 +----------\n submodule.c       |  9 ++-------\n transport.c       |  8 +++-----\n 5 files changed, 8 insertions(+), 31 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 3b79c50..1a475d8 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -764,7 +764,6 @@ static const char edit_description[] = \"BRANCH_DESCRIPTION\";\n \n static int edit_branch_description(const char *branch_name)\n {\n-\tFILE *fp;\n \tint status;\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct strbuf name = STRBUF_INIT;\n@@ -777,8 +776,7 @@ static int edit_branch_description(const char *branch_name)\n \t\t    \"  %s\\n\"\n \t\t    \"Lines starting with '%c' will be stripped.\\n\",\n \t\t    branch_name, comment_line_char);\n-\tfp = fopen(git_path(edit_description), \"w\");\n-\tif ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) {\n+\tif (write_file(git_path(edit_description), 0, \"%s\", buf.buf)) {\n \t\tstrbuf_release(&buf);\n \t\treturn error(_(\"could not write branch description template: %s\"),\n \t\t\t     strerror(errno));\ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex 587a505..6b7fa5f 100644\n--- a/builtin/init-db.c\n+++ b/builtin/init-db.c\n@@ -342,7 +342,6 @@ int set_git_dir_init(const char *git_dir, const char *real_git_dir,\n static void separate_git_dir(const char *git_dir)\n {\n \tstruct stat st;\n-\tFILE *fp;\n \n \tif (!stat(git_link, &st)) {\n \t\tconst char *src;\n@@ -358,11 +357,7 @@ static void separate_git_dir(const char *git_dir)\n \t\t\tdie_errno(_(\"unable to move %s to %s\"), src, git_dir);\n \t}\n \n-\tfp = fopen(git_link, \"w\");\n-\tif (!fp)\n-\t\tdie(_(\"Could not create git link %s\"), git_link);\n-\tfprintf(fp, \"gitdir: %s\\n\", git_dir);\n-\tfclose(fp);\n+\twrite_file(git_link, 1, \"gitdir: %s\\n\", git_dir);\n }\n \n int init_db(const char *template_dir, unsigned int flags)\ndiff --git a/daemon.c b/daemon.c\nindex 54a03bd..60bbab6 100644\n--- a/daemon.c\n+++ b/daemon.c\n@@ -1070,15 +1070,6 @@ static struct credentials *prepare_credentials(const char *user_name,\n }\n #endif\n \n-static void store_pid(const char *path)\n-{\n-\tFILE *f = fopen(path, \"w\");\n-\tif (!f)\n-\t\tdie_errno(\"cannot open pid file '%s'\", path);\n-\tif (fprintf(f, \"%\"PRIuMAX\"\\n\", (uintmax_t) getpid()) < 0 || fclose(f) != 0)\n-\t\tdie_errno(\"failed to write pid file '%s'\", path);\n-}\n-\n static int serve(struct string_list *listen_addr, int listen_port,\n     struct credentials *cred)\n {\n@@ -1289,7 +1280,7 @@ int main(int argc, char **argv)\n \t\tsanitize_stdfds();\n \n \tif (pid_file)\n-\t\tstore_pid(pid_file);\n+\t\twrite_file(pid_file, 1, \"%\"PRIuMAX\"\\n\", (uintmax_t) getpid());\n \n \t/* prepare argv for serving-processes */\n \tcld_argv = xmalloc(sizeof (char *) * (argc + 2));\ndiff --git a/submodule.c b/submodule.c\nindex 0690dc5..34094f5 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1102,16 +1102,11 @@ void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir)\n \tstruct strbuf file_name = STRBUF_INIT;\n \tstruct strbuf rel_path = STRBUF_INIT;\n \tconst char *real_work_tree = xstrdup(real_path(work_tree));\n-\tFILE *fp;\n \n \t/* Update gitfile */\n \tstrbuf_addf(&file_name, \"%s/.git\", work_tree);\n-\tfp = fopen(file_name.buf, \"w\");\n-\tif (!fp)\n-\t\tdie(_(\"Could not create git link %s\"), file_name.buf);\n-\tfprintf(fp, \"gitdir: %s\\n\", relative_path(git_dir, real_work_tree,\n-\t\t\t\t\t\t  &rel_path));\n-\tfclose(fp);\n+\twrite_file(file_name.buf, 1, \"gitdir: %s\\n\",\n+\t\t   relative_path(git_dir, real_work_tree, &rel_path));\n \n \t/* Update core.worktree setting */\n \tstrbuf_reset(&file_name);\ndiff --git a/transport.c b/transport.c\nindex 70d38e4..051b7ac 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -283,7 +283,6 @@ static int write_one_ref(const char *name, const unsigned char *sha1,\n {\n \tstruct strbuf *buf = data;\n \tint len = buf->len;\n-\tFILE *f;\n \n \t/* when called via for_each_ref(), flags is non-zero */\n \tif (flags && !starts_with(name, \"refs/heads/\") &&\n@@ -292,10 +291,9 @@ static int write_one_ref(const char *name, const unsigned char *sha1,\n \n \tstrbuf_addstr(buf, name);\n \tif (safe_create_leading_directories(buf->buf) ||\n-\t\t\t!(f = fopen(buf->buf, \"w\")) ||\n-\t\t\tfprintf(f, \"%s\\n\", sha1_to_hex(sha1)) < 0 ||\n-\t\t\tfclose(f))\n-\t\treturn error(\"problems writing temporary file %s\", buf->buf);\n+\t    write_file(buf->buf, 0, \"%s\\n\", sha1_to_hex(sha1)))\n+\t\treturn error(\"problems writing temporary file %s: %s\",\n+\t\t\t     buf->buf, strerror(errno));\n \tstrbuf_setlen(buf, len);\n \treturn 0;\n }\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252705","messageId":"1417335899-27307-23-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 22/34] checkout: support checking out into a new working directory","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:47Z","receivedAt":"2014-11-30T08:24:47Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"\"git checkout --to\" sets up a new working directory with a .git file\npointing to $GIT_DIR/worktrees/<id>. It then executes \"git checkout\"\nagain on the new worktree with the same arguments except \"--to\" is\ntaken out. The second checkout execution, which is not contaminated\nwith any info from the current repository, will actually check out and\neverything that normal \"git checkout\" does.\n\nHelped-by: Marc Branchaud <marcnarc@xiplink.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-checkout.txt         | 46 ++++++++++++++++\n Documentation/git.txt                  |  3 +-\n Documentation/gitrepository-layout.txt |  7 +++\n builtin/checkout.c                     | 95 +++++++++++++++++++++++++++++++++-\n path.c                                 |  2 +-\n t/t2025-checkout-to.sh (new +x)        | 63 ++++++++++++++++++++++\n 6 files changed, 212 insertions(+), 4 deletions(-)\n create mode 100755 t/t2025-checkout-to.sh\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex 33ad2ad..c101575 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -225,6 +225,13 @@ 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 branch in a separate working directory at\n+\t`<path>`. A new working directory is linked to the current\n+\trepository, sharing everything except working directory\n+\tspecific files such as HEAD, index... See \"MULTIPLE WORKING\n+\tTREES\" section for more information.\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\n@@ -388,6 +395,45 @@ $ git reflog -2 HEAD # or\n $ git log -g -2 HEAD\n ------------\n \n+MULTIPLE WORKING TREES\n+----------------------\n+\n+A git repository can support multiple working trees, allowing you to check\n+out more than one branch at a time.  With `git checkout --to` a new working\n+tree is associated with the repository.  This new working tree is called a\n+\"linked working tree\" as opposed to the \"main working tree\" prepared by \"git\n+init\" or \"git clone\".  A repository has one main working tree (if it's not a\n+bare repository) and zero or more linked working trees.\n+\n+Each linked working tree has a private sub-directory in the repository's\n+$GIT_DIR/worktrees directory.  The private sub-directory's name is usually\n+the base name of the linked working tree's path, possibly appended with a\n+number to make it unique.  For example, when `$GIT_DIR=/path/main/.git` the\n+command `git checkout --to /path/other/test-next next` creates the linked\n+working tree in `/path/other/test-next` and also creates a\n+`$GIT_DIR/worktrees/test-next` directory (or `$GIT_DIR/worktrees/test-next1`\n+if `test-next` is already taken).\n+\n+Within a linked working tree, $GIT_DIR is set to point to this private\n+directory (e.g. `/path/main/.git/worktrees/test-next` in the example) and\n+$GIT_COMMON_DIR is set to point back to the main working tree's $GIT_DIR\n+(e.g. `/path/main/.git`). These settings are made in a `.git` file located at\n+the top directory of the linked working tree.\n+\n+Path resolution via `git rev-parse --git-path` uses either\n+$GIT_DIR or $GIT_COMMON_DIR depending on the path. For example, in the\n+linked working tree `git rev-parse --git-path HEAD` returns\n+`/path/main/.git/worktrees/test-next/HEAD` (not\n+`/path/other/test-next/.git/HEAD` or `/path/main/.git/HEAD`) while `git\n+rev-parse --git-path refs/heads/master` uses\n+$GIT_COMMON_DIR and returns `/path/main/.git/refs/heads/master`,\n+since refs are shared across all working trees.\n+\n+See linkgit:gitrepository-layout[5] for more information. The rule of\n+thumb is do not make any assumption about whether a path belongs to\n+$GIT_DIR or $GIT_COMMON_DIR when you need to directly access something\n+inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path.\n+\n EXAMPLES\n --------\n \ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex 96354af..0c2dcfa 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -813,7 +813,8 @@ Git so take care if using Cogito etc.\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. See linkgit:gitrepository-layout[5] for\n+\ttaken from $GIT_DIR. See linkgit:gitrepository-layout[5] and\n+\tthe section 'MULTIPLE CHECKOUT MODE' in linkgit:checkout[1]\n \tdetails. This variable has lower precedence than other path\n \tvariables such as GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY...\n \ndiff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt\nindex 2dc5667..8228450 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -252,6 +252,13 @@ modules::\n \tdirectory is ignored if $GIT_COMMON_DIR is set and\n \t\"$GIT_COMMON_DIR/modules\" will be used instead.\n \n+worktrees::\n+\tContains worktree specific information of linked\n+\tcheckouts. Each subdirectory contains the worktree-related\n+\tpart of a linked checkout. This directory is ignored if\n+\t$GIT_COMMON_DIR is set and \"$GIT_COMMON_DIR/worktrees\" will be\n+\tused instead.\n+\n SEE ALSO\n --------\n linkgit:git-init[1],\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 195aca7..797e14d 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -48,6 +48,10 @@ 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+\tint new_worktree_mode;\n };\n \n static int post_checkout_hook(struct commit *old, struct commit *new,\n@@ -249,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@@ -484,7 +491,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\ttopts.dir->flags |= DIR_SHOW_IGNORED;\n \t\t\tsetup_standard_excludes(topts.dir);\n \t\t}\n-\t\ttree = parse_tree_indirect(old->commit ?\n+\t\ttree = parse_tree_indirect(old->commit && !opts->new_worktree_mode ?\n \t\t\t\t\t   old->commit->object.sha1 :\n \t\t\t\t\t   EMPTY_TREE_SHA1_BIN);\n \t\tinit_tree_desc(&trees[0], tree->buffer, tree->size);\n@@ -800,7 +807,8 @@ static int switch_branches(const struct checkout_opts *opts,\n \t\treturn ret;\n \t}\n \n-\tif (!opts->quiet && !old.path && old.commit && new->commit != old.commit)\n+\tif (!opts->quiet && !old.path && old.commit &&\n+\t    new->commit != old.commit && !opts->new_worktree_mode)\n \t\torphaned_commit_warning(old.commit, new->commit);\n \n \tupdate_refs_for_switch(opts, &old, new);\n@@ -810,6 +818,76 @@ static int switch_branches(const struct checkout_opts *opts,\n \treturn ret || writeout_error;\n }\n \n+static int prepare_linked_checkout(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, *name;\n+\tstruct stat st;\n+\tstruct child_process cp;\n+\tint counter = 0, len;\n+\n+\tif (!new->commit)\n+\t\tdie(_(\"no branch specified\"));\n+\tif (file_exists(path))\n+\t\tdie(_(\"'%s' already exists\"), path);\n+\n+\tlen = strlen(path);\n+\twhile (len && is_dir_sep(path[len - 1]))\n+\t\tlen--;\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,\n+\t\t      git_path(\"worktrees/%.*s\", (int)(path + len - name), 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 = strrchr(sb_repo.buf, '/') + 1;\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_file(sb_git.buf, 1, \"gitdir: %s/worktrees/%s\\n\",\n+\t\t   real_path(get_git_common_dir()), name);\n+\t/*\n+\t * This is to keep resolve_ref() happy. We need a valid HEAD\n+\t * or is_git_directory() will reject the directory. Any valid\n+\t * value would do because this value will be ignored and\n+\t * replaced at the next (real) checkout.\n+\t */\n+\tstrbuf_addf(&sb, \"%s/HEAD\", sb_repo.buf);\n+\twrite_file(sb.buf, 1, \"%s\\n\", sha1_to_hex(new->commit->object.sha1));\n+\tstrbuf_reset(&sb);\n+\tstrbuf_addf(&sb, \"%s/commondir\", sb_repo.buf);\n+\twrite_file(sb.buf, 1, \"../..\\n\");\n+\n+\tif (!opts->quiet)\n+\t\tfprintf_ln(stderr, _(\"Enter %s (identifier %s)\"), path, name);\n+\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@@ -1071,6 +1149,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 prepare_linked_checkout(opts, new);\n+\n \tif (!new->commit && opts->new_branch) {\n \t\tunsigned char rev[20];\n \t\tint flag;\n@@ -1113,6 +1194,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_FILENAME(0, \"to\", &opts.new_worktree,\n+\t\t\t   N_(\"check a branch out in a separate working directory\")),\n \t\tOPT_END(),\n \t};\n \n@@ -1121,6 +1204,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@@ -1129,6 +1215,11 @@ 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+\t/* recursive execution from checkout_new_worktree() */\n+\topts.new_worktree_mode = getenv(\"GIT_CHECKOUT_NEW_WORKTREE\") != NULL;\n+\tif (opts.new_worktree_mode)\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 94db501..72eca6d 100644\n--- a/path.c\n+++ b/path.c\n@@ -92,7 +92,7 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)\n \n static const char *common_list[] = {\n \t\"/branches\", \"/hooks\", \"/info\", \"/logs\", \"/lost-found\", \"/modules\",\n-\t\"/objects\", \"/refs\", \"/remotes\", \"/rr-cache\", \"/svn\",\n+\t\"/objects\", \"/refs\", \"/remotes\", \"/worktrees\", \"/rr-cache\", \"/svn\",\n \t\"config\", \"gc.pid\", \"packed-refs\", \"shallow\",\n \tNULL\n };\ndiff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh\nnew file mode 100755\nindex 0000000..4963415\n--- /dev/null\n+++ b/t/t2025-checkout-to.sh\n@@ -0,0 +1,63 @@\n+#!/bin/sh\n+\n+test_description='test git checkout --to'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\ttest_commit init\n+'\n+\n+test_expect_success 'checkout --to not updating paths' '\n+\ttest_must_fail git checkout --to -- init.t\n+'\n+\n+test_expect_success 'checkout --to an existing worktree' '\n+\tmkdir existing &&\n+\ttest_must_fail git checkout --detach --to existing master\n+'\n+\n+test_expect_success 'checkout --to a new worktree' '\n+\tgit checkout --to here master &&\n+\t(\n+\t\tcd here &&\n+\t\ttest_cmp ../init.t init.t &&\n+\t\tgit symbolic-ref HEAD >actual &&\n+\t\techo refs/heads/master >expect &&\n+\t\ttest_cmp expect actual &&\n+\t\tgit fsck\n+\t)\n+'\n+\n+test_expect_success 'checkout --to a new worktree from a subdir' '\n+\t(\n+\t\tmkdir sub &&\n+\t\tcd sub &&\n+\t\tgit checkout --detach --to here master &&\n+\t\tcd here &&\n+\t\ttest_cmp ../../init.t init.t\n+\t)\n+'\n+\n+test_expect_success 'checkout --to from a linked checkout' '\n+\t(\n+\t\tcd here &&\n+\t\tgit checkout --to nested-here master &&\n+\t\tcd nested-here &&\n+\t\tgit fsck\n+\t)\n+'\n+\n+test_expect_success 'checkout --to a new worktree creating new branch' '\n+\tgit checkout --to there -b newmaster master &&\n+\t(\n+\t\tcd there &&\n+\t\ttest_cmp ../init.t init.t &&\n+\t\tgit symbolic-ref HEAD >actual &&\n+\t\techo refs/heads/newmaster >expect &&\n+\t\ttest_cmp expect actual &&\n+\t\tgit fsck\n+\t)\n+'\n+\n+test_done\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252706","messageId":"1417335899-27307-24-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 23/34] prune: strategies for linked checkouts","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:48Z","receivedAt":"2014-11-30T08:24:48Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"(alias R=$GIT_COMMON_DIR/worktrees/<id>)\n\n - linked checkouts are supposed to keep its location in $R/gitdir up\n   to date. The use case is auto fixup after a manual checkout move.\n\n - linked checkouts are supposed to update mtime of $R/gitdir. If\n   $R/gitdir's mtime is older than a limit, and it points to nowhere,\n   worktrees/<id> is to be pruned.\n\n - If $R/locked exists, worktrees/<id> is not supposed to be pruned. If\n   $R/locked exists and $R/gitdir's mtime is older than a really long\n   limit, warn about old unused repo.\n\n - \"git checkout --to\" is supposed to make a hard link named $R/link\n   pointing to the .git file on supported file systems to help detect\n   the user manually deleting the checkout. If $R/link exists and its\n   link count is greated than 1, the repo is kept.\n\nHelped-by: Marc Branchaud <marcnarc@xiplink.com>\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nHelped-by: Johannes Sixt <j6t@kdbg.org>\nSigned-off-by: Marc Branchaud <marcnarc@xiplink.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-checkout.txt             | 20 +++++++\n Documentation/git-prune.txt                |  3 +\n Documentation/gitrepository-layout.txt     | 19 ++++++\n builtin/checkout.c                         | 19 +++++-\n builtin/prune.c                            | 95 ++++++++++++++++++++++++++++++\n setup.c                                    | 13 ++++\n t/t2026-prune-linked-checkouts.sh (new +x) | 84 ++++++++++++++++++++++++++\n 7 files changed, 251 insertions(+), 2 deletions(-)\n create mode 100755 t/t2026-prune-linked-checkouts.sh\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex c101575..35675da 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -434,6 +434,26 @@ thumb is do not make any assumption about whether a path belongs to\n $GIT_DIR or $GIT_COMMON_DIR when you need to directly access something\n inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path.\n \n+When you are done with a linked working tree you can simply delete it.\n+You can clean up any stale $GIT_DIR/worktrees entries via `git prune\n+--worktrees` in the main or any linked working tree.\n+\n+If you move a linked working directory to another file system, or\n+within a file system that does not support hard links, you need to run\n+at least one git command inside the linked working directory\n+(e.g. `git status`) in order to update its entry in $GIT_DIR/worktrees\n+so that it does not get automatically removed.\n+\n+To prevent `git prune --worktrees` from deleting a $GIT_DIR/worktrees\n+entry (which can be useful in some situations, such as when the\n+entry's working tree is stored on a portable device), add a file named\n+'locked' to the entry's directory. The file contains the reason in\n+plain text. For example, if a linked working tree's `.git` file points\n+to `/path/main/.git/worktrees/test-next` then a file named\n+`/path/main/.git/worktrees/test-next/locked` will prevent the\n+`test-next` entry from being pruned.  See\n+linkgit:gitrepository-layout[5] for details.\n+\n EXAMPLES\n --------\n \ndiff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt\nindex 7a493c8..1cf3bed 100644\n--- a/Documentation/git-prune.txt\n+++ b/Documentation/git-prune.txt\n@@ -48,6 +48,9 @@ OPTIONS\n --expire <time>::\n \tOnly expire loose objects older than <time>.\n \n+--worktrees::\n+\tPrune dead working tree information in $GIT_DIR/worktrees.\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 8228450..2b30a92 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -259,6 +259,25 @@ worktrees::\n \t$GIT_COMMON_DIR is set and \"$GIT_COMMON_DIR/worktrees\" will be\n \tused instead.\n \n+worktrees/<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+worktrees/<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 `worktrees/<id>` could be\n+\tremoved. The file's content contains a reason string on why\n+\tthe repository is locked.\n+\n+worktrees/<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 797e14d..645135a 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -826,7 +826,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n \tconst char *path = opts->new_worktree, *name;\n \tstruct stat st;\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@@ -857,11 +857,21 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n \tif (mkdir(sb_repo.buf, 0777))\n \t\tdie_errno(_(\"could not create directory of '%s'\"), sb_repo.buf);\n \n+\t/*\n+\t * lock the incomplete repo so prune won't delete it, unlock\n+\t * after the preparation is over.\n+\t */\n+\tstrbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n+\twrite_file(sb.buf, 1, \"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 \n+\tstrbuf_reset(&sb);\n+\tstrbuf_addf(&sb, \"%s/gitdir\", sb_repo.buf);\n+\twrite_file(sb.buf, 1, \"%s\\n\", real_path(sb_git.buf));\n \twrite_file(sb_git.buf, 1, \"gitdir: %s/worktrees/%s\\n\",\n \t\t   real_path(get_git_common_dir()), name);\n \t/*\n@@ -870,6 +880,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n \t * value would do because this value will be ignored and\n \t * replaced at the next (real) checkout.\n \t */\n+\tstrbuf_reset(&sb);\n \tstrbuf_addf(&sb, \"%s/HEAD\", sb_repo.buf);\n \twrite_file(sb.buf, 1, \"%s\\n\", sha1_to_hex(new->commit->object.sha1));\n \tstrbuf_reset(&sb);\n@@ -885,7 +896,11 @@ static int prepare_linked_checkout(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+\tstrbuf_reset(&sb);\n+\tstrbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n+\tunlink_or_warn(sb.buf);\n+\treturn ret;\n }\n \n static int git_checkout_config(const char *var, const char *value, void *cb)\ndiff --git a/builtin/prune.c b/builtin/prune.c\nindex 04d3b12..f08670a 100644\n--- a/builtin/prune.c\n+++ b/builtin/prune.c\n@@ -76,6 +76,91 @@ static int prune_subdir(int nr, const char *path, void *data)\n \treturn 0;\n }\n \n+static int prune_worktree(const char *id, struct strbuf *reason)\n+{\n+\tstruct stat st;\n+\tchar *path;\n+\tint fd, len;\n+\n+\tif (!is_directory(git_path(\"worktrees/%s\", id))) {\n+\t\tstrbuf_addf(reason, _(\"Removing worktrees/%s: not a valid directory\"), id);\n+\t\treturn 1;\n+\t}\n+\tif (file_exists(git_path(\"worktrees/%s/locked\", id)))\n+\t\treturn 0;\n+\tif (stat(git_path(\"worktrees/%s/gitdir\", id), &st)) {\n+\t\tstrbuf_addf(reason, _(\"Removing worktrees/%s: gitdir file does not exist\"), id);\n+\t\treturn 1;\n+\t}\n+\tfd = open(git_path(\"worktrees/%s/gitdir\", id), O_RDONLY);\n+\tif (fd < 0) {\n+\t\tstrbuf_addf(reason, _(\"Removing worktrees/%s: unable to read gitdir file (%s)\"),\n+\t\t\t    id, strerror(errno));\n+\t\treturn 1;\n+\t}\n+\tlen = st.st_size;\n+\tpath = xmalloc(len + 1);\n+\tread_in_full(fd, path, len);\n+\tclose(fd);\n+\twhile (len && (path[len - 1] == '\\n' || path[len - 1] == '\\r'))\n+\t\tlen--;\n+\tif (!len) {\n+\t\tstrbuf_addf(reason, _(\"Removing worktrees/%s: invalid gitdir file\"), id);\n+\t\tfree(path);\n+\t\treturn 1;\n+\t}\n+\tpath[len] = '\\0';\n+\tif (!file_exists(path)) {\n+\t\tstruct stat st_link;\n+\t\tfree(path);\n+\t\t/*\n+\t\t * the repo is moved manually and has not been\n+\t\t * accessed since?\n+\t\t */\n+\t\tif (!stat(git_path(\"worktrees/%s/link\", id), &st_link) &&\n+\t\t    st_link.st_nlink > 1)\n+\t\t\treturn 0;\n+\t\tstrbuf_addf(reason, _(\"Removing worktrees/%s: gitdir file points to non-existent location\"), id);\n+\t\treturn 1;\n+\t}\n+\tfree(path);\n+\treturn st.st_mtime <= expire;\n+}\n+\n+static void prune_worktrees(void)\n+{\n+\tstruct strbuf reason = STRBUF_INIT;\n+\tstruct strbuf path = STRBUF_INIT;\n+\tDIR *dir = opendir(git_path(\"worktrees\"));\n+\tstruct dirent *d;\n+\tint ret;\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\tstrbuf_reset(&reason);\n+\t\tif (!prune_worktree(d->d_name, &reason))\n+\t\t\tcontinue;\n+\t\tif (show_only || verbose)\n+\t\t\tprintf(\"%s\\n\", reason.buf);\n+\t\tif (show_only)\n+\t\t\tcontinue;\n+\t\tstrbuf_reset(&path);\n+\t\tstrbuf_addstr(&path, git_path(\"worktrees/%s\", d->d_name));\n+\t\tret = remove_dir_recursively(&path, 0);\n+\t\tif (ret < 0 && errno == ENOTDIR)\n+\t\t\tret = unlink(path.buf);\n+\t\tif (ret)\n+\t\t\terror(_(\"failed to remove: %s\"), strerror(errno));\n+\t}\n+\tclosedir(dir);\n+\tif (!show_only)\n+\t\trmdir(git_path(\"worktrees\"));\n+\tstrbuf_release(&reason);\n+\tstrbuf_release(&path);\n+}\n+\n /*\n  * Write errors (particularly out of space) can result in\n  * failed temporary packs (and more rarely indexes and other\n@@ -102,10 +187,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 do_prune_worktrees = 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, \"worktrees\", &do_prune_worktrees, N_(\"prune .git/worktrees\")),\n \t\tOPT_EXPIRY_DATE(0, \"expire\", &expire,\n \t\t\t\tN_(\"expire objects older than <time>\")),\n \t\tOPT_END()\n@@ -118,6 +205,14 @@ int cmd_prune(int argc, const char **argv, const char *prefix)\n \tinit_revisions(&revs, prefix);\n \n \targc = parse_options(argc, argv, prefix, options, prune_usage, 0);\n+\n+\tif (do_prune_worktrees) {\n+\t\tif (argc)\n+\t\t\tdie(_(\"--worktrees does not take extra arguments\"));\n+\t\tprune_worktrees();\n+\t\treturn 0;\n+\t}\n+\n \twhile (argc--) {\n \t\tunsigned char sha1[20];\n \t\tconst char *name = *argv++;\ndiff --git a/setup.c b/setup.c\nindex b99bdd3..fb61860 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -390,6 +390,17 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)\n \treturn ret;\n }\n \n+static void update_linked_gitdir(const char *gitfile, const char *gitdir)\n+{\n+\tstruct strbuf path = STRBUF_INIT;\n+\tstruct stat st;\n+\n+\tstrbuf_addf(&path, \"%s/gitfile\", gitdir);\n+\tif (stat(path.buf, &st) || st.st_mtime + 24 * 3600 < time(NULL))\n+\t\twrite_file(path.buf, 0, \"%s\\n\", gitfile);\n+\tstrbuf_release(&path);\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@@ -438,6 +449,8 @@ const char *read_gitfile(const char *path)\n \n \tif (!is_git_directory(dir))\n \t\tdie(\"Not a git repository: %s\", dir);\n+\n+\tupdate_linked_gitdir(path, dir);\n \tpath = real_path(dir);\n \n \tfree(buf);\ndiff --git a/t/t2026-prune-linked-checkouts.sh b/t/t2026-prune-linked-checkouts.sh\nnew file mode 100755\nindex 0000000..170aefe\n--- /dev/null\n+++ b/t/t2026-prune-linked-checkouts.sh\n@@ -0,0 +1,84 @@\n+#!/bin/sh\n+\n+test_description='prune $GIT_DIR/worktrees'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'prune --worktrees on normal repo' '\n+\tgit prune --worktrees &&\n+\ttest_must_fail git prune --worktrees abc\n+'\n+\n+test_expect_success 'prune files inside $GIT_DIR/worktrees' '\n+\tmkdir .git/worktrees &&\n+\t: >.git/worktrees/abc &&\n+\tgit prune --worktrees --verbose >actual &&\n+\tcat >expect <<EOF &&\n+Removing worktrees/abc: not a valid directory\n+EOF\n+\ttest_i18ncmp expect actual &&\n+\t! test -f .git/worktrees/abc &&\n+\t! test -d .git/worktrees\n+'\n+\n+test_expect_success 'prune directories without gitdir' '\n+\tmkdir -p .git/worktrees/def/abc &&\n+\t: >.git/worktrees/def/def &&\n+\tcat >expect <<EOF &&\n+Removing worktrees/def: gitdir file does not exist\n+EOF\n+\tgit prune --worktrees --verbose >actual &&\n+\ttest_i18ncmp expect actual &&\n+\t! test -d .git/worktrees/def &&\n+\t! test -d .git/worktrees\n+'\n+\n+test_expect_success POSIXPERM 'prune directories with unreadable gitdir' '\n+\tmkdir -p .git/worktrees/def/abc &&\n+\t: >.git/worktrees/def/def &&\n+\t: >.git/worktrees/def/gitdir &&\n+\tchmod u-r .git/worktrees/def/gitdir &&\n+\tgit prune --worktrees --verbose >actual &&\n+\ttest_i18ngrep \"Removing worktrees/def: unable to read gitdir file\" actual &&\n+\t! test -d .git/worktrees/def &&\n+\t! test -d .git/worktrees\n+'\n+\n+test_expect_success 'prune directories with invalid gitdir' '\n+\tmkdir -p .git/worktrees/def/abc &&\n+\t: >.git/worktrees/def/def &&\n+\t: >.git/worktrees/def/gitdir &&\n+\tgit prune --worktrees --verbose >actual &&\n+\ttest_i18ngrep \"Removing worktrees/def: invalid gitdir file\" actual &&\n+\t! test -d .git/worktrees/def &&\n+\t! test -d .git/worktrees\n+'\n+\n+test_expect_success 'prune directories with gitdir pointing to nowhere' '\n+\tmkdir -p .git/worktrees/def/abc &&\n+\t: >.git/worktrees/def/def &&\n+\techo \"$(pwd)\"/nowhere >.git/worktrees/def/gitdir &&\n+\tgit prune --worktrees --verbose >actual &&\n+\ttest_i18ngrep \"Removing worktrees/def: gitdir file points to non-existent location\" actual &&\n+\t! test -d .git/worktrees/def &&\n+\t! test -d .git/worktrees\n+'\n+\n+test_expect_success 'not prune locked checkout' '\n+\ttest_when_finished rm -r .git/worktrees\n+\tmkdir -p .git/worktrees/ghi &&\n+\t: >.git/worktrees/ghi/locked &&\n+\tgit prune --worktrees &&\n+\ttest -d .git/worktrees/ghi\n+'\n+\n+test_expect_success 'not prune recent checkouts' '\n+\ttest_when_finished rm -r .git/worktrees\n+\tmkdir zz &&\n+\tmkdir -p .git/worktrees/jlm &&\n+\techo \"$(pwd)\"/zz >.git/worktrees/jlm/gitdir &&\n+\tgit prune --worktrees --verbose --expire=2.days.ago &&\n+\ttest -d .git/worktrees/jlm\n+'\n+\n+test_done\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252707","messageId":"1417335899-27307-25-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 24/34] checkout: reject if the branch is already checked out elsewhere","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:49Z","receivedAt":"2014-11-30T08:24:49Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"One branch obviously can't be checked out at two places (but detached\nheads are ok). Give the user a choice in this case: --detach, -b\nnew-branch, switch branch in the other checkout first or simply 'cd'\nand continue to work there.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/checkout.c     | 86 ++++++++++++++++++++++++++++++++++++++++++++++++--\n t/t2025-checkout-to.sh | 25 ++++++++++++---\n 2 files changed, 104 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 645135a..01a28b4 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -430,6 +430,11 @@ struct branch_info {\n \tconst char *name; /* The short name used */\n \tconst char *path; /* The full name of a real branch */\n \tstruct commit *commit; /* The named commit */\n+\t/*\n+\t * if not null the branch is detached because it's already\n+\t * checked out in this checkout\n+\t */\n+\tchar *checkout;\n };\n \n static void setup_branch_path(struct branch_info *branch)\n@@ -958,12 +963,78 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1)\n \treturn NULL;\n }\n \n+static void check_linked_checkout(struct branch_info *new, const char *id)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tstruct strbuf path = STRBUF_INIT;\n+\tstruct strbuf gitdir = STRBUF_INIT;\n+\tconst char *start, *end;\n+\n+\tif (id)\n+\t\tstrbuf_addf(&path, \"%s/worktrees/%s/HEAD\", get_git_common_dir(), id);\n+\telse\n+\t\tstrbuf_addf(&path, \"%s/HEAD\", get_git_common_dir());\n+\n+\tif (strbuf_read_file(&sb, path.buf, 0) < 0 ||\n+\t    !skip_prefix(sb.buf, \"ref:\", &start))\n+\t\tgoto done;\n+\twhile (isspace(*start))\n+\t\tstart++;\n+\tend = start;\n+\twhile (*end && !isspace(*end))\n+\t\tend++;\n+\tif (strncmp(start, new->path, end - start) || new->path[end - start] != '\\0')\n+\t\tgoto done;\n+\tif (id) {\n+\t\tstrbuf_reset(&path);\n+\t\tstrbuf_addf(&path, \"%s/worktrees/%s/gitdir\", get_git_common_dir(), id);\n+\t\tif (strbuf_read_file(&gitdir, path.buf, 0) <= 0)\n+\t\t\tgoto done;\n+\t\tstrbuf_rtrim(&gitdir);\n+\t} else\n+\t\tstrbuf_addstr(&gitdir, get_git_common_dir());\n+\tdie(_(\"'%s' is already checked out at '%s'\"), new->name, gitdir.buf);\n+done:\n+\tstrbuf_release(&path);\n+\tstrbuf_release(&sb);\n+\tstrbuf_release(&gitdir);\n+}\n+\n+static void check_linked_checkouts(struct branch_info *new)\n+{\n+\tstruct strbuf path = STRBUF_INIT;\n+\tDIR *dir;\n+\tstruct dirent *d;\n+\n+\tstrbuf_addf(&path, \"%s/worktrees\", get_git_common_dir());\n+\tif ((dir = opendir(path.buf)) == NULL) {\n+\t\tstrbuf_release(&path);\n+\t\treturn;\n+\t}\n+\n+\t/*\n+\t * $GIT_COMMON_DIR/HEAD is practically outside\n+\t * $GIT_DIR so resolve_ref_unsafe() won't work (it\n+\t * uses git_path). Parse the ref ourselves.\n+\t */\n+\tcheck_linked_checkout(new, NULL);\n+\n+\twhile ((d = readdir(dir)) != NULL) {\n+\t\tif (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n+\t\t\tcontinue;\n+\t\tcheck_linked_checkout(new, d->d_name);\n+\t}\n+\tstrbuf_release(&path);\n+\tclosedir(dir);\n+}\n+\n static int parse_branchname_arg(int argc, const char **argv,\n \t\t\t\tint dwim_new_local_branch_ok,\n \t\t\t\tstruct branch_info *new,\n \t\t\t\tstruct tree **source_tree,\n \t\t\t\tunsigned char rev[20],\n-\t\t\t\tconst char **new_branch)\n+\t\t\t\tconst char **new_branch,\n+\t\t\t\tint force_detach)\n {\n \tint argcount = 0;\n \tunsigned char branch_rev[20];\n@@ -1085,6 +1156,16 @@ static int parse_branchname_arg(int argc, const char **argv,\n \telse\n \t\tnew->path = NULL; /* not an existing branch */\n \n+\tif (new->path && !force_detach && !*new_branch) {\n+\t\tunsigned char sha1[20];\n+\t\tint flag;\n+\t\tchar *head_ref = resolve_refdup(\"HEAD\", 0, sha1, &flag);\n+\t\tif (head_ref &&\n+\t\t    (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))\n+\t\t\tcheck_linked_checkouts(new);\n+\t\tfree(head_ref);\n+\t}\n+\n \tnew->commit = lookup_commit_reference_gently(rev, 1);\n \tif (!new->commit) {\n \t\t/* not a commit */\n@@ -1289,7 +1370,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\t\t!opts.new_branch;\n \t\tint n = parse_branchname_arg(argc, argv, dwim_ok,\n \t\t\t\t\t     &new, &opts.source_tree,\n-\t\t\t\t\t     rev, &opts.new_branch);\n+\t\t\t\t\t     rev, &opts.new_branch,\n+\t\t\t\t\t     opts.force_detach);\n \t\targv += n;\n \t\targc -= n;\n \t}\ndiff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh\nindex 4963415..edd3404 100755\n--- a/t/t2025-checkout-to.sh\n+++ b/t/t2025-checkout-to.sh\n@@ -18,13 +18,14 @@ test_expect_success 'checkout --to an existing worktree' '\n '\n \n test_expect_success 'checkout --to a new worktree' '\n-\tgit checkout --to here master &&\n+\tgit rev-parse HEAD >expect &&\n+\tgit checkout --detach --to here master &&\n \t(\n \t\tcd here &&\n \t\ttest_cmp ../init.t init.t &&\n-\t\tgit symbolic-ref HEAD >actual &&\n-\t\techo refs/heads/master >expect &&\n-\t\ttest_cmp expect actual &&\n+\t\ttest_must_fail git symbolic-ref HEAD &&\n+\t\tgit rev-parse HEAD >actual &&\n+\t\ttest_cmp ../expect actual &&\n \t\tgit fsck\n \t)\n '\n@@ -42,7 +43,7 @@ test_expect_success 'checkout --to a new worktree from a subdir' '\n test_expect_success 'checkout --to from a linked checkout' '\n \t(\n \t\tcd here &&\n-\t\tgit checkout --to nested-here master &&\n+\t\tgit checkout --detach --to nested-here master &&\n \t\tcd nested-here &&\n \t\tgit fsck\n \t)\n@@ -60,4 +61,18 @@ test_expect_success 'checkout --to a new worktree creating new branch' '\n \t)\n '\n \n+test_expect_success 'die the same branch is already checked out' '\n+\t(\n+\t\tcd here &&\n+\t\ttest_must_fail git checkout newmaster\n+\t)\n+'\n+\n+test_expect_success 'not die on re-checking out current branch' '\n+\t(\n+\t\tcd there &&\n+\t\tgit checkout newmaster\n+\t)\n+'\n+\n test_done\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252708","messageId":"1417335899-27307-26-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 25/34] checkout: clean up half-prepared directories in --to mode","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:50Z","receivedAt":"2014-11-30T08:24:50Z","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     | 48 ++++++++++++++++++++++++++++++++++++++++++++++++\n t/t2025-checkout-to.sh |  6 ++++++\n 2 files changed, 54 insertions(+)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 01a28b4..5dfdbda 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@@ -823,6 +824,35 @@ static int switch_branches(const struct checkout_opts *opts,\n \treturn ret || writeout_error;\n }\n \n+static char *junk_work_tree;\n+static 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}\n+\tstrbuf_release(&sb);\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 prepare_linked_checkout(const struct checkout_opts *opts,\n \t\t\t\t   struct branch_info *new)\n {\n@@ -859,8 +889,15 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n \t\tstrbuf_addf(&sb_repo, \"%d\", counter);\n \t}\n \tname = strrchr(sb_repo.buf, '/') + 1;\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 = xstrdup(sb_repo.buf);\n+\tis_junk = 1;\n \n \t/*\n \t * lock the incomplete repo so prune won't delete it, unlock\n@@ -873,6 +910,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\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 = xstrdup(path);\n \n \tstrbuf_reset(&sb);\n \tstrbuf_addf(&sb, \"%s/gitdir\", sb_repo.buf);\n@@ -902,9 +940,19 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n \tcp.git_cmd = 1;\n \tcp.argv = opts->saved_argv;\n \tret = run_command(&cp);\n+\tif (!ret) {\n+\t\tis_junk = 0;\n+\t\tfree(junk_work_tree);\n+\t\tfree(junk_git_dir);\n+\t\tjunk_work_tree = NULL;\n+\t\tjunk_git_dir = NULL;\n+\t}\n \tstrbuf_reset(&sb);\n \tstrbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n \tunlink_or_warn(sb.buf);\n+\tstrbuf_release(&sb);\n+\tstrbuf_release(&sb_repo);\n+\tstrbuf_release(&sb_git);\n \treturn ret;\n }\n \ndiff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh\nindex edd3404..e2db078 100755\n--- a/t/t2025-checkout-to.sh\n+++ b/t/t2025-checkout-to.sh\n@@ -17,6 +17,12 @@ test_expect_success 'checkout --to an existing worktree' '\n \ttest_must_fail git checkout --detach --to existing master\n '\n \n+test_expect_success 'checkout --to refuses to checkout locked branch' '\n+\ttest_must_fail git checkout --to zere master &&\n+\t! test -d zere &&\n+\t! test -d .git/worktrees/zere\n+'\n+\n test_expect_success 'checkout --to a new worktree' '\n \tgit rev-parse HEAD >expect &&\n \tgit checkout --detach --to here master &&\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252709","messageId":"1417335899-27307-27-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 26/34] gc: style change -- no SP before closing parenthesis","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:51Z","receivedAt":"2014-11-30T08:24:51Z","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/gc.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 005adbe..0e65eff 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -287,7 +287,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n \targv_array_pushl(&pack_refs_cmd, \"pack-refs\", \"--all\", \"--prune\", NULL);\n \targv_array_pushl(&reflog, \"reflog\", \"expire\", \"--all\", NULL);\n \targv_array_pushl(&repack, \"repack\", \"-d\", \"-l\", NULL);\n-\targv_array_pushl(&prune, \"prune\", \"--expire\", NULL );\n+\targv_array_pushl(&prune, \"prune\", \"--expire\", NULL);\n \targv_array_pushl(&rerere, \"rerere\", \"gc\", NULL);\n \n \tgc_config();\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252710","messageId":"1417335899-27307-28-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 27/34] gc: factor out gc.pruneexpire parsing code","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:52Z","receivedAt":"2014-11-30T08:24:52Z","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/gc.c | 22 ++++++++++++----------\n 1 file changed, 12 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 0e65eff..0728650 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -55,6 +55,17 @@ static void remove_pidfile_on_signal(int signo)\n \traise(signo);\n }\n \n+static void git_config_date_string(const char *key, const char **output)\n+{\n+\tif (git_config_get_string_const(key, output))\n+\t\treturn;\n+\tif (strcmp(*output, \"now\")) {\n+\t\tunsigned long now = approxidate(\"now\");\n+\t\tif (approxidate(*output) >= now)\n+\t\t\tgit_die_config(key, _(\"Invalid %s: '%s'\"), key, *output);\n+\t}\n+}\n+\n static void gc_config(void)\n {\n \tconst char *value;\n@@ -71,16 +82,7 @@ static void gc_config(void)\n \tgit_config_get_int(\"gc.auto\", &gc_auto_threshold);\n \tgit_config_get_int(\"gc.autopacklimit\", &gc_auto_pack_limit);\n \tgit_config_get_bool(\"gc.autodetach\", &detach_auto);\n-\n-\tif (!git_config_get_string_const(\"gc.pruneexpire\", &prune_expire)) {\n-\t\tif (strcmp(prune_expire, \"now\")) {\n-\t\t\tunsigned long now = approxidate(\"now\");\n-\t\t\tif (approxidate(prune_expire) >= now) {\n-\t\t\t\tgit_die_config(\"gc.pruneexpire\", _(\"Invalid gc.pruneexpire: '%s'\"),\n-\t\t\t\t\t\tprune_expire);\n-\t\t\t}\n-\t\t}\n-\t}\n+\tgit_config_date_string(\"gc.pruneexpire\", &prune_expire);\n \tgit_config(git_default_config, NULL);\n }\n \n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252711","messageId":"1417335899-27307-29-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 28/34] gc: support prune --worktrees","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:53Z","receivedAt":"2014-11-30T08:24:53Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Helped-by: Marc Branchaud <marcnarc@xiplink.com>\nSigned-off-by: Marc Branchaud <marcnarc@xiplink.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/config.txt       |  7 +++++++\n Documentation/git-checkout.txt | 11 +++++++----\n builtin/gc.c                   | 10 ++++++++++\n 3 files changed, 24 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex e1623ec..2700a1b 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1229,6 +1229,13 @@ gc.pruneexpire::\n \t\"now\" may be used to disable this  grace period and always prune\n \tunreachable objects immediately.\n \n+gc.pruneworktreesexpire::\n+\tWhen 'git gc' is run, it will call\n+\t'prune --worktrees --expire 3.months.ago'.\n+\tOverride the grace period with this config variable. The value\n+\t\"now\" may be used to disable the grace period and prune\n+\t$GIT_DIR/worktrees immediately.\n+\n gc.reflogexpire::\n gc.<pattern>.reflogexpire::\n \t'git reflog expire' removes reflog entries older than\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex 35675da..0c13825 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -435,8 +435,11 @@ $GIT_DIR or $GIT_COMMON_DIR when you need to directly access something\n inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path.\n \n When you are done with a linked working tree you can simply delete it.\n-You can clean up any stale $GIT_DIR/worktrees entries via `git prune\n---worktrees` in the main or any linked working tree.\n+The working tree's entry in the repository's $GIT_DIR/worktrees\n+directory will eventually be removed automatically (see\n+`gc.pruneworktreesexpire` in linkgit::git-config[1]), or you can run\n+`git prune --worktrees` in the main or any linked working tree to\n+clean up any stale entries in $GIT_DIR/worktrees.\n \n If you move a linked working directory to another file system, or\n within a file system that does not support hard links, you need to run\n@@ -444,8 +447,8 @@ at least one git command inside the linked working directory\n (e.g. `git status`) in order to update its entry in $GIT_DIR/worktrees\n so that it does not get automatically removed.\n \n-To prevent `git prune --worktrees` from deleting a $GIT_DIR/worktrees\n-entry (which can be useful in some situations, such as when the\n+To prevent a $GIT_DIR/worktrees entry from from being pruned (which\n+can be useful in some situations, such as when the\n entry's working tree is stored on a portable device), add a file named\n 'locked' to the entry's directory. The file contains the reason in\n plain text. For example, if a linked working tree's `.git` file points\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 0728650..fa87ad3 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -33,11 +33,13 @@ static int gc_auto_threshold = 6700;\n static int gc_auto_pack_limit = 50;\n static int detach_auto = 1;\n static const char *prune_expire = \"2.weeks.ago\";\n+static const char *prune_worktrees_expire = \"3.months.ago\";\n \n static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT;\n static struct argv_array reflog = ARGV_ARRAY_INIT;\n static struct argv_array repack = ARGV_ARRAY_INIT;\n static struct argv_array prune = ARGV_ARRAY_INIT;\n+static struct argv_array prune_worktrees = ARGV_ARRAY_INIT;\n static struct argv_array rerere = ARGV_ARRAY_INIT;\n \n static char *pidfile;\n@@ -83,6 +85,7 @@ static void gc_config(void)\n \tgit_config_get_int(\"gc.autopacklimit\", &gc_auto_pack_limit);\n \tgit_config_get_bool(\"gc.autodetach\", &detach_auto);\n \tgit_config_date_string(\"gc.pruneexpire\", &prune_expire);\n+\tgit_config_date_string(\"gc.pruneworktreesexpire\", &prune_worktrees_expire);\n \tgit_config(git_default_config, NULL);\n }\n \n@@ -290,6 +293,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n \targv_array_pushl(&reflog, \"reflog\", \"expire\", \"--all\", NULL);\n \targv_array_pushl(&repack, \"repack\", \"-d\", \"-l\", NULL);\n \targv_array_pushl(&prune, \"prune\", \"--expire\", NULL);\n+\targv_array_pushl(&prune_worktrees, \"prune\", \"--worktrees\", \"--expire\", NULL);\n \targv_array_pushl(&rerere, \"rerere\", \"gc\", NULL);\n \n \tgc_config();\n@@ -359,6 +363,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n \t\t\treturn error(FAILED_RUN, prune.argv[0]);\n \t}\n \n+\tif (prune_worktrees_expire) {\n+\t\targv_array_push(&prune_worktrees, prune_worktrees_expire);\n+\t\tif (run_command_v_opt(prune_worktrees.argv, RUN_GIT_CMD))\n+\t\t\treturn error(FAILED_RUN, prune_worktrees.argv[0]);\n+\t}\n+\n \tif (run_command_v_opt(rerere.argv, RUN_GIT_CMD))\n \t\treturn error(FAILED_RUN, rerere.argv[0]);\n \n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252712","messageId":"1417335899-27307-30-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 29/34] count-objects: report unused files in $GIT_DIR/worktrees/...","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:54Z","receivedAt":"2014-11-30T08:24:54Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"In linked checkouts, borrowed parts like config is taken from\n$GIT_COMMON_DIR. $GIT_DIR/config is never used. Report them as\ngarbage.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/count-objects.c |  4 +++-\n cache.h                 |  1 +\n path.c                  | 29 +++++++++++++++++++++++++++--\n 3 files changed, 31 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/count-objects.c b/builtin/count-objects.c\nindex e47ef0b..ad0c799 100644\n--- a/builtin/count-objects.c\n+++ b/builtin/count-objects.c\n@@ -70,8 +70,10 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)\n \t/* we do not take arguments other than flags for now */\n \tif (argc)\n \t\tusage_with_options(count_objects_usage, opts);\n-\tif (verbose)\n+\tif (verbose) {\n \t\treport_garbage = real_report_garbage;\n+\t\treport_linked_checkout_garbage();\n+\t}\n \n \tfor_each_loose_file_in_objdir(get_object_directory(),\n \t\t\t\t      count_loose, count_cruft, NULL, NULL);\ndiff --git a/cache.h b/cache.h\nindex f8e75a1..3f60a11 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -695,6 +695,7 @@ extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1\n extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n extern const char *git_path_submodule(const char *path, const char *fmt, ...)\n \t__attribute__((format (printf, 2, 3)));\n+extern void report_linked_checkout_garbage(void);\n \n /*\n  * Return the name of the file in the local object database that would\ndiff --git a/path.c b/path.c\nindex 72eca6d..cd8e2d6 100644\n--- a/path.c\n+++ b/path.c\n@@ -4,6 +4,7 @@\n #include \"cache.h\"\n #include \"strbuf.h\"\n #include \"string-list.h\"\n+#include \"dir.h\"\n \n static int get_st_mode_bits(const char *path, int *mode)\n {\n@@ -91,9 +92,9 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)\n }\n \n static const char *common_list[] = {\n-\t\"/branches\", \"/hooks\", \"/info\", \"/logs\", \"/lost-found\", \"/modules\",\n+\t\"/branches\", \"/hooks\", \"/info\", \"!/logs\", \"/lost-found\", \"/modules\",\n \t\"/objects\", \"/refs\", \"/remotes\", \"/worktrees\", \"/rr-cache\", \"/svn\",\n-\t\"config\", \"gc.pid\", \"packed-refs\", \"shallow\",\n+\t\"config\", \"!gc.pid\", \"packed-refs\", \"shallow\",\n \tNULL\n };\n \n@@ -107,6 +108,8 @@ static void update_common_dir(struct strbuf *buf, int git_dir_len)\n \tfor (p = common_list; *p; p++) {\n \t\tconst char *path = *p;\n \t\tint is_dir = 0;\n+\t\tif (*path == '!')\n+\t\t\tpath++;\n \t\tif (*path == '/') {\n \t\t\tpath++;\n \t\t\tis_dir = 1;\n@@ -122,6 +125,28 @@ static void update_common_dir(struct strbuf *buf, int git_dir_len)\n \t}\n }\n \n+void report_linked_checkout_garbage(void)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tconst char **p;\n+\tint len;\n+\n+\tif (!git_common_dir_env)\n+\t\treturn;\n+\tstrbuf_addf(&sb, \"%s/\", get_git_dir());\n+\tlen = sb.len;\n+\tfor (p = common_list; *p; p++) {\n+\t\tconst char *path = *p;\n+\t\tif (*path == '!')\n+\t\t\tcontinue;\n+\t\tstrbuf_setlen(&sb, len);\n+\t\tstrbuf_addstr(&sb, path);\n+\t\tif (file_exists(sb.buf))\n+\t\t\treport_garbage(\"unused in linked checkout\", sb.buf);\n+\t}\n+\tstrbuf_release(&sb);\n+}\n+\n static void adjust_git_path(struct strbuf *buf, int git_dir_len)\n {\n \tconst char *base = buf->buf + git_dir_len;\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252713","messageId":"1417335899-27307-31-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 30/34] git_path(): keep \"info/sparse-checkout\" per work-tree","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:55Z","receivedAt":"2014-11-30T08:24:55Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Currently git_path(\"info/sparse-checkout\") resolves to\n$GIT_COMMON_DIR/info/sparse-checkout in multiple worktree mode. It\nmakes more sense for the sparse checkout patterns to be per worktree,\nso you can have multiple checkouts with different parts of the tree.\n\nWith this, \"git checkout --to <new>\" on a sparse checkout will create\n<new> as a full checkout. Which is expected, it's how a new checkout\nis made. The user can reshape the worktree afterwards.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n path.c                | 3 ++-\n t/t0060-path-utils.sh | 1 +\n 2 files changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/path.c b/path.c\nindex cd8e2d6..35d498e 100644\n--- a/path.c\n+++ b/path.c\n@@ -103,7 +103,8 @@ static void update_common_dir(struct strbuf *buf, int git_dir_len)\n \tchar *base = buf->buf + git_dir_len;\n \tconst char **p;\n \n-\tif (is_dir_file(base, \"logs\", \"HEAD\"))\n+\tif (is_dir_file(base, \"logs\", \"HEAD\") ||\n+\t    is_dir_file(base, \"info\", \"sparse-checkout\"))\n \t\treturn;\t/* keep this in $GIT_DIR */\n \tfor (p = common_list; *p; p++) {\n \t\tconst char *path = *p;\ndiff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh\nindex f5d6f80..93605f4 100755\n--- a/t/t0060-path-utils.sh\n+++ b/t/t0060-path-utils.sh\n@@ -270,6 +270,7 @@ test_git_path GIT_COMMON_DIR=bar objects                  bar/objects\n test_git_path GIT_COMMON_DIR=bar objects/bar              bar/objects/bar\n test_git_path GIT_COMMON_DIR=bar info/exclude             bar/info/exclude\n test_git_path GIT_COMMON_DIR=bar info/grafts              bar/info/grafts\n+test_git_path GIT_COMMON_DIR=bar info/sparse-checkout     .git/info/sparse-checkout\n test_git_path GIT_COMMON_DIR=bar remotes/bar              bar/remotes/bar\n test_git_path GIT_COMMON_DIR=bar branches/bar             bar/branches/bar\n test_git_path GIT_COMMON_DIR=bar logs/refs/heads/master   bar/logs/refs/heads/master\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252714","messageId":"1417335899-27307-32-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 31/34] checkout: don't require a work tree when checking out into a new one","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:56Z","receivedAt":"2014-11-30T08:24:56Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"From: Dennis Kaarsemaker <dennis@kaarsemaker.net>\n\nFor normal use cases, it does not make sense for 'checkout' to work on\na bare repository, without a worktree. But \"checkout --to\" is an\nexception because it _creates_ a new worktree. Allow this option to\nrun on bare repositories.\n\nPeople who check out from a bare repository should remember that\ncore.logallrefupdates is off by default and it should be turned back\non. `--to` cannot do this automatically behind the user's back because\nsome user may deliberately want no reflog.\n\nFor people interested in repository setup/discovery code,\nis_bare_repository_cfg (aka \"core.bare\") is unchanged by this patch,\nwhich means 'true' by default for bare repos. Fortunately when we get\nthe repo through a linked checkout, is_bare_repository_cfg is never\nused. So all is still good.\n\n[nd: commit message]\n\nSigned-off-by: Dennis Kaarsemaker <dennis@kaarsemaker.net>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/checkout.c     |  3 +++\n git.c                  |  2 +-\n t/t2025-checkout-to.sh | 15 +++++++++++++++\n 3 files changed, 19 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 5dfdbda..8c5276c 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1364,6 +1364,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \tif (opts.new_worktree_mode)\n \t\topts.new_worktree = NULL;\n \n+\tif (!opts.new_worktree)\n+\t\tsetup_work_tree();\n+\n \tif (conflict_style) {\n \t\topts.merge = 1; /* implied */\n \t\tgit_xmerge_config(\"merge.conflictstyle\", conflict_style, NULL);\ndiff --git a/git.c b/git.c\nindex 18fbf79..160896a 100644\n--- a/git.c\n+++ b/git.c\n@@ -383,7 +383,7 @@ static struct cmd_struct commands[] = {\n \t{ \"check-ignore\", cmd_check_ignore, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"check-mailmap\", cmd_check_mailmap, RUN_SETUP },\n \t{ \"check-ref-format\", cmd_check_ref_format },\n-\t{ \"checkout\", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },\n+\t{ \"checkout\", cmd_checkout, RUN_SETUP },\n \t{ \"checkout-index\", cmd_checkout_index,\n \t\tRUN_SETUP | NEED_WORK_TREE},\n \t{ \"cherry\", cmd_cherry, RUN_SETUP },\ndiff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh\nindex e2db078..4bd1df4 100755\n--- a/t/t2025-checkout-to.sh\n+++ b/t/t2025-checkout-to.sh\n@@ -81,4 +81,19 @@ test_expect_success 'not die on re-checking out current branch' '\n \t)\n '\n \n+test_expect_success 'checkout --to from a bare repo' '\n+\t(\n+\t\tgit clone --bare . bare &&\n+\t\tcd bare &&\n+\t\tgit checkout --to ../there2 -b bare-master master\n+\t)\n+'\n+\n+test_expect_success 'checkout from a bare repo without --to' '\n+\t(\n+\t\tcd bare &&\n+\t\ttest_must_fail git checkout master\n+\t)\n+'\n+\n test_done\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252715","messageId":"1417335899-27307-33-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 32/34] t2025: add a test to make sure grafts is working from a linked checkout","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:57Z","receivedAt":"2014-11-30T08:24: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 t/t2025-checkout-to.sh | 18 ++++++++++++++++++\n 1 file changed, 18 insertions(+)\n\ndiff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh\nindex 4bd1df4..eddd325 100755\n--- a/t/t2025-checkout-to.sh\n+++ b/t/t2025-checkout-to.sh\n@@ -96,4 +96,22 @@ test_expect_success 'checkout from a bare repo without --to' '\n \t)\n '\n \n+test_expect_success 'checkout with grafts' '\n+\ttest_when_finished rm .git/info/grafts &&\n+\ttest_commit abc &&\n+\tSHA1=`git rev-parse HEAD` &&\n+\ttest_commit def &&\n+\ttest_commit xyz &&\n+\techo \"`git rev-parse HEAD` $SHA1\" >.git/info/grafts &&\n+\tcat >expected <<-\\EOF &&\n+\txyz\n+\tabc\n+\tEOF\n+\tgit log --format=%s -2 >actual &&\n+\ttest_cmp expected actual &&\n+\tgit checkout --detach --to grafted master &&\n+\tgit --git-dir=grafted/.git log --format=%s -2 >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_done\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252716","messageId":"1417335899-27307-34-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 33/34] checkout: do not fail if target is an empty directory","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:58Z","receivedAt":"2014-11-30T08:24:58Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"From: Max Kirillov <max@max630.net>\n\nNon-recursive checkout creates empty directpries in place of submodules.\nIf then I try to \"checkout --to\" submodules there, it refuses to do so,\nbecause directory already exists.\n\nFix by allowing checking out to empty directory. Add test and modify the\nexisting one so that it uses non-empty directory.\n\nSigned-off-by: Max Kirillov <max@max630.net>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/checkout.c     | 2 +-\n t/t2025-checkout-to.sh | 7 ++++++-\n 2 files changed, 7 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 8c5276c..953b763 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -865,7 +865,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n \n \tif (!new->commit)\n \t\tdie(_(\"no branch specified\"));\n-\tif (file_exists(path))\n+\tif (file_exists(path) && !is_empty_dir(path))\n \t\tdie(_(\"'%s' already exists\"), path);\n \n \tlen = strlen(path);\ndiff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh\nindex eddd325..915b506 100755\n--- a/t/t2025-checkout-to.sh\n+++ b/t/t2025-checkout-to.sh\n@@ -13,10 +13,15 @@ test_expect_success 'checkout --to not updating paths' '\n '\n \n test_expect_success 'checkout --to an existing worktree' '\n-\tmkdir existing &&\n+\tmkdir -p existing/subtree &&\n \ttest_must_fail git checkout --detach --to existing master\n '\n \n+test_expect_success 'checkout --to an existing empty worktree' '\n+\tmkdir existing_empty &&\n+\tgit checkout --detach --to existing_empty master\n+'\n+\n test_expect_success 'checkout --to refuses to checkout locked branch' '\n \ttest_must_fail git checkout --to zere master &&\n \t! test -d zere &&\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252717","messageId":"1417335899-27307-35-git-send-email-pclouds@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 34/34] git-common-dir: make \"modules/\" per-working-directory directory","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-30T08:24:59Z","receivedAt":"2014-11-30T08:24:59Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"From: Max Kirillov <max@max630.net>\n\nEach working directory of main repository has its own working directory\nof submodule, and in most cases they should be checked out to different\nrevisions. So they should be separated.\n\nIt looks logical to make submodule instances in different working\ndirectories to reuse the submodule directory in the common dir of\nthe main repository, and probably this is how \"checkout --to\" should\ninitialize them called on the main repository, but they also should work\nfine being completely separated clones.\n\nTestfile t7410-submodule-checkout-to.sh demostrates the behavior.\n\nSigned-off-by: Max Kirillov <max@max630.net>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/gitrepository-layout.txt    |  4 +--\n path.c                                    |  2 +-\n t/t7410-submodule-checkout-to.sh (new +x) | 50 +++++++++++++++++++++++++++++++\n 3 files changed, 52 insertions(+), 4 deletions(-)\n create mode 100755 t/t7410-submodule-checkout-to.sh\n\ndiff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt\nindex 2b30a92..7173b38 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -248,9 +248,7 @@ commondir::\n \tincomplete without the repository pointed by \"commondir\".\n \n modules::\n-\tContains the git-repositories of the submodules. This\n-\tdirectory is ignored if $GIT_COMMON_DIR is set and\n-\t\"$GIT_COMMON_DIR/modules\" will be used instead.\n+\tContains the git-repositories of the submodules.\n \n worktrees::\n \tContains worktree specific information of linked\ndiff --git a/path.c b/path.c\nindex 35d498e..a5c51a3 100644\n--- a/path.c\n+++ b/path.c\n@@ -92,7 +92,7 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)\n }\n \n static const char *common_list[] = {\n-\t\"/branches\", \"/hooks\", \"/info\", \"!/logs\", \"/lost-found\", \"/modules\",\n+\t\"/branches\", \"/hooks\", \"/info\", \"!/logs\", \"/lost-found\",\n \t\"/objects\", \"/refs\", \"/remotes\", \"/worktrees\", \"/rr-cache\", \"/svn\",\n \t\"config\", \"!gc.pid\", \"packed-refs\", \"shallow\",\n \tNULL\ndiff --git a/t/t7410-submodule-checkout-to.sh b/t/t7410-submodule-checkout-to.sh\nnew file mode 100755\nindex 0000000..8f30aed\n--- /dev/null\n+++ b/t/t7410-submodule-checkout-to.sh\n@@ -0,0 +1,50 @@\n+#!/bin/sh\n+\n+test_description='Combination of submodules and multiple workdirs'\n+\n+. ./test-lib.sh\n+\n+base_path=$(pwd -P)\n+\n+test_expect_success 'setup: make origin' \\\n+    'mkdir -p origin/sub && ( cd origin/sub && git init &&\n+\techo file1 >file1 &&\n+\tgit add file1 &&\n+\tgit commit -m file1 ) &&\n+    mkdir -p origin/main && ( cd origin/main && git init &&\n+\tgit submodule add ../sub &&\n+\tgit commit -m \"add sub\" ) &&\n+    ( cd origin/sub &&\n+\techo file1updated >file1 &&\n+\tgit add file1 &&\n+\tgit commit -m \"file1 updated\" ) &&\n+    ( cd origin/main/sub && git pull ) &&\n+    ( cd origin/main &&\n+\tgit add sub &&\n+\tgit commit -m \"sub updated\" )'\n+\n+test_expect_success 'setup: clone' \\\n+    'mkdir clone && ( cd clone &&\n+\tgit clone --recursive \"$base_path/origin/main\")'\n+\n+rev1_hash_main=$(git --git-dir=origin/main/.git show --pretty=format:%h -q \"HEAD~1\")\n+rev1_hash_sub=$(git --git-dir=origin/sub/.git show --pretty=format:%h -q \"HEAD~1\")\n+\n+test_expect_success 'checkout main' \\\n+    'mkdir default_checkout &&\n+    (cd clone/main &&\n+\tgit checkout --to \"$base_path/default_checkout/main\" \"$rev1_hash_main\")'\n+\n+test_expect_failure 'can see submodule diffs just after checkout' \\\n+    '(cd default_checkout/main && git diff --submodule master\"^!\" | grep \"file1 updated\")'\n+\n+test_expect_success 'checkout main and initialize independed clones' \\\n+    'mkdir fully_cloned_submodule &&\n+    (cd clone/main &&\n+\tgit checkout --to \"$base_path/fully_cloned_submodule/main\" \"$rev1_hash_main\") &&\n+    (cd fully_cloned_submodule/main && git submodule update)'\n+\n+test_expect_success 'can see submodule diffs after independed cloning' \\\n+    '(cd fully_cloned_submodule/main && git diff --submodule master\"^!\" | grep \"file1 updated\")'\n+\n+test_done\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"252748","messageId":"547B5170.6050206@gmail.com","threadId":"38074","inReplyTo":"1417335899-27307-25-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 24/34] checkout: reject if the branch is already checked out elsewhere","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2014-11-30T17:18:40Z","receivedAt":"2014-11-30T17:18:40Z","isPatch":true,"sender":{"key":"mdl123@verizon.net","avatar":"https://avatars.githubusercontent.com/u/5302462?v=4"},"body":"On 11/30/2014 03:24 AM, Nguyễn Thái Ngọc Duy wrote:\n> One branch obviously can't be checked out at two places (but detached\n> heads are ok). Give the user a choice in this case: --detach, -b\n> new-branch, switch branch in the other checkout first or simply 'cd'\n> and continue to work there.\n>\n\nThis seems too restrictive and is not \"obvious\" to me: I currently use \ngit-new-workdir to have multiple checkouts of the same branch, with no \nill effect. While those who do not understand what is going on \nunderneath might be confused by one checkout suddenly showing \nuncommitted diffs, I don't accept that as a reason to outright prevent \nsuch use. I suggest, at the very least, that this behavior be overridden \nby a --force flag?\n\nMark\n"},{"id":"252774","messageId":"20141201103818.GA20429@lanh","threadId":"38074","inReplyTo":"547B5170.6050206@gmail.com","subject":"Re: [PATCH 24/34] checkout: reject if the branch is already checked out elsewhere","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-12-01T10:38:18Z","receivedAt":"2014-12-01T10:38:18Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Sun, Nov 30, 2014 at 12:18:40PM -0500, Mark Levedahl wrote:\n> On 11/30/2014 03:24 AM, Nguyễn Thái Ngọc Duy wrote:\n> > One branch obviously can't be checked out at two places (but detached\n> > heads are ok). Give the user a choice in this case: --detach, -b\n> > new-branch, switch branch in the other checkout first or simply 'cd'\n> > and continue to work there.\n> >\n> \n> This seems too restrictive and is not \"obvious\" to me: I currently use \n> git-new-workdir to have multiple checkouts of the same branch, with no \n> ill effect. While those who do not understand what is going on \n> underneath might be confused by one checkout suddenly showing \n> uncommitted diffs, I don't accept that as a reason to outright prevent \n> such use. I suggest, at the very least, that this behavior be overridden \n> by a --force flag?\n\nPrevention is a strong word. It's more about safety for the mere\nmortals. It's certainly possible to do something like this patch\n(--force can't be reused, it already carries some other meaning).\nShould I add this one in the next (hic) reroll?\n\n-- 8< --\nSubject: [PATCH] checkout: add --ignore-other-wortrees\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-checkout.txt |  6 ++++++\n builtin/checkout.c             | 19 +++++++++++--------\n t/t2025-checkout-to.sh         |  7 +++++++\n 3 files changed, 24 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex 0c13825..71d9e4e 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -232,6 +232,12 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.\n \tspecific files such as HEAD, index... See \"MULTIPLE WORKING\n \tTREES\" section for more information.\n \n+--ignore-other-worktrees::\n+\t`git checkout` refuses when the wanted ref is already checked out\n+\tby another worktree. This option makes it check the\n+\tref out anyway. In other words, the ref is held by more than one\n+\tworktree.\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/builtin/checkout.c b/builtin/checkout.c\nindex 953b763..8b2bf20 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -37,6 +37,7 @@ struct checkout_opts {\n \tint writeout_stage;\n \tint overwrite_ignore;\n \tint ignore_skipworktree;\n+\tint ignore_other_worktrees;\n \n \tconst char *new_branch;\n \tconst char *new_branch_force;\n@@ -1079,11 +1080,12 @@ static void check_linked_checkouts(struct branch_info *new)\n static int parse_branchname_arg(int argc, const char **argv,\n \t\t\t\tint dwim_new_local_branch_ok,\n \t\t\t\tstruct branch_info *new,\n-\t\t\t\tstruct tree **source_tree,\n-\t\t\t\tunsigned char rev[20],\n-\t\t\t\tconst char **new_branch,\n-\t\t\t\tint force_detach)\n+\t\t\t\tstruct checkout_opts *opts,\n+\t\t\t\tunsigned char rev[20])\n {\n+\tstruct tree **source_tree = &opts->source_tree;\n+\tconst char **new_branch = &opts->new_branch;\n+\tint force_detach = opts->force_detach;\n \tint argcount = 0;\n \tunsigned char branch_rev[20];\n \tconst char *arg;\n@@ -1209,7 +1211,8 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\tint flag;\n \t\tchar *head_ref = resolve_refdup(\"HEAD\", 0, sha1, &flag);\n \t\tif (head_ref &&\n-\t\t    (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))\n+\t\t    (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)) &&\n+\t\t    !opts->ignore_other_worktrees)\n \t\t\tcheck_linked_checkouts(new);\n \t\tfree(head_ref);\n \t}\n@@ -1340,6 +1343,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\t\t\tN_(\"second guess 'git checkout no-such-branch'\")),\n \t\tOPT_FILENAME(0, \"to\", &opts.new_worktree,\n \t\t\t   N_(\"check a branch out in a separate working directory\")),\n+\t\tOPT_BOOL(0, \"ignore-other-worktrees\", &opts.ignore_other_worktrees,\n+\t\t\t N_(\"do not check if another worktree is holding the given ref\")),\n \t\tOPT_END(),\n \t};\n \n@@ -1420,9 +1425,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\t\topts.track == BRANCH_TRACK_UNSPECIFIED &&\n \t\t\t!opts.new_branch;\n \t\tint n = parse_branchname_arg(argc, argv, dwim_ok,\n-\t\t\t\t\t     &new, &opts.source_tree,\n-\t\t\t\t\t     rev, &opts.new_branch,\n-\t\t\t\t\t     opts.force_detach);\n+\t\t\t\t\t     &new, &opts, rev);\n \t\targv += n;\n \t\targc -= n;\n \t}\ndiff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh\nindex 915b506..f8e4df4 100755\n--- a/t/t2025-checkout-to.sh\n+++ b/t/t2025-checkout-to.sh\n@@ -79,6 +79,13 @@ test_expect_success 'die the same branch is already checked out' '\n \t)\n '\n \n+test_expect_success 'not die the same branch is already checked out' '\n+\t(\n+\t\tcd here &&\n+\t\tgit checkout --ignore-other-worktrees --to anothernewmaster newmaster\n+\t)\n+'\n+\n test_expect_success 'not die on re-checking out current branch' '\n \t(\n \t\tcd there &&\n-- \n2.2.0.60.gb7b3c64\n\n-- 8< --\n"},{"id":"252792","messageId":"xmqq1tojqnfg.fsf@gitster.dls.corp.google.com","threadId":"38074","inReplyTo":"20141201103818.GA20429@lanh","subject":"Re: [PATCH 24/34] checkout: reject if the branch is already checked out elsewhere","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-12-01T17:39:47Z","receivedAt":"2014-12-01T17:39:47Z","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> On Sun, Nov 30, 2014 at 12:18:40PM -0500, Mark Levedahl wrote:\n>> On 11/30/2014 03:24 AM, Nguyễn Thái Ngọc Duy wrote:\n>> > One branch obviously can't be checked out at two places (but detached\n>> > heads are ok). Give the user a choice in this case: --detach, -b\n>> > new-branch, switch branch in the other checkout first or simply 'cd'\n>> > and continue to work there.\n>> >\n>> \n>> This seems too restrictive and is not \"obvious\" to me: I currently use \n>> git-new-workdir to have multiple checkouts of the same branch, with no \n>> ill effect. While those who do not understand what is going on \n>> underneath might be confused by one checkout suddenly showing \n>> uncommitted diffs, I don't accept that as a reason to outright prevent \n>> such use. I suggest, at the very least, that this behavior be overridden \n>> by a --force flag?\n>\n> Prevention is a strong word. It's more about safety for the mere\n> mortals. It's certainly possible to do something like this patch\n> (--force can't be reused, it already carries some other meaning).\n> Should I add this one in the next (hic) reroll?\n\nSorry, what is a hic?\n\nIf this were an existing feature like git-new-workdir, even though\nit is from contrib, making it impossible to do something that used\nto be possible, even if that something is what mere mortals would\nnever want to to to avoid risking confusion, would be a regression\nthat needs an escape hatch.\n\nBut this is a new feature.  I am not sure why you need to make this\noverridable in the first place.  Those who want to have multiple\ncheckouts of the same commit can just detach HEAD at the same commit\nin multiple working trees, as the first thing they need to do would\nbe to run \"git reset --hard $branch\" to synchronize the HEAD and the\nworking tree state to work in the other out-of-sync repositories\neither case anyway.\n"},{"id":"252829","messageId":"547D487B.4040502@gmail.com","threadId":"38074","inReplyTo":"xmqq1tojqnfg.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH 24/34] checkout: reject if the branch is already checked out elsewhere","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2014-12-02T05:04:59Z","receivedAt":"2014-12-02T05:04:59Z","isPatch":true,"sender":{"key":"mdl123@verizon.net","avatar":"https://avatars.githubusercontent.com/u/5302462?v=4"},"body":"On 12/01/2014 12:39 PM, Junio C Hamano wrote:\n> Sorry, what is a hic? If this were an existing feature like \n> git-new-workdir, even though it is from contrib, making it impossible \n> to do something that used to be possible, even if that something is \n> what mere mortals would never want to to to avoid risking confusion, \n> would be a regression that needs an escape hatch. But this is a new \n> feature. I am not sure why you need to make this overridable in the \n> first place. Those who want to have multiple checkouts of the same \n> commit can just detach HEAD at the same commit in multiple working \n> trees, as the first thing they need to do would be to run \"git reset \n> --hard $branch\" to synchronize the HEAD and the working tree state to \n> work in the other out-of-sync repositories either case anyway. \n\nYes, detached HEADS allow multiple checkouts, but now the user needs \nanother system to record what $branch was for each checked out tree or \nneeds to resort to forensics using various git-branch / git-log \ninvocations to find the most-likely value. So, I do not find detached \nHEADS useful in general, and specifically not for this case. Duy's \nlatest addition ('--ignore-other-worktrees') would, so far as I see, \nallow this feature to replace git-new-workdir in my uses, but without \nthe addition it cannot.\n\nMark\n"},{"id":"252851","messageId":"CACsJy8C9pVZ+y79WL-SqfXDMrLFmXE7Dze-jcstTAbE3khMPmg@mail.gmail.com","threadId":"38074","inReplyTo":"xmqq1tojqnfg.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH 24/34] checkout: reject if the branch is already checked out elsewhere","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-12-02T11:50:59Z","receivedAt":"2014-12-02T11:50:59Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Tue, Dec 2, 2014 at 12:39 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Sorry, what is a hic?\n\nOff topic. It's the sound (in Vietnamese) when you inhale through your\nnose, e.g. like when you cry.. I know there's an equivalent in\nEnglish, just can't remember it now.\n-- \nDuy\n"},{"id":"252852","messageId":"CACsJy8DjbgNpbf9Z-OVCpv+YNmvsuHfcPW_Jfm3_gbq2VgYgkQ@mail.gmail.com","threadId":"38074","inReplyTo":"547D487B.4040502@gmail.com","subject":"Re: [PATCH 24/34] checkout: reject if the branch is already checked out elsewhere","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-12-02T12:01:47Z","receivedAt":"2014-12-02T12:01:47Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Tue, Dec 2, 2014 at 12:04 PM, Mark Levedahl <mlevedahl@gmail.com> wrote:\n> On 12/01/2014 12:39 PM, Junio C Hamano wrote:\n>>\n>> Sorry, what is a hic? If this were an existing feature like\n>> git-new-workdir, even though it is from contrib, making it impossible to do\n>> something that used to be possible, even if that something is what mere\n>> mortals would never want to to to avoid risking confusion, would be a\n>> regression that needs an escape hatch. But this is a new feature. I am not\n>> sure why you need to make this overridable in the first place. Those who\n>> want to have multiple checkouts of the same commit can just detach HEAD at\n>> the same commit in multiple working trees, as the first thing they need to\n>> do would be to run \"git reset --hard $branch\" to synchronize the HEAD and\n>> the working tree state to work in the other out-of-sync repositories either\n>> case anyway.\n>\n>\n> Yes, detached HEADS allow multiple checkouts, but now the user needs another\n> system to record what $branch was for each checked out tree or needs to\n> resort to forensics using various git-branch / git-log invocations to find\n> the most-likely value. So, I do not find detached HEADS useful in general,\n> and specifically not for this case. Duy's latest addition\n> ('--ignore-other-worktrees') would, so far as I see, allow this feature to\n> replace git-new-workdir in my uses, but without the addition it cannot.\n\nI'm ok either way. So I'll let you and Junio (and maybe others) sort\nthis out. No objection means --ignore-other-worktrees is in.\n\nFWIW git-branch usually can show the original branch of detached head\n(must not always). I don't think we have a plumbing equivalent for it\nthough. People can \"tail -1 $GIT_DIR/logs/HEAD| sed ..\" but that seems\nhacky. I do like \"read-only\" ref concept where we can keep ref name\n(especially tags) in HEAD until the next commit. But it didn't go\nanywhere\n-- \nDuy\n"},{"id":"252879","messageId":"xmqq4mtem029.fsf@gitster.dls.corp.google.com","threadId":"38074","inReplyTo":"CACsJy8DjbgNpbf9Z-OVCpv+YNmvsuHfcPW_Jfm3_gbq2VgYgkQ@mail.gmail.com","subject":"Re: [PATCH 24/34] checkout: reject if the branch is already checked out elsewhere","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-12-02T17:30:22Z","receivedAt":"2014-12-02T17:30:22Z","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> FWIW git-branch usually can show the original branch of detached head\n> (must not always). I don't think we have a plumbing equivalent for it\n> though. People can \"tail -1 $GIT_DIR/logs/HEAD| sed ..\" but that seems\n> hacky.\n\n@{-1}, i.e. \"the last branch I checked out\"?\n\n> I do like \"read-only\" ref concept where we can keep ref name\n> (especially tags) in HEAD until the next commit. But it didn't go\n> anywhere\n\nRemind me.  That sounds somewhat interesting.\n"},{"id":"252941","messageId":"547EF46F.3000007@gmail.com","threadId":"38074","inReplyTo":"xmqq4mtem029.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH 24/34] checkout: reject if the branch is already checked out elsewhere","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2014-12-03T11:30:55Z","receivedAt":"2014-12-03T11:30:55Z","isPatch":true,"sender":{"key":"mdl123@verizon.net","avatar":"https://avatars.githubusercontent.com/u/5302462?v=4"},"body":"On 12/02/2014 12:30 PM, Junio C Hamano wrote:\n> Duy Nguyen <pclouds@gmail.com> writes:\n>\n>> FWIW git-branch usually can show the original branch of detached head\n>> (must not always). I don't think we have a plumbing equivalent for it\n>> though. People can \"tail -1 $GIT_DIR/logs/HEAD| sed ..\" but that seems\n>> hacky.\n> @{-1}, i.e. \"the last branch I checked out\"?\n>\n>> I do like \"read-only\" ref concept where we can keep ref name\n>> (especially tags) in HEAD until the next commit. But it didn't go\n>> anywhere\n> Remind me.  That sounds somewhat interesting.\n>\n>\nI think these ideas support solutions more complicated than the \n\"problem\" trying to be solved. Consider a use case: multiple algorithms, \neach a different branch in one repository, any of which can be used to \nanalyze the same kinds of data. We also have multiple data sets, each a \nseparate branch in a other repository. For development / test / analysis \nit is necessary to have multiple checked out pairs (algorithm + data) on \nthe same machine to allow comparison / debugging in place of different \ncombinations. Assume one algorithm, multiple data sets are checked out \nand being worked on.\n\nWith new-workdir, or Duy's approach with --ignore-other-checkouts, all \nare checked out normally in git, git-branch, git-status, git log all \nwork normally. If a change needs to be made that affects the branch in \nmore than one checked out repository, once done the other copies are out \nof date. It does not matter which instance is modified, once committed \nthe change is already visible in all others, and \"git reset --hard\" in \nall the others completes the process. This is not difficult to \nunderstand, requires no new code, no special methods.\n\nConsider the alternatives:\na) Use separate complete repositories + work trees: now the new branch \nneeds to be broadcast to all using fetch or pull, and as the change \nmight have been an amend, fetch + reset --hard may be required. This is \nnot \"simpler\" to implement in practice, nor do I find it easier to \nexplain. Note also that if using push, it is possible to force push into \nthe current branch of the other copies, with receive.denyCurrentBranch = \nfalse, resulting in exactly the same situation as above using \nnew-workdir (checked out code not matching the ref).\n\nb) Use Duy's approach without --ignore-other-checkouts. First, you have \nto find the copy that is not on a detached HEAD, detach the HEAD their, \nthen go back to the copy where the problem is found, attach the HEAD in \nthat one, and make the change. Then go back and do git reset --hard \n$branch in the others.\n\nI just don't see how these alternatives are in the end any simpler to \nuse or explain.\n\n\nMark\n"},{"id":"252942","messageId":"CACsJy8DLd4biEHo+FWYLqc4HKezHfv1ymWSQ8kLHLTQv=YKc3A@mail.gmail.com","threadId":"38074","inReplyTo":"xmqq4mtem029.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH 24/34] checkout: reject if the branch is already checked out elsewhere","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-12-03T12:50:31Z","receivedAt":"2014-12-03T12:50:31Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Dec 3, 2014 at 12:30 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> I do like \"read-only\" ref concept where we can keep ref name\n>> (especially tags) in HEAD until the next commit. But it didn't go\n>> anywhere\n>\n> Remind me.  That sounds somewhat interesting.\n\nCouldn't find anything in my mail archive. But the idea is simple:\n\n - we delay detaching HEAD until we need to update the associated ref\nat commit/reset time\n - currently refs/tags/* are read-only. we generalize it to allow this\n'read-only' attribute on some refs/heads/* as well. Because we can't\nreally add attributes to refs, config var could be used.\n-- \nDuy\n"},{"id":"252948","messageId":"xmqqegsgloeq.fsf@gitster.dls.corp.google.com","threadId":"38074","inReplyTo":"CACsJy8DLd4biEHo+FWYLqc4HKezHfv1ymWSQ8kLHLTQv=YKc3A@mail.gmail.com","subject":"Re: [PATCH 24/34] checkout: reject if the branch is already checked out elsewhere","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-12-03T15:54:21Z","receivedAt":"2014-12-03T15:54:21Z","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> On Wed, Dec 3, 2014 at 12:30 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>>> I do like \"read-only\" ref concept where we can keep ref name\n>>> (especially tags) in HEAD until the next commit. But it didn't go\n>>> anywhere\n>>\n>> Remind me.  That sounds somewhat interesting.\n>\n> Couldn't find anything in my mail archive. But the idea is simple:\n>\n>  - we delay detaching HEAD until we need to update the associated ref\n> at commit/reset time\n>  - currently refs/tags/* are read-only. we generalize it to allow this\n> 'read-only' attribute on some refs/heads/* as well. Because we can't\n> really add attributes to refs, config var could be used.\n\nIt could be some annotation in HEAD instead, e.g.\n\n    $ cat .git/HEAD\n    ref: refs/heads/frotz\n    options: read-only\n    $ exit\n\nand it may make more sense as this read-only-ness is closely tied to\nthe state of HEAD, i.e. whatever operation that manipulates the\nHEAD, it needs to be very much aware of the read-only-ness, and it\nis much less likely to go out of sync, compared to a solution where\nyou store this bit to anywhere else, e.g. config.\n"}]}