{"thread":{"id":"37083","subject":"[PATCH v6 00/32] Support multiple checkouts","startedAt":"2014-07-09T07:32:45Z","lastAt":"2014-07-23T08:02:03Z","messageCount":83,"participants":["Nguyễn Thái Ngọc Duy","Eric Sunshine","Dennis Kaarsemaker","Max Kirillov","Junio C Hamano","Duy Nguyen","Thomas Rast"],"isPatch":true,"patchVersion":6,"patchTotal":32},"messages":[{"id":"245580","messageId":"1404891197-18067-1-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":null,"subject":"[PATCH v6 00/32] Support multiple checkouts","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:32:45Z","receivedAt":"2014-07-09T07:32:45Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is basically a reroll from what was parked on 'pu' with two\nnew patches at the end: one to not share info/sparse-checkout\nacross worktrees, and one to allow 'checkout --to` from a bare\nrepository.\n\nI cherry-picked two patches from jk/xstrfmt (on next) because they\nresult in non-trivial conflicts. When this series is merged with\njk/xstrfmt, you still get conflicts in environment.c, but you can just\npick up my changes and drop Jeff's.\n\nDennis Kaarsemaker (1):\n  checkout: don't require a work tree when checking out into a new one\n\nJeff King (2):\n  setup_git_env: use git_pathdup instead of xmalloc + sprintf\n  setup_git_env(): introduce git_path_from_env() helper\n\nNguyễn Thái Ngọc Duy (29):\n  path.c: make get_pathname() return strbuf instead of static buffer\n  path.c: make get_pathname() call sites return const char *\n  git_snpath(): retire and replace with strbuf_git_path()\n  path.c: rename vsnpath() to do_git_path()\n  path.c: group git_path(), git_pathdup() and strbuf_git_path() together\n  git_path(): be aware of file relocation in $GIT_DIR\n  *.sh: respect $GIT_INDEX_FILE\n  reflog: avoid constructing .lock path with git_path\n  fast-import: use git_path() for accessing .git dir instead of get_git_dir()\n  commit: use SEQ_DIR instead of hardcoding \"sequencer\"\n  $GIT_COMMON_DIR: a new environment variable\n  git-sh-setup.sh: use rev-parse --git-path to get $GIT_DIR/objects\n  *.sh: avoid hardcoding $GIT_DIR/hooks/...\n  git-stash: avoid hardcoding $GIT_DIR/logs/....\n  setup.c: convert is_git_directory() to use strbuf\n  setup.c: detect $GIT_COMMON_DIR in is_git_directory()\n  setup.c: convert check_repository_format_gently to use strbuf\n  setup.c: detect $GIT_COMMON_DIR check_repository_format_gently()\n  setup.c: support multi-checkout repo setup\n  wrapper.c: wrapper to open a file, fprintf then close\n  use new wrapper write_file() for simple file writing\n  checkout: support checking out into a new working directory\n  checkout: clean up half-prepared directories in --to mode\n  checkout: detach if the branch is already checked out elsewhere\n  prune: strategies for linked checkouts\n  gc: style change -- no SP before closing bracket\n  gc: support prune --repos\n  count-objects: report unused files in $GIT_DIR/repos/...\n  git_path(): keep \"info/sparse-checkout\" per work-tree\n\n Documentation/config.txt               |   9 ++\n Documentation/git-checkout.txt         |  34 +++++\n Documentation/git-prune.txt            |   3 +\n Documentation/git-rev-parse.txt        |  10 ++\n Documentation/git.txt                  |   9 ++\n Documentation/gitrepository-layout.txt |  75 ++++++++--\n builtin/branch.c                       |   4 +-\n builtin/checkout.c                     | 248 ++++++++++++++++++++++++++++++++-\n builtin/clone.c                        |   9 +-\n builtin/commit.c                       |   2 +-\n builtin/count-objects.c                |   4 +-\n builtin/fetch.c                        |   5 +-\n builtin/fsck.c                         |   4 +-\n builtin/gc.c                           |  21 ++-\n builtin/init-db.c                      |   7 +-\n builtin/prune.c                        |  74 ++++++++++\n builtin/receive-pack.c                 |   2 +-\n builtin/reflog.c                       |   2 +-\n builtin/remote.c                       |   2 +-\n builtin/repack.c                       |   8 +-\n builtin/rev-parse.c                    |  11 ++\n cache.h                                |  17 ++-\n daemon.c                               |  11 +-\n environment.c                          |  45 ++++--\n fast-import.c                          |   7 +-\n git-am.sh                              |  22 +--\n git-pull.sh                            |   2 +-\n git-rebase--interactive.sh             |   6 +-\n git-rebase--merge.sh                   |   6 +-\n git-rebase.sh                          |   4 +-\n git-sh-setup.sh                        |   2 +-\n git-stash.sh                           |   6 +-\n git.c                                  |   2 +-\n notes-merge.c                          |   6 +-\n path.c                                 | 234 +++++++++++++++++++++----------\n refs.c                                 |  86 +++++++-----\n refs.h                                 |   2 +-\n run-command.c                          |   4 +-\n run-command.h                          |   2 +-\n setup.c                                | 124 +++++++++++++----\n sha1_file.c                            |   2 +-\n submodule.c                            |   9 +-\n t/t0060-path-utils.sh                  |  35 +++++\n t/t1501-worktree.sh                    |  76 ++++++++++\n t/t1510-repo-setup.sh                  |   1 +\n t/t2025-checkout-to.sh (new +x)        |  72 ++++++++++\n templates/hooks--applypatch-msg.sample |   4 +-\n templates/hooks--pre-applypatch.sample |   4 +-\n trace.c                                |   1 +\n transport.c                            |   8 +-\n wrapper.c                              |  31 +++++\n 51 files changed, 1109 insertions(+), 265 deletions(-)\n create mode 100755 t/t2025-checkout-to.sh\n\n-- \n1.9.1.346.ga2b5940\n"},{"id":"245581","messageId":"1404891197-18067-2-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 01/32] 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-07-09T07:32:46Z","receivedAt":"2014-07-09T07:32:46Z","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 bc804a3..42ef3af 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@@ -158,41 +154,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-- \n1.9.1.346.ga2b5940\n"},{"id":"245582","messageId":"1404891197-18067-3-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 02/32] path.c: make get_pathname() call sites return const char *","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:32:47Z","receivedAt":"2014-07-09T07:32:47Z","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 f1dc56e..6f74cfb 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -585,7 +585,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 b12989d..0878e73 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -289,16 +289,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 dd46b61..eb3180d 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -573,7 +573,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@@ -807,7 +808,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 8aadca1..d414962 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 c323081..3b64fef 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -599,7 +599,7 @@ static void run_update_post_hook(struct command *commands)\n \tint argc;\n \tconst char **argv;\n \tstruct child_process proc;\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 c9102e8..c9b67ff 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 ff2216a..a64a4a9 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -285,7 +285,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@@ -313,7 +314,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@@ -366,7 +368,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 df65231..822017c 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -682,9 +682,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 6707a66..c74ea15 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -404,7 +404,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 fd5fae2..a9e6b15 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 42ef3af..a3f8826 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@@ -152,7 +152,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 20e2bf1..ce5968a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1441,7 +1441,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@@ -2254,7 +2254,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\t\t\t\t    const unsigned char *old_sha1,\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@@ -2317,7 +2317,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@@ -2755,7 +2755,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 be07d4a..614b8ac 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -758,9 +758,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 ea73de3..890cc95 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -48,7 +48,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 34d527f..52768d6 100644\n--- a/sha1_file.c\n+++ b/sha1_file.c\n@@ -401,7 +401,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-- \n1.9.1.346.ga2b5940\n"},{"id":"245583","messageId":"1404891197-18067-4-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 03/32] git_snpath(): retire and replace with strbuf_git_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:32:48Z","receivedAt":"2014-07-09T07:32:48Z","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             | 78 +++++++++++++++++++++++++++++++++---------------------\n refs.h             |  2 +-\n 5 files changed, 61 insertions(+), 47 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 6f74cfb..7356799 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -584,18 +584,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 822017c..8faf947 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -674,8 +674,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 a3f8826..e545064 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 ce5968a..45ab86d 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1534,10 +1534,12 @@ static const char *handle_missing_loose_ref(const char *refname,\n \n const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)\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@@ -1546,15 +1548,17 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \t\treturn NULL;\n \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 \n \t\tif (--depth < 0)\n-\t\t\treturn NULL;\n+\t\t\tgoto fail;\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@@ -1568,10 +1572,11 @@ 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\treturn handle_missing_loose_ref(refname, sha1,\n-\t\t\t\t\t\t\t\treading, flag);\n+\t\t\t\tret = handle_missing_loose_ref(refname, sha1,\n+\t\t\t\t\t\t\t       reading, flag);\n \t\t\telse\n-\t\t\t\treturn NULL;\n+\t\t\t\tret = NULL;\n+\t\t\tgoto done;\n \t\t}\n \n \t\t/* Follow \"normalized\" - ie \"refs/..\" symlinks by hand */\n@@ -1582,7 +1587,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\treturn NULL;\n+\t\t\t\t\tgoto fail;\n \t\t\t}\n \t\t\tbuffer[len] = 0;\n \t\t\tif (starts_with(buffer, \"refs/\") &&\n@@ -1598,7 +1603,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\treturn NULL;\n+\t\t\tgoto fail;\n \t\t}\n \n \t\t/*\n@@ -1611,12 +1616,13 @@ 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\treturn NULL;\n+\t\t\t\tgoto fail;\n \t\t}\n+\n \t\tlen = read_in_full(fd, buffer, sizeof(buffer)-1);\n \t\tclose(fd);\n \t\tif (len < 0)\n-\t\t\treturn NULL;\n+\t\t\tgoto fail;\n \t\twhile (len && isspace(buffer[len-1]))\n \t\t\tlen--;\n \t\tbuffer[len] = '\\0';\n@@ -1633,9 +1639,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \t\t\t    (buffer[40] != '\\0' && !isspace(buffer[40]))) {\n \t\t\t\tif (flag)\n \t\t\t\t\t*flag |= REF_ISBROKEN;\n-\t\t\t\treturn NULL;\n+\t\t\t\tgoto fail;\n \t\t\t}\n-\t\t\treturn refname;\n+\t\t\tret = refname;\n+\t\t\tgoto done;\n \t\t}\n \t\tif (flag)\n \t\t\t*flag |= REF_ISSYMREF;\n@@ -1645,10 +1652,15 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \t\tif (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {\n \t\t\tif (flag)\n \t\t\t\t*flag |= REF_ISBROKEN;\n-\t\t\treturn NULL;\n+\t\t\tgoto fail;\n \t\t}\n \t\trefname = strcpy(refname_buffer, buf);\n \t}\n+fail:\n+\tret = NULL;\n+done:\n+\tstrbuf_release(&sb_path);\n+\treturn ret;\n }\n \n char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag)\n@@ -2939,41 +2951,41 @@ static int copy_msg(char *buf, const char *msg)\n \treturn cp - buf;\n }\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 \tint logfd, oflags = O_APPEND | O_WRONLY;\n \n-\tgit_snpath(logfile, bufsize, \"logs/%s\", refname);\n+\tstrbuf_git_path(logfile, \"logs/%s\", refname);\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) < 0)\n+\t\tif (safe_create_leading_directories(logfile->buf) < 0)\n \t\t\treturn error(\"unable to create directory for %s\",\n-\t\t\t\t     logfile);\n+\t\t\t\t     logfile->buf);\n \t\toflags |= O_CREAT;\n \t}\n \n-\tlogfd = open(logfile, oflags, 0666);\n+\tlogfd = open(logfile->buf, 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)) {\n+\t\t\tif (remove_empty_directories(logfile->buf)) {\n \t\t\t\treturn error(\"There are still logs under '%s'\",\n-\t\t\t\t\t     logfile);\n+\t\t\t\t\t     logfile->buf);\n \t\t\t}\n-\t\t\tlogfd = open(logfile, oflags, 0666);\n+\t\t\tlogfd = open(logfile->buf, oflags, 0666);\n \t\t}\n \n \t\tif (logfd < 0)\n \t\t\treturn error(\"Unable to append to %s: %s\",\n-\t\t\t\t     logfile, strerror(errno));\n+\t\t\t\t     logfile->buf, strerror(errno));\n \t}\n \n-\tadjust_shared_perm(logfile);\n+\tadjust_shared_perm(logfile->buf);\n \tclose(logfd);\n \treturn 0;\n }\n@@ -2984,20 +2996,22 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,\n \tint logfd, result, written, oflags = O_APPEND | O_WRONLY;\n \tunsigned maxlen, len;\n \tint msglen;\n-\tchar log_file[PATH_MAX];\n+\tstruct strbuf sb_log_file = STRBUF_INIT;\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+\t\tgoto done;\n+\tlog_file = sb_log_file.buf;\n \n \tlogfd = open(log_file, oflags);\n \tif (logfd < 0)\n-\t\treturn 0;\n+\t\tgoto done;\n \tmsglen = msg ? strlen(msg) : 0;\n \tcommitter = git_committer_info(0);\n \tmaxlen = strlen(committer) + msglen + 100;\n@@ -3010,9 +3024,13 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,\n \t\tlen += copy_msg(logrec + len - 1, msg) - 1;\n \twritten = len <= maxlen ? write_in_full(logfd, logrec, len) : -1;\n \tfree(logrec);\n-\tif (close(logfd) != 0 || written != len)\n-\t\treturn error(\"Unable to append to %s\", log_file);\n-\treturn 0;\n+\tif (close(logfd) != 0 || written != len) {\n+\t\terror(\"Unable to append to %s\", log_file);\n+\t\tresult = -1;\n+\t}\n+done:\n+\tstrbuf_release(&sb_log_file);\n+\treturn result;\n }\n \n static int is_branch(const char *refname)\ndiff --git a/refs.h b/refs.h\nindex 4e3050d..99d88a7 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -157,7 +157,7 @@ extern void unlock_ref(struct ref_lock *lock);\n extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);\n \n /** Setup reflog before using. **/\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 long at_time, int cnt,\n-- \n1.9.1.346.ga2b5940\n"},{"id":"245584","messageId":"1404891197-18067-5-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 04/32] path.c: rename vsnpath() to do_git_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:32:49Z","receivedAt":"2014-07-09T07:32:49Z","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 e545064..2cb2e61 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245585","messageId":"1404891197-18067-6-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 05/32] 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-07-09T07:32:50Z","receivedAt":"2014-07-09T07:32: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 path.c | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/path.c b/path.c\nindex 2cb2e61..65881aa 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245586","messageId":"1404891197-18067-7-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 06/32] setup_git_env: use git_pathdup instead of xmalloc + sprintf","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:32:51Z","receivedAt":"2014-07-09T07:32:51Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"From: Jeff King <peff@peff.net>\n\nThis is shorter, harder to get wrong, and more clearly\ncaptures the intent.\n\nSigned-off-by: Jeff King <peff@peff.net>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n environment.c | 12 ++++--------\n 1 file changed, 4 insertions(+), 8 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex 4dac5e9..4de7b81 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -135,15 +135,11 @@ static void setup_git_env(void)\n \tgitfile = read_gitfile(git_dir);\n \tgit_dir = xstrdup(gitfile ? gitfile : git_dir);\n \tgit_object_dir = getenv(DB_ENVIRONMENT);\n-\tif (!git_object_dir) {\n-\t\tgit_object_dir = xmalloc(strlen(git_dir) + 9);\n-\t\tsprintf(git_object_dir, \"%s/objects\", git_dir);\n-\t}\n+\tif (!git_object_dir)\n+\t\tgit_object_dir = git_pathdup(\"objects\");\n \tgit_index_file = getenv(INDEX_ENVIRONMENT);\n-\tif (!git_index_file) {\n-\t\tgit_index_file = xmalloc(strlen(git_dir) + 7);\n-\t\tsprintf(git_index_file, \"%s/index\", git_dir);\n-\t}\n+\tif (!git_index_file)\n+\t\tgit_index_file = git_pathdup(\"index\");\n \tgit_graft_file = getenv(GRAFT_ENVIRONMENT);\n \tif (!git_graft_file)\n \t\tgit_graft_file = git_pathdup(\"info/grafts\");\n-- \n1.9.1.346.ga2b5940\n"},{"id":"245587","messageId":"1404891197-18067-8-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 07/32] setup_git_env(): introduce git_path_from_env() helper","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:32:52Z","receivedAt":"2014-07-09T07:32:52Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"From: Jeff King <peff@peff.net>\n\n\"Check the value of an environment and fall back to a known path\ninside $GIT_DIR\" is repeated a few times to determine the location\nof the data store, the index and the graft file, but the return\nvalue of getenv is not guaranteed to survive across further\ninvocations of setenv or even getenv.\n\nMake sure to xstrdup() the value we receive from getenv(3), and\nencapsulate the pattern into a helper function.\n\nSigned-off-by: Jeff King <peff@peff.net>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n environment.c | 18 +++++++++---------\n 1 file changed, 9 insertions(+), 9 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex 4de7b81..565f652 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -124,6 +124,12 @@ 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+{\n+\tconst char *value = getenv(envvar);\n+\treturn value ? xstrdup(value) : git_pathdup(\"%s\", path);\n+}\n+\n static void setup_git_env(void)\n {\n \tconst char *gitfile;\n@@ -134,15 +140,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 = getenv(DB_ENVIRONMENT);\n-\tif (!git_object_dir)\n-\t\tgit_object_dir = git_pathdup(\"objects\");\n-\tgit_index_file = getenv(INDEX_ENVIRONMENT);\n-\tif (!git_index_file)\n-\t\tgit_index_file = git_pathdup(\"index\");\n-\tgit_graft_file = getenv(GRAFT_ENVIRONMENT);\n-\tif (!git_graft_file)\n-\t\tgit_graft_file = git_pathdup(\"info/grafts\");\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 \tif (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))\n \t\tcheck_replace_refs = 0;\n \tnamespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));\n-- \n1.9.1.346.ga2b5940\n"},{"id":"245589","messageId":"1404891197-18067-9-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 08/32] git_path(): be aware of file relocation in $GIT_DIR","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:32:53Z","receivedAt":"2014-07-09T07:32:53Z","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 987395d..9465399 100644\n--- a/Documentation/git-rev-parse.txt\n+++ b/Documentation/git-rev-parse.txt\n@@ -232,6 +232,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 1a6122d..7606d43 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -529,6 +529,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 8faf947..961f93d 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -612,6 +612,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..06bc8cc 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 65881aa..3deb80c 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245588","messageId":"1404891197-18067-10-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 09/32] *.sh: respect $GIT_INDEX_FILE","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:32:54Z","receivedAt":"2014-07-09T07:32:54Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n 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 18a394f..6ab0c31 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 bcc757b..393e1ec 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245590","messageId":"1404891197-18067-11-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 10/32] reflog: avoid constructing .lock path with git_path","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:32:55Z","receivedAt":"2014-07-09T07:32:55Z","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 e8a8fb1..9bd874d 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245591","messageId":"1404891197-18067-12-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 11/32] 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-07-09T07:32:56Z","receivedAt":"2014-07-09T07:32:56Z","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 c74ea15..e8ec34d 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -3123,12 +3123,9 @@ static void parse_progress(void)\n \n static char* make_fast_import_path(const char *path)\n {\n-\tstruct strbuf abs_path = STRBUF_INIT;\n-\n \tif (!relative_marks_paths || is_absolute_path(path))\n \t\treturn xstrdup(path);\n-\tstrbuf_addf(&abs_path, \"%s/info/fast-import/%s\", get_git_dir(), path);\n-\treturn strbuf_detach(&abs_path, NULL);\n+\treturn xstrdup(git_path(\"info/fast-import/%s\", path));\n }\n \n static void option_import_marks(const char *marks,\n-- \n1.9.1.346.ga2b5940\n"},{"id":"245592","messageId":"1404891197-18067-13-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 12/32] commit: use SEQ_DIR instead of hardcoding \"sequencer\"","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:32:57Z","receivedAt":"2014-07-09T07:32: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 builtin/commit.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 84cec9a..9f2aba3 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -156,7 +156,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-- \n1.9.1.346.ga2b5940\n"},{"id":"245593","messageId":"1404891197-18067-14-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 13/32] $GIT_COMMON_DIR: a new environment variable","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:32:58Z","receivedAt":"2014-07-09T07:32:58Z","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\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git.txt                  |  8 +++++++\n Documentation/gitrepository-layout.txt | 42 ++++++++++++++++++++++++++--------\n cache.h                                |  4 +++-\n environment.c                          | 28 +++++++++++++++++------\n path.c                                 | 34 +++++++++++++++++++++++++++\n t/t0060-path-utils.sh                  | 15 ++++++++++++\n 6 files changed, 114 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex 7924209..749052f 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -788,6 +788,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 17d2ea6..7629e38 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+\t$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@@ -157,7 +172,8 @@ index::\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@@ -197,12 +213,16 @@ 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+\tfor more information. This directory is ignored\n+\t$GIT_COMMON_DIR is set and \"$GIT_COMMON_DIR/logs\" will be used\n+\tinstead.\n \n logs/refs/heads/`name`::\n \tRecords all changes made to the branch tip named `name`.\n@@ -213,10 +233,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 961f93d..8052ddc 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -362,6 +362,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@@ -415,6 +416,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@@ -612,7 +614,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 06bc8cc..76eb38c 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@@ -125,8 +125,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 +149,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_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 +183,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 3deb80c..8a6586c 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..2dabcef 100755\n--- a/t/t0060-path-utils.sh\n+++ b/t/t0060-path-utils.sh\n@@ -262,5 +262,20 @@ 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 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245594","messageId":"1404891197-18067-15-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 14/32] 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-07-09T07:32:59Z","receivedAt":"2014-07-09T07:32:59Z","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 9447980..d3dbb2f 100644\n--- a/git-sh-setup.sh\n+++ b/git-sh-setup.sh\n@@ -345,7 +345,7 @@ then\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 fi\n \n peel_committish () {\n-- \n1.9.1.346.ga2b5940\n"},{"id":"245595","messageId":"1404891197-18067-16-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 15/32] *.sh: avoid hardcoding $GIT_DIR/hooks/...","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:33:00Z","receivedAt":"2014-07-09T07:33:00Z","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 7e1eda0..e8995f9 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 06c810b..d60e710 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -201,9 +201,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-- \n1.9.1.346.ga2b5940\n"},{"id":"245596","messageId":"1404891197-18067-17-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 16/32] git-stash: avoid hardcoding $GIT_DIR/logs/....","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:33:01Z","receivedAt":"2014-07-09T07:33:01Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n git-stash.sh | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/git-stash.sh b/git-stash.sh\nindex 393e1ec..41f8f6b 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245597","messageId":"1404891197-18067-18-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 17/32] setup.c: convert is_git_directory() to use strbuf","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:33:02Z","receivedAt":"2014-07-09T07:33:02Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n setup.c | 37 +++++++++++++++++++++----------------\n 1 file changed, 21 insertions(+), 16 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 0a22f8b..425fd79 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245598","messageId":"1404891197-18067-19-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 18/32] setup.c: detect $GIT_COMMON_DIR in is_git_directory()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:33:03Z","receivedAt":"2014-07-09T07:33:03Z","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 7629e38..0f341fc 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -237,6 +237,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 425fd79..176d505 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245599","messageId":"1404891197-18067-20-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 19/32] setup.c: convert check_repository_format_gently to use strbuf","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:33:04Z","receivedAt":"2014-07-09T07:33:04Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n setup.c | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 176d505..a17389f 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245600","messageId":"1404891197-18067-21-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 20/32] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:33:05Z","receivedAt":"2014-07-09T07:33:05Z","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 a17389f..79f79f2 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245601","messageId":"1404891197-18067-22-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 21/32] setup.c: support multi-checkout repo setup","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:33:06Z","receivedAt":"2014-07-09T07:33:06Z","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\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 1d718bd..286e539 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -380,6 +380,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 9465399..f1867d3 100644\n--- a/Documentation/git-rev-parse.txt\n+++ b/Documentation/git-rev-parse.txt\n@@ -215,6 +215,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 7606d43..29475c5 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -757,6 +757,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n \t\t\t\tprintf(\"%s%s.git\\n\", cwd, len && cwd[len-1] != '/' ? \"/\" : \"\");\n \t\t\t\tcontinue;\n \t\t\t}\n+\t\t\tif (!strcmp(arg, \"--git-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 8052ddc..e7dd5ce 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -422,6 +422,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 76eb38c..8865765 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -141,6 +141,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@@ -149,12 +150,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 79f79f2..8f90bc3 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@@ -831,11 +847,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..e6ac7a4 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 \"$TRASH_DIRECTORY/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 08180a9..a594761 100644\n--- a/trace.c\n+++ b/trace.c\n@@ -173,6 +173,7 @@ void trace_repo_setup(const char *prefix)\n \t\tprefix = \"(null)\";\n \n \ttrace_printf_key(key, \"setup: git_dir: %s\\n\", quote_crnl(get_git_dir()));\n+\ttrace_printf_key(key, \"setup: git_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-- \n1.9.1.346.ga2b5940\n"},{"id":"245602","messageId":"1404891197-18067-23-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 22/32] wrapper.c: wrapper to open a file, fprintf then close","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:33:07Z","receivedAt":"2014-07-09T07:33:07Z","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 e7dd5ce..b363c00 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1355,6 +1355,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 bc1bfb8..9d7b9ac 100644\n--- a/wrapper.c\n+++ b/wrapper.c\n@@ -493,3 +493,34 @@ struct passwd *xgetpwuid_self(void)\n \t\t    errno ? strerror(errno) : _(\"no such user\"));\n \treturn pw;\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-- \n1.9.1.346.ga2b5940\n"},{"id":"245603","messageId":"1404891197-18067-24-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 23/32] use new wrapper write_file() for simple file writing","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:33:08Z","receivedAt":"2014-07-09T07:33:08Z","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 652b1d2..2d1c57c 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -754,7 +754,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@@ -767,8 +766,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 56f85e2..ce8416a 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 f9c63e9..40d0297 100644\n--- a/daemon.c\n+++ b/daemon.c\n@@ -1091,15 +1091,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@@ -1309,7 +1300,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 b80ecac..b7b6059 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1112,16 +1112,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 325f03e..172b3d8 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -294,7 +294,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@@ -303,10 +302,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-- \n1.9.1.346.ga2b5940\n"},{"id":"245604","messageId":"1404891197-18067-25-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 24/32] checkout: support checking out into a new working directory","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:33:09Z","receivedAt":"2014-07-09T07:33:09Z","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/repos/<id>. It then executes \"git checkout\" again\non the new worktree with the same arguments except \"--to\" is taken\nout. The second checkout execution, which is not contaminated with any\ninfo from the current repository, will actually check out and\neverything that normal \"git checkout\" does.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-checkout.txt         | 34 +++++++++++++\n Documentation/git.txt                  |  3 +-\n Documentation/gitrepository-layout.txt |  7 +++\n builtin/checkout.c                     | 93 +++++++++++++++++++++++++++++++++-\n path.c                                 |  2 +-\n t/t2025-checkout-to.sh (new +x)        | 48 ++++++++++++++++++\n 6 files changed, 183 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..fcf73b2 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 new 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 CHECKOUT\n+\tMODE\" 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,33 @@ $ git reflog -2 HEAD # or\n $ git log -g -2 HEAD\n ------------\n \n+MULTIPLE CHECKOUT MODE\n+-------------------------------\n+Normally a working directory is attached to repository. When \"git\n+checkout --to\" is used, a new working directory is attached to the\n+current repository. This new working directory is called \"linked\n+checkout\" as compared to the \"main checkout\" prepared by \"git init\" or\n+\"git clone\". A repository has one main checkout and zero or more\n+linked checkouts.\n+\n+All checkouts share the same repository. Linked checkouts see the\n+repository a bit different from the main checkout. When the checkout\n+\"new\" reads the path $GIT_DIR/HEAD for example, the actual path\n+returned could be $GIT_DIR/repos/new/HEAD. This ensures checkouts\n+won't step on each other.\n+\n+Each linked checkout has a private space in $GIT_DIR/repos, usually\n+named after the base name of the working directory with a number added\n+to make it unique. The linked checkout's $GIT_DIR points to this\n+private space while $GIT_COMMON_DIR points to the main checkout's\n+$GIT_DIR. These settings are done by \"git checkout --to\".\n+\n+Because in this mode $GIT_DIR becomes a lightweight virtual file\n+system where a path could be rewritten to some place else, accessing\n+$GIT_DIR from scripts should use `git rev-parse --git-path` to resolve\n+a path instead of using it directly unless the path is known to be\n+private to the working directory.\n+\n EXAMPLES\n --------\n \ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex 749052f..c0a4940 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -792,7 +792,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 0f341fc..543d874 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -249,6 +249,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+repos::\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\n+\t$GIT_COMMON_DIR is set and \"$GIT_COMMON_DIR/repos\" 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 7356799..7df586a 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@@ -250,6 +254,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@@ -485,7 +492,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@@ -796,7 +803,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@@ -806,6 +814,74 @@ 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+\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(\"repos/%.*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/repos/%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@@ -1067,6 +1143,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@@ -1109,6 +1188,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\t\t N_(\"do not limit pathspecs to sparse entries only\")),\n \t\tOPT_HIDDEN_BOOL(0, \"guess\", &dwim_new_local_branch,\n \t\t\t\tN_(\"second guess 'git checkout no-such-branch'\")),\n+\t\tOPT_STRING(0, \"to\", &opts.new_worktree, N_(\"path\"),\n+\t\t\t   N_(\"check a branch out in a separate working directory\")),\n \t\tOPT_END(),\n \t};\n \n@@ -1117,6 +1198,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@@ -1125,6 +1209,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 8a6586c..e41d6b3 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\", \"/repos\", \"/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..5ec49e2\n--- /dev/null\n+++ b/t/t2025-checkout-to.sh\n@@ -0,0 +1,48 @@\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 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 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245605","messageId":"1404891197-18067-26-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 25/32] checkout: clean up half-prepared directories in --to mode","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:33:10Z","receivedAt":"2014-07-09T07:33:10Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/checkout.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 47 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 7df586a..5a52da4 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@@ -814,6 +815,35 @@ static int switch_branches(const struct checkout_opts *opts,\n \treturn ret || writeout_error;\n }\n \n+static const char *junk_work_tree;\n+static const char *junk_git_dir;\n+static int is_junk;\n+static pid_t junk_pid;\n+\n+static void remove_junk(void)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tif (!is_junk || getpid() != junk_pid)\n+\t\treturn;\n+\tif (junk_git_dir) {\n+\t\tstrbuf_addstr(&sb, junk_git_dir);\n+\t\tremove_dir_recursively(&sb, 0);\n+\t\tstrbuf_reset(&sb);\n+\t}\n+\tif (junk_work_tree) {\n+\t\tstrbuf_addstr(&sb, junk_work_tree);\n+\t\tremove_dir_recursively(&sb, 0);\n+\t}\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@@ -822,7 +852,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@@ -848,13 +878,21 @@ 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 = sb_repo.buf;\n+\tis_junk = 1;\n \n \tstrbuf_addf(&sb_git, \"%s/.git\", path);\n \tif (safe_create_leading_directories_const(sb_git.buf))\n \t\tdie_errno(_(\"could not create leading directories of '%s'\"),\n \t\t\t  sb_git.buf);\n+\tjunk_work_tree = path;\n \n \twrite_file(sb_git.buf, 1, \"gitdir: %s/repos/%s\\n\",\n \t\t   real_path(get_git_common_dir()), name);\n@@ -879,7 +917,14 @@ 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+\tif (!ret)\n+\t\tis_junk = 0;\n+\tstrbuf_release(&sb);\n+\tstrbuf_release(&sb_repo);\n+\tstrbuf_release(&sb_git);\n+\treturn ret;\n+\n }\n \n static int git_checkout_config(const char *var, const char *value, void *cb)\n-- \n1.9.1.346.ga2b5940\n"},{"id":"245606","messageId":"1404891197-18067-27-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 26/32] checkout: detach if the branch is already checked out elsewhere","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:33:11Z","receivedAt":"2014-07-09T07:33:11Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The normal rule is anything outside refs/heads/ is detached. This\nincreases strictness of the rule a bit more: if the branch is checked\nout (either in $GIT_COMMON_DIR/HEAD or any $GIT_DIR/repos/.../HEAD)\nthen it's detached as well.\n\nA hint is given so the user knows where to go and do something there\nif they still want to checkout undetached here.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/checkout.c     | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++\n t/t2025-checkout-to.sh | 15 ++++++++--\n 2 files changed, 92 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 5a52da4..069e803 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -432,6 +432,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@@ -640,6 +645,11 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\tif (old->path && advice_detached_head)\n \t\t\t\tdetach_advice(new->name);\n \t\t\tdescribe_detached_head(_(\"HEAD is now at\"), new->commit);\n+\t\t\tif (new->checkout && !*new->checkout)\n+\t\t\t\tfprintf(stderr, _(\"hint: the main checkout is holding this branch\\n\"));\n+\t\t\telse if (new->checkout)\n+\t\t\t\tfprintf(stderr, _(\"hint: the linked checkout %s is holding this branch\\n\"),\n+\t\t\t\t\tnew->checkout);\n \t\t}\n \t} else if (new->path) {\t/* Switch branches. */\n \t\tcreate_symref(\"HEAD\", new->path, msg.buf);\n@@ -982,6 +992,73 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1)\n \treturn NULL;\n }\n \n+static int check_linked_checkout(struct branch_info *new,\n+\t\t\t\t  const char *name, const char *path)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tchar *start, *end;\n+\tif (strbuf_read_file(&sb, path, 0) < 0)\n+\t\treturn 0;\n+\tif (!starts_with(sb.buf, \"ref:\")) {\n+\t\tstrbuf_release(&sb);\n+\t\treturn 0;\n+\t}\n+\n+\tstart = sb.buf + 4;\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) &&\n+\t    new->path[end - start] == '\\0') {\n+\t\tstrbuf_release(&sb);\n+\t\tnew->path = NULL; /* detach */\n+\t\tnew->checkout = xstrdup(name); /* reason */\n+\t\treturn 1;\n+\t}\n+\tstrbuf_release(&sb);\n+\treturn 0;\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/repos\", get_git_common_dir());\n+\tif ((dir = opendir(path.buf)) == NULL) {\n+\t\tstrbuf_release(&path);\n+\t\treturn;\n+\t}\n+\n+\tstrbuf_reset(&path);\n+\tstrbuf_addf(&path, \"%s/HEAD\", get_git_common_dir());\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+\tif (check_linked_checkout(new, \"\", path.buf)) {\n+\t\tstrbuf_release(&path);\n+\t\tclosedir(dir);\n+\t\treturn;\n+\t}\n+\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(&path);\n+\t\tstrbuf_addf(&path, \"%s/repos/%s/HEAD\",\n+\t\t\t    get_git_common_dir(), d->d_name);\n+\t\tif (check_linked_checkout(new, d->d_name, path.buf))\n+\t\t\tbreak;\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@@ -1109,6 +1186,9 @@ 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)\n+\t\tcheck_linked_checkouts(new);\n+\n \tnew->commit = lookup_commit_reference_gently(rev, 1);\n \tif (!new->commit) {\n \t\t/* not a commit */\ndiff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh\nindex 5ec49e2..2d35a9b 100755\n--- a/t/t2025-checkout-to.sh\n+++ b/t/t2025-checkout-to.sh\n@@ -13,13 +13,14 @@ test_expect_success 'checkout --to not updating paths' '\n '\n \n test_expect_success 'checkout --to a new worktree' '\n+\tgit rev-parse HEAD >expect &&\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\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@@ -45,4 +46,12 @@ test_expect_success 'checkout --to a new worktree creating new branch' '\n \t)\n '\n \n+test_expect_success 'detach if the same branch is already checked out' '\n+\t(\n+\t\tcd here &&\n+\t\tgit checkout newmaster &&\n+\t\ttest_must_fail git symbolic-ref HEAD\n+\t)\n+'\n+\n test_done\n-- \n1.9.1.346.ga2b5940\n"},{"id":"245607","messageId":"1404891197-18067-28-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 27/32] prune: strategies for linked checkouts","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:33:12Z","receivedAt":"2014-07-09T07:33:12Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"(alias R=$GIT_COMMON_DIR/repos/<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   repos/<id> is to be pruned.\n\n - If $R/locked exists, repos/<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\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-prune.txt            |  3 ++\n Documentation/gitrepository-layout.txt | 19 +++++++++\n builtin/checkout.c                     | 14 +++++++\n builtin/prune.c                        | 74 ++++++++++++++++++++++++++++++++++\n setup.c                                | 13 ++++++\n 5 files changed, 123 insertions(+)\n\ndiff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt\nindex 7a493c8..50e39ec 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+--repos::\n+\tPrune directories in $GIT_DIR/repos.\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 543d874..bed4f1a 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -256,6 +256,25 @@ repos::\n \t$GIT_COMMON_DIR is set and \"$GIT_COMMON_DIR/repos\" will be\n \tused instead.\n \n+repos/<id>/gitdir::\n+\tA text file containing the absolute path back to the .git file\n+\tthat points to here. This is used to check if the linked\n+\trepository has been manually removed and there is no need to\n+\tkeep this directory any more. mtime of this file should be\n+\tupdated every time the linked repository is accessed.\n+\n+repos/<id>/locked::\n+\tIf this file exists, the linked repository may be on a\n+\tportable device and not available. It does not mean that the\n+\tlinked repository is gone and `repos/<id>` could be\n+\tremoved. The file's content contains a reason string on why\n+\tthe repository is locked.\n+\n+repos/<id>/link::\n+\tIf this file exists, it is a hard link to the linked .git\n+\tfile. It is used to detect if the linked repository is\n+\tmanually removed.\n+\n SEE ALSO\n --------\n linkgit:git-init[1],\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 069e803..98a2f5f 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -898,12 +898,22 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n \tjunk_git_dir = sb_repo.buf;\n \tis_junk = 1;\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 \tjunk_work_tree = path;\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/repos/%s\\n\",\n \t\t   real_path(get_git_common_dir()), name);\n \t/*\n@@ -912,6 +922,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@@ -930,6 +941,9 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n \tret = run_command(&cp);\n \tif (!ret)\n \t\tis_junk = 0;\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);\ndiff --git a/builtin/prune.c b/builtin/prune.c\nindex 144a3bd..6db6bcc 100644\n--- a/builtin/prune.c\n+++ b/builtin/prune.c\n@@ -112,6 +112,70 @@ static void prune_object_dir(const char *path)\n \t}\n }\n \n+static const char *prune_repo_dir(const char *id, struct stat *st)\n+{\n+\tchar *path;\n+\tint fd, len;\n+\tif (file_exists(git_path(\"repos/%s/locked\", id)))\n+\t\treturn NULL;\n+\tif (stat(git_path(\"repos/%s/gitdir\", id), st)) {\n+\t\tst->st_mtime = expire;\n+\t\treturn _(\"gitdir does not exist\");\n+\t}\n+\tfd = open(git_path(\"repos/%s/gitdir\", id), O_RDONLY);\n+\tlen = st->st_size;\n+\tpath = xmalloc(len + 1);\n+\tread_in_full(fd, path, len);\n+\tclose(fd);\n+\twhile (path[len - 1] == '\\n' || path[len - 1] == '\\r')\n+\t\tlen--;\n+\tpath[len] = '\\0';\n+\tif (!file_exists(path)) {\n+\t\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(\"repos/%s/link\", id), &st_link) &&\n+\t\t    st_link.st_nlink > 1)\n+\t\t\treturn NULL;\n+\t\treturn _(\"gitdir points to non-existing file\");\n+\t}\n+\tfree(path);\n+\treturn NULL;\n+}\n+\n+static void prune_repos_dir(void)\n+{\n+\tconst char *reason;\n+\tDIR *dir = opendir(git_path(\"repos\"));\n+\tstruct dirent *d;\n+\tint removed = 0;\n+\tstruct stat st;\n+\tif (!dir)\n+\t\treturn;\n+\twhile ((d = readdir(dir)) != NULL) {\n+\t\tif (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n+\t\t\tcontinue;\n+\t\tif ((reason = prune_repo_dir(d->d_name, &st)) != NULL &&\n+\t\t    st.st_mtime <= expire) {\n+\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\t\tif (show_only || verbose)\n+\t\t\t\tprintf(_(\"Removing repos/%s: %s\\n\"), d->d_name, reason);\n+\t\t\tif (show_only)\n+\t\t\t\tcontinue;\n+\t\t\tstrbuf_addstr(&sb, git_path(\"repos/%s\", d->d_name));\n+\t\t\tremove_dir_recursively(&sb, 0);\n+\t\t\tstrbuf_release(&sb);\n+\t\t\tremoved = 1;\n+\t\t}\n+\t}\n+\tclosedir(dir);\n+\tif (removed)\n+\t\trmdir(git_path(\"repos\"));\n+}\n+\n /*\n  * Write errors (particularly out of space) can result in\n  * failed temporary packs (and more rarely indexes and other\n@@ -138,10 +202,12 @@ int cmd_prune(int argc, const char **argv, const char *prefix)\n {\n \tstruct rev_info revs;\n \tstruct progress *progress = NULL;\n+\tint prune_repos = 0;\n \tconst struct option options[] = {\n \t\tOPT__DRY_RUN(&show_only, N_(\"do not remove, show only\")),\n \t\tOPT__VERBOSE(&verbose, N_(\"report pruned objects\")),\n \t\tOPT_BOOL(0, \"progress\", &show_progress, N_(\"show progress\")),\n+\t\tOPT_BOOL(0, \"repos\", &prune_repos, N_(\"prune .git/repos/\")),\n \t\tOPT_EXPIRY_DATE(0, \"expire\", &expire,\n \t\t\t\tN_(\"expire objects older than <time>\")),\n \t\tOPT_END()\n@@ -154,6 +220,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 (prune_repos) {\n+\t\tif (argc)\n+\t\t\tdie(_(\"--repos does not take extra arguments\"));\n+\t\tprune_repos_dir();\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 8f90bc3..da2d669 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);\n-- \n1.9.1.346.ga2b5940\n"},{"id":"245608","messageId":"1404891197-18067-29-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 28/32] gc: style change -- no SP before closing bracket","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:33:13Z","receivedAt":"2014-07-09T07:33:13Z","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 | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 8d219d8..53f1302 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -285,7 +285,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n \t\tOPT__QUIET(&quiet, N_(\"suppress progress reporting\")),\n \t\t{ OPTION_STRING, 0, \"prune\", &prune_expire, N_(\"date\"),\n \t\t\tN_(\"prune unreferenced objects\"),\n-\t\t\tPARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },\n+\t\t\tPARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire},\n \t\tOPT_BOOL(0, \"aggressive\", &aggressive, N_(\"be more thorough (increased runtime)\")),\n \t\tOPT_BOOL(0, \"auto\", &auto_gc, N_(\"enable auto-gc mode\")),\n \t\tOPT_BOOL(0, \"force\", &force, N_(\"force running gc even if there may be another gc running\")),\n@@ -298,7 +298,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 \tgit_config(gc_config, NULL);\n-- \n1.9.1.346.ga2b5940\n"},{"id":"245609","messageId":"1404891197-18067-30-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 29/32] gc: support prune --repos","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:33:14Z","receivedAt":"2014-07-09T07:33:14Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/config.txt |  7 +++++++\n builtin/gc.c             | 17 +++++++++++++++++\n 2 files changed, 24 insertions(+)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 286e539..470f979 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1211,6 +1211,13 @@ gc.pruneexpire::\n \t\"now\" may be used to disable this  grace period and always prune\n \tunreachable objects immediately.\n \n+gc.prunereposexpire::\n+\tWhen 'git gc' is run, it will call\n+\t'prune --repos --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 always prune\n+\t$GIT_DIR/repos immediately.\n+\n gc.reflogexpire::\n gc.<pattern>.reflogexpire::\n \t'git reflog expire' removes reflog entries older than\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 53f1302..1190183 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_repos_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_repos = ARGV_ARRAY_INIT;\n static struct argv_array rerere = ARGV_ARRAY_INIT;\n \n static char *pidfile;\n@@ -92,6 +94,14 @@ static int gc_config(const char *var, const char *value, void *cb)\n \t\t}\n \t\treturn git_config_string(&prune_expire, var, value);\n \t}\n+\tif (!strcmp(var, \"gc.prunereposexpire\")) {\n+\t\tif (value && strcmp(value, \"now\")) {\n+\t\t\tunsigned long now = approxidate(\"now\");\n+\t\t\tif (approxidate(value) >= now)\n+\t\t\t\treturn error(_(\"Invalid %s: '%s'\"), var, value);\n+\t\t}\n+\t\treturn git_config_string(&prune_repos_expire, var, value);\n+\t}\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -299,6 +309,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_repos, \"prune\", \"--repos\", \"--expire\", NULL);\n \targv_array_pushl(&rerere, \"rerere\", \"gc\", NULL);\n \n \tgit_config(gc_config, NULL);\n@@ -368,6 +379,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_repos_expire) {\n+\t\targv_array_push(&prune_repos, prune_repos_expire);\n+\t\tif (run_command_v_opt(prune_repos.argv, RUN_GIT_CMD))\n+\t\t\treturn error(FAILED_RUN, prune_repos.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-- \n1.9.1.346.ga2b5940\n"},{"id":"245610","messageId":"1404891197-18067-31-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 30/32] count-objects: report unused files in $GIT_DIR/repos/...","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:33:15Z","receivedAt":"2014-07-09T07:33:15Z","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 a7f70cb..d3a1620 100644\n--- a/builtin/count-objects.c\n+++ b/builtin/count-objects.c\n@@ -102,8 +102,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 \tmemcpy(path, objdir, len);\n \tif (len && objdir[len-1] != '/')\n \t\tpath[len++] = '/';\ndiff --git a/cache.h b/cache.h\nindex b363c00..39cf3f0 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -690,6 +690,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 e41d6b3..b5af137 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\", \"/repos\", \"/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-- \n1.9.1.346.ga2b5940\n"},{"id":"245611","messageId":"1404891197-18067-32-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 31/32] git_path(): keep \"info/sparse-checkout\" per work-tree","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-09T07:33:16Z","receivedAt":"2014-07-09T07:33:16Z","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 b5af137..b1dd2bc 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 2dabcef..da82aab 100755\n--- a/t/t0060-path-utils.sh\n+++ b/t/t0060-path-utils.sh\n@@ -269,6 +269,7 @@ 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/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-- \n1.9.1.346.ga2b5940\n"},{"id":"245612","messageId":"1404891197-18067-33-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v6 32/32] 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-07-09T07:33:17Z","receivedAt":"2014-07-09T07:33:17Z","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 98a2f5f..fd89d93 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1353,6 +1353,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 d6b4a55..04d6c88 100644\n--- a/git.c\n+++ b/git.c\n@@ -384,7 +384,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 2d35a9b..a219851 100755\n--- a/t/t2025-checkout-to.sh\n+++ b/t/t2025-checkout-to.sh\n@@ -54,4 +54,19 @@ test_expect_success 'detach if the same branch is already checked out' '\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 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245614","messageId":"CAPig+cSfbs3NuyfbrUrJ5BGP9J7GvGCSda2E7ZjA2k4ZXotNRQ@mail.gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-29-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v6 28/32] gc: style change -- no SP before closing bracket","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-07-09T09:47:27Z","receivedAt":"2014-07-09T09:47:27Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Jul 9, 2014 at 3:33 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  builtin/gc.c | 4 ++--\n>  1 file changed, 2 insertions(+), 2 deletions(-)\n>\n> diff --git a/builtin/gc.c b/builtin/gc.c\n> index 8d219d8..53f1302 100644\n> --- a/builtin/gc.c\n> +++ b/builtin/gc.c\n> @@ -285,7 +285,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n>                 OPT__QUIET(&quiet, N_(\"suppress progress reporting\")),\n>                 { OPTION_STRING, 0, \"prune\", &prune_expire, N_(\"date\"),\n>                         N_(\"prune unreferenced objects\"),\n> -                       PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },\n> +                       PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire},\n\nYet, there is a space after the opening '{'. So, this is now\ninconsistently formatted as:\n\n    { foo, bar}\n\n>                 OPT_BOOL(0, \"aggressive\", &aggressive, N_(\"be more thorough (increased runtime)\")),\n>                 OPT_BOOL(0, \"auto\", &auto_gc, N_(\"enable auto-gc mode\")),\n>                 OPT_BOOL(0, \"force\", &force, N_(\"force running gc even if there may be another gc running\")),\n> @@ -298,7 +298,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n>         argv_array_pushl(&pack_refs_cmd, \"pack-refs\", \"--all\", \"--prune\", NULL);\n>         argv_array_pushl(&reflog, \"reflog\", \"expire\", \"--all\", NULL);\n>         argv_array_pushl(&repack, \"repack\", \"-d\", \"-l\", NULL);\n> -       argv_array_pushl(&prune, \"prune\", \"--expire\", NULL );\n> +       argv_array_pushl(&prune, \"prune\", \"--expire\", NULL);\n>         argv_array_pushl(&rerere, \"rerere\", \"gc\", NULL);\n>\n>         git_config(gc_config, NULL);\n> --\n> 1.9.1.346.ga2b5940\n"},{"id":"245615","messageId":"CAPig+cThYY9ERTrUrMM_JcGoALqcn1RTRGa_0k60cPJXcaLkLQ@mail.gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-30-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v6 29/32] gc: support prune --repos","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-07-09T10:05:02Z","receivedAt":"2014-07-09T10:05:02Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Jul 9, 2014 at 3:33 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  Documentation/config.txt |  7 +++++++\n>  builtin/gc.c             | 17 +++++++++++++++++\n>  2 files changed, 24 insertions(+)\n>\n> diff --git a/Documentation/config.txt b/Documentation/config.txt\n> index 286e539..470f979 100644\n> --- a/Documentation/config.txt\n> +++ b/Documentation/config.txt\n> @@ -1211,6 +1211,13 @@ gc.pruneexpire::\n>         \"now\" may be used to disable this  grace period and always prune\n>         unreachable objects immediately.\n>\n> +gc.prunereposexpire::\n> +       When 'git gc' is run, it will call\n> +       'prune --repos --expire 3.months.ago'.\n> +       Override the grace period with this config variable. The value\n> +       \"now\" may be used to disable the grace period and always prune\n> +       $GIT_DIR/repos immediately.\n\nnit: This reads very slightly better without \"always\":\n\n    ... disable the grace period and prune [...] immediately.\n\nMore below.\n\n> +\n>  gc.reflogexpire::\n>  gc.<pattern>.reflogexpire::\n>         'git reflog expire' removes reflog entries older than\n> diff --git a/builtin/gc.c b/builtin/gc.c\n> index 53f1302..1190183 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_repos_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_repos = ARGV_ARRAY_INIT;\n>  static struct argv_array rerere = ARGV_ARRAY_INIT;\n>\n>  static char *pidfile;\n> @@ -92,6 +94,14 @@ static int gc_config(const char *var, const char *value, void *cb)\n>                 }\n>                 return git_config_string(&prune_expire, var, value);\n>         }\n> +       if (!strcmp(var, \"gc.prunereposexpire\")) {\n> +               if (value && strcmp(value, \"now\")) {\n> +                       unsigned long now = approxidate(\"now\");\n> +                       if (approxidate(value) >= now)\n> +                               return error(_(\"Invalid %s: '%s'\"), var, value);\n> +               }\n> +               return git_config_string(&prune_repos_expire, var, value);\n\nThis logic is copied from the \"gc.pruneexpire\" case immediately above\nit. Would it make sense to factor it out into a helper function (or is\nit not worth the bother for just two cases)?\n\n> +       }\n>         return git_default_config(var, value, cb);\n>  }\n>\n> @@ -299,6 +309,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n>         argv_array_pushl(&reflog, \"reflog\", \"expire\", \"--all\", NULL);\n>         argv_array_pushl(&repack, \"repack\", \"-d\", \"-l\", NULL);\n>         argv_array_pushl(&prune, \"prune\", \"--expire\", NULL);\n> +       argv_array_pushl(&prune_repos, \"prune\", \"--repos\", \"--expire\", NULL);\n>         argv_array_pushl(&rerere, \"rerere\", \"gc\", NULL);\n>\n>         git_config(gc_config, NULL);\n> @@ -368,6 +379,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n>                         return error(FAILED_RUN, prune.argv[0]);\n>         }\n>\n> +       if (prune_repos_expire) {\n> +               argv_array_push(&prune_repos, prune_repos_expire);\n> +               if (run_command_v_opt(prune_repos.argv, RUN_GIT_CMD))\n> +                       return error(FAILED_RUN, prune_repos.argv[0]);\n> +       }\n> +\n>         if (run_command_v_opt(rerere.argv, RUN_GIT_CMD))\n>                 return error(FAILED_RUN, rerere.argv[0]);\n>\n> --\n> 1.9.1.346.ga2b5940\n"},{"id":"245622","messageId":"CAPig+cTcwuwnndOBeNbOqc4oTmyC3GW2+RsAXPDRznUVvLp8Ew@mail.gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-28-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v6 27/32] prune: strategies for linked checkouts","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-07-09T11:24:22Z","receivedAt":"2014-07-09T11:24:22Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Jul 9, 2014 at 3:33 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> (alias R=$GIT_COMMON_DIR/repos/<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>    repos/<id> is to be pruned.\n>\n>  - If $R/locked exists, repos/<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>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/builtin/prune.c b/builtin/prune.c\n> index 144a3bd..6db6bcc 100644\n> --- a/builtin/prune.c\n> +++ b/builtin/prune.c\n> @@ -112,6 +112,70 @@ static void prune_object_dir(const char *path)\n>         }\n>  }\n>\n> +static const char *prune_repo_dir(const char *id, struct stat *st)\n> +{\n> +       char *path;\n> +       int fd, len;\n> +       if (file_exists(git_path(\"repos/%s/locked\", id)))\n> +               return NULL;\n> +       if (stat(git_path(\"repos/%s/gitdir\", id), st)) {\n> +               st->st_mtime = expire;\n> +               return _(\"gitdir does not exist\");\n\nIf a plain file exists in 'repos' for some reason, it will be caught\nby this case. Would it make sense, however, to handle that specially\nand report a more accurate message, such as \"not a repo\" or some such?\n\n> +       }\n> +       fd = open(git_path(\"repos/%s/gitdir\", id), O_RDONLY);\n\nIf 'gitdir' fails to open for some reason (lack of permissions, it's a\ndirectory, etc.), the subsequent read_in_full() will crash.\n\n> +       len = st->st_size;\n> +       path = xmalloc(len + 1);\n> +       read_in_full(fd, path, len);\n> +       close(fd);\n\nstrbuf_readfile() might make this a bit cleaner (though has higher overhead).\n\n> +       while (path[len - 1] == '\\n' || path[len - 1] == '\\r')\n> +               len--;\n\nIf, for some reason, 'gitdir' content is empty or consists only of CR\nand LF, this will access memory outside of the allocated region.\nProbably want:\n\n    while (len > 0 && (... || ...))\n\n> +       path[len] = '\\0';\n> +       if (!file_exists(path)) {\n\nWhat happens if 'path' ends up empty (due to hand-editing of 'gitdir'\nby the user, for instance)? Does this case deserve an appropriate\ndiagnostic (\"corrupt 'gitdir' file\" or something)?\n\n> +               struct stat st_link;\n> +               free(path);\n> +               /*\n> +                * the repo is moved manually and has not been\n> +                * accessed since?\n> +                */\n> +               if (!stat(git_path(\"repos/%s/link\", id), &st_link) &&\n> +                   st_link.st_nlink > 1)\n> +                       return NULL;\n> +               return _(\"gitdir points to non-existing file\");\n\ns/existing/existent/\ns/file/location/\n\n> +       }\n> +       free(path);\n> +       return NULL;\n> +}\n> +\n> +static void prune_repos_dir(void)\n> +{\n> +       const char *reason;\n> +       DIR *dir = opendir(git_path(\"repos\"));\n> +       struct dirent *d;\n> +       int removed = 0;\n> +       struct stat st;\n> +       if (!dir)\n> +               return;\n> +       while ((d = readdir(dir)) != NULL) {\n> +               if (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n> +                       continue;\n> +               if ((reason = prune_repo_dir(d->d_name, &st)) != NULL &&\n> +                   st.st_mtime <= expire) {\n> +                       struct strbuf sb = STRBUF_INIT;\n> +                       if (show_only || verbose)\n> +                               printf(_(\"Removing repos/%s: %s\\n\"), d->d_name, reason);\n> +                       if (show_only)\n> +                               continue;\n> +                       strbuf_addstr(&sb, git_path(\"repos/%s\", d->d_name));\n> +                       remove_dir_recursively(&sb, 0);\n\nWhat happens if this entry in 'repos' is a plain file (or other\nnon-directory)? Based upon my reading of remove_dir_recursively(), it\nwon't be deleted, yet the logic of prune_repo_dir() implies that such\nan entry should be pruned. Perhaps handle this case specially with\nunlink()?\n\n> +                       strbuf_release(&sb);\n> +                       removed = 1;\n> +               }\n> +       }\n> +       closedir(dir);\n> +       if (removed)\n> +               rmdir(git_path(\"repos\"));\n\nThis works, but at first glance it seems strange not to be checking\n'show_only' before calling destructive rmdir().\n\nHowever, stepping back, it's not quite clear what the intent is.\nIgnoring the return value of rmdir() implies that you trust it to Do\nThe Right Thing: succeed when 'repos' is empty and fail when not. This\nassumption of behavior applies regardless of whether or not any\ncontent of 'repos' was removed, so the 'removed' flag does not seem\nbeneficial.\n\nMoreover, the 'removed' flag actively prevents the 'repos' directory\nfrom being pruned in the corner case where the user manually emptied\nthe content of 'repos' before invoking \"git prune\". Therefore, it\nmight be simpler to drop the 'removed' variable altogether and\nrephrase as:\n\n    if (!show_only)\n        rmdir(git_path(\"repos\"));\n\n> +}\n> +\n>  /*\n>   * Write errors (particularly out of space) can result in\n>   * failed temporary packs (and more rarely indexes and other\n> @@ -138,10 +202,12 @@ int cmd_prune(int argc, const char **argv, const char *prefix)\n>  {\n>         struct rev_info revs;\n>         struct progress *progress = NULL;\n> +       int prune_repos = 0;\n>         const struct option options[] = {\n>                 OPT__DRY_RUN(&show_only, N_(\"do not remove, show only\")),\n>                 OPT__VERBOSE(&verbose, N_(\"report pruned objects\")),\n>                 OPT_BOOL(0, \"progress\", &show_progress, N_(\"show progress\")),\n> +               OPT_BOOL(0, \"repos\", &prune_repos, N_(\"prune .git/repos/\")),\n>                 OPT_EXPIRY_DATE(0, \"expire\", &expire,\n>                                 N_(\"expire objects older than <time>\")),\n>                 OPT_END()\n> @@ -154,6 +220,14 @@ int cmd_prune(int argc, const char **argv, const char *prefix)\n>         init_revisions(&revs, prefix);\n>\n>         argc = parse_options(argc, argv, prefix, options, prune_usage, 0);\n> +\n> +       if (prune_repos) {\n> +               if (argc)\n> +                       die(_(\"--repos does not take extra arguments\"));\n> +               prune_repos_dir();\n> +               return 0;\n> +       }\n> +\n>         while (argc--) {\n>                 unsigned char sha1[20];\n>                 const char *name = *argv++;\n> diff --git a/setup.c b/setup.c\n> index 8f90bc3..da2d669 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>         return ret;\n>  }\n>\n> +static void update_linked_gitdir(const char *gitfile, const char *gitdir)\n> +{\n> +       struct strbuf path = STRBUF_INIT;\n> +       struct stat st;\n> +\n> +       strbuf_addf(&path, \"%s/gitfile\", gitdir);\n> +       if (stat(path.buf, &st) || st.st_mtime + 24 * 3600 < time(NULL))\n> +               write_file(path.buf, 0, \"%s\\n\", gitfile);\n> +       strbuf_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>         if (!is_git_directory(dir))\n>                 die(\"Not a git repository: %s\", dir);\n> +\n> +       update_linked_gitdir(path, dir);\n>         path = real_path(dir);\n>\n>         free(buf);\n> --\n> 1.9.1.346.ga2b5940\n"},{"id":"245757","messageId":"1405062810.23880.6.camel@spirit","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v6 00/32] Support multiple checkouts","fromName":"Dennis Kaarsemaker","fromEmail":"dennis@kaarsemaker.net","sentAt":"2014-07-11T07:13:30Z","receivedAt":"2014-07-11T07:13:30Z","isPatch":true,"sender":{"key":"dennis@kaarsemaker.net","avatar":"https://avatars.githubusercontent.com/u/200649?v=4"},"body":"Tested-by: Dennis Kaarsemaker <dennis@kaarsemaker.net>\n\nI've been using this branch for a little while now and have no breakages\nto report. Max Kirillov reported some bugs in the interaction with\nsubmodules which I plan to chase when I have some time unless someone\nbeats me to it :)\n\nOn wo, 2014-07-09 at 14:32 +0700, Nguyễn Thái Ngọc Duy wrote:\n> This is basically a reroll from what was parked on 'pu' with two\n> new patches at the end: one to not share info/sparse-checkout\n> across worktrees, and one to allow 'checkout --to` from a bare\n> repository.\n> \n> I cherry-picked two patches from jk/xstrfmt (on next) because they\n> result in non-trivial conflicts. When this series is merged with\n> jk/xstrfmt, you still get conflicts in environment.c, but you can just\n> pick up my changes and drop Jeff's.\n> \n> Dennis Kaarsemaker (1):\n>   checkout: don't require a work tree when checking out into a new one\n> \n> Jeff King (2):\n>   setup_git_env: use git_pathdup instead of xmalloc + sprintf\n>   setup_git_env(): introduce git_path_from_env() helper\n> \n> Nguyễn Thái Ngọc Duy (29):\n>   path.c: make get_pathname() return strbuf instead of static buffer\n>   path.c: make get_pathname() call sites return const char *\n>   git_snpath(): retire and replace with strbuf_git_path()\n>   path.c: rename vsnpath() to do_git_path()\n>   path.c: group git_path(), git_pathdup() and strbuf_git_path() together\n>   git_path(): be aware of file relocation in $GIT_DIR\n>   *.sh: respect $GIT_INDEX_FILE\n>   reflog: avoid constructing .lock path with git_path\n>   fast-import: use git_path() for accessing .git dir instead of get_git_dir()\n>   commit: use SEQ_DIR instead of hardcoding \"sequencer\"\n>   $GIT_COMMON_DIR: a new environment variable\n>   git-sh-setup.sh: use rev-parse --git-path to get $GIT_DIR/objects\n>   *.sh: avoid hardcoding $GIT_DIR/hooks/...\n>   git-stash: avoid hardcoding $GIT_DIR/logs/....\n>   setup.c: convert is_git_directory() to use strbuf\n>   setup.c: detect $GIT_COMMON_DIR in is_git_directory()\n>   setup.c: convert check_repository_format_gently to use strbuf\n>   setup.c: detect $GIT_COMMON_DIR check_repository_format_gently()\n>   setup.c: support multi-checkout repo setup\n>   wrapper.c: wrapper to open a file, fprintf then close\n>   use new wrapper write_file() for simple file writing\n>   checkout: support checking out into a new working directory\n>   checkout: clean up half-prepared directories in --to mode\n>   checkout: detach if the branch is already checked out elsewhere\n>   prune: strategies for linked checkouts\n>   gc: style change -- no SP before closing bracket\n>   gc: support prune --repos\n>   count-objects: report unused files in $GIT_DIR/repos/...\n>   git_path(): keep \"info/sparse-checkout\" per work-tree\n> \n>  Documentation/config.txt               |   9 ++\n>  Documentation/git-checkout.txt         |  34 +++++\n>  Documentation/git-prune.txt            |   3 +\n>  Documentation/git-rev-parse.txt        |  10 ++\n>  Documentation/git.txt                  |   9 ++\n>  Documentation/gitrepository-layout.txt |  75 ++++++++--\n>  builtin/branch.c                       |   4 +-\n>  builtin/checkout.c                     | 248 ++++++++++++++++++++++++++++++++-\n>  builtin/clone.c                        |   9 +-\n>  builtin/commit.c                       |   2 +-\n>  builtin/count-objects.c                |   4 +-\n>  builtin/fetch.c                        |   5 +-\n>  builtin/fsck.c                         |   4 +-\n>  builtin/gc.c                           |  21 ++-\n>  builtin/init-db.c                      |   7 +-\n>  builtin/prune.c                        |  74 ++++++++++\n>  builtin/receive-pack.c                 |   2 +-\n>  builtin/reflog.c                       |   2 +-\n>  builtin/remote.c                       |   2 +-\n>  builtin/repack.c                       |   8 +-\n>  builtin/rev-parse.c                    |  11 ++\n>  cache.h                                |  17 ++-\n>  daemon.c                               |  11 +-\n>  environment.c                          |  45 ++++--\n>  fast-import.c                          |   7 +-\n>  git-am.sh                              |  22 +--\n>  git-pull.sh                            |   2 +-\n>  git-rebase--interactive.sh             |   6 +-\n>  git-rebase--merge.sh                   |   6 +-\n>  git-rebase.sh                          |   4 +-\n>  git-sh-setup.sh                        |   2 +-\n>  git-stash.sh                           |   6 +-\n>  git.c                                  |   2 +-\n>  notes-merge.c                          |   6 +-\n>  path.c                                 | 234 +++++++++++++++++++++----------\n>  refs.c                                 |  86 +++++++-----\n>  refs.h                                 |   2 +-\n>  run-command.c                          |   4 +-\n>  run-command.h                          |   2 +-\n>  setup.c                                | 124 +++++++++++++----\n>  sha1_file.c                            |   2 +-\n>  submodule.c                            |   9 +-\n>  t/t0060-path-utils.sh                  |  35 +++++\n>  t/t1501-worktree.sh                    |  76 ++++++++++\n>  t/t1510-repo-setup.sh                  |   1 +\n>  t/t2025-checkout-to.sh (new +x)        |  72 ++++++++++\n>  templates/hooks--applypatch-msg.sample |   4 +-\n>  templates/hooks--pre-applypatch.sample |   4 +-\n>  trace.c                                |   1 +\n>  transport.c                            |   8 +-\n>  wrapper.c                              |  31 +++++\n>  51 files changed, 1109 insertions(+), 265 deletions(-)\n>  create mode 100755 t/t2025-checkout-to.sh\n> \n\n\n-- \nDennis Kaarsemaker\nwww.kaarsemaker.net\n"},{"id":"245892","messageId":"20140712122125.GC21237@wheezy.local","threadId":"37083","inReplyTo":"1404891197-18067-27-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v6 26/32] checkout: detach if the branch is already checked out elsewhere","fromName":"Max Kirillov","fromEmail":"max@max630.net","sentAt":"2014-07-12T12:21:25Z","receivedAt":"2014-07-12T12:21:25Z","isPatch":true,"sender":{"key":"max@max630.net","avatar":"https://avatars.githubusercontent.com/u/381560?v=4"},"body":"On Wed, Jul 09, 2014 at 02:33:11PM +0700, Nguyễn Thái Ngọc Duy wrote:\n> +static int check_linked_checkout(struct branch_info *new,\n> +\t\t\t\t  const char *name, const char *path)\n> +{\n...\n> +\tif (!strncmp(start, new->path, end - start) &&\n> +\t    new->path[end - start] == '\\0') {\n\nThis also detaches if the \"holding checkout\" is _this_\ncheckout, that is, when you are checking out the currently\nchecked our branch.\n\n-- \nMax\n"},{"id":"245904","messageId":"1405227068-25506-1-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1404891197-18067-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 00/31] Support multiple checkouts","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:50:37Z","receivedAt":"2014-07-13T04:50:37Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"v7 fixes all comments from Eric and Max. Jeff's two patches are\ndropped because they have landed in latest master now. Diff against\nv6:\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 470f979..57999fa 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1215,7 +1215,7 @@ gc.prunereposexpire::\n \tWhen 'git gc' is run, it will call\n \t'prune --repos --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 always prune\n+\t\"now\" may be used to disable the grace period and prune\n \t$GIT_DIR/repos immediately.\n \n gc.reflogexpire::\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex dc8503a..c83f476 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1010,15 +1010,13 @@ static int check_linked_checkout(struct branch_info *new,\n \t\t\t\t  const char *name, const char *path)\n {\n \tstruct strbuf sb = STRBUF_INIT;\n-\tchar *start, *end;\n-\tif (strbuf_read_file(&sb, path, 0) < 0)\n-\t\treturn 0;\n-\tif (!starts_with(sb.buf, \"ref:\")) {\n+\tconst char *start, *end;\n+\tif (strbuf_read_file(&sb, path, 0) < 0 ||\n+\t    !skip_prefix(sb.buf, \"ref:\", &start)) {\n \t\tstrbuf_release(&sb);\n \t\treturn 0;\n \t}\n \n-\tstart = sb.buf + 4;\n \twhile (isspace(*start))\n \t\tstart++;\n \tend = start;\n@@ -1200,8 +1198,14 @@ 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)\n-\t\tcheck_linked_checkouts(new);\n+\tif (new->path) {\n+\t\tunsigned char sha1[20];\n+\t\tint flag;\n+\t\tchar *head_ref = resolve_refdup(\"HEAD\", sha1, 0, &flag);\n+\t\tif (!(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) {\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 1190183..0c65808 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -57,6 +57,17 @@ static void remove_pidfile_on_signal(int signo)\n \traise(signo);\n }\n \n+static int git_config_date_string(const char **output,\n+\t\t\t\t  const char *var, const char *value)\n+{\n+\tif (value && strcmp(value, \"now\")) {\n+\t\tunsigned long now = approxidate(\"now\");\n+\t\tif (approxidate(value) >= now)\n+\t\t\treturn error(_(\"Invalid %s: '%s'\"), var, value);\n+\t}\n+\treturn git_config_string(output, var, value);\n+}\n+\n static int gc_config(const char *var, const char *value, void *cb)\n {\n \tif (!strcmp(var, \"gc.packrefs\")) {\n@@ -86,22 +97,10 @@ static int gc_config(const char *var, const char *value, void *cb)\n \t\tdetach_auto = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n-\tif (!strcmp(var, \"gc.pruneexpire\")) {\n-\t\tif (value && strcmp(value, \"now\")) {\n-\t\t\tunsigned long now = approxidate(\"now\");\n-\t\t\tif (approxidate(value) >= now)\n-\t\t\t\treturn error(_(\"Invalid %s: '%s'\"), var, value);\n-\t\t}\n-\t\treturn git_config_string(&prune_expire, var, value);\n-\t}\n-\tif (!strcmp(var, \"gc.prunereposexpire\")) {\n-\t\tif (value && strcmp(value, \"now\")) {\n-\t\t\tunsigned long now = approxidate(\"now\");\n-\t\t\tif (approxidate(value) >= now)\n-\t\t\t\treturn error(_(\"Invalid %s: '%s'\"), var, value);\n-\t\t}\n-\t\treturn git_config_string(&prune_repos_expire, var, value);\n-\t}\n+\tif (!strcmp(var, \"gc.pruneexpire\"))\n+\t\treturn git_config_date_string(&prune_expire, var, value);\n+\tif (!strcmp(var, \"gc.prunereposexpire\"))\n+\t\treturn git_config_date_string(&prune_repos_expire, var, value);\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -295,7 +294,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n \t\tOPT__QUIET(&quiet, N_(\"suppress progress reporting\")),\n \t\t{ OPTION_STRING, 0, \"prune\", &prune_expire, N_(\"date\"),\n \t\t\tN_(\"prune unreferenced objects\"),\n-\t\t\tPARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire},\n+\t\t\tPARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },\n \t\tOPT_BOOL(0, \"aggressive\", &aggressive, N_(\"be more thorough (increased runtime)\")),\n \t\tOPT_BOOL(0, \"auto\", &auto_gc, N_(\"enable auto-gc mode\")),\n \t\tOPT_BOOL(0, \"force\", &force, N_(\"force running gc even if there may be another gc running\")),\ndiff --git a/builtin/prune.c b/builtin/prune.c\nindex 6db6bcc..28b7adf 100644\n--- a/builtin/prune.c\n+++ b/builtin/prune.c\n@@ -112,23 +112,41 @@ static void prune_object_dir(const char *path)\n \t}\n }\n \n-static const char *prune_repo_dir(const char *id, struct stat *st)\n+static int prune_repo_dir(const char *id, struct stat *st, struct strbuf *reason)\n {\n \tchar *path;\n \tint fd, len;\n+\n+\tif (!is_directory(git_path(\"repos/%s\", id))) {\n+\t\tstrbuf_addf(reason, _(\"Removing repos/%s: not a valid directory\"), id);\n+\t\treturn 1;\n+\t}\n \tif (file_exists(git_path(\"repos/%s/locked\", id)))\n-\t\treturn NULL;\n+\t\treturn 0;\n \tif (stat(git_path(\"repos/%s/gitdir\", id), st)) {\n \t\tst->st_mtime = expire;\n-\t\treturn _(\"gitdir does not exist\");\n+\t\tstrbuf_addf(reason, _(\"Removing repos/%s: gitdir file does not exist\"), id);\n+\t\treturn 1;\n \t}\n \tfd = open(git_path(\"repos/%s/gitdir\", id), O_RDONLY);\n+\tif (fd < 0) {\n+\t\tst->st_mtime = expire;\n+\t\tstrbuf_addf(reason, _(\"Removing repos/%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 (path[len - 1] == '\\n' || path[len - 1] == '\\r')\n+\twhile (len && (path[len - 1] == '\\n' || path[len - 1] == '\\r'))\n \t\tlen--;\n+\tif (!len) {\n+\t\tst->st_mtime = expire;\n+\t\tstrbuf_addf(reason, _(\"Removing repos/%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@@ -139,41 +157,48 @@ static const char *prune_repo_dir(const char *id, struct stat *st)\n \t\t */\n \t\tif (!stat(git_path(\"repos/%s/link\", id), &st_link) &&\n \t\t    st_link.st_nlink > 1)\n-\t\t\treturn NULL;\n-\t\treturn _(\"gitdir points to non-existing file\");\n+\t\t\treturn 0;\n+\t\tstrbuf_addf(reason, _(\"Removing repos/%s: gitdir file points to non-existent location\"), id);\n+\t\treturn 1;\n \t}\n \tfree(path);\n-\treturn NULL;\n+\treturn 0;\n }\n \n static void prune_repos_dir(void)\n {\n-\tconst char *reason;\n+\tstruct strbuf reason = STRBUF_INIT;\n+\tstruct strbuf path = STRBUF_INIT;\n \tDIR *dir = opendir(git_path(\"repos\"));\n \tstruct dirent *d;\n-\tint removed = 0;\n+\tint ret;\n \tstruct stat st;\n \tif (!dir)\n \t\treturn;\n \twhile ((d = readdir(dir)) != NULL) {\n \t\tif (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n \t\t\tcontinue;\n-\t\tif ((reason = prune_repo_dir(d->d_name, &st)) != NULL &&\n-\t\t    st.st_mtime <= expire) {\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tif (show_only || verbose)\n-\t\t\t\tprintf(_(\"Removing repos/%s: %s\\n\"), d->d_name, reason);\n-\t\t\tif (show_only)\n-\t\t\t\tcontinue;\n-\t\t\tstrbuf_addstr(&sb, git_path(\"repos/%s\", d->d_name));\n-\t\t\tremove_dir_recursively(&sb, 0);\n-\t\t\tstrbuf_release(&sb);\n-\t\t\tremoved = 1;\n-\t\t}\n+\t\tstrbuf_reset(&reason);\n+\t\tif (!prune_repo_dir(d->d_name, &st, &reason) ||\n+\t\t    st.st_mtime > expire)\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(\"repos/%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 (removed)\n+\tif (!show_only)\n \t\trmdir(git_path(\"repos\"));\n+\tstrbuf_release(&reason);\n+\tstrbuf_release(&path);\n }\n \n /*\ndiff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh\nindex a219851..b0d97a0 100755\n--- a/t/t2025-checkout-to.sh\n+++ b/t/t2025-checkout-to.sh\n@@ -54,6 +54,14 @@ test_expect_success 'detach if the same branch is already checked out' '\n \t)\n '\n \n+test_expect_success 'not detach on re-checking out current branch' '\n+\t(\n+\t\tcd there &&\n+\t\tgit checkout newmaster &&\n+\t\tgit symbolic-ref HEAD\n+\t)\n+'\n+\n test_expect_success 'checkout --to from a bare repo' '\n \t(\n \t\tgit clone --bare . bare &&\ndiff --git a/t/t2026-prune-linked-checkouts.sh b/t/t2026-prune-linked-checkouts.sh\nnew file mode 100755\nindex 0000000..4ccfa4e\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/repos'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'prune --repos on normal repo' '\n+\tgit prune --repos &&\n+\ttest_must_fail git prune --repos abc\n+'\n+\n+test_expect_success 'prune files inside $GIT_DIR/repos' '\n+\tmkdir .git/repos &&\n+\t: >.git/repos/abc &&\n+\tgit prune --repos --verbose >actual &&\n+\tcat >expect <<EOF &&\n+Removing repos/abc: not a valid directory\n+EOF\n+\ttest_i18ncmp expect actual &&\n+\t! test -f .git/repos/abc &&\n+\t! test -d .git/repos\n+'\n+\n+test_expect_success 'prune directories without gitdir' '\n+\tmkdir -p .git/repos/def/abc &&\n+\t: >.git/repos/def/def &&\n+\tcat >expect <<EOF &&\n+Removing repos/def: gitdir file does not exist\n+EOF\n+\tgit prune --repos --verbose >actual &&\n+\ttest_i18ncmp expect actual &&\n+\t! test -d .git/repos/def &&\n+\t! test -d .git/repos\n+'\n+\n+test_expect_success POSIXPERM 'prune directories with unreadable gitdir' '\n+\tmkdir -p .git/repos/def/abc &&\n+\t: >.git/repos/def/def &&\n+\t: >.git/repos/def/gitdir &&\n+\tchmod u-r .git/repos/def/gitdir &&\n+\tgit prune --repos --verbose >actual &&\n+\ttest_i18ngrep \"Removing repos/def: unable to read gitdir file\" actual &&\n+\t! test -d .git/repos/def &&\n+\t! test -d .git/repos\n+'\n+\n+test_expect_success 'prune directories with invalid gitdir' '\n+\tmkdir -p .git/repos/def/abc &&\n+\t: >.git/repos/def/def &&\n+\t: >.git/repos/def/gitdir &&\n+\tgit prune --repos --verbose >actual &&\n+\ttest_i18ngrep \"Removing repos/def: invalid gitdir file\" actual &&\n+\t! test -d .git/repos/def &&\n+\t! test -d .git/repos\n+'\n+\n+test_expect_success 'prune directories with gitdir pointing to nowhere' '\n+\tmkdir -p .git/repos/def/abc &&\n+\t: >.git/repos/def/def &&\n+\techo \"$TRASH_DIRECTORY\"/nowhere >.git/repos/def/gitdir &&\n+\tgit prune --repos --verbose >actual &&\n+\ttest_i18ngrep \"Removing repos/def: gitdir file points to non-existent location\" actual &&\n+\t! test -d .git/repos/def &&\n+\t! test -d .git/repos\n+'\n+\n+test_expect_success 'not prune locked checkout' '\n+\ttest_when_finished rm -r .git/repos\n+\tmkdir -p .git/repos/ghi &&\n+\t: >.git/repos/ghi/locked &&\n+\tgit prune --repos &&\n+\ttest -d .git/repos/ghi\n+'\n+\n+test_expect_success 'not prune recent checkouts' '\n+\ttest_when_finished rm -r .git/repos\n+\tmkdir zz &&\n+\tmkdir -p .git/repos/jlm &&\n+\techo \"$TRASH_DIRECTORY\"/zz >.git/repos/jlm/gitdir &&\n+\tgit prune --repos --verbose &&\n+\ttest -d .git/repos/jlm\n+'\n+\n+test_done\n\n-- \n1.9.1.346.ga2b5940\n"},{"id":"245905","messageId":"1405227068-25506-2-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 01/31] 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-07-13T04:50:38Z","receivedAt":"2014-07-13T04:50:38Z","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 bc804a3..42ef3af 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@@ -158,41 +154,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-- \n1.9.1.346.ga2b5940\n"},{"id":"245906","messageId":"1405227068-25506-3-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 02/31] path.c: make get_pathname() call sites return const char *","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:50:39Z","receivedAt":"2014-07-13T04:50:39Z","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 463cfee..3abcef1 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -585,7 +585,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 e15ca33..91fac9d 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -289,16 +289,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 e8d0cca..9522b1b 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -573,7 +573,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@@ -807,7 +808,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 8aadca1..d414962 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 18458e8..ed11e7e 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -599,7 +599,7 @@ static void run_update_post_hook(struct command *commands)\n \tint argc;\n \tconst char **argv;\n \tstruct child_process proc;\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 a8efe3d..3d99c9c 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -580,7 +580,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 ff2216a..a64a4a9 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -285,7 +285,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@@ -313,7 +314,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@@ -366,7 +368,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 44aa439..707408b 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -682,9 +682,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 fa635f7..d9c068b 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -404,7 +404,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 fd5fae2..a9e6b15 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 42ef3af..a3f8826 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@@ -152,7 +152,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 82e4842..dbd131e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1442,7 +1442,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@@ -2255,7 +2255,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\t\t\t\t    const unsigned char *old_sha1,\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@@ -2318,7 +2318,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@@ -2756,7 +2756,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 be07d4a..614b8ac 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -758,9 +758,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 ea73de3..890cc95 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -48,7 +48,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 a38854c..9700108 100644\n--- a/sha1_file.c\n+++ b/sha1_file.c\n@@ -402,7 +402,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-- \n1.9.1.346.ga2b5940\n"},{"id":"245907","messageId":"1405227068-25506-4-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 03/31] git_snpath(): retire and replace with strbuf_git_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:50:40Z","receivedAt":"2014-07-13T04:50:40Z","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             | 78 +++++++++++++++++++++++++++++++++---------------------\n refs.h             |  2 +-\n 5 files changed, 61 insertions(+), 47 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 3abcef1..8023987 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -584,18 +584,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 707408b..279b581 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -674,8 +674,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 a3f8826..e545064 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 dbd131e..00b2312 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1535,10 +1535,12 @@ static const char *handle_missing_loose_ref(const char *refname,\n \n const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)\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@@ -1547,15 +1549,17 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \t\treturn NULL;\n \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 \n \t\tif (--depth < 0)\n-\t\t\treturn NULL;\n+\t\t\tgoto fail;\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@@ -1569,10 +1573,11 @@ 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\treturn handle_missing_loose_ref(refname, sha1,\n-\t\t\t\t\t\t\t\treading, flag);\n+\t\t\t\tret = handle_missing_loose_ref(refname, sha1,\n+\t\t\t\t\t\t\t       reading, flag);\n \t\t\telse\n-\t\t\t\treturn NULL;\n+\t\t\t\tret = NULL;\n+\t\t\tgoto done;\n \t\t}\n \n \t\t/* Follow \"normalized\" - ie \"refs/..\" symlinks by hand */\n@@ -1583,7 +1588,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\treturn NULL;\n+\t\t\t\t\tgoto fail;\n \t\t\t}\n \t\t\tbuffer[len] = 0;\n \t\t\tif (starts_with(buffer, \"refs/\") &&\n@@ -1599,7 +1604,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\treturn NULL;\n+\t\t\tgoto fail;\n \t\t}\n \n \t\t/*\n@@ -1612,12 +1617,13 @@ 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\treturn NULL;\n+\t\t\t\tgoto fail;\n \t\t}\n+\n \t\tlen = read_in_full(fd, buffer, sizeof(buffer)-1);\n \t\tclose(fd);\n \t\tif (len < 0)\n-\t\t\treturn NULL;\n+\t\t\tgoto fail;\n \t\twhile (len && isspace(buffer[len-1]))\n \t\t\tlen--;\n \t\tbuffer[len] = '\\0';\n@@ -1634,9 +1640,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \t\t\t    (buffer[40] != '\\0' && !isspace(buffer[40]))) {\n \t\t\t\tif (flag)\n \t\t\t\t\t*flag |= REF_ISBROKEN;\n-\t\t\t\treturn NULL;\n+\t\t\t\tgoto fail;\n \t\t\t}\n-\t\t\treturn refname;\n+\t\t\tret = refname;\n+\t\t\tgoto done;\n \t\t}\n \t\tif (flag)\n \t\t\t*flag |= REF_ISSYMREF;\n@@ -1646,10 +1653,15 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \t\tif (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {\n \t\t\tif (flag)\n \t\t\t\t*flag |= REF_ISBROKEN;\n-\t\t\treturn NULL;\n+\t\t\tgoto fail;\n \t\t}\n \t\trefname = strcpy(refname_buffer, buf);\n \t}\n+fail:\n+\tret = NULL;\n+done:\n+\tstrbuf_release(&sb_path);\n+\treturn ret;\n }\n \n char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag)\n@@ -2940,41 +2952,41 @@ static int copy_msg(char *buf, const char *msg)\n \treturn cp - buf;\n }\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 \tint logfd, oflags = O_APPEND | O_WRONLY;\n \n-\tgit_snpath(logfile, bufsize, \"logs/%s\", refname);\n+\tstrbuf_git_path(logfile, \"logs/%s\", refname);\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) < 0)\n+\t\tif (safe_create_leading_directories(logfile->buf) < 0)\n \t\t\treturn error(\"unable to create directory for %s\",\n-\t\t\t\t     logfile);\n+\t\t\t\t     logfile->buf);\n \t\toflags |= O_CREAT;\n \t}\n \n-\tlogfd = open(logfile, oflags, 0666);\n+\tlogfd = open(logfile->buf, 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)) {\n+\t\t\tif (remove_empty_directories(logfile->buf)) {\n \t\t\t\treturn error(\"There are still logs under '%s'\",\n-\t\t\t\t\t     logfile);\n+\t\t\t\t\t     logfile->buf);\n \t\t\t}\n-\t\t\tlogfd = open(logfile, oflags, 0666);\n+\t\t\tlogfd = open(logfile->buf, oflags, 0666);\n \t\t}\n \n \t\tif (logfd < 0)\n \t\t\treturn error(\"Unable to append to %s: %s\",\n-\t\t\t\t     logfile, strerror(errno));\n+\t\t\t\t     logfile->buf, strerror(errno));\n \t}\n \n-\tadjust_shared_perm(logfile);\n+\tadjust_shared_perm(logfile->buf);\n \tclose(logfd);\n \treturn 0;\n }\n@@ -2985,20 +2997,22 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,\n \tint logfd, result, written, oflags = O_APPEND | O_WRONLY;\n \tunsigned maxlen, len;\n \tint msglen;\n-\tchar log_file[PATH_MAX];\n+\tstruct strbuf sb_log_file = STRBUF_INIT;\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+\t\tgoto done;\n+\tlog_file = sb_log_file.buf;\n \n \tlogfd = open(log_file, oflags);\n \tif (logfd < 0)\n-\t\treturn 0;\n+\t\tgoto done;\n \tmsglen = msg ? strlen(msg) : 0;\n \tcommitter = git_committer_info(0);\n \tmaxlen = strlen(committer) + msglen + 100;\n@@ -3011,9 +3025,13 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,\n \t\tlen += copy_msg(logrec + len - 1, msg) - 1;\n \twritten = len <= maxlen ? write_in_full(logfd, logrec, len) : -1;\n \tfree(logrec);\n-\tif (close(logfd) != 0 || written != len)\n-\t\treturn error(\"Unable to append to %s\", log_file);\n-\treturn 0;\n+\tif (close(logfd) != 0 || written != len) {\n+\t\terror(\"Unable to append to %s\", log_file);\n+\t\tresult = -1;\n+\t}\n+done:\n+\tstrbuf_release(&sb_log_file);\n+\treturn result;\n }\n \n static int is_branch(const char *refname)\ndiff --git a/refs.h b/refs.h\nindex 4e3050d..99d88a7 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -157,7 +157,7 @@ extern void unlock_ref(struct ref_lock *lock);\n extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);\n \n /** Setup reflog before using. **/\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 long at_time, int cnt,\n-- \n1.9.1.346.ga2b5940\n"},{"id":"245908","messageId":"1405227068-25506-5-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 04/31] path.c: rename vsnpath() to do_git_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:50:41Z","receivedAt":"2014-07-13T04:50:41Z","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 e545064..2cb2e61 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245909","messageId":"1405227068-25506-6-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 05/31] 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-07-13T04:50:42Z","receivedAt":"2014-07-13T04:50: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 path.c | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/path.c b/path.c\nindex 2cb2e61..65881aa 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245910","messageId":"1405227068-25506-7-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 06/31] git_path(): be aware of file relocation in $GIT_DIR","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:50:43Z","receivedAt":"2014-07-13T04:50:43Z","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 987395d..9465399 100644\n--- a/Documentation/git-rev-parse.txt\n+++ b/Documentation/git-rev-parse.txt\n@@ -232,6 +232,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 1a6122d..7606d43 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -529,6 +529,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 279b581..0128b9a 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -612,6 +612,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 65881aa..3deb80c 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245911","messageId":"1405227068-25506-8-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 07/31] *.sh: respect $GIT_INDEX_FILE","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:50:44Z","receivedAt":"2014-07-13T04:50:44Z","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 18a394f..6ab0c31 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 bcc757b..393e1ec 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245912","messageId":"1405227068-25506-9-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 08/31] reflog: avoid constructing .lock path with git_path","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:50:45Z","receivedAt":"2014-07-13T04:50:45Z","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 e8a8fb1..9bd874d 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245913","messageId":"1405227068-25506-10-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 09/31] 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-07-13T04:50:46Z","receivedAt":"2014-07-13T04:50:46Z","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 d9c068b..ea426c4 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -3103,12 +3103,9 @@ static void parse_progress(void)\n \n static char* make_fast_import_path(const char *path)\n {\n-\tstruct strbuf abs_path = STRBUF_INIT;\n-\n \tif (!relative_marks_paths || is_absolute_path(path))\n \t\treturn xstrdup(path);\n-\tstrbuf_addf(&abs_path, \"%s/info/fast-import/%s\", get_git_dir(), path);\n-\treturn strbuf_detach(&abs_path, NULL);\n+\treturn xstrdup(git_path(\"info/fast-import/%s\", path));\n }\n \n static void option_import_marks(const char *marks,\n-- \n1.9.1.346.ga2b5940\n"},{"id":"245914","messageId":"1405227068-25506-11-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 10/31] commit: use SEQ_DIR instead of hardcoding \"sequencer\"","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:50:47Z","receivedAt":"2014-07-13T04:50:47Z","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 461c3b1..4b9f012 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -156,7 +156,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-- \n1.9.1.346.ga2b5940\n"},{"id":"245915","messageId":"1405227068-25506-12-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 11/31] $GIT_COMMON_DIR: a new environment variable","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:50:48Z","receivedAt":"2014-07-13T04:50:48Z","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\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git.txt                  |  8 +++++++\n Documentation/gitrepository-layout.txt | 42 ++++++++++++++++++++++++++--------\n cache.h                                |  4 +++-\n environment.c                          | 28 +++++++++++++++++------\n path.c                                 | 34 +++++++++++++++++++++++++++\n t/t0060-path-utils.sh                  | 15 ++++++++++++\n 6 files changed, 114 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex 7924209..749052f 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -788,6 +788,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 17d2ea6..7629e38 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+\t$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@@ -157,7 +172,8 @@ index::\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@@ -197,12 +213,16 @@ 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+\tfor more information. This directory is ignored\n+\t$GIT_COMMON_DIR is set and \"$GIT_COMMON_DIR/logs\" will be used\n+\tinstead.\n \n logs/refs/heads/`name`::\n \tRecords all changes made to the branch tip named `name`.\n@@ -213,10 +233,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 0128b9a..b606ee4 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -362,6 +362,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@@ -415,6 +416,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@@ -612,7 +614,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..6b74f68 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@@ -125,8 +125,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 +149,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_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 +183,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 3deb80c..8a6586c 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..2dabcef 100755\n--- a/t/t0060-path-utils.sh\n+++ b/t/t0060-path-utils.sh\n@@ -262,5 +262,20 @@ 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 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245916","messageId":"1405227068-25506-13-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 12/31] 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-07-13T04:50:49Z","receivedAt":"2014-07-13T04:50:49Z","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 9447980..d3dbb2f 100644\n--- a/git-sh-setup.sh\n+++ b/git-sh-setup.sh\n@@ -345,7 +345,7 @@ then\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 fi\n \n peel_committish () {\n-- \n1.9.1.346.ga2b5940\n"},{"id":"245917","messageId":"1405227068-25506-14-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 13/31] *.sh: avoid hardcoding $GIT_DIR/hooks/...","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:50:50Z","receivedAt":"2014-07-13T04:50:50Z","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 7e1eda0..e8995f9 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 06c810b..d60e710 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -201,9 +201,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-- \n1.9.1.346.ga2b5940\n"},{"id":"245918","messageId":"1405227068-25506-15-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 14/31] git-stash: avoid hardcoding $GIT_DIR/logs/....","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:50:51Z","receivedAt":"2014-07-13T04:50: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 git-stash.sh | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/git-stash.sh b/git-stash.sh\nindex 393e1ec..41f8f6b 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245919","messageId":"1405227068-25506-16-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 15/31] setup.c: convert is_git_directory() to use strbuf","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:50:52Z","receivedAt":"2014-07-13T04:50: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 setup.c | 37 +++++++++++++++++++++----------------\n 1 file changed, 21 insertions(+), 16 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 0a22f8b..425fd79 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245920","messageId":"1405227068-25506-17-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 16/31] setup.c: detect $GIT_COMMON_DIR in is_git_directory()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:50:53Z","receivedAt":"2014-07-13T04:50:53Z","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 7629e38..0f341fc 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -237,6 +237,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 425fd79..176d505 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245921","messageId":"1405227068-25506-18-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 17/31] setup.c: convert check_repository_format_gently to use strbuf","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:50:54Z","receivedAt":"2014-07-13T04:50:54Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n setup.c | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 176d505..a17389f 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245922","messageId":"1405227068-25506-19-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 18/31] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:50:55Z","receivedAt":"2014-07-13T04:50:55Z","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 a17389f..79f79f2 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245923","messageId":"1405227068-25506-20-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 19/31] setup.c: support multi-checkout repo setup","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:50:56Z","receivedAt":"2014-07-13T04:50:56Z","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\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 1d718bd..286e539 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -380,6 +380,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 9465399..f1867d3 100644\n--- a/Documentation/git-rev-parse.txt\n+++ b/Documentation/git-rev-parse.txt\n@@ -215,6 +215,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 7606d43..29475c5 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -757,6 +757,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n \t\t\t\tprintf(\"%s%s.git\\n\", cwd, len && cwd[len-1] != '/' ? \"/\" : \"\");\n \t\t\t\tcontinue;\n \t\t\t}\n+\t\t\tif (!strcmp(arg, \"--git-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 b606ee4..e3ff7dc 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -422,6 +422,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 6b74f68..50ed40a 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -141,6 +141,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@@ -149,12 +150,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 79f79f2..8f90bc3 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@@ -831,11 +847,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..e6ac7a4 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 \"$TRASH_DIRECTORY/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 08180a9..a594761 100644\n--- a/trace.c\n+++ b/trace.c\n@@ -173,6 +173,7 @@ void trace_repo_setup(const char *prefix)\n \t\tprefix = \"(null)\";\n \n \ttrace_printf_key(key, \"setup: git_dir: %s\\n\", quote_crnl(get_git_dir()));\n+\ttrace_printf_key(key, \"setup: git_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-- \n1.9.1.346.ga2b5940\n"},{"id":"245924","messageId":"1405227068-25506-21-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 20/31] wrapper.c: wrapper to open a file, fprintf then close","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:50:57Z","receivedAt":"2014-07-13T04:50: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 cache.h   |  2 ++\n wrapper.c | 31 +++++++++++++++++++++++++++++++\n 2 files changed, 33 insertions(+)\n\ndiff --git a/cache.h b/cache.h\nindex e3ff7dc..bf4d15e 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1359,6 +1359,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 bc1bfb8..9d7b9ac 100644\n--- a/wrapper.c\n+++ b/wrapper.c\n@@ -493,3 +493,34 @@ struct passwd *xgetpwuid_self(void)\n \t\t    errno ? strerror(errno) : _(\"no such user\"));\n \treturn pw;\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-- \n1.9.1.346.ga2b5940\n"},{"id":"245925","messageId":"1405227068-25506-22-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 21/31] use new wrapper write_file() for simple file writing","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:50:58Z","receivedAt":"2014-07-13T04:50:58Z","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 0591b22..e4265a1 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -754,7 +754,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@@ -767,8 +766,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 56f85e2..ce8416a 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 1eb6631..dd638a9 100644\n--- a/daemon.c\n+++ b/daemon.c\n@@ -1094,15 +1094,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@@ -1313,7 +1304,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 b80ecac..b7b6059 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1112,16 +1112,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 59c9727..1b779bb 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -296,7 +296,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@@ -305,10 +304,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-- \n1.9.1.346.ga2b5940\n"},{"id":"245926","messageId":"1405227068-25506-23-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 22/31] checkout: support checking out into a new working directory","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:50:59Z","receivedAt":"2014-07-13T04:50:59Z","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/repos/<id>. It then executes \"git checkout\" again\non the new worktree with the same arguments except \"--to\" is taken\nout. The second checkout execution, which is not contaminated with any\ninfo from the current repository, will actually check out and\neverything that normal \"git checkout\" does.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-checkout.txt         | 34 +++++++++++++\n Documentation/git.txt                  |  3 +-\n Documentation/gitrepository-layout.txt |  7 +++\n builtin/checkout.c                     | 93 +++++++++++++++++++++++++++++++++-\n path.c                                 |  2 +-\n t/t2025-checkout-to.sh (new +x)        | 48 ++++++++++++++++++\n 6 files changed, 183 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..fcf73b2 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 new 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 CHECKOUT\n+\tMODE\" 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,33 @@ $ git reflog -2 HEAD # or\n $ git log -g -2 HEAD\n ------------\n \n+MULTIPLE CHECKOUT MODE\n+-------------------------------\n+Normally a working directory is attached to repository. When \"git\n+checkout --to\" is used, a new working directory is attached to the\n+current repository. This new working directory is called \"linked\n+checkout\" as compared to the \"main checkout\" prepared by \"git init\" or\n+\"git clone\". A repository has one main checkout and zero or more\n+linked checkouts.\n+\n+All checkouts share the same repository. Linked checkouts see the\n+repository a bit different from the main checkout. When the checkout\n+\"new\" reads the path $GIT_DIR/HEAD for example, the actual path\n+returned could be $GIT_DIR/repos/new/HEAD. This ensures checkouts\n+won't step on each other.\n+\n+Each linked checkout has a private space in $GIT_DIR/repos, usually\n+named after the base name of the working directory with a number added\n+to make it unique. The linked checkout's $GIT_DIR points to this\n+private space while $GIT_COMMON_DIR points to the main checkout's\n+$GIT_DIR. These settings are done by \"git checkout --to\".\n+\n+Because in this mode $GIT_DIR becomes a lightweight virtual file\n+system where a path could be rewritten to some place else, accessing\n+$GIT_DIR from scripts should use `git rev-parse --git-path` to resolve\n+a path instead of using it directly unless the path is known to be\n+private to the working directory.\n+\n EXAMPLES\n --------\n \ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex 749052f..c0a4940 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -792,7 +792,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 0f341fc..543d874 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -249,6 +249,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+repos::\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\n+\t$GIT_COMMON_DIR is set and \"$GIT_COMMON_DIR/repos\" 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 8023987..81adb74 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@@ -250,6 +254,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@@ -485,7 +492,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@@ -796,7 +803,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@@ -806,6 +814,74 @@ 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+\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(\"repos/%.*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/repos/%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@@ -1067,6 +1143,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@@ -1109,6 +1188,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\t\t N_(\"do not limit pathspecs to sparse entries only\")),\n \t\tOPT_HIDDEN_BOOL(0, \"guess\", &dwim_new_local_branch,\n \t\t\t\tN_(\"second guess 'git checkout no-such-branch'\")),\n+\t\tOPT_STRING(0, \"to\", &opts.new_worktree, N_(\"path\"),\n+\t\t\t   N_(\"check a branch out in a separate working directory\")),\n \t\tOPT_END(),\n \t};\n \n@@ -1117,6 +1198,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@@ -1125,6 +1209,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 8a6586c..e41d6b3 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\", \"/repos\", \"/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..5ec49e2\n--- /dev/null\n+++ b/t/t2025-checkout-to.sh\n@@ -0,0 +1,48 @@\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 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 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245927","messageId":"1405227068-25506-24-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 23/31] checkout: clean up half-prepared directories in --to mode","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:51:00Z","receivedAt":"2014-07-13T04:51:00Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/checkout.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 47 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 81adb74..3167fd2 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@@ -814,6 +815,35 @@ static int switch_branches(const struct checkout_opts *opts,\n \treturn ret || writeout_error;\n }\n \n+static const char *junk_work_tree;\n+static const char *junk_git_dir;\n+static int is_junk;\n+static pid_t junk_pid;\n+\n+static void remove_junk(void)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tif (!is_junk || getpid() != junk_pid)\n+\t\treturn;\n+\tif (junk_git_dir) {\n+\t\tstrbuf_addstr(&sb, junk_git_dir);\n+\t\tremove_dir_recursively(&sb, 0);\n+\t\tstrbuf_reset(&sb);\n+\t}\n+\tif (junk_work_tree) {\n+\t\tstrbuf_addstr(&sb, junk_work_tree);\n+\t\tremove_dir_recursively(&sb, 0);\n+\t}\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@@ -822,7 +852,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@@ -848,13 +878,21 @@ 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 = sb_repo.buf;\n+\tis_junk = 1;\n \n \tstrbuf_addf(&sb_git, \"%s/.git\", path);\n \tif (safe_create_leading_directories_const(sb_git.buf))\n \t\tdie_errno(_(\"could not create leading directories of '%s'\"),\n \t\t\t  sb_git.buf);\n+\tjunk_work_tree = path;\n \n \twrite_file(sb_git.buf, 1, \"gitdir: %s/repos/%s\\n\",\n \t\t   real_path(get_git_common_dir()), name);\n@@ -879,7 +917,14 @@ 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+\tif (!ret)\n+\t\tis_junk = 0;\n+\tstrbuf_release(&sb);\n+\tstrbuf_release(&sb_repo);\n+\tstrbuf_release(&sb_git);\n+\treturn ret;\n+\n }\n \n static int git_checkout_config(const char *var, const char *value, void *cb)\n-- \n1.9.1.346.ga2b5940\n"},{"id":"245928","messageId":"1405227068-25506-25-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 24/31] checkout: detach if the branch is already checked out elsewhere","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:51:01Z","receivedAt":"2014-07-13T04:51:01Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The normal rule is anything outside refs/heads/ is detached. This\nincreases strictness of the rule a bit more: if the branch is checked\nout (either in $GIT_COMMON_DIR/HEAD or any $GIT_DIR/repos/.../HEAD)\nthen it's detached as well.\n\nA hint is given so the user knows where to go and do something there\nif they still want to checkout undetached here.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/checkout.c     | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++\n t/t2025-checkout-to.sh | 23 ++++++++++++--\n 2 files changed, 104 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 3167fd2..fe24766 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -432,6 +432,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@@ -640,6 +645,11 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\tif (old->path && advice_detached_head)\n \t\t\t\tdetach_advice(new->name);\n \t\t\tdescribe_detached_head(_(\"HEAD is now at\"), new->commit);\n+\t\t\tif (new->checkout && !*new->checkout)\n+\t\t\t\tfprintf(stderr, _(\"hint: the main checkout is holding this branch\\n\"));\n+\t\t\telse if (new->checkout)\n+\t\t\t\tfprintf(stderr, _(\"hint: the linked checkout %s is holding this branch\\n\"),\n+\t\t\t\t\tnew->checkout);\n \t\t}\n \t} else if (new->path) {\t/* Switch branches. */\n \t\tcreate_symref(\"HEAD\", new->path, msg.buf);\n@@ -982,6 +992,71 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1)\n \treturn NULL;\n }\n \n+static int check_linked_checkout(struct branch_info *new,\n+\t\t\t\t  const char *name, const char *path)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tconst char *start, *end;\n+\tif (strbuf_read_file(&sb, path, 0) < 0 ||\n+\t    !skip_prefix(sb.buf, \"ref:\", &start)) {\n+\t\tstrbuf_release(&sb);\n+\t\treturn 0;\n+\t}\n+\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) &&\n+\t    new->path[end - start] == '\\0') {\n+\t\tstrbuf_release(&sb);\n+\t\tnew->path = NULL; /* detach */\n+\t\tnew->checkout = xstrdup(name); /* reason */\n+\t\treturn 1;\n+\t}\n+\tstrbuf_release(&sb);\n+\treturn 0;\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/repos\", get_git_common_dir());\n+\tif ((dir = opendir(path.buf)) == NULL) {\n+\t\tstrbuf_release(&path);\n+\t\treturn;\n+\t}\n+\n+\tstrbuf_reset(&path);\n+\tstrbuf_addf(&path, \"%s/HEAD\", get_git_common_dir());\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+\tif (check_linked_checkout(new, \"\", path.buf)) {\n+\t\tstrbuf_release(&path);\n+\t\tclosedir(dir);\n+\t\treturn;\n+\t}\n+\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(&path);\n+\t\tstrbuf_addf(&path, \"%s/repos/%s/HEAD\",\n+\t\t\t    get_git_common_dir(), d->d_name);\n+\t\tif (check_linked_checkout(new, d->d_name, path.buf))\n+\t\t\tbreak;\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@@ -1109,6 +1184,15 @@ 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) {\n+\t\tunsigned char sha1[20];\n+\t\tint flag;\n+\t\tchar *head_ref = resolve_refdup(\"HEAD\", sha1, 0, &flag);\n+\t\tif (!(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 */\ndiff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh\nindex 5ec49e2..20e3dc7 100755\n--- a/t/t2025-checkout-to.sh\n+++ b/t/t2025-checkout-to.sh\n@@ -13,13 +13,14 @@ test_expect_success 'checkout --to not updating paths' '\n '\n \n test_expect_success 'checkout --to a new worktree' '\n+\tgit rev-parse HEAD >expect &&\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\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@@ -45,4 +46,20 @@ test_expect_success 'checkout --to a new worktree creating new branch' '\n \t)\n '\n \n+test_expect_success 'detach if the same branch is already checked out' '\n+\t(\n+\t\tcd here &&\n+\t\tgit checkout newmaster &&\n+\t\ttest_must_fail git symbolic-ref HEAD\n+\t)\n+'\n+\n+test_expect_success 'not detach on re-checking out current branch' '\n+\t(\n+\t\tcd there &&\n+\t\tgit checkout newmaster &&\n+\t\tgit symbolic-ref HEAD\n+\t)\n+'\n+\n test_done\n-- \n1.9.1.346.ga2b5940\n"},{"id":"245929","messageId":"1405227068-25506-26-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 25/31] prune: strategies for linked checkouts","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:51:02Z","receivedAt":"2014-07-13T04:51:02Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"(alias R=$GIT_COMMON_DIR/repos/<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   repos/<id> is to be pruned.\n\n - If $R/locked exists, repos/<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\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-prune.txt                |  3 +\n Documentation/gitrepository-layout.txt     | 19 ++++++\n builtin/checkout.c                         | 14 +++++\n builtin/prune.c                            | 99 ++++++++++++++++++++++++++++++\n setup.c                                    | 13 ++++\n t/t2026-prune-linked-checkouts.sh (new +x) | 84 +++++++++++++++++++++++++\n 6 files changed, 232 insertions(+)\n create mode 100755 t/t2026-prune-linked-checkouts.sh\n\ndiff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt\nindex 7a493c8..50e39ec 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+--repos::\n+\tPrune directories in $GIT_DIR/repos.\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 543d874..bed4f1a 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -256,6 +256,25 @@ repos::\n \t$GIT_COMMON_DIR is set and \"$GIT_COMMON_DIR/repos\" will be\n \tused instead.\n \n+repos/<id>/gitdir::\n+\tA text file containing the absolute path back to the .git file\n+\tthat points to here. This is used to check if the linked\n+\trepository has been manually removed and there is no need to\n+\tkeep this directory any more. mtime of this file should be\n+\tupdated every time the linked repository is accessed.\n+\n+repos/<id>/locked::\n+\tIf this file exists, the linked repository may be on a\n+\tportable device and not available. It does not mean that the\n+\tlinked repository is gone and `repos/<id>` could be\n+\tremoved. The file's content contains a reason string on why\n+\tthe repository is locked.\n+\n+repos/<id>/link::\n+\tIf this file exists, it is a hard link to the linked .git\n+\tfile. It is used to detect if the linked repository is\n+\tmanually removed.\n+\n SEE ALSO\n --------\n linkgit:git-init[1],\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex fe24766..5b93f49 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -898,12 +898,22 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n \tjunk_git_dir = sb_repo.buf;\n \tis_junk = 1;\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 \tjunk_work_tree = path;\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/repos/%s\\n\",\n \t\t   real_path(get_git_common_dir()), name);\n \t/*\n@@ -912,6 +922,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@@ -930,6 +941,9 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n \tret = run_command(&cp);\n \tif (!ret)\n \t\tis_junk = 0;\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);\ndiff --git a/builtin/prune.c b/builtin/prune.c\nindex 144a3bd..28b7adf 100644\n--- a/builtin/prune.c\n+++ b/builtin/prune.c\n@@ -112,6 +112,95 @@ static void prune_object_dir(const char *path)\n \t}\n }\n \n+static int prune_repo_dir(const char *id, struct stat *st, struct strbuf *reason)\n+{\n+\tchar *path;\n+\tint fd, len;\n+\n+\tif (!is_directory(git_path(\"repos/%s\", id))) {\n+\t\tstrbuf_addf(reason, _(\"Removing repos/%s: not a valid directory\"), id);\n+\t\treturn 1;\n+\t}\n+\tif (file_exists(git_path(\"repos/%s/locked\", id)))\n+\t\treturn 0;\n+\tif (stat(git_path(\"repos/%s/gitdir\", id), st)) {\n+\t\tst->st_mtime = expire;\n+\t\tstrbuf_addf(reason, _(\"Removing repos/%s: gitdir file does not exist\"), id);\n+\t\treturn 1;\n+\t}\n+\tfd = open(git_path(\"repos/%s/gitdir\", id), O_RDONLY);\n+\tif (fd < 0) {\n+\t\tst->st_mtime = expire;\n+\t\tstrbuf_addf(reason, _(\"Removing repos/%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\tst->st_mtime = expire;\n+\t\tstrbuf_addf(reason, _(\"Removing repos/%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(\"repos/%s/link\", id), &st_link) &&\n+\t\t    st_link.st_nlink > 1)\n+\t\t\treturn 0;\n+\t\tstrbuf_addf(reason, _(\"Removing repos/%s: gitdir file points to non-existent location\"), id);\n+\t\treturn 1;\n+\t}\n+\tfree(path);\n+\treturn 0;\n+}\n+\n+static void prune_repos_dir(void)\n+{\n+\tstruct strbuf reason = STRBUF_INIT;\n+\tstruct strbuf path = STRBUF_INIT;\n+\tDIR *dir = opendir(git_path(\"repos\"));\n+\tstruct dirent *d;\n+\tint ret;\n+\tstruct stat st;\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_repo_dir(d->d_name, &st, &reason) ||\n+\t\t    st.st_mtime > expire)\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(\"repos/%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(\"repos\"));\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@@ -138,10 +227,12 @@ int cmd_prune(int argc, const char **argv, const char *prefix)\n {\n \tstruct rev_info revs;\n \tstruct progress *progress = NULL;\n+\tint prune_repos = 0;\n \tconst struct option options[] = {\n \t\tOPT__DRY_RUN(&show_only, N_(\"do not remove, show only\")),\n \t\tOPT__VERBOSE(&verbose, N_(\"report pruned objects\")),\n \t\tOPT_BOOL(0, \"progress\", &show_progress, N_(\"show progress\")),\n+\t\tOPT_BOOL(0, \"repos\", &prune_repos, N_(\"prune .git/repos/\")),\n \t\tOPT_EXPIRY_DATE(0, \"expire\", &expire,\n \t\t\t\tN_(\"expire objects older than <time>\")),\n \t\tOPT_END()\n@@ -154,6 +245,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 (prune_repos) {\n+\t\tif (argc)\n+\t\t\tdie(_(\"--repos does not take extra arguments\"));\n+\t\tprune_repos_dir();\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 8f90bc3..da2d669 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..4ccfa4e\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/repos'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'prune --repos on normal repo' '\n+\tgit prune --repos &&\n+\ttest_must_fail git prune --repos abc\n+'\n+\n+test_expect_success 'prune files inside $GIT_DIR/repos' '\n+\tmkdir .git/repos &&\n+\t: >.git/repos/abc &&\n+\tgit prune --repos --verbose >actual &&\n+\tcat >expect <<EOF &&\n+Removing repos/abc: not a valid directory\n+EOF\n+\ttest_i18ncmp expect actual &&\n+\t! test -f .git/repos/abc &&\n+\t! test -d .git/repos\n+'\n+\n+test_expect_success 'prune directories without gitdir' '\n+\tmkdir -p .git/repos/def/abc &&\n+\t: >.git/repos/def/def &&\n+\tcat >expect <<EOF &&\n+Removing repos/def: gitdir file does not exist\n+EOF\n+\tgit prune --repos --verbose >actual &&\n+\ttest_i18ncmp expect actual &&\n+\t! test -d .git/repos/def &&\n+\t! test -d .git/repos\n+'\n+\n+test_expect_success POSIXPERM 'prune directories with unreadable gitdir' '\n+\tmkdir -p .git/repos/def/abc &&\n+\t: >.git/repos/def/def &&\n+\t: >.git/repos/def/gitdir &&\n+\tchmod u-r .git/repos/def/gitdir &&\n+\tgit prune --repos --verbose >actual &&\n+\ttest_i18ngrep \"Removing repos/def: unable to read gitdir file\" actual &&\n+\t! test -d .git/repos/def &&\n+\t! test -d .git/repos\n+'\n+\n+test_expect_success 'prune directories with invalid gitdir' '\n+\tmkdir -p .git/repos/def/abc &&\n+\t: >.git/repos/def/def &&\n+\t: >.git/repos/def/gitdir &&\n+\tgit prune --repos --verbose >actual &&\n+\ttest_i18ngrep \"Removing repos/def: invalid gitdir file\" actual &&\n+\t! test -d .git/repos/def &&\n+\t! test -d .git/repos\n+'\n+\n+test_expect_success 'prune directories with gitdir pointing to nowhere' '\n+\tmkdir -p .git/repos/def/abc &&\n+\t: >.git/repos/def/def &&\n+\techo \"$TRASH_DIRECTORY\"/nowhere >.git/repos/def/gitdir &&\n+\tgit prune --repos --verbose >actual &&\n+\ttest_i18ngrep \"Removing repos/def: gitdir file points to non-existent location\" actual &&\n+\t! test -d .git/repos/def &&\n+\t! test -d .git/repos\n+'\n+\n+test_expect_success 'not prune locked checkout' '\n+\ttest_when_finished rm -r .git/repos\n+\tmkdir -p .git/repos/ghi &&\n+\t: >.git/repos/ghi/locked &&\n+\tgit prune --repos &&\n+\ttest -d .git/repos/ghi\n+'\n+\n+test_expect_success 'not prune recent checkouts' '\n+\ttest_when_finished rm -r .git/repos\n+\tmkdir zz &&\n+\tmkdir -p .git/repos/jlm &&\n+\techo \"$TRASH_DIRECTORY\"/zz >.git/repos/jlm/gitdir &&\n+\tgit prune --repos --verbose &&\n+\ttest -d .git/repos/jlm\n+'\n+\n+test_done\n-- \n1.9.1.346.ga2b5940\n"},{"id":"245930","messageId":"1405227068-25506-27-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 26/31] gc: style change -- no SP before closing bracket","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:51:03Z","receivedAt":"2014-07-13T04:51:03Z","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 8d219d8..3bfb990 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -298,7 +298,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 \tgit_config(gc_config, NULL);\n-- \n1.9.1.346.ga2b5940\n"},{"id":"245931","messageId":"1405227068-25506-28-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 27/31] gc: factor out gc.pruneexpire parsing code","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:51:04Z","receivedAt":"2014-07-13T04:51:04Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/gc.c | 21 +++++++++++++--------\n 1 file changed, 13 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 3bfb990..e38c902 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 int git_config_date_string(const char **output,\n+\t\t\t\t  const char *var, const char *value)\n+{\n+\tif (value && strcmp(value, \"now\")) {\n+\t\tunsigned long now = approxidate(\"now\");\n+\t\tif (approxidate(value) >= now)\n+\t\t\treturn error(_(\"Invalid %s: '%s'\"), var, value);\n+\t}\n+\treturn git_config_string(output, var, value);\n+}\n+\n static int gc_config(const char *var, const char *value, void *cb)\n {\n \tif (!strcmp(var, \"gc.packrefs\")) {\n@@ -84,14 +95,8 @@ static int gc_config(const char *var, const char *value, void *cb)\n \t\tdetach_auto = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n-\tif (!strcmp(var, \"gc.pruneexpire\")) {\n-\t\tif (value && strcmp(value, \"now\")) {\n-\t\t\tunsigned long now = approxidate(\"now\");\n-\t\t\tif (approxidate(value) >= now)\n-\t\t\t\treturn error(_(\"Invalid %s: '%s'\"), var, value);\n-\t\t}\n-\t\treturn git_config_string(&prune_expire, var, value);\n-\t}\n+\tif (!strcmp(var, \"gc.pruneexpire\"))\n+\t\treturn git_config_date_string(&prune_expire, var, value);\n \treturn git_default_config(var, value, cb);\n }\n \n-- \n1.9.1.346.ga2b5940\n"},{"id":"245932","messageId":"1405227068-25506-29-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 28/31] gc: support prune --repos","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:51:05Z","receivedAt":"2014-07-13T04:51:05Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/config.txt |  7 +++++++\n builtin/gc.c             | 11 +++++++++++\n 2 files changed, 18 insertions(+)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 286e539..57999fa 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1211,6 +1211,13 @@ gc.pruneexpire::\n \t\"now\" may be used to disable this  grace period and always prune\n \tunreachable objects immediately.\n \n+gc.prunereposexpire::\n+\tWhen 'git gc' is run, it will call\n+\t'prune --repos --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/repos immediately.\n+\n gc.reflogexpire::\n gc.<pattern>.reflogexpire::\n \t'git reflog expire' removes reflog entries older than\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex e38c902..0c65808 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_repos_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_repos = ARGV_ARRAY_INIT;\n static struct argv_array rerere = ARGV_ARRAY_INIT;\n \n static char *pidfile;\n@@ -97,6 +99,8 @@ static int gc_config(const char *var, const char *value, void *cb)\n \t}\n \tif (!strcmp(var, \"gc.pruneexpire\"))\n \t\treturn git_config_date_string(&prune_expire, var, value);\n+\tif (!strcmp(var, \"gc.prunereposexpire\"))\n+\t\treturn git_config_date_string(&prune_repos_expire, var, value);\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -304,6 +308,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_repos, \"prune\", \"--repos\", \"--expire\", NULL);\n \targv_array_pushl(&rerere, \"rerere\", \"gc\", NULL);\n \n \tgit_config(gc_config, NULL);\n@@ -373,6 +378,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_repos_expire) {\n+\t\targv_array_push(&prune_repos, prune_repos_expire);\n+\t\tif (run_command_v_opt(prune_repos.argv, RUN_GIT_CMD))\n+\t\t\treturn error(FAILED_RUN, prune_repos.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-- \n1.9.1.346.ga2b5940\n"},{"id":"245933","messageId":"1405227068-25506-30-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 29/31] count-objects: report unused files in $GIT_DIR/repos/...","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:51:06Z","receivedAt":"2014-07-13T04:51:06Z","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 a7f70cb..d3a1620 100644\n--- a/builtin/count-objects.c\n+++ b/builtin/count-objects.c\n@@ -102,8 +102,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 \tmemcpy(path, objdir, len);\n \tif (len && objdir[len-1] != '/')\n \t\tpath[len++] = '/';\ndiff --git a/cache.h b/cache.h\nindex bf4d15e..12f0fc0 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -690,6 +690,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 e41d6b3..b5af137 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\", \"/repos\", \"/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-- \n1.9.1.346.ga2b5940\n"},{"id":"245934","messageId":"1405227068-25506-31-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 30/31] git_path(): keep \"info/sparse-checkout\" per work-tree","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-13T04:51:07Z","receivedAt":"2014-07-13T04:51:07Z","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 b5af137..b1dd2bc 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 2dabcef..da82aab 100755\n--- a/t/t0060-path-utils.sh\n+++ b/t/t0060-path-utils.sh\n@@ -269,6 +269,7 @@ 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/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-- \n1.9.1.346.ga2b5940\n"},{"id":"245935","messageId":"1405227068-25506-32-git-send-email-pclouds@gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v7 31/31] 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-07-13T04:51:08Z","receivedAt":"2014-07-13T04:51:08Z","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 5b93f49..c83f476 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1357,6 +1357,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 5b6c761..7426651 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 20e3dc7..b0d97a0 100755\n--- a/t/t2025-checkout-to.sh\n+++ b/t/t2025-checkout-to.sh\n@@ -62,4 +62,19 @@ test_expect_success 'not detach 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 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-- \n1.9.1.346.ga2b5940\n"},{"id":"245966","messageId":"CAPc5daVnE4P4f9vC0r=jrzxU9exs1f1R8hVJyzqTNrpzW5MVGg@mail.gmail.com","threadId":"37083","inReplyTo":"CAPig+cSfbs3NuyfbrUrJ5BGP9J7GvGCSda2E7ZjA2k4ZXotNRQ@mail.gmail.com","subject":"Re: [PATCH v6 28/32] gc: style change -- no SP before closing bracket","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-07-14T04:40:17Z","receivedAt":"2014-07-14T04:40:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"On Wed, Jul 9, 2014 at 2:47 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> On Wed, Jul 9, 2014 at 3:33 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>\n> Yet, there is a space after the opening '{'. So, this is now\n> inconsistently formatted as:\n>\n>     { foo, bar}\n>\n\nSo, if we drop the other hunk and keep only the one below,\nthe patch needs to be retitled with s/bracket/parenthesis/...\n\n>> @@ -298,7 +298,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n>>         argv_array_pushl(&pack_refs_cmd, \"pack-refs\", \"--all\", \"--prune\", NULL);\n>>         argv_array_pushl(&reflog, \"reflog\", \"expire\", \"--all\", NULL);\n>>         argv_array_pushl(&repack, \"repack\", \"-d\", \"-l\", NULL);\n>> -       argv_array_pushl(&prune, \"prune\", \"--expire\", NULL );\n>> +       argv_array_pushl(&prune, \"prune\", \"--expire\", NULL);\n>>         argv_array_pushl(&rerere, \"rerere\", \"gc\", NULL);\n>>\n>>         git_config(gc_config, NULL);\n>> --\n>> 1.9.1.346.ga2b5940\n"},{"id":"245967","messageId":"xmqqvbr0zgy1.fsf@gitster.dls.corp.google.com","threadId":"37083","inReplyTo":"1405227068-25506-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v7 00/31] Support multiple checkouts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-07-14T04:45:42Z","receivedAt":"2014-07-14T04:45:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n>  \tfd = open(git_path(\"repos/%s/gitdir\", id), O_RDONLY);\n> ...\n> -\twhile (path[len - 1] == '\\n' || path[len - 1] == '\\r')\n> +\twhile (len && (path[len - 1] == '\\n' || path[len - 1] == '\\r'))\n>  \t\tlen--;\n\nDo we anticipate (or even allow/endorse) that end users will edit\nthis file with a random editor?  And, to a lessor degree, the same\nquestion on the reading side.  Do we encourage users to directly\nread from this file to learn something about their repository?\n\nIf we are the only tool that writes into this file, and if we are\nthe only tool to be used to read (and use) the contents of this\nfile, I do not see the need to cater to LF vs CRLF line endings.\n"},{"id":"245984","messageId":"CACsJy8AAfZJtg1MBNmKJcYZ=VKaLV4NWJ0nETk=0uD9XK3+exg@mail.gmail.com","threadId":"37083","inReplyTo":"xmqqvbr0zgy1.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v7 00/31] Support multiple checkouts","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-14T11:06:22Z","receivedAt":"2014-07-14T11:06:22Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Mon, Jul 14, 2014 at 11:45 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>\n>>       fd = open(git_path(\"repos/%s/gitdir\", id), O_RDONLY);\n>> ...\n>> -     while (path[len - 1] == '\\n' || path[len - 1] == '\\r')\n>> +     while (len && (path[len - 1] == '\\n' || path[len - 1] == '\\r'))\n>>               len--;\n>\n> Do we anticipate (or even allow/endorse) that end users will edit\n> this file with a random editor?  And, to a lessor degree, the same\n> question on the reading side.  Do we encourage users to directly\n> read from this file to learn something about their repository?\n>\n> If we are the only tool that writes into this file, and if we are\n> the only tool to be used to read (and use) the contents of this\n> file, I do not see the need to cater to LF vs CRLF line endings.\n\nNo I don't expect users to go and change these files by themselves.\nBut once directory structure is documented, other tools might appear\nand scripts on Windows may just append CRLF by default.\n-- \nDuy\n"},{"id":"245992","messageId":"xmqqmwcbzx8u.fsf@gitster.dls.corp.google.com","threadId":"37083","inReplyTo":"CACsJy8AAfZJtg1MBNmKJcYZ=VKaLV4NWJ0nETk=0uD9XK3+exg@mail.gmail.com","subject":"Re: [PATCH v7 00/31] Support multiple checkouts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-07-14T17:05:53Z","receivedAt":"2014-07-14T17:05:53Z","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 Mon, Jul 14, 2014 at 11:45 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>>\n>>>       fd = open(git_path(\"repos/%s/gitdir\", id), O_RDONLY);\n>>> ...\n>>> -     while (path[len - 1] == '\\n' || path[len - 1] == '\\r')\n>>> +     while (len && (path[len - 1] == '\\n' || path[len - 1] == '\\r'))\n>>>               len--;\n>>\n>> Do we anticipate (or even allow/endorse) that end users will edit\n>> this file with a random editor?  And, to a lessor degree, the same\n>> question on the reading side.  Do we encourage users to directly\n>> read from this file to learn something about their repository?\n>>\n>> If we are the only tool that writes into this file, and if we are\n>> the only tool to be used to read (and use) the contents of this\n>> file, I do not see the need to cater to LF vs CRLF line endings.\n>\n> No I don't expect users to go and change these files by themselves.\n> But once directory structure is documented, other tools might appear\n> and scripts on Windows may just append CRLF by default.\n\nEssentially you are encouraging them to edit this file with a random\neditor, bypassing Git, then.\n"},{"id":"246214","messageId":"20140717041928.GA10056@wheezy.local","threadId":"37083","inReplyTo":"1405227068-25506-23-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v7 22/31] checkout: support checking out into a new working directory","fromName":"Max Kirillov","fromEmail":"max@max630.net","sentAt":"2014-07-17T04:19:28Z","receivedAt":"2014-07-17T04:19:28Z","isPatch":true,"sender":{"key":"max@max630.net","avatar":"https://avatars.githubusercontent.com/u/381560?v=4"},"body":"Hi.\n\nOn Sun, Jul 13, 2014 at 11:50:59AM +0700, Nguyễn Thái Ngọc Duy wrote:\n> +MULTIPLE CHECKOUT MODE\n> +-------------------------------\n\nThis generates incorrect html for me, making all section\nuntil next heading \"EXAMPLES\" into a preformatted text. If I\njustify the line of dashes to be the exactly same width as\nheader it starts generating correctly looking html.\n\nI'm not sure which software to refer to. I mostly use Debian\nstable. The installed version of asciidocs is 8.6.7-1. I\nsuppose the line really should be justified because for all\nother headings they are.\n\n-- \nMax\n"},{"id":"246215","messageId":"CAPc5daXiLqx6wkRFN7fs29Hu+zyZGL9kFed4jkwjJQB8hR0E-Q@mail.gmail.com","threadId":"37083","inReplyTo":"20140717041928.GA10056@wheezy.local","subject":"Re: [PATCH v7 22/31] checkout: support checking out into a new working directory","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-07-17T06:37:10Z","receivedAt":"2014-07-17T06:37:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"On Wed, Jul 16, 2014 at 9:19 PM, Max Kirillov <max@max630.net> wrote:\n> Hi.\n>\n> On Sun, Jul 13, 2014 at 11:50:59AM +0700, Nguyễn Thái Ngọc Duy wrote:\n>> +MULTIPLE CHECKOUT MODE\n>> +-------------------------------\n>\n> This generates incorrect html for me, making all section\n> until next heading \"EXAMPLES\" into a preformatted text. If I\n> justify the line of dashes to be the exactly same width as\n> header it starts generating correctly looking html.\n\nThanks; what you did is the correct AsciiDoc formatting.\n"},{"id":"246284","messageId":"CAPig+cRJjA3cby-gTUD_-amdd-LhMnQ6f1dyV4qQLPMoFXCm-A@mail.gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-23-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v7 22/31] checkout: support checking out into a new working directory","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-07-18T04:10:50Z","receivedAt":"2014-07-18T04:10:50Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, Jul 13, 2014 at 12:50 AM, Nguyễn Thái Ngọc Duy\n<pclouds@gmail.com> wrote:\n> \"git checkout --to\" sets up a new working directory with a .git file\n> pointing to $GIT_DIR/repos/<id>. It then executes \"git checkout\" again\n> on the new worktree with the same arguments except \"--to\" is taken\n> out. The second checkout execution, which is not contaminated with any\n> info from the current repository, will actually check out and\n> everything that normal \"git checkout\" does.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt\n> index 0f341fc..543d874 100644\n> --- a/Documentation/gitrepository-layout.txt\n> +++ b/Documentation/gitrepository-layout.txt\n> @@ -249,6 +249,13 @@ modules::\n>         directory is ignored if $GIT_COMMON_DIR is set and\n>         \"$GIT_COMMON_DIR/modules\" will be used instead.\n>\n> +repos::\n> +       Contains worktree specific information of linked\n> +       checkouts. Each subdirectory contains the worktree-related\n> +       part of a linked checkout. This directory is ignored\n\ns/ignored/ignored if/\n\n> +       $GIT_COMMON_DIR is set and \"$GIT_COMMON_DIR/repos\" will be\n> +       used instead.\n> +\n>  SEE ALSO\n>  --------\n>  linkgit:git-init[1],\n"},{"id":"246325","messageId":"87r41i352a.fsf@thomasrast.ch","threadId":"37083","inReplyTo":"1405227068-25506-26-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v7 25/31] prune: strategies for linked checkouts","fromName":"Thomas Rast","fromEmail":"tr@thomasrast.ch","sentAt":"2014-07-18T18:17:01Z","receivedAt":"2014-07-18T18:17:01Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Nguyễn Thái Ngọc Duy <pclouds@gmail.com> writes:\n\n> (alias R=$GIT_COMMON_DIR/repos/<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>    repos/<id> is to be pruned.\n>\n>  - If $R/locked exists, repos/<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>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  Documentation/git-prune.txt                |  3 +\n>  Documentation/gitrepository-layout.txt     | 19 ++++++\n>  builtin/checkout.c                         | 14 +++++\n>  builtin/prune.c                            | 99 ++++++++++++++++++++++++++++++\n>  setup.c                                    | 13 ++++\n>  t/t2026-prune-linked-checkouts.sh (new +x) | 84 +++++++++++++++++++++++++\n\nI get this from t2026.2 under valgrind:\n\n  ==21334== Conditional jump or move depends on uninitialised value(s)\n  ==21334==    at 0x46D49B: prune_repos_dir (prune.c:182)\n  ==21334==    by 0x46D8C0: cmd_prune (prune.c:252)\n  ==21334==    by 0x405C2F: run_builtin (git.c:351)\n  ==21334==    by 0x405E47: handle_builtin (git.c:530)\n  ==21334==    by 0x405F6B: run_argv (git.c:576)\n  ==21334==    by 0x40610B: main (git.c:663)\n  ==21334==  Uninitialised value was created by a stack allocation\n  ==21334==    at 0x46D3BB: prune_repos_dir (prune.c:169)\n  ==21334== \n  {\n     <insert_a_suppression_name_here>\n     Memcheck:Cond\n     fun:prune_repos_dir\n     fun:cmd_prune\n     fun:run_builtin\n     fun:handle_builtin\n     fun:run_argv\n     fun:main\n  }\n  not ok 2 - prune files inside $GIT_DIR/repos\n  #\n  #               mkdir .git/repos &&\n  #               : >.git/repos/abc &&\n  #               git prune --repos --verbose >actual &&\n  #               cat >expect <<EOF &&\n  #       Removing repos/abc: not a valid directory\n  #       EOF\n  #               test_i18ncmp expect actual &&\n  #               ! test -f .git/repos/abc &&\n  #               ! test -d .git/repos\n  #\n\nI think it's because of the early 'return 0' ...\n\n> +static int prune_repo_dir(const char *id, struct stat *st, struct strbuf *reason)\n> +{\n> +\tchar *path;\n> +\tint fd, len;\n> +\n> +\tif (!is_directory(git_path(\"repos/%s\", id))) {\n> +\t\tstrbuf_addf(reason, _(\"Removing repos/%s: not a valid directory\"), id);\n> +\t\treturn 1;\n> +\t}\n> +\tif (file_exists(git_path(\"repos/%s/locked\", id)))\n> +\t\treturn 0;\n\nin this line, before the stat() actually runs, which then in the\ncondition ...\n\n> +\tif (stat(git_path(\"repos/%s/gitdir\", id), st)) {\n> +\t\tst->st_mtime = expire;\n> +\t\tstrbuf_addf(reason, _(\"Removing repos/%s: gitdir file does not exist\"), id);\n> +\t\treturn 1;\n> +\t}\n[...]\n> +}\n> +\n> +static void prune_repos_dir(void)\n> +{\n[...]\n> +\tstruct stat st;\n[...]\n> +\t\tif (!prune_repo_dir(d->d_name, &st, &reason) ||\n> +\t\t    st.st_mtime > expire)\n\ncauses the second arm to be evaluated when st.st_mtime is not\ninitialized yet.  Can you look into this?\n\n-- \nThomas Rast\ntr@thomasrast.ch\n"},{"id":"246368","messageId":"CACsJy8A7bS+1Z1yrpS94WWg6Lr_F39vvuqbH7L-pQY7vK-f10w@mail.gmail.com","threadId":"37083","inReplyTo":"87r41i352a.fsf@thomasrast.ch","subject":"Re: [PATCH v7 25/31] prune: strategies for linked checkouts","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-19T12:52:30Z","receivedAt":"2014-07-19T12:52:30Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Sat, Jul 19, 2014 at 1:17 AM, Thomas Rast <tr@thomasrast.ch> wrote:\n> I get this from t2026.2 under valgrind:\n>\n>   ==21334== Conditional jump or move depends on uninitialised value(s)\n>   ==21334==    at 0x46D49B: prune_repos_dir (prune.c:182)\n>   ==21334==    by 0x46D8C0: cmd_prune (prune.c:252)\n>   ==21334==    by 0x405C2F: run_builtin (git.c:351)\n>   ==21334==    by 0x405E47: handle_builtin (git.c:530)\n>   ==21334==    by 0x405F6B: run_argv (git.c:576)\n>   ==21334==    by 0x40610B: main (git.c:663)\n>   ==21334==  Uninitialised value was created by a stack allocation\n>   ==21334==    at 0x46D3BB: prune_repos_dir (prune.c:169)\n\nWill fix. Thanks.\n-- \nDuy\n"},{"id":"246427","messageId":"CAPig+cRE0xOhQM4mtJQRX_gSQ3_OMr6ws87wU03TZ1eaUXui2g@mail.gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-24-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v7 23/31] checkout: clean up half-prepared directories in --to mode","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-07-20T23:55:46Z","receivedAt":"2014-07-20T23:55:46Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, Jul 13, 2014 at 12:51 AM, Nguyễn Thái Ngọc Duy\n<pclouds@gmail.com> wrote:\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  builtin/checkout.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++--\n>  1 file changed, 47 insertions(+), 2 deletions(-)\n>\n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> index 81adb74..3167fd2 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>         N_(\"git checkout [options] <branch>\"),\n> @@ -814,6 +815,35 @@ static int switch_branches(const struct checkout_opts *opts,\n>         return ret || writeout_error;\n>  }\n>\n> +static const char *junk_work_tree;\n> +static const char *junk_git_dir;\n> +static int is_junk;\n> +static pid_t junk_pid;\n> +\n> +static void remove_junk(void)\n> +{\n> +       struct strbuf sb = STRBUF_INIT;\n> +       if (!is_junk || getpid() != junk_pid)\n> +               return;\n> +       if (junk_git_dir) {\n> +               strbuf_addstr(&sb, junk_git_dir);\n> +               remove_dir_recursively(&sb, 0);\n> +               strbuf_reset(&sb);\n> +       }\n> +       if (junk_work_tree) {\n> +               strbuf_addstr(&sb, junk_work_tree);\n> +               remove_dir_recursively(&sb, 0);\n> +       }\n> +       strbuf_release(&sb);\n> +}\n> +\n> +static void remove_junk_on_signal(int signo)\n> +{\n> +       remove_junk();\n> +       sigchain_pop(signo);\n> +       raise(signo);\n> +}\n> +\n>  static int prepare_linked_checkout(const struct checkout_opts *opts,\n>                                    struct branch_info *new)\n>  {\n> @@ -822,7 +852,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n>         const char *path = opts->new_worktree, *name;\n>         struct stat st;\n>         struct child_process cp;\n> -       int counter = 0, len;\n> +       int counter = 0, len, ret;\n>\n>         if (!new->commit)\n>                 die(_(\"no branch specified\"));\n> @@ -848,13 +878,21 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n>                 strbuf_addf(&sb_repo, \"%d\", counter);\n>         }\n>         name = strrchr(sb_repo.buf, '/') + 1;\n> +\n> +       junk_pid = getpid();\n> +       atexit(remove_junk);\n> +       sigchain_push_common(remove_junk_on_signal);\n> +\n>         if (mkdir(sb_repo.buf, 0777))\n>                 die_errno(_(\"could not create directory of '%s'\"), sb_repo.buf);\n> +       junk_git_dir = sb_repo.buf;\n\nI've managed to convince myself that, although junk_git_dir becomes a\ndangling pointer by the end of prepare_linked_checkout(), it should\nnever afterward be accessed. Perhaps it would make sense to make this\neasier to follow by clearing junk_git_dir when is_junk is cleared?\n\n> +       is_junk = 1;\n>\n>         strbuf_addf(&sb_git, \"%s/.git\", path);\n>         if (safe_create_leading_directories_const(sb_git.buf))\n>                 die_errno(_(\"could not create leading directories of '%s'\"),\n>                           sb_git.buf);\n> +       junk_work_tree = path;\n>\n>         write_file(sb_git.buf, 1, \"gitdir: %s/repos/%s\\n\",\n>                    real_path(get_git_common_dir()), name);\n> @@ -879,7 +917,14 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n>         memset(&cp, 0, sizeof(cp));\n>         cp.git_cmd = 1;\n>         cp.argv = opts->saved_argv;\n> -       return run_command(&cp);\n> +       ret = run_command(&cp);\n> +       if (!ret)\n> +               is_junk = 0;\n\nHere: perhaps also set is_junk_dir to NULL since it otherwise is about\nto become a dangling pointer.\n\n> +       strbuf_release(&sb);\n> +       strbuf_release(&sb_repo);\n> +       strbuf_release(&sb_git);\n> +       return ret;\n> +\n>  }\n>\n>  static int git_checkout_config(const char *var, const char *value, void *cb)\n> --\n> 1.9.1.346.ga2b5940\n"},{"id":"246430","messageId":"CAPig+cTgPgk=gJEn1kOxn_gPzqqQM-2tFfuZTxkwuZF-2X-pMw@mail.gmail.com","threadId":"37083","inReplyTo":"CAPig+cRE0xOhQM4mtJQRX_gSQ3_OMr6ws87wU03TZ1eaUXui2g@mail.gmail.com","subject":"Re: [PATCH v7 23/31] checkout: clean up half-prepared directories in --to mode","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-07-21T03:34:32Z","receivedAt":"2014-07-21T03:34:32Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, Jul 20, 2014 at 7:55 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> On Sun, Jul 13, 2014 at 12:51 AM, Nguyễn Thái Ngọc Duy\n> <pclouds@gmail.com> wrote:\n>> +\n>> +       junk_pid = getpid();\n>> +       atexit(remove_junk);\n>> +       sigchain_push_common(remove_junk_on_signal);\n>> +\n>>         if (mkdir(sb_repo.buf, 0777))\n>>                 die_errno(_(\"could not create directory of '%s'\"), sb_repo.buf);\n>> +       junk_git_dir = sb_repo.buf;\n>\n> I've managed to convince myself that, although junk_git_dir becomes a\n> dangling pointer by the end of prepare_linked_checkout(), it should\n> never afterward be accessed. Perhaps it would make sense to make this\n> easier to follow by clearing junk_git_dir when is_junk is cleared?\n>\n>> +       is_junk = 1;\n>>\n>>         strbuf_addf(&sb_git, \"%s/.git\", path);\n>>         if (safe_create_leading_directories_const(sb_git.buf))\n>>                 die_errno(_(\"could not create leading directories of '%s'\"),\n>>                           sb_git.buf);\n>> +       junk_work_tree = path;\n>>\n>>         write_file(sb_git.buf, 1, \"gitdir: %s/repos/%s\\n\",\n>>                    real_path(get_git_common_dir()), name);\n>> @@ -879,7 +917,14 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n>>         memset(&cp, 0, sizeof(cp));\n>>         cp.git_cmd = 1;\n>>         cp.argv = opts->saved_argv;\n>> -       return run_command(&cp);\n>> +       ret = run_command(&cp);\n>> +       if (!ret)\n>> +               is_junk = 0;\n>\n> Here: perhaps also set is_junk_dir to NULL since it otherwise is about\n> to become a dangling pointer.\n\nMy bad: s/is_junk_dir/junk_git_dir/\n"},{"id":"246541","messageId":"CAPig+cQPqzvQ7wVSJf_h3UB75eqsg378Zw=YVN-exHVpVFs-mg@mail.gmail.com","threadId":"37083","inReplyTo":"1405227068-25506-12-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v7 11/31] $GIT_COMMON_DIR: a new environment variable","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-07-23T05:21:58Z","receivedAt":"2014-07-23T05:21:58Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, Jul 13, 2014 at 12:50 AM, Nguyễn Thái Ngọc Duy\n<pclouds@gmail.com> wrote:\n> This variable is intended to support multiple working directories\n> attached to a repository. Such a repository may have a main working\n> directory, created by either \"git init\" or \"git clone\" and one or more\n> linked working directories. These working directories and the main\n> repository share the same repository directory.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt\n> index 17d2ea6..7629e38 100644\n> --- a/Documentation/gitrepository-layout.txt\n> +++ b/Documentation/gitrepository-layout.txt\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> +       Repository specific configuration file. This file is ignored\n\ns/ignored/ignored if/\n\n> +       $GIT_COMMON_DIR is set and \"$GIT_COMMON_DIR/config\" will be\n> +       used instead.\n> +\n>  branches::\n>         A slightly deprecated way to store shorthands to be used\n>         to specify a URL to 'git fetch', 'git pull' and 'git push'.\n> diff --git a/environment.c b/environment.c\n> index fee12a6..6b74f68 100644\n> --- a/environment.c\n> +++ b/environment.c\n> @@ -149,9 +149,18 @@ static void setup_git_env(void)\n>                 git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;\n>         gitfile = read_gitfile(git_dir);\n>         git_dir = xstrdup(gitfile ? gitfile : git_dir);\n> -       git_object_dir = git_path_from_env(DB_ENVIRONMENT, \"objects\", &git_db_env);\n> -       git_index_file = git_path_from_env(INDEX_ENVIRONMENT, \"index\", &git_index_env);\n> -       git_graft_file = git_path_from_env(GRAFT_ENVIRONMENT, \"info/grafts\", &git_graft_env);\n> +       git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);\n> +       if (git_common_dir) {\n> +               git_common_dir_env = 1;\n> +               git_common_dir = xstrdup(git_common_dir);\n> +       } else\n> +               git_common_dir = git_dir;\n> +       git_object_dir = git_path_from_env(DB_ENVIRONMENT, git_common_dir,\n> +                                          \"objects\", &git_db_env);\n> +       git_index_file = git_path_from_env(INDEX_ENVIRONMENT, git_dir,\n> +                                          \"index\", &git_index_env);\n> +       git_graft_file = git_path_from_env(GRAFT_ENVIRONMENT, git_dir,\n> +                                          \"info/grafts\", &git_graft_env);\n\nReplacement refs come from git_common_dir, but grafts come from\ngit_dir. Is the inconsistency intentional?\n\n>         if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))\n>                 check_replace_refs = 0;\n>         namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));\n> diff --git a/path.c b/path.c\n> index 3deb80c..8a6586c 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>                 buf->buf[newlen] = '/';\n>  }\n>\n> +static const char *common_list[] = {\n> +       \"/branches\", \"/hooks\", \"/info\", \"/logs\", \"/lost-found\", \"/modules\",\n> +       \"/objects\", \"/refs\", \"/remotes\", \"/rr-cache\", \"/svn\",\n> +       \"config\", \"gc.pid\", \"packed-refs\", \"shallow\",\n> +       NULL\n> +};\n> +\n> +static void update_common_dir(struct strbuf *buf, int git_dir_len)\n> +{\n> +       char *base = buf->buf + git_dir_len;\n> +       const char **p;\n> +\n> +       if (is_dir_file(base, \"logs\", \"HEAD\"))\n> +               return; /* keep this in $GIT_DIR */\n> +       for (p = common_list; *p; p++) {\n> +               const char *path = *p;\n> +               int is_dir = 0;\n> +               if (*path == '/') {\n> +                       path++;\n> +                       is_dir = 1;\n> +               }\n> +               if (is_dir && dir_prefix(base, path)) {\n> +                       replace_dir(buf, git_dir_len, get_git_common_dir());\n> +                       return;\n> +               }\n> +               if (!is_dir && !strcmp(base, path)) {\n> +                       replace_dir(buf, git_dir_len, get_git_common_dir());\n> +                       return;\n> +               }\n\nThe bodies of these two conditionals are identical. Would it make\nsense to combine them (or does a later patch take advantage of the\ndistinction)?\n\n    if ((is_dir && dir_prefix(base, path)) ||\n       (!is_dir && !strcmp(base, path))) {\n        replace_dir(buf, git_dir_len, get_git_common_dir());\n        return;\n    }\n\n> +       }\n> +}\n> +\n>  static void adjust_git_path(struct strbuf *buf, int git_dir_len)\n>  {\n>         const char *base = buf->buf + git_dir_len;\n"},{"id":"246544","messageId":"CACsJy8CmKZj8soQ09h=6LYf94OVjtydbb2HXBszTPCfta0_Khg@mail.gmail.com","threadId":"37083","inReplyTo":"CAPig+cRE0xOhQM4mtJQRX_gSQ3_OMr6ws87wU03TZ1eaUXui2g@mail.gmail.com","subject":"Re: [PATCH v7 23/31] checkout: clean up half-prepared directories in --to mode","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-23T08:02:03Z","receivedAt":"2014-07-23T08:02:03Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Mon, Jul 21, 2014 at 6:55 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>> @@ -848,13 +878,21 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n>>                 strbuf_addf(&sb_repo, \"%d\", counter);\n>>         }\n>>         name = strrchr(sb_repo.buf, '/') + 1;\n>> +\n>> +       junk_pid = getpid();\n>> +       atexit(remove_junk);\n>> +       sigchain_push_common(remove_junk_on_signal);\n>> +\n>>         if (mkdir(sb_repo.buf, 0777))\n>>                 die_errno(_(\"could not create directory of '%s'\"), sb_repo.buf);\n>> +       junk_git_dir = sb_repo.buf;\n>\n> I've managed to convince myself that, although junk_git_dir becomes a\n> dangling pointer by the end of prepare_linked_checkout(), it should\n> never afterward be accessed. Perhaps it would make sense to make this\n> easier to follow by clearing junk_git_dir when is_junk is cleared?\n\nYou're right. And the dangling junk_git_dir can be accessed if \"ret\"\nbelow is non-zero. Will strdup() both junk_*_dir and free them in \"if\n(!ret)\" block. Thanks for catching.\n\n>> @@ -879,7 +917,14 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n>>         memset(&cp, 0, sizeof(cp));\n>>         cp.git_cmd = 1;\n>>         cp.argv = opts->saved_argv;\n>> -       return run_command(&cp);\n>> +       ret = run_command(&cp);\n>> +       if (!ret)\n>> +               is_junk = 0;\n>\n> Here: perhaps also set is_junk_dir to NULL since it otherwise is about\n> to become a dangling pointer.\n>\n>> +       strbuf_release(&sb);\n>> +       strbuf_release(&sb_repo);\n>> +       strbuf_release(&sb_git);\n>> +       return ret;\n-- \nDuy\n"}]}