{"thread":{"id":"35908","subject":"[PATCH v3 00/25] Support multiple checkouts","startedAt":"2014-02-18T13:39:49Z","lastAt":"2014-07-07T17:32:45Z","messageCount":169,"participants":["Nguyễn Thái Ngọc Duy","Junio C Hamano","Eric Sunshine","Duy Nguyen","Philip Oakley","Torsten Bögershausen","Max Kirillov","Dennis Kaarsemaker"],"isPatch":true,"patchVersion":3,"patchTotal":25},"messages":[{"id":"234965","messageId":"1392730814-19656-1-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":null,"subject":"[PATCH v3 00/25] Support multiple checkouts","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:39:49Z","receivedAt":"2014-02-18T13:39:49Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"In short you can attach multiple worktrees to the same git repository\nwith \"git checkout --to <somewhere>\". This is basically what\ngit-new-workdir is for. Previous discussion here\n\nhttp://thread.gmane.org/gmane.comp.version-control.git/239194/focus=239581\n\nCompared to last time:\n\n- .git file format remains unchanged. It was a stupid idea to tie\n  $GIT_COMMON_DIR pointer to .git file because you will have to pass\n  that info another way if you don't go through .git file. Now it's\n  stored in $GIT_DIR/commondir\n\n- Last time, checking out an already checked out branch will detach\n  the previous checkout. Junio wanted to error out for less user\n  confusion. I go with a (good, imo) compromise in this reroll: the\n  new checkout detaches itself in this case, hinting where the branch\n  is truly checked out so the user can go there and do things\n\nNguyễn Thái Ngọc Duy (25):\n  path.c: make get_pathname() return strbuf instead of static buffer\n  Convert git_snpath() to 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  Make git_path() 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  Add new environment variable $GIT_COMMON_DIR\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: support prune --repos\n\n Documentation/config.txt               |   9 +-\n Documentation/git-checkout.txt         |  34 +++++\n Documentation/git-prune.txt            |   3 +\n Documentation/git-rev-parse.txt        |   8 +\n Documentation/git.txt                  |   8 +\n Documentation/gitrepository-layout.txt |  26 ++++\n builtin/branch.c                       |   4 +-\n builtin/checkout.c                     | 272 +++++++++++++++++++++++++++++++--\n builtin/commit.c                       |   2 +-\n builtin/gc.c                           |  17 +++\n builtin/init-db.c                      |   7 +-\n builtin/prune.c                        |  75 +++++++++\n builtin/reflog.c                       |   2 +-\n builtin/rev-parse.c                    |  11 ++\n cache.h                                |  10 +-\n daemon.c                               |  11 +-\n environment.c                          |  24 ++-\n fast-import.c                          |   5 +-\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 path.c                                 | 201 +++++++++++++++---------\n refs.c                                 |  66 +++++---\n refs.h                                 |   2 +-\n setup.c                                | 117 ++++++++++----\n strbuf.c                               |   8 +\n strbuf.h                               |   5 +\n submodule.c                            |   9 +-\n t/t0060-path-utils.sh                  |  34 +++++\n t/t1501-worktree.sh                    |  76 +++++++++\n t/t1510-repo-setup.sh                  |   1 +\n t/t2025-checkout-to.sh (new +x)        |  48 ++++++\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 41 files changed, 976 insertions(+), 215 deletions(-)\n create mode 100755 t/t2025-checkout-to.sh\n\n-- \n1.8.5.2.240.g8478abd\n"},{"id":"234966","messageId":"1392730814-19656-2-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 01/25] 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-02-18T13:39:50Z","receivedAt":"2014-02-18T13:39:50Z","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\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n path.c | 119 +++++++++++++++++++++++++++--------------------------------------\n 1 file changed, 50 insertions(+), 69 deletions(-)\n\ndiff --git a/path.c b/path.c\nindex 24594c4..1a1b784 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()\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,69 @@ 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 = get_pathname();\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+\treturn buf;\n }\n \n char *git_pathdup(const char *fmt, ...)\n {\n-\tchar path[PATH_MAX], *ret;\n+\tstruct strbuf *path = get_pathname();\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 +153,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.8.5.2.240.g8478abd\n"},{"id":"234968","messageId":"1392730814-19656-3-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 02/25] Convert git_snpath() to strbuf_git_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:39:51Z","receivedAt":"2014-02-18T13:39:51Z","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 take a strbuf\nbuffer from get_pathname() because vsnpath() needs that. But that\nmakes it awkward because git_snpath() receives a pre-allocated buffer\nfrom outside and has to copy data back.\n\nRename it to strbuf_git_path() and make it receive strbuf directly.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/checkout.c | 22 +++++++++---------\n cache.h            |  4 ++--\n path.c             |  8 +------\n refs.c             | 66 +++++++++++++++++++++++++++++++++++-------------------\n refs.h             |  2 +-\n 5 files changed, 57 insertions(+), 45 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 5df3837..0570e41 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -585,18 +585,20 @@ 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\tstruct strbuf log_file = STRBUF_INIT;\n \t\t\t\tchar *ref_name = mkpath(\"refs/heads/%s\", opts->new_orphan_branch);\n+\t\t\t\tint ret;\n \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\n@@ -651,14 +653,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\t\t\tnew->name);\n \t\t\t}\n \t\t}\n-\t\tif (old->path && old->name) {\n-\t\t\tchar log_file[PATH_MAX], ref_file[PATH_MAX];\n-\n-\t\t\tgit_snpath(log_file, sizeof(log_file), \"logs/%s\", old->path);\n-\t\t\tgit_snpath(ref_file, sizeof(ref_file), \"%s\", old->path);\n-\t\t\tif (!file_exists(ref_file) && file_exists(log_file))\n-\t\t\t\tremove_path(log_file);\n-\t\t}\n+\t\tif (old->path && old->name &&\n+\t\t    !file_exists(git_path(\"%s\", old->path)) &&\n+\t\t     file_exists(git_path(\"logs/%s\", old->path)))\n+\t\t\tremove_path(git_path(\"logs/%s\", old->path));\n \t}\n \tremove_branch_state();\n \tstrbuf_release(&msg);\ndiff --git a/cache.h b/cache.h\nindex dc040fb..8d3697e 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -646,8 +646,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 1a1b784..fe3f844 100644\n--- a/path.c\n+++ b/path.c\n@@ -70,18 +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 = get_pathname();\n \tva_list args;\n \tva_start(args, fmt);\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-\treturn buf;\n }\n \n char *git_pathdup(const char *fmt, ...)\ndiff --git a/refs.c b/refs.c\nindex 89228e2..434bd5e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1325,10 +1325,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@@ -1337,15 +1339,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@@ -1359,10 +1363,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@@ -1373,7 +1378,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@@ -1389,7 +1394,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@@ -1402,12 +1407,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@@ -1424,9 +1430,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@@ -1436,10 +1443,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@@ -2717,17 +2729,19 @@ 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 *sb_logfile)\n {\n \tint logfd, oflags = O_APPEND | O_WRONLY;\n+\tconst char *logfile;\n \n-\tgit_snpath(logfile, bufsize, \"logs/%s\", refname);\n+\tstrbuf_git_path(sb_logfile, \"logs/%s\", refname);\n+\tlogfile = sb_logfile->buf;\n \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(sb_logfile->buf) < 0)\n \t\t\treturn error(\"unable to create directory for %s\",\n \t\t\t\t     logfile);\n \t\toflags |= O_CREAT;\n@@ -2762,20 +2776,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@@ -2788,9 +2804,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 87a1a79..783033a 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -166,7 +166,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 *ref_name, char *logfile, int bufsize);\n+int log_ref_setup(const char *ref_name, 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.8.5.2.240.g8478abd\n"},{"id":"234967","messageId":"1392730814-19656-4-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 03/25] path.c: rename vsnpath() to do_git_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:39:52Z","receivedAt":"2014-02-18T13:39:52Z","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 fe3f844..635ec41 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 = get_pathname();\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 @@ 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.8.5.2.240.g8478abd\n"},{"id":"234969","messageId":"1392730814-19656-5-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 04/25] 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-02-18T13:39:53Z","receivedAt":"2014-02-18T13:39:53Z","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 635ec41..e088c40 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+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 = get_pathname();\n@@ -109,16 +119,6 @@ char *mkpath(const char *fmt, ...)\n \treturn cleanup_path(pathname->buf);\n }\n \n-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.8.5.2.240.g8478abd\n"},{"id":"234970","messageId":"1392730814-19656-6-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 05/25] Make git_path() aware of file relocation in $GIT_DIR","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:39:54Z","receivedAt":"2014-02-18T13:39:54Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"We allow the user to relocate certain paths out of $GIT_DIR via\nenvironment variables, e.g. GIT_OBJECT_DIRECTORY, GIT_INDEX_FILE and\nGIT_GRAFT_FILE. All callers are not supposed to use git_path() or\ngit_pathdup() to get those paths. Instead they must use\nget_object_directory(), get_index_file() and get_graft_file()\nrespectively. This is inconvenient and could be missed in review\n(there's git_path(\"objects/info/alternates\") somewhere in\nsha1_file.c).\n\nThis patch makes git_path() and git_pathdup() understand those\nenvironment variables. So if you set GIT_OBJECT_DIRECTORY to /foo/bar,\ngit_path(\"objects/abc\") should return /tmp/bar/abc. The same is done\nfor the two remaining env variables.\n\n\"git rev-parse --git-path\" is the wrapper for script use.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-rev-parse.txt |  5 +++++\n builtin/rev-parse.c             |  7 +++++++\n cache.h                         |  1 +\n environment.c                   |  9 ++++++--\n path.c                          | 46 +++++++++++++++++++++++++++++++++++++++++\n t/t0060-path-utils.sh           | 19 +++++++++++++++++\n 6 files changed, 85 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt\nindex 0d2cdcd..33e4e90 100644\n--- a/Documentation/git-rev-parse.txt\n+++ b/Documentation/git-rev-parse.txt\n@@ -232,6 +232,11 @@ 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.\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 aaeb611..e50bc65 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -518,6 +518,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 8d3697e..6c08e4a 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -585,6 +585,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 4a3437d..f513479 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -82,6 +82,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@@ -137,15 +138,19 @@ static void setup_git_env(void)\n \tif (!git_object_dir) {\n \t\tgit_object_dir = xmalloc(strlen(git_dir) + 9);\n \t\tsprintf(git_object_dir, \"%s/objects\", git_dir);\n-\t}\n+\t} else\n+\t\tgit_db_env = 1;\n \tgit_index_file = getenv(INDEX_ENVIRONMENT);\n \tif (!git_index_file) {\n \t\tgit_index_file = xmalloc(strlen(git_dir) + 7);\n \t\tsprintf(git_index_file, \"%s/index\", git_dir);\n-\t}\n+\t} else\n+\t\tgit_index_env = 1;\n \tgit_graft_file = getenv(GRAFT_ENVIRONMENT);\n \tif (!git_graft_file)\n \t\tgit_graft_file = git_pathdup(\"info/grafts\");\n+\telse\n+\t\tgit_graft_env = 1;\n \tif (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))\n \t\tread_replace_refs = 0;\n \tnamespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));\ndiff --git a/path.c b/path.c\nindex e088c40..0f8c3dc 100644\n--- a/path.c\n+++ b/path.c\n@@ -60,13 +60,59 @@ 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+\tint gitdir_len;\n \tstrbuf_addstr(buf, 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 07c10c8..1d29901 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@@ -223,4 +231,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.8.5.2.240.g8478abd\n"},{"id":"234982","messageId":"1392730814-19656-7-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 06/25] *.sh: respect $GIT_INDEX_FILE","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:39:55Z","receivedAt":"2014-02-18T13:39: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 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 0a5aa2c..c9dc9ba 100755\n--- a/git-pull.sh\n+++ b/git-pull.sh\n@@ -218,7 +218,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 f0a94ab..ae7d16e 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.8.5.2.240.g8478abd\n"},{"id":"234973","messageId":"1392730814-19656-8-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 07/25] reflog: avoid constructing .lock path with git_path","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:39:56Z","receivedAt":"2014-02-18T13:39:56Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"git_path() soon understands the path given to it. Some paths \"abc\" may\nbecome \"def\" while other \"ghi\" may become \"ijk\". We don't want\ngit_path() to interfere with .lock path construction. Concatenate\n\".lock\" after the path has been resolved by git_path() so if \"abc\"\nbecomes \"def\", we'll have \"def.lock\", not \"ijk\".\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 852cff6..ccf2cf6 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -372,7 +372,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,\n \tif (!file_exists(log_file))\n \t\tgoto finish;\n \tif (!cmd->dry_run) {\n-\t\tnewlog_path = git_pathdup(\"logs/%s.lock\", ref);\n+\t\tnewlog_path = mkpathdup(\"%s.lock\", log_file);\n \t\tcb.newlog = fopen(newlog_path, \"w\");\n \t}\n \n-- \n1.8.5.2.240.g8478abd\n"},{"id":"234972","messageId":"1392730814-19656-9-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 08/25] 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-02-18T13:39:57Z","receivedAt":"2014-02-18T13:39:57Z","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 4fd18a3..08a1e78 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -3125,12 +3125,9 @@ static void parse_progress(void)\n \n static char* make_fast_import_path(const char *path)\n {\n-\tstruct strbuf abs_path = STRBUF_INIT;\n-\n \tif (!relative_marks_paths || is_absolute_path(path))\n \t\treturn xstrdup(path);\n-\tstrbuf_addf(&abs_path, \"%s/info/fast-import/%s\", get_git_dir(), path);\n-\treturn strbuf_detach(&abs_path, NULL);\n+\treturn xstrdup(git_path(\"info/fast-import/%s\", path));\n }\n \n static void option_import_marks(const char *marks,\n-- \n1.8.5.2.240.g8478abd\n"},{"id":"234971","messageId":"1392730814-19656-10-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 09/25] commit: use SEQ_DIR instead of hardcoding \"sequencer\"","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:39:58Z","receivedAt":"2014-02-18T13:39:58Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/commit.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 3767478..ee3ac10 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -155,7 +155,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.8.5.2.240.g8478abd\n"},{"id":"234975","messageId":"1392730814-19656-11-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:39:59Z","receivedAt":"2014-02-18T13:39:59Z","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\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\nNote that logs/refs/.tmp-renamed-log is used to prepare new reflog\nentry and it's supposed to be on the same filesystem as the target\nreflog file. This is not guaranteed true for logs/HEAD when it's\nmapped to repos/xx/logs/HEAD because the user can put \"repos\"\ndirectory on different filesystem. Don't mess with .git unless you\nknow what you're doing.\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 cache.h               |  4 +++-\n environment.c         | 19 +++++++++++++++----\n path.c                | 28 ++++++++++++++++++++++++++++\n t/t0060-path-utils.sh | 15 +++++++++++++++\n 5 files changed, 69 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex 02bbc08..2c4a055 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -773,6 +773,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. This variable has lower precedence than\n+\tother path variables such as GIT_INDEX_FILE,\n+\tGIT_OBJECT_DIRECTORY...\n+\n Git Commits\n ~~~~~~~~~~~\n 'GIT_AUTHOR_NAME'::\ndiff --git a/cache.h b/cache.h\nindex 6c08e4a..51ade32 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -347,6 +347,7 @@ static inline enum object_type object_type(unsigned int mode)\n \n /* Double-check local_repo_env below if you add to this list. */\n #define GIT_DIR_ENVIRONMENT \"GIT_DIR\"\n+#define GIT_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@@ -400,6 +401,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@@ -585,7 +587,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 f513479..c998120 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -80,9 +80,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@@ -134,10 +134,16 @@ 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+\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 = 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\tgit_object_dir = xmalloc(strlen(git_common_dir) + 9);\n+\t\tsprintf(git_object_dir, \"%s/objects\", git_common_dir);\n \t} else\n \t\tgit_db_env = 1;\n \tgit_index_file = getenv(INDEX_ENVIRONMENT);\n@@ -173,6 +179,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 0f8c3dc..2d757dc 100644\n--- a/path.c\n+++ b/path.c\n@@ -90,6 +90,32 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)\n \t\tbuf->buf[newlen] = '/';\n }\n \n+static void update_common_dir(struct strbuf *buf, int git_dir_len)\n+{\n+\tconst char *common_dir_list[] = {\n+\t\t\"branches\", \"hooks\", \"info\", \"logs\", \"lost-found\", \"modules\",\n+\t\t\"objects\", \"refs\", \"remotes\", \"rr-cache\", \"svn\",\n+\t\tNULL\n+\t};\n+\tconst char *common_top_file_list[] = {\n+\t\t\"config\", \"gc.pid\", \"packed-refs\", \"shallow\", NULL\n+\t};\n+\tchar *base = buf->buf + git_dir_len;\n+\tconst char **p;\n+\tif (is_dir_file(base, \"logs\", \"HEAD\"))\n+\t\treturn;\t/* keep this in $GIT_DIR */\n+\tfor (p = common_dir_list; *p; p++)\n+\t\tif (dir_prefix(base, *p)) {\n+\t\t\treplace_dir(buf, git_dir_len, get_git_common_dir());\n+\t\t\treturn;\n+\t\t}\n+\tfor (p = common_top_file_list; *p; p++)\n+\t\tif (!strcmp(base, *p)) {\n+\t\t\treplace_dir(buf, git_dir_len, get_git_common_dir());\n+\t\t\treturn;\n+\t\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 +127,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 1d29901..f9a77e4 100755\n--- a/t/t0060-path-utils.sh\n+++ b/t/t0060-path-utils.sh\n@@ -241,5 +241,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.8.5.2.240.g8478abd\n"},{"id":"234974","messageId":"1392730814-19656-12-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 11/25] 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-02-18T13:40:00Z","receivedAt":"2014-02-18T13:40:00Z","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 fffa3c7..fec9430 100644\n--- a/git-sh-setup.sh\n+++ b/git-sh-setup.sh\n@@ -343,7 +343,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.8.5.2.240.g8478abd\n"},{"id":"234977","messageId":"1392730814-19656-13-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 12/25] *.sh: avoid hardcoding $GIT_DIR/hooks/...","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:40:01Z","receivedAt":"2014-02-18T13:40:01Z","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 bbea430..dfa0618 100755\n--- a/git-am.sh\n+++ b/git-am.sh\n@@ -803,10 +803,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@@ -880,9 +880,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@@ -908,18 +909,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 43c19e0..d741b04 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -632,9 +632,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 e7d96de..68f5d09 100644\n--- a/git-rebase--merge.sh\n+++ b/git-rebase--merge.sh\n@@ -93,10 +93,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 8a3efa2..1cf8dba 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -195,9 +195,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..28b843b 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..51aa244 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.8.5.2.240.g8478abd\n"},{"id":"234976","messageId":"1392730814-19656-14-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 13/25] git-stash: avoid hardcoding $GIT_DIR/logs/....","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:40:02Z","receivedAt":"2014-02-18T13:40: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 git-stash.sh | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/git-stash.sh b/git-stash.sh\nindex ae7d16e..12d9b37 100755\n--- a/git-stash.sh\n+++ b/git-stash.sh\n@@ -183,7 +183,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@@ -258,7 +258,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.8.5.2.240.g8478abd\n"},{"id":"234978","messageId":"1392730814-19656-15-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 14/25] setup.c: convert is_git_directory() to use strbuf","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:40:03Z","receivedAt":"2014-02-18T13:40: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 setup.c  | 35 +++++++++++++++++++----------------\n strbuf.h |  4 ++++\n 2 files changed, 23 insertions(+), 16 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 6c3f85f..999225b 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -184,31 +184,34 @@ 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_addstr_at(&path, len, \"/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_addstr_at(&path, len, \"/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)\ndiff --git a/strbuf.h b/strbuf.h\nindex 73e80ce..aec9fdb 100644\n--- a/strbuf.h\n+++ b/strbuf.h\n@@ -116,6 +116,10 @@ extern void strbuf_add(struct strbuf *, const void *, size_t);\n static inline void strbuf_addstr(struct strbuf *sb, const char *s) {\n \tstrbuf_add(sb, s, strlen(s));\n }\n+static inline void strbuf_addstr_at(struct strbuf *sb, size_t len, const char *s) {\n+\tstrbuf_setlen(sb, len);\n+\tstrbuf_add(sb, s, strlen(s));\n+}\n static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) {\n \tstrbuf_grow(sb, sb2->len);\n \tstrbuf_add(sb, sb2->buf, sb2->len);\n-- \n1.8.5.2.240.g8478abd\n"},{"id":"234979","messageId":"1392730814-19656-16-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 15/25] setup.c: detect $GIT_COMMON_DIR in is_git_directory()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:40:04Z","receivedAt":"2014-02-18T13:40:04Z","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 |  4 ++++\n setup.c                                | 38 ++++++++++++++++++++++++++++------\n strbuf.c                               |  8 +++++++\n strbuf.h                               |  1 +\n 4 files changed, 45 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt\nindex aa03882..9bfe0f1 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -211,6 +211,10 @@ shallow::\n \tand maintained by shallow clone mechanism.  See `--depth`\n \toption to linkgit:git-clone[1] and linkgit:git-fetch[1].\n \n+commondir::\n+\tIf this file exists, $GIT_COMMON_DIR will be set to the path\n+\tspecified in this file if it is not set.\n+\n modules::\n \tContains the git-repositories of the submodules.\n \ndiff --git a/setup.c b/setup.c\nindex 999225b..4e5711c 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -170,6 +170,30 @@ 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\tstrbuf_chomp(&data);\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@@ -188,14 +212,20 @@ int is_git_directory(const char *suspect)\n \tint ret = 0;\n \tsize_t len;\n \n-\tstrbuf_addstr(&path, suspect);\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 \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_addstr(&path, \"/objects\");\n+\t\tstrbuf_addstr_at(&path, len, \"/objects\");\n \t\tif (access(path.buf, X_OK))\n \t\t\tgoto done;\n \t}\n@@ -204,10 +234,6 @@ int is_git_directory(const char *suspect)\n \tif (access(path.buf, X_OK))\n \t\tgoto done;\n \n-\tstrbuf_addstr_at(&path, len, \"/HEAD\");\n-\tif (validate_headref(path.buf))\n-\t\tgoto done;\n-\n \tret = 1;\n done:\n \tstrbuf_release(&path);\ndiff --git a/strbuf.c b/strbuf.c\nindex 83caf4a..e17c358 100644\n--- a/strbuf.c\n+++ b/strbuf.c\n@@ -588,3 +588,11 @@ int fprintf_ln(FILE *fp, const char *fmt, ...)\n \t\treturn -1;\n \treturn ret + 1;\n }\n+\n+void strbuf_chomp(struct strbuf *sb)\n+{\n+\twhile (sb->len && (sb->buf[sb->len - 1] == '\\n' ||\n+\t\t\t   sb->buf[sb->len - 1] == '\\r'))\n+\t\tsb->len--;\n+\tsb->buf[sb->len] = '\\0';\n+}\ndiff --git a/strbuf.h b/strbuf.h\nindex aec9fdb..cd9578f 100644\n--- a/strbuf.h\n+++ b/strbuf.h\n@@ -109,6 +109,7 @@ extern void strbuf_remove(struct strbuf *, size_t pos, size_t len);\n /* splice pos..pos+len with given data */\n extern void strbuf_splice(struct strbuf *, size_t pos, size_t len,\n                           const void *, size_t);\n+extern void strbuf_chomp(struct strbuf *sb);\n \n extern void strbuf_add_commented_lines(struct strbuf *out, const char *buf, size_t size);\n \n-- \n1.8.5.2.240.g8478abd\n"},{"id":"234981","messageId":"1392730814-19656-17-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 16/25] setup.c: convert check_repository_format_gently to use strbuf","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:40:05Z","receivedAt":"2014-02-18T13:40: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 | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 4e5711c..282fdc9 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -281,7 +281,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@@ -292,7 +294,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@@ -302,9 +305,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.8.5.2.240.g8478abd\n"},{"id":"234980","messageId":"1392730814-19656-18-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 17/25] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:40:06Z","receivedAt":"2014-02-18T13:40:06Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n setup.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 282fdc9..e56ec11 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -285,6 +285,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@@ -294,8 +298,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.8.5.2.240.g8478abd\n"},{"id":"234988","messageId":"1392730814-19656-19-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 18/25] setup.c: support multi-checkout repo setup","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:40:07Z","receivedAt":"2014-02-18T13:40:07Z","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_DIR/commondir presents. This is\nbecause \"commondir\" repos are intended for separate/linked checkouts\nand pointing them back to a fixed core.worktree just does not make\nsense.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/config.txt        |  3 +-\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(+), 15 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 5f4d793..cbf4d97 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -381,7 +381,8 @@ false), while all other repositories are assumed to be bare (bare\n \n core.worktree::\n \tSet the path to the root of the working tree.\n-\tThis can be overridden by the GIT_WORK_TREE environment\n+\tThis can be overridden by the GIT_WORK_TREE\n+\tor GIT_COMMON_DIR environment\n \tvariable and the '--work-tree' command line option.\n \tThe value can be an absolute path or relative to the path to\n \tthe .git directory, which is either specified by --git-dir\ndiff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt\nindex 33e4e90..8e6ad32 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 e50bc65..c7057ce 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -744,6 +744,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 = resolve_gitdir(argv[i+1]);\n \t\t\t\tif (!gitdir)\ndiff --git a/cache.h b/cache.h\nindex 51ade32..98b5dd3 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -407,6 +407,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 c998120..0999fc1 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -126,6 +126,7 @@ static char *expand_namespace(const char *raw_namespace)\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@@ -134,12 +135,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 = getenv(DB_ENVIRONMENT);\n \tif (!git_object_dir) {\n \t\tgit_object_dir = xmalloc(strlen(git_common_dir) + 9);\ndiff --git a/setup.c b/setup.c\nindex e56ec11..d4ac878 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -170,14 +170,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@@ -189,10 +190,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@@ -279,13 +282,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@@ -298,7 +314,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@@ -770,11 +786,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..d8bdaf4 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+\tunset 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\techo \"$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\techo \"$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\techo \"$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\techo \"$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 cf2ee78..c211ef5 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.8.5.2.240.g8478abd\n"},{"id":"234985","messageId":"1392730814-19656-20-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 19/25] wrapper.c: wrapper to open a file, fprintf then close","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:40:08Z","receivedAt":"2014-02-18T13:40:08Z","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 98b5dd3..99b86d9 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1239,6 +1239,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 0cc5636..5ced50d 100644\n--- a/wrapper.c\n+++ b/wrapper.c\n@@ -455,3 +455,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+\tint fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);\n+\tva_list params;\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\terrno = err;\n+\t\tstrbuf_release(&sb);\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.8.5.2.240.g8478abd\n"},{"id":"234986","messageId":"1392730814-19656-21-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 20/25] use new wrapper write_file() for simple file writing","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:40:09Z","receivedAt":"2014-02-18T13:40:09Z","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 b4d7716..3eebdbc 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 c7c76bb..081e512 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 503e039..b880d30 100644\n--- a/daemon.c\n+++ b/daemon.c\n@@ -1122,15 +1122,6 @@ static void daemonize(void)\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@@ -1339,7 +1330,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 613857e..fe5748d 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1135,16 +1135,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 ca7bb44..2df8a15 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.8.5.2.240.g8478abd\n"},{"id":"234983","messageId":"1392730814-19656-22-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 21/25] checkout: support checking out into a new working directory","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:40:10Z","receivedAt":"2014-02-18T13:40:10Z","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/gitrepository-layout.txt |  3 ++\n builtin/checkout.c                     | 93 +++++++++++++++++++++++++++++++++-\n t/t2025-checkout-to.sh (new +x)        | 39 ++++++++++++++\n 4 files changed, 167 insertions(+), 2 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/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt\nindex 9bfe0f1..495a937 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -218,6 +218,9 @@ commondir::\n modules::\n \tContains the git-repositories of the submodules.\n \n+repos::\n+\tContains worktree specific information of linked checkouts.\n+\n SEE ALSO\n --------\n linkgit:git-init[1],\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 0570e41..2b856a6 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@@ -486,7 +493,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;\n+\tstruct stat st;\n+\tconst char *name;\n+\tstruct child_process cp;\n+\tint counter = 0, len;\n+\n+\tif (!new->commit)\n+\t\tdie(_(\"no branch specified\"));\n+\n+\tlen = strlen(path);\n+\tif (!len || is_dir_sep(path[len - 1]))\n+\t\tdie(_(\"'--to' argument '%s' cannot end with a slash\"), path);\n+\n+\tfor (name = path + len - 1; name > path; name--)\n+\t\tif (is_dir_sep(*name)) {\n+\t\t\tname++;\n+\t\t\tbreak;\n+\t\t}\n+\tstrbuf_addstr(&sb_repo, git_path(\"repos/%s\", name));\n+\tlen = sb_repo.len;\n+\tif (safe_create_leading_directories_const(sb_repo.buf))\n+\t\tdie_errno(_(\"could not create leading directories of '%s'\"),\n+\t\t\t  sb_repo.buf);\n+\twhile (!stat(sb_repo.buf, &st)) {\n+\t\tcounter++;\n+\t\tstrbuf_setlen(&sb_repo, len);\n+\t\tstrbuf_addf(&sb_repo, \"%d\", counter);\n+\t}\n+\tname = sb_repo.buf + len - strlen(name);\n+\tif (mkdir(sb_repo.buf, 0777))\n+\t\tdie_errno(_(\"could not create directory of '%s'\"), sb_repo.buf);\n+\n+\tstrbuf_addf(&sb_git, \"%s/.git\", path);\n+\tif (safe_create_leading_directories_const(sb_git.buf))\n+\t\tdie_errno(_(\"could not create leading directories of '%s'\"),\n+\t\t\t  sb_git.buf);\n+\n+\twrite_file(sb_git.buf, 1, \"gitdir: %s/repos/%s\\n\",\n+\t\t   real_path(get_git_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/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh\nnew file mode 100755\nindex 0000000..76eae4a\n--- /dev/null\n+++ b/t/t2025-checkout-to.sh\n@@ -0,0 +1,39 @@\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 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.8.5.2.240.g8478abd\n"},{"id":"234984","messageId":"1392730814-19656-23-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 22/25] checkout: clean up half-prepared directories in --to mode","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:40:11Z","receivedAt":"2014-02-18T13:40:11Z","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 2b856a6..f961604 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\tstrbuf_reset(&sb);\n+\t}\n+}\n+\n+static void remove_junk_on_signal(int signo)\n+{\n+\tremove_junk();\n+\tsigchain_pop(signo);\n+\traise(signo);\n+}\n+\n static int prepare_linked_checkout(const struct checkout_opts *opts,\n \t\t\t\t   struct branch_info *new)\n {\n@@ -823,7 +853,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n \tstruct stat st;\n \tconst char *name;\n \tstruct child_process cp;\n-\tint counter = 0, len;\n+\tint counter = 0, len, ret;\n \n \tif (!new->commit)\n \t\tdie(_(\"no branch specified\"));\n@@ -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 = sb_repo.buf + len - strlen(name);\n+\n+\tjunk_pid = getpid();\n+\tatexit(remove_junk);\n+\tsigchain_push_common(remove_junk_on_signal);\n+\n \tif (mkdir(sb_repo.buf, 0777))\n \t\tdie_errno(_(\"could not create directory of '%s'\"), sb_repo.buf);\n+\tjunk_git_dir = sb_repo.buf;\n+\tis_junk = 1;\n \n \tstrbuf_addf(&sb_git, \"%s/.git\", path);\n \tif (safe_create_leading_directories_const(sb_git.buf))\n \t\tdie_errno(_(\"could not create leading directories of '%s'\"),\n \t\t\t  sb_git.buf);\n+\tjunk_work_tree = path;\n \n \twrite_file(sb_git.buf, 1, \"gitdir: %s/repos/%s\\n\",\n \t\t   real_path(get_git_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.8.5.2.240.g8478abd\n"},{"id":"234989","messageId":"1392730814-19656-24-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 23/25] checkout: detach if the branch is already checked out elsewhere","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:40:12Z","receivedAt":"2014-02-18T13:40:12Z","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\nstrictens the rule a bit more: if the branch is checked out (either in\n$GIT_COMMON_DIR/HEAD or any $GIT_DIR/repos/.../HEAD) then it's\ndetached 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     | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++\n t/t2025-checkout-to.sh | 15 ++++++++--\n 2 files changed, 90 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex f961604..7b86f2b 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -433,6 +433,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 alrady\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+\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\treturn;\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,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 76eae4a..f6a5c47 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\t! 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@@ -36,4 +37,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.8.5.2.240.g8478abd\n"},{"id":"234990","messageId":"1392730814-19656-25-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 24/25] prune: strategies for linked checkouts","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:40:13Z","receivedAt":"2014-02-18T13:40:13Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"alias REPO=$GIT_COMMON_DIR/repos/<id>\n\n - linked checkouts are supposed to update mtime of $REPO/gitdir\n\n - linked checkouts are supposed to keep its location in $REPO/gitdir up to\n   date\n\n - \"git checkout --to\" is supposed to create $REPO/locked if the new\n   repo is on a different partition than the shared one.\n\n - \"git checkout --to\" is supposed to make hardlink named $REPO/link\n   pointing to the .git file\n\nThe pruning rules are:\n\n - if $REPO/locked exists, repos/<id> is not supposed to be pruned.\n\n - if $REPO/locked exists and $REPO/gitdir's mtimer is older than a\n   really long limit, warn about old unused repo.\n\n - if $REPO/link exists and its link count is greated than 1, the repo\n   is kept.\n\n - if $REPO/gitdir's mtime is older than a limit, and it points to\n   nowhere, repos/<id> is to be pruned.\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                     | 36 +++++++++++++++-\n builtin/prune.c                        | 75 ++++++++++++++++++++++++++++++++++\n setup.c                                | 13 ++++++\n 5 files changed, 145 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt\nindex 058ac0d..7babf11 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 495a937..784d0a5 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -221,6 +221,25 @@ modules::\n repos::\n \tContains worktree specific information of linked checkouts.\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 7b86f2b..c501e9c 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -854,6 +854,17 @@ static void remove_junk_on_signal(int signo)\n \traise(signo);\n }\n \n+static dev_t get_device_or_die(const char *path)\n+{\n+\tstruct stat buf;\n+\tif (stat(path, &buf))\n+\t\tdie_errno(\"failed to stat '%s'\", path);\n+\t/* Ah Windows! Make different drives different \"partitions\" */\n+\tif (buf.st_dev == 0 && has_dos_drive_prefix(\"c:\\\\\"))\n+\t\tbuf.st_dev = toupper(real_path(path)[0]);\n+\treturn buf.st_dev;\n+}\n+\n static int prepare_linked_checkout(const struct checkout_opts *opts,\n \t\t\t\t   struct branch_info *new)\n {\n@@ -863,7 +874,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n \tstruct stat st;\n \tconst char *name;\n \tstruct child_process cp;\n-\tint counter = 0, len, ret;\n+\tint counter = 0, len, keep_locked = 0, ret;\n \n \tif (!new->commit)\n \t\tdie(_(\"no branch specified\"));\n@@ -898,12 +909,18 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n \tjunk_git_dir = sb_repo.buf;\n \tis_junk = 1;\n \n+\tstrbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n+\twrite_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_dir()), name);\n \t/*\n@@ -912,12 +929,24 @@ 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 \tstrbuf_addf(&sb, \"%s/commondir\", sb_repo.buf);\n \twrite_file(sb.buf, 1, \"../..\\n\");\n \n+\tif (get_device_or_die(path) != get_device_or_die(get_git_dir())) {\n+\t\tstrbuf_reset(&sb);\n+\t\tstrbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n+\t\twrite_file(sb.buf, 1, \"located on a different file system\\n\");\n+\t\tkeep_locked = 1;\n+\t} else {\n+\t\tstrbuf_reset(&sb);\n+\t\tstrbuf_addf(&sb, \"%s/link\", sb_repo.buf);\n+\t\tlink(sb_git.buf, sb.buf); /* it's ok to fail */\n+\t}\n+\n \tif (!opts->quiet)\n \t\tfprintf_ln(stderr, _(\"Enter %s (identifier %s)\"), path, name);\n \n@@ -930,6 +959,11 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n \tret = run_command(&cp);\n \tif (!ret)\n \t\tis_junk = 0;\n+\tif (!keep_locked) {\n+\t\tstrbuf_reset(&sb);\n+\t\tstrbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n+\t\tunlink_or_warn(sb.buf);\n+\t}\n \tstrbuf_release(&sb);\n \tstrbuf_release(&sb_repo);\n \tstrbuf_release(&sb_git);\ndiff --git a/builtin/prune.c b/builtin/prune.c\nindex de43b26..87a6d21 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++;\n@@ -166,6 +240,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix)\n \t\t\tdie(\"unrecognized argument: %s\", name);\n \t}\n \n+\n \tif (show_progress == -1)\n \t\tshow_progress = isatty(2);\n \tif (show_progress)\ndiff --git a/setup.c b/setup.c\nindex d4ac878..2772cdd 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -329,6 +329,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@@ -377,6 +388,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.8.5.2.240.g8478abd\n"},{"id":"234987","messageId":"1392730814-19656-26-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 25/25] gc: support prune --repos","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-18T13:40:14Z","receivedAt":"2014-02-18T13:40: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 |  6 ++++++\n builtin/gc.c             | 17 +++++++++++++++++\n 2 files changed, 23 insertions(+)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex cbf4d97..eec2d05 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1182,6 +1182,12 @@ 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 '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 this  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 c19545d..fbeba86 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -30,11 +30,13 @@ static int aggressive_window = 250;\n static int gc_auto_threshold = 6700;\n static int gc_auto_pack_limit = 50;\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@@ -81,6 +83,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@@ -274,6 +284,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@@ -334,6 +345,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.8.5.2.240.g8478abd\n"},{"id":"235066","messageId":"xmqqppmi97s3.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"1392730814-19656-15-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 14/25] setup.c: convert is_git_directory() to use strbuf","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-19T20:17:32Z","receivedAt":"2014-02-19T20:17:32Z","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> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n\n(Only minor nits first during this round of review)\n\n> diff --git a/strbuf.h b/strbuf.h\n> index 73e80ce..aec9fdb 100644\n> --- a/strbuf.h\n> +++ b/strbuf.h\n> @@ -116,6 +116,10 @@ extern void strbuf_add(struct strbuf *, const void *, size_t);\n>  static inline void strbuf_addstr(struct strbuf *sb, const char *s) {\n>  \tstrbuf_add(sb, s, strlen(s));\n>  }\n> +static inline void strbuf_addstr_at(struct strbuf *sb, size_t len, const char *s) {\n\nPlease have the opening \"{\" on its own line.\n\nSurrounding existing functions are all offenders, but that is not an\nexcuse to make it worse (cleaning them up will need to be done in a\nseparate patch).\n\n> +\tstrbuf_setlen(sb, len);\n> +\tstrbuf_add(sb, s, strlen(s));\n\nI am not sure addstr_at() gives us a good abstraction, or at least\nthe name conveys what it does well not to confuse readers.\n\nAt first after only seeing its name, I would have expected that it\nwould splice the given string into an existing strbuf at the\nlocation, not chopping the existing strbuf at the location and\nappending.\n\n> +}\n>  static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) {\n>  \tstrbuf_grow(sb, sb2->len);\n>  \tstrbuf_add(sb, sb2->buf, sb2->len);\n"},{"id":"235067","messageId":"xmqqlhx697oi.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"1392730814-19656-19-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 18/25] setup.c: support multi-checkout repo setup","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-19T20:19:41Z","receivedAt":"2014-02-19T20:19:41Z","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(Only nitpicks during this round of review).\n\n> diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh\n> index 8f36aa9..d8bdaf4 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> +\tunset GIT_DIR GIT_CONFIG GIT_WORK_TREE\n\nAre these known to be set always when we get to this point?\nOtherwise please use sane_unset.\n"},{"id":"235068","messageId":"xmqqha7u97mz.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"1392730814-19656-24-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 23/25] checkout: detach if the branch is already checked out elsewhere","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-19T20:20:36Z","receivedAt":"2014-02-19T20:20:36Z","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> The normal rule is anything outside refs/heads/ is detached. This\n> strictens the rule a bit more: if the branch is checked out (either in\n> $GIT_COMMON_DIR/HEAD or any $GIT_DIR/repos/.../HEAD) then it's\n> detached as well.\n>\n> A hint is given so the user knows where to go and do something there\n> if they still want to checkout undetached here.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n\n(Only nitpicks during this round of review).\n\n> diff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh\n> index 76eae4a..f6a5c47 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\t! git symbolic-ref HEAD &&\n\ntest_must_fail?\n"},{"id":"235069","messageId":"xmqqd2ii97jo.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"1392730814-19656-26-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 25/25] gc: support prune --repos","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-19T20:22:35Z","receivedAt":"2014-02-19T20:22:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"(Only nitpicks during this round of review).\n\n> @@ -274,6 +284,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\nNo SP before closing \")\".\n"},{"id":"235070","messageId":"xmqq7g8q9790.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"1392730814-19656-2-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 01/25] path.c: make get_pathname() return strbuf instead of static buffer","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-19T20:28:59Z","receivedAt":"2014-02-19T20:28:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n(Only nitpicks during this round of review).\n\n> -static char *get_pathname(void)\n> +static struct strbuf *get_pathname()\n\nstatic struct strbuf *get_pathname(void)\n"},{"id":"235071","messageId":"xmqq38je9739.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"1392730814-19656-25-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 24/25] prune: strategies for linked checkouts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-19T20:32:26Z","receivedAt":"2014-02-19T20:32:26Z","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(Only nitpicks during this round of review).\n\n> +\tif (get_device_or_die(path) != get_device_or_die(get_git_dir())) {\n> +\t\tstrbuf_reset(&sb);\n> +\t\tstrbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n> +\t\twrite_file(sb.buf, 1, \"located on a different file system\\n\");\n> +\t\tkeep_locked = 1;\n> +\t} else {\n> +\t\tstrbuf_reset(&sb);\n> +\t\tstrbuf_addf(&sb, \"%s/link\", sb_repo.buf);\n> +\t\tlink(sb_git.buf, sb.buf); /* it's ok to fail */\n\nIf so, perhaps tell that to the code by saying something like\n\n\t\t(void) link(...);\n\n?\n\nBut why is it OK to fail in the first place?  If we couldn't link,\ndon't you want to fall back to the lock codepath?  After all, the\n\"are we on the same device?\" check is to cope with the case where\nwe cannot link and that alternate codepath is supposed to be\nprepared to handle the \"ah, we cannot link\" case correctly, no?\n"},{"id":"235073","messageId":"xmqqy5167s18.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"xmqq38je9739.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v3 24/25] prune: strategies for linked checkouts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-19T20:42:59Z","receivedAt":"2014-02-19T20:42:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>\n> (Only nitpicks during this round of review).\n>\n>> +\tif (get_device_or_die(path) != get_device_or_die(get_git_dir())) {\n>> +\t\tstrbuf_reset(&sb);\n>> +\t\tstrbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n>> +\t\twrite_file(sb.buf, 1, \"located on a different file system\\n\");\n>> +\t\tkeep_locked = 1;\n>> +\t} else {\n>> +\t\tstrbuf_reset(&sb);\n>> +\t\tstrbuf_addf(&sb, \"%s/link\", sb_repo.buf);\n>> +\t\tlink(sb_git.buf, sb.buf); /* it's ok to fail */\n>\n> If so, perhaps tell that to the code by saying something like\n>\n> \t\t(void) link(...);\n>\n> ?\n>\n> But why is it OK to fail in the first place?  If we couldn't link,\n> don't you want to fall back to the lock codepath?  After all, the\n> \"are we on the same device?\" check is to cope with the case where\n> we cannot link and that alternate codepath is supposed to be\n> prepared to handle the \"ah, we cannot link\" case correctly, no?\n\nIn other words, couldn't that part more like this?  That is, we\noptimisiticly try the link(2) first and if it fails for whatever\nreason fall back to whatever magic the lock codepath implements.\n\n+\tstrbuf_reset(&sb);\n+\tstrbuf_addf(&sb, \"%s/link\", sb_repo.buf);\n+\tif (link(sb_git.buf, sb.buf) < 0) {\n+\t\tstrbuf_reset(&sb);\n+\t\tstrbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n+\t\twrite_file(sb.buf, 1, \"located on a different file system\\n\");\n+\t\tkeep_locked = 1;\n+\t}\n+\n"},{"id":"235074","messageId":"xmqqtxbu7rcf.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 00/25] Support multiple checkouts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-19T20:57:52Z","receivedAt":"2014-02-19T20:57:52Z","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> In short you can attach multiple worktrees to the same git repository\n> with \"git checkout --to <somewhere>\". This is basically what\n> git-new-workdir is for.\n\nThis is exciting.\n\nI'll be pushing this out on 'pu' (with trivial fix-ups squashed in\nand/or queued on-top) for people to play with.\n\nI'm still slowly chewing thru these patches, though. Haven't had a\nchance to read them carefully yet.\n\nThanks.\n"},{"id":"235076","messageId":"CAPig+cRUBZ9SE8DHymnFGK5EtZpa_U4bpaB87vSrKwHNfLQ9+w@mail.gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-24-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 23/25] checkout: detach if the branch is already checked out elsewhere","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-02-19T21:52:58Z","receivedAt":"2014-02-19T21:52:58Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Feb 18, 2014 at 8:40 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> The normal rule is anything outside refs/heads/ is detached. This\n> strictens the rule a bit more: if the branch is checked out (either in\n\ns/strictens/increases strictness of/\n\n> $GIT_COMMON_DIR/HEAD or any $GIT_DIR/repos/.../HEAD) then it's\n> detached as well.\n>\n> A hint is given so the user knows where to go and do something there\n> if they still want to checkout undetached here.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> index f961604..7b86f2b 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n> @@ -433,6 +433,11 @@ struct branch_info {\n>         const char *name; /* The short name used */\n>         const char *path; /* The full name of a real branch */\n>         struct commit *commit; /* The named commit */\n> +       /*\n> +        * if not null the branch is detached because it's alrady\n\ns/alrady/already/\n\n> +        * checked out in this checkout\n> +        */\n> +       char *checkout;\n>  };\n>\n> +static void check_linked_checkouts(struct branch_info *new)\n> +{\n> +       struct strbuf path = STRBUF_INIT;\n> +       DIR *dir;\n> +       struct dirent *d;\n> +\n> +       strbuf_addf(&path, \"%s/repos\", get_git_common_dir());\n> +       if ((dir = opendir(path.buf)) == NULL)\n\nstrbuf_release(&path);\n\n> +               return;\n> +\n> +       strbuf_reset(&path);\n> +       strbuf_addf(&path, \"%s/HEAD\", get_git_common_dir());\n> +       /*\n> +        * $GIT_COMMON_DIR/HEAD is practically outside\n> +        * $GIT_DIR so resolve_ref_unsafe() won't work (it\n> +        * uses git_path). Parse the ref ourselves.\n> +        */\n> +       if (check_linked_checkout(new, \"\", path.buf)) {\n> +               strbuf_release(&path);\n> +               closedir(dir);\n> +               return;\n> +       }\n> +\n> +       while ((d = readdir(dir)) != NULL) {\n> +               if (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n> +                       continue;\n> +               strbuf_reset(&path);\n> +               strbuf_addf(&path, \"%s/repos/%s/HEAD\",\n> +                           get_git_common_dir(), d->d_name);\n> +               if (check_linked_checkout(new, d->d_name, path.buf))\n> +                       break;\n> +       }\n> +       strbuf_release(&path);\n> +       closedir(dir);\n> +}\n"},{"id":"235077","messageId":"CAPig+cQgphSBA6iAYqLJ7RnEwSzBA1dApcLWAydZEXarQRcTKQ@mail.gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-25-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 24/25] prune: strategies for linked checkouts","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-02-19T22:08:02Z","receivedAt":"2014-02-19T22:08:02Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Feb 18, 2014 at 8:40 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> The pruning rules are:\n>\n>  - if $REPO/locked exists, repos/<id> is not supposed to be pruned.\n>\n>  - if $REPO/locked exists and $REPO/gitdir's mtimer is older than a\n\ns/mtimer/mtime/\n\n>    really long limit, warn about old unused repo.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> index 7b86f2b..c501e9c 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n> @@ -854,6 +854,17 @@ static void remove_junk_on_signal(int signo)\n>         raise(signo);\n>  }\n>\n> +static dev_t get_device_or_die(const char *path)\n> +{\n> +       struct stat buf;\n> +       if (stat(path, &buf))\n> +               die_errno(\"failed to stat '%s'\", path);\n> +       /* Ah Windows! Make different drives different \"partitions\" */\n> +       if (buf.st_dev == 0 && has_dos_drive_prefix(\"c:\\\\\"))\n> +               buf.st_dev = toupper(real_path(path)[0]);\n\nThis invocation of has_dos_drive_prefix() with hardcoded \"c:\\\\\" at\nfirst looks like an error until the reader realizes that it's an\n#ifdef-less check if the platforms is Windows. Would it make more\nsense to encapsulate this anomaly and abstract it away by fixing\ncompat/mingw.c:do_lstat() to instead set 'st_dev' automatically like\nyou do here? In particular, this line in mingw.c:\n\n    buf->st_dev = buf->st_rdev = 0; /* not used by Git */\n\n> +       return buf.st_dev;\n> +}\n"},{"id":"235078","messageId":"CAPig+cQFw+8RE_zPntCkqKyYGK0rXVFQ=OxrBS6MeFEpw5-cEA@mail.gmail.com","threadId":"35908","inReplyTo":"CAPig+cQgphSBA6iAYqLJ7RnEwSzBA1dApcLWAydZEXarQRcTKQ@mail.gmail.com","subject":"Re: [PATCH v3 24/25] prune: strategies for linked checkouts","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-02-19T22:53:12Z","receivedAt":"2014-02-19T22:53:12Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Feb 19, 2014 at 5:08 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> On Tue, Feb 18, 2014 at 8:40 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>> +static dev_t get_device_or_die(const char *path)\n>> +{\n>> +       struct stat buf;\n>> +       if (stat(path, &buf))\n>> +               die_errno(\"failed to stat '%s'\", path);\n>> +       /* Ah Windows! Make different drives different \"partitions\" */\n>> +       if (buf.st_dev == 0 && has_dos_drive_prefix(\"c:\\\\\"))\n>> +               buf.st_dev = toupper(real_path(path)[0]);\n>\n> This invocation of has_dos_drive_prefix() with hardcoded \"c:\\\\\" at\n> first looks like an error until the reader realizes that it's an\n> #ifdef-less check if the platforms is Windows. Would it make more\n> sense to encapsulate this anomaly and abstract it away by fixing\n> compat/mingw.c:do_lstat() to instead set 'st_dev' automatically like\n> you do here? In particular, this line in mingw.c:\n>\n>     buf->st_dev = buf->st_rdev = 0; /* not used by Git */\n>\n>> +       return buf.st_dev;\n\nOr, if doing this in do_lstat() is too expensive for normal stat()\noperations (which is likely), then a simple #ifdef might be easier to\nread; or abstract it into a get_device() function which Windows/MinGW\ncan override, doing buf.st_dev = toupper(real_path(...)), thus also\nmaking the code easier to understand.\n"},{"id":"235080","messageId":"xmqqk3cq7kgm.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"1392730814-19656-2-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 01/25] path.c: make get_pathname() return strbuf instead of static buffer","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-19T23:26:33Z","receivedAt":"2014-02-19T23:26:33Z","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> We've been avoiding PATH_MAX whenever possible. This patch makes\n> get_pathname() return a strbuf and updates the callers to take\n> advantage of this. The code is simplified as we no longer need to\n> worry about buffer overflow.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  path.c | 119 +++++++++++++++++++++++++++--------------------------------------\n>  1 file changed, 50 insertions(+), 69 deletions(-)\n\nNice.\n\n>  char *git_pathdup(const char *fmt, ...)\n>  {\n> -\tchar path[PATH_MAX], *ret;\n> +\tstruct strbuf *path = get_pathname();\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\nThis feels somewhat wrong.\n\nThis function is for callers who are willing to take ownership of\nthe path buffer and promise to free the returned buffer when they\nare done, because you are returning strbuf_detach()'ed piece of\nmemory, giving the ownership away.\n\nThe whole point of using get_pathname() is to allow callers not to\ncare about allocation issues on the paths they scribble on during\ntheir short-and-simple codepaths that do not have too many uses of\nsimilar temporary path buffers.  Why borrow from that round-robin\npool (which may now cause some codepaths to overflow the number of\nsuch active temporary path buffers---have they been all audited)?\n\nIs there a reason not to do just an equivalent of\n\n    #define git_pathdup mkpathdup\n\nand be done with it?  Am I missing something?\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\nOn the other hand, this one does seem correct.\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\nSo does this.\n\nThanks.\n"},{"id":"235082","messageId":"xmqqeh2y7jff.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"1392730814-19656-3-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 02/25] Convert git_snpath() to strbuf_git_path()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-19T23:48:52Z","receivedAt":"2014-02-19T23:48:52Z","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> @@ -651,14 +653,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n>  \t\t\t\t\tnew->name);\n>  \t\t\t}\n>  \t\t}\n> -\t\tif (old->path && old->name) {\n> -\t\t\tchar log_file[PATH_MAX], ref_file[PATH_MAX];\n> -\n> -\t\t\tgit_snpath(log_file, sizeof(log_file), \"logs/%s\", old->path);\n> -\t\t\tgit_snpath(ref_file, sizeof(ref_file), \"%s\", old->path);\n> -\t\t\tif (!file_exists(ref_file) && file_exists(log_file))\n> -\t\t\t\tremove_path(log_file);\n> -\t\t}\n> +\t\tif (old->path && old->name &&\n> +\t\t    !file_exists(git_path(\"%s\", old->path)) &&\n> +\t\t     file_exists(git_path(\"logs/%s\", old->path)))\n> +\t\t\tremove_path(git_path(\"logs/%s\", old->path));\n\nHmph.  Is this conversion safe?\n\nThis adds three uses of the round-robin path buffer; if a caller of\nthis function used two or more path buffers obtained from\nget_pathname() and expected their contents to remain stable across\nthe call to this, it will silently break.\n"},{"id":"235083","messageId":"CACsJy8BJ=OKzGDFwWQF_k_Gp9XeNwf7pe7c6_ebduJXN=xeOxw@mail.gmail.com","threadId":"35908","inReplyTo":"xmqqeh2y7jff.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v3 02/25] Convert git_snpath() to strbuf_git_path()","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-19T23:54:39Z","receivedAt":"2014-02-19T23:54:39Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Feb 20, 2014 at 6:48 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>\n>> @@ -651,14 +653,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n>>                                       new->name);\n>>                       }\n>>               }\n>> -             if (old->path && old->name) {\n>> -                     char log_file[PATH_MAX], ref_file[PATH_MAX];\n>> -\n>> -                     git_snpath(log_file, sizeof(log_file), \"logs/%s\", old->path);\n>> -                     git_snpath(ref_file, sizeof(ref_file), \"%s\", old->path);\n>> -                     if (!file_exists(ref_file) && file_exists(log_file))\n>> -                             remove_path(log_file);\n>> -             }\n>> +             if (old->path && old->name &&\n>> +                 !file_exists(git_path(\"%s\", old->path)) &&\n>> +                  file_exists(git_path(\"logs/%s\", old->path)))\n>> +                     remove_path(git_path(\"logs/%s\", old->path));\n>\n> Hmph.  Is this conversion safe?\n>\n> This adds three uses of the round-robin path buffer; if a caller of\n> this function used two or more path buffers obtained from\n> get_pathname() and expected their contents to remain stable across\n> the call to this, it will silently break.\n\nthree round-robin buffers but not all required at the same time, once\nthe first file_exists() returns the first round-robin buffer could be\nfree, and remove_path() calls git_path again, not reusing the result\nfrom the second file_exists, so the second buffer is free to go too.\n-- \nDuy\n"},{"id":"235085","messageId":"xmqqa9dm78n3.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"CACsJy8BJ=OKzGDFwWQF_k_Gp9XeNwf7pe7c6_ebduJXN=xeOxw@mail.gmail.com","subject":"Re: [PATCH v3 02/25] Convert git_snpath() to strbuf_git_path()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-20T03:41:52Z","receivedAt":"2014-02-20T03:41:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Duy Nguyen <pclouds@gmail.com> writes:\n\n>>> -             }\n>>> +             if (old->path && old->name &&\n>>> +                 !file_exists(git_path(\"%s\", old->path)) &&\n>>> +                  file_exists(git_path(\"logs/%s\", old->path)))\n>>> +                     remove_path(git_path(\"logs/%s\", old->path));\n>>\n>> Hmph.  Is this conversion safe?\n>>\n>> This adds three uses of the round-robin path buffer; if a caller of\n>> this function used two or more path buffers obtained from\n>> get_pathname() and expected their contents to remain stable across\n>> the call to this, it will silently break.\n>\n> three round-robin buffers but not all required at the same time, once\n> the first file_exists() returns the first round-robin buffer could be\n> free, and remove_path() calls git_path again, not reusing the result\n> from the second file_exists, so the second buffer is free to go too.\n\nI know these three callers to git_path() will not step on each\nother's toes.  But that is not the question I asked.\n"},{"id":"235086","messageId":"CACsJy8DB+OP6_vwZKWXjq4YbtiKDg2NE2zRsAuz1ER6oTmyGag@mail.gmail.com","threadId":"35908","inReplyTo":"xmqqa9dm78n3.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v3 02/25] Convert git_snpath() to strbuf_git_path()","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-20T03:55:29Z","receivedAt":"2014-02-20T03:55:29Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Feb 20, 2014 at 10:41 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Duy Nguyen <pclouds@gmail.com> writes:\n>\n>>>> -             }\n>>>> +             if (old->path && old->name &&\n>>>> +                 !file_exists(git_path(\"%s\", old->path)) &&\n>>>> +                  file_exists(git_path(\"logs/%s\", old->path)))\n>>>> +                     remove_path(git_path(\"logs/%s\", old->path));\n>>>\n>>> Hmph.  Is this conversion safe?\n>>>\n>>> This adds three uses of the round-robin path buffer; if a caller of\n>>> this function used two or more path buffers obtained from\n>>> get_pathname() and expected their contents to remain stable across\n>>> the call to this, it will silently break.\n>>\n>> three round-robin buffers but not all required at the same time, once\n>> the first file_exists() returns the first round-robin buffer could be\n>> free, and remove_path() calls git_path again, not reusing the result\n>> from the second file_exists, so the second buffer is free to go too.\n>\n> I know these three callers to git_path() will not step on each\n> other's toes.  But that is not the question I asked.\n\nOK so your question was if there was a git_path() or mkpath() call\nearlier in update_refs_for_switch() and the result was expected to\nremain stable till the end of update_refs_for_switch(), then this\nconversion could ruin it, correct? I didn't think about that, but I\nhave checked and the only mkpath() call in this function is not\nsupposed to last long. If it's about a git_pathname() call outside\nupdate_refs_...() that still expects to be stable, we should fix that\ncode instead.\n-- \nDuy\n"},{"id":"235093","messageId":"CACsJy8Ab0OVQw4ATgv6E7rv-B3OwbhyrJBbXiXjYiUvtMknMrg@mail.gmail.com","threadId":"35908","inReplyTo":"xmqqppmi97s3.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v3 14/25] setup.c: convert is_git_directory() to use strbuf","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-20T13:04:31Z","receivedAt":"2014-02-20T13:04:31Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Feb 20, 2014 at 3:17 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> +     strbuf_setlen(sb, len);\n>> +     strbuf_add(sb, s, strlen(s));\n>\n> I am not sure addstr_at() gives us a good abstraction, or at least\n> the name conveys what it does well not to confuse readers.\n>\n> At first after only seeing its name, I would have expected that it\n> would splice the given string into an existing strbuf at the\n> location, not chopping the existing strbuf at the location and\n> appending.\n\nI think I invented a few new strbuf_* in this series and this is one\nof them. We have about ~14 other places in current code that do\nsimilar pattern: set length back, then add something on top. Not sure\nif it's worth a convenient wrapper. I don't know, maybe it's not worth\nreducing one line and causing more confusion.\n\n>\n>> +}\n>>  static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) {\n>>       strbuf_grow(sb, sb2->len);\n>>       strbuf_add(sb, sb2->buf, sb2->len);\n\n\n\n-- \nDuy\n"},{"id":"235095","messageId":"CACsJy8B11ZjLgLiE=S1sqZ09z2GtbwGfdJRuQ0AC_ppnQsQvcQ@mail.gmail.com","threadId":"35908","inReplyTo":"xmqq38je9739.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v3 24/25] prune: strategies for linked checkouts","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-20T13:15:06Z","receivedAt":"2014-02-20T13:15:06Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Feb 20, 2014 at 3:32 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>\n> (Only nitpicks during this round of review).\n>\n>> +     if (get_device_or_die(path) != get_device_or_die(get_git_dir())) {\n>> +             strbuf_reset(&sb);\n>> +             strbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n>> +             write_file(sb.buf, 1, \"located on a different file system\\n\");\n>> +             keep_locked = 1;\n>> +     } else {\n>> +             strbuf_reset(&sb);\n>> +             strbuf_addf(&sb, \"%s/link\", sb_repo.buf);\n>> +             link(sb_git.buf, sb.buf); /* it's ok to fail */\n>\n> If so, perhaps tell that to the code by saying something like\n>\n>                 (void) link(...);\n>\n> ?\n>\n> But why is it OK to fail in the first place?  If we couldn't link,\n> don't you want to fall back to the lock codepath?  After all, the\n> \"are we on the same device?\" check is to cope with the case where\n> we cannot link and that alternate codepath is supposed to be\n> prepared to handle the \"ah, we cannot link\" case correctly, no?\n\nFilesystems not supporting hardlinks are one reason. The idea behind\n\"locked\" is that the new checkout could be on a portable device and we\ndon't want to prune its metadata just because the device is not\nplugged in. Checking same device help but even that can't guarantee no\nfalse positives, unless your only mount point is /. So no I don't\nreally think we should go lock whenever link() fails, that would just\nmake fewer checkouts prunable.\n-- \nDuy\n"},{"id":"235096","messageId":"CACsJy8Bxp2sfP0M6htwNg-LP1CcFMpZbhE=sgR6xXJkj-5shcw@mail.gmail.com","threadId":"35908","inReplyTo":"CAPig+cQgphSBA6iAYqLJ7RnEwSzBA1dApcLWAydZEXarQRcTKQ@mail.gmail.com","subject":"Re: [PATCH v3 24/25] prune: strategies for linked checkouts","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-20T13:19:40Z","receivedAt":"2014-02-20T13:19:40Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Feb 20, 2014 at 5:08 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>> +static dev_t get_device_or_die(const char *path)\n>> +{\n>> +       struct stat buf;\n>> +       if (stat(path, &buf))\n>> +               die_errno(\"failed to stat '%s'\", path);\n>> +       /* Ah Windows! Make different drives different \"partitions\" */\n>> +       if (buf.st_dev == 0 && has_dos_drive_prefix(\"c:\\\\\"))\n>> +               buf.st_dev = toupper(real_path(path)[0]);\n>\n> This invocation of has_dos_drive_prefix() with hardcoded \"c:\\\\\" at\n> first looks like an error until the reader realizes that it's an\n> #ifdef-less check if the platforms is Windows. Would it make more\n> sense to encapsulate this anomaly and abstract it away by fixing\n> compat/mingw.c:do_lstat() to instead set 'st_dev' automatically like\n> you do here? In particular, this line in mingw.c:\n>\n>     buf->st_dev = buf->st_rdev = 0; /* not used by Git */\n>\n\nI don't want to hide too much magic behind compat curtain, especially\nwhen these magic is just about 90% as real, the rest is mysterious\ncorner cases. I guess we could just add an inline function\nis_windows() that always returns 0 or 1 based on #ifdef.\n-- \nDuy\n"},{"id":"235102","messageId":"xmqq1tyx7gxx.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"CACsJy8DB+OP6_vwZKWXjq4YbtiKDg2NE2zRsAuz1ER6oTmyGag@mail.gmail.com","subject":"Re: [PATCH v3 02/25] Convert git_snpath() to strbuf_git_path()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-20T18:54:50Z","receivedAt":"2014-02-20T18:54:50Z","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> OK so your question was if there was a git_path() or mkpath() call\n> earlier in update_refs_for_switch() and the result was expected to\n> remain stable till the end of update_refs_for_switch(), then this\n> conversion could ruin it, correct? I didn't think about that,...\n\nYeah, I couldn't tell if you thought about it, and that was why I\nasked.\n\nIf a (recursively) caller does this:\n\n\tcaller () {\n\t\tconst char *path1 = git_path(...);\n                const char *path2 = mkpath(...);\n                const char *path3 = git_path_submodule(...);\n\t\tcallee();\n                use(path1, path2, path3);\n\t}\n\nit was safe back when the callee() did not mess with the round-robin\npathname buffer, but it will be broken once callee() does.  While\nlooking at the patch I didn't check what the caller was doing hence\nmy question.\n\nIn general, in order to reduce that kind of hard-to-debug bugs, we\nshould be reducing the uses of these functions when we do not have\nto (which applies equally to such a caller that expects multiple\ntemporary paths to persist, and to a callee as well).  Adding\nmultiple repeated calls to git_path(), especially two of them\nformatting the same string into two separate round-robin pathname\nbuffer, looked strange in a patch that was supposed to be a\npreparatory code-cleanup stage.\n"},{"id":"235104","messageId":"xmqqwqgp61tl.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"CACsJy8Ab0OVQw4ATgv6E7rv-B3OwbhyrJBbXiXjYiUvtMknMrg@mail.gmail.com","subject":"Re: [PATCH v3 14/25] setup.c: convert is_git_directory() to use strbuf","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-20T19:06:46Z","receivedAt":"2014-02-20T19:06:46Z","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 Thu, Feb 20, 2014 at 3:17 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>>> +     strbuf_setlen(sb, len);\n>>> +     strbuf_add(sb, s, strlen(s));\n>>\n>> I am not sure addstr_at() gives us a good abstraction, or at least\n>> the name conveys what it does well not to confuse readers.\n>>\n>> At first after only seeing its name, I would have expected that it\n>> would splice the given string into an existing strbuf at the\n>> location, not chopping the existing strbuf at the location and\n>> appending.\n>\n> I think I invented a few new strbuf_* in this series and this is one\n> of them. We have about ~14 other places in current code that do\n> similar pattern: set length back, then add something on top.\n\nYes, and you can count getline/getwholeline as a special case to\nchomp to empty at the beginning.  I am not opposed to a helper to\ngive us an easy access to this common pattern.\n\nIt was just the name \"addstr-at\" did not sound, at least to me, what\nit does, i.e. \"replace with s from the pos to the end\", which I\nthink is the same thing as a single-liner:\n\n    strbuf_splice(sb, pos, sb->len - pos, s, strlen(s))\n"},{"id":"235106","messageId":"xmqqob2160fa.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"xmqqwqgp61tl.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v3 14/25] setup.c: convert is_git_directory() to use strbuf","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-20T19:36:57Z","receivedAt":"2014-02-20T19:36:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Duy Nguyen <pclouds@gmail.com> writes:\n>\n>> On Thu, Feb 20, 2014 at 3:17 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>>>> +     strbuf_setlen(sb, len);\n>>>> +     strbuf_add(sb, s, strlen(s));\n>>>\n>>> I am not sure addstr_at() gives us a good abstraction, or at least\n>>> the name conveys what it does well not to confuse readers.\n>>>\n>>> At first after only seeing its name, I would have expected that it\n>>> would splice the given string into an existing strbuf at the\n>>> location, not chopping the existing strbuf at the location and\n>>> appending.\n>>\n>> I think I invented a few new strbuf_* in this series and this is one\n>> of them. We have about ~14 other places in current code that do\n>> similar pattern: set length back, then add something on top.\n>\n> Yes, and you can count getline/getwholeline as a special case to\n> chomp to empty at the beginning.  I am not opposed to a helper to\n> give us an easy access to this common pattern.\n>\n> It was just the name \"addstr-at\" did not sound, at least to me, what\n> it does, i.e. \"replace with s from the pos to the end\", which I\n> think is the same thing as a single-liner:\n>\n>     strbuf_splice(sb, pos, sb->len - pos, s, strlen(s))\n\nOh, and as to other strbuf_* helpers, I am finding myself getting\nvery fond of strbuf_git_path() as I read the series along; it gives\nus the same convenience as git_path() [*1*] while giving us tighter\ncontrol on the lifetime rules of the path buffer.\n\n[Footnote]\n\n*1* And the new git_path() updated in this series has to be a lot\n more than catenate($GIT_DIR, \"/\", $path) but needs the smart of\n adjust_git_path(), the convenience matters.\n"},{"id":"235107","messageId":"xmqqha7t6039.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"1392730814-19656-3-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 02/25] Convert git_snpath() to strbuf_git_path()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-20T19:44:10Z","receivedAt":"2014-02-20T19:44:10Z","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> @@ -2717,17 +2729,19 @@ 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 *sb_logfile)\n>  {\n>  \tint logfd, oflags = O_APPEND | O_WRONLY;\n> +\tconst char *logfile;\n>  \n> -\tgit_snpath(logfile, bufsize, \"logs/%s\", refname);\n> +\tstrbuf_git_path(sb_logfile, \"logs/%s\", refname);\n> +\tlogfile = sb_logfile->buf;\n>  \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(sb_logfile->buf) < 0)\n\nOther references to logfile in this function are kept as-is, and I\nthink this line can also stay as-it-was.  That is the whole point of\nintroducing the local variable logfile and assinging to it upfront\nas a synonym, isn't it?\n"},{"id":"235108","messageId":"xmqqd2ih5zk4.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"CACsJy8B11ZjLgLiE=S1sqZ09z2GtbwGfdJRuQ0AC_ppnQsQvcQ@mail.gmail.com","subject":"Re: [PATCH v3 24/25] prune: strategies for linked checkouts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-20T19:55:39Z","receivedAt":"2014-02-20T19:55:39Z","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>> But why is it OK to fail in the first place?  If we couldn't link,\n>> don't you want to fall back to the lock codepath?  After all, the\n>> \"are we on the same device?\" check is to cope with the case where\n>> we cannot link and that alternate codepath is supposed to be\n>> prepared to handle the \"ah, we cannot link\" case correctly, no?\n>\n> Filesystems not supporting hardlinks are one reason. The idea behind\n> \"locked\" is that the new checkout could be on a portable device and we\n> don't want to prune its metadata just because the device is not\n> plugged in. Checking same device help but even that can't guarantee no\n> false positives, unless your only mount point is /. So no I don't\n> really think we should go lock whenever link() fails, that would just\n> make fewer checkouts prunable.\n\nIt just looked wrong to have a logic that goes like this:\n\n        if (we detect one case link() can never possibly work) {\n                do something for a case we do not use link;\n        } else {\n                prepare parameters that is used when we do link;\n                do link() but ignore error;\n        }\n\nand felt that I would have understood if it were more like this:\n\n        prepare parameters that is used when we do link;\n        if (try link() and it fails) {\n                do something for a case we do not use link;\n        }\n\nbut I did not mean that was the only alternative to correct the\n\"wrongness\".\n\nI do not think I yet know the motivation behind this step well\nenough to further comment---until I read the earlier parts of the\nseries that lead to this step, so I'll do that first.\n\nThanks.\n"},{"id":"235119","messageId":"CAPig+cQ=fujZ=M8TgRcemR5+0kSYNLGHbJDAksiUq8GJCkDPFA@mail.gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-15-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 14/25] setup.c: convert is_git_directory() to use strbuf","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-02-21T03:38:00Z","receivedAt":"2014-02-21T03:38:00Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Feb 18, 2014 at 8:40 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> index 73e80ce..aec9fdb 100644\n> --- a/strbuf.h\n> +++ b/strbuf.h\n> @@ -116,6 +116,10 @@ extern void strbuf_add(struct strbuf *, const void *, size_t);\n>  static inline void strbuf_addstr(struct strbuf *sb, const char *s) {\n>         strbuf_add(sb, s, strlen(s));\n>  }\n> +static inline void strbuf_addstr_at(struct strbuf *sb, size_t len, const char *s) {\n> +       strbuf_setlen(sb, len);\n> +       strbuf_add(sb, s, strlen(s));\n> +}\n\nUpdate Documentation/technical/api-strbuf.txt?\n\n>  static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) {\n>         strbuf_grow(sb, sb2->len);\n>         strbuf_add(sb, sb2->buf, sb2->len);\n> --\n> 1.8.5.2.240.g8478abd\n"},{"id":"235343","messageId":"xmqqr46q4xt6.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"1392730814-19656-8-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 07/25] reflog: avoid constructing .lock path with git_path","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-25T22:44:37Z","receivedAt":"2014-02-25T22:44:37Z","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> git_path() soon understands the path given to it. Some paths \"abc\" may\n> become \"def\" while other \"ghi\" may become \"ijk\". We don't want\n> git_path() to interfere with .lock path construction. Concatenate\n> \".lock\" after the path has been resolved by git_path() so if \"abc\"\n> becomes \"def\", we'll have \"def.lock\", not \"ijk\".\n\nHmph.  I am not sure if the above is readable (or if I am reading it\ncorrectly).\n\nIf \"abc\" becomes \"def\", it would take deliberate work to make\n\"abc.lock\" into \"ijk\", and it would be natural to expect that\n\"abc.lock\" would become \"def.lock\" without any fancy trick, no?\n\n\n> Signed-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>\n> diff --git a/builtin/reflog.c b/builtin/reflog.c\n> index 852cff6..ccf2cf6 100644\n> --- a/builtin/reflog.c\n> +++ b/builtin/reflog.c\n> @@ -372,7 +372,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,\n>  \tif (!file_exists(log_file))\n>  \t\tgoto finish;\n>  \tif (!cmd->dry_run) {\n> -\t\tnewlog_path = git_pathdup(\"logs/%s.lock\", ref);\n> +\t\tnewlog_path = mkpathdup(\"%s.lock\", log_file);\n>  \t\tcb.newlog = fopen(newlog_path, \"w\");\n>  \t}\n"},{"id":"235349","messageId":"CAPig+cRPpdfNYP=8o+9ku7Lr6bQ3EBiFQcS_aM4YBVhCbuJ3sQ@mail.gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-11-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-02-26T01:24:47Z","receivedAt":"2014-02-26T01:24:47Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Feb 18, 2014 at 8:39 AM, Nguyễn Thái Ngọc Duy <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/git.txt b/Documentation/git.txt\n> index 02bbc08..2c4a055 100644\n> --- a/Documentation/git.txt\n> +++ b/Documentation/git.txt\n> @@ -773,6 +773,14 @@ Git so take care if using Cogito etc.\n>         an explicit repository directory set via 'GIT_DIR' or on the\n>         command line.\n>\n> +'GIT_COMMON_DIR'::\n> +       If this variable is set to a path, non-worktree files that are\n> +       normally in $GIT_DIR will be taken from this path\n> +       instead. Worktree-specific files such as HEAD or index are\n> +       taken from $GIT_DIR. This variable has lower precedence than\n> +       other path variables such as GIT_INDEX_FILE,\n> +       GIT_OBJECT_DIRECTORY...\n\nFor a person not familiar with \"git checkout --to\" or its underlying\nimplementation, this description may be lacking. Such a reader may be\nleft wondering about GIT_COMMON_DIR's overall purpose, and when and\nhow it should be used. Perhaps it would make sense to talk a bit about\n\"git checkout --to\" here?\n\n>  Git Commits\n>  ~~~~~~~~~~~\n>  'GIT_AUTHOR_NAME'::\n"},{"id":"235367","messageId":"CACsJy8CSZZptDr6rWTNES1peDrkk5k7Ai9mzQvSBDZz7FGJxLA@mail.gmail.com","threadId":"35908","inReplyTo":"CAPig+cRPpdfNYP=8o+9ku7Lr6bQ3EBiFQcS_aM4YBVhCbuJ3sQ@mail.gmail.com","subject":"Re: [PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-26T10:55:54Z","receivedAt":"2014-02-26T10:55:54Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Feb 26, 2014 at 8:24 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>> +'GIT_COMMON_DIR'::\n>> +       If this variable is set to a path, non-worktree files that are\n>> +       normally in $GIT_DIR will be taken from this path\n>> +       instead. Worktree-specific files such as HEAD or index are\n>> +       taken from $GIT_DIR. This variable has lower precedence than\n>> +       other path variables such as GIT_INDEX_FILE,\n>> +       GIT_OBJECT_DIRECTORY...\n>\n> For a person not familiar with \"git checkout --to\" or its underlying\n> implementation, this description may be lacking. Such a reader may be\n> left wondering about GIT_COMMON_DIR's overall purpose, and when and\n> how it should be used. Perhaps it would make sense to talk a bit about\n> \"git checkout --to\" here?\n\nI don't want to repeat too much. Maybe mention about \"git checkout\n--to\" and point them to git-checkout man page?\n-- \nDuy\n"},{"id":"235385","messageId":"6CF0FBAC168D48BA901A31901FAD0C3E@PhilipOakley","threadId":"35908","inReplyTo":"CACsJy8CSZZptDr6rWTNES1peDrkk5k7Ai9mzQvSBDZz7FGJxLA@mail.gmail.com","subject":"Re: [PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":null,"receivedAt":"2014-02-26T15:54:39Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"From: \"Duy Nguyen\" <pclouds@gmail.com>\n> On Wed, Feb 26, 2014 at 8:24 AM, Eric Sunshine \n> <sunshine@sunshineco.com> wrote:\n>>> +'GIT_COMMON_DIR'::\n>>> +       If this variable is set to a path, non-worktree files that \n>>> are\n>>> +       normally in $GIT_DIR will be taken from this path\n>>> +       instead. Worktree-specific files such as HEAD or index are\n>>> +       taken from $GIT_DIR. This variable has lower precedence than\n>>> +       other path variables such as GIT_INDEX_FILE,\n>>> +       GIT_OBJECT_DIRECTORY...\n>>\n>> For a person not familiar with \"git checkout --to\" or its underlying\n>> implementation, this description may be lacking. Such a reader may be\n>> left wondering about GIT_COMMON_DIR's overall purpose, and when and\n>> how it should be used. Perhaps it would make sense to talk a bit \n>> about\n>> \"git checkout --to\" here?\n>\n> I don't want to repeat too much. Maybe mention about \"git checkout\n> --to\" and point them to git-checkout man page?\n\nI've just looked at both \nhttps://www.kernel.org/pub/software/scm/git/docs/git-checkout.html and \nhttp://git-htmldocs.googlecode.com/git/git-checkout.html and neither \nappear to mention the --to option.\n\nIs it missing from the man page? Or is it me that's missing something?\n\n--\nPhilip \n"},{"id":"235391","messageId":"CAPig+cQdrZE-x1KdCwPWOaer7=Cwze-snNZ3-o+9mS7X1AneaQ@mail.gmail.com","threadId":"35908","inReplyTo":"6CF0FBAC168D48BA901A31901FAD0C3E@PhilipOakley","subject":"Re: [PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-02-26T17:23:27Z","receivedAt":"2014-02-26T17:23:27Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Feb 26, 2014 at 11:12 AM, Philip Oakley <philipoakley@iee.org> wrote:\n> From: \"Duy Nguyen\" <pclouds@gmail.com>\n>> On Wed, Feb 26, 2014 at 8:24 AM, Eric Sunshine <sunshine@sunshineco.com>\n>> wrote:\n>>>>\n>>>> +'GIT_COMMON_DIR'::\n>>>> +       If this variable is set to a path, non-worktree files that are\n>>>> +       normally in $GIT_DIR will be taken from this path\n>>>> +       instead. Worktree-specific files such as HEAD or index are\n>>>> +       taken from $GIT_DIR. This variable has lower precedence than\n>>>> +       other path variables such as GIT_INDEX_FILE,\n>>>> +       GIT_OBJECT_DIRECTORY...\n>>>\n>>> For a person not familiar with \"git checkout --to\" or its underlying\n>>> implementation, this description may be lacking. Such a reader may be\n>>> left wondering about GIT_COMMON_DIR's overall purpose, and when and\n>>> how it should be used. Perhaps it would make sense to talk a bit about\n>>> \"git checkout --to\" here?\n>>\n>> I don't want to repeat too much. Maybe mention about \"git checkout\n>> --to\" and point them to git-checkout man page?\n>\n> I've just looked at both\n> https://www.kernel.org/pub/software/scm/git/docs/git-checkout.html and\n> http://git-htmldocs.googlecode.com/git/git-checkout.html and neither appear\n> to mention the --to option.\n>\n> Is it missing from the man page? Or is it me that's missing something?\n\n'git checkout --to' is the new feature being introduced by this\n25-patch series [1] from Duy (to which we are responding).\n\n[1]: http://thread.gmane.org/gmane.comp.version-control.git/242300\n"},{"id":"235402","messageId":"CAPig+cSpnK0vqG_jQYmW1SC-VTQknG_aFnBRN-HvBmEsN0mzSw@mail.gmail.com","threadId":"35908","inReplyTo":"CACsJy8CSZZptDr6rWTNES1peDrkk5k7Ai9mzQvSBDZz7FGJxLA@mail.gmail.com","subject":"Re: [PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-02-26T19:43:59Z","receivedAt":"2014-02-26T19:43:59Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Feb 26, 2014 at 5:55 AM, Duy Nguyen <pclouds@gmail.com> wrote:\n> On Wed, Feb 26, 2014 at 8:24 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>>> +'GIT_COMMON_DIR'::\n>>> +       If this variable is set to a path, non-worktree files that are\n>>> +       normally in $GIT_DIR will be taken from this path\n>>> +       instead. Worktree-specific files such as HEAD or index are\n>>> +       taken from $GIT_DIR. This variable has lower precedence than\n>>> +       other path variables such as GIT_INDEX_FILE,\n>>> +       GIT_OBJECT_DIRECTORY...\n>>\n>> For a person not familiar with \"git checkout --to\" or its underlying\n>> implementation, this description may be lacking. Such a reader may be\n>> left wondering about GIT_COMMON_DIR's overall purpose, and when and\n>> how it should be used. Perhaps it would make sense to talk a bit about\n>> \"git checkout --to\" here?\n>\n> I don't want to repeat too much. Maybe mention about \"git checkout\n> --to\" and point them to git-checkout man page?\n\nYes, that might be sufficient. \"git checkout --to\" documentation\npoints the reader at the \"MULTIPLE CHECKOUT MODE\" section which gives\na more detailed explanation of GIT_COMMON_DIR, so a user wanting to\nunderstand GIT_COMMON_DIR better would have a way to find the\ninformation.\n"},{"id":"235406","messageId":"CAPig+cSDT72MHFdwnZHBwEUiJQiQAkc-nv0vK166O8EuXVtgbw@mail.gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-22-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 21/25] checkout: support checking out into a new working directory","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-02-26T20:06:49Z","receivedAt":"2014-02-26T20:06:49Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Feb 18, 2014 at 8:40 AM, Nguyễn Thái Ngọc Duy <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/builtin/checkout.c b/builtin/checkout.c\n> index 0570e41..2b856a6 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n> @@ -806,6 +814,74 @@ static int switch_branches(const struct checkout_opts *opts,\n>         return ret || writeout_error;\n>  }\n>\n> +static int prepare_linked_checkout(const struct checkout_opts *opts,\n> +                                  struct branch_info *new)\n> +{\n> +       struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;\n> +       struct strbuf sb = STRBUF_INIT;\n> +       const char *path = opts->new_worktree;\n> +       struct stat st;\n> +       const char *name;\n> +       struct child_process cp;\n> +       int counter = 0, len;\n> +\n> +       if (!new->commit)\n> +               die(_(\"no branch specified\"));\n> +\n> +       len = strlen(path);\n> +       if (!len || is_dir_sep(path[len - 1]))\n> +               die(_(\"'--to' argument '%s' cannot end with a slash\"), path);\n\nWhat is the purpose of this restriction?\n\n> +       for (name = path + len - 1; name > path; name--)\n> +               if (is_dir_sep(*name)) {\n> +                       name++;\n> +                       break;\n> +               }\n> +       strbuf_addstr(&sb_repo, git_path(\"repos/%s\", name));\n> +       len = sb_repo.len;\n> +       if (safe_create_leading_directories_const(sb_repo.buf))\n> +               die_errno(_(\"could not create leading directories of '%s'\"),\n> +                         sb_repo.buf);\n> +       while (!stat(sb_repo.buf, &st)) {\n> +               counter++;\n> +               strbuf_setlen(&sb_repo, len);\n> +               strbuf_addf(&sb_repo, \"%d\", counter);\n> +       }\n> +       name = sb_repo.buf + len - strlen(name);\n> +       if (mkdir(sb_repo.buf, 0777))\n> +               die_errno(_(\"could not create directory of '%s'\"), sb_repo.buf);\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> +\n> +       write_file(sb_git.buf, 1, \"gitdir: %s/repos/%s\\n\",\n> +                  real_path(get_git_dir()), name);\n> +       /*\n> +        * This is to keep resolve_ref() happy. We need a valid HEAD\n> +        * or is_git_directory() will reject the directory. Any valid\n> +        * value would do because this value will be ignored and\n> +        * replaced at the next (real) checkout.\n> +        */\n> +       strbuf_addf(&sb, \"%s/HEAD\", sb_repo.buf);\n> +       write_file(sb.buf, 1, \"%s\\n\", sha1_to_hex(new->commit->object.sha1));\n> +       strbuf_reset(&sb);\n> +       strbuf_addf(&sb, \"%s/commondir\", sb_repo.buf);\n> +       write_file(sb.buf, 1, \"../..\\n\");\n> +\n> +       if (!opts->quiet)\n> +               fprintf_ln(stderr, _(\"Enter %s (identifier %s)\"), path, name);\n> +\n> +       setenv(\"GIT_CHECKOUT_NEW_WORKTREE\", \"1\", 1);\n> +       setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1);\n> +       setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1);\n> +       memset(&cp, 0, sizeof(cp));\n> +       cp.git_cmd = 1;\n> +       cp.argv = opts->saved_argv;\n> +       return run_command(&cp);\n> +}\n> +\n"},{"id":"235424","messageId":"CACsJy8BkAOk4d3nFP_CtUN4Feer0sHy8H8vpbUWcZcYMLYgYyg@mail.gmail.com","threadId":"35908","inReplyTo":"CAPig+cSDT72MHFdwnZHBwEUiJQiQAkc-nv0vK166O8EuXVtgbw@mail.gmail.com","subject":"Re: [PATCH v3 21/25] checkout: support checking out into a new working directory","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-26T23:19:48Z","receivedAt":"2014-02-26T23:19:48Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Feb 27, 2014 at 3:06 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>> +       len = strlen(path);\n>> +       if (!len || is_dir_sep(path[len - 1]))\n>> +               die(_(\"'--to' argument '%s' cannot end with a slash\"), path);\n>\n> What is the purpose of this restriction?\n\nLaziness on my part :) Because the following loop searches backward to\nget the `basename $path`, trailing slash would make it return empty\nbase name. I could have just removed the trailing slash here instead\nof dying though. Thanks for catching.\n\n>\n>> +       for (name = path + len - 1; name > path; name--)\n>> +               if (is_dir_sep(*name)) {\n>> +                       name++;\n>> +                       break;\n>> +               }\n\n\n\n-- \nDuy\n"},{"id":"235426","messageId":"xmqqzjld1l59.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"1392730814-19656-11-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-26T23:58:42Z","receivedAt":"2014-02-26T23:58: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> Note that logs/refs/.tmp-renamed-log is used to prepare new reflog\n> entry and it's supposed to be on the same filesystem as the target\n> reflog file. This is not guaranteed true for logs/HEAD when it's\n> mapped to repos/xx/logs/HEAD because the user can put \"repos\"\n> directory on different filesystem. Don't mess with .git unless you\n> know what you're doing.\n\nHmph.  I am puzzled.\n\nWe use TMP_RENAMED_LOG in rename_ref() in this sequence:\n\n * First move refs/logs/$oldname to TMP_RENAMED_LOG;\n * Delete refs/$oldname;\n * Delete refs/$newname if exists;\n * Move TMP_RENAMED_LOG to refs/logs/$newname;\n * Create refs/$newname.\n\nin rename_ref(), and TMP_RENAMED_LOG or the helper function\nrename_tmp_log() that does the actual rename do not seem to be\ncalled by any other codepath.\n\nHow would logs/HEAD get in the picture?  Specifically, I am not sure\nif we ever allow renaming the HEAD (which typically is a symref but\ncould be detached) via rename_ref() at all.\n\n> The redirection is done by git_path(), git_pathdup() and\n> strbuf_git_path(). The selected list of paths goes to $GIT_COMMON_DIR,\n> not the other way around in case a developer adds a new\n> worktree-specific file and it's accidentally promoted to be shared\n> across repositories (this includes unknown files added by third party\n> commands)\n>\n> The list of known files that belong to $GIT_DIR are:\n>\n> ADD_EDIT.patch BISECT_ANCESTORS_OK BISECT_EXPECTED_REV BISECT_LOG\n> BISECT_NAMES CHERRY_PICK_HEAD COMMIT_MSG FETCH_HEAD HEAD MERGE_HEAD\n> MERGE_MODE MERGE_RR NOTES_EDITMSG NOTES_MERGE_WORKTREE ORIG_HEAD\n> REVERT_HEAD SQUASH_MSG TAG_EDITMSG fast_import_crash_* logs/HEAD\n> next-index-* rebase-apply rebase-merge rsync-refs-* sequencer/*\n> shallow_*\n>\n> Path mapping is NOT done for git_path_submodule(). Multi-checkouts are\n> not supported as submodules.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n\nOther than the \"I do not understand why logs/HEAD is an issue\", the\nother aspect of the above design feels sound to me.\n\n> diff --git a/Documentation/git.txt b/Documentation/git.txt\n> index 02bbc08..2c4a055 100644\n> --- a/Documentation/git.txt\n> +++ b/Documentation/git.txt\n> @@ -773,6 +773,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. This variable has lower precedence than\n> +\tother path variables such as GIT_INDEX_FILE,\n> +\tGIT_OBJECT_DIRECTORY...\n> +\n\nWe may want to add something (not necessarily with this commit) to\nrepository-layout and glossary to describe this new \"officially\nsupported\" way to implement what contrib/workdir wanted to do.\n\n> diff --git a/path.c b/path.c\n> index 0f8c3dc..2d757dc 100644\n> --- a/path.c\n> +++ b/path.c\n> @@ -90,6 +90,32 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)\n>  \t\tbuf->buf[newlen] = '/';\n>  }\n>  \n> +static void update_common_dir(struct strbuf *buf, int git_dir_len)\n> +{\n> +\tconst char *common_dir_list[] = {\n> +\t\t\"branches\", \"hooks\", \"info\", \"logs\", \"lost-found\", \"modules\",\n> +\t\t\"objects\", \"refs\", \"remotes\", \"rr-cache\", \"svn\",\n> +\t\tNULL\n> +\t};\n> +\tconst char *common_top_file_list[] = {\n> +\t\t\"config\", \"gc.pid\", \"packed-refs\", \"shallow\", NULL\n> +\t};\n> +\tchar *base = buf->buf + git_dir_len;\n> +\tconst char **p;\n> +\tif (is_dir_file(base, \"logs\", \"HEAD\"))\n\nJust a style, but could you have a blank line between the series of\ndecls and the first statement?  It would be easier to read that way.\n\n> +\t\treturn;\t/* keep this in $GIT_DIR */\n> +\tfor (p = common_dir_list; *p; p++)\n> +\t\tif (dir_prefix(base, *p)) {\n> +\t\t\treplace_dir(buf, git_dir_len, get_git_common_dir());\n> +\t\t\treturn;\n> +\t\t}\n> +\tfor (p = common_top_file_list; *p; p++)\n> +\t\tif (!strcmp(base, *p)) {\n> +\t\t\treplace_dir(buf, git_dir_len, get_git_common_dir());\n> +\t\t\treturn;\n> +\t\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 +127,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)\n> diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh\n> index 1d29901..f9a77e4 100755\n> --- a/t/t0060-path-utils.sh\n> +++ b/t/t0060-path-utils.sh\n> @@ -241,5 +241,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"},{"id":"235427","messageId":"xmqqvbw11l2x.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"1392730814-19656-12-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 11/25] git-sh-setup.sh: use rev-parse --git-path to get $GIT_DIR/objects","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-27T00:00:06Z","receivedAt":"2014-02-27T00:00:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> If $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>\n> Signed-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>\n> diff --git a/git-sh-setup.sh b/git-sh-setup.sh\n> index fffa3c7..fec9430 100644\n> --- a/git-sh-setup.sh\n> +++ b/git-sh-setup.sh\n> @@ -343,7 +343,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\n$(...) is the preferred way over `...` in this codebase.\n\n>  fi\n>  \n>  peel_committish () {\n"},{"id":"235429","messageId":"xmqqr46p1kbo.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"1392730814-19656-16-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 15/25] setup.c: detect $GIT_COMMON_DIR in is_git_directory()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-27T00:16:27Z","receivedAt":"2014-02-27T00:16:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> If the file \"$GIT_DIR/commondir\" exists, it contains the value of\n> $GIT_COMMON_DIR.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  Documentation/gitrepository-layout.txt |  4 ++++\n>  setup.c                                | 38 ++++++++++++++++++++++++++++------\n>  strbuf.c                               |  8 +++++++\n>  strbuf.h                               |  1 +\n>  4 files changed, 45 insertions(+), 6 deletions(-)\n>\n> diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt\n> index aa03882..9bfe0f1 100644\n> --- a/Documentation/gitrepository-layout.txt\n> +++ b/Documentation/gitrepository-layout.txt\n> @@ -211,6 +211,10 @@ shallow::\n>  \tand maintained by shallow clone mechanism.  See `--depth`\n>  \toption to linkgit:git-clone[1] and linkgit:git-fetch[1].\n>  \n> +commondir::\n> +\tIf this file exists, $GIT_COMMON_DIR will be set to the path\n> +\tspecified in this file if it is not set.\n> +\n>  modules::\n>  \tContains the git-repositories of the submodules.\n\nIn a way similar to the \"*Note*\" in a very early part of this file\ndescribes the .git-file redirected repositories, we would need to\nmention that there now exist repositories borrowing from another\nrepository via this commondir mechanism, and what the caveats are\nwhen using them (like, \"do not ever nuke the original repository\nsomebody else is borrowing from with 'rm -rf' when you think you are\ndone with the original\").\n\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\nDo we care about the case where we cannot tell if the file exists\n(e.g. stat() fails due to EPERM or something), or would it be not\nworth worrying about?\n\n> +\t\tstrbuf_chomp(&data);\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\nOK, so commondir can be relative to the containing gitdir\n(e.g. /a/foo/.git/commondir with \"../../bar/.git\" would name\n/a/bar/.git as the common dir).\n\nIt needs to be documented in the repository-layout somehow.\n\n> @@ -188,14 +212,20 @@ int is_git_directory(const char *suspect)\n>  \tint ret = 0;\n>  \tsize_t len;\n>  \n> -\tstrbuf_addstr(&path, suspect);\n> +\tstrbuf_addf(&path, \"%s/HEAD\", suspect);\n\n> +\tif (validate_headref(path.buf))\n> +\t\tgoto done;\n\nIs there a reason why we want to check HEAD before other stuff?\nJust being curious, as I do not think of any (I am not saying that\nwe shouldn't change the order).\n\n> +void strbuf_chomp(struct strbuf *sb)\n> +{\n> +\twhile (sb->len && (sb->buf[sb->len - 1] == '\\n' ||\n> +\t\t\t   sb->buf[sb->len - 1] == '\\r'))\n> +\t\tsb->len--;\n> +\tsb->buf[sb->len] = '\\0';\n> +}\n\nIt feels a bit yucky to ignore trailing \\r on non-DOS filesystems\n(if it were also removing any whitespace, then I would sort of\nunderstand in the context of the expected caller of this function in\nthis series---except that it would no longer be a \"chomp\"), but I'd\nlet it pass.\n"},{"id":"235430","messageId":"xmqqmwhd1k8z.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"1392730814-19656-17-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 16/25] setup.c: convert check_repository_format_gently to use strbuf","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-27T00:18:04Z","receivedAt":"2014-02-27T00:18:04Z","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> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n\nNice.\n"},{"id":"235432","messageId":"xmqqios11k0v.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"1392730814-19656-18-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 17/25] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-27T00:22:56Z","receivedAt":"2014-02-27T00:22:56Z","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> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n\nIt is a good thing to do to read config from the real repository we\nare borrowing from when we have .git/commondir, but it makes me\nwonder if we should signal some kind of error if we find .git/config\nin such a borrowing repository---which will be silently ignored.\n\nMy gut feeling is that such a check may be necessary, but may not\nbelong to this function.\n\n>  setup.c | 6 ++++--\n>  1 file changed, 4 insertions(+), 2 deletions(-)\n>\n> diff --git a/setup.c b/setup.c\n> index 282fdc9..e56ec11 100644\n> --- a/setup.c\n> +++ b/setup.c\n> @@ -285,6 +285,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> @@ -294,8 +298,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"},{"id":"235434","messageId":"CACsJy8CQTV65KnzhQ8PrgrEUAJA+LPD=-5=g9J_gtNoLOPApAg@mail.gmail.com","threadId":"35908","inReplyTo":"xmqqios11k0v.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v3 17/25] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently()","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-27T02:43:56Z","receivedAt":"2014-02-27T02:43:56Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Feb 27, 2014 at 7:22 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>\n>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>> ---\n>\n> It is a good thing to do to read config from the real repository we\n> are borrowing from when we have .git/commondir, but it makes me\n> wonder if we should signal some kind of error if we find .git/config\n> in such a borrowing repository---which will be silently ignored.\n>\n> My gut feeling is that such a check may be necessary, but may not\n> belong to this function.\n\nJust checking. Once we do this, what about .git/refs/.. that is also\nignored in such a repo? I can see the point that config could be\nmanually edited so the user may edit the file at the wrong place, so\nit's good the we catch this exception.\n-- \nDuy\n"},{"id":"235435","messageId":"CACsJy8ChMq7cW-H4oSCi8QdMjb2Juqi=Y4k_+5395qA51RxN0g@mail.gmail.com","threadId":"35908","inReplyTo":"xmqqzjld1l59.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-27T03:03:15Z","receivedAt":"2014-02-27T03:03:15Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Feb 27, 2014 at 6:58 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>\n>> Note that logs/refs/.tmp-renamed-log is used to prepare new reflog\n>> entry and it's supposed to be on the same filesystem as the target\n>> reflog file. This is not guaranteed true for logs/HEAD when it's\n>> mapped to repos/xx/logs/HEAD because the user can put \"repos\"\n>> directory on different filesystem. Don't mess with .git unless you\n>> know what you're doing.\n>\n> Hmph.  I am puzzled.\n>\n> We use TMP_RENAMED_LOG in rename_ref() in this sequence:\n>\n>  * First move refs/logs/$oldname to TMP_RENAMED_LOG;\n>  * Delete refs/$oldname;\n>  * Delete refs/$newname if exists;\n>  * Move TMP_RENAMED_LOG to refs/logs/$newname;\n>  * Create refs/$newname.\n>\n> in rename_ref(), and TMP_RENAMED_LOG or the helper function\n> rename_tmp_log() that does the actual rename do not seem to be\n> called by any other codepath.\n>\n> How would logs/HEAD get in the picture?  Specifically, I am not sure\n> if we ever allow renaming the HEAD (which typically is a symref but\n> could be detached) via rename_ref() at all.\n\nHEAD is an exception, I agree. If you rename HEAD to something else,\nthe repo will not be recognized anymore because HEAD is part of the\nrepo signature. There are other refs outside refs/ though that can be\nrenamed in theory.  In practice all rename_ref() callers only operate\non refs/ domain so this is a false alarm.\n-- \nDuy\n"},{"id":"235438","messageId":"CAPig+cRnAm1fz0u2UHEWeZvERbwgOsSe=m3wWLMVvmjzUnRarg@mail.gmail.com","threadId":"35908","inReplyTo":"CACsJy8BkAOk4d3nFP_CtUN4Feer0sHy8H8vpbUWcZcYMLYgYyg@mail.gmail.com","subject":"Re: [PATCH v3 21/25] checkout: support checking out into a new working directory","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-02-27T06:59:03Z","receivedAt":"2014-02-27T06:59:03Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Feb 26, 2014 at 6:19 PM, Duy Nguyen <pclouds@gmail.com> wrote:\n> On Thu, Feb 27, 2014 at 3:06 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>>> +       len = strlen(path);\n>>> +       if (!len || is_dir_sep(path[len - 1]))\n>>> +               die(_(\"'--to' argument '%s' cannot end with a slash\"), path);\n>>\n>> What is the purpose of this restriction?\n>\n> Laziness on my part :) Because the following loop searches backward to\n> get the `basename $path`, trailing slash would make it return empty\n> base name. I could have just removed the trailing slash here instead\n> of dying though. Thanks for catching.\n\nThanks for the explanation. I thought that that might be the case.\n\n>>> +       for (name = path + len - 1; name > path; name--)\n>>> +               if (is_dir_sep(*name)) {\n>>> +                       name++;\n>>> +                       break;\n>>> +               }\n"},{"id":"235485","messageId":"xmqq38j41m70.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"CACsJy8CQTV65KnzhQ8PrgrEUAJA+LPD=-5=g9J_gtNoLOPApAg@mail.gmail.com","subject":"Re: [PATCH v3 17/25] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-27T17:48:19Z","receivedAt":"2014-02-27T17:48:19Z","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 Thu, Feb 27, 2014 at 7:22 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>>\n>>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>>> ---\n>>\n>> It is a good thing to do to read config from the real repository we\n>> are borrowing from when we have .git/commondir, but it makes me\n>> wonder if we should signal some kind of error if we find .git/config\n>> in such a borrowing repository---which will be silently ignored.\n>>\n>> My gut feeling is that such a check may be necessary, but may not\n>> belong to this function.\n>\n> Just checking. Once we do this, what about .git/refs/.. that is also\n> ignored in such a repo?\n\nIt was just that I became aware of the issue while reading this\npatch to check-repository-format which is only about config, but\nanything inside .git/ of the borrowing repository that are ignored\nbecause it has .git/common-dir (including .git/refs) should be a\ncause of the same error, I would say.  That would be the same set\nas symlinks created by contrib/workdir/git-new-workdir script.\n"},{"id":"235503","messageId":"xmqqk3cgxptp.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"1392730814-19656-19-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 18/25] setup.c: support multi-checkout repo setup","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-27T20:28:50Z","receivedAt":"2014-02-27T20:28:50Z","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> The repo setup procedure is updated to detect $GIT_DIR/commondir and\n> set $GIT_COMMON_DIR properly.\n>\n> The core.worktree is ignored when $GIT_DIR/commondir presents. This is\n> because \"commondir\" repos are intended for separate/linked checkouts\n> and pointing them back to a fixed core.worktree just does not make\n> sense.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  Documentation/config.txt        |  3 +-\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(+), 15 deletions(-)\n>\n> diff --git a/Documentation/config.txt b/Documentation/config.txt\n> index 5f4d793..cbf4d97 100644\n> --- a/Documentation/config.txt\n> +++ b/Documentation/config.txt\n> @@ -381,7 +381,8 @@ false), while all other repositories are assumed to be bare (bare\n>  \n>  core.worktree::\n>  \tSet the path to the root of the working tree.\n> -\tThis can be overridden by the GIT_WORK_TREE environment\n> +\tThis can be overridden by the GIT_WORK_TREE\n> +\tor GIT_COMMON_DIR environment\n>  \tvariable and the '--work-tree' command line option.\n\nDuring my first reading, I was guessing that the reason you changed\nthis is because COMMON_DIR may redirect the config file from which\ncore.worktree may be read.  But that is probably not what you meant.\nYou do not want to share core.worktree between the borrowing and the\nborrowed repositories.  If the presense of GIT_COMMON_DIR _disables_\ncore.worktree settings without supplying an alternative value, as\nopposed to GIT_WORK_TREE which does override with an alternative\nvalue, it is very different from \"can be overriden\".\n\nIt needs a better phrasing.\n\nBy the way, do we need to do something special for core.bare as\nwell for a similar reason?\n"},{"id":"235687","messageId":"CACsJy8AKu6UQURW-Zn=VE5WF3Yqfg329nsCoxiCt+nSJKfTW3g@mail.gmail.com","threadId":"35908","inReplyTo":"xmqqk3cq7kgm.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v3 01/25] path.c: make get_pathname() return strbuf instead of static buffer","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T02:40:23Z","receivedAt":"2014-03-01T02:40:23Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Feb 20, 2014 at 6:26 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Is there a reason not to do just an equivalent of\n>\n>     #define git_pathdup mkpathdup\n>\n> and be done with it?  Am I missing something?\n>\n\nThey have a subtle difference: mkpathdup() calls cleanup_path() while\ngit_pathdup() does not. Without auditing all call sites, we can't be\nsure this difference is insignificant. So I keep both functions\nseparate for now.\n-- \nDuy\n"},{"id":"235689","messageId":"1393642255-2551-1-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"xmqqppmi97s3.fsf@gitster.dls.corp.google.com","subject":"[PATCH] strbuf: style fix -- top opening bracket on a separate line","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T02:50:55Z","receivedAt":"2014-03-01T02: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 On Thu, Feb 20, 2014 at 3:17 AM, Junio C Hamano <gitster@pobox.com> wrote:\n >> --- a/strbuf.h\n >> +++ b/strbuf.h\n >> @@ -116,6 +116,10 @@ extern void strbuf_add(struct strbuf *, const void *, size_t);\n >>  static inline void strbuf_addstr(struct strbuf *sb, const char *s) {\n >>       strbuf_add(sb, s, strlen(s));\n >>  }\n >> +static inline void strbuf_addstr_at(struct strbuf *sb, size_t len, const char *s) {\n >\n > Please have the opening \"{\" on its own line.\n >\n > Surrounding existing functions are all offenders, but that is not an\n > excuse to make it worse (cleaning them up will need to be done in a\n > separate patch).\n\n Let's fix the surrounding code then.\n\n strbuf.h | 18 ++++++++++++------\n 1 file changed, 12 insertions(+), 6 deletions(-)\n\ndiff --git a/strbuf.h b/strbuf.h\nindex 73e80ce..39c14cf 100644\n--- a/strbuf.h\n+++ b/strbuf.h\n@@ -17,20 +17,23 @@ extern void strbuf_init(struct strbuf *, size_t);\n extern void strbuf_release(struct strbuf *);\n extern char *strbuf_detach(struct strbuf *, size_t *);\n extern void strbuf_attach(struct strbuf *, void *, size_t, size_t);\n-static inline void strbuf_swap(struct strbuf *a, struct strbuf *b) {\n+static inline void strbuf_swap(struct strbuf *a, struct strbuf *b)\n+{\n \tstruct strbuf tmp = *a;\n \t*a = *b;\n \t*b = tmp;\n }\n \n /*----- strbuf size related -----*/\n-static inline size_t strbuf_avail(const struct strbuf *sb) {\n+static inline size_t strbuf_avail(const struct strbuf *sb)\n+{\n \treturn sb->alloc ? sb->alloc - sb->len - 1 : 0;\n }\n \n extern void strbuf_grow(struct strbuf *, size_t);\n \n-static inline void strbuf_setlen(struct strbuf *sb, size_t len) {\n+static inline void strbuf_setlen(struct strbuf *sb, size_t len)\n+{\n \tif (len > (sb->alloc ? sb->alloc - 1 : 0))\n \t\tdie(\"BUG: strbuf_setlen() beyond buffer\");\n \tsb->len = len;\n@@ -97,7 +100,8 @@ static inline struct strbuf **strbuf_split(const struct strbuf *sb,\n extern void strbuf_list_free(struct strbuf **);\n \n /*----- add data in your buffer -----*/\n-static inline void strbuf_addch(struct strbuf *sb, int c) {\n+static inline void strbuf_addch(struct strbuf *sb, int c)\n+{\n \tstrbuf_grow(sb, 1);\n \tsb->buf[sb->len++] = c;\n \tsb->buf[sb->len] = '\\0';\n@@ -113,10 +117,12 @@ extern void strbuf_splice(struct strbuf *, size_t pos, size_t len,\n extern void strbuf_add_commented_lines(struct strbuf *out, const char *buf, size_t size);\n \n extern void strbuf_add(struct strbuf *, const void *, size_t);\n-static inline void strbuf_addstr(struct strbuf *sb, const char *s) {\n+static inline void strbuf_addstr(struct strbuf *sb, const char *s)\n+{\n \tstrbuf_add(sb, s, strlen(s));\n }\n-static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) {\n+static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2)\n+{\n \tstrbuf_grow(sb, sb2->len);\n \tstrbuf_add(sb, sb2->buf, sb2->len);\n }\n-- \n1.9.0.40.gaa8c3ea\n"},{"id":"235691","messageId":"CACsJy8BQxZOQOHEuw9w2-gCqiFyP=YZ_PQzHkydnSM1j3hUK-Q@mail.gmail.com","threadId":"35908","inReplyTo":"xmqqr46p1kbo.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v3 15/25] setup.c: detect $GIT_COMMON_DIR in is_git_directory()","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T03:33:23Z","receivedAt":"2014-03-01T03:33:23Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Feb 27, 2014 at 7:16 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> +     if (file_exists(path.buf)) {\n>> +             if (strbuf_read_file(&data, path.buf, 0) <= 0)\n>> +                     die_errno(_(\"failed to read %s\"), path.buf);\n>\n> Do we care about the case where we cannot tell if the file exists\n> (e.g. stat() fails due to EPERM or something), or would it be not\n> worth worrying about?\n\nIn that case we assume (incorrectly) that the repository is complete.\nFollowing operations would fail. So not too bad, I think.\n\n>> @@ -188,14 +212,20 @@ int is_git_directory(const char *suspect)\n>>       int ret = 0;\n>>       size_t len;\n>>\n>> -     strbuf_addstr(&path, suspect);\n>> +     strbuf_addf(&path, \"%s/HEAD\", suspect);\n>\n>> +     if (validate_headref(path.buf))\n>> +             goto done;\n>\n> Is there a reason why we want to check HEAD before other stuff?\n> Just being curious, as I do not think of any (I am not saying that\n> we shouldn't change the order).\n\nYes, it's reordered so that worktree signature (e.g. HEAD) is checked\nfirst, against $GIT_DIR. Then non-worktree signatures (\"refs\" and\n\"objects\") are checked against $GIT_COMMON_DIR (or $GIT_DIR still if\n$GIT_DIR/commondir does not exist). Notice \"path\" is reset to\n$GIT_COMMON_DIR just after checking HEAD. I should probably add a\ncomment about this separation.\n-- \nDuy\n"},{"id":"235692","messageId":"CACsJy8A3mGCJzacaVrVUSxJnqXZ=GUhgOzpheUixpyWtYE6gDw@mail.gmail.com","threadId":"35908","inReplyTo":"xmqqr46q4xt6.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v3 07/25] reflog: avoid constructing .lock path with git_path","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T03:42:51Z","receivedAt":"2014-03-01T03:42:51Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Feb 26, 2014 at 5:44 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>\n>> git_path() soon understands the path given to it. Some paths \"abc\" may\n>> become \"def\" while other \"ghi\" may become \"ijk\". We don't want\n>> git_path() to interfere with .lock path construction. Concatenate\n>> \".lock\" after the path has been resolved by git_path() so if \"abc\"\n>> becomes \"def\", we'll have \"def.lock\", not \"ijk\".\n>\n> Hmph.  I am not sure if the above is readable (or if I am reading it\n> correctly).\n\nDefinitely not now that I have had my break from the series and reread it.\n\n> If \"abc\" becomes \"def\", it would take deliberate work to make\n> \"abc.lock\" into \"ijk\", and it would be natural to expect that\n> \"abc.lock\" would become \"def.lock\" without any fancy trick, no?\n\nA better explanation may be, while many paths are not converted by\ngit_path() (\"abc\" -> \"abc\"), some of them will be based on the given\npath (\"def\" -> \"ghi\"). Giving path def.lock to git_path() may confuse\nit and make it believe def.lock should not be converted because the\nsignature is \"def.lock\" not \"def\". But we want the lock file to have\nthe same base name with the locked file (e.g. \"ghi.lock\", not\n\"def.lock\"). So it's best to append \".lock\" after git_path() has done\nits conversion.\n\nThe alternative is teach git_path about \".lock\", but I don't really\nwant to put more logic down there.\n-- \nDuy\n"},{"id":"235716","messageId":"1393675983-3232-1-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1392730814-19656-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 00/27] Support multiple checkouts","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:12:36Z","receivedAt":"2014-03-01T12:12:36Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Phew.. v4 changes are\n\n- address all comments from Junio and Eric (I hope)\n- fix \"git checkout --to\" not working from the linked checkouts\n- report unused files (e.g. $GIT_DIR/repos/xx/config) in count-objects -v\n\nNguyễn Thái Ngọc Duy (27):\n  path.c: make get_pathname() return strbuf instead of static buffer\n  Convert git_snpath() to 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  Make git_path() 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  Add new environment variable $GIT_COMMON_DIR\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\n Documentation/config.txt               |   8 +\n Documentation/git-checkout.txt         |  34 ++++\n Documentation/git-prune.txt            |   3 +\n Documentation/git-rev-parse.txt        |   8 +\n Documentation/git.txt                  |   9 ++\n Documentation/gitrepository-layout.txt |  75 +++++++--\n builtin/branch.c                       |   4 +-\n builtin/checkout.c                     | 274 +++++++++++++++++++++++++++++++--\n builtin/commit.c                       |   2 +-\n builtin/count-objects.c                |  37 ++++-\n builtin/gc.c                           |  21 ++-\n builtin/init-db.c                      |   7 +-\n builtin/prune.c                        |  74 +++++++++\n builtin/reflog.c                       |   2 +-\n builtin/rev-parse.c                    |  11 ++\n cache.h                                |  10 +-\n compat/mingw.h                         |   1 +\n daemon.c                               |  11 +-\n environment.c                          |  24 ++-\n fast-import.c                          |   5 +-\n git-am.sh                              |  22 +--\n git-compat-util.h                      |   4 +\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 path.c                                 | 206 ++++++++++++++++---------\n refs.c                                 |  66 +++++---\n refs.h                                 |   2 +-\n setup.c                                | 124 +++++++++++----\n submodule.c                            |   9 +-\n t/t0060-path-utils.sh                  |  34 ++++\n t/t1501-worktree.sh                    |  76 +++++++++\n t/t1510-repo-setup.sh                  |   1 +\n t/t2025-checkout-to.sh (new +x)        |  57 +++++++\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 42 files changed, 1069 insertions(+), 226 deletions(-)\n\n-- \n1.9.0.40.gaa8c3ea\n"},{"id":"235717","messageId":"1393675983-3232-2-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 01/27] 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-03-01T12:12:37Z","receivedAt":"2014-03-01T12:12:37Z","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\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 24594c4..5346700 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.0.40.gaa8c3ea\n"},{"id":"235718","messageId":"1393675983-3232-3-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 02/27] Convert git_snpath() to strbuf_git_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:12:38Z","receivedAt":"2014-03-01T12:12:38Z","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 awkward\nbecause git_snpath() receives a pre-allocated buffer from outside and\nhas to copy data back. Rename it to strbuf_git_path() and make it\nreceive strbuf directly.\n\nThe conversion from git_snpath() to git_path() in\nupdate_refs_for_switch() is safe because that function does not keep\nany 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 | 22 +++++++++---------\n cache.h            |  4 ++--\n path.c             | 11 ++-------\n refs.c             | 66 +++++++++++++++++++++++++++++++++++-------------------\n refs.h             |  2 +-\n 5 files changed, 58 insertions(+), 47 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 5df3837..0570e41 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -585,18 +585,20 @@ 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\tstruct strbuf log_file = STRBUF_INIT;\n \t\t\t\tchar *ref_name = mkpath(\"refs/heads/%s\", opts->new_orphan_branch);\n+\t\t\t\tint ret;\n \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\n@@ -651,14 +653,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\t\t\tnew->name);\n \t\t\t}\n \t\t}\n-\t\tif (old->path && old->name) {\n-\t\t\tchar log_file[PATH_MAX], ref_file[PATH_MAX];\n-\n-\t\t\tgit_snpath(log_file, sizeof(log_file), \"logs/%s\", old->path);\n-\t\t\tgit_snpath(ref_file, sizeof(ref_file), \"%s\", old->path);\n-\t\t\tif (!file_exists(ref_file) && file_exists(log_file))\n-\t\t\t\tremove_path(log_file);\n-\t\t}\n+\t\tif (old->path && old->name &&\n+\t\t    !file_exists(git_path(\"%s\", old->path)) &&\n+\t\t     file_exists(git_path(\"logs/%s\", old->path)))\n+\t\t\tremove_path(git_path(\"logs/%s\", old->path));\n \t}\n \tremove_branch_state();\n \tstrbuf_release(&msg);\ndiff --git a/cache.h b/cache.h\nindex dc040fb..8d3697e 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -646,8 +646,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 5346700..b52a16f 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 89228e2..434bd5e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1325,10 +1325,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@@ -1337,15 +1339,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@@ -1359,10 +1363,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@@ -1373,7 +1378,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@@ -1389,7 +1394,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@@ -1402,12 +1407,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@@ -1424,9 +1430,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@@ -1436,10 +1443,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@@ -2717,17 +2729,19 @@ 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 *sb_logfile)\n {\n \tint logfd, oflags = O_APPEND | O_WRONLY;\n+\tconst char *logfile;\n \n-\tgit_snpath(logfile, bufsize, \"logs/%s\", refname);\n+\tstrbuf_git_path(sb_logfile, \"logs/%s\", refname);\n+\tlogfile = sb_logfile->buf;\n \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(sb_logfile->buf) < 0)\n \t\t\treturn error(\"unable to create directory for %s\",\n \t\t\t\t     logfile);\n \t\toflags |= O_CREAT;\n@@ -2762,20 +2776,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@@ -2788,9 +2804,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 87a1a79..783033a 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -166,7 +166,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 *ref_name, char *logfile, int bufsize);\n+int log_ref_setup(const char *ref_name, 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.0.40.gaa8c3ea\n"},{"id":"235719","messageId":"1393675983-3232-4-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 03/27] path.c: rename vsnpath() to do_git_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:12:39Z","receivedAt":"2014-03-01T12:12:39Z","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 b52a16f..29048fe 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 @@ 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.0.40.gaa8c3ea\n"},{"id":"235720","messageId":"1393675983-3232-5-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 04/27] 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-03-01T12:12:40Z","receivedAt":"2014-03-01T12:12:40Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n path.c | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/path.c b/path.c\nindex 29048fe..ccd7228 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+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 @@ char *mkpath(const char *fmt, ...)\n \treturn cleanup_path(pathname->buf);\n }\n \n-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.0.40.gaa8c3ea\n"},{"id":"235721","messageId":"1393675983-3232-6-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 05/27] Make git_path() aware of file relocation in $GIT_DIR","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:12:41Z","receivedAt":"2014-03-01T12:12:41Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"We allow the user to relocate certain paths out of $GIT_DIR via\nenvironment variables, e.g. GIT_OBJECT_DIRECTORY, GIT_INDEX_FILE and\nGIT_GRAFT_FILE. All callers are not supposed to use git_path() or\ngit_pathdup() to get those paths. Instead they must use\nget_object_directory(), get_index_file() and get_graft_file()\nrespectively. This is inconvenient and could be missed in review\n(there's git_path(\"objects/info/alternates\") somewhere in\nsha1_file.c).\n\nThis patch makes git_path() and git_pathdup() understand those\nenvironment variables. So if you set GIT_OBJECT_DIRECTORY to /foo/bar,\ngit_path(\"objects/abc\") should return /tmp/bar/abc. The same is done\nfor the two remaining env variables.\n\n\"git rev-parse --git-path\" is the wrapper for script use.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-rev-parse.txt |  5 +++++\n builtin/rev-parse.c             |  7 +++++++\n cache.h                         |  1 +\n environment.c                   |  9 ++++++--\n path.c                          | 46 +++++++++++++++++++++++++++++++++++++++++\n t/t0060-path-utils.sh           | 19 +++++++++++++++++\n 6 files changed, 85 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt\nindex 0d2cdcd..33e4e90 100644\n--- a/Documentation/git-rev-parse.txt\n+++ b/Documentation/git-rev-parse.txt\n@@ -232,6 +232,11 @@ 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.\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 aaeb611..e50bc65 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -518,6 +518,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 8d3697e..6c08e4a 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -585,6 +585,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 4a3437d..f513479 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -82,6 +82,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@@ -137,15 +138,19 @@ static void setup_git_env(void)\n \tif (!git_object_dir) {\n \t\tgit_object_dir = xmalloc(strlen(git_dir) + 9);\n \t\tsprintf(git_object_dir, \"%s/objects\", git_dir);\n-\t}\n+\t} else\n+\t\tgit_db_env = 1;\n \tgit_index_file = getenv(INDEX_ENVIRONMENT);\n \tif (!git_index_file) {\n \t\tgit_index_file = xmalloc(strlen(git_dir) + 7);\n \t\tsprintf(git_index_file, \"%s/index\", git_dir);\n-\t}\n+\t} else\n+\t\tgit_index_env = 1;\n \tgit_graft_file = getenv(GRAFT_ENVIRONMENT);\n \tif (!git_graft_file)\n \t\tgit_graft_file = git_pathdup(\"info/grafts\");\n+\telse\n+\t\tgit_graft_env = 1;\n \tif (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))\n \t\tread_replace_refs = 0;\n \tnamespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));\ndiff --git a/path.c b/path.c\nindex ccd7228..e020530 100644\n--- a/path.c\n+++ b/path.c\n@@ -60,13 +60,59 @@ 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+\tint gitdir_len;\n \tstrbuf_addstr(buf, 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 07c10c8..1d29901 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@@ -223,4 +231,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.0.40.gaa8c3ea\n"},{"id":"235722","messageId":"1393675983-3232-7-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 06/27] *.sh: respect $GIT_INDEX_FILE","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:12:42Z","receivedAt":"2014-03-01T12:12: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 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 0a5aa2c..c9dc9ba 100755\n--- a/git-pull.sh\n+++ b/git-pull.sh\n@@ -218,7 +218,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 f0a94ab..ae7d16e 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.0.40.gaa8c3ea\n"},{"id":"235723","messageId":"1393675983-3232-8-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 07/27] reflog: avoid constructing .lock path with git_path","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:12:43Z","receivedAt":"2014-03-01T12:12:43Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"git_path() soon understands the path given to it and can transform the\npath instead of just prepending $GIT_DIR. So given path \"abc\",\ngit_path() may return \"$GIT_DIR/abc\". But given path \"def\", git_path()\nmay return \"$GIT_DIR/ghi\".\n\nGiving path \"def.lock\" to git_path() may confuse it and make it\nbelieve \"def.lock\" should not be transformed because the signature is\n\"def.lock\" not \"def\". But we want the lock file to have the same base\nname with the locked file (e.g. \"ghi.lock\", not \"def.lock\"). It's best\nto append \".lock\" after git_path() has done its conversion.\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 852cff6..ccf2cf6 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -372,7 +372,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,\n \tif (!file_exists(log_file))\n \t\tgoto finish;\n \tif (!cmd->dry_run) {\n-\t\tnewlog_path = git_pathdup(\"logs/%s.lock\", ref);\n+\t\tnewlog_path = mkpathdup(\"%s.lock\", log_file);\n \t\tcb.newlog = fopen(newlog_path, \"w\");\n \t}\n \n-- \n1.9.0.40.gaa8c3ea\n"},{"id":"235724","messageId":"1393675983-3232-9-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 08/27] 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-03-01T12:12:44Z","receivedAt":"2014-03-01T12:12:44Z","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 4fd18a3..08a1e78 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -3125,12 +3125,9 @@ static void parse_progress(void)\n \n static char* make_fast_import_path(const char *path)\n {\n-\tstruct strbuf abs_path = STRBUF_INIT;\n-\n \tif (!relative_marks_paths || is_absolute_path(path))\n \t\treturn xstrdup(path);\n-\tstrbuf_addf(&abs_path, \"%s/info/fast-import/%s\", get_git_dir(), path);\n-\treturn strbuf_detach(&abs_path, NULL);\n+\treturn xstrdup(git_path(\"info/fast-import/%s\", path));\n }\n \n static void option_import_marks(const char *marks,\n-- \n1.9.0.40.gaa8c3ea\n"},{"id":"235725","messageId":"1393675983-3232-10-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 09/27] commit: use SEQ_DIR instead of hardcoding \"sequencer\"","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:12:45Z","receivedAt":"2014-03-01T12:12:45Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/commit.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 3767478..ee3ac10 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -155,7 +155,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.0.40.gaa8c3ea\n"},{"id":"235726","messageId":"1393675983-3232-11-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 10/27] Add new environment variable $GIT_COMMON_DIR","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:12:46Z","receivedAt":"2014-03-01T12:12:46Z","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\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                          | 19 +++++++++++----\n path.c                                 | 29 +++++++++++++++++++++++\n t/t0060-path-utils.sh                  | 15 ++++++++++++\n 6 files changed, 103 insertions(+), 14 deletions(-)\n\ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex 02bbc08..b094b1f 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -773,6 +773,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 aa03882..10672a1 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 $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 $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 $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 $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 $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 $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@@ -193,12 +209,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 $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@@ -209,10 +229,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 $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 $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 6c08e4a..51ade32 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -347,6 +347,7 @@ static inline enum object_type object_type(unsigned int mode)\n \n /* Double-check local_repo_env below if you add to this list. */\n #define GIT_DIR_ENVIRONMENT \"GIT_DIR\"\n+#define GIT_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@@ -400,6 +401,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@@ -585,7 +587,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 f513479..c998120 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -80,9 +80,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@@ -134,10 +134,16 @@ 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+\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 = 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\tgit_object_dir = xmalloc(strlen(git_common_dir) + 9);\n+\t\tsprintf(git_object_dir, \"%s/objects\", git_common_dir);\n \t} else\n \t\tgit_db_env = 1;\n \tgit_index_file = getenv(INDEX_ENVIRONMENT);\n@@ -173,6 +179,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 e020530..6129026 100644\n--- a/path.c\n+++ b/path.c\n@@ -90,6 +90,33 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)\n \t\tbuf->buf[newlen] = '/';\n }\n \n+static void update_common_dir(struct strbuf *buf, int git_dir_len)\n+{\n+\tconst char *common_dir_list[] = {\n+\t\t\"branches\", \"hooks\", \"info\", \"logs\", \"lost-found\", \"modules\",\n+\t\t\"objects\", \"refs\", \"remotes\", \"rr-cache\", \"svn\",\n+\t\tNULL\n+\t};\n+\tconst char *common_top_file_list[] = {\n+\t\t\"config\", \"gc.pid\", \"packed-refs\", \"shallow\", NULL\n+\t};\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_dir_list; *p; p++)\n+\t\tif (dir_prefix(base, *p)) {\n+\t\t\treplace_dir(buf, git_dir_len, get_git_common_dir());\n+\t\t\treturn;\n+\t\t}\n+\tfor (p = common_top_file_list; *p; p++)\n+\t\tif (!strcmp(base, *p)) {\n+\t\t\treplace_dir(buf, git_dir_len, get_git_common_dir());\n+\t\t\treturn;\n+\t\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 +128,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 1d29901..f9a77e4 100755\n--- a/t/t0060-path-utils.sh\n+++ b/t/t0060-path-utils.sh\n@@ -241,5 +241,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.0.40.gaa8c3ea\n"},{"id":"235727","messageId":"1393675983-3232-12-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 11/27] 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-03-01T12:12:47Z","receivedAt":"2014-03-01T12:12:47Z","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 fffa3c7..475ca43 100644\n--- a/git-sh-setup.sh\n+++ b/git-sh-setup.sh\n@@ -343,7 +343,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.0.40.gaa8c3ea\n"},{"id":"235728","messageId":"1393675983-3232-13-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 12/27] *.sh: avoid hardcoding $GIT_DIR/hooks/...","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:12:48Z","receivedAt":"2014-03-01T12:12:48Z","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 bbea430..dfa0618 100755\n--- a/git-am.sh\n+++ b/git-am.sh\n@@ -803,10 +803,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@@ -880,9 +880,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@@ -908,18 +909,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 43c19e0..d741b04 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -632,9 +632,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 e7d96de..68f5d09 100644\n--- a/git-rebase--merge.sh\n+++ b/git-rebase--merge.sh\n@@ -93,10 +93,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 8a3efa2..1cf8dba 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -195,9 +195,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..28b843b 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..51aa244 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.0.40.gaa8c3ea\n"},{"id":"235729","messageId":"1393675983-3232-14-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 13/27] git-stash: avoid hardcoding $GIT_DIR/logs/....","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:12:49Z","receivedAt":"2014-03-01T12:12:49Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n git-stash.sh | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/git-stash.sh b/git-stash.sh\nindex ae7d16e..12d9b37 100755\n--- a/git-stash.sh\n+++ b/git-stash.sh\n@@ -183,7 +183,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@@ -258,7 +258,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.0.40.gaa8c3ea\n"},{"id":"235730","messageId":"1393675983-3232-15-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 14/27] setup.c: convert is_git_directory() to use strbuf","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:12:50Z","receivedAt":"2014-03-01T12:12: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 setup.c | 37 +++++++++++++++++++++----------------\n 1 file changed, 21 insertions(+), 16 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 6c3f85f..4994437 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -184,31 +184,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.0.40.gaa8c3ea\n"},{"id":"235731","messageId":"1393675983-3232-16-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 15/27] setup.c: detect $GIT_COMMON_DIR in is_git_directory()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:12:51Z","receivedAt":"2014-03-01T12:12:51Z","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 10672a1..f72ce75 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -233,6 +233,13 @@ shallow::\n \tfile is ignored $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 $GIT_COMMON_DIR is set and\ndiff --git a/setup.c b/setup.c\nindex 4994437..7e5b334 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -170,6 +170,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@@ -188,13 +215,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@@ -205,11 +241,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.0.40.gaa8c3ea\n"},{"id":"235732","messageId":"1393675983-3232-17-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 16/27] setup.c: convert check_repository_format_gently to use strbuf","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:12:52Z","receivedAt":"2014-03-01T12:12: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 | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 7e5b334..5085ab1 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -288,7 +288,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@@ -299,7 +301,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@@ -309,9 +312,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.0.40.gaa8c3ea\n"},{"id":"235733","messageId":"1393675983-3232-18-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 17/27] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:12:53Z","receivedAt":"2014-03-01T12:12:53Z","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 5085ab1..42849f3 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -292,6 +292,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@@ -301,8 +305,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.0.40.gaa8c3ea\n"},{"id":"235734","messageId":"1393675983-3232-19-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 18/27] setup.c: support multi-checkout repo setup","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:12:54Z","receivedAt":"2014-03-01T12:12:54Z","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_DIR/commondir presents. This is\nbecause \"commondir\" repos are intended for separate/linked checkouts\nand pointing them back to a fixed core.worktree just does not make\nsense.\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 5f4d793..313d4b3 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -381,6 +381,8 @@ false), while all other repositories are assumed to be bare (bare\n \n core.worktree::\n \tSet the path to the root of the working tree.\n+\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 33e4e90..8e6ad32 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 e50bc65..c7057ce 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -744,6 +744,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 = resolve_gitdir(argv[i+1]);\n \t\t\t\tif (!gitdir)\ndiff --git a/cache.h b/cache.h\nindex 51ade32..98b5dd3 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -407,6 +407,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 c998120..0999fc1 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -126,6 +126,7 @@ static char *expand_namespace(const char *raw_namespace)\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@@ -134,12 +135,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 = getenv(DB_ENVIRONMENT);\n \tif (!git_object_dir) {\n \t\tgit_object_dir = xmalloc(strlen(git_common_dir) + 9);\ndiff --git a/setup.c b/setup.c\nindex 42849f3..40ce191 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -170,14 +170,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@@ -192,10 +193,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@@ -286,13 +289,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@@ -305,7 +321,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@@ -777,11 +793,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..2ac4424 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\techo \"$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\techo \"$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\techo \"$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\techo \"$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 cf2ee78..c211ef5 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.0.40.gaa8c3ea\n"},{"id":"235735","messageId":"1393675983-3232-20-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 19/27] wrapper.c: wrapper to open a file, fprintf then close","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:12:55Z","receivedAt":"2014-03-01T12:12: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 cache.h   |  2 ++\n wrapper.c | 31 +++++++++++++++++++++++++++++++\n 2 files changed, 33 insertions(+)\n\ndiff --git a/cache.h b/cache.h\nindex 98b5dd3..99b86d9 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1239,6 +1239,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 0cc5636..5ced50d 100644\n--- a/wrapper.c\n+++ b/wrapper.c\n@@ -455,3 +455,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+\tint fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);\n+\tva_list params;\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\terrno = err;\n+\t\tstrbuf_release(&sb);\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.0.40.gaa8c3ea\n"},{"id":"235744","messageId":"1393675983-3232-21-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 20/27] use new wrapper write_file() for simple file writing","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:12:56Z","receivedAt":"2014-03-01T12:12:56Z","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 b4d7716..3eebdbc 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 c7c76bb..081e512 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 503e039..b880d30 100644\n--- a/daemon.c\n+++ b/daemon.c\n@@ -1122,15 +1122,6 @@ static void daemonize(void)\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@@ -1339,7 +1330,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 613857e..fe5748d 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1135,16 +1135,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 ca7bb44..2df8a15 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.0.40.gaa8c3ea\n"},{"id":"235736","messageId":"1393675983-3232-22-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 21/27] checkout: support checking out into a new working directory","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:12:57Z","receivedAt":"2014-03-01T12:12:57Z","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 b094b1f..bdb9b0f 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -777,7 +777,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 f72ce75..418e5c8 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -245,6 +245,13 @@ modules::\n \tdirectory is ignored $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 0570e41..fa7b54a 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@@ -486,7 +493,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 6129026..47383ff 100644\n--- a/path.c\n+++ b/path.c\n@@ -94,7 +94,7 @@ static void update_common_dir(struct strbuf *buf, int git_dir_len)\n {\n \tconst char *common_dir_list[] = {\n \t\t\"branches\", \"hooks\", \"info\", \"logs\", \"lost-found\", \"modules\",\n-\t\t\"objects\", \"refs\", \"remotes\", \"rr-cache\", \"svn\",\n+\t\t\"objects\", \"refs\", \"remotes\", \"repos\", \"rr-cache\", \"svn\",\n \t\tNULL\n \t};\n \tconst char *common_top_file_list[] = {\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.0.40.gaa8c3ea\n"},{"id":"235737","messageId":"1393675983-3232-23-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 22/27] checkout: clean up half-prepared directories in --to mode","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:12:58Z","receivedAt":"2014-03-01T12:12:58Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/checkout.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 47 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex fa7b54a..28f9ac1 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\tstrbuf_reset(&sb);\n+\t}\n+}\n+\n+static void remove_junk_on_signal(int signo)\n+{\n+\tremove_junk();\n+\tsigchain_pop(signo);\n+\traise(signo);\n+}\n+\n static int 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.0.40.gaa8c3ea\n"},{"id":"235738","messageId":"1393675983-3232-24-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 23/27] checkout: detach if the branch is already checked out elsewhere","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:12:59Z","receivedAt":"2014-03-01T12:12:59Z","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 28f9ac1..1675808 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -433,6 +433,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.0.40.gaa8c3ea\n"},{"id":"235739","messageId":"1393675983-3232-25-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 24/27] prune: strategies for linked checkouts","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:13:00Z","receivedAt":"2014-03-01T12:13:00Z","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 - \"git checkout --to\" is supposed to create $R/locked if the new repo\n   is on a different partition than the shared one. The main use case\n   is when the checkout is on a portable device and may not be\n   available at prune time.\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                     | 36 ++++++++++++++++-\n builtin/prune.c                        | 74 ++++++++++++++++++++++++++++++++++\n compat/mingw.h                         |  1 +\n git-compat-util.h                      |  4 ++\n setup.c                                | 13 ++++++\n 7 files changed, 149 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt\nindex 058ac0d..7babf11 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 418e5c8..2dc6901 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -252,6 +252,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 1675808..1fc85d3 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -854,6 +854,17 @@ static void remove_junk_on_signal(int signo)\n \traise(signo);\n }\n \n+static dev_t get_device_or_die(const char *path)\n+{\n+\tstruct stat buf;\n+\tif (stat(path, &buf))\n+\t\tdie_errno(\"failed to stat '%s'\", path);\n+\t/* Ah Windows! Make different drives different \"partitions\" */\n+\tif (is_windows())\n+\t\tbuf.st_dev = toupper(real_path(path)[0]);\n+\treturn buf.st_dev;\n+}\n+\n static int prepare_linked_checkout(const struct checkout_opts *opts,\n \t\t\t\t   struct branch_info *new)\n {\n@@ -862,7 +873,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, ret;\n+\tint counter = 0, len, keep_locked = 0, ret;\n \n \tif (!new->commit)\n \t\tdie(_(\"no branch specified\"));\n@@ -898,12 +909,18 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n \tjunk_git_dir = sb_repo.buf;\n \tis_junk = 1;\n \n+\tstrbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n+\twrite_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,12 +929,24 @@ 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 \tstrbuf_addf(&sb, \"%s/commondir\", sb_repo.buf);\n \twrite_file(sb.buf, 1, \"../..\\n\");\n \n+\tif (get_device_or_die(path) != get_device_or_die(get_git_dir())) {\n+\t\tstrbuf_reset(&sb);\n+\t\tstrbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n+\t\twrite_file(sb.buf, 1, \"located on a different file system\\n\");\n+\t\tkeep_locked = 1;\n+\t} else {\n+\t\tstrbuf_reset(&sb);\n+\t\tstrbuf_addf(&sb, \"%s/link\", sb_repo.buf);\n+\t\t(void)link(sb_git.buf, sb.buf);\n+\t}\n+\n \tif (!opts->quiet)\n \t\tfprintf_ln(stderr, _(\"Enter %s (identifier %s)\"), path, name);\n \n@@ -930,6 +959,11 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n \tret = run_command(&cp);\n \tif (!ret)\n \t\tis_junk = 0;\n+\tif (!keep_locked) {\n+\t\tstrbuf_reset(&sb);\n+\t\tstrbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n+\t\tunlink_or_warn(sb.buf);\n+\t}\n \tstrbuf_release(&sb);\n \tstrbuf_release(&sb_repo);\n \tstrbuf_release(&sb_git);\ndiff --git a/builtin/prune.c b/builtin/prune.c\nindex de43b26..733cb3b 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/compat/mingw.h b/compat/mingw.h\nindex e033e72..18323c1 100644\n--- a/compat/mingw.h\n+++ b/compat/mingw.h\n@@ -328,6 +328,7 @@ int winansi_fprintf(FILE *stream, const char *format, ...) __attribute__((format\n  * git specific compatibility\n  */\n \n+#define is_windows() 1\n #define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':')\n #define is_dir_sep(c) ((c) == '/' || (c) == '\\\\')\n static inline char *mingw_find_last_dir_sep(const char *path)\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex cbd86c3..41f1b74 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -266,6 +266,10 @@ extern char *gitbasename(char *);\n #define STRIP_EXTENSION \"\"\n #endif\n \n+#ifndef is_windows\n+#define is_windows() 0\n+#endif\n+\n #ifndef has_dos_drive_prefix\n #define has_dos_drive_prefix(path) 0\n #endif\ndiff --git a/setup.c b/setup.c\nindex 40ce191..5529b26 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -336,6 +336,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@@ -384,6 +395,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.0.40.gaa8c3ea\n"},{"id":"235740","messageId":"1393675983-3232-26-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 25/27] gc: style change -- no SP before closing bracket","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:13:01Z","receivedAt":"2014-03-01T12:13:01Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/gc.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex c19545d..39d9b27 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -260,7 +260,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@@ -273,7 +273,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.0.40.gaa8c3ea\n"},{"id":"235741","messageId":"1393675983-3232-27-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 26/27] gc: support prune --repos","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:13:02Z","receivedAt":"2014-03-01T12:13: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 Documentation/config.txt |  6 ++++++\n builtin/gc.c             | 17 +++++++++++++++++\n 2 files changed, 23 insertions(+)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 313d4b3..438b213 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1183,6 +1183,12 @@ 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 '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 this  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 39d9b27..85c3c0c 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -30,11 +30,13 @@ static int aggressive_window = 250;\n static int gc_auto_threshold = 6700;\n static int gc_auto_pack_limit = 50;\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@@ -81,6 +83,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@@ -274,6 +284,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@@ -334,6 +345,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.0.40.gaa8c3ea\n"},{"id":"235742","messageId":"1393675983-3232-28-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4 27/27] count-objects: report unused files in $GIT_DIR/repos/...","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-01T12:13:03Z","receivedAt":"2014-03-01T12:13:03Z","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 | 37 ++++++++++++++++++++++++++++++++++++-\n path.c                  |  4 ++++\n 2 files changed, 40 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/count-objects.c b/builtin/count-objects.c\nindex a7f70cb..725cd5f 100644\n--- a/builtin/count-objects.c\n+++ b/builtin/count-objects.c\n@@ -78,6 +78,39 @@ static void count_objects(DIR *d, char *path, int len, int verbose,\n \t}\n }\n \n+static void report_linked_checkout_garbage(void)\n+{\n+\t/*\n+\t * must be more or less in sync with * path.c:update_common_dir().\n+\t *\n+\t * \"logs\" is let slip because logs/HEAD is in $GIT_DIR but the\n+\t * remaining in $GIT_COMMON_DIR. Probably not worth traversing\n+\t * the entire \"logs\" directory for that.\n+\t *\n+\t * The same \"gc.pid\" for because it's a temporary file.\n+\t */\n+\tconst char *list[] = {\n+\t\t\"branches\", \"hooks\", \"info\", \"lost-found\", \"modules\",\n+\t\t\"objects\", \"refs\", \"remotes\", \"rr-cache\", \"svn\",\n+\t\t\"config\", \"packed-refs\", \"shallow\", NULL\n+\t};\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tconst char **p;\n+\tint len;\n+\n+\tif (!file_exists(git_path(\"commondir\")))\n+\t\treturn;\n+\tstrbuf_addf(&sb, \"%s/\", get_git_dir());\n+\tlen = sb.len;\n+\tfor (p = list; *p; p++) {\n+\t\tstrbuf_setlen(&sb, len);\n+\t\tstrbuf_addstr(&sb, *p);\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 char const * const count_objects_usage[] = {\n \tN_(\"git count-objects [-v] [-H | --human-readable]\"),\n \tNULL\n@@ -102,8 +135,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/path.c b/path.c\nindex 47383ff..2e6035d 100644\n--- a/path.c\n+++ b/path.c\n@@ -92,6 +92,10 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)\n \n static void update_common_dir(struct strbuf *buf, int git_dir_len)\n {\n+\t/*\n+\t * Remember to report_linked_checkout_garbage()\n+\t * builtin/count-objects.c\n+\t */\n \tconst char *common_dir_list[] = {\n \t\t\"branches\", \"hooks\", \"info\", \"logs\", \"lost-found\", \"modules\",\n \t\t\"objects\", \"refs\", \"remotes\", \"repos\", \"rr-cache\", \"svn\",\n-- \n1.9.0.40.gaa8c3ea\n"},{"id":"235753","messageId":"531201D4.6090900@web.de","threadId":"35908","inReplyTo":"1393675983-3232-14-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v4 13/27] git-stash: avoid hardcoding $GIT_DIR/logs/....","fromName":"Torsten Bögershausen","fromEmail":"tboegi@web.de","sentAt":"2014-03-01T15:50:44Z","receivedAt":"2014-03-01T15:50:44Z","isPatch":true,"sender":{"key":"tboegi@web.de","avatar":"https://avatars.githubusercontent.com/u/7138363?v=4"},"body":"On 2014-03-01 13.12, Nguyễn Thái Ngọc Duy wrote:\n> 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> \n> diff --git a/git-stash.sh b/git-stash.sh\n> index ae7d16e..12d9b37 100755\n> --- a/git-stash.sh\n> +++ b/git-stash.sh\n> @@ -183,7 +183,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            \"$(git rev-parse --git-path logs/$ref_stash)\"\nShouldn't we prefer $() over `` ?\n"},{"id":"235756","messageId":"531214BC.3070704@web.de","threadId":"35908","inReplyTo":"1393675983-3232-20-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v4 19/27] wrapper.c: wrapper to open a file, fprintf then close","fromName":"Torsten Bögershausen","fromEmail":"tboegi@web.de","sentAt":"2014-03-01T17:11:24Z","receivedAt":"2014-03-01T17:11:24Z","isPatch":true,"sender":{"key":"tboegi@web.de","avatar":"https://avatars.githubusercontent.com/u/7138363?v=4"},"body":"On 2014-03-01 13.12, Nguyễn Thái Ngọc Duy wrote:\n> 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> \n> diff --git a/cache.h b/cache.h\n> index 98b5dd3..99b86d9 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -1239,6 +1239,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);\n> diff --git a/wrapper.c b/wrapper.c\n> index 0cc5636..5ced50d 100644\n> --- a/wrapper.c\n> +++ b/wrapper.c\n> @@ -455,3 +455,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> +\tint fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);\n> +\tva_list params;\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\terrno = err;\n> +\t\tstrbuf_release(&sb);\nMicro nit:\nToday we now what strbuf_release() is doing, but if we ever change the\nimplementation, it is 3% safer to keep err a little bit longer like this:\n> +\t\tstrbuf_release(&sb);\n> +\t\terrno = err;\n"},{"id":"235757","messageId":"5312151B.6080605@web.de","threadId":"35908","inReplyTo":"1393675983-3232-25-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v4 24/27] prune: strategies for linked checkouts","fromName":"Torsten Bögershausen","fromEmail":"tboegi@web.de","sentAt":"2014-03-01T17:12:59Z","receivedAt":"2014-03-01T17:12:59Z","isPatch":true,"sender":{"key":"tboegi@web.de","avatar":"https://avatars.githubusercontent.com/u/7138363?v=4"},"body":"On 2014-03-01 13.13, Nguyễn Thái Ngọc Duy wrote:\n[]\n>  \n> +static dev_t get_device_or_die(const char *path)\n> +{\n> +\tstruct stat buf;\n> +\tif (stat(path, &buf))\n> +\t\tdie_errno(\"failed to stat '%s'\", path);\n> +\t/* Ah Windows! Make different drives different \"partitions\" */\n> +\tif (is_windows())\n> +\t\tbuf.st_dev = toupper(real_path(path)[0]);\n> +\treturn buf.st_dev;\n\nIs this only related to Windows ?\nDo we have other file systems, which return st_dev == 0 ?\nShould we check that path[0] != '/', or better !is_dir_sep(path[0]) ?\nDo we need has_dos_drive_prefix() ?\n\nAs a first suggestion, would this be better:\n\n> +\tif (!buf.st_dev)\n> +\t\tbuf.st_dev = toupper(real_path(path)[0]);\n\n(End of loose thinking)\n"},{"id":"235765","messageId":"CACsJy8CbE0VH_2bAH3weokuHVMUor9es7bgT7rWFi28fJjZ36w@mail.gmail.com","threadId":"35908","inReplyTo":"5312151B.6080605@web.de","subject":"Re: [PATCH v4 24/27] prune: strategies for linked checkouts","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-02T00:01:23Z","receivedAt":"2014-03-02T00:01:23Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Sun, Mar 2, 2014 at 12:12 AM, Torsten Bögershausen <tboegi@web.de> wrote:\n> On 2014-03-01 13.13, Nguyễn Thái Ngọc Duy wrote:\n> []\n>>\n>> +static dev_t get_device_or_die(const char *path)\n>> +{\n>> +     struct stat buf;\n>> +     if (stat(path, &buf))\n>> +             die_errno(\"failed to stat '%s'\", path);\n>> +     /* Ah Windows! Make different drives different \"partitions\" */\n>> +     if (is_windows())\n>> +             buf.st_dev = toupper(real_path(path)[0]);\n>> +     return buf.st_dev;\n>\n> Is this only related to Windows ?\n\nYes. At least the treatment is Windows specific. If st_dev == 0 in\nother cases, then we have to deal with them case-by-case.\n\n> Do we have other file systems, which return st_dev == 0 ?\n> Should we check that path[0] != '/', or better !is_dir_sep(path[0]) ?\n> Do we need has_dos_drive_prefix() ?\n\nreal_path() returns an absolute path, so we're guaranteed its first\ncharacter is the drive letter, right? (I tried to confirm this by\nreading read_path_internal, but it's a bit complex, and I don't have\nWindows machine to quickly test it out)\n\n>\n> As a first suggestion, would this be better:\n>\n>> +     if (!buf.st_dev)\n>> +             buf.st_dev = toupper(real_path(path)[0]);\n>\n> (End of loose thinking)\n\n\n\n-- \nDuy\n"},{"id":"235808","messageId":"CAPig+cQubZByK3rNPOJy8rfLZpALpW0dOMz55gnFWRPppTAApg@mail.gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-2-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v4 01/27] path.c: make get_pathname() return strbuf instead of static buffer","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-03-02T19:51:35Z","receivedAt":"2014-03-02T19:51:35Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sat, Mar 1, 2014 at 7:12 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> We've been avoiding PATH_MAX whenever possible. This patch makes\n> get_pathname() return a strbuf and updates the callers to take\n> advantage of this. The code is simplified as we no longer need to\n> worry about buffer overflow.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/path.c b/path.c\n> index 24594c4..5346700 100644\n> --- a/path.c\n> +++ b/path.c\n> @@ -49,85 +60,70 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)\n>         return cleanup_path(buf);\n>  }\n>\n> -static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)\n> +static void vsnpath(struct strbuf *buf, const char *fmt, va_list args)\n>  {\n>         const char *git_dir = get_git_dir();\n> -       size_t len;\n> -\n> -       len = strlen(git_dir);\n> -       if (n < len + 1)\n> -               goto bad;\n> -       memcpy(buf, git_dir, len);\n> -       if (len && !is_dir_sep(git_dir[len-1]))\n> -               buf[len++] = '/';\n> -       len += vsnprintf(buf + len, n - len, fmt, args);\n> -       if (len >= n)\n> -               goto bad;\n> -       return cleanup_path(buf);\n> -bad:\n> -       strlcpy(buf, bad_path, n);\n> -       return buf;\n> +       strbuf_addstr(buf, git_dir);\n> +       if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))\n> +               strbuf_addch(buf, '/');\n> +       strbuf_vaddf(buf, fmt, args);\n> +       strbuf_cleanup_path(buf);\n>  }\n\nThere's a slight semantic change here. The old code overwrote 'buf',\nbut the new code appends to 'buf'. For someone familiar with\nsprintf(), or typical va_list or variadic functions there may be an\nintuitive expectation that 'buf' will be overwritten. Should this code\nbe doing strbuf_reset() as its first action (or should that be the\nresponsibility of the caller who is reusing 'buff')?\n\n>  char *mkpath(const char *fmt, ...)\n>  {\n>         va_list args;\n> -       unsigned len;\n> -       char *pathname = get_pathname();\n> -\n> +       struct strbuf *pathname = get_pathname();\n>         va_start(args, fmt);\n> -       len = vsnprintf(pathname, PATH_MAX, fmt, args);\n> +       strbuf_vaddf(pathname, fmt, args);\n>         va_end(args);\n> -       if (len >= PATH_MAX)\n> -               return bad_path;\n> -       return cleanup_path(pathname);\n> +       return cleanup_path(pathname->buf);\n>  }\n\nPrior to this change, it was possible (though probably not\nrecommended) for a caller to append gunk to the returned path up to\nPATH_MAX without worrying about stomping memory. With the change, this\nis no longer true. Should the function be changed to return 'const\nchar *' to enforce this restriction?\n\n>  char *git_path(const char *fmt, ...)\n>  {\n> -       char *pathname = get_pathname();\n> +       struct strbuf *pathname = get_pathname();\n>         va_list args;\n> -       char *ret;\n> -\n>         va_start(args, fmt);\n> -       ret = vsnpath(pathname, PATH_MAX, fmt, args);\n> +       vsnpath(pathname, fmt, args);\n>         va_end(args);\n> -       return ret;\n> +       return pathname->buf;\n>  }\n\nDitto.\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> -       char *pathname = get_pathname();\n> -       struct strbuf buf = STRBUF_INIT;\n> +       struct strbuf *buf = get_pathname();\n> ...\n> +       strbuf_cleanup_path(buf);\n> +       return buf->buf;\n>  }\n\nAnd here?\n\n>\n>  int validate_headref(const char *path)\n> --\n> 1.9.0.40.gaa8c3ea\n"},{"id":"235816","messageId":"CAPig+cTjqrveuOS+3+bonwJa_Kjg=STJU1JJ2Kj7Gs5U9eEB9Q@mail.gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-3-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v4 02/27] Convert git_snpath() to strbuf_git_path()","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-03-03T00:02:16Z","receivedAt":"2014-03-03T00:02:16Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sat, Mar 1, 2014 at 7:12 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> In the previous patch, git_snpath() is modified to allocate a new\n> strbuf buffer because vsnpath() needs that. But that makes it awkward\n> because git_snpath() receives a pre-allocated buffer from outside and\n> has to copy data back. Rename it to strbuf_git_path() and make it\n> receive strbuf directly.\n>\n> The conversion from git_snpath() to git_path() in\n> update_refs_for_switch() is safe because that function does not keep\n> any pointer to the round-robin buffer pool allocated by\n> get_pathname().\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/refs.c b/refs.c\n> index 89228e2..434bd5e 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -2717,17 +2729,19 @@ static int copy_msg(char *buf, const char *msg)\n>         return 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 *sb_logfile)\n>  {\n>         int logfd, oflags = O_APPEND | O_WRONLY;\n> +       const char *logfile;\n>\n> -       git_snpath(logfile, bufsize, \"logs/%s\", refname);\n> +       strbuf_git_path(sb_logfile, \"logs/%s\", refname);\n> +       logfile = sb_logfile->buf;\n>         if (log_all_ref_updates &&\n>             (starts_with(refname, \"refs/heads/\") ||\n>              starts_with(refname, \"refs/remotes/\") ||\n>              starts_with(refname, \"refs/notes/\") ||\n>              !strcmp(refname, \"HEAD\"))) {\n> -               if (safe_create_leading_directories(logfile) < 0)\n> +               if (safe_create_leading_directories(sb_logfile->buf) < 0)\n\nAt this point, 'logfile' is still 'sb_logfile->buf', so do you really\nneed this change?\n\n>                         return error(\"unable to create directory for %s\",\n>                                      logfile);\n>                 oflags |= O_CREAT;\n> @@ -2762,20 +2776,22 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,\n>         int logfd, result, written, oflags = O_APPEND | O_WRONLY;\n>         unsigned maxlen, len;\n>         int msglen;\n> -       char log_file[PATH_MAX];\n> +       struct strbuf sb_log_file = STRBUF_INIT;\n> +       const char *log_file;\n>         char *logrec;\n>         const char *committer;\n>\n>         if (log_all_ref_updates < 0)\n>                 log_all_ref_updates = !is_bare_repository();\n>\n> -       result = log_ref_setup(refname, log_file, sizeof(log_file));\n> +       result = log_ref_setup(refname, &sb_log_file);\n>         if (result)\n> -               return result;\n> +               goto done;\n> +       log_file = sb_log_file.buf;\n>\n>         logfd = open(log_file, oflags);\n>         if (logfd < 0)\n> -               return 0;\n> +               goto done;\n>         msglen = msg ? strlen(msg) : 0;\n>         committer = git_committer_info(0);\n>         maxlen = strlen(committer) + msglen + 100;\n> --\n> 1.9.0.40.gaa8c3ea\n"},{"id":"235817","messageId":"CACsJy8DEkMJ+RD8ekLWRCy_d4Zm1x18ELg7fyndEt+zcynZ5XA@mail.gmail.com","threadId":"35908","inReplyTo":"CAPig+cQubZByK3rNPOJy8rfLZpALpW0dOMz55gnFWRPppTAApg@mail.gmail.com","subject":"Re: [PATCH v4 01/27] path.c: make get_pathname() return strbuf instead of static buffer","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-03T00:14:56Z","receivedAt":"2014-03-03T00:14:56Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Mon, Mar 3, 2014 at 2:51 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> On Sat, Mar 1, 2014 at 7:12 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>> We've been avoiding PATH_MAX whenever possible. This patch makes\n>> get_pathname() return a strbuf and updates the callers to take\n>> advantage of this. The code is simplified as we no longer need to\n>> worry about buffer overflow.\n>>\n>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>> ---\n>> diff --git a/path.c b/path.c\n>> index 24594c4..5346700 100644\n>> --- a/path.c\n>> +++ b/path.c\n>> @@ -49,85 +60,70 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)\n>>         return cleanup_path(buf);\n>>  }\n>>\n>> -static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)\n>> +static void vsnpath(struct strbuf *buf, const char *fmt, va_list args)\n>>  {\n>>         const char *git_dir = get_git_dir();\n>> -       size_t len;\n>> -\n>> -       len = strlen(git_dir);\n>> -       if (n < len + 1)\n>> -               goto bad;\n>> -       memcpy(buf, git_dir, len);\n>> -       if (len && !is_dir_sep(git_dir[len-1]))\n>> -               buf[len++] = '/';\n>> -       len += vsnprintf(buf + len, n - len, fmt, args);\n>> -       if (len >= n)\n>> -               goto bad;\n>> -       return cleanup_path(buf);\n>> -bad:\n>> -       strlcpy(buf, bad_path, n);\n>> -       return buf;\n>> +       strbuf_addstr(buf, git_dir);\n>> +       if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))\n>> +               strbuf_addch(buf, '/');\n>> +       strbuf_vaddf(buf, fmt, args);\n>> +       strbuf_cleanup_path(buf);\n>>  }\n>\n> There's a slight semantic change here. The old code overwrote 'buf',\n> but the new code appends to 'buf'. For someone familiar with\n> sprintf(), or typical va_list or variadic functions there may be an\n> intuitive expectation that 'buf' will be overwritten. Should this code\n> be doing strbuf_reset() as its first action (or should that be the\n> responsibility of the caller who is reusing 'buff')?\n\nthose callers that use get_pathname() already have strbuf reset, so\nI'd say let the remaining callers reset the buffer.\n\n>\n>>  char *mkpath(const char *fmt, ...)\n>>  {\n>>         va_list args;\n>> -       unsigned len;\n>> -       char *pathname = get_pathname();\n>> -\n>> +       struct strbuf *pathname = get_pathname();\n>>         va_start(args, fmt);\n>> -       len = vsnprintf(pathname, PATH_MAX, fmt, args);\n>> +       strbuf_vaddf(pathname, fmt, args);\n>>         va_end(args);\n>> -       if (len >= PATH_MAX)\n>> -               return bad_path;\n>> -       return cleanup_path(pathname);\n>> +       return cleanup_path(pathname->buf);\n>>  }\n>\n> Prior to this change, it was possible (though probably not\n> recommended) for a caller to append gunk to the returned path up to\n> PATH_MAX without worrying about stomping memory. With the change, this\n> is no longer true. Should the function be changed to return 'const\n> char *' to enforce this restriction?\n\nSure. Will do (same for below)\n-- \nDuy\n"},{"id":"235818","messageId":"CACsJy8DeGCdnWHOxTeGUV4CsHyV-u_DgR-03iyOrEKznsQsqGA@mail.gmail.com","threadId":"35908","inReplyTo":"CAPig+cTjqrveuOS+3+bonwJa_Kjg=STJU1JJ2Kj7Gs5U9eEB9Q@mail.gmail.com","subject":"Re: [PATCH v4 02/27] Convert git_snpath() to strbuf_git_path()","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-03T00:15:47Z","receivedAt":"2014-03-03T00:15:47Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Mon, Mar 3, 2014 at 7:02 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> On Sat, Mar 1, 2014 at 7:12 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>> In the previous patch, git_snpath() is modified to allocate a new\n>> strbuf buffer because vsnpath() needs that. But that makes it awkward\n>> because git_snpath() receives a pre-allocated buffer from outside and\n>> has to copy data back. Rename it to strbuf_git_path() and make it\n>> receive strbuf directly.\n>>\n>> The conversion from git_snpath() to git_path() in\n>> update_refs_for_switch() is safe because that function does not keep\n>> any pointer to the round-robin buffer pool allocated by\n>> get_pathname().\n>>\n>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>> ---\n>> diff --git a/refs.c b/refs.c\n>> index 89228e2..434bd5e 100644\n>> --- a/refs.c\n>> +++ b/refs.c\n>> @@ -2717,17 +2729,19 @@ static int copy_msg(char *buf, const char *msg)\n>>         return 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 *sb_logfile)\n>>  {\n>>         int logfd, oflags = O_APPEND | O_WRONLY;\n>> +       const char *logfile;\n>>\n>> -       git_snpath(logfile, bufsize, \"logs/%s\", refname);\n>> +       strbuf_git_path(sb_logfile, \"logs/%s\", refname);\n>> +       logfile = sb_logfile->buf;\n>>         if (log_all_ref_updates &&\n>>             (starts_with(refname, \"refs/heads/\") ||\n>>              starts_with(refname, \"refs/remotes/\") ||\n>>              starts_with(refname, \"refs/notes/\") ||\n>>              !strcmp(refname, \"HEAD\"))) {\n>> -               if (safe_create_leading_directories(logfile) < 0)\n>> +               if (safe_create_leading_directories(sb_logfile->buf) < 0)\n>\n> At this point, 'logfile' is still 'sb_logfile->buf', so do you really\n> need this change?\n\nJunio made the same comment last time and I missed it. Will update.\n-- \nDuy\n"},{"id":"235821","messageId":"CAPig+cTU8-sBsbHpJXHxW2RuXdArWnamTXWvkw9d0WnYpYJtig@mail.gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-6-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v4 05/27] Make git_path() aware of file relocation in $GIT_DIR","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-03-03T01:34:49Z","receivedAt":"2014-03-03T01:34:49Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sat, Mar 1, 2014 at 7:12 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> We allow the user to relocate certain paths out of $GIT_DIR via\n> environment variables, e.g. GIT_OBJECT_DIRECTORY, GIT_INDEX_FILE and\n> GIT_GRAFT_FILE. All callers are not supposed to use git_path() or\n\n\"All callers are not\" is unusually difficult to understand. Changing\nit to \"Callers are not\" simplifies.\n\n> git_pathdup() to get those paths. Instead they must use\n> get_object_directory(), get_index_file() and get_graft_file()\n> respectively. This is inconvenient and could be missed in review\n> (there's git_path(\"objects/info/alternates\") somewhere in\n\n\"(for example, there's...\" reads a bit better.\n\n> sha1_file.c).\n>\n> This patch makes git_path() and git_pathdup() understand those\n> environment variables. So if you set GIT_OBJECT_DIRECTORY to /foo/bar,\n> git_path(\"objects/abc\") should return /tmp/bar/abc. The same is done\n\nI guess you mean it should return /foo/bar/abc.\n\n> for the two remaining env variables.\n>\n> \"git rev-parse --git-path\" is the wrapper for script use.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  Documentation/git-rev-parse.txt |  5 +++++\n>  builtin/rev-parse.c             |  7 +++++++\n>  cache.h                         |  1 +\n>  environment.c                   |  9 ++++++--\n>  path.c                          | 46 +++++++++++++++++++++++++++++++++++++++++\n>  t/t0060-path-utils.sh           | 19 +++++++++++++++++\n>  6 files changed, 85 insertions(+), 2 deletions(-)\n>\n> diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt\n> index 0d2cdcd..33e4e90 100644\n> --- a/Documentation/git-rev-parse.txt\n> +++ b/Documentation/git-rev-parse.txt\n> @@ -232,6 +232,11 @@ print a message to stderr and exit with nonzero status.\n>         repository.  If <path> is a gitfile then the resolved path\n>         to the real repository is printed.\n>\n> +--git-path <path>::\n> +       Resolve \"$GIT_DIR/<path>\" and takes other path relocation\n> +       variables such as $GIT_OBJECT_DIRECTORY,\n> +       $GIT_INDEX_FILE... into account.\n\nWould it help to add a quick illustration here?\n\n    For example, if GIT_OBJECT_DIRECTORY is /foo/bar,\n    then \"git rev-parse --git-path objects/abc\" returns /foo/bar/abc.\n\n>  --show-cdup::\n>         When the command is invoked from a subdirectory, show the\n>         path of the top-level directory relative to the current\n> diff --git a/path.c b/path.c\n> index ccd7228..e020530 100644\n> --- a/path.c\n> +++ b/path.c\n> @@ -60,13 +60,59 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)\n>         return cleanup_path(buf);\n>  }\n>\n>  static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)\n>  {\n>         const char *git_dir = get_git_dir();\n> +       int gitdir_len;\n>         strbuf_addstr(buf, git_dir);\n\nMaybe simplify by dropping git_dir and invoking strbuf_addstr(buf,\nget_git_dir())?\n\n>         if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))\n>                 strbuf_addch(buf, '/');\n> +       gitdir_len = buf->len;\n>         strbuf_vaddf(buf, fmt, args);\n> +       adjust_git_path(buf, gitdir_len);\n>         strbuf_cleanup_path(buf);\n>  }\n>\n"},{"id":"235841","messageId":"CAPig+cSq0nRWC97_7L+nBuiKdDjPtOkP3x=5u2a5cvONgwyMRg@mail.gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-11-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v4 10/27] Add new environment variable $GIT_COMMON_DIR","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-03-03T07:29:33Z","receivedAt":"2014-03-03T07:29:33Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sat, Mar 1, 2014 at 7:12 AM, Nguyễn Thái Ngọc Duy <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> diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt\n> index aa03882..10672a1 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 $GIT_COMMON_DIR is set and\n\ns/ignored \\$/ignored if $/g\n\nNote the /g since this error is repeated throughout the rest of the\ngitrepository-layout.txt patch.\n\n> +\"$GIT_COMMON_DIR/objects\" will be used instead.\n>\n>  objects/[0-9a-f][0-9a-f]::\n>         A newly created object is stored in its own file.\n"},{"id":"235848","messageId":"CAPig+cSOityd484g3x7tD9s90YXTrP7Jc-Obxmw6iKAEh-+RBA@mail.gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-13-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v4 12/27] *.sh: avoid hardcoding $GIT_DIR/hooks/...","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-03-03T08:31:46Z","receivedAt":"2014-03-03T08:31:46Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sat, Mar 1, 2014 at 7:12 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> 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>\n> Signed-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>\n> diff --git a/git-am.sh b/git-am.sh\n> index bbea430..dfa0618 100755\n> --- a/git-am.sh\n> +++ b/git-am.sh\n> @@ -803,10 +803,10 @@ To restore the original branch and stop patching run \\\"\\$cmdline --abort\\\".\"\n>                 continue\n>         fi\n>\n> -       if test -x \"$GIT_DIR\"/hooks/applypatch-msg\n> +       hook=\"`git rev-parse --git-path hooks/applypatch-msg`\"\n\nDid you want to use $(...) rather than `...`?\n\nSame question for the remainder of the patch.\n\n> +       if test -x \"$hook\"\n>         then\n> -               \"$GIT_DIR\"/hooks/applypatch-msg \"$dotest/final-commit\" ||\n> -               stop_here $this\n> +               \"$hook\" \"$dotest/final-commit\" || stop_here $this\n>         fi\n>\n>         if test -f \"$dotest/final-commit\"\n> @@ -880,9 +880,10 @@ did you forget to use 'git add'?\"\n>                 stop_here_user_resolve $this\n>         fi\n>\n> -       if test -x \"$GIT_DIR\"/hooks/pre-applypatch\n> +       hook=\"`git rev-parse --git-path hooks/pre-applypatch`\"\n> +       if test -x \"$hook\"\n>         then\n> -               \"$GIT_DIR\"/hooks/pre-applypatch || stop_here $this\n> +               \"$hook\" || stop_here $this\n>         fi\n>\n>         tree=$(git write-tree) &&\n> @@ -908,18 +909,17 @@ did you forget to use 'git add'?\"\n>                 echo \"$(cat \"$dotest/original-commit\") $commit\" >> \"$dotest/rewritten\"\n>         fi\n>\n> -       if test -x \"$GIT_DIR\"/hooks/post-applypatch\n> -       then\n> -               \"$GIT_DIR\"/hooks/post-applypatch\n> -       fi\n> +       hook=\"`git rev-parse --git-path hooks/post-applypatch`\"\n> +       test -x \"$hook\" && \"$hook\"\n>\n>         go_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> -       \"$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> +       \"$hook\" rebase < \"$dotest\"/rewritten\n>      fi\n>  fi\n>\n> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\n> index 43c19e0..d741b04 100644\n> --- a/git-rebase--interactive.sh\n> +++ b/git-rebase--interactive.sh\n> @@ -632,9 +632,9 @@ do_next () {\n>                 git notes copy --for-rewrite=rebase < \"$rewritten_list\" ||\n>                 true # we don't care if this copying failed\n>         } &&\n> -       if test -x \"$GIT_DIR\"/hooks/post-rewrite &&\n> -               test -s \"$rewritten_list\"; then\n> -               \"$GIT_DIR\"/hooks/post-rewrite rebase < \"$rewritten_list\"\n> +       hook=\"`git rev-parse --git-path hooks/post-rewrite`\"\n> +       if test -x \"$hook\" && test -s \"$rewritten_list\"; then\n> +               \"$hook\" rebase < \"$rewritten_list\"\n>                 true # we don't care if this hook failed\n>         fi &&\n>         warn \"Successfully rebased and updated $head_name.\"\n> diff --git a/git-rebase--merge.sh b/git-rebase--merge.sh\n> index e7d96de..68f5d09 100644\n> --- a/git-rebase--merge.sh\n> +++ b/git-rebase--merge.sh\n> @@ -93,10 +93,8 @@ finish_rb_merge () {\n>         if test -s \"$state_dir\"/rewritten\n>         then\n>                 git notes copy --for-rewrite=rebase <\"$state_dir\"/rewritten\n> -               if test -x \"$GIT_DIR\"/hooks/post-rewrite\n> -               then\n> -                       \"$GIT_DIR\"/hooks/post-rewrite rebase <\"$state_dir\"/rewritten\n> -               fi\n> +               hook=\"`git rev-parse --git-path hooks/post-rewrite`\"\n> +               test -x \"$hook\" && \"$hook\" rebase <\"$state_dir\"/rewritten\n>         fi\n>         say All done.\n>  }\n> diff --git a/git-rebase.sh b/git-rebase.sh\n> index 8a3efa2..1cf8dba 100755\n> --- a/git-rebase.sh\n> +++ b/git-rebase.sh\n> @@ -195,9 +195,9 @@ run_specific_rebase () {\n>\n>  run_pre_rebase_hook () {\n>         if test -z \"$ok_to_skip_pre_rebase\" &&\n> -          test -x \"$GIT_DIR/hooks/pre-rebase\"\n> +          test -x \"`git rev-parse --git-path hooks/pre-rebase`\"\n>         then\n> -               \"$GIT_DIR/hooks/pre-rebase\" ${1+\"$@\"} ||\n> +               \"`git rev-parse --git-path hooks/pre-rebase`\" ${1+\"$@\"} ||\n>                 die \"$(gettext \"The pre-rebase hook refused to rebase.\")\"\n>         fi\n>  }\n> diff --git a/templates/hooks--applypatch-msg.sample b/templates/hooks--applypatch-msg.sample\n> index 8b2a2fe..28b843b 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> -       exec \"$GIT_DIR/hooks/commit-msg\" ${1+\"$@\"}\n> +commitmsg=\"`git rev-parse --git-path hooks/commit-msg`\"\n> +test -x \"$commitmsg\" && exec \"$commitmsg\" ${1+\"$@\"}\n>  :\n> diff --git a/templates/hooks--pre-applypatch.sample b/templates/hooks--pre-applypatch.sample\n> index b1f187c..51aa244 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> -       exec \"$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> --\n> 1.9.0.40.gaa8c3ea\n>\n> --\n> To unsubscribe from this list: send the line \"unsubscribe git\" in\n> the body of a message to majordomo@vger.kernel.org\n> More majordomo info at  http://vger.kernel.org/majordomo-info.html\n"},{"id":"235890","messageId":"xmqqob1nrwsc.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"CACsJy8AKu6UQURW-Zn=VE5WF3Yqfg329nsCoxiCt+nSJKfTW3g@mail.gmail.com","subject":"Re: [PATCH v3 01/25] path.c: make get_pathname() return strbuf instead of static buffer","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-03-03T17:56:19Z","receivedAt":"2014-03-03T17:56:19Z","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 Thu, Feb 20, 2014 at 6:26 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> Is there a reason not to do just an equivalent of\n>>\n>>     #define git_pathdup mkpathdup\n>>\n>> and be done with it?  Am I missing something?\n>\n> They have a subtle difference: mkpathdup() calls cleanup_path() while\n> git_pathdup() does not. Without auditing all call sites, we can't be\n> sure this difference is insignificant. So I keep both functions\n> separate for now.\n\nThanks; that is a very good explanation why #define'ing two to be\nthe same would not be a workable solution to the ownership issue I\nraised.\n\n>  char *git_pathdup(const char *fmt, ...)\n>  {\n> -\tchar path[PATH_MAX], *ret;\n> +\tstruct strbuf *path = get_pathname();\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\nThis feels somewhat wrong.\n\nThis function is for callers who are willing to take ownership of\nthe path buffer and promise to free the returned buffer when they\nare done, because you are returning strbuf_detach()'ed piece of\nmemory, giving the ownership away.\n\nThe whole point of using get_pathname() is to allow callers not to\ncare about allocation issues on the paths they scribble on during\ntheir short-and-simple codepaths that do not have too many uses of\nsimilar temporary path buffers.  Why borrow from that round-robin\npool (which may now cause some codepaths to overflow the number of\nsuch active temporary path buffers---have they been all audited)?\n"},{"id":"235891","messageId":"xmqqk3cbrw8y.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"CACsJy8A3mGCJzacaVrVUSxJnqXZ=GUhgOzpheUixpyWtYE6gDw@mail.gmail.com","subject":"Re: [PATCH v3 07/25] reflog: avoid constructing .lock path with git_path","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-03-03T18:07:57Z","receivedAt":"2014-03-03T18:07:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Duy Nguyen <pclouds@gmail.com> writes:\n\n> On Wed, Feb 26, 2014 at 5:44 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>>\n>>> git_path() soon understands the path given to it. Some paths \"abc\" may\n>>> become \"def\" while other \"ghi\" may become \"ijk\". We don't want\n>>> git_path() to interfere with .lock path construction. Concatenate\n>>> \".lock\" after the path has been resolved by git_path() so if \"abc\"\n>>> becomes \"def\", we'll have \"def.lock\", not \"ijk\".\n>>\n>> Hmph.  I am not sure if the above is readable (or if I am reading it\n>> correctly).\n>\n> Definitely not now that I have had my break from the series and reread it.\n>\n>> If \"abc\" becomes \"def\", it would take deliberate work to make\n>> \"abc.lock\" into \"ijk\", and it would be natural to expect that\n>> \"abc.lock\" would become \"def.lock\" without any fancy trick, no?\n>\n> A better explanation may be, while many paths are not converted by\n> git_path() (\"abc\" -> \"abc\"), some of them will be based on the given\n> path (\"def\" -> \"ghi\"). Giving path def.lock to git_path() may confuse\n> it and make it believe def.lock should not be converted because the\n> signature is \"def.lock\" not \"def\". But we want the lock file to have\n> the same base name with the locked file (e.g. \"ghi.lock\", not\n> \"def.lock\"). So it's best to append \".lock\" after git_path() has done\n> its conversion.\n>\n> The alternative is teach git_path about \".lock\", but I don't really\n> want to put more logic down there.\n\nI think your attempt to sound generic by using \"abc\", \"def\",\netc. backfired and made the description only obscure.  It would have\nbeen immediately understandable if it were more like this:\n\n\tAmong pathnames in $GIT_DIR, e.g. \"index\" or \"packed-refs\",\n\twe want to automatically and silently map some of them to\n\tthe $GIT_DIR of the repository we are borrowing from via\n\t$GIT_COMMON_DIR mechanism.  When we formulate the pathname\n\tfor its lockfile, we want it to be in the same location as\n\tits final destination.  \"index\" is not shared and needs to\n\tremain in the borrowing repository, while \"packed-refs\" is\n\tshared and needs to go to the borrowed repository.\n\n\tgit_path() could be taught about the \".lock\" suffix and map\n\t\"index.lock\" and \"packed-refs.lock\" the same way their\n\tbasenames are mapped, but instead the caller can help by\n\tasking where the basename (e.g. \"index\") is mapped to\n\tgit_path() and then appending \".lock\" after the mapping is\n\tdone.\n\nThanks for an explanation.  With that understanding, the patch makes\nsense.\n\n\t\n"},{"id":"235974","messageId":"CAPig+cQT33-h2nmRDD8FC7KrCH-1UPqK39Vb3qHKSKz8nhtndQ@mail.gmail.com","threadId":"35908","inReplyTo":"531214BC.3070704@web.de","subject":"Re: [PATCH v4 19/27] wrapper.c: wrapper to open a file, fprintf then close","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-03-04T02:47:05Z","receivedAt":"2014-03-04T02:47:05Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sat, Mar 1, 2014 at 12:11 PM, Torsten Bögershausen <tboegi@web.de> wrote:\n> On 2014-03-01 13.12, Nguyễn Thái Ngọc Duy wrote:\n>> 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>>\n>> diff --git a/cache.h b/cache.h\n>> index 98b5dd3..99b86d9 100644\n>> --- a/cache.h\n>> +++ b/cache.h\n>> @@ -1239,6 +1239,8 @@ static inline ssize_t write_str_in_full(int fd, const char *str)\n>>  {\n>>       return 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);\n>> diff --git a/wrapper.c b/wrapper.c\n>> index 0cc5636..5ced50d 100644\n>> --- a/wrapper.c\n>> +++ b/wrapper.c\n>> @@ -455,3 +455,34 @@ struct passwd *xgetpwuid_self(void)\n>>                   errno ? strerror(errno) : _(\"no such user\"));\n>>       return pw;\n>>  }\n>> +\n>> +int write_file(const char *path, int fatal, const char *fmt, ...)\n>> +{\n>> +     struct strbuf sb = STRBUF_INIT;\n>> +     int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);\n>> +     va_list params;\n>> +     if (fd < 0) {\n\nMicro nit atop Torsten's micro nit:\n\nIt is 3% easier to understand the code if the check for open() failure\nimmediately follows the open() attempt:\n\n    va_list params;\n    int fd = open(...);\n    if (fd < 0) {\n\n>> +             if (fatal)\n>> +                     die_errno(_(\"could not open %s for writing\"), path);\n>> +             return -1;\n>> +     }\n>> +     va_start(params, fmt);\n>> +     strbuf_vaddf(&sb, fmt, params);\n>> +     va_end(params);\n>> +     if (write_in_full(fd, sb.buf, sb.len) != sb.len) {\n>> +             int err = errno;\n>> +             close(fd);\n>> +             errno = err;\n>> +             strbuf_release(&sb);\n> Micro nit:\n> Today we now what strbuf_release() is doing, but if we ever change the\n> implementation, it is 3% safer to keep err a little bit longer like this:\n>> +             strbuf_release(&sb);\n>> +             errno = err;\n"},{"id":"236067","messageId":"CAPig+cToTqJ4PPX3mvt0ftC-+Ncfiakz5dd_=xymM76T01z4fQ@mail.gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-28-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v4 27/27] count-objects: report unused files in $GIT_DIR/repos/...","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-03-05T04:25:42Z","receivedAt":"2014-03-05T04:25:42Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sat, Mar 1, 2014 at 7:13 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> In linked checkouts, borrowed parts like config is taken from\n> $GIT_COMMON_DIR. $GIT_DIR/config is never used. Report them as\n> garbage.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  builtin/count-objects.c | 37 ++++++++++++++++++++++++++++++++++++-\n>  path.c                  |  4 ++++\n>  2 files changed, 40 insertions(+), 1 deletion(-)\n>\n> diff --git a/builtin/count-objects.c b/builtin/count-objects.c\n> index a7f70cb..725cd5f 100644\n> --- a/builtin/count-objects.c\n> +++ b/builtin/count-objects.c\n> @@ -78,6 +78,39 @@ static void count_objects(DIR *d, char *path, int len, int verbose,\n>         }\n>  }\n>\n> +static void report_linked_checkout_garbage(void)\n> +{\n> +       /*\n> +        * must be more or less in sync with * path.c:update_common_dir().\n> +        *\n> +        * \"logs\" is let slip because logs/HEAD is in $GIT_DIR but the\n> +        * remaining in $GIT_COMMON_DIR. Probably not worth traversing\n> +        * the entire \"logs\" directory for that.\n> +        *\n> +        * The same \"gc.pid\" for because it's a temporary file.\n> +        */\n> +       const char *list[] = {\n> +               \"branches\", \"hooks\", \"info\", \"lost-found\", \"modules\",\n> +               \"objects\", \"refs\", \"remotes\", \"rr-cache\", \"svn\",\n> +               \"config\", \"packed-refs\", \"shallow\", NULL\n> +       };\n> +       struct strbuf sb = STRBUF_INIT;\n> +       const char **p;\n> +       int len;\n> +\n> +       if (!file_exists(git_path(\"commondir\")))\n> +               return;\n> +       strbuf_addf(&sb, \"%s/\", get_git_dir());\n> +       len = sb.len;\n> +       for (p = list; *p; p++) {\n> +               strbuf_setlen(&sb, len);\n> +               strbuf_addstr(&sb, *p);\n> +               if (file_exists(sb.buf))\n> +                       report_garbage(\"unused in linked checkout\", sb.buf);\n> +       }\n> +       strbuf_release(&sb);\n> +}\n> +\n>  static char const * const count_objects_usage[] = {\n>         N_(\"git count-objects [-v] [-H | --human-readable]\"),\n>         NULL\n> @@ -102,8 +135,10 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)\n>         /* we do not take arguments other than flags for now */\n>         if (argc)\n>                 usage_with_options(count_objects_usage, opts);\n> -       if (verbose)\n> +       if (verbose) {\n>                 report_garbage = real_report_garbage;\n> +               report_linked_checkout_garbage();\n> +       }\n>         memcpy(path, objdir, len);\n>         if (len && objdir[len-1] != '/')\n>                 path[len++] = '/';\n> diff --git a/path.c b/path.c\n> index 47383ff..2e6035d 100644\n> --- a/path.c\n> +++ b/path.c\n> @@ -92,6 +92,10 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)\n>\n>  static void update_common_dir(struct strbuf *buf, int git_dir_len)\n>  {\n> +       /*\n> +        * Remember to report_linked_checkout_garbage()\n> +        * builtin/count-objects.c\n> +        */\n\nI couldn't figure out why this comment was telling me to remember to\nreport \"linked checkout garbage\" until I realized that you omitted the\nword \"update\" (as in \"remember to update\"). It might be clearer to say\nsomething along these lines:\n\n    Keep synchronized with related list in\n    builtin/count-objects.c:report_linked_checkout_garbage().\n\nIs it not possible or just too much of a hassle to maintain this list\nin just one place, as in a header which is included by these two\nfiles? The exceptions, such as 'log' and 'gc.pid', could be marked by\na special character in the entry (\"!gc.pid\", for example) or any such\nscheme.\n\n>         const char *common_dir_list[] = {\n>                 \"branches\", \"hooks\", \"info\", \"logs\", \"lost-found\", \"modules\",\n>                 \"objects\", \"refs\", \"remotes\", \"repos\", \"rr-cache\", \"svn\",\n> --\n> 1.9.0.40.gaa8c3ea\n"},{"id":"236082","messageId":"CACsJy8AP7n8psnfWfASVgmQQTgW1qiLYr7SjV-cnPBgNsK+j+A@mail.gmail.com","threadId":"35908","inReplyTo":"CAPig+cToTqJ4PPX3mvt0ftC-+Ncfiakz5dd_=xymM76T01z4fQ@mail.gmail.com","subject":"Re: [PATCH v4 27/27] count-objects: report unused files in $GIT_DIR/repos/...","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-05T12:08:14Z","receivedAt":"2014-03-05T12:08:14Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Mar 5, 2014 at 11:25 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>>  static void update_common_dir(struct strbuf *buf, int git_dir_len)\n>>  {\n>> +       /*\n>> +        * Remember to report_linked_checkout_garbage()\n>> +        * builtin/count-objects.c\n>> +        */\n>\n> I couldn't figure out why this comment was telling me to remember to\n> report \"linked checkout garbage\" until I realized that you omitted the\n> word \"update\" (as in \"remember to update\"). It might be clearer to say\n> something along these lines:\n>\n>     Keep synchronized with related list in\n>     builtin/count-objects.c:report_linked_checkout_garbage().\n>\n\nI have a tendency to accidentally important words :)\n\n> Is it not possible or just too much of a hassle to maintain this list\n> in just one place, as in a header which is included by these two\n> files? The exceptions, such as 'log' and 'gc.pid', could be marked by\n> a special character in the entry (\"!gc.pid\", for example) or any such\n> scheme.\n\nIt might work. Let me try.\n-- \nDuy\n"},{"id":"236120","messageId":"xmqqlhwoh1p4.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"1393675983-3232-19-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v4 18/27] setup.c: support multi-checkout repo setup","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-03-05T19:42:31Z","receivedAt":"2014-03-05T19:42:31Z","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>  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\nJust thinking aloud to see if I got the full implication of the\nabove right...\n\nIf we find ourselves in the multi-checkout mode because we saw\n.git/commondir on the filesystem, it is clear that the root of the\nworking tree is the parent directory of that .git directory.\n\nIf the reason we think we are in the multi-checkout mode is not\nbecause of .git/commondir but because $GIT_COMMON_DIR is set, should\nwe assume the same relationship between the root of the working tree\nand the GIT_DIR (however we find it) when the environment variable\n$GIT_WORK_TREE is not set?  Or should that configuration be an error?\nWith $GIT_DIR set without $GIT_WORK_TREE set, the user is telling us\nthat the $cwd is the root of the working tree, so perhaps we should\ndo the same?\n"},{"id":"236123","messageId":"xmqqd2i0h0j3.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"1393675983-3232-25-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v4 24/27] prune: strategies for linked checkouts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-03-05T20:07:44Z","receivedAt":"2014-03-05T20:07:44Z","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> +\tif (get_device_or_die(path) != get_device_or_die(get_git_dir())) {\n> +\t\tstrbuf_reset(&sb);\n> +\t\tstrbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n> +\t\twrite_file(sb.buf, 1, \"located on a different file system\\n\");\n> +\t\tkeep_locked = 1;\n> +\t} else {\n> +\t\tstrbuf_reset(&sb);\n> +\t\tstrbuf_addf(&sb, \"%s/link\", sb_repo.buf);\n> +\t\t(void)link(sb_git.buf, sb.buf);\n> +\t}\n\nJust in case you did not realize, casting the return away with\n(void) will not squelch this out of the compiler:\n\n    builtin/checkout.c: In function 'prepare_linked_checkout':\n    builtin/checkout.c:947:3: error: ignoring return value of 'link', declared with attribute warn_unused_result [-Werror=unused-result]\n\nIt still feels fishy to see \"we attempt to link but we do not care\nif it works or not\" to me, with or without the \"unused result\"\nissue.\n"},{"id":"236157","messageId":"CAPig+cROi2TR+T8f+T_fRmAgBrBtBpC3qo5pCYAMSfRp0JoWWw@mail.gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-23-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v4 22/27] checkout: clean up half-prepared directories in --to mode","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-03-06T10:06:57Z","receivedAt":"2014-03-06T10:06:57Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sat, Mar 1, 2014 at 7:12 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/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 fa7b54a..28f9ac1 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> +               strbuf_reset(&sb);\n> +       }\n\nstrbuf_release(&sb);\n\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> +       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> +       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.0.40.gaa8c3ea\n"},{"id":"236233","messageId":"CACsJy8BwUa63yG0WownJQNedNE8JC+5_oG8PBs6sy7_tiHOh-A@mail.gmail.com","threadId":"35908","inReplyTo":"CACsJy8DeGCdnWHOxTeGUV4CsHyV-u_DgR-03iyOrEKznsQsqGA@mail.gmail.com","subject":"Re: [PATCH v4 02/27] Convert git_snpath() to strbuf_git_path()","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-07T05:03:13Z","receivedAt":"2014-03-07T05:03:13Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Mon, Mar 3, 2014 at 7:15 AM, Duy Nguyen <pclouds@gmail.com> wrote:\n> On Mon, Mar 3, 2014 at 7:02 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>> On Sat, Mar 1, 2014 at 7:12 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>>> In the previous patch, git_snpath() is modified to allocate a new\n>>> strbuf buffer because vsnpath() needs that. But that makes it awkward\n>>> because git_snpath() receives a pre-allocated buffer from outside and\n>>> has to copy data back. Rename it to strbuf_git_path() and make it\n>>> receive strbuf directly.\n>>>\n>>> The conversion from git_snpath() to git_path() in\n>>> update_refs_for_switch() is safe because that function does not keep\n>>> any pointer to the round-robin buffer pool allocated by\n>>> get_pathname().\n>>>\n>>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>>> ---\n>>> diff --git a/refs.c b/refs.c\n>>> index 89228e2..434bd5e 100644\n>>> --- a/refs.c\n>>> +++ b/refs.c\n>>> @@ -2717,17 +2729,19 @@ static int copy_msg(char *buf, const char *msg)\n>>>         return 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 *sb_logfile)\n>>>  {\n>>>         int logfd, oflags = O_APPEND | O_WRONLY;\n>>> +       const char *logfile;\n>>>\n>>> -       git_snpath(logfile, bufsize, \"logs/%s\", refname);\n>>> +       strbuf_git_path(sb_logfile, \"logs/%s\", refname);\n>>> +       logfile = sb_logfile->buf;\n>>>         if (log_all_ref_updates &&\n>>>             (starts_with(refname, \"refs/heads/\") ||\n>>>              starts_with(refname, \"refs/remotes/\") ||\n>>>              starts_with(refname, \"refs/notes/\") ||\n>>>              !strcmp(refname, \"HEAD\"))) {\n>>> -               if (safe_create_leading_directories(logfile) < 0)\n>>> +               if (safe_create_leading_directories(sb_logfile->buf) < 0)\n>>\n>> At this point, 'logfile' is still 'sb_logfile->buf', so do you really\n>> need this change?\n>\n> Junio made the same comment last time and I missed it. Will update.\n\nNo I will not :-) safe_create_leading_directories takes an editable\nstring, but logfile is now a const string. We could use\ns_c_l_d_const() but that one will make a copy of the string\nunncessarily. Will make a note in the commit message though.\n-- \nDuy\n"},{"id":"236234","messageId":"CAPig+cR_JaaW18ng8Z8_hsXy3DNT216o7ogNLxmcW2+tp2Ax9Q@mail.gmail.com","threadId":"35908","inReplyTo":"CACsJy8BwUa63yG0WownJQNedNE8JC+5_oG8PBs6sy7_tiHOh-A@mail.gmail.com","subject":"Re: [PATCH v4 02/27] Convert git_snpath() to strbuf_git_path()","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-03-07T05:26:43Z","receivedAt":"2014-03-07T05:26:43Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Mar 7, 2014 at 12:03 AM, Duy Nguyen <pclouds@gmail.com> wrote:\n> On Mon, Mar 3, 2014 at 7:15 AM, Duy Nguyen <pclouds@gmail.com> wrote:\n>> On Mon, Mar 3, 2014 at 7:02 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>>> On Sat, Mar 1, 2014 at 7:12 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>>>> In the previous patch, git_snpath() is modified to allocate a new\n>>>> strbuf buffer because vsnpath() needs that. But that makes it awkward\n>>>> because git_snpath() receives a pre-allocated buffer from outside and\n>>>> has to copy data back. Rename it to strbuf_git_path() and make it\n>>>> receive strbuf directly.\n>>>>\n>>>> The conversion from git_snpath() to git_path() in\n>>>> update_refs_for_switch() is safe because that function does not keep\n>>>> any pointer to the round-robin buffer pool allocated by\n>>>> get_pathname().\n>>>>\n>>>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>>>> ---\n>>>> diff --git a/refs.c b/refs.c\n>>>> index 89228e2..434bd5e 100644\n>>>> --- a/refs.c\n>>>> +++ b/refs.c\n>>>> @@ -2717,17 +2729,19 @@ static int copy_msg(char *buf, const char *msg)\n>>>>         return 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 *sb_logfile)\n>>>>  {\n>>>>         int logfd, oflags = O_APPEND | O_WRONLY;\n>>>> +       const char *logfile;\n>>>>\n>>>> -       git_snpath(logfile, bufsize, \"logs/%s\", refname);\n>>>> +       strbuf_git_path(sb_logfile, \"logs/%s\", refname);\n>>>> +       logfile = sb_logfile->buf;\n>>>>         if (log_all_ref_updates &&\n>>>>             (starts_with(refname, \"refs/heads/\") ||\n>>>>              starts_with(refname, \"refs/remotes/\") ||\n>>>>              starts_with(refname, \"refs/notes/\") ||\n>>>>              !strcmp(refname, \"HEAD\"))) {\n>>>> -               if (safe_create_leading_directories(logfile) < 0)\n>>>> +               if (safe_create_leading_directories(sb_logfile->buf) < 0)\n>>>\n>>> At this point, 'logfile' is still 'sb_logfile->buf', so do you really\n>>> need this change?\n>>\n>> Junio made the same comment last time and I missed it. Will update.\n>\n> No I will not :-) safe_create_leading_directories takes an editable\n> string, but logfile is now a const string. We could use\n> s_c_l_d_const() but that one will make a copy of the string\n> unncessarily. Will make a note in the commit message though.\n\nRather than explaining it in the commit message, it might be better\neliminate the source of confusion by taking one of these approaches:\n\n1. Drop the 'const char *logfile' variable altogether; rename the\nstrbuf argument to 'logfile'; and just use logfile->buf everywhere\n'logfile' is used in the current code. This makes the diff a bit more\nnoisy, but eliminates confusion of reviewers reading the patch.\n\n2. Keep the 'const char *logfile' but assign it just before its first\n(real) use in 'logfd = open(logfile...)'. (There's one earlier use in\na diagnostic, but sb_logfile->buf could suffice there.)\n\n3. Just declare it 'char *logfile' and use it everywhere in the\nfunction. This is a bit ugly since it's not obvious to the reviewer\nthat it is non-const only for the sake of\nsafe_create_leading_directories().\n\nI fiind myself favoring #1 in this particular case.\n"},{"id":"236248","messageId":"CAPig+cTVdCYCs4BvtcXFkp4WXFueP8HepD7YzFh5hfLDgNH1xg@mail.gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-27-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v4 26/27] gc: support prune --repos","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-03-07T06:40:08Z","receivedAt":"2014-03-07T06:40:08Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sat, Mar 1, 2014 at 7:13 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 |  6 ++++++\n>  builtin/gc.c             | 17 +++++++++++++++++\n>  2 files changed, 23 insertions(+)\n>\n> diff --git a/Documentation/config.txt b/Documentation/config.txt\n> index 313d4b3..438b213 100644\n> --- a/Documentation/config.txt\n> +++ b/Documentation/config.txt\n> @@ -1183,6 +1183,12 @@ 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 'prune --repos --expire 3.months.ago'.\n> +       Override the grace period with this config variable.  The value\n> +       \"now\" may be used to disable this  grace period and always prune\n\nExtra space between \"this\" and \"grace\". However, \"the grace period\"\nsounds a bit better.\n\n> +       $GIT_DIR/repos immediately.\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 39d9b27..85c3c0c 100644\n> --- a/builtin/gc.c\n> +++ b/builtin/gc.c\n> @@ -30,11 +30,13 @@ static int aggressive_window = 250;\n>  static int gc_auto_threshold = 6700;\n>  static int gc_auto_pack_limit = 50;\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> @@ -81,6 +83,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> +       }\n>         return git_default_config(var, value, cb);\n>  }\n>\n> @@ -274,6 +284,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> @@ -334,6 +345,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.0.40.gaa8c3ea\n>\n> --\n> To unsubscribe from this list: send the line \"unsubscribe git\" in\n> the body of a message to majordomo@vger.kernel.org\n> More majordomo info at  http://vger.kernel.org/majordomo-info.html\n"},{"id":"236286","messageId":"CACsJy8DyqJ-6SpgUdgM0Q2MQxDKDdE2uotF7wV4cBvCigCO_vw@mail.gmail.com","threadId":"35908","inReplyTo":"xmqqlhwoh1p4.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v4 18/27] setup.c: support multi-checkout repo setup","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T01:55:15Z","receivedAt":"2014-03-08T01:55:15Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Mar 6, 2014 at 2:42 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>\n>>  core.worktree::\n>>       Set the path to the root of the working tree.\n>> +     If GIT_COMMON_DIR environment variable is set, core.worktree\n>> +     is ignored and not used for determining the root of working tree.\n>\n> Just thinking aloud to see if I got the full implication of the\n> above right...\n>\n> If we find ourselves in the multi-checkout mode because we saw\n> .git/commondir on the filesystem, it is clear that the root of the\n> working tree is the parent directory of that .git directory.\n>\n> If the reason we think we are in the multi-checkout mode is not\n> because of .git/commondir but because $GIT_COMMON_DIR is set,\n\nI tend to think so, .git/commondir is just a convenient way to set\n$GIT_COMMON_DIR. $GIT_COMMON_DIR is the key. config.txt correctly\nstates so, but the commit message is misleading.\n\n>  should we assume the same relationship between the root of the working tree\n> and the GIT_DIR (however we find it) when the environment variable\n> $GIT_WORK_TREE is not set?  Or should that configuration be an error?\n> With $GIT_DIR set without $GIT_WORK_TREE set, the user is telling us\n> that the $cwd is the root of the working tree, so perhaps we should\n> do the same?\n\nIt should work exactly like how normal worktree does, so if $GIT_DIR\nis set and $GIT_WORK_TREE is not, $PWD is the worktree root.\n-- \nDuy\n"},{"id":"236287","messageId":"1394246900-31535-1-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1393675983-3232-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 00/28] Support multiple checkouts","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:47:52Z","receivedAt":"2014-03-08T02:47:52Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The diff against v4 is kinda big but it's mostly about converting\n`...` to $(...) and making git_path() and friends return a const\nstring.\n\nAnother notable change is I no longer attempt to support checkouts on\nportable devices. Torsten pointed out (privately) that my dealing with\nWindows drives was insufficient. And Junio was not so happy with how\nlink() was handled either. We can revisit it later.\n\nMany thanks to Eric, who was very patient to go through the series\ncarefully and pointed out problems that I overlooked.\n\nv4..v5 diff below for convenience\n-- 8< --\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 438b213..c635b8d 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1184,9 +1184,10 @@ gc.pruneexpire::\n \tunreachable objects immediately.\n \n gc.prunereposexpire::\n-\tWhen 'git gc' is run, it will call '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 this  grace period and always prune\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::\ndiff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt\nindex 8e6ad32..8a9cac8 100644\n--- a/Documentation/git-rev-parse.txt\n+++ b/Documentation/git-rev-parse.txt\n@@ -238,7 +238,9 @@ print a message to stderr and exit with nonzero status.\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.\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 /tmp/bar/abc.\n \n --show-cdup::\n \tWhen the command is invoked from a subdirectory, show the\ndiff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt\nindex 2dc6901..dff6c47 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -47,7 +47,7 @@ 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 $GIT_COMMON_DIR is set and\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@@ -95,7 +95,7 @@ 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. This directory is ignored $GIT_COMMON_DIR\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@@ -118,7 +118,7 @@ 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]. This file is ignored $GIT_COMMON_DIR\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@@ -151,7 +151,7 @@ branches::\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. This\n-\tdirectory is ignored $GIT_COMMON_DIR is set and\n+\tdirectory is ignored if $GIT_COMMON_DIR is set and\n \t\"$GIT_COMMON_DIR/branches\" will be used instead.\n \n \n@@ -162,7 +162,7 @@ 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. This directory is ignored $GIT_COMMON_DIR is set\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@@ -172,7 +172,7 @@ index::\n \n info::\n \tAdditional information about the repository is recorded\n-\tin this directory. This directory is ignored $GIT_COMMON_DIR\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@@ -210,7 +210,7 @@ remotes::\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. This\n-\tdirectory is ignored $GIT_COMMON_DIR is set and\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@@ -230,7 +230,7 @@ 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]. This\n-\tfile is ignored $GIT_COMMON_DIR is set and\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@@ -242,7 +242,7 @@ commondir::\n \n modules::\n \tContains the git-repositories of the submodules. This\n-\tdirectory is ignored $GIT_COMMON_DIR is set and\n+\tdirectory is ignored if $GIT_COMMON_DIR is set and\n \t\"$GIT_COMMON_DIR/modules\" will be used instead.\n \n repos::\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 24b7f36..9dc80f1 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -599,9 +599,10 @@ 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\tstruct strbuf log_file = STRBUF_INIT;\n-\t\t\t\tchar *ref_name = mkpath(\"refs/heads/%s\", opts->new_orphan_branch);\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\tret = log_ref_setup(ref_name, &log_file);\n@@ -843,8 +844,8 @@ static void remove_junk(void)\n \tif (junk_work_tree) {\n \t\tstrbuf_addstr(&sb, junk_work_tree);\n \t\tremove_dir_recursively(&sb, 0);\n-\t\tstrbuf_reset(&sb);\n \t}\n+\tstrbuf_release(&sb);\n }\n \n static void remove_junk_on_signal(int signo)\n@@ -854,17 +855,6 @@ static void remove_junk_on_signal(int signo)\n \traise(signo);\n }\n \n-static dev_t get_device_or_die(const char *path)\n-{\n-\tstruct stat buf;\n-\tif (stat(path, &buf))\n-\t\tdie_errno(\"failed to stat '%s'\", path);\n-\t/* Ah Windows! Make different drives different \"partitions\" */\n-\tif (is_windows())\n-\t\tbuf.st_dev = toupper(real_path(path)[0]);\n-\treturn buf.st_dev;\n-}\n-\n static int prepare_linked_checkout(const struct checkout_opts *opts,\n \t\t\t\t   struct branch_info *new)\n {\n@@ -873,7 +863,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, keep_locked = 0, ret;\n+\tint counter = 0, len, ret;\n \n \tif (!new->commit)\n \t\tdie(_(\"no branch specified\"));\n@@ -909,6 +899,10 @@ 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 prunt 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@@ -936,18 +930,6 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n \tstrbuf_addf(&sb, \"%s/commondir\", sb_repo.buf);\n \twrite_file(sb.buf, 1, \"../..\\n\");\n \n-\tif (get_device_or_die(path) != get_device_or_die(get_git_dir())) {\n-\t\tstrbuf_reset(&sb);\n-\t\tstrbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n-\t\twrite_file(sb.buf, 1, \"located on a different file system\\n\");\n-\t\tkeep_locked = 1;\n-\t} else {\n-\t\tstrbuf_reset(&sb);\n-\t\tstrbuf_addf(&sb, \"%s/link\", sb_repo.buf);\n-\t\tif (link(sb_git.buf, sb.buf))\n-\t\t\t; /* ignore silently, without a clear reason */\n-\t}\n-\n \tif (!opts->quiet)\n \t\tfprintf_ln(stderr, _(\"Enter %s (identifier %s)\"), path, name);\n \n@@ -960,11 +942,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-\tif (!keep_locked) {\n-\t\tstrbuf_reset(&sb);\n-\t\tstrbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n-\t\tunlink_or_warn(sb.buf);\n-\t}\n+\tstrbuf_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/clone.c b/builtin/clone.c\nindex 43e772c..9339371 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/count-objects.c b/builtin/count-objects.c\nindex 725cd5f..d3a1620 100644\n--- a/builtin/count-objects.c\n+++ b/builtin/count-objects.c\n@@ -78,39 +78,6 @@ static void count_objects(DIR *d, char *path, int len, int verbose,\n \t}\n }\n \n-static void report_linked_checkout_garbage(void)\n-{\n-\t/*\n-\t * must be more or less in sync with * path.c:update_common_dir().\n-\t *\n-\t * \"logs\" is let slip because logs/HEAD is in $GIT_DIR but the\n-\t * remaining in $GIT_COMMON_DIR. Probably not worth traversing\n-\t * the entire \"logs\" directory for that.\n-\t *\n-\t * The same \"gc.pid\" for because it's a temporary file.\n-\t */\n-\tconst char *list[] = {\n-\t\t\"branches\", \"hooks\", \"info\", \"lost-found\", \"modules\",\n-\t\t\"objects\", \"refs\", \"remotes\", \"rr-cache\", \"svn\",\n-\t\t\"config\", \"packed-refs\", \"shallow\", NULL\n-\t};\n-\tstruct strbuf sb = STRBUF_INIT;\n-\tconst char **p;\n-\tint len;\n-\n-\tif (!file_exists(git_path(\"commondir\")))\n-\t\treturn;\n-\tstrbuf_addf(&sb, \"%s/\", get_git_dir());\n-\tlen = sb.len;\n-\tfor (p = list; *p; p++) {\n-\t\tstrbuf_setlen(&sb, len);\n-\t\tstrbuf_addstr(&sb, *p);\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 char const * const count_objects_usage[] = {\n \tN_(\"git count-objects [-v] [-H | --human-readable]\"),\n \tNULL\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 025bc3e..04b51ca 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -544,7 +544,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@@ -778,7 +779,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 1affdd5..643c980 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 85bba35..4d27acb 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 b3ab4cf..ca3cb3b 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 bb2314c..4d35349 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -257,7 +257,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 < 2; 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]);\n \t\t\tif (!file_exists(fname)) {\n@@ -285,7 +286,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@@ -334,7 +336,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 < 2; 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 99b86d9..a0ff430 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -658,10 +658,11 @@ 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+extern void report_linked_checkout_garbage(void);\n \n extern char *sha1_file_name(const unsigned char *sha1);\n extern char *sha1_pack_name(const unsigned char *sha1);\ndiff --git a/compat/mingw.h b/compat/mingw.h\nindex 18323c1..e033e72 100644\n--- a/compat/mingw.h\n+++ b/compat/mingw.h\n@@ -328,7 +328,6 @@ int winansi_fprintf(FILE *stream, const char *format, ...) __attribute__((format\n  * git specific compatibility\n  */\n \n-#define is_windows() 1\n #define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':')\n #define is_dir_sep(c) ((c) == '/' || (c) == '\\\\')\n static inline char *mingw_find_last_dir_sep(const char *path)\ndiff --git a/fast-import.c b/fast-import.c\nindex 08a1e78..28e7a63 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -403,7 +403,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/git-am.sh b/git-am.sh\nindex dfa0618..10babd2 100755\n--- a/git-am.sh\n+++ b/git-am.sh\n@@ -803,7 +803,7 @@ To restore the original branch and stop patching run \\\"\\$cmdline --abort\\\".\"\n \t\tcontinue\n \tfi\n \n-\thook=\"`git rev-parse --git-path hooks/applypatch-msg`\"\n+\thook=\"$(git rev-parse --git-path hooks/applypatch-msg)\"\n \tif test -x \"$hook\"\n \tthen\n \t\t\"$hook\" \"$dotest/final-commit\" || stop_here $this\n@@ -880,7 +880,7 @@ did you forget to use 'git add'?\"\n \t\tstop_here_user_resolve $this\n \tfi\n \n-\thook=\"`git rev-parse --git-path hooks/pre-applypatch`\"\n+\thook=\"$(git rev-parse --git-path hooks/pre-applypatch)\"\n \tif test -x \"$hook\"\n \tthen\n \t\t\"$hook\" || stop_here $this\n@@ -909,7 +909,7 @@ did you forget to use 'git add'?\"\n \t\techo \"$(cat \"$dotest/original-commit\") $commit\" >> \"$dotest/rewritten\"\n \tfi\n \n-\thook=\"`git rev-parse --git-path hooks/post-applypatch`\"\n+\thook=\"$(git rev-parse --git-path hooks/post-applypatch)\"\n \ttest -x \"$hook\" && \"$hook\"\n \n \tgo_next\n@@ -917,7 +917,7 @@ done\n \n if test -s \"$dotest\"/rewritten; then\n     git notes copy --for-rewrite=rebase < \"$dotest\"/rewritten\n-    hook=\"`git rev-parse --git-path hooks/post-rewrite`\"\n+    hook=\"$(git rev-parse --git-path hooks/post-rewrite)\"\n     if test -x \"$hook\"; then\n \t\"$hook\" rebase < \"$dotest\"/rewritten\n     fi\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 41f1b74..cbd86c3 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -266,10 +266,6 @@ extern char *gitbasename(char *);\n #define STRIP_EXTENSION \"\"\n #endif\n \n-#ifndef is_windows\n-#define is_windows() 0\n-#endif\n-\n #ifndef has_dos_drive_prefix\n #define has_dos_drive_prefix(path) 0\n #endif\ndiff --git a/git-pull.sh b/git-pull.sh\nindex c9dc9ba..2a90262 100755\n--- a/git-pull.sh\n+++ b/git-pull.sh\n@@ -218,7 +218,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 rev-parse --git-path 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-rebase--interactive.sh b/git-rebase--interactive.sh\nindex d741b04..dd84f9d 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -632,7 +632,7 @@ 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-\thook=\"`git rev-parse --git-path hooks/post-rewrite`\"\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\ndiff --git a/git-rebase--merge.sh b/git-rebase--merge.sh\nindex 68f5d09..93d7bcf 100644\n--- a/git-rebase--merge.sh\n+++ b/git-rebase--merge.sh\n@@ -93,7 +93,7 @@ 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\thook=\"`git rev-parse --git-path hooks/post-rewrite`\"\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.\ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex 1cf8dba..b90a6d9 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -195,9 +195,9 @@ run_specific_rebase () {\n \n run_pre_rebase_hook () {\n \tif test -z \"$ok_to_skip_pre_rebase\" &&\n-\t   test -x \"`git rev-parse --git-path hooks/pre-rebase`\"\n+\t   test -x \"$(git rev-parse --git-path hooks/pre-rebase)\"\n \tthen\n-\t\t\"`git rev-parse --git-path 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/git-stash.sh b/git-stash.sh\nindex 12d9b37..8093326 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 rev-parse --git-path 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@@ -183,7 +183,7 @@ store_stash () {\n \tfi\n \n \t# Make sure the reflog for stash is kept.\n-\t: >>\"`git rev-parse --git-path 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@@ -258,7 +258,7 @@ save_stash () {\n \t\tsay \"$(gettext \"No local changes to save\")\"\n \t\texit 0\n \tfi\n-\ttest -f \"`git rev-parse --git-path 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\ndiff --git a/notes-merge.c b/notes-merge.c\nindex 94a1a8a..d59bcc2 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 2e6035d..5a7dc45 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@@ -90,35 +91,60 @@ 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\", \"/repos\", \"/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-\t/*\n-\t * Remember to report_linked_checkout_garbage()\n-\t * builtin/count-objects.c\n-\t */\n-\tconst char *common_dir_list[] = {\n-\t\t\"branches\", \"hooks\", \"info\", \"logs\", \"lost-found\", \"modules\",\n-\t\t\"objects\", \"refs\", \"remotes\", \"repos\", \"rr-cache\", \"svn\",\n-\t\tNULL\n-\t};\n-\tconst char *common_top_file_list[] = {\n-\t\t\"config\", \"gc.pid\", \"packed-refs\", \"shallow\", NULL\n-\t};\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_dir_list; *p; p++)\n-\t\tif (dir_prefix(base, *p)) {\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+\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-\tfor (p = common_top_file_list; *p; p++)\n-\t\tif (!strcmp(base, *p)) {\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+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@@ -138,9 +164,8 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)\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 \tint gitdir_len;\n-\tstrbuf_addstr(buf, git_dir);\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@@ -157,7 +182,7 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)\n \tva_end(args);\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@@ -188,7 +213,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@@ -224,7 +249,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 434bd5e..c5613b0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1232,7 +1232,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@@ -2044,7 +2044,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@@ -2107,7 +2107,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@@ -2545,7 +2545,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:\n@@ -2729,43 +2729,41 @@ static int copy_msg(char *buf, const char *msg)\n \treturn cp - buf;\n }\n \n-int log_ref_setup(const char *refname, struct strbuf *sb_logfile)\n+int log_ref_setup(const char *refname, struct strbuf *logfile)\n {\n \tint logfd, oflags = O_APPEND | O_WRONLY;\n-\tconst char *logfile;\n \n-\tstrbuf_git_path(sb_logfile, \"logs/%s\", refname);\n-\tlogfile = sb_logfile->buf;\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(sb_logfile->buf) < 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 }\ndiff --git a/run-command.c b/run-command.c\nindex 3914d9c..a94a612 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -751,9 +751,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 6b985af..da01954 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -45,7 +45,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(const char *index_file, const char *name, ...);\n \ndiff --git a/sha1_file.c b/sha1_file.c\nindex 6e8c05d..8af0e18 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\");\ndiff --git a/templates/hooks--applypatch-msg.sample b/templates/hooks--applypatch-msg.sample\nindex 28b843b..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-commitmsg=\"`git rev-parse --git-path hooks/commit-msg`\"\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 51aa244..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-precommit=\"`git rev-parse --git-path hooks/pre-commit`\"\n+precommit=\"$(git rev-parse --git-path hooks/pre-commit)\"\n test -x \"$precommit\" && exec \"$precommit\" ${1+\"$@\"}\n :\ndiff --git a/wrapper.c b/wrapper.c\nindex 5ced50d..5ad628c 100644\n--- a/wrapper.c\n+++ b/wrapper.c\n@@ -459,8 +459,8 @@ struct passwd *xgetpwuid_self(void)\n int write_file(const char *path, int fatal, const char *fmt, ...)\n {\n \tstruct strbuf sb = STRBUF_INIT;\n-\tint fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);\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@@ -472,8 +472,8 @@ int write_file(const char *path, int fatal, const char *fmt, ...)\n \tif (write_in_full(fd, sb.buf, sb.len) != sb.len) {\n \t\tint err = errno;\n \t\tclose(fd);\n-\t\terrno = err;\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-- 8< --\n-- \n1.9.0.40.gaa8c3ea\n"},{"id":"236288","messageId":"1394246900-31535-2-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 01/28] 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-03-08T02:47:53Z","receivedAt":"2014-03-08T02:47:53Z","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 24594c4..5346700 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.0.40.gaa8c3ea\n"},{"id":"236293","messageId":"1394246900-31535-3-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 02/28] path.c: make get_pathname() call sites return const char *","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:47:54Z","receivedAt":"2014-03-08T02:47:54Z","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 5df3837..64c2aca 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -586,7 +586,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 43e772c..9339371 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 025bc3e..04b51ca 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -544,7 +544,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@@ -778,7 +779,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 1affdd5..643c980 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 85bba35..4d27acb 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 b3ab4cf..ca3cb3b 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 bb2314c..4d35349 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -257,7 +257,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 < 2; 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]);\n \t\t\tif (!file_exists(fname)) {\n@@ -285,7 +286,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@@ -334,7 +336,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 < 2; 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 dc040fb..a344a5f 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -654,9 +654,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 extern char *sha1_file_name(const unsigned char *sha1);\ndiff --git a/fast-import.c b/fast-import.c\nindex 4fd18a3..a9f328d 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -403,7 +403,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 94a1a8a..d59bcc2 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 5346700..36d461e 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 89228e2..f846f2f 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1232,7 +1232,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@@ -2032,7 +2032,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@@ -2095,7 +2095,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@@ -2533,7 +2533,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 3914d9c..a94a612 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -751,9 +751,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 6b985af..da01954 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -45,7 +45,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(const char *index_file, const char *name, ...);\n \ndiff --git a/sha1_file.c b/sha1_file.c\nindex 6e8c05d..8af0e18 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.0.40.gaa8c3ea\n"},{"id":"236292","messageId":"1394246900-31535-4-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 03/28] Convert git_snpath() to strbuf_git_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:47:55Z","receivedAt":"2014-03-08T02:47:55Z","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 awkward\nbecause git_snpath() receives a pre-allocated buffer from outside and\nhas to copy data back. Rename it to strbuf_git_path() and make it\nreceive strbuf directly.\n\nThe conversion from git_snpath() to git_path() in\nupdate_refs_for_switch() is safe because that function does not keep\nany 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 | 25 +++++++++--------\n cache.h            |  4 +--\n path.c             | 11 ++------\n refs.c             | 78 +++++++++++++++++++++++++++++++++---------------------\n refs.h             |  2 +-\n 5 files changed, 65 insertions(+), 55 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 64c2aca..efb5e2f 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -585,18 +585,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\n@@ -651,14 +654,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\t\t\tnew->name);\n \t\t\t}\n \t\t}\n-\t\tif (old->path && old->name) {\n-\t\t\tchar log_file[PATH_MAX], ref_file[PATH_MAX];\n-\n-\t\t\tgit_snpath(log_file, sizeof(log_file), \"logs/%s\", old->path);\n-\t\t\tgit_snpath(ref_file, sizeof(ref_file), \"%s\", old->path);\n-\t\t\tif (!file_exists(ref_file) && file_exists(log_file))\n-\t\t\t\tremove_path(log_file);\n-\t\t}\n+\t\tif (old->path && old->name &&\n+\t\t    !file_exists(git_path(\"%s\", old->path)) &&\n+\t\t     file_exists(git_path(\"logs/%s\", old->path)))\n+\t\t\tremove_path(git_path(\"logs/%s\", old->path));\n \t}\n \tremove_branch_state();\n \tstrbuf_release(&msg);\ndiff --git a/cache.h b/cache.h\nindex a344a5f..0fae730 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -646,8 +646,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 36d461e..417df76 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 f846f2f..c5613b0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1325,10 +1325,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@@ -1337,15 +1339,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@@ -1359,10 +1363,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@@ -1373,7 +1378,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@@ -1389,7 +1394,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@@ -1402,12 +1407,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@@ -1424,9 +1430,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@@ -1436,10 +1443,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@@ -2717,41 +2729,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@@ -2762,20 +2774,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@@ -2788,9 +2802,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 87a1a79..783033a 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -166,7 +166,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 *ref_name, char *logfile, int bufsize);\n+int log_ref_setup(const char *ref_name, 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.0.40.gaa8c3ea\n"},{"id":"236290","messageId":"1394246900-31535-5-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 04/28] path.c: rename vsnpath() to do_git_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:47:56Z","receivedAt":"2014-03-08T02:47:56Z","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 417df76..987fdec 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.0.40.gaa8c3ea\n"},{"id":"236289","messageId":"1394246900-31535-6-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 05/28] 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-03-08T02:47:57Z","receivedAt":"2014-03-08T02:47: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 path.c | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/path.c b/path.c\nindex 987fdec..1c0f160 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.0.40.gaa8c3ea\n"},{"id":"236295","messageId":"1394246900-31535-7-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 06/28] Make git_path() aware of file relocation in $GIT_DIR","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:47:58Z","receivedAt":"2014-03-08T02:47:58Z","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\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                   |  9 ++++++--\n path.c                          | 49 +++++++++++++++++++++++++++++++++++++++--\n t/t0060-path-utils.sh           | 19 ++++++++++++++++\n 6 files changed, 88 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt\nindex 0d2cdcd..46020d9 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 /tmp/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 aaeb611..e50bc65 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -518,6 +518,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 0fae730..0f5f44f 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -585,6 +585,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 4a3437d..f513479 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -82,6 +82,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@@ -137,15 +138,19 @@ static void setup_git_env(void)\n \tif (!git_object_dir) {\n \t\tgit_object_dir = xmalloc(strlen(git_dir) + 9);\n \t\tsprintf(git_object_dir, \"%s/objects\", git_dir);\n-\t}\n+\t} else\n+\t\tgit_db_env = 1;\n \tgit_index_file = getenv(INDEX_ENVIRONMENT);\n \tif (!git_index_file) {\n \t\tgit_index_file = xmalloc(strlen(git_dir) + 7);\n \t\tsprintf(git_index_file, \"%s/index\", git_dir);\n-\t}\n+\t} else\n+\t\tgit_index_env = 1;\n \tgit_graft_file = getenv(GRAFT_ENVIRONMENT);\n \tif (!git_graft_file)\n \t\tgit_graft_file = git_pathdup(\"info/grafts\");\n+\telse\n+\t\tgit_graft_env = 1;\n \tif (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))\n \t\tread_replace_refs = 0;\n \tnamespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));\ndiff --git a/path.c b/path.c\nindex 1c0f160..1069ae0 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 07c10c8..1d29901 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@@ -223,4 +231,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.0.40.gaa8c3ea\n"},{"id":"236291","messageId":"1394246900-31535-8-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 07/28] *.sh: respect $GIT_INDEX_FILE","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:47:59Z","receivedAt":"2014-03-08T02:47:59Z","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 0a5aa2c..2a90262 100755\n--- a/git-pull.sh\n+++ b/git-pull.sh\n@@ -218,7 +218,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 f0a94ab..0de9a6c 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.0.40.gaa8c3ea\n"},{"id":"236294","messageId":"1394246900-31535-9-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 08/28] reflog: avoid constructing .lock path with git_path","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:48:00Z","receivedAt":"2014-03-08T02:48:00Z","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 852cff6..ccf2cf6 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -372,7 +372,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,\n \tif (!file_exists(log_file))\n \t\tgoto finish;\n \tif (!cmd->dry_run) {\n-\t\tnewlog_path = git_pathdup(\"logs/%s.lock\", ref);\n+\t\tnewlog_path = mkpathdup(\"%s.lock\", log_file);\n \t\tcb.newlog = fopen(newlog_path, \"w\");\n \t}\n \n-- \n1.9.0.40.gaa8c3ea\n"},{"id":"236296","messageId":"1394246900-31535-10-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 09/28] 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-03-08T02:48:01Z","receivedAt":"2014-03-08T02:48:01Z","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 a9f328d..28e7a63 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -3125,12 +3125,9 @@ static void parse_progress(void)\n \n static char* make_fast_import_path(const char *path)\n {\n-\tstruct strbuf abs_path = STRBUF_INIT;\n-\n \tif (!relative_marks_paths || is_absolute_path(path))\n \t\treturn xstrdup(path);\n-\tstrbuf_addf(&abs_path, \"%s/info/fast-import/%s\", get_git_dir(), path);\n-\treturn strbuf_detach(&abs_path, NULL);\n+\treturn xstrdup(git_path(\"info/fast-import/%s\", path));\n }\n \n static void option_import_marks(const char *marks,\n-- \n1.9.0.40.gaa8c3ea\n"},{"id":"236297","messageId":"1394246900-31535-11-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 10/28] commit: use SEQ_DIR instead of hardcoding \"sequencer\"","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:48:02Z","receivedAt":"2014-03-08T02:48: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 builtin/commit.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 3767478..ee3ac10 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -155,7 +155,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.0.40.gaa8c3ea\n"},{"id":"236298","messageId":"1394246900-31535-12-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 11/28] Add new environment variable $GIT_COMMON_DIR","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:48:03Z","receivedAt":"2014-03-08T02:48:03Z","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\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                          | 19 +++++++++++----\n path.c                                 | 34 +++++++++++++++++++++++++++\n t/t0060-path-utils.sh                  | 15 ++++++++++++\n 6 files changed, 108 insertions(+), 14 deletions(-)\n\ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex 02bbc08..b094b1f 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -773,6 +773,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 aa03882..556da09 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@@ -193,12 +209,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@@ -209,10 +229,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 0f5f44f..9182427 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -347,6 +347,7 @@ static inline enum object_type object_type(unsigned int mode)\n \n /* Double-check local_repo_env below if you add to this list. */\n #define GIT_DIR_ENVIRONMENT \"GIT_DIR\"\n+#define GIT_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@@ -400,6 +401,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@@ -585,7 +587,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 f513479..c998120 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -80,9 +80,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@@ -134,10 +134,16 @@ 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+\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 = 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\tgit_object_dir = xmalloc(strlen(git_common_dir) + 9);\n+\t\tsprintf(git_object_dir, \"%s/objects\", git_common_dir);\n \t} else\n \t\tgit_db_env = 1;\n \tgit_index_file = getenv(INDEX_ENVIRONMENT);\n@@ -173,6 +179,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 1069ae0..8453687 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 1d29901..f9a77e4 100755\n--- a/t/t0060-path-utils.sh\n+++ b/t/t0060-path-utils.sh\n@@ -241,5 +241,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.0.40.gaa8c3ea\n"},{"id":"236299","messageId":"1394246900-31535-13-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 12/28] 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-03-08T02:48:04Z","receivedAt":"2014-03-08T02:48:04Z","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 fffa3c7..475ca43 100644\n--- a/git-sh-setup.sh\n+++ b/git-sh-setup.sh\n@@ -343,7 +343,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.0.40.gaa8c3ea\n"},{"id":"236300","messageId":"1394246900-31535-14-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 13/28] *.sh: avoid hardcoding $GIT_DIR/hooks/...","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:48:05Z","receivedAt":"2014-03-08T02:48:05Z","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 bbea430..10babd2 100755\n--- a/git-am.sh\n+++ b/git-am.sh\n@@ -803,10 +803,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@@ -880,9 +880,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@@ -908,18 +909,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 43c19e0..dd84f9d 100644\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -632,9 +632,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 e7d96de..93d7bcf 100644\n--- a/git-rebase--merge.sh\n+++ b/git-rebase--merge.sh\n@@ -93,10 +93,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 8a3efa2..b90a6d9 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -195,9 +195,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.0.40.gaa8c3ea\n"},{"id":"236301","messageId":"1394246900-31535-15-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 14/28] git-stash: avoid hardcoding $GIT_DIR/logs/....","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:48:06Z","receivedAt":"2014-03-08T02:48:06Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n git-stash.sh | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/git-stash.sh b/git-stash.sh\nindex 0de9a6c..8093326 100755\n--- a/git-stash.sh\n+++ b/git-stash.sh\n@@ -183,7 +183,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@@ -258,7 +258,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.0.40.gaa8c3ea\n"},{"id":"236302","messageId":"1394246900-31535-16-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 15/28] setup.c: convert is_git_directory() to use strbuf","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:48:07Z","receivedAt":"2014-03-08T02:48: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 setup.c | 37 +++++++++++++++++++++----------------\n 1 file changed, 21 insertions(+), 16 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 6c3f85f..4994437 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -184,31 +184,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.0.40.gaa8c3ea\n"},{"id":"236303","messageId":"1394246900-31535-17-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 16/28] setup.c: detect $GIT_COMMON_DIR in is_git_directory()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:48:08Z","receivedAt":"2014-03-08T02:48:08Z","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 556da09..c6f3649 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -233,6 +233,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 4994437..7e5b334 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -170,6 +170,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@@ -188,13 +215,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@@ -205,11 +241,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.0.40.gaa8c3ea\n"},{"id":"236304","messageId":"1394246900-31535-18-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 17/28] setup.c: convert check_repository_format_gently to use strbuf","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:48:09Z","receivedAt":"2014-03-08T02:48:09Z","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 7e5b334..5085ab1 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -288,7 +288,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@@ -299,7 +301,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@@ -309,9 +312,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.0.40.gaa8c3ea\n"},{"id":"236305","messageId":"1394246900-31535-19-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 18/28] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:48:10Z","receivedAt":"2014-03-08T02:48: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 setup.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 5085ab1..42849f3 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -292,6 +292,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@@ -301,8 +305,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.0.40.gaa8c3ea\n"},{"id":"236306","messageId":"1394246900-31535-20-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 19/28] setup.c: support multi-checkout repo setup","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:48:11Z","receivedAt":"2014-03-08T02:48:11Z","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 5f4d793..313d4b3 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -381,6 +381,8 @@ false), while all other repositories are assumed to be bare (bare\n \n core.worktree::\n \tSet the path to the root of the working tree.\n+\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 46020d9..8a9cac8 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 e50bc65..c7057ce 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -744,6 +744,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 = resolve_gitdir(argv[i+1]);\n \t\t\t\tif (!gitdir)\ndiff --git a/cache.h b/cache.h\nindex 9182427..992e241 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -407,6 +407,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 c998120..0999fc1 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -126,6 +126,7 @@ static char *expand_namespace(const char *raw_namespace)\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@@ -134,12 +135,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 = getenv(DB_ENVIRONMENT);\n \tif (!git_object_dir) {\n \t\tgit_object_dir = xmalloc(strlen(git_common_dir) + 9);\ndiff --git a/setup.c b/setup.c\nindex 42849f3..40ce191 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -170,14 +170,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@@ -192,10 +193,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@@ -286,13 +289,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@@ -305,7 +321,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@@ -777,11 +793,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..2ac4424 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\techo \"$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\techo \"$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\techo \"$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\techo \"$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 cf2ee78..c211ef5 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.0.40.gaa8c3ea\n"},{"id":"236307","messageId":"1394246900-31535-21-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 20/28] wrapper.c: wrapper to open a file, fprintf then close","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:48:12Z","receivedAt":"2014-03-08T02:48:12Z","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 992e241..979ac6e 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1239,6 +1239,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 0cc5636..5ad628c 100644\n--- a/wrapper.c\n+++ b/wrapper.c\n@@ -455,3 +455,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.0.40.gaa8c3ea\n"},{"id":"236308","messageId":"1394246900-31535-22-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 21/28] use new wrapper write_file() for simple file writing","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:48:13Z","receivedAt":"2014-03-08T02:48:13Z","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 b4d7716..3eebdbc 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 c7c76bb..081e512 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 503e039..b880d30 100644\n--- a/daemon.c\n+++ b/daemon.c\n@@ -1122,15 +1122,6 @@ static void daemonize(void)\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@@ -1339,7 +1330,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 613857e..fe5748d 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1135,16 +1135,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 ca7bb44..2df8a15 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.0.40.gaa8c3ea\n"},{"id":"236309","messageId":"1394246900-31535-23-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 22/28] checkout: support checking out into a new working directory","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:48:14Z","receivedAt":"2014-03-08T02:48:14Z","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 b094b1f..bdb9b0f 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -777,7 +777,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 c6f3649..2206f35 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -245,6 +245,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 efb5e2f..aa4ffd1 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@@ -486,7 +493,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@@ -797,7 +804,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@@ -807,6 +815,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@@ -1068,6 +1144,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@@ -1110,6 +1189,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@@ -1118,6 +1199,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@@ -1126,6 +1210,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 8453687..ddb5962 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.0.40.gaa8c3ea\n"},{"id":"236310","messageId":"1394246900-31535-24-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 23/28] checkout: clean up half-prepared directories in --to mode","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:48:15Z","receivedAt":"2014-03-08T02:48:15Z","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 aa4ffd1..97fdfcc 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@@ -815,6 +816,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@@ -823,7 +853,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@@ -849,13 +879,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@@ -880,7 +918,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.0.40.gaa8c3ea\n"},{"id":"236311","messageId":"1394246900-31535-25-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 24/28] checkout: detach if the branch is already checked out elsewhere","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:48:16Z","receivedAt":"2014-03-08T02:48:16Z","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 97fdfcc..598b43d 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -433,6 +433,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@@ -641,6 +646,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@@ -983,6 +993,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@@ -1110,6 +1187,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.0.40.gaa8c3ea\n"},{"id":"236312","messageId":"1394246900-31535-26-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 25/28] prune: strategies for linked checkouts","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:48:17Z","receivedAt":"2014-03-08T02:48:17Z","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 058ac0d..7babf11 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 2206f35..dff6c47 100644\n--- a/Documentation/gitrepository-layout.txt\n+++ b/Documentation/gitrepository-layout.txt\n@@ -252,6 +252,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 598b43d..9dc80f1 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -899,12 +899,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 prunt 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@@ -913,6 +923,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@@ -931,6 +942,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 de43b26..733cb3b 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 40ce191..5529b26 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -336,6 +336,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@@ -384,6 +395,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.0.40.gaa8c3ea\n"},{"id":"236313","messageId":"1394246900-31535-27-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 26/28] gc: style change -- no SP before closing bracket","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:48:18Z","receivedAt":"2014-03-08T02:48:18Z","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 c19545d..39d9b27 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -260,7 +260,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@@ -273,7 +273,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.0.40.gaa8c3ea\n"},{"id":"236314","messageId":"1394246900-31535-28-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 27/28] gc: support prune --repos","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:48:19Z","receivedAt":"2014-03-08T02:48:19Z","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 313d4b3..c635b8d 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1183,6 +1183,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 39d9b27..85c3c0c 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -30,11 +30,13 @@ static int aggressive_window = 250;\n static int gc_auto_threshold = 6700;\n static int gc_auto_pack_limit = 50;\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@@ -81,6 +83,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@@ -274,6 +284,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@@ -334,6 +345,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.0.40.gaa8c3ea\n"},{"id":"236315","messageId":"1394246900-31535-29-git-send-email-pclouds@gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v5 28/28] count-objects: report unused files in $GIT_DIR/repos/...","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T02:48:20Z","receivedAt":"2014-03-08T02:48:20Z","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 979ac6e..a0ff430 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -662,6 +662,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 extern char *sha1_file_name(const unsigned char *sha1);\n extern char *sha1_pack_name(const unsigned char *sha1);\ndiff --git a/path.c b/path.c\nindex ddb5962..5a7dc45 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.0.40.gaa8c3ea\n"},{"id":"236316","messageId":"531AB47B.7070506@web.de","threadId":"35908","inReplyTo":"1394246900-31535-2-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v5 01/28] path.c: make get_pathname() return strbuf instead of static buffer","fromName":"Torsten Bögershausen","fromEmail":"tboegi@web.de","sentAt":"2014-03-08T06:11:07Z","receivedAt":"2014-03-08T06:11:07Z","isPatch":true,"sender":{"key":"tboegi@web.de","avatar":"https://avatars.githubusercontent.com/u/7138363?v=4"},"body":"On 2014-03-08 03.47, Nguyễn Thái Ngọc Duy wrote:\n> We've been avoiding PATH_MAX whenever possible. This patch makes\n> get_pathname() return a strbuf and updates the callers to take\n> advantage of this. The code is simplified as we no longer need to\n> worry about buffer overflow.\n> \n> vsnpath() behavior is changed slightly: previously it always clears\nMinor question:\nIs the function name vsnpath() appropriate any more ?\nHow about renaming the function into strbuf_vaddpath() ?\n\n> the buffer before writing, now it just appends. Fortunately this is a\n> static function and all of its callers prepare the buffer properly:\n> git_path() gets the buffer from get_pathname() which resets the\n> buffer, the remaining call sites start with STRBUF_INIT'd buffer.\n> \n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n"},{"id":"236318","messageId":"CACsJy8BMK-CkzQjx5cRA6EK4rHdvng9QtNuh5jexd2UKpM_CyA@mail.gmail.com","threadId":"35908","inReplyTo":"531AB47B.7070506@web.de","subject":"Re: [PATCH v5 01/28] path.c: make get_pathname() return strbuf instead of static buffer","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-08T08:57:49Z","receivedAt":"2014-03-08T08:57:49Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Sat, Mar 8, 2014 at 1:11 PM, Torsten Bögershausen <tboegi@web.de> wrote:\n> On 2014-03-08 03.47, Nguyễn Thái Ngọc Duy wrote:\n>> We've been avoiding PATH_MAX whenever possible. This patch makes\n>> get_pathname() return a strbuf and updates the callers to take\n>> advantage of this. The code is simplified as we no longer need to\n>> worry about buffer overflow.\n>>\n>> vsnpath() behavior is changed slightly: previously it always clears\n> Minor question:\n> Is the function name vsnpath() appropriate any more ?\n> How about renaming the function into strbuf_vaddpath() ?\n\nIt is renamed in 04/28 to do_git_path().\n-- \nDuy\n"},{"id":"236335","messageId":"CAPig+cSQUFtT+eoNG8JYEqJq=yKU+xou7WNJNcJ2DPU5wBLy-w@mail.gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v5 00/28] Support multiple checkouts","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-03-09T08:18:42Z","receivedAt":"2014-03-09T08:18:42Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Mar 7, 2014 at 9:47 PM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> The diff against v4 is kinda big but it's mostly about converting\n> `...` to $(...) and making git_path() and friends return a const\n> string.\n>\n> Another notable change is I no longer attempt to support checkouts on\n> portable devices. Torsten pointed out (privately) that my dealing with\n> Windows drives was insufficient. And Junio was not so happy with how\n> link() was handled either. We can revisit it later.\n>\n> Many thanks to Eric, who was very patient to go through the series\n> carefully and pointed out problems that I overlooked.\n>\n> v4..v5 diff below for convenience\n\nThanks for providing an interdiff between the two versions. It\nsimplified things considerably, especially with the v4 reviews still\nrelatively fresh in my brain.\n"},{"id":"236336","messageId":"CAPig+cTZb5diOEGNKJVpmQaAGZS9+3wWvS_MxGTQEeLLkhsRjg@mail.gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-7-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v5 06/28] Make git_path() aware of file relocation in $GIT_DIR","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-03-09T08:19:27Z","receivedAt":"2014-03-09T08:19:27Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Mar 7, 2014 at 9:47 PM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> We allow the user to relocate certain paths out of $GIT_DIR via\n> environment variables, e.g. GIT_OBJECT_DIRECTORY, GIT_INDEX_FILE and\n> GIT_GRAFT_FILE. Callers are not supposed to use git_path() or\n> git_pathdup() to get those paths. Instead they must use\n> get_object_directory(), get_index_file() and get_graft_file()\n> respectively. This is inconvenient and could be missed in review (for\n> example, there's git_path(\"objects/info/alternates\") somewhere in\n> sha1_file.c).\n>\n> This patch makes git_path() and git_pathdup() understand those\n> environment variables. So if you set GIT_OBJECT_DIRECTORY to /foo/bar,\n> git_path(\"objects/abc\") should return /foo/bar/abc. The same is done\n> for the two remaining env variables.\n>\n> \"git rev-parse --git-path\" is the wrapper for script use.\n>\n> Signed-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                   |  9 ++++++--\n>  path.c                          | 49 +++++++++++++++++++++++++++++++++++++++--\n>  t/t0060-path-utils.sh           | 19 ++++++++++++++++\n>  6 files changed, 88 insertions(+), 4 deletions(-)\n>\n> diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt\n> index 0d2cdcd..46020d9 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>         repository.  If <path> is a gitfile then the resolved path\n>         to the real repository is printed.\n>\n> +--git-path <path>::\n> +       Resolve \"$GIT_DIR/<path>\" and takes other path relocation\n> +       variables such as $GIT_OBJECT_DIRECTORY,\n> +       $GIT_INDEX_FILE... into account. For example, if\n> +       $GIT_OBJECT_DIRECTORY is set to /foo/bar then \"git rev-parse\n> +       --git-path objects/abc\" returns /tmp/bar/abc.\n\ns/tmp/foo/\n\n> +\n>  --show-cdup::\n>         When the command is invoked from a subdirectory, show the\n>         path of the top-level directory relative to the current\n> diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\n> index aaeb611..e50bc65 100644\n> --- a/builtin/rev-parse.c\n> +++ b/builtin/rev-parse.c\n> @@ -518,6 +518,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n>         for (i = 1; i < argc; i++) {\n>                 const char *arg = argv[i];\n>\n> +               if (!strcmp(arg, \"--git-path\")) {\n> +                       if (!argv[i + 1])\n> +                               die(\"--git-path requires an argument\");\n> +                       puts(git_path(\"%s\", argv[i + 1]));\n> +                       i++;\n> +                       continue;\n> +               }\n>                 if (as_is) {\n>                         if (show_file(arg, output_prefix) && as_is < 2)\n>                                 verify_filename(prefix, arg, 0);\n"},{"id":"236337","messageId":"CAPig+cRcyP7=g61MDkZeEQnkoNop-Qs64UW8qnPaPCjti8B_tw@mail.gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-26-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v5 25/28] prune: strategies for linked checkouts","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-03-09T08:20:28Z","receivedAt":"2014-03-09T08:20:28Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Mar 7, 2014 at 9:48 PM, 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/checkout.c b/builtin/checkout.c\n> index 598b43d..9dc80f1 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n> @@ -899,12 +899,22 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,\n>         junk_git_dir = sb_repo.buf;\n>         is_junk = 1;\n>\n> +       /*\n> +        * lock the incomplete repo so prunt won't delete it, unlock\n\ns/prunt/prune/\n\n> +        * after the preparation is over.\n> +        */\n> +       strbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n> +       write_file(sb.buf, 1, \"initializing\\n\");\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> +       strbuf_reset(&sb);\n> +       strbuf_addf(&sb, \"%s/gitdir\", sb_repo.buf);\n> +       write_file(sb.buf, 1, \"%s\\n\", real_path(sb_git.buf));\n>         write_file(sb_git.buf, 1, \"gitdir: %s/repos/%s\\n\",\n>                    real_path(get_git_common_dir()), name);\n>         /*\n"},{"id":"236338","messageId":"CAPig+cQpzYo_VzJfjMCevYm4__bHPSaFLNZSv2MFMA3ondJoSQ@mail.gmail.com","threadId":"35908","inReplyTo":"1394246900-31535-29-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v5 28/28] count-objects: report unused files in $GIT_DIR/repos/...","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-03-09T08:21:38Z","receivedAt":"2014-03-09T08:21:38Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Mar 7, 2014 at 9:48 PM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> In linked checkouts, borrowed parts like config is taken from\n> $GIT_COMMON_DIR. $GIT_DIR/config is never used. Report them as\n> garbage.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/path.c b/path.c\n> index ddb5962..5a7dc45 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\nDo you want to add a comment here explaining what the \"!\" prefix on\nsome entries means, or is it sufficiently self-evident to anyone\nlooking at the consumers of this array?\n\n>  static const char *common_list[] = {\n> -       \"/branches\", \"/hooks\", \"/info\", \"/logs\", \"/lost-found\", \"/modules\",\n> +       \"/branches\", \"/hooks\", \"/info\", \"!/logs\", \"/lost-found\", \"/modules\",\n>         \"/objects\", \"/refs\", \"/remotes\", \"/repos\", \"/rr-cache\", \"/svn\",\n> -       \"config\", \"gc.pid\", \"packed-refs\", \"shallow\",\n> +       \"config\", \"!gc.pid\", \"packed-refs\", \"shallow\",\n>         NULL\n>  };\n>\n> @@ -107,6 +108,8 @@ static void update_common_dir(struct strbuf *buf, int git_dir_len)\n>         for (p = common_list; *p; p++) {\n>                 const char *path = *p;\n>                 int is_dir = 0;\n> +               if (*path == '!')\n> +                       path++;\n>                 if (*path == '/') {\n>                         path++;\n>                         is_dir = 1;\n> @@ -122,6 +125,28 @@ static void update_common_dir(struct strbuf *buf, int git_dir_len)\n>         }\n>  }\n>\n> +void report_linked_checkout_garbage(void)\n> +{\n> +       struct strbuf sb = STRBUF_INIT;\n> +       const char **p;\n> +       int len;\n> +\n> +       if (!git_common_dir_env)\n> +               return;\n> +       strbuf_addf(&sb, \"%s/\", get_git_dir());\n> +       len = sb.len;\n> +       for (p = common_list; *p; p++) {\n> +               const char *path = *p;\n> +               if (*path == '!')\n> +                       continue;\n> +               strbuf_setlen(&sb, len);\n> +               strbuf_addstr(&sb, path);\n> +               if (file_exists(sb.buf))\n> +                       report_garbage(\"unused in linked checkout\", sb.buf);\n> +       }\n> +       strbuf_release(&sb);\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> --\n> 1.9.0.40.gaa8c3ea\n"},{"id":"236340","messageId":"CACsJy8DGSTYxam7Xws=y7as+h7Mppj9yXr_FwDywQxG8p3dr=g@mail.gmail.com","threadId":"35908","inReplyTo":"CAPig+cQpzYo_VzJfjMCevYm4__bHPSaFLNZSv2MFMA3ondJoSQ@mail.gmail.com","subject":"Re: [PATCH v5 28/28] count-objects: report unused files in $GIT_DIR/repos/...","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-09T11:25:02Z","receivedAt":"2014-03-09T11:25:02Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Sun, Mar 9, 2014 at 3:21 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> On Fri, Mar 7, 2014 at 9:48 PM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>> In linked checkouts, borrowed parts like config is taken from\n>> $GIT_COMMON_DIR. $GIT_DIR/config is never used. Report them as\n>> garbage.\n>>\n>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>> ---\n>> diff --git a/path.c b/path.c\n>> index ddb5962..5a7dc45 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> Do you want to add a comment here explaining what the \"!\" prefix on\n> some entries means, or is it sufficiently self-evident to anyone\n> looking at the consumers of this array?\n\nI was hoping it was clear from the patch how this \"!\" was used (or \"/\"\nin the patch that introduces common_list[]). But if any reader thinks\notherwise, I'm happy to add a comment.\n-- \nDuy\n"},{"id":"236892","messageId":"xmqqvbvceluf.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"1394246900-31535-4-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v5 03/28] Convert git_snpath() to strbuf_git_path()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-03-17T18:11:52Z","receivedAt":"2014-03-17T18:11:52Z","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> In the previous patch, git_snpath() is modified to allocate a new\n> strbuf buffer because vsnpath() needs that. But that makes it awkward\n> because git_snpath() receives a pre-allocated buffer from outside and\n> has to copy data back. Rename it to strbuf_git_path() and make it\n> receive strbuf directly.\n>\n> The conversion from git_snpath() to git_path() in\n> update_refs_for_switch() is safe because that function does not keep\n> any pointer to the round-robin buffer pool allocated by\n> get_pathname().\n\ns/that function does not/that function and all of its callers do not/\n\nis the guarantee we need to say it is safe.  And I think that holds\ntrue.\n\nThanks.\n\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  builtin/checkout.c | 25 +++++++++--------\n>  cache.h            |  4 +--\n>  path.c             | 11 ++------\n>  refs.c             | 78 +++++++++++++++++++++++++++++++++---------------------\n>  refs.h             |  2 +-\n>  5 files changed, 65 insertions(+), 55 deletions(-)\n>\n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> index 64c2aca..efb5e2f 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n> @@ -585,18 +585,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\n> @@ -651,14 +654,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n>  \t\t\t\t\tnew->name);\n>  \t\t\t}\n>  \t\t}\n> -\t\tif (old->path && old->name) {\n> -\t\t\tchar log_file[PATH_MAX], ref_file[PATH_MAX];\n> -\n> -\t\t\tgit_snpath(log_file, sizeof(log_file), \"logs/%s\", old->path);\n> -\t\t\tgit_snpath(ref_file, sizeof(ref_file), \"%s\", old->path);\n> -\t\t\tif (!file_exists(ref_file) && file_exists(log_file))\n> -\t\t\t\tremove_path(log_file);\n> -\t\t}\n> +\t\tif (old->path && old->name &&\n> +\t\t    !file_exists(git_path(\"%s\", old->path)) &&\n> +\t\t     file_exists(git_path(\"logs/%s\", old->path)))\n> +\t\t\tremove_path(git_path(\"logs/%s\", old->path));\n>  \t}\n>  \tremove_branch_state();\n>  \tstrbuf_release(&msg);\n> diff --git a/cache.h b/cache.h\n> index a344a5f..0fae730 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -646,8 +646,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, ...)\n> diff --git a/path.c b/path.c\n> index 36d461e..417df76 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, ...)\n> diff --git a/refs.c b/refs.c\n> index f846f2f..c5613b0 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -1325,10 +1325,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> @@ -1337,15 +1339,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> @@ -1359,10 +1363,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> @@ -1373,7 +1378,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> @@ -1389,7 +1394,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> @@ -1402,12 +1407,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> @@ -1424,9 +1430,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> @@ -1436,10 +1443,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> @@ -2717,41 +2729,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> @@ -2762,20 +2774,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> @@ -2788,9 +2802,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)\n> diff --git a/refs.h b/refs.h\n> index 87a1a79..783033a 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -166,7 +166,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 *ref_name, char *logfile, int bufsize);\n> +int log_ref_setup(const char *ref_name, 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"},{"id":"237444","messageId":"5330469D.9020008@web.de","threadId":"35908","inReplyTo":"1392730814-19656-19-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 18/25] setup.c: support multi-checkout repo setup","fromName":"Torsten Bögershausen","fromEmail":"tboegi@web.de","sentAt":"2014-03-24T14:52:13Z","receivedAt":"2014-03-24T14:52:13Z","isPatch":true,"sender":{"key":"tboegi@web.de","avatar":"https://avatars.githubusercontent.com/u/7138363?v=4"},"body":"On 02/18/2014 02:40 PM, Nguyễn Thái Ngọc Duy wrote:\n> The repo setup procedure is updated to detect $GIT_DIR/commondir and\n> set $GIT_COMMON_DIR properly.\n>\n> The core.worktree is ignored when $GIT_DIR/commondir presents. This is\n> because \"commondir\" repos are intended for separate/linked checkouts\n> and pointing them back to a fixed core.worktree just does not make\n> sense.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  Documentation/config.txt        |  3 +-\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(+), 15 deletions(-)\n>\n> diff --git a/Documentation/config.txt b/Documentation/config.txt\n> index 5f4d793..cbf4d97 100644\n> --- a/Documentation/config.txt\n> +++ b/Documentation/config.txt\n> @@ -381,7 +381,8 @@ false), while all other repositories are assumed to be bare (bare\n>  \n>  core.worktree::\n>  \tSet the path to the root of the working tree.\n> -\tThis can be overridden by the GIT_WORK_TREE environment\n> +\tThis can be overridden by the GIT_WORK_TREE\n> +\tor GIT_COMMON_DIR environment\n>  \tvariable and the '--work-tree' command line option.\n>  \tThe value can be an absolute path or relative to the path to\n>  \tthe .git directory, which is either specified by --git-dir\n> diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt\n> index 33e4e90..8e6ad32 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\".\n> diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\n> index e50bc65..c7057ce 100644\n> --- a/builtin/rev-parse.c\n> +++ b/builtin/rev-parse.c\n> @@ -744,6 +744,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 = resolve_gitdir(argv[i+1]);\n>  \t\t\t\tif (!gitdir)\n> diff --git a/cache.h b/cache.h\n> index 51ade32..98b5dd3 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -407,6 +407,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);\n> diff --git a/environment.c b/environment.c\n> index c998120..0999fc1 100644\n> --- a/environment.c\n> +++ b/environment.c\n> @@ -126,6 +126,7 @@ static char *expand_namespace(const char *raw_namespace)\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> @@ -134,12 +135,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 = getenv(DB_ENVIRONMENT);\n>  \tif (!git_object_dir) {\n>  \t\tgit_object_dir = xmalloc(strlen(git_common_dir) + 9);\n> diff --git a/setup.c b/setup.c\n> index e56ec11..d4ac878 100644\n> --- a/setup.c\n> +++ b/setup.c\n> @@ -170,14 +170,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> @@ -189,10 +190,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> @@ -279,13 +282,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> @@ -298,7 +314,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> @@ -770,11 +786,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;\n> diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh\n> index 8f36aa9..d8bdaf4 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> +\tunset 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\techo \"$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\techo \"$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\techo \"$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\techo \"$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\n> diff --git a/t/t1510-repo-setup.sh b/t/t1510-repo-setup.sh\n> index cf2ee78..c211ef5 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\n> diff --git a/trace.c b/trace.c\n> index 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\nDid I report that t1501  fails when  there is a softlink in $PWD ?\n/home/tb/projects is a softlink to /disc5/projects/\n(And in total 4 TC failed)\n--------------\n\nexpecting success:\n        echo \"gitdir: repo.git/repos/foo\" >gitfile &&\n        echo ../.. >repo.git/repos/foo/commondir &&\n        (\n                cd work &&\n                GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&\n                echo \"$TRASH_DIRECTORY/repo.git\" >expect &&\n                test_cmp expect actual\n        )\n\n--- expect      2014-03-24 07:55:29.895870540 +0000\n+++ actual      2014-03-24 07:55:29.895870540 +0000\n@@ -1 +1 @@\n-/home/tb/projects/git/git.pu/t/trash directory.t1501-worktree/repo.git\n+/disc5/projects/git/git.pu/t/trash directory.t1501-worktree/repo.git\nnot ok 33 - GIT_DIR set (1)\n"},{"id":"237657","messageId":"CACsJy8DQ16xd+LOki0pxDVwPyWJ_hYLqJjW4-4VXa92yo1rD1A@mail.gmail.com","threadId":"35908","inReplyTo":"5330469D.9020008@web.de","subject":"Re: [PATCH v3 18/25] setup.c: support multi-checkout repo setup","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-25T13:52:13Z","receivedAt":"2014-03-25T13:52:13Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Mon, Mar 24, 2014 at 9:52 PM, Torsten Bögershausen <tboegi@web.de> wrote:\n> Did I report that t1501  fails when  there is a softlink in $PWD ?\n> /home/tb/projects is a softlink to /disc5/projects/\n\nYes you did and I forgot. I have fixed it, running test suite and will\nsend the reroll soon.\n-- \nDuy\n"},{"id":"237659","messageId":"20140325140710.GA25789@lanh","threadId":"35908","inReplyTo":"CACsJy8DQ16xd+LOki0pxDVwPyWJ_hYLqJjW4-4VXa92yo1rD1A@mail.gmail.com","subject":"Re: [PATCH v3 18/25] setup.c: support multi-checkout repo setup","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-03-25T14:07:10Z","receivedAt":"2014-03-25T14:07:10Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Tue, Mar 25, 2014 at 08:52:13PM +0700, Duy Nguyen wrote:\n> On Mon, Mar 24, 2014 at 9:52 PM, Torsten Bögershausen <tboegi@web.de> wrote:\n> > Did I report that t1501  fails when  there is a softlink in $PWD ?\n> > /home/tb/projects is a softlink to /disc5/projects/\n> \n> Yes you did and I forgot. I have fixed it, running test suite and will\n> send the reroll soon.\n\nJunio, it seems you have picked up all minor changes after\nv5. Resending the whole series for one fix seems overkill. Could you\njust --autosquash this one in?\n\n-- 8< --\nSubject: [PATCH] fixup! setup.c: support multi-checkout repo setup\n\n---\n t/t1501-worktree.sh | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh\nindex 2ac4424..e6ac7a4 100755\n--- a/t/t1501-worktree.sh\n+++ b/t/t1501-worktree.sh\n@@ -359,7 +359,7 @@ test_expect_success 'GIT_DIR set (1)' '\n \t(\n \t\tcd work &&\n \t\tGIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&\n-\t\techo \"$TRASH_DIRECTORY/repo.git\" >expect &&\n+\t\ttest-path-utils real_path \"$TRASH_DIRECTORY/repo.git\" >expect &&\n \t\ttest_cmp expect actual\n \t)\n '\n@@ -370,7 +370,7 @@ test_expect_success 'GIT_DIR set (2)' '\n \t(\n \t\tcd work &&\n \t\tGIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&\n-\t\techo \"$TRASH_DIRECTORY/repo.git\" >expect &&\n+\t\ttest-path-utils real_path \"$TRASH_DIRECTORY/repo.git\" >expect &&\n \t\ttest_cmp expect actual\n \t)\n '\n@@ -381,7 +381,7 @@ test_expect_success 'Auto discovery' '\n \t(\n \t\tcd work &&\n \t\tgit rev-parse --git-common-dir >actual &&\n-\t\techo \"$TRASH_DIRECTORY/repo.git\" >expect &&\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@@ -399,7 +399,7 @@ test_expect_success '$GIT_DIR/common overrides core.worktree' '\n \t(\n \t\tcd work &&\n \t\tgit rev-parse --git-common-dir >actual &&\n-\t\techo \"$TRASH_DIRECTORY/repo.git\" >expect &&\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-- \n1.9.1.345.ga1a145c\n-- 8< --\n"},{"id":"237848","messageId":"xmqqd2h8ises.fsf@gitster.dls.corp.google.com","threadId":"35908","inReplyTo":"20140325140710.GA25789@lanh","subject":"Re: [PATCH v3 18/25] setup.c: support multi-checkout repo setup","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-03-26T21:03:39Z","receivedAt":"2014-03-26T21:03:39Z","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 Tue, Mar 25, 2014 at 08:52:13PM +0700, Duy Nguyen wrote:\n>> On Mon, Mar 24, 2014 at 9:52 PM, Torsten Bögershausen <tboegi@web.de> wrote:\n>> > Did I report that t1501  fails when  there is a softlink in $PWD ?\n>> > /home/tb/projects is a softlink to /disc5/projects/\n>> \n>> Yes you did and I forgot. I have fixed it, running test suite and will\n>> send the reroll soon.\n>\n> Junio, it seems you have picked up all minor changes after\n> v5. Resending the whole series for one fix seems overkill. Could you\n> just --autosquash this one in?\n\nGladly; thanks for a quick turnaround.\n\n> -- 8< --\n> Subject: [PATCH] fixup! setup.c: support multi-checkout repo setup\n>\n> ---\n>  t/t1501-worktree.sh | 8 ++++----\n>  1 file changed, 4 insertions(+), 4 deletions(-)\n>\n> diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh\n> index 2ac4424..e6ac7a4 100755\n> --- a/t/t1501-worktree.sh\n> +++ b/t/t1501-worktree.sh\n> @@ -359,7 +359,7 @@ test_expect_success 'GIT_DIR set (1)' '\n>  \t(\n>  \t\tcd work &&\n>  \t\tGIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&\n> -\t\techo \"$TRASH_DIRECTORY/repo.git\" >expect &&\n> +\t\ttest-path-utils real_path \"$TRASH_DIRECTORY/repo.git\" >expect &&\n>  \t\ttest_cmp expect actual\n>  \t)\n>  '\n> @@ -370,7 +370,7 @@ test_expect_success 'GIT_DIR set (2)' '\n>  \t(\n>  \t\tcd work &&\n>  \t\tGIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&\n> -\t\techo \"$TRASH_DIRECTORY/repo.git\" >expect &&\n> +\t\ttest-path-utils real_path \"$TRASH_DIRECTORY/repo.git\" >expect &&\n>  \t\ttest_cmp expect actual\n>  \t)\n>  '\n> @@ -381,7 +381,7 @@ test_expect_success 'Auto discovery' '\n>  \t(\n>  \t\tcd work &&\n>  \t\tgit rev-parse --git-common-dir >actual &&\n> -\t\techo \"$TRASH_DIRECTORY/repo.git\" >expect &&\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> @@ -399,7 +399,7 @@ test_expect_success '$GIT_DIR/common overrides core.worktree' '\n>  \t(\n>  \t\tcd work &&\n>  \t\tgit rev-parse --git-common-dir >actual &&\n> -\t\techo \"$TRASH_DIRECTORY/repo.git\" >expect &&\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"},{"id":"245450","messageId":"20140706204651.GB21237@wheezy.local","threadId":"35908","inReplyTo":"1394246900-31535-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v5 00/28] Support multiple checkouts","fromName":"Max Kirillov","fromEmail":"max@max630.net","sentAt":"2014-07-06T20:46:51Z","receivedAt":"2014-07-06T20:46:51Z","isPatch":true,"sender":{"key":"max@max630.net","avatar":"https://avatars.githubusercontent.com/u/381560?v=4"},"body":"Hi.\n\nWhat future does this have? Currently it is marked as\n\"Stalled\", but still mergeable with some trivial conflicts\nand seem to be working (except some bugs in interaction with\nsubmodules, see below). It would be very nice if this\nfeature is officially supported.\n\nI also have a comment about how it interacts with submodules.\nWould it be more appropriate to mark \"modules\" as a\nper-checkout directory? Because each of the working tree's\nsubmodule is obviously a separated directory in filesystem,\nand in most cases (at least in my practice) they are\nchecked-out to different revisions.\n\nSo, currently (before proper linking of submodules checkouts\nimplemented), if make submodules per-checkout (actually it\nappears to somehow work even with current code, maybe\nbecause some submodule code ignores the common_dir), one\ncould run \"git submodule update\" if necessary, and get fully\nseparated clones, which would work normally.\n\nIt still may break if submodules are removed, added or\nrenamed, but this seems to be inevitable until config is\nseparated to per-checkout and common parts, which I suppose\nis a much bigger task.\n\n-- \nMax\n"},{"id":"245470","messageId":"CACsJy8D=9vVkZ2PtZq=b6R6wEmr1pX-yxCCXVBrd-akfeqE1sQ@mail.gmail.com","threadId":"35908","inReplyTo":"20140706204651.GB21237@wheezy.local","subject":"Re: [PATCH v5 00/28] Support multiple checkouts","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-07-07T10:25:16Z","receivedAt":"2014-07-07T10:25:16Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Mon, Jul 7, 2014 at 3:46 AM, Max Kirillov <max@max630.net> wrote:\n> Hi.\n>\n> What future does this have? Currently it is marked as\n> \"Stalled\", but still mergeable with some trivial conflicts\n> and seem to be working (except some bugs in interaction with\n> submodules, see below). It would be very nice if this\n> feature is officially supported.\n\nIt's to be re-rolled soon. I have a patch about sparse-checkout and\nDennis may contribute another one to enable \"checkout --to\" from a\nbare repository. By the way Dennis has been testing this feature and\nhe reported (off-list) it worked fine, which is good news. I have done\nnothing so far because my git time (or energy to be precise) is\nlimited these days, and I wanted to see if Dennis reported any new\nbugs.\n\n> I also have a comment about how it interacts with submodules.\n> Would it be more appropriate to mark \"modules\" as a\n> per-checkout directory? Because each of the working tree's\n> submodule is obviously a separated directory in filesystem,\n> and in most cases (at least in my practice) they are\n> checked-out to different revisions.\n\nSubmodule interaction is something I have avoided so far because I'm\nnot a big user and admittedly does not follow its development closely.\nI think we could get this in first, then let submodule people aware\nabout this feature and let them decide how to use it.\n\n> So, currently (before proper linking of submodules checkouts\n> implemented), if make submodules per-checkout (actually it\n> appears to somehow work even with current code, maybe\n> because some submodule code ignores the common_dir), one\n> could run \"git submodule update\" if necessary, and get fully\n> separated clones, which would work normally.\n>\n> It still may break if submodules are removed, added or\n> renamed, but this seems to be inevitable until config is\n> separated to per-checkout and common parts, which I suppose\n> is a much bigger task.\n-- \nDuy\n"},{"id":"245471","messageId":"1404730141.4779.25.camel@seahawk","threadId":"35908","inReplyTo":"CACsJy8D=9vVkZ2PtZq=b6R6wEmr1pX-yxCCXVBrd-akfeqE1sQ@mail.gmail.com","subject":"Re: [PATCH v5 00/28] Support multiple checkouts","fromName":"Dennis Kaarsemaker","fromEmail":"dennis@kaarsemaker.net","sentAt":"2014-07-07T10:49:01Z","receivedAt":"2014-07-07T10:49:01Z","isPatch":true,"sender":{"key":"dennis@kaarsemaker.net","avatar":"https://avatars.githubusercontent.com/u/200649?v=4"},"body":"On ma, 2014-07-07 at 17:25 +0700, Duy Nguyen wrote:\n\n> > I also have a comment about how it interacts with submodules.\n> > Would it be more appropriate to mark \"modules\" as a\n> > per-checkout directory? Because each of the working tree's\n> > submodule is obviously a separated directory in filesystem,\n> > and in most cases (at least in my practice) they are\n> > checked-out to different revisions.\n> \n> Submodule interaction is something I have avoided so far because I'm\n> not a big user and admittedly does not follow its development closely.\n> I think we could get this in first, then let submodule people aware\n> about this feature and let them decide how to use it.\n\nI do intend to use checkout --to and submodule update on the same\nrepository, but have not yet done so. I will poke at that later this\nmonth. If you can easily reproduce errors, I would appreciate to know\nhow, because my use of submodules is very limited.\n-- \nDennis Kaarsemaker\nhttp://www.kaarsemaker.net\n"},{"id":"245480","messageId":"20140707173245.GA10530@wheezy.local","threadId":"35908","inReplyTo":"1404730141.4779.25.camel@seahawk","subject":"Re: [PATCH v5 00/28] Support multiple checkouts","fromName":"Max Kirillov","fromEmail":"max@max630.net","sentAt":"2014-07-07T17:32:45Z","receivedAt":"2014-07-07T17:32:45Z","isPatch":true,"sender":{"key":"max@max630.net","avatar":"https://avatars.githubusercontent.com/u/381560?v=4"},"body":"On Mon, Jul 07, 2014 at 12:49:01PM +0200, Dennis Kaarsemaker wrote:\n> I do intend to use checkout --to and submodule update on the same\n> repository, but have not yet done so. I will poke at that later this\n> month. If you can easily reproduce errors, I would appreciate to know\n> how, because my use of submodules is very limited.\n\nI have collected all my tests to this script:\nhttps://raw.githubusercontent.com/max630/git/tmp/multiple_work_trees_dev/t/t7410-submodule-checkout-to.sh\n\n-- \nMax\n"}]}