All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 00/25] Support multiple checkouts
@ 2014-02-18 13:39 Nguyễn Thái Ngọc Duy
  2014-02-18 13:39 ` [PATCH v3 01/25] path.c: make get_pathname() return strbuf instead of static buffer Nguyễn Thái Ngọc Duy
                   ` (26 more replies)
  0 siblings, 27 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:39 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

In short you can attach multiple worktrees to the same git repository
with "git checkout --to <somewhere>". This is basically what
git-new-workdir is for. Previous discussion here

http://thread.gmane.org/gmane.comp.version-control.git/239194/focus=239581

Compared to last time:

- .git file format remains unchanged. It was a stupid idea to tie
  $GIT_COMMON_DIR pointer to .git file because you will have to pass
  that info another way if you don't go through .git file. Now it's
  stored in $GIT_DIR/commondir

- Last time, checking out an already checked out branch will detach
  the previous checkout. Junio wanted to error out for less user
  confusion. I go with a (good, imo) compromise in this reroll: the
  new checkout detaches itself in this case, hinting where the branch
  is truly checked out so the user can go there and do things

Nguyễn Thái Ngọc Duy (25):
  path.c: make get_pathname() return strbuf instead of static buffer
  Convert git_snpath() to strbuf_git_path()
  path.c: rename vsnpath() to do_git_path()
  path.c: group git_path(), git_pathdup() and strbuf_git_path() together
  Make git_path() aware of file relocation in $GIT_DIR
  *.sh: respect $GIT_INDEX_FILE
  reflog: avoid constructing .lock path with git_path
  fast-import: use git_path() for accessing .git dir instead of get_git_dir()
  commit: use SEQ_DIR instead of hardcoding "sequencer"
  Add new environment variable $GIT_COMMON_DIR
  git-sh-setup.sh: use rev-parse --git-path to get $GIT_DIR/objects
  *.sh: avoid hardcoding $GIT_DIR/hooks/...
  git-stash: avoid hardcoding $GIT_DIR/logs/....
  setup.c: convert is_git_directory() to use strbuf
  setup.c: detect $GIT_COMMON_DIR in is_git_directory()
  setup.c: convert check_repository_format_gently to use strbuf
  setup.c: detect $GIT_COMMON_DIR check_repository_format_gently()
  setup.c: support multi-checkout repo setup
  wrapper.c: wrapper to open a file, fprintf then close
  use new wrapper write_file() for simple file writing
  checkout: support checking out into a new working directory
  checkout: clean up half-prepared directories in --to mode
  checkout: detach if the branch is already checked out elsewhere
  prune: strategies for linked checkouts
  gc: support prune --repos

 Documentation/config.txt               |   9 +-
 Documentation/git-checkout.txt         |  34 +++++
 Documentation/git-prune.txt            |   3 +
 Documentation/git-rev-parse.txt        |   8 +
 Documentation/git.txt                  |   8 +
 Documentation/gitrepository-layout.txt |  26 ++++
 builtin/branch.c                       |   4 +-
 builtin/checkout.c                     | 272 +++++++++++++++++++++++++++++++--
 builtin/commit.c                       |   2 +-
 builtin/gc.c                           |  17 +++
 builtin/init-db.c                      |   7 +-
 builtin/prune.c                        |  75 +++++++++
 builtin/reflog.c                       |   2 +-
 builtin/rev-parse.c                    |  11 ++
 cache.h                                |  10 +-
 daemon.c                               |  11 +-
 environment.c                          |  24 ++-
 fast-import.c                          |   5 +-
 git-am.sh                              |  22 +--
 git-pull.sh                            |   2 +-
 git-rebase--interactive.sh             |   6 +-
 git-rebase--merge.sh                   |   6 +-
 git-rebase.sh                          |   4 +-
 git-sh-setup.sh                        |   2 +-
 git-stash.sh                           |   6 +-
 path.c                                 | 201 +++++++++++++++---------
 refs.c                                 |  66 +++++---
 refs.h                                 |   2 +-
 setup.c                                | 117 ++++++++++----
 strbuf.c                               |   8 +
 strbuf.h                               |   5 +
 submodule.c                            |   9 +-
 t/t0060-path-utils.sh                  |  34 +++++
 t/t1501-worktree.sh                    |  76 +++++++++
 t/t1510-repo-setup.sh                  |   1 +
 t/t2025-checkout-to.sh (new +x)        |  48 ++++++
 templates/hooks--applypatch-msg.sample |   4 +-
 templates/hooks--pre-applypatch.sample |   4 +-
 trace.c                                |   1 +
 transport.c                            |   8 +-
 wrapper.c                              |  31 ++++
 41 files changed, 976 insertions(+), 215 deletions(-)
 create mode 100755 t/t2025-checkout-to.sh

-- 
1.8.5.2.240.g8478abd

^ permalink raw reply	[flat|nested] 169+ messages in thread

* [PATCH v3 01/25] path.c: make get_pathname() return strbuf instead of static buffer
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:39 ` Nguyễn Thái Ngọc Duy
  2014-02-19 20:28   ` Junio C Hamano
  2014-02-19 23:26   ` Junio C Hamano
  2014-02-18 13:39 ` [PATCH v3 02/25] Convert git_snpath() to strbuf_git_path() Nguyễn Thái Ngọc Duy
                   ` (25 subsequent siblings)
  26 siblings, 2 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:39 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

We've been avoiding PATH_MAX whenever possible. This patch makes
get_pathname() return a strbuf and updates the callers to take
advantage of this. The code is simplified as we no longer need to
worry about buffer overflow.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 path.c | 119 +++++++++++++++++++++++++++--------------------------------------
 1 file changed, 50 insertions(+), 69 deletions(-)

diff --git a/path.c b/path.c
index 24594c4..1a1b784 100644
--- a/path.c
+++ b/path.c
@@ -16,11 +16,15 @@ static int get_st_mode_bits(const char *path, int *mode)
 
 static char bad_path[] = "/bad-path/";
 
-static char *get_pathname(void)
+static struct strbuf *get_pathname()
 {
-	static char pathname_array[4][PATH_MAX];
+	static struct strbuf pathname_array[4] = {
+		STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
+	};
 	static int index;
-	return pathname_array[3 & ++index];
+	struct strbuf *sb = &pathname_array[3 & ++index];
+	strbuf_reset(sb);
+	return sb;
 }
 
 static char *cleanup_path(char *path)
@@ -34,6 +38,13 @@ static char *cleanup_path(char *path)
 	return path;
 }
 
+static void strbuf_cleanup_path(struct strbuf *sb)
+{
+	char *path = cleanup_path(sb->buf);
+	if (path > sb->buf)
+		strbuf_remove(sb, 0, path - sb->buf);
+}
+
 char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 {
 	va_list args;
@@ -49,85 +60,69 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 	return cleanup_path(buf);
 }
 
-static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)
+static void vsnpath(struct strbuf *buf, const char *fmt, va_list args)
 {
 	const char *git_dir = get_git_dir();
-	size_t len;
-
-	len = strlen(git_dir);
-	if (n < len + 1)
-		goto bad;
-	memcpy(buf, git_dir, len);
-	if (len && !is_dir_sep(git_dir[len-1]))
-		buf[len++] = '/';
-	len += vsnprintf(buf + len, n - len, fmt, args);
-	if (len >= n)
-		goto bad;
-	return cleanup_path(buf);
-bad:
-	strlcpy(buf, bad_path, n);
-	return buf;
+	strbuf_addstr(buf, git_dir);
+	if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
+		strbuf_addch(buf, '/');
+	strbuf_vaddf(buf, fmt, args);
+	strbuf_cleanup_path(buf);
 }
 
 char *git_snpath(char *buf, size_t n, const char *fmt, ...)
 {
-	char *ret;
+	struct strbuf *sb = get_pathname();
 	va_list args;
 	va_start(args, fmt);
-	ret = vsnpath(buf, n, fmt, args);
+	vsnpath(sb, fmt, args);
 	va_end(args);
-	return ret;
+	if (sb->len >= n)
+		strlcpy(buf, bad_path, n);
+	else
+		memcpy(buf, sb->buf, sb->len + 1);
+	return buf;
 }
 
 char *git_pathdup(const char *fmt, ...)
 {
-	char path[PATH_MAX], *ret;
+	struct strbuf *path = get_pathname();
 	va_list args;
 	va_start(args, fmt);
-	ret = vsnpath(path, sizeof(path), fmt, args);
+	vsnpath(path, fmt, args);
 	va_end(args);
-	return xstrdup(ret);
+	return strbuf_detach(path, NULL);
 }
 
 char *mkpathdup(const char *fmt, ...)
 {
-	char *path;
 	struct strbuf sb = STRBUF_INIT;
 	va_list args;
-
 	va_start(args, fmt);
 	strbuf_vaddf(&sb, fmt, args);
 	va_end(args);
-	path = xstrdup(cleanup_path(sb.buf));
-
-	strbuf_release(&sb);
-	return path;
+	strbuf_cleanup_path(&sb);
+	return strbuf_detach(&sb, NULL);
 }
 
 char *mkpath(const char *fmt, ...)
 {
 	va_list args;
-	unsigned len;
-	char *pathname = get_pathname();
-
+	struct strbuf *pathname = get_pathname();
 	va_start(args, fmt);
-	len = vsnprintf(pathname, PATH_MAX, fmt, args);
+	strbuf_vaddf(pathname, fmt, args);
 	va_end(args);
-	if (len >= PATH_MAX)
-		return bad_path;
-	return cleanup_path(pathname);
+	return cleanup_path(pathname->buf);
 }
 
 char *git_path(const char *fmt, ...)
 {
-	char *pathname = get_pathname();
+	struct strbuf *pathname = get_pathname();
 	va_list args;
-	char *ret;
-
 	va_start(args, fmt);
-	ret = vsnpath(pathname, PATH_MAX, fmt, args);
+	vsnpath(pathname, fmt, args);
 	va_end(args);
-	return ret;
+	return pathname->buf;
 }
 
 void home_config_paths(char **global, char **xdg, char *file)
@@ -158,41 +153,27 @@ void home_config_paths(char **global, char **xdg, char *file)
 
 char *git_path_submodule(const char *path, const char *fmt, ...)
 {
-	char *pathname = get_pathname();
-	struct strbuf buf = STRBUF_INIT;
+	struct strbuf *buf = get_pathname();
 	const char *git_dir;
 	va_list args;
-	unsigned len;
-
-	len = strlen(path);
-	if (len > PATH_MAX-100)
-		return bad_path;
 
-	strbuf_addstr(&buf, path);
-	if (len && path[len-1] != '/')
-		strbuf_addch(&buf, '/');
-	strbuf_addstr(&buf, ".git");
+	strbuf_addstr(buf, path);
+	if (buf->len && buf->buf[buf->len - 1] != '/')
+		strbuf_addch(buf, '/');
+	strbuf_addstr(buf, ".git");
 
-	git_dir = read_gitfile(buf.buf);
+	git_dir = read_gitfile(buf->buf);
 	if (git_dir) {
-		strbuf_reset(&buf);
-		strbuf_addstr(&buf, git_dir);
+		strbuf_reset(buf);
+		strbuf_addstr(buf, git_dir);
 	}
-	strbuf_addch(&buf, '/');
-
-	if (buf.len >= PATH_MAX)
-		return bad_path;
-	memcpy(pathname, buf.buf, buf.len + 1);
-
-	strbuf_release(&buf);
-	len = strlen(pathname);
+	strbuf_addch(buf, '/');
 
 	va_start(args, fmt);
-	len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
+	strbuf_vaddf(buf, fmt, args);
 	va_end(args);
-	if (len >= PATH_MAX)
-		return bad_path;
-	return cleanup_path(pathname);
+	strbuf_cleanup_path(buf);
+	return buf->buf;
 }
 
 int validate_headref(const char *path)
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 02/25] Convert git_snpath() to strbuf_git_path()
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
  2014-02-18 13:39 ` [PATCH v3 01/25] path.c: make get_pathname() return strbuf instead of static buffer Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:39 ` Nguyễn Thái Ngọc Duy
  2014-02-19 23:48   ` Junio C Hamano
  2014-02-20 19:44   ` Junio C Hamano
  2014-02-18 13:39 ` [PATCH v3 03/25] path.c: rename vsnpath() to do_git_path() Nguyễn Thái Ngọc Duy
                   ` (24 subsequent siblings)
  26 siblings, 2 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:39 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

In the previous patch, git_snpath() is modified to take a strbuf
buffer from get_pathname() because vsnpath() needs that. But that
makes it awkward because git_snpath() receives a pre-allocated buffer
from outside and has to copy data back.

Rename it to strbuf_git_path() and make it receive strbuf directly.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 22 +++++++++---------
 cache.h            |  4 ++--
 path.c             |  8 +------
 refs.c             | 66 +++++++++++++++++++++++++++++++++++-------------------
 refs.h             |  2 +-
 5 files changed, 57 insertions(+), 45 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 5df3837..0570e41 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -585,18 +585,20 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 		if (opts->new_orphan_branch) {
 			if (opts->new_branch_log && !log_all_ref_updates) {
 				int temp;
-				char log_file[PATH_MAX];
+				struct strbuf log_file = STRBUF_INIT;
 				char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
+				int ret;
 
 				temp = log_all_ref_updates;
 				log_all_ref_updates = 1;
-				if (log_ref_setup(ref_name, log_file, sizeof(log_file))) {
+				ret = log_ref_setup(ref_name, &log_file);
+				log_all_ref_updates = temp;
+				strbuf_release(&log_file);
+				if (ret) {
 					fprintf(stderr, _("Can not do reflog for '%s'\n"),
 					    opts->new_orphan_branch);
-					log_all_ref_updates = temp;
 					return;
 				}
-				log_all_ref_updates = temp;
 			}
 		}
 		else
@@ -651,14 +653,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 					new->name);
 			}
 		}
-		if (old->path && old->name) {
-			char log_file[PATH_MAX], ref_file[PATH_MAX];
-
-			git_snpath(log_file, sizeof(log_file), "logs/%s", old->path);
-			git_snpath(ref_file, sizeof(ref_file), "%s", old->path);
-			if (!file_exists(ref_file) && file_exists(log_file))
-				remove_path(log_file);
-		}
+		if (old->path && old->name &&
+		    !file_exists(git_path("%s", old->path)) &&
+		     file_exists(git_path("logs/%s", old->path)))
+			remove_path(git_path("logs/%s", old->path));
 	}
 	remove_branch_state();
 	strbuf_release(&msg);
diff --git a/cache.h b/cache.h
index dc040fb..8d3697e 100644
--- a/cache.h
+++ b/cache.h
@@ -646,8 +646,8 @@ extern int check_repository_format(void);
 
 extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 	__attribute__((format (printf, 3, 4)));
-extern char *git_snpath(char *buf, size_t n, const char *fmt, ...)
-	__attribute__((format (printf, 3, 4)));
+extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
+	__attribute__((format (printf, 2, 3)));
 extern char *git_pathdup(const char *fmt, ...)
 	__attribute__((format (printf, 1, 2)));
 extern char *mkpathdup(const char *fmt, ...)
diff --git a/path.c b/path.c
index 1a1b784..fe3f844 100644
--- a/path.c
+++ b/path.c
@@ -70,18 +70,12 @@ static void vsnpath(struct strbuf *buf, const char *fmt, va_list args)
 	strbuf_cleanup_path(buf);
 }
 
-char *git_snpath(char *buf, size_t n, const char *fmt, ...)
+void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 {
-	struct strbuf *sb = get_pathname();
 	va_list args;
 	va_start(args, fmt);
 	vsnpath(sb, fmt, args);
 	va_end(args);
-	if (sb->len >= n)
-		strlcpy(buf, bad_path, n);
-	else
-		memcpy(buf, sb->buf, sb->len + 1);
-	return buf;
 }
 
 char *git_pathdup(const char *fmt, ...)
diff --git a/refs.c b/refs.c
index 89228e2..434bd5e 100644
--- a/refs.c
+++ b/refs.c
@@ -1325,10 +1325,12 @@ static const char *handle_missing_loose_ref(const char *refname,
 
 const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)
 {
+	struct strbuf sb_path = STRBUF_INIT;
 	int depth = MAXDEPTH;
 	ssize_t len;
 	char buffer[256];
 	static char refname_buffer[256];
+	const char *ret;
 
 	if (flag)
 		*flag = 0;
@@ -1337,15 +1339,17 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 		return NULL;
 
 	for (;;) {
-		char path[PATH_MAX];
+		const char *path;
 		struct stat st;
 		char *buf;
 		int fd;
 
 		if (--depth < 0)
-			return NULL;
+			goto fail;
 
-		git_snpath(path, sizeof(path), "%s", refname);
+		strbuf_reset(&sb_path);
+		strbuf_git_path(&sb_path, "%s", refname);
+		path = sb_path.buf;
 
 		/*
 		 * We might have to loop back here to avoid a race
@@ -1359,10 +1363,11 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 	stat_ref:
 		if (lstat(path, &st) < 0) {
 			if (errno == ENOENT)
-				return handle_missing_loose_ref(refname, sha1,
-								reading, flag);
+				ret = handle_missing_loose_ref(refname, sha1,
+							       reading, flag);
 			else
-				return NULL;
+				ret = NULL;
+			goto done;
 		}
 
 		/* Follow "normalized" - ie "refs/.." symlinks by hand */
@@ -1373,7 +1378,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 					/* inconsistent with lstat; retry */
 					goto stat_ref;
 				else
-					return NULL;
+					goto fail;
 			}
 			buffer[len] = 0;
 			if (starts_with(buffer, "refs/") &&
@@ -1389,7 +1394,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 		/* Is it a directory? */
 		if (S_ISDIR(st.st_mode)) {
 			errno = EISDIR;
-			return NULL;
+			goto fail;
 		}
 
 		/*
@@ -1402,12 +1407,13 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 				/* inconsistent with lstat; retry */
 				goto stat_ref;
 			else
-				return NULL;
+				goto fail;
 		}
+
 		len = read_in_full(fd, buffer, sizeof(buffer)-1);
 		close(fd);
 		if (len < 0)
-			return NULL;
+			goto fail;
 		while (len && isspace(buffer[len-1]))
 			len--;
 		buffer[len] = '\0';
@@ -1424,9 +1430,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 			    (buffer[40] != '\0' && !isspace(buffer[40]))) {
 				if (flag)
 					*flag |= REF_ISBROKEN;
-				return NULL;
+				goto fail;
 			}
-			return refname;
+			ret = refname;
+			goto done;
 		}
 		if (flag)
 			*flag |= REF_ISSYMREF;
@@ -1436,10 +1443,15 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 		if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
 			if (flag)
 				*flag |= REF_ISBROKEN;
-			return NULL;
+			goto fail;
 		}
 		refname = strcpy(refname_buffer, buf);
 	}
+fail:
+	ret = NULL;
+done:
+	strbuf_release(&sb_path);
+	return ret;
 }
 
 char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag)
@@ -2717,17 +2729,19 @@ static int copy_msg(char *buf, const char *msg)
 	return cp - buf;
 }
 
-int log_ref_setup(const char *refname, char *logfile, int bufsize)
+int log_ref_setup(const char *refname, struct strbuf *sb_logfile)
 {
 	int logfd, oflags = O_APPEND | O_WRONLY;
+	const char *logfile;
 
-	git_snpath(logfile, bufsize, "logs/%s", refname);
+	strbuf_git_path(sb_logfile, "logs/%s", refname);
+	logfile = sb_logfile->buf;
 	if (log_all_ref_updates &&
 	    (starts_with(refname, "refs/heads/") ||
 	     starts_with(refname, "refs/remotes/") ||
 	     starts_with(refname, "refs/notes/") ||
 	     !strcmp(refname, "HEAD"))) {
-		if (safe_create_leading_directories(logfile) < 0)
+		if (safe_create_leading_directories(sb_logfile->buf) < 0)
 			return error("unable to create directory for %s",
 				     logfile);
 		oflags |= O_CREAT;
@@ -2762,20 +2776,22 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,
 	int logfd, result, written, oflags = O_APPEND | O_WRONLY;
 	unsigned maxlen, len;
 	int msglen;
-	char log_file[PATH_MAX];
+	struct strbuf sb_log_file = STRBUF_INIT;
+	const char *log_file;
 	char *logrec;
 	const char *committer;
 
 	if (log_all_ref_updates < 0)
 		log_all_ref_updates = !is_bare_repository();
 
-	result = log_ref_setup(refname, log_file, sizeof(log_file));
+	result = log_ref_setup(refname, &sb_log_file);
 	if (result)
-		return result;
+		goto done;
+	log_file = sb_log_file.buf;
 
 	logfd = open(log_file, oflags);
 	if (logfd < 0)
-		return 0;
+		goto done;
 	msglen = msg ? strlen(msg) : 0;
 	committer = git_committer_info(0);
 	maxlen = strlen(committer) + msglen + 100;
@@ -2788,9 +2804,13 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,
 		len += copy_msg(logrec + len - 1, msg) - 1;
 	written = len <= maxlen ? write_in_full(logfd, logrec, len) : -1;
 	free(logrec);
-	if (close(logfd) != 0 || written != len)
-		return error("Unable to append to %s", log_file);
-	return 0;
+	if (close(logfd) != 0 || written != len) {
+		error("Unable to append to %s", log_file);
+		result = -1;
+	}
+done:
+	strbuf_release(&sb_log_file);
+	return result;
 }
 
 static int is_branch(const char *refname)
diff --git a/refs.h b/refs.h
index 87a1a79..783033a 100644
--- a/refs.h
+++ b/refs.h
@@ -166,7 +166,7 @@ extern void unlock_ref(struct ref_lock *lock);
 extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
 
 /** Setup reflog before using. **/
-int log_ref_setup(const char *ref_name, char *logfile, int bufsize);
+int log_ref_setup(const char *ref_name, struct strbuf *logfile);
 
 /** Reads log for the value of ref during at_time. **/
 extern int read_ref_at(const char *refname, unsigned long at_time, int cnt,
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 03/25] path.c: rename vsnpath() to do_git_path()
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
  2014-02-18 13:39 ` [PATCH v3 01/25] path.c: make get_pathname() return strbuf instead of static buffer Nguyễn Thái Ngọc Duy
  2014-02-18 13:39 ` [PATCH v3 02/25] Convert git_snpath() to strbuf_git_path() Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:39 ` Nguyễn Thái Ngọc Duy
  2014-02-18 13:39 ` [PATCH v3 04/25] path.c: group git_path(), git_pathdup() and strbuf_git_path() together Nguyễn Thái Ngọc Duy
                   ` (23 subsequent siblings)
  26 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:39 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

The name vsnpath() gives an impression that this is general path
handling function. It's not. This is the underlying implementation of
git_path(), git_pathdup() and strbuf_git_path() which will prefix
$GIT_DIR in the result string.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 path.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/path.c b/path.c
index fe3f844..635ec41 100644
--- a/path.c
+++ b/path.c
@@ -60,7 +60,7 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 	return cleanup_path(buf);
 }
 
-static void vsnpath(struct strbuf *buf, const char *fmt, va_list args)
+static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
 {
 	const char *git_dir = get_git_dir();
 	strbuf_addstr(buf, git_dir);
@@ -74,7 +74,7 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 {
 	va_list args;
 	va_start(args, fmt);
-	vsnpath(sb, fmt, args);
+	do_git_path(sb, fmt, args);
 	va_end(args);
 }
 
@@ -83,7 +83,7 @@ char *git_pathdup(const char *fmt, ...)
 	struct strbuf *path = get_pathname();
 	va_list args;
 	va_start(args, fmt);
-	vsnpath(path, fmt, args);
+	do_git_path(path, fmt, args);
 	va_end(args);
 	return strbuf_detach(path, NULL);
 }
@@ -114,7 +114,7 @@ char *git_path(const char *fmt, ...)
 	struct strbuf *pathname = get_pathname();
 	va_list args;
 	va_start(args, fmt);
-	vsnpath(pathname, fmt, args);
+	do_git_path(pathname, fmt, args);
 	va_end(args);
 	return pathname->buf;
 }
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 04/25] path.c: group git_path(), git_pathdup() and strbuf_git_path() together
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (2 preceding siblings ...)
  2014-02-18 13:39 ` [PATCH v3 03/25] path.c: rename vsnpath() to do_git_path() Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:39 ` Nguyễn Thái Ngọc Duy
  2014-02-18 13:39 ` [PATCH v3 05/25] Make git_path() aware of file relocation in $GIT_DIR Nguyễn Thái Ngọc Duy
                   ` (22 subsequent siblings)
  26 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:39 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 path.c | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/path.c b/path.c
index 635ec41..e088c40 100644
--- a/path.c
+++ b/path.c
@@ -78,6 +78,16 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 	va_end(args);
 }
 
+char *git_path(const char *fmt, ...)
+{
+	struct strbuf *pathname = get_pathname();
+	va_list args;
+	va_start(args, fmt);
+	do_git_path(pathname, fmt, args);
+	va_end(args);
+	return pathname->buf;
+}
+
 char *git_pathdup(const char *fmt, ...)
 {
 	struct strbuf *path = get_pathname();
@@ -109,16 +119,6 @@ char *mkpath(const char *fmt, ...)
 	return cleanup_path(pathname->buf);
 }
 
-char *git_path(const char *fmt, ...)
-{
-	struct strbuf *pathname = get_pathname();
-	va_list args;
-	va_start(args, fmt);
-	do_git_path(pathname, fmt, args);
-	va_end(args);
-	return pathname->buf;
-}
-
 void home_config_paths(char **global, char **xdg, char *file)
 {
 	char *xdg_home = getenv("XDG_CONFIG_HOME");
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 05/25] Make git_path() aware of file relocation in $GIT_DIR
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (3 preceding siblings ...)
  2014-02-18 13:39 ` [PATCH v3 04/25] path.c: group git_path(), git_pathdup() and strbuf_git_path() together Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:39 ` Nguyễn Thái Ngọc Duy
  2014-02-18 13:39 ` [PATCH v3 06/25] *.sh: respect $GIT_INDEX_FILE Nguyễn Thái Ngọc Duy
                   ` (21 subsequent siblings)
  26 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:39 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

We allow the user to relocate certain paths out of $GIT_DIR via
environment variables, e.g. GIT_OBJECT_DIRECTORY, GIT_INDEX_FILE and
GIT_GRAFT_FILE. All callers are not supposed to use git_path() or
git_pathdup() to get those paths. Instead they must use
get_object_directory(), get_index_file() and get_graft_file()
respectively. This is inconvenient and could be missed in review
(there's git_path("objects/info/alternates") somewhere in
sha1_file.c).

This patch makes git_path() and git_pathdup() understand those
environment variables. So if you set GIT_OBJECT_DIRECTORY to /foo/bar,
git_path("objects/abc") should return /tmp/bar/abc. The same is done
for the two remaining env variables.

"git rev-parse --git-path" is the wrapper for script use.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-rev-parse.txt |  5 +++++
 builtin/rev-parse.c             |  7 +++++++
 cache.h                         |  1 +
 environment.c                   |  9 ++++++--
 path.c                          | 46 +++++++++++++++++++++++++++++++++++++++++
 t/t0060-path-utils.sh           | 19 +++++++++++++++++
 6 files changed, 85 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 0d2cdcd..33e4e90 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -232,6 +232,11 @@ print a message to stderr and exit with nonzero status.
 	repository.  If <path> is a gitfile then the resolved path
 	to the real repository is printed.
 
+--git-path <path>::
+	Resolve "$GIT_DIR/<path>" and takes other path relocation
+	variables such as $GIT_OBJECT_DIRECTORY,
+	$GIT_INDEX_FILE... into account.
+
 --show-cdup::
 	When the command is invoked from a subdirectory, show the
 	path of the top-level directory relative to the current
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index aaeb611..e50bc65 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -518,6 +518,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
 	for (i = 1; i < argc; i++) {
 		const char *arg = argv[i];
 
+		if (!strcmp(arg, "--git-path")) {
+			if (!argv[i + 1])
+				die("--git-path requires an argument");
+			puts(git_path("%s", argv[i + 1]));
+			i++;
+			continue;
+		}
 		if (as_is) {
 			if (show_file(arg, output_prefix) && as_is < 2)
 				verify_filename(prefix, arg, 0);
diff --git a/cache.h b/cache.h
index 8d3697e..6c08e4a 100644
--- a/cache.h
+++ b/cache.h
@@ -585,6 +585,7 @@ extern int fsync_object_files;
 extern int core_preload_index;
 extern int core_apply_sparse_checkout;
 extern int precomposed_unicode;
+extern int git_db_env, git_index_env, git_graft_env;
 
 /*
  * The character that begins a commented line in user-editable file
diff --git a/environment.c b/environment.c
index 4a3437d..f513479 100644
--- a/environment.c
+++ b/environment.c
@@ -82,6 +82,7 @@ static size_t namespace_len;
 
 static const char *git_dir;
 static char *git_object_dir, *git_index_file, *git_graft_file;
+int git_db_env, git_index_env, git_graft_env;
 
 /*
  * Repository-local GIT_* environment variables; see cache.h for details.
@@ -137,15 +138,19 @@ static void setup_git_env(void)
 	if (!git_object_dir) {
 		git_object_dir = xmalloc(strlen(git_dir) + 9);
 		sprintf(git_object_dir, "%s/objects", git_dir);
-	}
+	} else
+		git_db_env = 1;
 	git_index_file = getenv(INDEX_ENVIRONMENT);
 	if (!git_index_file) {
 		git_index_file = xmalloc(strlen(git_dir) + 7);
 		sprintf(git_index_file, "%s/index", git_dir);
-	}
+	} else
+		git_index_env = 1;
 	git_graft_file = getenv(GRAFT_ENVIRONMENT);
 	if (!git_graft_file)
 		git_graft_file = git_pathdup("info/grafts");
+	else
+		git_graft_env = 1;
 	if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
 		read_replace_refs = 0;
 	namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));
diff --git a/path.c b/path.c
index e088c40..0f8c3dc 100644
--- a/path.c
+++ b/path.c
@@ -60,13 +60,59 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 	return cleanup_path(buf);
 }
 
+static int dir_prefix(const char *buf, const char *dir)
+{
+	int len = strlen(dir);
+	return !strncmp(buf, dir, len) &&
+		(is_dir_sep(buf[len]) || buf[len] == '\0');
+}
+
+/* $buf =~ m|$dir/+$file| but without regex */
+static int is_dir_file(const char *buf, const char *dir, const char *file)
+{
+	int len = strlen(dir);
+	if (strncmp(buf, dir, len) || !is_dir_sep(buf[len]))
+		return 0;
+	while (is_dir_sep(buf[len]))
+		len++;
+	return !strcmp(buf + len, file);
+}
+
+static void replace_dir(struct strbuf *buf, int len, const char *newdir)
+{
+	int newlen = strlen(newdir);
+	int need_sep = (buf->buf[len] && !is_dir_sep(buf->buf[len])) &&
+		!is_dir_sep(newdir[newlen - 1]);
+	if (need_sep)
+		len--;	 /* keep one char, to be replaced with '/'  */
+	strbuf_splice(buf, 0, len, newdir, newlen);
+	if (need_sep)
+		buf->buf[newlen] = '/';
+}
+
+static void adjust_git_path(struct strbuf *buf, int git_dir_len)
+{
+	const char *base = buf->buf + git_dir_len;
+	if (git_graft_env && is_dir_file(base, "info", "grafts"))
+		strbuf_splice(buf, 0, buf->len,
+			      get_graft_file(), strlen(get_graft_file()));
+	else if (git_index_env && !strcmp(base, "index"))
+		strbuf_splice(buf, 0, buf->len,
+			      get_index_file(), strlen(get_index_file()));
+	else if (git_db_env && dir_prefix(base, "objects"))
+		replace_dir(buf, git_dir_len + 7, get_object_directory());
+}
+
 static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
 {
 	const char *git_dir = get_git_dir();
+	int gitdir_len;
 	strbuf_addstr(buf, git_dir);
 	if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
 		strbuf_addch(buf, '/');
+	gitdir_len = buf->len;
 	strbuf_vaddf(buf, fmt, args);
+	adjust_git_path(buf, gitdir_len);
 	strbuf_cleanup_path(buf);
 }
 
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index 07c10c8..1d29901 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -19,6 +19,14 @@ relative_path() {
 	"test \"\$(test-path-utils relative_path '$1' '$2')\" = '$expected'"
 }
 
+test_git_path() {
+	test_expect_success "git-path $1 $2 => $3" "
+		$1 git rev-parse --git-path $2 >actual &&
+		echo $3 >expect &&
+		test_cmp expect actual
+	"
+}
+
 # On Windows, we are using MSYS's bash, which mangles the paths.
 # Absolute paths are anchored at the MSYS installation directory,
 # which means that the path / accounts for this many characters:
@@ -223,4 +231,15 @@ relative_path "<null>"		"<empty>"	./
 relative_path "<null>"		"<null>"	./
 relative_path "<null>"		/foo/a/b	./
 
+test_git_path A=B                info/grafts .git/info/grafts
+test_git_path GIT_GRAFT_FILE=foo info/grafts foo
+test_git_path GIT_GRAFT_FILE=foo info/////grafts foo
+test_git_path GIT_INDEX_FILE=foo index foo
+test_git_path GIT_INDEX_FILE=foo index/foo .git/index/foo
+test_git_path GIT_INDEX_FILE=foo index2 .git/index2
+test_expect_success 'setup fake objects directory foo' 'mkdir foo'
+test_git_path GIT_OBJECT_DIRECTORY=foo objects foo
+test_git_path GIT_OBJECT_DIRECTORY=foo objects/foo foo/foo
+test_git_path GIT_OBJECT_DIRECTORY=foo objects2 .git/objects2
+
 test_done
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 06/25] *.sh: respect $GIT_INDEX_FILE
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (4 preceding siblings ...)
  2014-02-18 13:39 ` [PATCH v3 05/25] Make git_path() aware of file relocation in $GIT_DIR Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:39 ` Nguyễn Thái Ngọc Duy
  2014-02-18 13:39 ` [PATCH v3 07/25] reflog: avoid constructing .lock path with git_path Nguyễn Thái Ngọc Duy
                   ` (20 subsequent siblings)
  26 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:39 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 git-pull.sh  | 2 +-
 git-stash.sh | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/git-pull.sh b/git-pull.sh
index 0a5aa2c..c9dc9ba 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -218,7 +218,7 @@ test true = "$rebase" && {
 	if ! git rev-parse -q --verify HEAD >/dev/null
 	then
 		# On an unborn branch
-		if test -f "$GIT_DIR/index"
+		if test -f "`git rev-parse --git-path index`"
 		then
 			die "$(gettext "updating an unborn branch with changes added to the index")"
 		fi
diff --git a/git-stash.sh b/git-stash.sh
index f0a94ab..ae7d16e 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -20,7 +20,7 @@ require_work_tree
 cd_to_toplevel
 
 TMP="$GIT_DIR/.git-stash.$$"
-TMPindex=${GIT_INDEX_FILE-"$GIT_DIR/index"}.stash.$$
+TMPindex=${GIT_INDEX_FILE-"`git rev-parse --git-path index`"}.stash.$$
 trap 'rm -f "$TMP-"* "$TMPindex"' 0
 
 ref_stash=refs/stash
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 07/25] reflog: avoid constructing .lock path with git_path
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (5 preceding siblings ...)
  2014-02-18 13:39 ` [PATCH v3 06/25] *.sh: respect $GIT_INDEX_FILE Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:39 ` Nguyễn Thái Ngọc Duy
  2014-02-25 22:44   ` Junio C Hamano
  2014-02-18 13:39 ` [PATCH v3 08/25] fast-import: use git_path() for accessing .git dir instead of get_git_dir() Nguyễn Thái Ngọc Duy
                   ` (19 subsequent siblings)
  26 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:39 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

git_path() soon understands the path given to it. Some paths "abc" may
become "def" while other "ghi" may become "ijk". We don't want
git_path() to interfere with .lock path construction. Concatenate
".lock" after the path has been resolved by git_path() so if "abc"
becomes "def", we'll have "def.lock", not "ijk".

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/reflog.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/reflog.c b/builtin/reflog.c
index 852cff6..ccf2cf6 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -372,7 +372,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
 	if (!file_exists(log_file))
 		goto finish;
 	if (!cmd->dry_run) {
-		newlog_path = git_pathdup("logs/%s.lock", ref);
+		newlog_path = mkpathdup("%s.lock", log_file);
 		cb.newlog = fopen(newlog_path, "w");
 	}
 
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 08/25] fast-import: use git_path() for accessing .git dir instead of get_git_dir()
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (6 preceding siblings ...)
  2014-02-18 13:39 ` [PATCH v3 07/25] reflog: avoid constructing .lock path with git_path Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:39 ` Nguyễn Thái Ngọc Duy
  2014-02-18 13:39 ` [PATCH v3 09/25] commit: use SEQ_DIR instead of hardcoding "sequencer" Nguyễn Thái Ngọc Duy
                   ` (18 subsequent siblings)
  26 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:39 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

This allows git_path() to redirect info/fast-import to another place
if needed

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 fast-import.c | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/fast-import.c b/fast-import.c
index 4fd18a3..08a1e78 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -3125,12 +3125,9 @@ static void parse_progress(void)
 
 static char* make_fast_import_path(const char *path)
 {
-	struct strbuf abs_path = STRBUF_INIT;
-
 	if (!relative_marks_paths || is_absolute_path(path))
 		return xstrdup(path);
-	strbuf_addf(&abs_path, "%s/info/fast-import/%s", get_git_dir(), path);
-	return strbuf_detach(&abs_path, NULL);
+	return xstrdup(git_path("info/fast-import/%s", path));
 }
 
 static void option_import_marks(const char *marks,
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 09/25] commit: use SEQ_DIR instead of hardcoding "sequencer"
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (7 preceding siblings ...)
  2014-02-18 13:39 ` [PATCH v3 08/25] fast-import: use git_path() for accessing .git dir instead of get_git_dir() Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:39 ` Nguyễn Thái Ngọc Duy
  2014-02-18 13:39 ` [PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR Nguyễn Thái Ngọc Duy
                   ` (17 subsequent siblings)
  26 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:39 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/commit.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/commit.c b/builtin/commit.c
index 3767478..ee3ac10 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -155,7 +155,7 @@ static void determine_whence(struct wt_status *s)
 		whence = FROM_MERGE;
 	else if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
 		whence = FROM_CHERRY_PICK;
-		if (file_exists(git_path("sequencer")))
+		if (file_exists(git_path(SEQ_DIR)))
 			sequencer_in_use = 1;
 	}
 	else
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (8 preceding siblings ...)
  2014-02-18 13:39 ` [PATCH v3 09/25] commit: use SEQ_DIR instead of hardcoding "sequencer" Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:39 ` Nguyễn Thái Ngọc Duy
  2014-02-26  1:24   ` Eric Sunshine
  2014-02-26 23:58   ` Junio C Hamano
  2014-02-18 13:40 ` [PATCH v3 11/25] git-sh-setup.sh: use rev-parse --git-path to get $GIT_DIR/objects Nguyễn Thái Ngọc Duy
                   ` (16 subsequent siblings)
  26 siblings, 2 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:39 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

This variable is intended to support multiple working directories
attached to a repository. Such a repository may have a main working
directory, created by either "git init" or "git clone" and one or more
linked working directories. These working directories and the main
repository share the same repository directory.

In linked working directories, $GIT_COMMON_DIR must be defined to point
to the real repository directory and $GIT_DIR points to an unused
subdirectory inside $GIT_COMMON_DIR. File locations inside the
repository are reorganized from the linked worktree view point:

 - worktree-specific such as HEAD, logs/HEAD, index, other top-level
   refs and unrecognized files are from $GIT_DIR.

 - the rest like objects, refs, info, hooks, packed-refs, shallow...
   are from $GIT_COMMON_DIR

Scripts are supposed to retrieve paths in $GIT_DIR with "git rev-parse
--git-path", which will take care of "$GIT_DIR vs $GIT_COMMON_DIR"
business.

Note that logs/refs/.tmp-renamed-log is used to prepare new reflog
entry and it's supposed to be on the same filesystem as the target
reflog file. This is not guaranteed true for logs/HEAD when it's
mapped to repos/xx/logs/HEAD because the user can put "repos"
directory on different filesystem. Don't mess with .git unless you
know what you're doing.

The redirection is done by git_path(), git_pathdup() and
strbuf_git_path(). The selected list of paths goes to $GIT_COMMON_DIR,
not the other way around in case a developer adds a new
worktree-specific file and it's accidentally promoted to be shared
across repositories (this includes unknown files added by third party
commands)

The list of known files that belong to $GIT_DIR are:

ADD_EDIT.patch BISECT_ANCESTORS_OK BISECT_EXPECTED_REV BISECT_LOG
BISECT_NAMES CHERRY_PICK_HEAD COMMIT_MSG FETCH_HEAD HEAD MERGE_HEAD
MERGE_MODE MERGE_RR NOTES_EDITMSG NOTES_MERGE_WORKTREE ORIG_HEAD
REVERT_HEAD SQUASH_MSG TAG_EDITMSG fast_import_crash_* logs/HEAD
next-index-* rebase-apply rebase-merge rsync-refs-* sequencer/*
shallow_*

Path mapping is NOT done for git_path_submodule(). Multi-checkouts are
not supported as submodules.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git.txt |  8 ++++++++
 cache.h               |  4 +++-
 environment.c         | 19 +++++++++++++++----
 path.c                | 28 ++++++++++++++++++++++++++++
 t/t0060-path-utils.sh | 15 +++++++++++++++
 5 files changed, 69 insertions(+), 5 deletions(-)

diff --git a/Documentation/git.txt b/Documentation/git.txt
index 02bbc08..2c4a055 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -773,6 +773,14 @@ Git so take care if using Cogito etc.
 	an explicit repository directory set via 'GIT_DIR' or on the
 	command line.
 
+'GIT_COMMON_DIR'::
+	If this variable is set to a path, non-worktree files that are
+	normally in $GIT_DIR will be taken from this path
+	instead. Worktree-specific files such as HEAD or index are
+	taken from $GIT_DIR. This variable has lower precedence than
+	other path variables such as GIT_INDEX_FILE,
+	GIT_OBJECT_DIRECTORY...
+
 Git Commits
 ~~~~~~~~~~~
 'GIT_AUTHOR_NAME'::
diff --git a/cache.h b/cache.h
index 6c08e4a..51ade32 100644
--- a/cache.h
+++ b/cache.h
@@ -347,6 +347,7 @@ static inline enum object_type object_type(unsigned int mode)
 
 /* Double-check local_repo_env below if you add to this list. */
 #define GIT_DIR_ENVIRONMENT "GIT_DIR"
+#define GIT_COMMON_DIR_ENVIRONMENT "GIT_COMMON_DIR"
 #define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE"
 #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
 #define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX"
@@ -400,6 +401,7 @@ extern int is_inside_git_dir(void);
 extern char *git_work_tree_cfg;
 extern int is_inside_work_tree(void);
 extern const char *get_git_dir(void);
+extern const char *get_git_common_dir(void);
 extern int is_git_directory(const char *path);
 extern char *get_object_directory(void);
 extern char *get_index_file(void);
@@ -585,7 +587,7 @@ extern int fsync_object_files;
 extern int core_preload_index;
 extern int core_apply_sparse_checkout;
 extern int precomposed_unicode;
-extern int git_db_env, git_index_env, git_graft_env;
+extern int git_db_env, git_index_env, git_graft_env, git_common_dir_env;
 
 /*
  * The character that begins a commented line in user-editable file
diff --git a/environment.c b/environment.c
index f513479..c998120 100644
--- a/environment.c
+++ b/environment.c
@@ -80,9 +80,9 @@ static char *work_tree;
 static const char *namespace;
 static size_t namespace_len;
 
-static const char *git_dir;
+static const char *git_dir, *git_common_dir;
 static char *git_object_dir, *git_index_file, *git_graft_file;
-int git_db_env, git_index_env, git_graft_env;
+int git_db_env, git_index_env, git_graft_env, git_common_dir_env;
 
 /*
  * Repository-local GIT_* environment variables; see cache.h for details.
@@ -134,10 +134,16 @@ static void setup_git_env(void)
 		git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
 	gitfile = read_gitfile(git_dir);
 	git_dir = xstrdup(gitfile ? gitfile : git_dir);
+	git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
+	if (git_common_dir) {
+		git_common_dir_env = 1;
+		git_common_dir = xstrdup(git_common_dir);
+	} else
+		git_common_dir = git_dir;
 	git_object_dir = getenv(DB_ENVIRONMENT);
 	if (!git_object_dir) {
-		git_object_dir = xmalloc(strlen(git_dir) + 9);
-		sprintf(git_object_dir, "%s/objects", git_dir);
+		git_object_dir = xmalloc(strlen(git_common_dir) + 9);
+		sprintf(git_object_dir, "%s/objects", git_common_dir);
 	} else
 		git_db_env = 1;
 	git_index_file = getenv(INDEX_ENVIRONMENT);
@@ -173,6 +179,11 @@ const char *get_git_dir(void)
 	return git_dir;
 }
 
+const char *get_git_common_dir(void)
+{
+	return git_common_dir;
+}
+
 const char *get_git_namespace(void)
 {
 	if (!namespace)
diff --git a/path.c b/path.c
index 0f8c3dc..2d757dc 100644
--- a/path.c
+++ b/path.c
@@ -90,6 +90,32 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)
 		buf->buf[newlen] = '/';
 }
 
+static void update_common_dir(struct strbuf *buf, int git_dir_len)
+{
+	const char *common_dir_list[] = {
+		"branches", "hooks", "info", "logs", "lost-found", "modules",
+		"objects", "refs", "remotes", "rr-cache", "svn",
+		NULL
+	};
+	const char *common_top_file_list[] = {
+		"config", "gc.pid", "packed-refs", "shallow", NULL
+	};
+	char *base = buf->buf + git_dir_len;
+	const char **p;
+	if (is_dir_file(base, "logs", "HEAD"))
+		return;	/* keep this in $GIT_DIR */
+	for (p = common_dir_list; *p; p++)
+		if (dir_prefix(base, *p)) {
+			replace_dir(buf, git_dir_len, get_git_common_dir());
+			return;
+		}
+	for (p = common_top_file_list; *p; p++)
+		if (!strcmp(base, *p)) {
+			replace_dir(buf, git_dir_len, get_git_common_dir());
+			return;
+		}
+}
+
 static void adjust_git_path(struct strbuf *buf, int git_dir_len)
 {
 	const char *base = buf->buf + git_dir_len;
@@ -101,6 +127,8 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)
 			      get_index_file(), strlen(get_index_file()));
 	else if (git_db_env && dir_prefix(base, "objects"))
 		replace_dir(buf, git_dir_len + 7, get_object_directory());
+	else if (git_common_dir_env)
+		update_common_dir(buf, git_dir_len);
 }
 
 static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index 1d29901..f9a77e4 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -241,5 +241,20 @@ test_expect_success 'setup fake objects directory foo' 'mkdir foo'
 test_git_path GIT_OBJECT_DIRECTORY=foo objects foo
 test_git_path GIT_OBJECT_DIRECTORY=foo objects/foo foo/foo
 test_git_path GIT_OBJECT_DIRECTORY=foo objects2 .git/objects2
+test_expect_success 'setup common repository' 'git --git-dir=bar init'
+test_git_path GIT_COMMON_DIR=bar index                    .git/index
+test_git_path GIT_COMMON_DIR=bar HEAD                     .git/HEAD
+test_git_path GIT_COMMON_DIR=bar logs/HEAD                .git/logs/HEAD
+test_git_path GIT_COMMON_DIR=bar objects                  bar/objects
+test_git_path GIT_COMMON_DIR=bar objects/bar              bar/objects/bar
+test_git_path GIT_COMMON_DIR=bar info/exclude             bar/info/exclude
+test_git_path GIT_COMMON_DIR=bar remotes/bar              bar/remotes/bar
+test_git_path GIT_COMMON_DIR=bar branches/bar             bar/branches/bar
+test_git_path GIT_COMMON_DIR=bar logs/refs/heads/master   bar/logs/refs/heads/master
+test_git_path GIT_COMMON_DIR=bar refs/heads/master        bar/refs/heads/master
+test_git_path GIT_COMMON_DIR=bar hooks/me                 bar/hooks/me
+test_git_path GIT_COMMON_DIR=bar config                   bar/config
+test_git_path GIT_COMMON_DIR=bar packed-refs              bar/packed-refs
+test_git_path GIT_COMMON_DIR=bar shallow                  bar/shallow
 
 test_done
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 11/25] git-sh-setup.sh: use rev-parse --git-path to get $GIT_DIR/objects
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (9 preceding siblings ...)
  2014-02-18 13:39 ` [PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:40 ` Nguyễn Thái Ngọc Duy
  2014-02-27  0:00   ` Junio C Hamano
  2014-02-18 13:40 ` [PATCH v3 12/25] *.sh: avoid hardcoding $GIT_DIR/hooks/ Nguyễn Thái Ngọc Duy
                   ` (15 subsequent siblings)
  26 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:40 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

If $GIT_COMMON_DIR is set, $GIT_OBJECT_DIRECTORY should be
$GIT_COMMON_DIR/objects, not $GIT_DIR/objects. Just let rev-parse
--git-path handle it.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 git-sh-setup.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index fffa3c7..fec9430 100644
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -343,7 +343,7 @@ then
 		echo >&2 "Unable to determine absolute path of git directory"
 		exit 1
 	}
-	: ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"}
+	: ${GIT_OBJECT_DIRECTORY="`git rev-parse --git-path objects`"}
 fi
 
 peel_committish () {
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 12/25] *.sh: avoid hardcoding $GIT_DIR/hooks/...
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (10 preceding siblings ...)
  2014-02-18 13:40 ` [PATCH v3 11/25] git-sh-setup.sh: use rev-parse --git-path to get $GIT_DIR/objects Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:40 ` Nguyễn Thái Ngọc Duy
  2014-02-18 13:40 ` [PATCH v3 13/25] git-stash: avoid hardcoding $GIT_DIR/logs/ Nguyễn Thái Ngọc Duy
                   ` (14 subsequent siblings)
  26 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:40 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

If $GIT_COMMON_DIR is set, it should be $GIT_COMMON_DIR/hooks/, not
$GIT_DIR/hooks/. Just let rev-parse --git-path handle it.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 git-am.sh                              | 22 +++++++++++-----------
 git-rebase--interactive.sh             |  6 +++---
 git-rebase--merge.sh                   |  6 ++----
 git-rebase.sh                          |  4 ++--
 templates/hooks--applypatch-msg.sample |  4 ++--
 templates/hooks--pre-applypatch.sample |  4 ++--
 6 files changed, 22 insertions(+), 24 deletions(-)

diff --git a/git-am.sh b/git-am.sh
index bbea430..dfa0618 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -803,10 +803,10 @@ To restore the original branch and stop patching run \"\$cmdline --abort\"."
 		continue
 	fi
 
-	if test -x "$GIT_DIR"/hooks/applypatch-msg
+	hook="`git rev-parse --git-path hooks/applypatch-msg`"
+	if test -x "$hook"
 	then
-		"$GIT_DIR"/hooks/applypatch-msg "$dotest/final-commit" ||
-		stop_here $this
+		"$hook" "$dotest/final-commit" || stop_here $this
 	fi
 
 	if test -f "$dotest/final-commit"
@@ -880,9 +880,10 @@ did you forget to use 'git add'?"
 		stop_here_user_resolve $this
 	fi
 
-	if test -x "$GIT_DIR"/hooks/pre-applypatch
+	hook="`git rev-parse --git-path hooks/pre-applypatch`"
+	if test -x "$hook"
 	then
-		"$GIT_DIR"/hooks/pre-applypatch || stop_here $this
+		"$hook" || stop_here $this
 	fi
 
 	tree=$(git write-tree) &&
@@ -908,18 +909,17 @@ did you forget to use 'git add'?"
 		echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten"
 	fi
 
-	if test -x "$GIT_DIR"/hooks/post-applypatch
-	then
-		"$GIT_DIR"/hooks/post-applypatch
-	fi
+	hook="`git rev-parse --git-path hooks/post-applypatch`"
+	test -x "$hook" && "$hook"
 
 	go_next
 done
 
 if test -s "$dotest"/rewritten; then
     git notes copy --for-rewrite=rebase < "$dotest"/rewritten
-    if test -x "$GIT_DIR"/hooks/post-rewrite; then
-	"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+    hook="`git rev-parse --git-path hooks/post-rewrite`"
+    if test -x "$hook"; then
+	"$hook" rebase < "$dotest"/rewritten
     fi
 fi
 
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 43c19e0..d741b04 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -632,9 +632,9 @@ do_next () {
 		git notes copy --for-rewrite=rebase < "$rewritten_list" ||
 		true # we don't care if this copying failed
 	} &&
-	if test -x "$GIT_DIR"/hooks/post-rewrite &&
-		test -s "$rewritten_list"; then
-		"$GIT_DIR"/hooks/post-rewrite rebase < "$rewritten_list"
+	hook="`git rev-parse --git-path hooks/post-rewrite`"
+	if test -x "$hook" && test -s "$rewritten_list"; then
+		"$hook" rebase < "$rewritten_list"
 		true # we don't care if this hook failed
 	fi &&
 	warn "Successfully rebased and updated $head_name."
diff --git a/git-rebase--merge.sh b/git-rebase--merge.sh
index e7d96de..68f5d09 100644
--- a/git-rebase--merge.sh
+++ b/git-rebase--merge.sh
@@ -93,10 +93,8 @@ finish_rb_merge () {
 	if test -s "$state_dir"/rewritten
 	then
 		git notes copy --for-rewrite=rebase <"$state_dir"/rewritten
-		if test -x "$GIT_DIR"/hooks/post-rewrite
-		then
-			"$GIT_DIR"/hooks/post-rewrite rebase <"$state_dir"/rewritten
-		fi
+		hook="`git rev-parse --git-path hooks/post-rewrite`"
+		test -x "$hook" && "$hook" rebase <"$state_dir"/rewritten
 	fi
 	say All done.
 }
diff --git a/git-rebase.sh b/git-rebase.sh
index 8a3efa2..1cf8dba 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -195,9 +195,9 @@ run_specific_rebase () {
 
 run_pre_rebase_hook () {
 	if test -z "$ok_to_skip_pre_rebase" &&
-	   test -x "$GIT_DIR/hooks/pre-rebase"
+	   test -x "`git rev-parse --git-path hooks/pre-rebase`"
 	then
-		"$GIT_DIR/hooks/pre-rebase" ${1+"$@"} ||
+		"`git rev-parse --git-path hooks/pre-rebase`" ${1+"$@"} ||
 		die "$(gettext "The pre-rebase hook refused to rebase.")"
 	fi
 }
diff --git a/templates/hooks--applypatch-msg.sample b/templates/hooks--applypatch-msg.sample
index 8b2a2fe..28b843b 100755
--- a/templates/hooks--applypatch-msg.sample
+++ b/templates/hooks--applypatch-msg.sample
@@ -10,6 +10,6 @@
 # To enable this hook, rename this file to "applypatch-msg".
 
 . git-sh-setup
-test -x "$GIT_DIR/hooks/commit-msg" &&
-	exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
+commitmsg="`git rev-parse --git-path hooks/commit-msg`"
+test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
 :
diff --git a/templates/hooks--pre-applypatch.sample b/templates/hooks--pre-applypatch.sample
index b1f187c..51aa244 100755
--- a/templates/hooks--pre-applypatch.sample
+++ b/templates/hooks--pre-applypatch.sample
@@ -9,6 +9,6 @@
 # To enable this hook, rename this file to "pre-applypatch".
 
 . git-sh-setup
-test -x "$GIT_DIR/hooks/pre-commit" &&
-	exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
+precommit="`git rev-parse --git-path hooks/pre-commit`"
+test -x "$precommit" && exec "$precommit" ${1+"$@"}
 :
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 13/25] git-stash: avoid hardcoding $GIT_DIR/logs/....
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (11 preceding siblings ...)
  2014-02-18 13:40 ` [PATCH v3 12/25] *.sh: avoid hardcoding $GIT_DIR/hooks/ Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:40 ` Nguyễn Thái Ngọc Duy
  2014-02-18 13:40 ` [PATCH v3 14/25] setup.c: convert is_git_directory() to use strbuf Nguyễn Thái Ngọc Duy
                   ` (13 subsequent siblings)
  26 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:40 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 git-stash.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/git-stash.sh b/git-stash.sh
index ae7d16e..12d9b37 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -183,7 +183,7 @@ store_stash () {
 	fi
 
 	# Make sure the reflog for stash is kept.
-	: >>"$GIT_DIR/logs/$ref_stash"
+	: >>"`git rev-parse --git-path logs/$ref_stash`"
 	git update-ref -m "$stash_msg" $ref_stash $w_commit
 	ret=$?
 	test $ret != 0 && test -z $quiet &&
@@ -258,7 +258,7 @@ save_stash () {
 		say "$(gettext "No local changes to save")"
 		exit 0
 	fi
-	test -f "$GIT_DIR/logs/$ref_stash" ||
+	test -f "`git rev-parse --git-path logs/$ref_stash`" ||
 		clear_stash || die "$(gettext "Cannot initialize stash")"
 
 	create_stash "$stash_msg" $untracked
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 14/25] setup.c: convert is_git_directory() to use strbuf
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (12 preceding siblings ...)
  2014-02-18 13:40 ` [PATCH v3 13/25] git-stash: avoid hardcoding $GIT_DIR/logs/ Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:40 ` Nguyễn Thái Ngọc Duy
  2014-02-19 20:17   ` Junio C Hamano
  2014-02-21  3:38   ` [PATCH v3 14/25] setup.c: convert is_git_directory() to use strbuf Eric Sunshine
  2014-02-18 13:40 ` [PATCH v3 15/25] setup.c: detect $GIT_COMMON_DIR in is_git_directory() Nguyễn Thái Ngọc Duy
                   ` (12 subsequent siblings)
  26 siblings, 2 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:40 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 setup.c  | 35 +++++++++++++++++++----------------
 strbuf.h |  4 ++++
 2 files changed, 23 insertions(+), 16 deletions(-)

diff --git a/setup.c b/setup.c
index 6c3f85f..999225b 100644
--- a/setup.c
+++ b/setup.c
@@ -184,31 +184,34 @@ void verify_non_filename(const char *prefix, const char *arg)
  */
 int is_git_directory(const char *suspect)
 {
-	char path[PATH_MAX];
-	size_t len = strlen(suspect);
+	struct strbuf path = STRBUF_INIT;
+	int ret = 0;
+	size_t len;
 
-	if (PATH_MAX <= len + strlen("/objects"))
-		die("Too long path: %.*s", 60, suspect);
-	strcpy(path, suspect);
+	strbuf_addstr(&path, suspect);
+	len = path.len;
 	if (getenv(DB_ENVIRONMENT)) {
 		if (access(getenv(DB_ENVIRONMENT), X_OK))
-			return 0;
+			goto done;
 	}
 	else {
-		strcpy(path + len, "/objects");
-		if (access(path, X_OK))
-			return 0;
+		strbuf_addstr(&path, "/objects");
+		if (access(path.buf, X_OK))
+			goto done;
 	}
 
-	strcpy(path + len, "/refs");
-	if (access(path, X_OK))
-		return 0;
+	strbuf_addstr_at(&path, len, "/refs");
+	if (access(path.buf, X_OK))
+		goto done;
 
-	strcpy(path + len, "/HEAD");
-	if (validate_headref(path))
-		return 0;
+	strbuf_addstr_at(&path, len, "/HEAD");
+	if (validate_headref(path.buf))
+		goto done;
 
-	return 1;
+	ret = 1;
+done:
+	strbuf_release(&path);
+	return ret;
 }
 
 int is_inside_git_dir(void)
diff --git a/strbuf.h b/strbuf.h
index 73e80ce..aec9fdb 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -116,6 +116,10 @@ extern void strbuf_add(struct strbuf *, const void *, size_t);
 static inline void strbuf_addstr(struct strbuf *sb, const char *s) {
 	strbuf_add(sb, s, strlen(s));
 }
+static inline void strbuf_addstr_at(struct strbuf *sb, size_t len, const char *s) {
+	strbuf_setlen(sb, len);
+	strbuf_add(sb, s, strlen(s));
+}
 static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) {
 	strbuf_grow(sb, sb2->len);
 	strbuf_add(sb, sb2->buf, sb2->len);
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 15/25] setup.c: detect $GIT_COMMON_DIR in is_git_directory()
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (13 preceding siblings ...)
  2014-02-18 13:40 ` [PATCH v3 14/25] setup.c: convert is_git_directory() to use strbuf Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:40 ` Nguyễn Thái Ngọc Duy
  2014-02-27  0:16   ` Junio C Hamano
  2014-02-18 13:40 ` [PATCH v3 16/25] setup.c: convert check_repository_format_gently to use strbuf Nguyễn Thái Ngọc Duy
                   ` (11 subsequent siblings)
  26 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:40 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

If the file "$GIT_DIR/commondir" exists, it contains the value of
$GIT_COMMON_DIR.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/gitrepository-layout.txt |  4 ++++
 setup.c                                | 38 ++++++++++++++++++++++++++++------
 strbuf.c                               |  8 +++++++
 strbuf.h                               |  1 +
 4 files changed, 45 insertions(+), 6 deletions(-)

diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index aa03882..9bfe0f1 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -211,6 +211,10 @@ shallow::
 	and maintained by shallow clone mechanism.  See `--depth`
 	option to linkgit:git-clone[1] and linkgit:git-fetch[1].
 
+commondir::
+	If this file exists, $GIT_COMMON_DIR will be set to the path
+	specified in this file if it is not set.
+
 modules::
 	Contains the git-repositories of the submodules.
 
diff --git a/setup.c b/setup.c
index 999225b..4e5711c 100644
--- a/setup.c
+++ b/setup.c
@@ -170,6 +170,30 @@ void verify_non_filename(const char *prefix, const char *arg)
 	    "'git <command> [<revision>...] -- [<file>...]'", arg);
 }
 
+static void get_common_dir(struct strbuf *sb, const char *gitdir)
+{
+	struct strbuf data = STRBUF_INIT;
+	struct strbuf path = STRBUF_INIT;
+	const char *git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
+	if (git_common_dir) {
+		strbuf_addstr(sb, git_common_dir);
+		return;
+	}
+	strbuf_addf(&path, "%s/commondir", gitdir);
+	if (file_exists(path.buf)) {
+		if (strbuf_read_file(&data, path.buf, 0) <= 0)
+			die_errno(_("failed to read %s"), path.buf);
+		strbuf_chomp(&data);
+		strbuf_reset(&path);
+		if (!is_absolute_path(data.buf))
+			strbuf_addf(&path, "%s/", gitdir);
+		strbuf_addbuf(&path, &data);
+		strbuf_addstr(sb, real_path(path.buf));
+	} else
+		strbuf_addstr(sb, gitdir);
+	strbuf_release(&data);
+	strbuf_release(&path);
+}
 
 /*
  * Test if it looks like we're at a git directory.
@@ -188,14 +212,20 @@ int is_git_directory(const char *suspect)
 	int ret = 0;
 	size_t len;
 
-	strbuf_addstr(&path, suspect);
+	strbuf_addf(&path, "%s/HEAD", suspect);
+	if (validate_headref(path.buf))
+		goto done;
+
+	strbuf_reset(&path);
+	get_common_dir(&path, suspect);
 	len = path.len;
+
 	if (getenv(DB_ENVIRONMENT)) {
 		if (access(getenv(DB_ENVIRONMENT), X_OK))
 			goto done;
 	}
 	else {
-		strbuf_addstr(&path, "/objects");
+		strbuf_addstr_at(&path, len, "/objects");
 		if (access(path.buf, X_OK))
 			goto done;
 	}
@@ -204,10 +234,6 @@ int is_git_directory(const char *suspect)
 	if (access(path.buf, X_OK))
 		goto done;
 
-	strbuf_addstr_at(&path, len, "/HEAD");
-	if (validate_headref(path.buf))
-		goto done;
-
 	ret = 1;
 done:
 	strbuf_release(&path);
diff --git a/strbuf.c b/strbuf.c
index 83caf4a..e17c358 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -588,3 +588,11 @@ int fprintf_ln(FILE *fp, const char *fmt, ...)
 		return -1;
 	return ret + 1;
 }
+
+void strbuf_chomp(struct strbuf *sb)
+{
+	while (sb->len && (sb->buf[sb->len - 1] == '\n' ||
+			   sb->buf[sb->len - 1] == '\r'))
+		sb->len--;
+	sb->buf[sb->len] = '\0';
+}
diff --git a/strbuf.h b/strbuf.h
index aec9fdb..cd9578f 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -109,6 +109,7 @@ extern void strbuf_remove(struct strbuf *, size_t pos, size_t len);
 /* splice pos..pos+len with given data */
 extern void strbuf_splice(struct strbuf *, size_t pos, size_t len,
                           const void *, size_t);
+extern void strbuf_chomp(struct strbuf *sb);
 
 extern void strbuf_add_commented_lines(struct strbuf *out, const char *buf, size_t size);
 
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 16/25] setup.c: convert check_repository_format_gently to use strbuf
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (14 preceding siblings ...)
  2014-02-18 13:40 ` [PATCH v3 15/25] setup.c: detect $GIT_COMMON_DIR in is_git_directory() Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:40 ` Nguyễn Thái Ngọc Duy
  2014-02-27  0:18   ` Junio C Hamano
  2014-02-18 13:40 ` [PATCH v3 17/25] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently() Nguyễn Thái Ngọc Duy
                   ` (10 subsequent siblings)
  26 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:40 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 setup.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/setup.c b/setup.c
index 4e5711c..282fdc9 100644
--- a/setup.c
+++ b/setup.c
@@ -281,7 +281,9 @@ void setup_work_tree(void)
 
 static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 {
-	char repo_config[PATH_MAX+1];
+	struct strbuf sb = STRBUF_INIT;
+	const char *repo_config;
+	int ret = 0;
 
 	/*
 	 * git_config() can't be used here because it calls git_pathdup()
@@ -292,7 +294,8 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 	 * Use a gentler version of git_config() to check if this repo
 	 * is a good one.
 	 */
-	snprintf(repo_config, PATH_MAX, "%s/config", gitdir);
+	strbuf_addf(&sb, "%s/config", gitdir);
+	repo_config = sb.buf;
 	git_config_early(check_repository_format_version, NULL, repo_config);
 	if (GIT_REPO_VERSION < repository_format_version) {
 		if (!nongit_ok)
@@ -302,9 +305,10 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 			GIT_REPO_VERSION, repository_format_version);
 		warning("Please upgrade Git");
 		*nongit_ok = -1;
-		return -1;
+		ret = -1;
 	}
-	return 0;
+	strbuf_release(&sb);
+	return ret;
 }
 
 /*
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 17/25] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently()
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (15 preceding siblings ...)
  2014-02-18 13:40 ` [PATCH v3 16/25] setup.c: convert check_repository_format_gently to use strbuf Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:40 ` Nguyễn Thái Ngọc Duy
  2014-02-27  0:22   ` Junio C Hamano
  2014-02-18 13:40 ` [PATCH v3 18/25] setup.c: support multi-checkout repo setup Nguyễn Thái Ngọc Duy
                   ` (9 subsequent siblings)
  26 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:40 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 setup.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/setup.c b/setup.c
index 282fdc9..e56ec11 100644
--- a/setup.c
+++ b/setup.c
@@ -285,6 +285,10 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 	const char *repo_config;
 	int ret = 0;
 
+	get_common_dir(&sb, gitdir);
+	strbuf_addstr(&sb, "/config");
+	repo_config = sb.buf;
+
 	/*
 	 * git_config() can't be used here because it calls git_pathdup()
 	 * to get $GIT_CONFIG/config. That call will make setup_git_env()
@@ -294,8 +298,6 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 	 * Use a gentler version of git_config() to check if this repo
 	 * is a good one.
 	 */
-	strbuf_addf(&sb, "%s/config", gitdir);
-	repo_config = sb.buf;
 	git_config_early(check_repository_format_version, NULL, repo_config);
 	if (GIT_REPO_VERSION < repository_format_version) {
 		if (!nongit_ok)
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 18/25] setup.c: support multi-checkout repo setup
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (16 preceding siblings ...)
  2014-02-18 13:40 ` [PATCH v3 17/25] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently() Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:40 ` Nguyễn Thái Ngọc Duy
  2014-02-19 20:19   ` Junio C Hamano
                     ` (2 more replies)
  2014-02-18 13:40 ` [PATCH v3 19/25] wrapper.c: wrapper to open a file, fprintf then close Nguyễn Thái Ngọc Duy
                   ` (8 subsequent siblings)
  26 siblings, 3 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:40 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

The repo setup procedure is updated to detect $GIT_DIR/commondir and
set $GIT_COMMON_DIR properly.

The core.worktree is ignored when $GIT_DIR/commondir presents. This is
because "commondir" repos are intended for separate/linked checkouts
and pointing them back to a fixed core.worktree just does not make
sense.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/config.txt        |  3 +-
 Documentation/git-rev-parse.txt |  3 ++
 builtin/rev-parse.c             |  4 +++
 cache.h                         |  1 +
 environment.c                   |  8 ++---
 setup.c                         | 33 +++++++++++++-----
 t/t1501-worktree.sh             | 76 +++++++++++++++++++++++++++++++++++++++++
 t/t1510-repo-setup.sh           |  1 +
 trace.c                         |  1 +
 9 files changed, 115 insertions(+), 15 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 5f4d793..cbf4d97 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -381,7 +381,8 @@ false), while all other repositories are assumed to be bare (bare
 
 core.worktree::
 	Set the path to the root of the working tree.
-	This can be overridden by the GIT_WORK_TREE environment
+	This can be overridden by the GIT_WORK_TREE
+	or GIT_COMMON_DIR environment
 	variable and the '--work-tree' command line option.
 	The value can be an absolute path or relative to the path to
 	the .git directory, which is either specified by --git-dir
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 33e4e90..8e6ad32 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -215,6 +215,9 @@ If `$GIT_DIR` is not defined and the current directory
 is not detected to lie in a Git repository or work tree
 print a message to stderr and exit with nonzero status.
 
+--git-common-dir::
+	Show `$GIT_COMMON_DIR` if defined, else `$GIT_DIR`.
+
 --is-inside-git-dir::
 	When the current working directory is below the repository
 	directory print "true", otherwise "false".
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index e50bc65..c7057ce 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -744,6 +744,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
 				printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : "");
 				continue;
 			}
+			if (!strcmp(arg, "--git-common-dir")) {
+				puts(get_git_common_dir());
+				continue;
+			}
 			if (!strcmp(arg, "--resolve-git-dir")) {
 				const char *gitdir = resolve_gitdir(argv[i+1]);
 				if (!gitdir)
diff --git a/cache.h b/cache.h
index 51ade32..98b5dd3 100644
--- a/cache.h
+++ b/cache.h
@@ -407,6 +407,7 @@ extern char *get_object_directory(void);
 extern char *get_index_file(void);
 extern char *get_graft_file(void);
 extern int set_git_dir(const char *path);
+extern int get_common_dir(struct strbuf *sb, const char *gitdir);
 extern const char *get_git_namespace(void);
 extern const char *strip_namespace(const char *namespaced_ref);
 extern const char *get_git_work_tree(void);
diff --git a/environment.c b/environment.c
index c998120..0999fc1 100644
--- a/environment.c
+++ b/environment.c
@@ -126,6 +126,7 @@ static char *expand_namespace(const char *raw_namespace)
 
 static void setup_git_env(void)
 {
+	struct strbuf sb = STRBUF_INIT;
 	const char *gitfile;
 	const char *shallow_file;
 
@@ -134,12 +135,9 @@ static void setup_git_env(void)
 		git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
 	gitfile = read_gitfile(git_dir);
 	git_dir = xstrdup(gitfile ? gitfile : git_dir);
-	git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
-	if (git_common_dir) {
+	if (get_common_dir(&sb, git_dir))
 		git_common_dir_env = 1;
-		git_common_dir = xstrdup(git_common_dir);
-	} else
-		git_common_dir = git_dir;
+	git_common_dir = strbuf_detach(&sb, NULL);
 	git_object_dir = getenv(DB_ENVIRONMENT);
 	if (!git_object_dir) {
 		git_object_dir = xmalloc(strlen(git_common_dir) + 9);
diff --git a/setup.c b/setup.c
index e56ec11..d4ac878 100644
--- a/setup.c
+++ b/setup.c
@@ -170,14 +170,15 @@ void verify_non_filename(const char *prefix, const char *arg)
 	    "'git <command> [<revision>...] -- [<file>...]'", arg);
 }
 
-static void get_common_dir(struct strbuf *sb, const char *gitdir)
+int get_common_dir(struct strbuf *sb, const char *gitdir)
 {
 	struct strbuf data = STRBUF_INIT;
 	struct strbuf path = STRBUF_INIT;
 	const char *git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
+	int ret = 0;
 	if (git_common_dir) {
 		strbuf_addstr(sb, git_common_dir);
-		return;
+		return 1;
 	}
 	strbuf_addf(&path, "%s/commondir", gitdir);
 	if (file_exists(path.buf)) {
@@ -189,10 +190,12 @@ static void get_common_dir(struct strbuf *sb, const char *gitdir)
 			strbuf_addf(&path, "%s/", gitdir);
 		strbuf_addbuf(&path, &data);
 		strbuf_addstr(sb, real_path(path.buf));
+		ret = 1;
 	} else
 		strbuf_addstr(sb, gitdir);
 	strbuf_release(&data);
 	strbuf_release(&path);
+	return ret;
 }
 
 /*
@@ -279,13 +282,26 @@ void setup_work_tree(void)
 	initialized = 1;
 }
 
+static int check_repo_format(const char *var, const char *value, void *cb)
+{
+	if (strcmp(var, "core.repositoryformatversion") == 0)
+		repository_format_version = git_config_int(var, value);
+	else if (strcmp(var, "core.sharedrepository") == 0)
+		shared_repository = git_config_perm(var, value);
+	return 0;
+}
+
 static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 {
 	struct strbuf sb = STRBUF_INIT;
 	const char *repo_config;
+	config_fn_t fn;
 	int ret = 0;
 
-	get_common_dir(&sb, gitdir);
+	if (get_common_dir(&sb, gitdir))
+		fn = check_repo_format;
+	else
+		fn = check_repository_format_version;
 	strbuf_addstr(&sb, "/config");
 	repo_config = sb.buf;
 
@@ -298,7 +314,7 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 	 * Use a gentler version of git_config() to check if this repo
 	 * is a good one.
 	 */
-	git_config_early(check_repository_format_version, NULL, repo_config);
+	git_config_early(fn, NULL, repo_config);
 	if (GIT_REPO_VERSION < repository_format_version) {
 		if (!nongit_ok)
 			die ("Expected git repo version <= %d, found %d",
@@ -770,11 +786,10 @@ int git_config_perm(const char *var, const char *value)
 
 int check_repository_format_version(const char *var, const char *value, void *cb)
 {
-	if (strcmp(var, "core.repositoryformatversion") == 0)
-		repository_format_version = git_config_int(var, value);
-	else if (strcmp(var, "core.sharedrepository") == 0)
-		shared_repository = git_config_perm(var, value);
-	else if (strcmp(var, "core.bare") == 0) {
+	int ret = check_repo_format(var, value, cb);
+	if (ret)
+		return ret;
+	if (strcmp(var, "core.bare") == 0) {
 		is_bare_repository_cfg = git_config_bool(var, value);
 		if (is_bare_repository_cfg == 1)
 			inside_work_tree = -1;
diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh
index 8f36aa9..d8bdaf4 100755
--- a/t/t1501-worktree.sh
+++ b/t/t1501-worktree.sh
@@ -346,4 +346,80 @@ test_expect_success 'relative $GIT_WORK_TREE and git subprocesses' '
 	test_cmp expected actual
 '
 
+test_expect_success 'Multi-worktree setup' '
+	mkdir work &&
+	mkdir -p repo.git/repos/foo &&
+	cp repo.git/HEAD repo.git/index repo.git/repos/foo &&
+	unset GIT_DIR GIT_CONFIG GIT_WORK_TREE
+'
+
+test_expect_success 'GIT_DIR set (1)' '
+	echo "gitdir: repo.git/repos/foo" >gitfile &&
+	echo ../.. >repo.git/repos/foo/commondir &&
+	(
+		cd work &&
+		GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
+		echo "$TRASH_DIRECTORY/repo.git" >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'GIT_DIR set (2)' '
+	echo "gitdir: repo.git/repos/foo" >gitfile &&
+	echo "$TRASH_DIRECTORY/repo.git" >repo.git/repos/foo/commondir &&
+	(
+		cd work &&
+		GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
+		echo "$TRASH_DIRECTORY/repo.git" >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'Auto discovery' '
+	echo "gitdir: repo.git/repos/foo" >.git &&
+	echo ../.. >repo.git/repos/foo/commondir &&
+	(
+		cd work &&
+		git rev-parse --git-common-dir >actual &&
+		echo "$TRASH_DIRECTORY/repo.git" >expect &&
+		test_cmp expect actual &&
+		echo haha >data1 &&
+		git add data1 &&
+		git ls-files --full-name :/ | grep data1 >actual &&
+		echo work/data1 >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '$GIT_DIR/common overrides core.worktree' '
+	mkdir elsewhere &&
+	git --git-dir=repo.git config core.worktree "$TRASH_DIRECTORY/elsewhere" &&
+	echo "gitdir: repo.git/repos/foo" >.git &&
+	echo ../.. >repo.git/repos/foo/commondir &&
+	(
+		cd work &&
+		git rev-parse --git-common-dir >actual &&
+		echo "$TRASH_DIRECTORY/repo.git" >expect &&
+		test_cmp expect actual &&
+		echo haha >data2 &&
+		git add data2 &&
+		git ls-files --full-name :/ | grep data2 >actual &&
+		echo work/data2 >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '$GIT_WORK_TREE overrides $GIT_DIR/common' '
+	echo "gitdir: repo.git/repos/foo" >.git &&
+	echo ../.. >repo.git/repos/foo/commondir &&
+	(
+		cd work &&
+		echo haha >data3 &&
+		git --git-dir=../.git --work-tree=. add data3 &&
+		git ls-files --full-name -- :/ | grep data3 >actual &&
+		echo data3 >expect &&
+		test_cmp expect actual
+	)
+'
+
 test_done
diff --git a/t/t1510-repo-setup.sh b/t/t1510-repo-setup.sh
index cf2ee78..c211ef5 100755
--- a/t/t1510-repo-setup.sh
+++ b/t/t1510-repo-setup.sh
@@ -106,6 +106,7 @@ setup_env () {
 expect () {
 	cat >"$1/expected" <<-EOF
 	setup: git_dir: $2
+	setup: git_common_dir: $2
 	setup: worktree: $3
 	setup: cwd: $4
 	setup: prefix: $5
diff --git a/trace.c b/trace.c
index 08180a9..a594761 100644
--- a/trace.c
+++ b/trace.c
@@ -173,6 +173,7 @@ void trace_repo_setup(const char *prefix)
 		prefix = "(null)";
 
 	trace_printf_key(key, "setup: git_dir: %s\n", quote_crnl(get_git_dir()));
+	trace_printf_key(key, "setup: git_common_dir: %s\n", quote_crnl(get_git_common_dir()));
 	trace_printf_key(key, "setup: worktree: %s\n", quote_crnl(git_work_tree));
 	trace_printf_key(key, "setup: cwd: %s\n", quote_crnl(cwd));
 	trace_printf_key(key, "setup: prefix: %s\n", quote_crnl(prefix));
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 19/25] wrapper.c: wrapper to open a file, fprintf then close
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (17 preceding siblings ...)
  2014-02-18 13:40 ` [PATCH v3 18/25] setup.c: support multi-checkout repo setup Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:40 ` Nguyễn Thái Ngọc Duy
  2014-02-18 13:40 ` [PATCH v3 20/25] use new wrapper write_file() for simple file writing Nguyễn Thái Ngọc Duy
                   ` (7 subsequent siblings)
  26 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:40 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 cache.h   |  2 ++
 wrapper.c | 31 +++++++++++++++++++++++++++++++
 2 files changed, 33 insertions(+)

diff --git a/cache.h b/cache.h
index 98b5dd3..99b86d9 100644
--- a/cache.h
+++ b/cache.h
@@ -1239,6 +1239,8 @@ static inline ssize_t write_str_in_full(int fd, const char *str)
 {
 	return write_in_full(fd, str, strlen(str));
 }
+__attribute__((format (printf,3,4)))
+extern int write_file(const char *path, int fatal, const char *fmt, ...);
 
 /* pager.c */
 extern void setup_pager(void);
diff --git a/wrapper.c b/wrapper.c
index 0cc5636..5ced50d 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -455,3 +455,34 @@ struct passwd *xgetpwuid_self(void)
 		    errno ? strerror(errno) : _("no such user"));
 	return pw;
 }
+
+int write_file(const char *path, int fatal, const char *fmt, ...)
+{
+	struct strbuf sb = STRBUF_INIT;
+	int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);
+	va_list params;
+	if (fd < 0) {
+		if (fatal)
+			die_errno(_("could not open %s for writing"), path);
+		return -1;
+	}
+	va_start(params, fmt);
+	strbuf_vaddf(&sb, fmt, params);
+	va_end(params);
+	if (write_in_full(fd, sb.buf, sb.len) != sb.len) {
+		int err = errno;
+		close(fd);
+		errno = err;
+		strbuf_release(&sb);
+		if (fatal)
+			die_errno(_("could not write to %s"), path);
+		return -1;
+	}
+	strbuf_release(&sb);
+	if (close(fd)) {
+		if (fatal)
+			die_errno(_("could not close %s"), path);
+		return -1;
+	}
+	return 0;
+}
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 20/25] use new wrapper write_file() for simple file writing
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (18 preceding siblings ...)
  2014-02-18 13:40 ` [PATCH v3 19/25] wrapper.c: wrapper to open a file, fprintf then close Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:40 ` Nguyễn Thái Ngọc Duy
  2014-02-18 13:40 ` [PATCH v3 21/25] checkout: support checking out into a new working directory Nguyễn Thái Ngọc Duy
                   ` (6 subsequent siblings)
  26 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:40 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

This fixes common problems in these code about error handling,
forgetting to close the file handle after fprintf() fails, or not
printing out the error string..

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/branch.c  |  4 +---
 builtin/init-db.c |  7 +------
 daemon.c          | 11 +----------
 submodule.c       |  9 ++-------
 transport.c       |  8 +++-----
 5 files changed, 8 insertions(+), 31 deletions(-)

diff --git a/builtin/branch.c b/builtin/branch.c
index b4d7716..3eebdbc 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -754,7 +754,6 @@ static const char edit_description[] = "BRANCH_DESCRIPTION";
 
 static int edit_branch_description(const char *branch_name)
 {
-	FILE *fp;
 	int status;
 	struct strbuf buf = STRBUF_INIT;
 	struct strbuf name = STRBUF_INIT;
@@ -767,8 +766,7 @@ static int edit_branch_description(const char *branch_name)
 		    "  %s\n"
 		    "Lines starting with '%c' will be stripped.\n",
 		    branch_name, comment_line_char);
-	fp = fopen(git_path(edit_description), "w");
-	if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) {
+	if (write_file(git_path(edit_description), 0, "%s", buf.buf)) {
 		strbuf_release(&buf);
 		return error(_("could not write branch description template: %s"),
 			     strerror(errno));
diff --git a/builtin/init-db.c b/builtin/init-db.c
index c7c76bb..081e512 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -342,7 +342,6 @@ int set_git_dir_init(const char *git_dir, const char *real_git_dir,
 static void separate_git_dir(const char *git_dir)
 {
 	struct stat st;
-	FILE *fp;
 
 	if (!stat(git_link, &st)) {
 		const char *src;
@@ -358,11 +357,7 @@ static void separate_git_dir(const char *git_dir)
 			die_errno(_("unable to move %s to %s"), src, git_dir);
 	}
 
-	fp = fopen(git_link, "w");
-	if (!fp)
-		die(_("Could not create git link %s"), git_link);
-	fprintf(fp, "gitdir: %s\n", git_dir);
-	fclose(fp);
+	write_file(git_link, 1, "gitdir: %s\n", git_dir);
 }
 
 int init_db(const char *template_dir, unsigned int flags)
diff --git a/daemon.c b/daemon.c
index 503e039..b880d30 100644
--- a/daemon.c
+++ b/daemon.c
@@ -1122,15 +1122,6 @@ static void daemonize(void)
 }
 #endif
 
-static void store_pid(const char *path)
-{
-	FILE *f = fopen(path, "w");
-	if (!f)
-		die_errno("cannot open pid file '%s'", path);
-	if (fprintf(f, "%"PRIuMAX"\n", (uintmax_t) getpid()) < 0 || fclose(f) != 0)
-		die_errno("failed to write pid file '%s'", path);
-}
-
 static int serve(struct string_list *listen_addr, int listen_port,
     struct credentials *cred)
 {
@@ -1339,7 +1330,7 @@ int main(int argc, char **argv)
 		sanitize_stdfds();
 
 	if (pid_file)
-		store_pid(pid_file);
+		write_file(pid_file, 1, "%"PRIuMAX"\n", (uintmax_t) getpid());
 
 	/* prepare argv for serving-processes */
 	cld_argv = xmalloc(sizeof (char *) * (argc + 2));
diff --git a/submodule.c b/submodule.c
index 613857e..fe5748d 100644
--- a/submodule.c
+++ b/submodule.c
@@ -1135,16 +1135,11 @@ void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir)
 	struct strbuf file_name = STRBUF_INIT;
 	struct strbuf rel_path = STRBUF_INIT;
 	const char *real_work_tree = xstrdup(real_path(work_tree));
-	FILE *fp;
 
 	/* Update gitfile */
 	strbuf_addf(&file_name, "%s/.git", work_tree);
-	fp = fopen(file_name.buf, "w");
-	if (!fp)
-		die(_("Could not create git link %s"), file_name.buf);
-	fprintf(fp, "gitdir: %s\n", relative_path(git_dir, real_work_tree,
-						  &rel_path));
-	fclose(fp);
+	write_file(file_name.buf, 1, "gitdir: %s\n",
+		   relative_path(git_dir, real_work_tree, &rel_path));
 
 	/* Update core.worktree setting */
 	strbuf_reset(&file_name);
diff --git a/transport.c b/transport.c
index ca7bb44..2df8a15 100644
--- a/transport.c
+++ b/transport.c
@@ -294,7 +294,6 @@ static int write_one_ref(const char *name, const unsigned char *sha1,
 {
 	struct strbuf *buf = data;
 	int len = buf->len;
-	FILE *f;
 
 	/* when called via for_each_ref(), flags is non-zero */
 	if (flags && !starts_with(name, "refs/heads/") &&
@@ -303,10 +302,9 @@ static int write_one_ref(const char *name, const unsigned char *sha1,
 
 	strbuf_addstr(buf, name);
 	if (safe_create_leading_directories(buf->buf) ||
-			!(f = fopen(buf->buf, "w")) ||
-			fprintf(f, "%s\n", sha1_to_hex(sha1)) < 0 ||
-			fclose(f))
-		return error("problems writing temporary file %s", buf->buf);
+	    write_file(buf->buf, 0, "%s\n", sha1_to_hex(sha1)))
+		return error("problems writing temporary file %s: %s",
+			     buf->buf, strerror(errno));
 	strbuf_setlen(buf, len);
 	return 0;
 }
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 21/25] checkout: support checking out into a new working directory
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (19 preceding siblings ...)
  2014-02-18 13:40 ` [PATCH v3 20/25] use new wrapper write_file() for simple file writing Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:40 ` Nguyễn Thái Ngọc Duy
  2014-02-26 20:06   ` Eric Sunshine
  2014-02-18 13:40 ` [PATCH v3 22/25] checkout: clean up half-prepared directories in --to mode Nguyễn Thái Ngọc Duy
                   ` (5 subsequent siblings)
  26 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:40 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

"git checkout --to" sets up a new working directory with a .git file
pointing to $GIT_DIR/repos/<id>. It then executes "git checkout" again
on the new worktree with the same arguments except "--to" is taken
out. The second checkout execution, which is not contaminated with any
info from the current repository, will actually check out and
everything that normal "git checkout" does.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt         | 34 +++++++++++++
 Documentation/gitrepository-layout.txt |  3 ++
 builtin/checkout.c                     | 93 +++++++++++++++++++++++++++++++++-
 t/t2025-checkout-to.sh (new +x)        | 39 ++++++++++++++
 4 files changed, 167 insertions(+), 2 deletions(-)
 create mode 100755 t/t2025-checkout-to.sh

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 33ad2ad..fcf73b2 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -225,6 +225,13 @@ This means that you can use `git checkout -p` to selectively discard
 edits from your current working tree. See the ``Interactive Mode''
 section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
 
+--to=<path>::
+	Check out a new branch in a separate working directory at
+	`<path>`. A new working directory is linked to the current
+	repository, sharing everything except working directory
+	specific files such as HEAD, index... See "MULTIPLE CHECKOUT
+	MODE" section for more information.
+
 <branch>::
 	Branch to checkout; if it refers to a branch (i.e., a name that,
 	when prepended with "refs/heads/", is a valid ref), then that
@@ -388,6 +395,33 @@ $ git reflog -2 HEAD # or
 $ git log -g -2 HEAD
 ------------
 
+MULTIPLE CHECKOUT MODE
+-------------------------------
+Normally a working directory is attached to repository. When "git
+checkout --to" is used, a new working directory is attached to the
+current repository. This new working directory is called "linked
+checkout" as compared to the "main checkout" prepared by "git init" or
+"git clone". A repository has one main checkout and zero or more
+linked checkouts.
+
+All checkouts share the same repository. Linked checkouts see the
+repository a bit different from the main checkout. When the checkout
+"new" reads the path $GIT_DIR/HEAD for example, the actual path
+returned could be $GIT_DIR/repos/new/HEAD. This ensures checkouts
+won't step on each other.
+
+Each linked checkout has a private space in $GIT_DIR/repos, usually
+named after the base name of the working directory with a number added
+to make it unique. The linked checkout's $GIT_DIR points to this
+private space while $GIT_COMMON_DIR points to the main checkout's
+$GIT_DIR. These settings are done by "git checkout --to".
+
+Because in this mode $GIT_DIR becomes a lightweight virtual file
+system where a path could be rewritten to some place else, accessing
+$GIT_DIR from scripts should use `git rev-parse --git-path` to resolve
+a path instead of using it directly unless the path is known to be
+private to the working directory.
+
 EXAMPLES
 --------
 
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index 9bfe0f1..495a937 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -218,6 +218,9 @@ commondir::
 modules::
 	Contains the git-repositories of the submodules.
 
+repos::
+	Contains worktree specific information of linked checkouts.
+
 SEE ALSO
 --------
 linkgit:git-init[1],
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 0570e41..2b856a6 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -48,6 +48,10 @@ struct checkout_opts {
 	const char *prefix;
 	struct pathspec pathspec;
 	struct tree *source_tree;
+
+	const char *new_worktree;
+	const char **saved_argv;
+	int new_worktree_mode;
 };
 
 static int post_checkout_hook(struct commit *old, struct commit *new,
@@ -250,6 +254,9 @@ static int checkout_paths(const struct checkout_opts *opts,
 		die(_("Cannot update paths and switch to branch '%s' at the same time."),
 		    opts->new_branch);
 
+	if (opts->new_worktree)
+		die(_("'%s' cannot be used with updating paths"), "--to");
+
 	if (opts->patch_mode)
 		return run_add_interactive(revision, "--patch=checkout",
 					   &opts->pathspec);
@@ -486,7 +493,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
 			topts.dir->flags |= DIR_SHOW_IGNORED;
 			setup_standard_excludes(topts.dir);
 		}
-		tree = parse_tree_indirect(old->commit ?
+		tree = parse_tree_indirect(old->commit && !opts->new_worktree_mode ?
 					   old->commit->object.sha1 :
 					   EMPTY_TREE_SHA1_BIN);
 		init_tree_desc(&trees[0], tree->buffer, tree->size);
@@ -796,7 +803,8 @@ static int switch_branches(const struct checkout_opts *opts,
 		return ret;
 	}
 
-	if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
+	if (!opts->quiet && !old.path && old.commit &&
+	    new->commit != old.commit && !opts->new_worktree_mode)
 		orphaned_commit_warning(old.commit, new->commit);
 
 	update_refs_for_switch(opts, &old, new);
@@ -806,6 +814,74 @@ static int switch_branches(const struct checkout_opts *opts,
 	return ret || writeout_error;
 }
 
+static int prepare_linked_checkout(const struct checkout_opts *opts,
+				   struct branch_info *new)
+{
+	struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
+	struct strbuf sb = STRBUF_INIT;
+	const char *path = opts->new_worktree;
+	struct stat st;
+	const char *name;
+	struct child_process cp;
+	int counter = 0, len;
+
+	if (!new->commit)
+		die(_("no branch specified"));
+
+	len = strlen(path);
+	if (!len || is_dir_sep(path[len - 1]))
+		die(_("'--to' argument '%s' cannot end with a slash"), path);
+
+	for (name = path + len - 1; name > path; name--)
+		if (is_dir_sep(*name)) {
+			name++;
+			break;
+		}
+	strbuf_addstr(&sb_repo, git_path("repos/%s", name));
+	len = sb_repo.len;
+	if (safe_create_leading_directories_const(sb_repo.buf))
+		die_errno(_("could not create leading directories of '%s'"),
+			  sb_repo.buf);
+	while (!stat(sb_repo.buf, &st)) {
+		counter++;
+		strbuf_setlen(&sb_repo, len);
+		strbuf_addf(&sb_repo, "%d", counter);
+	}
+	name = sb_repo.buf + len - strlen(name);
+	if (mkdir(sb_repo.buf, 0777))
+		die_errno(_("could not create directory of '%s'"), sb_repo.buf);
+
+	strbuf_addf(&sb_git, "%s/.git", path);
+	if (safe_create_leading_directories_const(sb_git.buf))
+		die_errno(_("could not create leading directories of '%s'"),
+			  sb_git.buf);
+
+	write_file(sb_git.buf, 1, "gitdir: %s/repos/%s\n",
+		   real_path(get_git_dir()), name);
+	/*
+	 * This is to keep resolve_ref() happy. We need a valid HEAD
+	 * or is_git_directory() will reject the directory. Any valid
+	 * value would do because this value will be ignored and
+	 * replaced at the next (real) checkout.
+	 */
+	strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
+	write_file(sb.buf, 1, "%s\n", sha1_to_hex(new->commit->object.sha1));
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
+	write_file(sb.buf, 1, "../..\n");
+
+	if (!opts->quiet)
+		fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
+
+	setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1);
+	setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1);
+	setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1);
+	memset(&cp, 0, sizeof(cp));
+	cp.git_cmd = 1;
+	cp.argv = opts->saved_argv;
+	return run_command(&cp);
+}
+
 static int git_checkout_config(const char *var, const char *value, void *cb)
 {
 	if (!strcmp(var, "diff.ignoresubmodules")) {
@@ -1067,6 +1143,9 @@ static int checkout_branch(struct checkout_opts *opts,
 		die(_("Cannot switch branch to a non-commit '%s'"),
 		    new->name);
 
+	if (opts->new_worktree)
+		return prepare_linked_checkout(opts, new);
+
 	if (!new->commit && opts->new_branch) {
 		unsigned char rev[20];
 		int flag;
@@ -1109,6 +1188,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			 N_("do not limit pathspecs to sparse entries only")),
 		OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
 				N_("second guess 'git checkout no-such-branch'")),
+		OPT_STRING(0, "to", &opts.new_worktree, N_("path"),
+			   N_("check a branch out in a separate working directory")),
 		OPT_END(),
 	};
 
@@ -1117,6 +1198,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts.overwrite_ignore = 1;
 	opts.prefix = prefix;
 
+	opts.saved_argv = xmalloc(sizeof(const char *) * (argc + 2));
+	memcpy(opts.saved_argv, argv, sizeof(const char *) * (argc + 1));
+
 	gitmodules_config();
 	git_config(git_checkout_config, &opts);
 
@@ -1125,6 +1209,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
+	/* recursive execution from checkout_new_worktree() */
+	opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL;
+	if (opts.new_worktree_mode)
+		opts.new_worktree = NULL;
+
 	if (conflict_style) {
 		opts.merge = 1; /* implied */
 		git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
diff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh
new file mode 100755
index 0000000..76eae4a
--- /dev/null
+++ b/t/t2025-checkout-to.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+test_description='test git checkout --to'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit init
+'
+
+test_expect_success 'checkout --to not updating paths' '
+	test_must_fail git checkout --to -- init.t
+'
+
+test_expect_success 'checkout --to a new worktree' '
+	git checkout --to here master &&
+	(
+		cd here &&
+		test_cmp ../init.t init.t &&
+		git symbolic-ref HEAD >actual &&
+		echo refs/heads/master >expect &&
+		test_cmp expect actual &&
+		git fsck
+	)
+'
+
+test_expect_success 'checkout --to a new worktree creating new branch' '
+	git checkout --to there -b newmaster master &&
+	(
+		cd there &&
+		test_cmp ../init.t init.t &&
+		git symbolic-ref HEAD >actual &&
+		echo refs/heads/newmaster >expect &&
+		test_cmp expect actual &&
+		git fsck
+	)
+'
+
+test_done
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 22/25] checkout: clean up half-prepared directories in --to mode
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (20 preceding siblings ...)
  2014-02-18 13:40 ` [PATCH v3 21/25] checkout: support checking out into a new working directory Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:40 ` Nguyễn Thái Ngọc Duy
  2014-02-18 13:40 ` [PATCH v3 23/25] checkout: detach if the branch is already checked out elsewhere Nguyễn Thái Ngọc Duy
                   ` (4 subsequent siblings)
  26 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:40 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 47 insertions(+), 2 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 2b856a6..f961604 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -20,6 +20,7 @@
 #include "resolve-undo.h"
 #include "submodule.h"
 #include "argv-array.h"
+#include "sigchain.h"
 
 static const char * const checkout_usage[] = {
 	N_("git checkout [options] <branch>"),
@@ -814,6 +815,35 @@ static int switch_branches(const struct checkout_opts *opts,
 	return ret || writeout_error;
 }
 
+static const char *junk_work_tree;
+static const char *junk_git_dir;
+static int is_junk;
+static pid_t junk_pid;
+
+static void remove_junk(void)
+{
+	struct strbuf sb = STRBUF_INIT;
+	if (!is_junk || getpid() != junk_pid)
+		return;
+	if (junk_git_dir) {
+		strbuf_addstr(&sb, junk_git_dir);
+		remove_dir_recursively(&sb, 0);
+		strbuf_reset(&sb);
+	}
+	if (junk_work_tree) {
+		strbuf_addstr(&sb, junk_work_tree);
+		remove_dir_recursively(&sb, 0);
+		strbuf_reset(&sb);
+	}
+}
+
+static void remove_junk_on_signal(int signo)
+{
+	remove_junk();
+	sigchain_pop(signo);
+	raise(signo);
+}
+
 static int prepare_linked_checkout(const struct checkout_opts *opts,
 				   struct branch_info *new)
 {
@@ -823,7 +853,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	struct stat st;
 	const char *name;
 	struct child_process cp;
-	int counter = 0, len;
+	int counter = 0, len, ret;
 
 	if (!new->commit)
 		die(_("no branch specified"));
@@ -848,13 +878,21 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 		strbuf_addf(&sb_repo, "%d", counter);
 	}
 	name = sb_repo.buf + len - strlen(name);
+
+	junk_pid = getpid();
+	atexit(remove_junk);
+	sigchain_push_common(remove_junk_on_signal);
+
 	if (mkdir(sb_repo.buf, 0777))
 		die_errno(_("could not create directory of '%s'"), sb_repo.buf);
+	junk_git_dir = sb_repo.buf;
+	is_junk = 1;
 
 	strbuf_addf(&sb_git, "%s/.git", path);
 	if (safe_create_leading_directories_const(sb_git.buf))
 		die_errno(_("could not create leading directories of '%s'"),
 			  sb_git.buf);
+	junk_work_tree = path;
 
 	write_file(sb_git.buf, 1, "gitdir: %s/repos/%s\n",
 		   real_path(get_git_dir()), name);
@@ -879,7 +917,14 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	memset(&cp, 0, sizeof(cp));
 	cp.git_cmd = 1;
 	cp.argv = opts->saved_argv;
-	return run_command(&cp);
+	ret = run_command(&cp);
+	if (!ret)
+		is_junk = 0;
+	strbuf_release(&sb);
+	strbuf_release(&sb_repo);
+	strbuf_release(&sb_git);
+	return ret;
+
 }
 
 static int git_checkout_config(const char *var, const char *value, void *cb)
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 23/25] checkout: detach if the branch is already checked out elsewhere
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (21 preceding siblings ...)
  2014-02-18 13:40 ` [PATCH v3 22/25] checkout: clean up half-prepared directories in --to mode Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:40 ` Nguyễn Thái Ngọc Duy
  2014-02-19 20:20   ` Junio C Hamano
  2014-02-19 21:52   ` Eric Sunshine
  2014-02-18 13:40 ` [PATCH v3 24/25] prune: strategies for linked checkouts Nguyễn Thái Ngọc Duy
                   ` (3 subsequent siblings)
  26 siblings, 2 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:40 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

The normal rule is anything outside refs/heads/ is detached. This
strictens the rule a bit more: if the branch is checked out (either in
$GIT_COMMON_DIR/HEAD or any $GIT_DIR/repos/.../HEAD) then it's
detached as well.

A hint is given so the user knows where to go and do something there
if they still want to checkout undetached here.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c     | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++
 t/t2025-checkout-to.sh | 15 ++++++++--
 2 files changed, 90 insertions(+), 3 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index f961604..7b86f2b 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -433,6 +433,11 @@ struct branch_info {
 	const char *name; /* The short name used */
 	const char *path; /* The full name of a real branch */
 	struct commit *commit; /* The named commit */
+	/*
+	 * if not null the branch is detached because it's alrady
+	 * checked out in this checkout
+	 */
+	char *checkout;
 };
 
 static void setup_branch_path(struct branch_info *branch)
@@ -640,6 +645,11 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 			if (old->path && advice_detached_head)
 				detach_advice(new->name);
 			describe_detached_head(_("HEAD is now at"), new->commit);
+			if (new->checkout && !*new->checkout)
+				fprintf(stderr, _("hint: the main checkout is holding this branch\n"));
+			else if (new->checkout)
+				fprintf(stderr, _("hint: the linked checkout %s is holding this branch\n"),
+					new->checkout);
 		}
 	} else if (new->path) {	/* Switch branches. */
 		create_symref("HEAD", new->path, msg.buf);
@@ -982,6 +992,71 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1)
 	return NULL;
 }
 
+static int check_linked_checkout(struct branch_info *new,
+				  const char *name, const char *path)
+{
+	struct strbuf sb = STRBUF_INIT;
+	char *start, *end;
+	if (strbuf_read_file(&sb, path, 0) < 0)
+		return 0;
+	if (!starts_with(sb.buf, "ref:")) {
+		strbuf_release(&sb);
+		return 0;
+	}
+
+	start = sb.buf + 4;
+	while (isspace(*start))
+		start++;
+	end = start;
+	while (*end && !isspace(*end))
+		end++;
+	if (!strncmp(start, new->path, end - start) &&
+	    new->path[end - start] == '\0') {
+		strbuf_release(&sb);
+		new->path = NULL; /* detach */
+		new->checkout = xstrdup(name); /* reason */
+		return 1;
+	}
+	strbuf_release(&sb);
+	return 0;
+}
+
+static void check_linked_checkouts(struct branch_info *new)
+{
+	struct strbuf path = STRBUF_INIT;
+	DIR *dir;
+	struct dirent *d;
+
+	strbuf_addf(&path, "%s/repos", get_git_common_dir());
+	if ((dir = opendir(path.buf)) == NULL)
+		return;
+
+	strbuf_reset(&path);
+	strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
+	/*
+	 * $GIT_COMMON_DIR/HEAD is practically outside
+	 * $GIT_DIR so resolve_ref_unsafe() won't work (it
+	 * uses git_path). Parse the ref ourselves.
+	 */
+	if (check_linked_checkout(new, "", path.buf)) {
+		strbuf_release(&path);
+		closedir(dir);
+		return;
+	}
+
+	while ((d = readdir(dir)) != NULL) {
+		if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+			continue;
+		strbuf_reset(&path);
+		strbuf_addf(&path, "%s/repos/%s/HEAD",
+			    get_git_common_dir(), d->d_name);
+		if (check_linked_checkout(new, d->d_name, path.buf))
+			break;
+	}
+	strbuf_release(&path);
+	closedir(dir);
+}
+
 static int parse_branchname_arg(int argc, const char **argv,
 				int dwim_new_local_branch_ok,
 				struct branch_info *new,
@@ -1109,6 +1184,9 @@ static int parse_branchname_arg(int argc, const char **argv,
 	else
 		new->path = NULL; /* not an existing branch */
 
+	if (new->path)
+		check_linked_checkouts(new);
+
 	new->commit = lookup_commit_reference_gently(rev, 1);
 	if (!new->commit) {
 		/* not a commit */
diff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh
index 76eae4a..f6a5c47 100755
--- a/t/t2025-checkout-to.sh
+++ b/t/t2025-checkout-to.sh
@@ -13,13 +13,14 @@ test_expect_success 'checkout --to not updating paths' '
 '
 
 test_expect_success 'checkout --to a new worktree' '
+	git rev-parse HEAD >expect &&
 	git checkout --to here master &&
 	(
 		cd here &&
 		test_cmp ../init.t init.t &&
-		git symbolic-ref HEAD >actual &&
-		echo refs/heads/master >expect &&
-		test_cmp expect actual &&
+		! git symbolic-ref HEAD &&
+		git rev-parse HEAD >actual &&
+		test_cmp ../expect actual &&
 		git fsck
 	)
 '
@@ -36,4 +37,12 @@ test_expect_success 'checkout --to a new worktree creating new branch' '
 	)
 '
 
+test_expect_success 'detach if the same branch is already checked out' '
+	(
+		cd here &&
+		git checkout newmaster &&
+		test_must_fail git symbolic-ref HEAD
+	)
+'
+
 test_done
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 24/25] prune: strategies for linked checkouts
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (22 preceding siblings ...)
  2014-02-18 13:40 ` [PATCH v3 23/25] checkout: detach if the branch is already checked out elsewhere Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:40 ` Nguyễn Thái Ngọc Duy
  2014-02-19 20:32   ` Junio C Hamano
  2014-02-19 22:08   ` Eric Sunshine
  2014-02-18 13:40 ` [PATCH v3 25/25] gc: support prune --repos Nguyễn Thái Ngọc Duy
                   ` (2 subsequent siblings)
  26 siblings, 2 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:40 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

alias REPO=$GIT_COMMON_DIR/repos/<id>

 - linked checkouts are supposed to update mtime of $REPO/gitdir

 - linked checkouts are supposed to keep its location in $REPO/gitdir up to
   date

 - "git checkout --to" is supposed to create $REPO/locked if the new
   repo is on a different partition than the shared one.

 - "git checkout --to" is supposed to make hardlink named $REPO/link
   pointing to the .git file

The pruning rules are:

 - if $REPO/locked exists, repos/<id> is not supposed to be pruned.

 - if $REPO/locked exists and $REPO/gitdir's mtimer is older than a
   really long limit, warn about old unused repo.

 - if $REPO/link exists and its link count is greated than 1, the repo
   is kept.

 - if $REPO/gitdir's mtime is older than a limit, and it points to
   nowhere, repos/<id> is to be pruned.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-prune.txt            |  3 ++
 Documentation/gitrepository-layout.txt | 19 +++++++++
 builtin/checkout.c                     | 36 +++++++++++++++-
 builtin/prune.c                        | 75 ++++++++++++++++++++++++++++++++++
 setup.c                                | 13 ++++++
 5 files changed, 145 insertions(+), 1 deletion(-)

diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt
index 058ac0d..7babf11 100644
--- a/Documentation/git-prune.txt
+++ b/Documentation/git-prune.txt
@@ -48,6 +48,9 @@ OPTIONS
 --expire <time>::
 	Only expire loose objects older than <time>.
 
+--repos::
+	Prune directories in $GIT_DIR/repos.
+
 <head>...::
 	In addition to objects
 	reachable from any of our references, keep objects
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index 495a937..784d0a5 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -221,6 +221,25 @@ modules::
 repos::
 	Contains worktree specific information of linked checkouts.
 
+repos/<id>/gitdir::
+	A text file containing the absolute path back to the .git file
+	that points to here. This is used to check if the linked
+	repository has been manually removed and there is no need to
+	keep this directory any more. mtime of this file should be
+	updated every time the linked repository is accessed.
+
+repos/<id>/locked::
+	If this file exists, the linked repository may be on a
+	portable device and not available. It does not mean that the
+	linked repository is gone and `repos/<id>` could be
+	removed. The file's content contains a reason string on why
+	the repository is locked.
+
+repos/<id>/link::
+	If this file exists, it is a hard link to the linked .git
+	file. It is used to detect if the linked repository is
+	manually removed.
+
 SEE ALSO
 --------
 linkgit:git-init[1],
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 7b86f2b..c501e9c 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -854,6 +854,17 @@ static void remove_junk_on_signal(int signo)
 	raise(signo);
 }
 
+static dev_t get_device_or_die(const char *path)
+{
+	struct stat buf;
+	if (stat(path, &buf))
+		die_errno("failed to stat '%s'", path);
+	/* Ah Windows! Make different drives different "partitions" */
+	if (buf.st_dev == 0 && has_dos_drive_prefix("c:\\"))
+		buf.st_dev = toupper(real_path(path)[0]);
+	return buf.st_dev;
+}
+
 static int prepare_linked_checkout(const struct checkout_opts *opts,
 				   struct branch_info *new)
 {
@@ -863,7 +874,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	struct stat st;
 	const char *name;
 	struct child_process cp;
-	int counter = 0, len, ret;
+	int counter = 0, len, keep_locked = 0, ret;
 
 	if (!new->commit)
 		die(_("no branch specified"));
@@ -898,12 +909,18 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	junk_git_dir = sb_repo.buf;
 	is_junk = 1;
 
+	strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+	write_file(sb.buf, 1, "initializing\n");
+
 	strbuf_addf(&sb_git, "%s/.git", path);
 	if (safe_create_leading_directories_const(sb_git.buf))
 		die_errno(_("could not create leading directories of '%s'"),
 			  sb_git.buf);
 	junk_work_tree = path;
 
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
+	write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf));
 	write_file(sb_git.buf, 1, "gitdir: %s/repos/%s\n",
 		   real_path(get_git_dir()), name);
 	/*
@@ -912,12 +929,24 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	 * value would do because this value will be ignored and
 	 * replaced at the next (real) checkout.
 	 */
+	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
 	write_file(sb.buf, 1, "%s\n", sha1_to_hex(new->commit->object.sha1));
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
 	write_file(sb.buf, 1, "../..\n");
 
+	if (get_device_or_die(path) != get_device_or_die(get_git_dir())) {
+		strbuf_reset(&sb);
+		strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+		write_file(sb.buf, 1, "located on a different file system\n");
+		keep_locked = 1;
+	} else {
+		strbuf_reset(&sb);
+		strbuf_addf(&sb, "%s/link", sb_repo.buf);
+		link(sb_git.buf, sb.buf); /* it's ok to fail */
+	}
+
 	if (!opts->quiet)
 		fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
 
@@ -930,6 +959,11 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	ret = run_command(&cp);
 	if (!ret)
 		is_junk = 0;
+	if (!keep_locked) {
+		strbuf_reset(&sb);
+		strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+		unlink_or_warn(sb.buf);
+	}
 	strbuf_release(&sb);
 	strbuf_release(&sb_repo);
 	strbuf_release(&sb_git);
diff --git a/builtin/prune.c b/builtin/prune.c
index de43b26..87a6d21 100644
--- a/builtin/prune.c
+++ b/builtin/prune.c
@@ -112,6 +112,70 @@ static void prune_object_dir(const char *path)
 	}
 }
 
+static const char *prune_repo_dir(const char *id, struct stat *st)
+{
+	char *path;
+	int fd, len;
+	if (file_exists(git_path("repos/%s/locked", id)))
+		return NULL;
+	if (stat(git_path("repos/%s/gitdir", id), st)) {
+		st->st_mtime = expire;
+		return _("gitdir does not exist");
+	}
+	fd = open(git_path("repos/%s/gitdir", id), O_RDONLY);
+	len = st->st_size;
+	path = xmalloc(len + 1);
+	read_in_full(fd, path, len);
+	close(fd);
+	while (path[len - 1] == '\n' || path[len - 1] == '\r')
+		len--;
+	path[len] = '\0';
+	if (!file_exists(path)) {
+		struct stat st_link;
+		free(path);
+		/*
+		 * the repo is moved manually and has not been
+		 * accessed since?
+		 */
+		if (!stat(git_path("repos/%s/link", id), &st_link) &&
+		    st_link.st_nlink > 1)
+			return NULL;
+		return _("gitdir points to non-existing file");
+	}
+	free(path);
+	return NULL;
+}
+
+static void prune_repos_dir(void)
+{
+	const char *reason;
+	DIR *dir = opendir(git_path("repos"));
+	struct dirent *d;
+	int removed = 0;
+	struct stat st;
+	if (!dir)
+		return;
+	while ((d = readdir(dir)) != NULL) {
+		if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+			continue;
+		if ((reason = prune_repo_dir(d->d_name, &st)) != NULL &&
+		    st.st_mtime <= expire) {
+			struct strbuf sb = STRBUF_INIT;
+			if (show_only || verbose)
+				printf(_("Removing repos/%s: %s\n"), d->d_name, reason);
+			if (show_only)
+				continue;
+			strbuf_addstr(&sb, git_path("repos/%s", d->d_name));
+			remove_dir_recursively(&sb, 0);
+			strbuf_release(&sb);
+			removed = 1;
+		}
+	}
+	closedir(dir);
+	if (removed)
+		rmdir(git_path("repos"));
+}
+
 /*
  * Write errors (particularly out of space) can result in
  * failed temporary packs (and more rarely indexes and other
@@ -138,10 +202,12 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
 {
 	struct rev_info revs;
 	struct progress *progress = NULL;
+	int prune_repos = 0;
 	const struct option options[] = {
 		OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
 		OPT__VERBOSE(&verbose, N_("report pruned objects")),
 		OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
+		OPT_BOOL(0, "repos", &prune_repos, N_("prune .git/repos/")),
 		OPT_EXPIRY_DATE(0, "expire", &expire,
 				N_("expire objects older than <time>")),
 		OPT_END()
@@ -154,6 +220,14 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
 	init_revisions(&revs, prefix);
 
 	argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
+
+	if (prune_repos) {
+		if (argc)
+			die(_("--repos does not take extra arguments"));
+		prune_repos_dir();
+		return 0;
+	}
+
 	while (argc--) {
 		unsigned char sha1[20];
 		const char *name = *argv++;
@@ -166,6 +240,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
 			die("unrecognized argument: %s", name);
 	}
 
+
 	if (show_progress == -1)
 		show_progress = isatty(2);
 	if (show_progress)
diff --git a/setup.c b/setup.c
index d4ac878..2772cdd 100644
--- a/setup.c
+++ b/setup.c
@@ -329,6 +329,17 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 	return ret;
 }
 
+static void update_linked_gitdir(const char *gitfile, const char *gitdir)
+{
+	struct strbuf path = STRBUF_INIT;
+	struct stat st;
+
+	strbuf_addf(&path, "%s/gitfile", gitdir);
+	if (stat(path.buf, &st) || st.st_mtime + 24 * 3600 < time(NULL))
+		write_file(path.buf, 0, "%s\n", gitfile);
+	strbuf_release(&path);
+}
+
 /*
  * Try to read the location of the git directory from the .git file,
  * return path to git directory if found.
@@ -377,6 +388,8 @@ const char *read_gitfile(const char *path)
 
 	if (!is_git_directory(dir))
 		die("Not a git repository: %s", dir);
+
+	update_linked_gitdir(path, dir);
 	path = real_path(dir);
 
 	free(buf);
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v3 25/25] gc: support prune --repos
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (23 preceding siblings ...)
  2014-02-18 13:40 ` [PATCH v3 24/25] prune: strategies for linked checkouts Nguyễn Thái Ngọc Duy
@ 2014-02-18 13:40 ` Nguyễn Thái Ngọc Duy
  2014-02-19 20:22   ` Junio C Hamano
  2014-02-19 20:57 ` [PATCH v3 00/25] Support multiple checkouts Junio C Hamano
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
  26 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-02-18 13:40 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/config.txt |  6 ++++++
 builtin/gc.c             | 17 +++++++++++++++++
 2 files changed, 23 insertions(+)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index cbf4d97..eec2d05 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1182,6 +1182,12 @@ gc.pruneexpire::
 	"now" may be used to disable this  grace period and always prune
 	unreachable objects immediately.
 
+gc.prunereposexpire::
+	When 'git gc' is run, it will call 'prune --repos --expire 3.months.ago'.
+	Override the grace period with this config variable.  The value
+	"now" may be used to disable this  grace period and always prune
+	$GIT_DIR/repos immediately.
+
 gc.reflogexpire::
 gc.<pattern>.reflogexpire::
 	'git reflog expire' removes reflog entries older than
diff --git a/builtin/gc.c b/builtin/gc.c
index c19545d..fbeba86 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -30,11 +30,13 @@ static int aggressive_window = 250;
 static int gc_auto_threshold = 6700;
 static int gc_auto_pack_limit = 50;
 static const char *prune_expire = "2.weeks.ago";
+static const char *prune_repos_expire = "3.months.ago";
 
 static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT;
 static struct argv_array reflog = ARGV_ARRAY_INIT;
 static struct argv_array repack = ARGV_ARRAY_INIT;
 static struct argv_array prune = ARGV_ARRAY_INIT;
+static struct argv_array prune_repos = ARGV_ARRAY_INIT;
 static struct argv_array rerere = ARGV_ARRAY_INIT;
 
 static char *pidfile;
@@ -81,6 +83,14 @@ static int gc_config(const char *var, const char *value, void *cb)
 		}
 		return git_config_string(&prune_expire, var, value);
 	}
+	if (!strcmp(var, "gc.prunereposexpire")) {
+		if (value && strcmp(value, "now")) {
+			unsigned long now = approxidate("now");
+			if (approxidate(value) >= now)
+				return error(_("Invalid %s: '%s'"), var, value);
+		}
+		return git_config_string(&prune_repos_expire, var, value);
+	}
 	return git_default_config(var, value, cb);
 }
 
@@ -274,6 +284,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
 	argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
 	argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
 	argv_array_pushl(&prune, "prune", "--expire", NULL );
+	argv_array_pushl(&prune_repos, "prune", "--repos", "--expire", NULL );
 	argv_array_pushl(&rerere, "rerere", "gc", NULL);
 
 	git_config(gc_config, NULL);
@@ -334,6 +345,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
 			return error(FAILED_RUN, prune.argv[0]);
 	}
 
+	if (prune_repos_expire) {
+		argv_array_push(&prune_repos, prune_repos_expire);
+		if (run_command_v_opt(prune_repos.argv, RUN_GIT_CMD))
+			return error(FAILED_RUN, prune_repos.argv[0]);
+	}
+
 	if (run_command_v_opt(rerere.argv, RUN_GIT_CMD))
 		return error(FAILED_RUN, rerere.argv[0]);
 
-- 
1.8.5.2.240.g8478abd

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 14/25] setup.c: convert is_git_directory() to use strbuf
  2014-02-18 13:40 ` [PATCH v3 14/25] setup.c: convert is_git_directory() to use strbuf Nguyễn Thái Ngọc Duy
@ 2014-02-19 20:17   ` Junio C Hamano
  2014-02-20 13:04     ` Duy Nguyen
  2014-03-01  2:50     ` [PATCH] strbuf: style fix -- top opening bracket on a separate line Nguyễn Thái Ngọc Duy
  2014-02-21  3:38   ` [PATCH v3 14/25] setup.c: convert is_git_directory() to use strbuf Eric Sunshine
  1 sibling, 2 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-02-19 20:17 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---

(Only minor nits first during this round of review)

> diff --git a/strbuf.h b/strbuf.h
> index 73e80ce..aec9fdb 100644
> --- a/strbuf.h
> +++ b/strbuf.h
> @@ -116,6 +116,10 @@ extern void strbuf_add(struct strbuf *, const void *, size_t);
>  static inline void strbuf_addstr(struct strbuf *sb, const char *s) {
>  	strbuf_add(sb, s, strlen(s));
>  }
> +static inline void strbuf_addstr_at(struct strbuf *sb, size_t len, const char *s) {

Please have the opening "{" on its own line.

Surrounding existing functions are all offenders, but that is not an
excuse to make it worse (cleaning them up will need to be done in a
separate patch).

> +	strbuf_setlen(sb, len);
> +	strbuf_add(sb, s, strlen(s));

I am not sure addstr_at() gives us a good abstraction, or at least
the name conveys what it does well not to confuse readers.

At first after only seeing its name, I would have expected that it
would splice the given string into an existing strbuf at the
location, not chopping the existing strbuf at the location and
appending.

> +}
>  static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) {
>  	strbuf_grow(sb, sb2->len);
>  	strbuf_add(sb, sb2->buf, sb2->len);

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 18/25] setup.c: support multi-checkout repo setup
  2014-02-18 13:40 ` [PATCH v3 18/25] setup.c: support multi-checkout repo setup Nguyễn Thái Ngọc Duy
@ 2014-02-19 20:19   ` Junio C Hamano
  2014-02-27 20:28   ` Junio C Hamano
  2014-03-24 14:52   ` Torsten Bögershausen
  2 siblings, 0 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-02-19 20:19 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

(Only nitpicks during this round of review).

> diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh
> index 8f36aa9..d8bdaf4 100755
> --- a/t/t1501-worktree.sh
> +++ b/t/t1501-worktree.sh
> @@ -346,4 +346,80 @@ test_expect_success 'relative $GIT_WORK_TREE and git subprocesses' '
>  	test_cmp expected actual
>  '
>  
> +test_expect_success 'Multi-worktree setup' '
> +	mkdir work &&
> +	mkdir -p repo.git/repos/foo &&
> +	cp repo.git/HEAD repo.git/index repo.git/repos/foo &&
> +	unset GIT_DIR GIT_CONFIG GIT_WORK_TREE

Are these known to be set always when we get to this point?
Otherwise please use sane_unset.

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 23/25] checkout: detach if the branch is already checked out elsewhere
  2014-02-18 13:40 ` [PATCH v3 23/25] checkout: detach if the branch is already checked out elsewhere Nguyễn Thái Ngọc Duy
@ 2014-02-19 20:20   ` Junio C Hamano
  2014-02-19 21:52   ` Eric Sunshine
  1 sibling, 0 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-02-19 20:20 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> The normal rule is anything outside refs/heads/ is detached. This
> strictens the rule a bit more: if the branch is checked out (either in
> $GIT_COMMON_DIR/HEAD or any $GIT_DIR/repos/.../HEAD) then it's
> detached as well.
>
> A hint is given so the user knows where to go and do something there
> if they still want to checkout undetached here.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>

(Only nitpicks during this round of review).

> diff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh
> index 76eae4a..f6a5c47 100755
> --- a/t/t2025-checkout-to.sh
> +++ b/t/t2025-checkout-to.sh
> @@ -13,13 +13,14 @@ test_expect_success 'checkout --to not updating paths' '
>  '
>  
>  test_expect_success 'checkout --to a new worktree' '
> +	git rev-parse HEAD >expect &&
>  	git checkout --to here master &&
>  	(
>  		cd here &&
>  		test_cmp ../init.t init.t &&
> -		git symbolic-ref HEAD >actual &&
> -		echo refs/heads/master >expect &&
> -		test_cmp expect actual &&
> +		! git symbolic-ref HEAD &&

test_must_fail?

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 25/25] gc: support prune --repos
  2014-02-18 13:40 ` [PATCH v3 25/25] gc: support prune --repos Nguyễn Thái Ngọc Duy
@ 2014-02-19 20:22   ` Junio C Hamano
  0 siblings, 0 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-02-19 20:22 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

(Only nitpicks during this round of review).

> @@ -274,6 +284,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
>  	argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
>  	argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
>  	argv_array_pushl(&prune, "prune", "--expire", NULL );
> +	argv_array_pushl(&prune_repos, "prune", "--repos", "--expire", NULL );

No SP before closing ")".

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 01/25] path.c: make get_pathname() return strbuf instead of static buffer
  2014-02-18 13:39 ` [PATCH v3 01/25] path.c: make get_pathname() return strbuf instead of static buffer Nguyễn Thái Ngọc Duy
@ 2014-02-19 20:28   ` Junio C Hamano
  2014-02-19 23:26   ` Junio C Hamano
  1 sibling, 0 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-02-19 20:28 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

(Only nitpicks during this round of review).

> -static char *get_pathname(void)
> +static struct strbuf *get_pathname()

static struct strbuf *get_pathname(void)

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 24/25] prune: strategies for linked checkouts
  2014-02-18 13:40 ` [PATCH v3 24/25] prune: strategies for linked checkouts Nguyễn Thái Ngọc Duy
@ 2014-02-19 20:32   ` Junio C Hamano
  2014-02-19 20:42     ` Junio C Hamano
  2014-02-20 13:15     ` Duy Nguyen
  2014-02-19 22:08   ` Eric Sunshine
  1 sibling, 2 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-02-19 20:32 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

(Only nitpicks during this round of review).

> +	if (get_device_or_die(path) != get_device_or_die(get_git_dir())) {
> +		strbuf_reset(&sb);
> +		strbuf_addf(&sb, "%s/locked", sb_repo.buf);
> +		write_file(sb.buf, 1, "located on a different file system\n");
> +		keep_locked = 1;
> +	} else {
> +		strbuf_reset(&sb);
> +		strbuf_addf(&sb, "%s/link", sb_repo.buf);
> +		link(sb_git.buf, sb.buf); /* it's ok to fail */

If so, perhaps tell that to the code by saying something like

		(void) link(...);

?

But why is it OK to fail in the first place?  If we couldn't link,
don't you want to fall back to the lock codepath?  After all, the
"are we on the same device?" check is to cope with the case where
we cannot link and that alternate codepath is supposed to be
prepared to handle the "ah, we cannot link" case correctly, no?

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 24/25] prune: strategies for linked checkouts
  2014-02-19 20:32   ` Junio C Hamano
@ 2014-02-19 20:42     ` Junio C Hamano
  2014-02-20 13:15     ` Duy Nguyen
  1 sibling, 0 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-02-19 20:42 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Junio C Hamano <gitster@pobox.com> writes:

> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
>
> (Only nitpicks during this round of review).
>
>> +	if (get_device_or_die(path) != get_device_or_die(get_git_dir())) {
>> +		strbuf_reset(&sb);
>> +		strbuf_addf(&sb, "%s/locked", sb_repo.buf);
>> +		write_file(sb.buf, 1, "located on a different file system\n");
>> +		keep_locked = 1;
>> +	} else {
>> +		strbuf_reset(&sb);
>> +		strbuf_addf(&sb, "%s/link", sb_repo.buf);
>> +		link(sb_git.buf, sb.buf); /* it's ok to fail */
>
> If so, perhaps tell that to the code by saying something like
>
> 		(void) link(...);
>
> ?
>
> But why is it OK to fail in the first place?  If we couldn't link,
> don't you want to fall back to the lock codepath?  After all, the
> "are we on the same device?" check is to cope with the case where
> we cannot link and that alternate codepath is supposed to be
> prepared to handle the "ah, we cannot link" case correctly, no?

In other words, couldn't that part more like this?  That is, we
optimisiticly try the link(2) first and if it fails for whatever
reason fall back to whatever magic the lock codepath implements.

+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s/link", sb_repo.buf);
+	if (link(sb_git.buf, sb.buf) < 0) {
+		strbuf_reset(&sb);
+		strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+		write_file(sb.buf, 1, "located on a different file system\n");
+		keep_locked = 1;
+	}
+

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 00/25] Support multiple checkouts
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (24 preceding siblings ...)
  2014-02-18 13:40 ` [PATCH v3 25/25] gc: support prune --repos Nguyễn Thái Ngọc Duy
@ 2014-02-19 20:57 ` Junio C Hamano
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
  26 siblings, 0 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-02-19 20:57 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> In short you can attach multiple worktrees to the same git repository
> with "git checkout --to <somewhere>". This is basically what
> git-new-workdir is for.

This is exciting.

I'll be pushing this out on 'pu' (with trivial fix-ups squashed in
and/or queued on-top) for people to play with.

I'm still slowly chewing thru these patches, though. Haven't had a
chance to read them carefully yet.

Thanks.

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 23/25] checkout: detach if the branch is already checked out elsewhere
  2014-02-18 13:40 ` [PATCH v3 23/25] checkout: detach if the branch is already checked out elsewhere Nguyễn Thái Ngọc Duy
  2014-02-19 20:20   ` Junio C Hamano
@ 2014-02-19 21:52   ` Eric Sunshine
  1 sibling, 0 replies; 169+ messages in thread
From: Eric Sunshine @ 2014-02-19 21:52 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: Git List

On Tue, Feb 18, 2014 at 8:40 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> The normal rule is anything outside refs/heads/ is detached. This
> strictens the rule a bit more: if the branch is checked out (either in

s/strictens/increases strictness of/

> $GIT_COMMON_DIR/HEAD or any $GIT_DIR/repos/.../HEAD) then it's
> detached as well.
>
> A hint is given so the user knows where to go and do something there
> if they still want to checkout undetached here.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index f961604..7b86f2b 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -433,6 +433,11 @@ struct branch_info {
>         const char *name; /* The short name used */
>         const char *path; /* The full name of a real branch */
>         struct commit *commit; /* The named commit */
> +       /*
> +        * if not null the branch is detached because it's alrady

s/alrady/already/

> +        * checked out in this checkout
> +        */
> +       char *checkout;
>  };
>
> +static void check_linked_checkouts(struct branch_info *new)
> +{
> +       struct strbuf path = STRBUF_INIT;
> +       DIR *dir;
> +       struct dirent *d;
> +
> +       strbuf_addf(&path, "%s/repos", get_git_common_dir());
> +       if ((dir = opendir(path.buf)) == NULL)

strbuf_release(&path);

> +               return;
> +
> +       strbuf_reset(&path);
> +       strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
> +       /*
> +        * $GIT_COMMON_DIR/HEAD is practically outside
> +        * $GIT_DIR so resolve_ref_unsafe() won't work (it
> +        * uses git_path). Parse the ref ourselves.
> +        */
> +       if (check_linked_checkout(new, "", path.buf)) {
> +               strbuf_release(&path);
> +               closedir(dir);
> +               return;
> +       }
> +
> +       while ((d = readdir(dir)) != NULL) {
> +               if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
> +                       continue;
> +               strbuf_reset(&path);
> +               strbuf_addf(&path, "%s/repos/%s/HEAD",
> +                           get_git_common_dir(), d->d_name);
> +               if (check_linked_checkout(new, d->d_name, path.buf))
> +                       break;
> +       }
> +       strbuf_release(&path);
> +       closedir(dir);
> +}

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 24/25] prune: strategies for linked checkouts
  2014-02-18 13:40 ` [PATCH v3 24/25] prune: strategies for linked checkouts Nguyễn Thái Ngọc Duy
  2014-02-19 20:32   ` Junio C Hamano
@ 2014-02-19 22:08   ` Eric Sunshine
  2014-02-19 22:53     ` Eric Sunshine
  2014-02-20 13:19     ` Duy Nguyen
  1 sibling, 2 replies; 169+ messages in thread
From: Eric Sunshine @ 2014-02-19 22:08 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: Git List

On Tue, Feb 18, 2014 at 8:40 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> The pruning rules are:
>
>  - if $REPO/locked exists, repos/<id> is not supposed to be pruned.
>
>  - if $REPO/locked exists and $REPO/gitdir's mtimer is older than a

s/mtimer/mtime/

>    really long limit, warn about old unused repo.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index 7b86f2b..c501e9c 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -854,6 +854,17 @@ static void remove_junk_on_signal(int signo)
>         raise(signo);
>  }
>
> +static dev_t get_device_or_die(const char *path)
> +{
> +       struct stat buf;
> +       if (stat(path, &buf))
> +               die_errno("failed to stat '%s'", path);
> +       /* Ah Windows! Make different drives different "partitions" */
> +       if (buf.st_dev == 0 && has_dos_drive_prefix("c:\\"))
> +               buf.st_dev = toupper(real_path(path)[0]);

This invocation of has_dos_drive_prefix() with hardcoded "c:\\" at
first looks like an error until the reader realizes that it's an
#ifdef-less check if the platforms is Windows. Would it make more
sense to encapsulate this anomaly and abstract it away by fixing
compat/mingw.c:do_lstat() to instead set 'st_dev' automatically like
you do here? In particular, this line in mingw.c:

    buf->st_dev = buf->st_rdev = 0; /* not used by Git */

> +       return buf.st_dev;
> +}

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 24/25] prune: strategies for linked checkouts
  2014-02-19 22:08   ` Eric Sunshine
@ 2014-02-19 22:53     ` Eric Sunshine
  2014-02-20 13:19     ` Duy Nguyen
  1 sibling, 0 replies; 169+ messages in thread
From: Eric Sunshine @ 2014-02-19 22:53 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: Git List

On Wed, Feb 19, 2014 at 5:08 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:
> On Tue, Feb 18, 2014 at 8:40 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
>> +static dev_t get_device_or_die(const char *path)
>> +{
>> +       struct stat buf;
>> +       if (stat(path, &buf))
>> +               die_errno("failed to stat '%s'", path);
>> +       /* Ah Windows! Make different drives different "partitions" */
>> +       if (buf.st_dev == 0 && has_dos_drive_prefix("c:\\"))
>> +               buf.st_dev = toupper(real_path(path)[0]);
>
> This invocation of has_dos_drive_prefix() with hardcoded "c:\\" at
> first looks like an error until the reader realizes that it's an
> #ifdef-less check if the platforms is Windows. Would it make more
> sense to encapsulate this anomaly and abstract it away by fixing
> compat/mingw.c:do_lstat() to instead set 'st_dev' automatically like
> you do here? In particular, this line in mingw.c:
>
>     buf->st_dev = buf->st_rdev = 0; /* not used by Git */
>
>> +       return buf.st_dev;

Or, if doing this in do_lstat() is too expensive for normal stat()
operations (which is likely), then a simple #ifdef might be easier to
read; or abstract it into a get_device() function which Windows/MinGW
can override, doing buf.st_dev = toupper(real_path(...)), thus also
making the code easier to understand.

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 01/25] path.c: make get_pathname() return strbuf instead of static buffer
  2014-02-18 13:39 ` [PATCH v3 01/25] path.c: make get_pathname() return strbuf instead of static buffer Nguyễn Thái Ngọc Duy
  2014-02-19 20:28   ` Junio C Hamano
@ 2014-02-19 23:26   ` Junio C Hamano
  2014-03-01  2:40     ` Duy Nguyen
  1 sibling, 1 reply; 169+ messages in thread
From: Junio C Hamano @ 2014-02-19 23:26 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> We've been avoiding PATH_MAX whenever possible. This patch makes
> get_pathname() return a strbuf and updates the callers to take
> advantage of this. The code is simplified as we no longer need to
> worry about buffer overflow.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  path.c | 119 +++++++++++++++++++++++++++--------------------------------------
>  1 file changed, 50 insertions(+), 69 deletions(-)

Nice.

>  char *git_pathdup(const char *fmt, ...)
>  {
> -	char path[PATH_MAX], *ret;
> +	struct strbuf *path = get_pathname();
>  	va_list args;
>  	va_start(args, fmt);
> -	ret = vsnpath(path, sizeof(path), fmt, args);
> +	vsnpath(path, fmt, args);
>  	va_end(args);
> -	return xstrdup(ret);
> +	return strbuf_detach(path, NULL);
>  }

This feels somewhat wrong.

This function is for callers who are willing to take ownership of
the path buffer and promise to free the returned buffer when they
are done, because you are returning strbuf_detach()'ed piece of
memory, giving the ownership away.

The whole point of using get_pathname() is to allow callers not to
care about allocation issues on the paths they scribble on during
their short-and-simple codepaths that do not have too many uses of
similar temporary path buffers.  Why borrow from that round-robin
pool (which may now cause some codepaths to overflow the number of
such active temporary path buffers---have they been all audited)?

Is there a reason not to do just an equivalent of

    #define git_pathdup mkpathdup

and be done with it?  Am I missing something?

>  char *mkpathdup(const char *fmt, ...)
>  {
> -	char *path;
>  	struct strbuf sb = STRBUF_INIT;
>  	va_list args;
> -
>  	va_start(args, fmt);
>  	strbuf_vaddf(&sb, fmt, args);
>  	va_end(args);
> -	path = xstrdup(cleanup_path(sb.buf));
> -
> -	strbuf_release(&sb);
> -	return path;
> +	strbuf_cleanup_path(&sb);
> +	return strbuf_detach(&sb, NULL);
>  }
>  
>  char *mkpath(const char *fmt, ...)
>  {
>  	va_list args;
> -	unsigned len;
> -	char *pathname = get_pathname();
> -
> +	struct strbuf *pathname = get_pathname();
>  	va_start(args, fmt);
> -	len = vsnprintf(pathname, PATH_MAX, fmt, args);
> +	strbuf_vaddf(pathname, fmt, args);
>  	va_end(args);
> -	if (len >= PATH_MAX)
> -		return bad_path;
> -	return cleanup_path(pathname);
> +	return cleanup_path(pathname->buf);
>  }

On the other hand, this one does seem correct.

>  char *git_path(const char *fmt, ...)
>  {
> -	char *pathname = get_pathname();
> +	struct strbuf *pathname = get_pathname();
>  	va_list args;
> -	char *ret;
> -
>  	va_start(args, fmt);
> -	ret = vsnpath(pathname, PATH_MAX, fmt, args);
> +	vsnpath(pathname, fmt, args);
>  	va_end(args);
> -	return ret;
> +	return pathname->buf;
>  }

So does this.

Thanks.

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 02/25] Convert git_snpath() to strbuf_git_path()
  2014-02-18 13:39 ` [PATCH v3 02/25] Convert git_snpath() to strbuf_git_path() Nguyễn Thái Ngọc Duy
@ 2014-02-19 23:48   ` Junio C Hamano
  2014-02-19 23:54     ` Duy Nguyen
  2014-02-20 19:44   ` Junio C Hamano
  1 sibling, 1 reply; 169+ messages in thread
From: Junio C Hamano @ 2014-02-19 23:48 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> @@ -651,14 +653,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
>  					new->name);
>  			}
>  		}
> -		if (old->path && old->name) {
> -			char log_file[PATH_MAX], ref_file[PATH_MAX];
> -
> -			git_snpath(log_file, sizeof(log_file), "logs/%s", old->path);
> -			git_snpath(ref_file, sizeof(ref_file), "%s", old->path);
> -			if (!file_exists(ref_file) && file_exists(log_file))
> -				remove_path(log_file);
> -		}
> +		if (old->path && old->name &&
> +		    !file_exists(git_path("%s", old->path)) &&
> +		     file_exists(git_path("logs/%s", old->path)))
> +			remove_path(git_path("logs/%s", old->path));

Hmph.  Is this conversion safe?

This adds three uses of the round-robin path buffer; if a caller of
this function used two or more path buffers obtained from
get_pathname() and expected their contents to remain stable across
the call to this, it will silently break.

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 02/25] Convert git_snpath() to strbuf_git_path()
  2014-02-19 23:48   ` Junio C Hamano
@ 2014-02-19 23:54     ` Duy Nguyen
  2014-02-20  3:41       ` Junio C Hamano
  0 siblings, 1 reply; 169+ messages in thread
From: Duy Nguyen @ 2014-02-19 23:54 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git Mailing List

On Thu, Feb 20, 2014 at 6:48 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
>
>> @@ -651,14 +653,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
>>                                       new->name);
>>                       }
>>               }
>> -             if (old->path && old->name) {
>> -                     char log_file[PATH_MAX], ref_file[PATH_MAX];
>> -
>> -                     git_snpath(log_file, sizeof(log_file), "logs/%s", old->path);
>> -                     git_snpath(ref_file, sizeof(ref_file), "%s", old->path);
>> -                     if (!file_exists(ref_file) && file_exists(log_file))
>> -                             remove_path(log_file);
>> -             }
>> +             if (old->path && old->name &&
>> +                 !file_exists(git_path("%s", old->path)) &&
>> +                  file_exists(git_path("logs/%s", old->path)))
>> +                     remove_path(git_path("logs/%s", old->path));
>
> Hmph.  Is this conversion safe?
>
> This adds three uses of the round-robin path buffer; if a caller of
> this function used two or more path buffers obtained from
> get_pathname() and expected their contents to remain stable across
> the call to this, it will silently break.

three round-robin buffers but not all required at the same time, once
the first file_exists() returns the first round-robin buffer could be
free, and remove_path() calls git_path again, not reusing the result
from the second file_exists, so the second buffer is free to go too.
-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 02/25] Convert git_snpath() to strbuf_git_path()
  2014-02-19 23:54     ` Duy Nguyen
@ 2014-02-20  3:41       ` Junio C Hamano
  2014-02-20  3:55         ` Duy Nguyen
  0 siblings, 1 reply; 169+ messages in thread
From: Junio C Hamano @ 2014-02-20  3:41 UTC (permalink / raw)
  To: Duy Nguyen; +Cc: Git Mailing List

Duy Nguyen <pclouds@gmail.com> writes:

>>> -             }
>>> +             if (old->path && old->name &&
>>> +                 !file_exists(git_path("%s", old->path)) &&
>>> +                  file_exists(git_path("logs/%s", old->path)))
>>> +                     remove_path(git_path("logs/%s", old->path));
>>
>> Hmph.  Is this conversion safe?
>>
>> This adds three uses of the round-robin path buffer; if a caller of
>> this function used two or more path buffers obtained from
>> get_pathname() and expected their contents to remain stable across
>> the call to this, it will silently break.
>
> three round-robin buffers but not all required at the same time, once
> the first file_exists() returns the first round-robin buffer could be
> free, and remove_path() calls git_path again, not reusing the result
> from the second file_exists, so the second buffer is free to go too.

I know these three callers to git_path() will not step on each
other's toes.  But that is not the question I asked.

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 02/25] Convert git_snpath() to strbuf_git_path()
  2014-02-20  3:41       ` Junio C Hamano
@ 2014-02-20  3:55         ` Duy Nguyen
  2014-02-20 18:54           ` Junio C Hamano
  0 siblings, 1 reply; 169+ messages in thread
From: Duy Nguyen @ 2014-02-20  3:55 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git Mailing List

On Thu, Feb 20, 2014 at 10:41 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Duy Nguyen <pclouds@gmail.com> writes:
>
>>>> -             }
>>>> +             if (old->path && old->name &&
>>>> +                 !file_exists(git_path("%s", old->path)) &&
>>>> +                  file_exists(git_path("logs/%s", old->path)))
>>>> +                     remove_path(git_path("logs/%s", old->path));
>>>
>>> Hmph.  Is this conversion safe?
>>>
>>> This adds three uses of the round-robin path buffer; if a caller of
>>> this function used two or more path buffers obtained from
>>> get_pathname() and expected their contents to remain stable across
>>> the call to this, it will silently break.
>>
>> three round-robin buffers but not all required at the same time, once
>> the first file_exists() returns the first round-robin buffer could be
>> free, and remove_path() calls git_path again, not reusing the result
>> from the second file_exists, so the second buffer is free to go too.
>
> I know these three callers to git_path() will not step on each
> other's toes.  But that is not the question I asked.

OK so your question was if there was a git_path() or mkpath() call
earlier in update_refs_for_switch() and the result was expected to
remain stable till the end of update_refs_for_switch(), then this
conversion could ruin it, correct? I didn't think about that, but I
have checked and the only mkpath() call in this function is not
supposed to last long. If it's about a git_pathname() call outside
update_refs_...() that still expects to be stable, we should fix that
code instead.
-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 14/25] setup.c: convert is_git_directory() to use strbuf
  2014-02-19 20:17   ` Junio C Hamano
@ 2014-02-20 13:04     ` Duy Nguyen
  2014-02-20 19:06       ` Junio C Hamano
  2014-03-01  2:50     ` [PATCH] strbuf: style fix -- top opening bracket on a separate line Nguyễn Thái Ngọc Duy
  1 sibling, 1 reply; 169+ messages in thread
From: Duy Nguyen @ 2014-02-20 13:04 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git Mailing List

On Thu, Feb 20, 2014 at 3:17 AM, Junio C Hamano <gitster@pobox.com> wrote:
>> +     strbuf_setlen(sb, len);
>> +     strbuf_add(sb, s, strlen(s));
>
> I am not sure addstr_at() gives us a good abstraction, or at least
> the name conveys what it does well not to confuse readers.
>
> At first after only seeing its name, I would have expected that it
> would splice the given string into an existing strbuf at the
> location, not chopping the existing strbuf at the location and
> appending.

I think I invented a few new strbuf_* in this series and this is one
of them. We have about ~14 other places in current code that do
similar pattern: set length back, then add something on top. Not sure
if it's worth a convenient wrapper. I don't know, maybe it's not worth
reducing one line and causing more confusion.

>
>> +}
>>  static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) {
>>       strbuf_grow(sb, sb2->len);
>>       strbuf_add(sb, sb2->buf, sb2->len);



-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 24/25] prune: strategies for linked checkouts
  2014-02-19 20:32   ` Junio C Hamano
  2014-02-19 20:42     ` Junio C Hamano
@ 2014-02-20 13:15     ` Duy Nguyen
  2014-02-20 19:55       ` Junio C Hamano
  1 sibling, 1 reply; 169+ messages in thread
From: Duy Nguyen @ 2014-02-20 13:15 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git Mailing List

On Thu, Feb 20, 2014 at 3:32 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
>
> (Only nitpicks during this round of review).
>
>> +     if (get_device_or_die(path) != get_device_or_die(get_git_dir())) {
>> +             strbuf_reset(&sb);
>> +             strbuf_addf(&sb, "%s/locked", sb_repo.buf);
>> +             write_file(sb.buf, 1, "located on a different file system\n");
>> +             keep_locked = 1;
>> +     } else {
>> +             strbuf_reset(&sb);
>> +             strbuf_addf(&sb, "%s/link", sb_repo.buf);
>> +             link(sb_git.buf, sb.buf); /* it's ok to fail */
>
> If so, perhaps tell that to the code by saying something like
>
>                 (void) link(...);
>
> ?
>
> But why is it OK to fail in the first place?  If we couldn't link,
> don't you want to fall back to the lock codepath?  After all, the
> "are we on the same device?" check is to cope with the case where
> we cannot link and that alternate codepath is supposed to be
> prepared to handle the "ah, we cannot link" case correctly, no?

Filesystems not supporting hardlinks are one reason. The idea behind
"locked" is that the new checkout could be on a portable device and we
don't want to prune its metadata just because the device is not
plugged in. Checking same device help but even that can't guarantee no
false positives, unless your only mount point is /. So no I don't
really think we should go lock whenever link() fails, that would just
make fewer checkouts prunable.
-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 24/25] prune: strategies for linked checkouts
  2014-02-19 22:08   ` Eric Sunshine
  2014-02-19 22:53     ` Eric Sunshine
@ 2014-02-20 13:19     ` Duy Nguyen
  1 sibling, 0 replies; 169+ messages in thread
From: Duy Nguyen @ 2014-02-20 13:19 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: Git List

On Thu, Feb 20, 2014 at 5:08 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
>> +static dev_t get_device_or_die(const char *path)
>> +{
>> +       struct stat buf;
>> +       if (stat(path, &buf))
>> +               die_errno("failed to stat '%s'", path);
>> +       /* Ah Windows! Make different drives different "partitions" */
>> +       if (buf.st_dev == 0 && has_dos_drive_prefix("c:\\"))
>> +               buf.st_dev = toupper(real_path(path)[0]);
>
> This invocation of has_dos_drive_prefix() with hardcoded "c:\\" at
> first looks like an error until the reader realizes that it's an
> #ifdef-less check if the platforms is Windows. Would it make more
> sense to encapsulate this anomaly and abstract it away by fixing
> compat/mingw.c:do_lstat() to instead set 'st_dev' automatically like
> you do here? In particular, this line in mingw.c:
>
>     buf->st_dev = buf->st_rdev = 0; /* not used by Git */
>

I don't want to hide too much magic behind compat curtain, especially
when these magic is just about 90% as real, the rest is mysterious
corner cases. I guess we could just add an inline function
is_windows() that always returns 0 or 1 based on #ifdef.
-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 02/25] Convert git_snpath() to strbuf_git_path()
  2014-02-20  3:55         ` Duy Nguyen
@ 2014-02-20 18:54           ` Junio C Hamano
  0 siblings, 0 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-02-20 18:54 UTC (permalink / raw)
  To: Duy Nguyen; +Cc: Git Mailing List

Duy Nguyen <pclouds@gmail.com> writes:

> OK so your question was if there was a git_path() or mkpath() call
> earlier in update_refs_for_switch() and the result was expected to
> remain stable till the end of update_refs_for_switch(), then this
> conversion could ruin it, correct? I didn't think about that,...

Yeah, I couldn't tell if you thought about it, and that was why I
asked.

If a (recursively) caller does this:

	caller () {
		const char *path1 = git_path(...);
                const char *path2 = mkpath(...);
                const char *path3 = git_path_submodule(...);
		callee();
                use(path1, path2, path3);
	}

it was safe back when the callee() did not mess with the round-robin
pathname buffer, but it will be broken once callee() does.  While
looking at the patch I didn't check what the caller was doing hence
my question.

In general, in order to reduce that kind of hard-to-debug bugs, we
should be reducing the uses of these functions when we do not have
to (which applies equally to such a caller that expects multiple
temporary paths to persist, and to a callee as well).  Adding
multiple repeated calls to git_path(), especially two of them
formatting the same string into two separate round-robin pathname
buffer, looked strange in a patch that was supposed to be a
preparatory code-cleanup stage.

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 14/25] setup.c: convert is_git_directory() to use strbuf
  2014-02-20 13:04     ` Duy Nguyen
@ 2014-02-20 19:06       ` Junio C Hamano
  2014-02-20 19:36         ` Junio C Hamano
  0 siblings, 1 reply; 169+ messages in thread
From: Junio C Hamano @ 2014-02-20 19:06 UTC (permalink / raw)
  To: Duy Nguyen; +Cc: Git Mailing List

Duy Nguyen <pclouds@gmail.com> writes:

> On Thu, Feb 20, 2014 at 3:17 AM, Junio C Hamano <gitster@pobox.com> wrote:
>>> +     strbuf_setlen(sb, len);
>>> +     strbuf_add(sb, s, strlen(s));
>>
>> I am not sure addstr_at() gives us a good abstraction, or at least
>> the name conveys what it does well not to confuse readers.
>>
>> At first after only seeing its name, I would have expected that it
>> would splice the given string into an existing strbuf at the
>> location, not chopping the existing strbuf at the location and
>> appending.
>
> I think I invented a few new strbuf_* in this series and this is one
> of them. We have about ~14 other places in current code that do
> similar pattern: set length back, then add something on top.

Yes, and you can count getline/getwholeline as a special case to
chomp to empty at the beginning.  I am not opposed to a helper to
give us an easy access to this common pattern.

It was just the name "addstr-at" did not sound, at least to me, what
it does, i.e. "replace with s from the pos to the end", which I
think is the same thing as a single-liner:

    strbuf_splice(sb, pos, sb->len - pos, s, strlen(s))

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 14/25] setup.c: convert is_git_directory() to use strbuf
  2014-02-20 19:06       ` Junio C Hamano
@ 2014-02-20 19:36         ` Junio C Hamano
  0 siblings, 0 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-02-20 19:36 UTC (permalink / raw)
  To: Duy Nguyen; +Cc: Git Mailing List

Junio C Hamano <gitster@pobox.com> writes:

> Duy Nguyen <pclouds@gmail.com> writes:
>
>> On Thu, Feb 20, 2014 at 3:17 AM, Junio C Hamano <gitster@pobox.com> wrote:
>>>> +     strbuf_setlen(sb, len);
>>>> +     strbuf_add(sb, s, strlen(s));
>>>
>>> I am not sure addstr_at() gives us a good abstraction, or at least
>>> the name conveys what it does well not to confuse readers.
>>>
>>> At first after only seeing its name, I would have expected that it
>>> would splice the given string into an existing strbuf at the
>>> location, not chopping the existing strbuf at the location and
>>> appending.
>>
>> I think I invented a few new strbuf_* in this series and this is one
>> of them. We have about ~14 other places in current code that do
>> similar pattern: set length back, then add something on top.
>
> Yes, and you can count getline/getwholeline as a special case to
> chomp to empty at the beginning.  I am not opposed to a helper to
> give us an easy access to this common pattern.
>
> It was just the name "addstr-at" did not sound, at least to me, what
> it does, i.e. "replace with s from the pos to the end", which I
> think is the same thing as a single-liner:
>
>     strbuf_splice(sb, pos, sb->len - pos, s, strlen(s))

Oh, and as to other strbuf_* helpers, I am finding myself getting
very fond of strbuf_git_path() as I read the series along; it gives
us the same convenience as git_path() [*1*] while giving us tighter
control on the lifetime rules of the path buffer.

[Footnote]

*1* And the new git_path() updated in this series has to be a lot
 more than catenate($GIT_DIR, "/", $path) but needs the smart of
 adjust_git_path(), the convenience matters.

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 02/25] Convert git_snpath() to strbuf_git_path()
  2014-02-18 13:39 ` [PATCH v3 02/25] Convert git_snpath() to strbuf_git_path() Nguyễn Thái Ngọc Duy
  2014-02-19 23:48   ` Junio C Hamano
@ 2014-02-20 19:44   ` Junio C Hamano
  1 sibling, 0 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-02-20 19:44 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> @@ -2717,17 +2729,19 @@ static int copy_msg(char *buf, const char *msg)
>  	return cp - buf;
>  }
>  
> -int log_ref_setup(const char *refname, char *logfile, int bufsize)
> +int log_ref_setup(const char *refname, struct strbuf *sb_logfile)
>  {
>  	int logfd, oflags = O_APPEND | O_WRONLY;
> +	const char *logfile;
>  
> -	git_snpath(logfile, bufsize, "logs/%s", refname);
> +	strbuf_git_path(sb_logfile, "logs/%s", refname);
> +	logfile = sb_logfile->buf;
>  	if (log_all_ref_updates &&
>  	    (starts_with(refname, "refs/heads/") ||
>  	     starts_with(refname, "refs/remotes/") ||
>  	     starts_with(refname, "refs/notes/") ||
>  	     !strcmp(refname, "HEAD"))) {
> -		if (safe_create_leading_directories(logfile) < 0)
> +		if (safe_create_leading_directories(sb_logfile->buf) < 0)

Other references to logfile in this function are kept as-is, and I
think this line can also stay as-it-was.  That is the whole point of
introducing the local variable logfile and assinging to it upfront
as a synonym, isn't it?

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 24/25] prune: strategies for linked checkouts
  2014-02-20 13:15     ` Duy Nguyen
@ 2014-02-20 19:55       ` Junio C Hamano
  0 siblings, 0 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-02-20 19:55 UTC (permalink / raw)
  To: Duy Nguyen; +Cc: Git Mailing List

Duy Nguyen <pclouds@gmail.com> writes:

>> But why is it OK to fail in the first place?  If we couldn't link,
>> don't you want to fall back to the lock codepath?  After all, the
>> "are we on the same device?" check is to cope with the case where
>> we cannot link and that alternate codepath is supposed to be
>> prepared to handle the "ah, we cannot link" case correctly, no?
>
> Filesystems not supporting hardlinks are one reason. The idea behind
> "locked" is that the new checkout could be on a portable device and we
> don't want to prune its metadata just because the device is not
> plugged in. Checking same device help but even that can't guarantee no
> false positives, unless your only mount point is /. So no I don't
> really think we should go lock whenever link() fails, that would just
> make fewer checkouts prunable.

It just looked wrong to have a logic that goes like this:

        if (we detect one case link() can never possibly work) {
                do something for a case we do not use link;
        } else {
                prepare parameters that is used when we do link;
                do link() but ignore error;
        }

and felt that I would have understood if it were more like this:

        prepare parameters that is used when we do link;
        if (try link() and it fails) {
                do something for a case we do not use link;
        }

but I did not mean that was the only alternative to correct the
"wrongness".

I do not think I yet know the motivation behind this step well
enough to further comment---until I read the earlier parts of the
series that lead to this step, so I'll do that first.

Thanks.

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 14/25] setup.c: convert is_git_directory() to use strbuf
  2014-02-18 13:40 ` [PATCH v3 14/25] setup.c: convert is_git_directory() to use strbuf Nguyễn Thái Ngọc Duy
  2014-02-19 20:17   ` Junio C Hamano
@ 2014-02-21  3:38   ` Eric Sunshine
  1 sibling, 0 replies; 169+ messages in thread
From: Eric Sunshine @ 2014-02-21  3:38 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: Git List

On Tue, Feb 18, 2014 at 8:40 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
> index 73e80ce..aec9fdb 100644
> --- a/strbuf.h
> +++ b/strbuf.h
> @@ -116,6 +116,10 @@ extern void strbuf_add(struct strbuf *, const void *, size_t);
>  static inline void strbuf_addstr(struct strbuf *sb, const char *s) {
>         strbuf_add(sb, s, strlen(s));
>  }
> +static inline void strbuf_addstr_at(struct strbuf *sb, size_t len, const char *s) {
> +       strbuf_setlen(sb, len);
> +       strbuf_add(sb, s, strlen(s));
> +}

Update Documentation/technical/api-strbuf.txt?

>  static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) {
>         strbuf_grow(sb, sb2->len);
>         strbuf_add(sb, sb2->buf, sb2->len);
> --
> 1.8.5.2.240.g8478abd

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 07/25] reflog: avoid constructing .lock path with git_path
  2014-02-18 13:39 ` [PATCH v3 07/25] reflog: avoid constructing .lock path with git_path Nguyễn Thái Ngọc Duy
@ 2014-02-25 22:44   ` Junio C Hamano
  2014-03-01  3:42     ` Duy Nguyen
  0 siblings, 1 reply; 169+ messages in thread
From: Junio C Hamano @ 2014-02-25 22:44 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> git_path() soon understands the path given to it. Some paths "abc" may
> become "def" while other "ghi" may become "ijk". We don't want
> git_path() to interfere with .lock path construction. Concatenate
> ".lock" after the path has been resolved by git_path() so if "abc"
> becomes "def", we'll have "def.lock", not "ijk".

Hmph.  I am not sure if the above is readable (or if I am reading it
correctly).

If "abc" becomes "def", it would take deliberate work to make
"abc.lock" into "ijk", and it would be natural to expect that
"abc.lock" would become "def.lock" without any fancy trick, no?


> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  builtin/reflog.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/builtin/reflog.c b/builtin/reflog.c
> index 852cff6..ccf2cf6 100644
> --- a/builtin/reflog.c
> +++ b/builtin/reflog.c
> @@ -372,7 +372,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
>  	if (!file_exists(log_file))
>  		goto finish;
>  	if (!cmd->dry_run) {
> -		newlog_path = git_pathdup("logs/%s.lock", ref);
> +		newlog_path = mkpathdup("%s.lock", log_file);
>  		cb.newlog = fopen(newlog_path, "w");
>  	}

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR
  2014-02-18 13:39 ` [PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR Nguyễn Thái Ngọc Duy
@ 2014-02-26  1:24   ` Eric Sunshine
  2014-02-26 10:55     ` Duy Nguyen
  2014-02-26 23:58   ` Junio C Hamano
  1 sibling, 1 reply; 169+ messages in thread
From: Eric Sunshine @ 2014-02-26  1:24 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: Git List

On Tue, Feb 18, 2014 at 8:39 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> This variable is intended to support multiple working directories
> attached to a repository. Such a repository may have a main working
> directory, created by either "git init" or "git clone" and one or more
> linked working directories. These working directories and the main
> repository share the same repository directory.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
> diff --git a/Documentation/git.txt b/Documentation/git.txt
> index 02bbc08..2c4a055 100644
> --- a/Documentation/git.txt
> +++ b/Documentation/git.txt
> @@ -773,6 +773,14 @@ Git so take care if using Cogito etc.
>         an explicit repository directory set via 'GIT_DIR' or on the
>         command line.
>
> +'GIT_COMMON_DIR'::
> +       If this variable is set to a path, non-worktree files that are
> +       normally in $GIT_DIR will be taken from this path
> +       instead. Worktree-specific files such as HEAD or index are
> +       taken from $GIT_DIR. This variable has lower precedence than
> +       other path variables such as GIT_INDEX_FILE,
> +       GIT_OBJECT_DIRECTORY...

For a person not familiar with "git checkout --to" or its underlying
implementation, this description may be lacking. Such a reader may be
left wondering about GIT_COMMON_DIR's overall purpose, and when and
how it should be used. Perhaps it would make sense to talk a bit about
"git checkout --to" here?

>  Git Commits
>  ~~~~~~~~~~~
>  'GIT_AUTHOR_NAME'::

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR
  2014-02-26  1:24   ` Eric Sunshine
@ 2014-02-26 10:55     ` Duy Nguyen
  2014-02-26 16:12       ` Philip Oakley
  2014-02-26 19:43       ` Eric Sunshine
  0 siblings, 2 replies; 169+ messages in thread
From: Duy Nguyen @ 2014-02-26 10:55 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: Git List

On Wed, Feb 26, 2014 at 8:24 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
>> +'GIT_COMMON_DIR'::
>> +       If this variable is set to a path, non-worktree files that are
>> +       normally in $GIT_DIR will be taken from this path
>> +       instead. Worktree-specific files such as HEAD or index are
>> +       taken from $GIT_DIR. This variable has lower precedence than
>> +       other path variables such as GIT_INDEX_FILE,
>> +       GIT_OBJECT_DIRECTORY...
>
> For a person not familiar with "git checkout --to" or its underlying
> implementation, this description may be lacking. Such a reader may be
> left wondering about GIT_COMMON_DIR's overall purpose, and when and
> how it should be used. Perhaps it would make sense to talk a bit about
> "git checkout --to" here?

I don't want to repeat too much. Maybe mention about "git checkout
--to" and point them to git-checkout man page?
-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR
  2014-02-26 10:55     ` Duy Nguyen
@ 2014-02-26 16:12       ` Philip Oakley
  2014-02-26 17:23         ` Eric Sunshine
  2014-02-26 19:43       ` Eric Sunshine
  1 sibling, 1 reply; 169+ messages in thread
From: Philip Oakley @ 2014-02-26 16:12 UTC (permalink / raw)
  To: Duy Nguyen, Eric Sunshine; +Cc: Git List

From: "Duy Nguyen" <pclouds@gmail.com>
> On Wed, Feb 26, 2014 at 8:24 AM, Eric Sunshine 
> <sunshine@sunshineco.com> wrote:
>>> +'GIT_COMMON_DIR'::
>>> +       If this variable is set to a path, non-worktree files that 
>>> are
>>> +       normally in $GIT_DIR will be taken from this path
>>> +       instead. Worktree-specific files such as HEAD or index are
>>> +       taken from $GIT_DIR. This variable has lower precedence than
>>> +       other path variables such as GIT_INDEX_FILE,
>>> +       GIT_OBJECT_DIRECTORY...
>>
>> For a person not familiar with "git checkout --to" or its underlying
>> implementation, this description may be lacking. Such a reader may be
>> left wondering about GIT_COMMON_DIR's overall purpose, and when and
>> how it should be used. Perhaps it would make sense to talk a bit 
>> about
>> "git checkout --to" here?
>
> I don't want to repeat too much. Maybe mention about "git checkout
> --to" and point them to git-checkout man page?

I've just looked at both 
https://www.kernel.org/pub/software/scm/git/docs/git-checkout.html and 
http://git-htmldocs.googlecode.com/git/git-checkout.html and neither 
appear to mention the --to option.

Is it missing from the man page? Or is it me that's missing something?

--
Philip 

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR
  2014-02-26 16:12       ` Philip Oakley
@ 2014-02-26 17:23         ` Eric Sunshine
  0 siblings, 0 replies; 169+ messages in thread
From: Eric Sunshine @ 2014-02-26 17:23 UTC (permalink / raw)
  To: Philip Oakley; +Cc: Duy Nguyen, Git List

On Wed, Feb 26, 2014 at 11:12 AM, Philip Oakley <philipoakley@iee.org> wrote:
> From: "Duy Nguyen" <pclouds@gmail.com>
>> On Wed, Feb 26, 2014 at 8:24 AM, Eric Sunshine <sunshine@sunshineco.com>
>> wrote:
>>>>
>>>> +'GIT_COMMON_DIR'::
>>>> +       If this variable is set to a path, non-worktree files that are
>>>> +       normally in $GIT_DIR will be taken from this path
>>>> +       instead. Worktree-specific files such as HEAD or index are
>>>> +       taken from $GIT_DIR. This variable has lower precedence than
>>>> +       other path variables such as GIT_INDEX_FILE,
>>>> +       GIT_OBJECT_DIRECTORY...
>>>
>>> For a person not familiar with "git checkout --to" or its underlying
>>> implementation, this description may be lacking. Such a reader may be
>>> left wondering about GIT_COMMON_DIR's overall purpose, and when and
>>> how it should be used. Perhaps it would make sense to talk a bit about
>>> "git checkout --to" here?
>>
>> I don't want to repeat too much. Maybe mention about "git checkout
>> --to" and point them to git-checkout man page?
>
> I've just looked at both
> https://www.kernel.org/pub/software/scm/git/docs/git-checkout.html and
> http://git-htmldocs.googlecode.com/git/git-checkout.html and neither appear
> to mention the --to option.
>
> Is it missing from the man page? Or is it me that's missing something?

'git checkout --to' is the new feature being introduced by this
25-patch series [1] from Duy (to which we are responding).

[1]: http://thread.gmane.org/gmane.comp.version-control.git/242300

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR
  2014-02-26 10:55     ` Duy Nguyen
  2014-02-26 16:12       ` Philip Oakley
@ 2014-02-26 19:43       ` Eric Sunshine
  1 sibling, 0 replies; 169+ messages in thread
From: Eric Sunshine @ 2014-02-26 19:43 UTC (permalink / raw)
  To: Duy Nguyen; +Cc: Git List

On Wed, Feb 26, 2014 at 5:55 AM, Duy Nguyen <pclouds@gmail.com> wrote:
> On Wed, Feb 26, 2014 at 8:24 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
>>> +'GIT_COMMON_DIR'::
>>> +       If this variable is set to a path, non-worktree files that are
>>> +       normally in $GIT_DIR will be taken from this path
>>> +       instead. Worktree-specific files such as HEAD or index are
>>> +       taken from $GIT_DIR. This variable has lower precedence than
>>> +       other path variables such as GIT_INDEX_FILE,
>>> +       GIT_OBJECT_DIRECTORY...
>>
>> For a person not familiar with "git checkout --to" or its underlying
>> implementation, this description may be lacking. Such a reader may be
>> left wondering about GIT_COMMON_DIR's overall purpose, and when and
>> how it should be used. Perhaps it would make sense to talk a bit about
>> "git checkout --to" here?
>
> I don't want to repeat too much. Maybe mention about "git checkout
> --to" and point them to git-checkout man page?

Yes, that might be sufficient. "git checkout --to" documentation
points the reader at the "MULTIPLE CHECKOUT MODE" section which gives
a more detailed explanation of GIT_COMMON_DIR, so a user wanting to
understand GIT_COMMON_DIR better would have a way to find the
information.

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 21/25] checkout: support checking out into a new working directory
  2014-02-18 13:40 ` [PATCH v3 21/25] checkout: support checking out into a new working directory Nguyễn Thái Ngọc Duy
@ 2014-02-26 20:06   ` Eric Sunshine
  2014-02-26 23:19     ` Duy Nguyen
  0 siblings, 1 reply; 169+ messages in thread
From: Eric Sunshine @ 2014-02-26 20:06 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: Git List

On Tue, Feb 18, 2014 at 8:40 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> "git checkout --to" sets up a new working directory with a .git file
> pointing to $GIT_DIR/repos/<id>. It then executes "git checkout" again
> on the new worktree with the same arguments except "--to" is taken
> out. The second checkout execution, which is not contaminated with any
> info from the current repository, will actually check out and
> everything that normal "git checkout" does.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index 0570e41..2b856a6 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -806,6 +814,74 @@ static int switch_branches(const struct checkout_opts *opts,
>         return ret || writeout_error;
>  }
>
> +static int prepare_linked_checkout(const struct checkout_opts *opts,
> +                                  struct branch_info *new)
> +{
> +       struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
> +       struct strbuf sb = STRBUF_INIT;
> +       const char *path = opts->new_worktree;
> +       struct stat st;
> +       const char *name;
> +       struct child_process cp;
> +       int counter = 0, len;
> +
> +       if (!new->commit)
> +               die(_("no branch specified"));
> +
> +       len = strlen(path);
> +       if (!len || is_dir_sep(path[len - 1]))
> +               die(_("'--to' argument '%s' cannot end with a slash"), path);

What is the purpose of this restriction?

> +       for (name = path + len - 1; name > path; name--)
> +               if (is_dir_sep(*name)) {
> +                       name++;
> +                       break;
> +               }
> +       strbuf_addstr(&sb_repo, git_path("repos/%s", name));
> +       len = sb_repo.len;
> +       if (safe_create_leading_directories_const(sb_repo.buf))
> +               die_errno(_("could not create leading directories of '%s'"),
> +                         sb_repo.buf);
> +       while (!stat(sb_repo.buf, &st)) {
> +               counter++;
> +               strbuf_setlen(&sb_repo, len);
> +               strbuf_addf(&sb_repo, "%d", counter);
> +       }
> +       name = sb_repo.buf + len - strlen(name);
> +       if (mkdir(sb_repo.buf, 0777))
> +               die_errno(_("could not create directory of '%s'"), sb_repo.buf);
> +
> +       strbuf_addf(&sb_git, "%s/.git", path);
> +       if (safe_create_leading_directories_const(sb_git.buf))
> +               die_errno(_("could not create leading directories of '%s'"),
> +                         sb_git.buf);
> +
> +       write_file(sb_git.buf, 1, "gitdir: %s/repos/%s\n",
> +                  real_path(get_git_dir()), name);
> +       /*
> +        * This is to keep resolve_ref() happy. We need a valid HEAD
> +        * or is_git_directory() will reject the directory. Any valid
> +        * value would do because this value will be ignored and
> +        * replaced at the next (real) checkout.
> +        */
> +       strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
> +       write_file(sb.buf, 1, "%s\n", sha1_to_hex(new->commit->object.sha1));
> +       strbuf_reset(&sb);
> +       strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
> +       write_file(sb.buf, 1, "../..\n");
> +
> +       if (!opts->quiet)
> +               fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
> +
> +       setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1);
> +       setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1);
> +       setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1);
> +       memset(&cp, 0, sizeof(cp));
> +       cp.git_cmd = 1;
> +       cp.argv = opts->saved_argv;
> +       return run_command(&cp);
> +}
> +

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 21/25] checkout: support checking out into a new working directory
  2014-02-26 20:06   ` Eric Sunshine
@ 2014-02-26 23:19     ` Duy Nguyen
  2014-02-27  6:59       ` Eric Sunshine
  0 siblings, 1 reply; 169+ messages in thread
From: Duy Nguyen @ 2014-02-26 23:19 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: Git List

On Thu, Feb 27, 2014 at 3:06 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
>> +       len = strlen(path);
>> +       if (!len || is_dir_sep(path[len - 1]))
>> +               die(_("'--to' argument '%s' cannot end with a slash"), path);
>
> What is the purpose of this restriction?

Laziness on my part :) Because the following loop searches backward to
get the `basename $path`, trailing slash would make it return empty
base name. I could have just removed the trailing slash here instead
of dying though. Thanks for catching.

>
>> +       for (name = path + len - 1; name > path; name--)
>> +               if (is_dir_sep(*name)) {
>> +                       name++;
>> +                       break;
>> +               }



-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR
  2014-02-18 13:39 ` [PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR Nguyễn Thái Ngọc Duy
  2014-02-26  1:24   ` Eric Sunshine
@ 2014-02-26 23:58   ` Junio C Hamano
  2014-02-27  3:03     ` Duy Nguyen
  1 sibling, 1 reply; 169+ messages in thread
From: Junio C Hamano @ 2014-02-26 23:58 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> Note that logs/refs/.tmp-renamed-log is used to prepare new reflog
> entry and it's supposed to be on the same filesystem as the target
> reflog file. This is not guaranteed true for logs/HEAD when it's
> mapped to repos/xx/logs/HEAD because the user can put "repos"
> directory on different filesystem. Don't mess with .git unless you
> know what you're doing.

Hmph.  I am puzzled.

We use TMP_RENAMED_LOG in rename_ref() in this sequence:

 * First move refs/logs/$oldname to TMP_RENAMED_LOG;
 * Delete refs/$oldname;
 * Delete refs/$newname if exists;
 * Move TMP_RENAMED_LOG to refs/logs/$newname;
 * Create refs/$newname.

in rename_ref(), and TMP_RENAMED_LOG or the helper function
rename_tmp_log() that does the actual rename do not seem to be
called by any other codepath.

How would logs/HEAD get in the picture?  Specifically, I am not sure
if we ever allow renaming the HEAD (which typically is a symref but
could be detached) via rename_ref() at all.

> The redirection is done by git_path(), git_pathdup() and
> strbuf_git_path(). The selected list of paths goes to $GIT_COMMON_DIR,
> not the other way around in case a developer adds a new
> worktree-specific file and it's accidentally promoted to be shared
> across repositories (this includes unknown files added by third party
> commands)
>
> The list of known files that belong to $GIT_DIR are:
>
> ADD_EDIT.patch BISECT_ANCESTORS_OK BISECT_EXPECTED_REV BISECT_LOG
> BISECT_NAMES CHERRY_PICK_HEAD COMMIT_MSG FETCH_HEAD HEAD MERGE_HEAD
> MERGE_MODE MERGE_RR NOTES_EDITMSG NOTES_MERGE_WORKTREE ORIG_HEAD
> REVERT_HEAD SQUASH_MSG TAG_EDITMSG fast_import_crash_* logs/HEAD
> next-index-* rebase-apply rebase-merge rsync-refs-* sequencer/*
> shallow_*
>
> Path mapping is NOT done for git_path_submodule(). Multi-checkouts are
> not supported as submodules.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---

Other than the "I do not understand why logs/HEAD is an issue", the
other aspect of the above design feels sound to me.

> diff --git a/Documentation/git.txt b/Documentation/git.txt
> index 02bbc08..2c4a055 100644
> --- a/Documentation/git.txt
> +++ b/Documentation/git.txt
> @@ -773,6 +773,14 @@ Git so take care if using Cogito etc.
>  	an explicit repository directory set via 'GIT_DIR' or on the
>  	command line.
>  
> +'GIT_COMMON_DIR'::
> +	If this variable is set to a path, non-worktree files that are
> +	normally in $GIT_DIR will be taken from this path
> +	instead. Worktree-specific files such as HEAD or index are
> +	taken from $GIT_DIR. This variable has lower precedence than
> +	other path variables such as GIT_INDEX_FILE,
> +	GIT_OBJECT_DIRECTORY...
> +

We may want to add something (not necessarily with this commit) to
repository-layout and glossary to describe this new "officially
supported" way to implement what contrib/workdir wanted to do.

> diff --git a/path.c b/path.c
> index 0f8c3dc..2d757dc 100644
> --- a/path.c
> +++ b/path.c
> @@ -90,6 +90,32 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)
>  		buf->buf[newlen] = '/';
>  }
>  
> +static void update_common_dir(struct strbuf *buf, int git_dir_len)
> +{
> +	const char *common_dir_list[] = {
> +		"branches", "hooks", "info", "logs", "lost-found", "modules",
> +		"objects", "refs", "remotes", "rr-cache", "svn",
> +		NULL
> +	};
> +	const char *common_top_file_list[] = {
> +		"config", "gc.pid", "packed-refs", "shallow", NULL
> +	};
> +	char *base = buf->buf + git_dir_len;
> +	const char **p;
> +	if (is_dir_file(base, "logs", "HEAD"))

Just a style, but could you have a blank line between the series of
decls and the first statement?  It would be easier to read that way.

> +		return;	/* keep this in $GIT_DIR */
> +	for (p = common_dir_list; *p; p++)
> +		if (dir_prefix(base, *p)) {
> +			replace_dir(buf, git_dir_len, get_git_common_dir());
> +			return;
> +		}
> +	for (p = common_top_file_list; *p; p++)
> +		if (!strcmp(base, *p)) {
> +			replace_dir(buf, git_dir_len, get_git_common_dir());
> +			return;
> +		}
> +}
> +
>  static void adjust_git_path(struct strbuf *buf, int git_dir_len)
>  {
>  	const char *base = buf->buf + git_dir_len;
> @@ -101,6 +127,8 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)
>  			      get_index_file(), strlen(get_index_file()));
>  	else if (git_db_env && dir_prefix(base, "objects"))
>  		replace_dir(buf, git_dir_len + 7, get_object_directory());
> +	else if (git_common_dir_env)
> +		update_common_dir(buf, git_dir_len);
>  }
>  
>  static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
> diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
> index 1d29901..f9a77e4 100755
> --- a/t/t0060-path-utils.sh
> +++ b/t/t0060-path-utils.sh
> @@ -241,5 +241,20 @@ test_expect_success 'setup fake objects directory foo' 'mkdir foo'
>  test_git_path GIT_OBJECT_DIRECTORY=foo objects foo
>  test_git_path GIT_OBJECT_DIRECTORY=foo objects/foo foo/foo
>  test_git_path GIT_OBJECT_DIRECTORY=foo objects2 .git/objects2
> +test_expect_success 'setup common repository' 'git --git-dir=bar init'
> +test_git_path GIT_COMMON_DIR=bar index                    .git/index
> +test_git_path GIT_COMMON_DIR=bar HEAD                     .git/HEAD
> +test_git_path GIT_COMMON_DIR=bar logs/HEAD                .git/logs/HEAD
> +test_git_path GIT_COMMON_DIR=bar objects                  bar/objects
> +test_git_path GIT_COMMON_DIR=bar objects/bar              bar/objects/bar
> +test_git_path GIT_COMMON_DIR=bar info/exclude             bar/info/exclude
> +test_git_path GIT_COMMON_DIR=bar remotes/bar              bar/remotes/bar
> +test_git_path GIT_COMMON_DIR=bar branches/bar             bar/branches/bar
> +test_git_path GIT_COMMON_DIR=bar logs/refs/heads/master   bar/logs/refs/heads/master
> +test_git_path GIT_COMMON_DIR=bar refs/heads/master        bar/refs/heads/master
> +test_git_path GIT_COMMON_DIR=bar hooks/me                 bar/hooks/me
> +test_git_path GIT_COMMON_DIR=bar config                   bar/config
> +test_git_path GIT_COMMON_DIR=bar packed-refs              bar/packed-refs
> +test_git_path GIT_COMMON_DIR=bar shallow                  bar/shallow
>  
>  test_done

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 11/25] git-sh-setup.sh: use rev-parse --git-path to get $GIT_DIR/objects
  2014-02-18 13:40 ` [PATCH v3 11/25] git-sh-setup.sh: use rev-parse --git-path to get $GIT_DIR/objects Nguyễn Thái Ngọc Duy
@ 2014-02-27  0:00   ` Junio C Hamano
  0 siblings, 0 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-02-27  0:00 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> If $GIT_COMMON_DIR is set, $GIT_OBJECT_DIRECTORY should be
> $GIT_COMMON_DIR/objects, not $GIT_DIR/objects. Just let rev-parse
> --git-path handle it.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  git-sh-setup.sh | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/git-sh-setup.sh b/git-sh-setup.sh
> index fffa3c7..fec9430 100644
> --- a/git-sh-setup.sh
> +++ b/git-sh-setup.sh
> @@ -343,7 +343,7 @@ then
>  		echo >&2 "Unable to determine absolute path of git directory"
>  		exit 1
>  	}
> -	: ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"}
> +	: ${GIT_OBJECT_DIRECTORY="`git rev-parse --git-path objects`"}

$(...) is the preferred way over `...` in this codebase.

>  fi
>  
>  peel_committish () {

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 15/25] setup.c: detect $GIT_COMMON_DIR in is_git_directory()
  2014-02-18 13:40 ` [PATCH v3 15/25] setup.c: detect $GIT_COMMON_DIR in is_git_directory() Nguyễn Thái Ngọc Duy
@ 2014-02-27  0:16   ` Junio C Hamano
  2014-03-01  3:33     ` Duy Nguyen
  0 siblings, 1 reply; 169+ messages in thread
From: Junio C Hamano @ 2014-02-27  0:16 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> If the file "$GIT_DIR/commondir" exists, it contains the value of
> $GIT_COMMON_DIR.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  Documentation/gitrepository-layout.txt |  4 ++++
>  setup.c                                | 38 ++++++++++++++++++++++++++++------
>  strbuf.c                               |  8 +++++++
>  strbuf.h                               |  1 +
>  4 files changed, 45 insertions(+), 6 deletions(-)
>
> diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
> index aa03882..9bfe0f1 100644
> --- a/Documentation/gitrepository-layout.txt
> +++ b/Documentation/gitrepository-layout.txt
> @@ -211,6 +211,10 @@ shallow::
>  	and maintained by shallow clone mechanism.  See `--depth`
>  	option to linkgit:git-clone[1] and linkgit:git-fetch[1].
>  
> +commondir::
> +	If this file exists, $GIT_COMMON_DIR will be set to the path
> +	specified in this file if it is not set.
> +
>  modules::
>  	Contains the git-repositories of the submodules.

In a way similar to the "*Note*" in a very early part of this file
describes the .git-file redirected repositories, we would need to
mention that there now exist repositories borrowing from another
repository via this commondir mechanism, and what the caveats are
when using them (like, "do not ever nuke the original repository
somebody else is borrowing from with 'rm -rf' when you think you are
done with the original").

> +	if (file_exists(path.buf)) {
> +		if (strbuf_read_file(&data, path.buf, 0) <= 0)
> +			die_errno(_("failed to read %s"), path.buf);

Do we care about the case where we cannot tell if the file exists
(e.g. stat() fails due to EPERM or something), or would it be not
worth worrying about?

> +		strbuf_chomp(&data);
> +		strbuf_reset(&path);
> +		if (!is_absolute_path(data.buf))
> +			strbuf_addf(&path, "%s/", gitdir);
> +		strbuf_addbuf(&path, &data);

OK, so commondir can be relative to the containing gitdir
(e.g. /a/foo/.git/commondir with "../../bar/.git" would name
/a/bar/.git as the common dir).

It needs to be documented in the repository-layout somehow.

> @@ -188,14 +212,20 @@ int is_git_directory(const char *suspect)
>  	int ret = 0;
>  	size_t len;
>  
> -	strbuf_addstr(&path, suspect);
> +	strbuf_addf(&path, "%s/HEAD", suspect);

> +	if (validate_headref(path.buf))
> +		goto done;

Is there a reason why we want to check HEAD before other stuff?
Just being curious, as I do not think of any (I am not saying that
we shouldn't change the order).

> +void strbuf_chomp(struct strbuf *sb)
> +{
> +	while (sb->len && (sb->buf[sb->len - 1] == '\n' ||
> +			   sb->buf[sb->len - 1] == '\r'))
> +		sb->len--;
> +	sb->buf[sb->len] = '\0';
> +}

It feels a bit yucky to ignore trailing \r on non-DOS filesystems
(if it were also removing any whitespace, then I would sort of
understand in the context of the expected caller of this function in
this series---except that it would no longer be a "chomp"), but I'd
let it pass.

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 16/25] setup.c: convert check_repository_format_gently to use strbuf
  2014-02-18 13:40 ` [PATCH v3 16/25] setup.c: convert check_repository_format_gently to use strbuf Nguyễn Thái Ngọc Duy
@ 2014-02-27  0:18   ` Junio C Hamano
  0 siblings, 0 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-02-27  0:18 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---

Nice.

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 17/25] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently()
  2014-02-18 13:40 ` [PATCH v3 17/25] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently() Nguyễn Thái Ngọc Duy
@ 2014-02-27  0:22   ` Junio C Hamano
  2014-02-27  2:43     ` Duy Nguyen
  0 siblings, 1 reply; 169+ messages in thread
From: Junio C Hamano @ 2014-02-27  0:22 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---

It is a good thing to do to read config from the real repository we
are borrowing from when we have .git/commondir, but it makes me
wonder if we should signal some kind of error if we find .git/config
in such a borrowing repository---which will be silently ignored.

My gut feeling is that such a check may be necessary, but may not
belong to this function.

>  setup.c | 6 ++++--
>  1 file changed, 4 insertions(+), 2 deletions(-)
>
> diff --git a/setup.c b/setup.c
> index 282fdc9..e56ec11 100644
> --- a/setup.c
> +++ b/setup.c
> @@ -285,6 +285,10 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
>  	const char *repo_config;
>  	int ret = 0;
>  
> +	get_common_dir(&sb, gitdir);
> +	strbuf_addstr(&sb, "/config");
> +	repo_config = sb.buf;
> +
>  	/*
>  	 * git_config() can't be used here because it calls git_pathdup()
>  	 * to get $GIT_CONFIG/config. That call will make setup_git_env()
> @@ -294,8 +298,6 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
>  	 * Use a gentler version of git_config() to check if this repo
>  	 * is a good one.
>  	 */
> -	strbuf_addf(&sb, "%s/config", gitdir);
> -	repo_config = sb.buf;
>  	git_config_early(check_repository_format_version, NULL, repo_config);
>  	if (GIT_REPO_VERSION < repository_format_version) {
>  		if (!nongit_ok)

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 17/25] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently()
  2014-02-27  0:22   ` Junio C Hamano
@ 2014-02-27  2:43     ` Duy Nguyen
  2014-02-27 17:48       ` Junio C Hamano
  0 siblings, 1 reply; 169+ messages in thread
From: Duy Nguyen @ 2014-02-27  2:43 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git Mailing List

On Thu, Feb 27, 2014 at 7:22 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
>
>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
>> ---
>
> It is a good thing to do to read config from the real repository we
> are borrowing from when we have .git/commondir, but it makes me
> wonder if we should signal some kind of error if we find .git/config
> in such a borrowing repository---which will be silently ignored.
>
> My gut feeling is that such a check may be necessary, but may not
> belong to this function.

Just checking. Once we do this, what about .git/refs/.. that is also
ignored in such a repo? I can see the point that config could be
manually edited so the user may edit the file at the wrong place, so
it's good the we catch this exception.
-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR
  2014-02-26 23:58   ` Junio C Hamano
@ 2014-02-27  3:03     ` Duy Nguyen
  0 siblings, 0 replies; 169+ messages in thread
From: Duy Nguyen @ 2014-02-27  3:03 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git Mailing List

On Thu, Feb 27, 2014 at 6:58 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
>
>> Note that logs/refs/.tmp-renamed-log is used to prepare new reflog
>> entry and it's supposed to be on the same filesystem as the target
>> reflog file. This is not guaranteed true for logs/HEAD when it's
>> mapped to repos/xx/logs/HEAD because the user can put "repos"
>> directory on different filesystem. Don't mess with .git unless you
>> know what you're doing.
>
> Hmph.  I am puzzled.
>
> We use TMP_RENAMED_LOG in rename_ref() in this sequence:
>
>  * First move refs/logs/$oldname to TMP_RENAMED_LOG;
>  * Delete refs/$oldname;
>  * Delete refs/$newname if exists;
>  * Move TMP_RENAMED_LOG to refs/logs/$newname;
>  * Create refs/$newname.
>
> in rename_ref(), and TMP_RENAMED_LOG or the helper function
> rename_tmp_log() that does the actual rename do not seem to be
> called by any other codepath.
>
> How would logs/HEAD get in the picture?  Specifically, I am not sure
> if we ever allow renaming the HEAD (which typically is a symref but
> could be detached) via rename_ref() at all.

HEAD is an exception, I agree. If you rename HEAD to something else,
the repo will not be recognized anymore because HEAD is part of the
repo signature. There are other refs outside refs/ though that can be
renamed in theory.  In practice all rename_ref() callers only operate
on refs/ domain so this is a false alarm.
-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 21/25] checkout: support checking out into a new working directory
  2014-02-26 23:19     ` Duy Nguyen
@ 2014-02-27  6:59       ` Eric Sunshine
  0 siblings, 0 replies; 169+ messages in thread
From: Eric Sunshine @ 2014-02-27  6:59 UTC (permalink / raw)
  To: Duy Nguyen; +Cc: Git List

On Wed, Feb 26, 2014 at 6:19 PM, Duy Nguyen <pclouds@gmail.com> wrote:
> On Thu, Feb 27, 2014 at 3:06 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
>>> +       len = strlen(path);
>>> +       if (!len || is_dir_sep(path[len - 1]))
>>> +               die(_("'--to' argument '%s' cannot end with a slash"), path);
>>
>> What is the purpose of this restriction?
>
> Laziness on my part :) Because the following loop searches backward to
> get the `basename $path`, trailing slash would make it return empty
> base name. I could have just removed the trailing slash here instead
> of dying though. Thanks for catching.

Thanks for the explanation. I thought that that might be the case.

>>> +       for (name = path + len - 1; name > path; name--)
>>> +               if (is_dir_sep(*name)) {
>>> +                       name++;
>>> +                       break;
>>> +               }

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 17/25] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently()
  2014-02-27  2:43     ` Duy Nguyen
@ 2014-02-27 17:48       ` Junio C Hamano
  0 siblings, 0 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-02-27 17:48 UTC (permalink / raw)
  To: Duy Nguyen; +Cc: Git Mailing List

Duy Nguyen <pclouds@gmail.com> writes:

> On Thu, Feb 27, 2014 at 7:22 AM, Junio C Hamano <gitster@pobox.com> wrote:
>> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
>>
>>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
>>> ---
>>
>> It is a good thing to do to read config from the real repository we
>> are borrowing from when we have .git/commondir, but it makes me
>> wonder if we should signal some kind of error if we find .git/config
>> in such a borrowing repository---which will be silently ignored.
>>
>> My gut feeling is that such a check may be necessary, but may not
>> belong to this function.
>
> Just checking. Once we do this, what about .git/refs/.. that is also
> ignored in such a repo?

It was just that I became aware of the issue while reading this
patch to check-repository-format which is only about config, but
anything inside .git/ of the borrowing repository that are ignored
because it has .git/common-dir (including .git/refs) should be a
cause of the same error, I would say.  That would be the same set
as symlinks created by contrib/workdir/git-new-workdir script.

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 18/25] setup.c: support multi-checkout repo setup
  2014-02-18 13:40 ` [PATCH v3 18/25] setup.c: support multi-checkout repo setup Nguyễn Thái Ngọc Duy
  2014-02-19 20:19   ` Junio C Hamano
@ 2014-02-27 20:28   ` Junio C Hamano
  2014-03-24 14:52   ` Torsten Bögershausen
  2 siblings, 0 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-02-27 20:28 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> The repo setup procedure is updated to detect $GIT_DIR/commondir and
> set $GIT_COMMON_DIR properly.
>
> The core.worktree is ignored when $GIT_DIR/commondir presents. This is
> because "commondir" repos are intended for separate/linked checkouts
> and pointing them back to a fixed core.worktree just does not make
> sense.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  Documentation/config.txt        |  3 +-
>  Documentation/git-rev-parse.txt |  3 ++
>  builtin/rev-parse.c             |  4 +++
>  cache.h                         |  1 +
>  environment.c                   |  8 ++---
>  setup.c                         | 33 +++++++++++++-----
>  t/t1501-worktree.sh             | 76 +++++++++++++++++++++++++++++++++++++++++
>  t/t1510-repo-setup.sh           |  1 +
>  trace.c                         |  1 +
>  9 files changed, 115 insertions(+), 15 deletions(-)
>
> diff --git a/Documentation/config.txt b/Documentation/config.txt
> index 5f4d793..cbf4d97 100644
> --- a/Documentation/config.txt
> +++ b/Documentation/config.txt
> @@ -381,7 +381,8 @@ false), while all other repositories are assumed to be bare (bare
>  
>  core.worktree::
>  	Set the path to the root of the working tree.
> -	This can be overridden by the GIT_WORK_TREE environment
> +	This can be overridden by the GIT_WORK_TREE
> +	or GIT_COMMON_DIR environment
>  	variable and the '--work-tree' command line option.

During my first reading, I was guessing that the reason you changed
this is because COMMON_DIR may redirect the config file from which
core.worktree may be read.  But that is probably not what you meant.
You do not want to share core.worktree between the borrowing and the
borrowed repositories.  If the presense of GIT_COMMON_DIR _disables_
core.worktree settings without supplying an alternative value, as
opposed to GIT_WORK_TREE which does override with an alternative
value, it is very different from "can be overriden".

It needs a better phrasing.

By the way, do we need to do something special for core.bare as
well for a similar reason?

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 01/25] path.c: make get_pathname() return strbuf instead of static buffer
  2014-02-19 23:26   ` Junio C Hamano
@ 2014-03-01  2:40     ` Duy Nguyen
  2014-03-03 17:56       ` Junio C Hamano
  0 siblings, 1 reply; 169+ messages in thread
From: Duy Nguyen @ 2014-03-01  2:40 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git Mailing List

On Thu, Feb 20, 2014 at 6:26 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Is there a reason not to do just an equivalent of
>
>     #define git_pathdup mkpathdup
>
> and be done with it?  Am I missing something?
>

They have a subtle difference: mkpathdup() calls cleanup_path() while
git_pathdup() does not. Without auditing all call sites, we can't be
sure this difference is insignificant. So I keep both functions
separate for now.
-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* [PATCH] strbuf: style fix -- top opening bracket on a separate line
  2014-02-19 20:17   ` Junio C Hamano
  2014-02-20 13:04     ` Duy Nguyen
@ 2014-03-01  2:50     ` Nguyễn Thái Ngọc Duy
  1 sibling, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01  2:50 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 On Thu, Feb 20, 2014 at 3:17 AM, Junio C Hamano <gitster@pobox.com> wrote:
 >> --- a/strbuf.h
 >> +++ b/strbuf.h
 >> @@ -116,6 +116,10 @@ extern void strbuf_add(struct strbuf *, const void *, size_t);
 >>  static inline void strbuf_addstr(struct strbuf *sb, const char *s) {
 >>       strbuf_add(sb, s, strlen(s));
 >>  }
 >> +static inline void strbuf_addstr_at(struct strbuf *sb, size_t len, const char *s) {
 >
 > Please have the opening "{" on its own line.
 >
 > Surrounding existing functions are all offenders, but that is not an
 > excuse to make it worse (cleaning them up will need to be done in a
 > separate patch).

 Let's fix the surrounding code then.

 strbuf.h | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

diff --git a/strbuf.h b/strbuf.h
index 73e80ce..39c14cf 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -17,20 +17,23 @@ extern void strbuf_init(struct strbuf *, size_t);
 extern void strbuf_release(struct strbuf *);
 extern char *strbuf_detach(struct strbuf *, size_t *);
 extern void strbuf_attach(struct strbuf *, void *, size_t, size_t);
-static inline void strbuf_swap(struct strbuf *a, struct strbuf *b) {
+static inline void strbuf_swap(struct strbuf *a, struct strbuf *b)
+{
 	struct strbuf tmp = *a;
 	*a = *b;
 	*b = tmp;
 }
 
 /*----- strbuf size related -----*/
-static inline size_t strbuf_avail(const struct strbuf *sb) {
+static inline size_t strbuf_avail(const struct strbuf *sb)
+{
 	return sb->alloc ? sb->alloc - sb->len - 1 : 0;
 }
 
 extern void strbuf_grow(struct strbuf *, size_t);
 
-static inline void strbuf_setlen(struct strbuf *sb, size_t len) {
+static inline void strbuf_setlen(struct strbuf *sb, size_t len)
+{
 	if (len > (sb->alloc ? sb->alloc - 1 : 0))
 		die("BUG: strbuf_setlen() beyond buffer");
 	sb->len = len;
@@ -97,7 +100,8 @@ static inline struct strbuf **strbuf_split(const struct strbuf *sb,
 extern void strbuf_list_free(struct strbuf **);
 
 /*----- add data in your buffer -----*/
-static inline void strbuf_addch(struct strbuf *sb, int c) {
+static inline void strbuf_addch(struct strbuf *sb, int c)
+{
 	strbuf_grow(sb, 1);
 	sb->buf[sb->len++] = c;
 	sb->buf[sb->len] = '\0';
@@ -113,10 +117,12 @@ extern void strbuf_splice(struct strbuf *, size_t pos, size_t len,
 extern void strbuf_add_commented_lines(struct strbuf *out, const char *buf, size_t size);
 
 extern void strbuf_add(struct strbuf *, const void *, size_t);
-static inline void strbuf_addstr(struct strbuf *sb, const char *s) {
+static inline void strbuf_addstr(struct strbuf *sb, const char *s)
+{
 	strbuf_add(sb, s, strlen(s));
 }
-static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) {
+static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2)
+{
 	strbuf_grow(sb, sb2->len);
 	strbuf_add(sb, sb2->buf, sb2->len);
 }
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 15/25] setup.c: detect $GIT_COMMON_DIR in is_git_directory()
  2014-02-27  0:16   ` Junio C Hamano
@ 2014-03-01  3:33     ` Duy Nguyen
  0 siblings, 0 replies; 169+ messages in thread
From: Duy Nguyen @ 2014-03-01  3:33 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git Mailing List

On Thu, Feb 27, 2014 at 7:16 AM, Junio C Hamano <gitster@pobox.com> wrote:
>> +     if (file_exists(path.buf)) {
>> +             if (strbuf_read_file(&data, path.buf, 0) <= 0)
>> +                     die_errno(_("failed to read %s"), path.buf);
>
> Do we care about the case where we cannot tell if the file exists
> (e.g. stat() fails due to EPERM or something), or would it be not
> worth worrying about?

In that case we assume (incorrectly) that the repository is complete.
Following operations would fail. So not too bad, I think.

>> @@ -188,14 +212,20 @@ int is_git_directory(const char *suspect)
>>       int ret = 0;
>>       size_t len;
>>
>> -     strbuf_addstr(&path, suspect);
>> +     strbuf_addf(&path, "%s/HEAD", suspect);
>
>> +     if (validate_headref(path.buf))
>> +             goto done;
>
> Is there a reason why we want to check HEAD before other stuff?
> Just being curious, as I do not think of any (I am not saying that
> we shouldn't change the order).

Yes, it's reordered so that worktree signature (e.g. HEAD) is checked
first, against $GIT_DIR. Then non-worktree signatures ("refs" and
"objects") are checked against $GIT_COMMON_DIR (or $GIT_DIR still if
$GIT_DIR/commondir does not exist). Notice "path" is reset to
$GIT_COMMON_DIR just after checking HEAD. I should probably add a
comment about this separation.
-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 07/25] reflog: avoid constructing .lock path with git_path
  2014-02-25 22:44   ` Junio C Hamano
@ 2014-03-01  3:42     ` Duy Nguyen
  2014-03-03 18:07       ` Junio C Hamano
  0 siblings, 1 reply; 169+ messages in thread
From: Duy Nguyen @ 2014-03-01  3:42 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git Mailing List

On Wed, Feb 26, 2014 at 5:44 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
>
>> git_path() soon understands the path given to it. Some paths "abc" may
>> become "def" while other "ghi" may become "ijk". We don't want
>> git_path() to interfere with .lock path construction. Concatenate
>> ".lock" after the path has been resolved by git_path() so if "abc"
>> becomes "def", we'll have "def.lock", not "ijk".
>
> Hmph.  I am not sure if the above is readable (or if I am reading it
> correctly).

Definitely not now that I have had my break from the series and reread it.

> If "abc" becomes "def", it would take deliberate work to make
> "abc.lock" into "ijk", and it would be natural to expect that
> "abc.lock" would become "def.lock" without any fancy trick, no?

A better explanation may be, while many paths are not converted by
git_path() ("abc" -> "abc"), some of them will be based on the given
path ("def" -> "ghi"). Giving path def.lock to git_path() may confuse
it and make it believe def.lock should not be converted because the
signature is "def.lock" not "def". But we want the lock file to have
the same base name with the locked file (e.g. "ghi.lock", not
"def.lock"). So it's best to append ".lock" after git_path() has done
its conversion.

The alternative is teach git_path about ".lock", but I don't really
want to put more logic down there.
-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* [PATCH v4 00/27] Support multiple checkouts
  2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
                   ` (25 preceding siblings ...)
  2014-02-19 20:57 ` [PATCH v3 00/25] Support multiple checkouts Junio C Hamano
@ 2014-03-01 12:12 ` Nguyễn Thái Ngọc Duy
  2014-03-01 12:12   ` [PATCH v4 01/27] path.c: make get_pathname() return strbuf instead of static buffer Nguyễn Thái Ngọc Duy
                     ` (27 more replies)
  26 siblings, 28 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Phew.. v4 changes are

- address all comments from Junio and Eric (I hope)
- fix "git checkout --to" not working from the linked checkouts
- report unused files (e.g. $GIT_DIR/repos/xx/config) in count-objects -v

Nguyễn Thái Ngọc Duy (27):
  path.c: make get_pathname() return strbuf instead of static buffer
  Convert git_snpath() to strbuf_git_path()
  path.c: rename vsnpath() to do_git_path()
  path.c: group git_path(), git_pathdup() and strbuf_git_path() together
  Make git_path() aware of file relocation in $GIT_DIR
  *.sh: respect $GIT_INDEX_FILE
  reflog: avoid constructing .lock path with git_path
  fast-import: use git_path() for accessing .git dir instead of get_git_dir()
  commit: use SEQ_DIR instead of hardcoding "sequencer"
  Add new environment variable $GIT_COMMON_DIR
  git-sh-setup.sh: use rev-parse --git-path to get $GIT_DIR/objects
  *.sh: avoid hardcoding $GIT_DIR/hooks/...
  git-stash: avoid hardcoding $GIT_DIR/logs/....
  setup.c: convert is_git_directory() to use strbuf
  setup.c: detect $GIT_COMMON_DIR in is_git_directory()
  setup.c: convert check_repository_format_gently to use strbuf
  setup.c: detect $GIT_COMMON_DIR check_repository_format_gently()
  setup.c: support multi-checkout repo setup
  wrapper.c: wrapper to open a file, fprintf then close
  use new wrapper write_file() for simple file writing
  checkout: support checking out into a new working directory
  checkout: clean up half-prepared directories in --to mode
  checkout: detach if the branch is already checked out elsewhere
  prune: strategies for linked checkouts
  gc: style change -- no SP before closing bracket
  gc: support prune --repos
  count-objects: report unused files in $GIT_DIR/repos/...

 Documentation/config.txt               |   8 +
 Documentation/git-checkout.txt         |  34 ++++
 Documentation/git-prune.txt            |   3 +
 Documentation/git-rev-parse.txt        |   8 +
 Documentation/git.txt                  |   9 ++
 Documentation/gitrepository-layout.txt |  75 +++++++--
 builtin/branch.c                       |   4 +-
 builtin/checkout.c                     | 274 +++++++++++++++++++++++++++++++--
 builtin/commit.c                       |   2 +-
 builtin/count-objects.c                |  37 ++++-
 builtin/gc.c                           |  21 ++-
 builtin/init-db.c                      |   7 +-
 builtin/prune.c                        |  74 +++++++++
 builtin/reflog.c                       |   2 +-
 builtin/rev-parse.c                    |  11 ++
 cache.h                                |  10 +-
 compat/mingw.h                         |   1 +
 daemon.c                               |  11 +-
 environment.c                          |  24 ++-
 fast-import.c                          |   5 +-
 git-am.sh                              |  22 +--
 git-compat-util.h                      |   4 +
 git-pull.sh                            |   2 +-
 git-rebase--interactive.sh             |   6 +-
 git-rebase--merge.sh                   |   6 +-
 git-rebase.sh                          |   4 +-
 git-sh-setup.sh                        |   2 +-
 git-stash.sh                           |   6 +-
 path.c                                 | 206 ++++++++++++++++---------
 refs.c                                 |  66 +++++---
 refs.h                                 |   2 +-
 setup.c                                | 124 +++++++++++----
 submodule.c                            |   9 +-
 t/t0060-path-utils.sh                  |  34 ++++
 t/t1501-worktree.sh                    |  76 +++++++++
 t/t1510-repo-setup.sh                  |   1 +
 t/t2025-checkout-to.sh (new +x)        |  57 +++++++
 templates/hooks--applypatch-msg.sample |   4 +-
 templates/hooks--pre-applypatch.sample |   4 +-
 trace.c                                |   1 +
 transport.c                            |   8 +-
 wrapper.c                              |  31 ++++
 42 files changed, 1069 insertions(+), 226 deletions(-)

-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply	[flat|nested] 169+ messages in thread

* [PATCH v4 01/27] path.c: make get_pathname() return strbuf instead of static buffer
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-02 19:51     ` Eric Sunshine
  2014-03-01 12:12   ` [PATCH v4 02/27] Convert git_snpath() to strbuf_git_path() Nguyễn Thái Ngọc Duy
                     ` (26 subsequent siblings)
  27 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

We've been avoiding PATH_MAX whenever possible. This patch makes
get_pathname() return a strbuf and updates the callers to take
advantage of this. The code is simplified as we no longer need to
worry about buffer overflow.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 path.c | 120 ++++++++++++++++++++++++++++-------------------------------------
 1 file changed, 51 insertions(+), 69 deletions(-)

diff --git a/path.c b/path.c
index 24594c4..5346700 100644
--- a/path.c
+++ b/path.c
@@ -16,11 +16,15 @@ static int get_st_mode_bits(const char *path, int *mode)
 
 static char bad_path[] = "/bad-path/";
 
-static char *get_pathname(void)
+static struct strbuf *get_pathname(void)
 {
-	static char pathname_array[4][PATH_MAX];
+	static struct strbuf pathname_array[4] = {
+		STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
+	};
 	static int index;
-	return pathname_array[3 & ++index];
+	struct strbuf *sb = &pathname_array[3 & ++index];
+	strbuf_reset(sb);
+	return sb;
 }
 
 static char *cleanup_path(char *path)
@@ -34,6 +38,13 @@ static char *cleanup_path(char *path)
 	return path;
 }
 
+static void strbuf_cleanup_path(struct strbuf *sb)
+{
+	char *path = cleanup_path(sb->buf);
+	if (path > sb->buf)
+		strbuf_remove(sb, 0, path - sb->buf);
+}
+
 char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 {
 	va_list args;
@@ -49,85 +60,70 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 	return cleanup_path(buf);
 }
 
-static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)
+static void vsnpath(struct strbuf *buf, const char *fmt, va_list args)
 {
 	const char *git_dir = get_git_dir();
-	size_t len;
-
-	len = strlen(git_dir);
-	if (n < len + 1)
-		goto bad;
-	memcpy(buf, git_dir, len);
-	if (len && !is_dir_sep(git_dir[len-1]))
-		buf[len++] = '/';
-	len += vsnprintf(buf + len, n - len, fmt, args);
-	if (len >= n)
-		goto bad;
-	return cleanup_path(buf);
-bad:
-	strlcpy(buf, bad_path, n);
-	return buf;
+	strbuf_addstr(buf, git_dir);
+	if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
+		strbuf_addch(buf, '/');
+	strbuf_vaddf(buf, fmt, args);
+	strbuf_cleanup_path(buf);
 }
 
 char *git_snpath(char *buf, size_t n, const char *fmt, ...)
 {
-	char *ret;
+	struct strbuf sb = STRBUF_INIT;
 	va_list args;
 	va_start(args, fmt);
-	ret = vsnpath(buf, n, fmt, args);
+	vsnpath(&sb, fmt, args);
 	va_end(args);
-	return ret;
+	if (sb.len >= n)
+		strlcpy(buf, bad_path, n);
+	else
+		memcpy(buf, sb.buf, sb.len + 1);
+	strbuf_release(&sb);
+	return buf;
 }
 
 char *git_pathdup(const char *fmt, ...)
 {
-	char path[PATH_MAX], *ret;
+	struct strbuf path = STRBUF_INIT;
 	va_list args;
 	va_start(args, fmt);
-	ret = vsnpath(path, sizeof(path), fmt, args);
+	vsnpath(&path, fmt, args);
 	va_end(args);
-	return xstrdup(ret);
+	return strbuf_detach(&path, NULL);
 }
 
 char *mkpathdup(const char *fmt, ...)
 {
-	char *path;
 	struct strbuf sb = STRBUF_INIT;
 	va_list args;
-
 	va_start(args, fmt);
 	strbuf_vaddf(&sb, fmt, args);
 	va_end(args);
-	path = xstrdup(cleanup_path(sb.buf));
-
-	strbuf_release(&sb);
-	return path;
+	strbuf_cleanup_path(&sb);
+	return strbuf_detach(&sb, NULL);
 }
 
 char *mkpath(const char *fmt, ...)
 {
 	va_list args;
-	unsigned len;
-	char *pathname = get_pathname();
-
+	struct strbuf *pathname = get_pathname();
 	va_start(args, fmt);
-	len = vsnprintf(pathname, PATH_MAX, fmt, args);
+	strbuf_vaddf(pathname, fmt, args);
 	va_end(args);
-	if (len >= PATH_MAX)
-		return bad_path;
-	return cleanup_path(pathname);
+	return cleanup_path(pathname->buf);
 }
 
 char *git_path(const char *fmt, ...)
 {
-	char *pathname = get_pathname();
+	struct strbuf *pathname = get_pathname();
 	va_list args;
-	char *ret;
-
 	va_start(args, fmt);
-	ret = vsnpath(pathname, PATH_MAX, fmt, args);
+	vsnpath(pathname, fmt, args);
 	va_end(args);
-	return ret;
+	return pathname->buf;
 }
 
 void home_config_paths(char **global, char **xdg, char *file)
@@ -158,41 +154,27 @@ void home_config_paths(char **global, char **xdg, char *file)
 
 char *git_path_submodule(const char *path, const char *fmt, ...)
 {
-	char *pathname = get_pathname();
-	struct strbuf buf = STRBUF_INIT;
+	struct strbuf *buf = get_pathname();
 	const char *git_dir;
 	va_list args;
-	unsigned len;
-
-	len = strlen(path);
-	if (len > PATH_MAX-100)
-		return bad_path;
 
-	strbuf_addstr(&buf, path);
-	if (len && path[len-1] != '/')
-		strbuf_addch(&buf, '/');
-	strbuf_addstr(&buf, ".git");
+	strbuf_addstr(buf, path);
+	if (buf->len && buf->buf[buf->len - 1] != '/')
+		strbuf_addch(buf, '/');
+	strbuf_addstr(buf, ".git");
 
-	git_dir = read_gitfile(buf.buf);
+	git_dir = read_gitfile(buf->buf);
 	if (git_dir) {
-		strbuf_reset(&buf);
-		strbuf_addstr(&buf, git_dir);
+		strbuf_reset(buf);
+		strbuf_addstr(buf, git_dir);
 	}
-	strbuf_addch(&buf, '/');
-
-	if (buf.len >= PATH_MAX)
-		return bad_path;
-	memcpy(pathname, buf.buf, buf.len + 1);
-
-	strbuf_release(&buf);
-	len = strlen(pathname);
+	strbuf_addch(buf, '/');
 
 	va_start(args, fmt);
-	len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
+	strbuf_vaddf(buf, fmt, args);
 	va_end(args);
-	if (len >= PATH_MAX)
-		return bad_path;
-	return cleanup_path(pathname);
+	strbuf_cleanup_path(buf);
+	return buf->buf;
 }
 
 int validate_headref(const char *path)
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 02/27] Convert git_snpath() to strbuf_git_path()
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
  2014-03-01 12:12   ` [PATCH v4 01/27] path.c: make get_pathname() return strbuf instead of static buffer Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-03  0:02     ` Eric Sunshine
  2014-03-01 12:12   ` [PATCH v4 03/27] path.c: rename vsnpath() to do_git_path() Nguyễn Thái Ngọc Duy
                     ` (25 subsequent siblings)
  27 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

In the previous patch, git_snpath() is modified to allocate a new
strbuf buffer because vsnpath() needs that. But that makes it awkward
because git_snpath() receives a pre-allocated buffer from outside and
has to copy data back. Rename it to strbuf_git_path() and make it
receive strbuf directly.

The conversion from git_snpath() to git_path() in
update_refs_for_switch() is safe because that function does not keep
any pointer to the round-robin buffer pool allocated by
get_pathname().

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 22 +++++++++---------
 cache.h            |  4 ++--
 path.c             | 11 ++-------
 refs.c             | 66 +++++++++++++++++++++++++++++++++++-------------------
 refs.h             |  2 +-
 5 files changed, 58 insertions(+), 47 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 5df3837..0570e41 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -585,18 +585,20 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 		if (opts->new_orphan_branch) {
 			if (opts->new_branch_log && !log_all_ref_updates) {
 				int temp;
-				char log_file[PATH_MAX];
+				struct strbuf log_file = STRBUF_INIT;
 				char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
+				int ret;
 
 				temp = log_all_ref_updates;
 				log_all_ref_updates = 1;
-				if (log_ref_setup(ref_name, log_file, sizeof(log_file))) {
+				ret = log_ref_setup(ref_name, &log_file);
+				log_all_ref_updates = temp;
+				strbuf_release(&log_file);
+				if (ret) {
 					fprintf(stderr, _("Can not do reflog for '%s'\n"),
 					    opts->new_orphan_branch);
-					log_all_ref_updates = temp;
 					return;
 				}
-				log_all_ref_updates = temp;
 			}
 		}
 		else
@@ -651,14 +653,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 					new->name);
 			}
 		}
-		if (old->path && old->name) {
-			char log_file[PATH_MAX], ref_file[PATH_MAX];
-
-			git_snpath(log_file, sizeof(log_file), "logs/%s", old->path);
-			git_snpath(ref_file, sizeof(ref_file), "%s", old->path);
-			if (!file_exists(ref_file) && file_exists(log_file))
-				remove_path(log_file);
-		}
+		if (old->path && old->name &&
+		    !file_exists(git_path("%s", old->path)) &&
+		     file_exists(git_path("logs/%s", old->path)))
+			remove_path(git_path("logs/%s", old->path));
 	}
 	remove_branch_state();
 	strbuf_release(&msg);
diff --git a/cache.h b/cache.h
index dc040fb..8d3697e 100644
--- a/cache.h
+++ b/cache.h
@@ -646,8 +646,8 @@ extern int check_repository_format(void);
 
 extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 	__attribute__((format (printf, 3, 4)));
-extern char *git_snpath(char *buf, size_t n, const char *fmt, ...)
-	__attribute__((format (printf, 3, 4)));
+extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
+	__attribute__((format (printf, 2, 3)));
 extern char *git_pathdup(const char *fmt, ...)
 	__attribute__((format (printf, 1, 2)));
 extern char *mkpathdup(const char *fmt, ...)
diff --git a/path.c b/path.c
index 5346700..b52a16f 100644
--- a/path.c
+++ b/path.c
@@ -70,19 +70,12 @@ static void vsnpath(struct strbuf *buf, const char *fmt, va_list args)
 	strbuf_cleanup_path(buf);
 }
 
-char *git_snpath(char *buf, size_t n, const char *fmt, ...)
+void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 {
-	struct strbuf sb = STRBUF_INIT;
 	va_list args;
 	va_start(args, fmt);
-	vsnpath(&sb, fmt, args);
+	vsnpath(sb, fmt, args);
 	va_end(args);
-	if (sb.len >= n)
-		strlcpy(buf, bad_path, n);
-	else
-		memcpy(buf, sb.buf, sb.len + 1);
-	strbuf_release(&sb);
-	return buf;
 }
 
 char *git_pathdup(const char *fmt, ...)
diff --git a/refs.c b/refs.c
index 89228e2..434bd5e 100644
--- a/refs.c
+++ b/refs.c
@@ -1325,10 +1325,12 @@ static const char *handle_missing_loose_ref(const char *refname,
 
 const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)
 {
+	struct strbuf sb_path = STRBUF_INIT;
 	int depth = MAXDEPTH;
 	ssize_t len;
 	char buffer[256];
 	static char refname_buffer[256];
+	const char *ret;
 
 	if (flag)
 		*flag = 0;
@@ -1337,15 +1339,17 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 		return NULL;
 
 	for (;;) {
-		char path[PATH_MAX];
+		const char *path;
 		struct stat st;
 		char *buf;
 		int fd;
 
 		if (--depth < 0)
-			return NULL;
+			goto fail;
 
-		git_snpath(path, sizeof(path), "%s", refname);
+		strbuf_reset(&sb_path);
+		strbuf_git_path(&sb_path, "%s", refname);
+		path = sb_path.buf;
 
 		/*
 		 * We might have to loop back here to avoid a race
@@ -1359,10 +1363,11 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 	stat_ref:
 		if (lstat(path, &st) < 0) {
 			if (errno == ENOENT)
-				return handle_missing_loose_ref(refname, sha1,
-								reading, flag);
+				ret = handle_missing_loose_ref(refname, sha1,
+							       reading, flag);
 			else
-				return NULL;
+				ret = NULL;
+			goto done;
 		}
 
 		/* Follow "normalized" - ie "refs/.." symlinks by hand */
@@ -1373,7 +1378,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 					/* inconsistent with lstat; retry */
 					goto stat_ref;
 				else
-					return NULL;
+					goto fail;
 			}
 			buffer[len] = 0;
 			if (starts_with(buffer, "refs/") &&
@@ -1389,7 +1394,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 		/* Is it a directory? */
 		if (S_ISDIR(st.st_mode)) {
 			errno = EISDIR;
-			return NULL;
+			goto fail;
 		}
 
 		/*
@@ -1402,12 +1407,13 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 				/* inconsistent with lstat; retry */
 				goto stat_ref;
 			else
-				return NULL;
+				goto fail;
 		}
+
 		len = read_in_full(fd, buffer, sizeof(buffer)-1);
 		close(fd);
 		if (len < 0)
-			return NULL;
+			goto fail;
 		while (len && isspace(buffer[len-1]))
 			len--;
 		buffer[len] = '\0';
@@ -1424,9 +1430,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 			    (buffer[40] != '\0' && !isspace(buffer[40]))) {
 				if (flag)
 					*flag |= REF_ISBROKEN;
-				return NULL;
+				goto fail;
 			}
-			return refname;
+			ret = refname;
+			goto done;
 		}
 		if (flag)
 			*flag |= REF_ISSYMREF;
@@ -1436,10 +1443,15 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 		if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
 			if (flag)
 				*flag |= REF_ISBROKEN;
-			return NULL;
+			goto fail;
 		}
 		refname = strcpy(refname_buffer, buf);
 	}
+fail:
+	ret = NULL;
+done:
+	strbuf_release(&sb_path);
+	return ret;
 }
 
 char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag)
@@ -2717,17 +2729,19 @@ static int copy_msg(char *buf, const char *msg)
 	return cp - buf;
 }
 
-int log_ref_setup(const char *refname, char *logfile, int bufsize)
+int log_ref_setup(const char *refname, struct strbuf *sb_logfile)
 {
 	int logfd, oflags = O_APPEND | O_WRONLY;
+	const char *logfile;
 
-	git_snpath(logfile, bufsize, "logs/%s", refname);
+	strbuf_git_path(sb_logfile, "logs/%s", refname);
+	logfile = sb_logfile->buf;
 	if (log_all_ref_updates &&
 	    (starts_with(refname, "refs/heads/") ||
 	     starts_with(refname, "refs/remotes/") ||
 	     starts_with(refname, "refs/notes/") ||
 	     !strcmp(refname, "HEAD"))) {
-		if (safe_create_leading_directories(logfile) < 0)
+		if (safe_create_leading_directories(sb_logfile->buf) < 0)
 			return error("unable to create directory for %s",
 				     logfile);
 		oflags |= O_CREAT;
@@ -2762,20 +2776,22 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,
 	int logfd, result, written, oflags = O_APPEND | O_WRONLY;
 	unsigned maxlen, len;
 	int msglen;
-	char log_file[PATH_MAX];
+	struct strbuf sb_log_file = STRBUF_INIT;
+	const char *log_file;
 	char *logrec;
 	const char *committer;
 
 	if (log_all_ref_updates < 0)
 		log_all_ref_updates = !is_bare_repository();
 
-	result = log_ref_setup(refname, log_file, sizeof(log_file));
+	result = log_ref_setup(refname, &sb_log_file);
 	if (result)
-		return result;
+		goto done;
+	log_file = sb_log_file.buf;
 
 	logfd = open(log_file, oflags);
 	if (logfd < 0)
-		return 0;
+		goto done;
 	msglen = msg ? strlen(msg) : 0;
 	committer = git_committer_info(0);
 	maxlen = strlen(committer) + msglen + 100;
@@ -2788,9 +2804,13 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,
 		len += copy_msg(logrec + len - 1, msg) - 1;
 	written = len <= maxlen ? write_in_full(logfd, logrec, len) : -1;
 	free(logrec);
-	if (close(logfd) != 0 || written != len)
-		return error("Unable to append to %s", log_file);
-	return 0;
+	if (close(logfd) != 0 || written != len) {
+		error("Unable to append to %s", log_file);
+		result = -1;
+	}
+done:
+	strbuf_release(&sb_log_file);
+	return result;
 }
 
 static int is_branch(const char *refname)
diff --git a/refs.h b/refs.h
index 87a1a79..783033a 100644
--- a/refs.h
+++ b/refs.h
@@ -166,7 +166,7 @@ extern void unlock_ref(struct ref_lock *lock);
 extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
 
 /** Setup reflog before using. **/
-int log_ref_setup(const char *ref_name, char *logfile, int bufsize);
+int log_ref_setup(const char *ref_name, struct strbuf *logfile);
 
 /** Reads log for the value of ref during at_time. **/
 extern int read_ref_at(const char *refname, unsigned long at_time, int cnt,
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 03/27] path.c: rename vsnpath() to do_git_path()
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
  2014-03-01 12:12   ` [PATCH v4 01/27] path.c: make get_pathname() return strbuf instead of static buffer Nguyễn Thái Ngọc Duy
  2014-03-01 12:12   ` [PATCH v4 02/27] Convert git_snpath() to strbuf_git_path() Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-01 12:12   ` [PATCH v4 04/27] path.c: group git_path(), git_pathdup() and strbuf_git_path() together Nguyễn Thái Ngọc Duy
                     ` (24 subsequent siblings)
  27 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

The name vsnpath() gives an impression that this is general path
handling function. It's not. This is the underlying implementation of
git_path(), git_pathdup() and strbuf_git_path() which will prefix
$GIT_DIR in the result string.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 path.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/path.c b/path.c
index b52a16f..29048fe 100644
--- a/path.c
+++ b/path.c
@@ -60,7 +60,7 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 	return cleanup_path(buf);
 }
 
-static void vsnpath(struct strbuf *buf, const char *fmt, va_list args)
+static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
 {
 	const char *git_dir = get_git_dir();
 	strbuf_addstr(buf, git_dir);
@@ -74,7 +74,7 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 {
 	va_list args;
 	va_start(args, fmt);
-	vsnpath(sb, fmt, args);
+	do_git_path(sb, fmt, args);
 	va_end(args);
 }
 
@@ -83,7 +83,7 @@ char *git_pathdup(const char *fmt, ...)
 	struct strbuf path = STRBUF_INIT;
 	va_list args;
 	va_start(args, fmt);
-	vsnpath(&path, fmt, args);
+	do_git_path(&path, fmt, args);
 	va_end(args);
 	return strbuf_detach(&path, NULL);
 }
@@ -114,7 +114,7 @@ char *git_path(const char *fmt, ...)
 	struct strbuf *pathname = get_pathname();
 	va_list args;
 	va_start(args, fmt);
-	vsnpath(pathname, fmt, args);
+	do_git_path(pathname, fmt, args);
 	va_end(args);
 	return pathname->buf;
 }
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 04/27] path.c: group git_path(), git_pathdup() and strbuf_git_path() together
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (2 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 03/27] path.c: rename vsnpath() to do_git_path() Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-01 12:12   ` [PATCH v4 05/27] Make git_path() aware of file relocation in $GIT_DIR Nguyễn Thái Ngọc Duy
                     ` (23 subsequent siblings)
  27 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 path.c | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/path.c b/path.c
index 29048fe..ccd7228 100644
--- a/path.c
+++ b/path.c
@@ -78,6 +78,16 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 	va_end(args);
 }
 
+char *git_path(const char *fmt, ...)
+{
+	struct strbuf *pathname = get_pathname();
+	va_list args;
+	va_start(args, fmt);
+	do_git_path(pathname, fmt, args);
+	va_end(args);
+	return pathname->buf;
+}
+
 char *git_pathdup(const char *fmt, ...)
 {
 	struct strbuf path = STRBUF_INIT;
@@ -109,16 +119,6 @@ char *mkpath(const char *fmt, ...)
 	return cleanup_path(pathname->buf);
 }
 
-char *git_path(const char *fmt, ...)
-{
-	struct strbuf *pathname = get_pathname();
-	va_list args;
-	va_start(args, fmt);
-	do_git_path(pathname, fmt, args);
-	va_end(args);
-	return pathname->buf;
-}
-
 void home_config_paths(char **global, char **xdg, char *file)
 {
 	char *xdg_home = getenv("XDG_CONFIG_HOME");
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 05/27] Make git_path() aware of file relocation in $GIT_DIR
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (3 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 04/27] path.c: group git_path(), git_pathdup() and strbuf_git_path() together Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-03  1:34     ` Eric Sunshine
  2014-03-01 12:12   ` [PATCH v4 06/27] *.sh: respect $GIT_INDEX_FILE Nguyễn Thái Ngọc Duy
                     ` (22 subsequent siblings)
  27 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

We allow the user to relocate certain paths out of $GIT_DIR via
environment variables, e.g. GIT_OBJECT_DIRECTORY, GIT_INDEX_FILE and
GIT_GRAFT_FILE. All callers are not supposed to use git_path() or
git_pathdup() to get those paths. Instead they must use
get_object_directory(), get_index_file() and get_graft_file()
respectively. This is inconvenient and could be missed in review
(there's git_path("objects/info/alternates") somewhere in
sha1_file.c).

This patch makes git_path() and git_pathdup() understand those
environment variables. So if you set GIT_OBJECT_DIRECTORY to /foo/bar,
git_path("objects/abc") should return /tmp/bar/abc. The same is done
for the two remaining env variables.

"git rev-parse --git-path" is the wrapper for script use.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-rev-parse.txt |  5 +++++
 builtin/rev-parse.c             |  7 +++++++
 cache.h                         |  1 +
 environment.c                   |  9 ++++++--
 path.c                          | 46 +++++++++++++++++++++++++++++++++++++++++
 t/t0060-path-utils.sh           | 19 +++++++++++++++++
 6 files changed, 85 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 0d2cdcd..33e4e90 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -232,6 +232,11 @@ print a message to stderr and exit with nonzero status.
 	repository.  If <path> is a gitfile then the resolved path
 	to the real repository is printed.
 
+--git-path <path>::
+	Resolve "$GIT_DIR/<path>" and takes other path relocation
+	variables such as $GIT_OBJECT_DIRECTORY,
+	$GIT_INDEX_FILE... into account.
+
 --show-cdup::
 	When the command is invoked from a subdirectory, show the
 	path of the top-level directory relative to the current
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index aaeb611..e50bc65 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -518,6 +518,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
 	for (i = 1; i < argc; i++) {
 		const char *arg = argv[i];
 
+		if (!strcmp(arg, "--git-path")) {
+			if (!argv[i + 1])
+				die("--git-path requires an argument");
+			puts(git_path("%s", argv[i + 1]));
+			i++;
+			continue;
+		}
 		if (as_is) {
 			if (show_file(arg, output_prefix) && as_is < 2)
 				verify_filename(prefix, arg, 0);
diff --git a/cache.h b/cache.h
index 8d3697e..6c08e4a 100644
--- a/cache.h
+++ b/cache.h
@@ -585,6 +585,7 @@ extern int fsync_object_files;
 extern int core_preload_index;
 extern int core_apply_sparse_checkout;
 extern int precomposed_unicode;
+extern int git_db_env, git_index_env, git_graft_env;
 
 /*
  * The character that begins a commented line in user-editable file
diff --git a/environment.c b/environment.c
index 4a3437d..f513479 100644
--- a/environment.c
+++ b/environment.c
@@ -82,6 +82,7 @@ static size_t namespace_len;
 
 static const char *git_dir;
 static char *git_object_dir, *git_index_file, *git_graft_file;
+int git_db_env, git_index_env, git_graft_env;
 
 /*
  * Repository-local GIT_* environment variables; see cache.h for details.
@@ -137,15 +138,19 @@ static void setup_git_env(void)
 	if (!git_object_dir) {
 		git_object_dir = xmalloc(strlen(git_dir) + 9);
 		sprintf(git_object_dir, "%s/objects", git_dir);
-	}
+	} else
+		git_db_env = 1;
 	git_index_file = getenv(INDEX_ENVIRONMENT);
 	if (!git_index_file) {
 		git_index_file = xmalloc(strlen(git_dir) + 7);
 		sprintf(git_index_file, "%s/index", git_dir);
-	}
+	} else
+		git_index_env = 1;
 	git_graft_file = getenv(GRAFT_ENVIRONMENT);
 	if (!git_graft_file)
 		git_graft_file = git_pathdup("info/grafts");
+	else
+		git_graft_env = 1;
 	if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
 		read_replace_refs = 0;
 	namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));
diff --git a/path.c b/path.c
index ccd7228..e020530 100644
--- a/path.c
+++ b/path.c
@@ -60,13 +60,59 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 	return cleanup_path(buf);
 }
 
+static int dir_prefix(const char *buf, const char *dir)
+{
+	int len = strlen(dir);
+	return !strncmp(buf, dir, len) &&
+		(is_dir_sep(buf[len]) || buf[len] == '\0');
+}
+
+/* $buf =~ m|$dir/+$file| but without regex */
+static int is_dir_file(const char *buf, const char *dir, const char *file)
+{
+	int len = strlen(dir);
+	if (strncmp(buf, dir, len) || !is_dir_sep(buf[len]))
+		return 0;
+	while (is_dir_sep(buf[len]))
+		len++;
+	return !strcmp(buf + len, file);
+}
+
+static void replace_dir(struct strbuf *buf, int len, const char *newdir)
+{
+	int newlen = strlen(newdir);
+	int need_sep = (buf->buf[len] && !is_dir_sep(buf->buf[len])) &&
+		!is_dir_sep(newdir[newlen - 1]);
+	if (need_sep)
+		len--;	 /* keep one char, to be replaced with '/'  */
+	strbuf_splice(buf, 0, len, newdir, newlen);
+	if (need_sep)
+		buf->buf[newlen] = '/';
+}
+
+static void adjust_git_path(struct strbuf *buf, int git_dir_len)
+{
+	const char *base = buf->buf + git_dir_len;
+	if (git_graft_env && is_dir_file(base, "info", "grafts"))
+		strbuf_splice(buf, 0, buf->len,
+			      get_graft_file(), strlen(get_graft_file()));
+	else if (git_index_env && !strcmp(base, "index"))
+		strbuf_splice(buf, 0, buf->len,
+			      get_index_file(), strlen(get_index_file()));
+	else if (git_db_env && dir_prefix(base, "objects"))
+		replace_dir(buf, git_dir_len + 7, get_object_directory());
+}
+
 static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
 {
 	const char *git_dir = get_git_dir();
+	int gitdir_len;
 	strbuf_addstr(buf, git_dir);
 	if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
 		strbuf_addch(buf, '/');
+	gitdir_len = buf->len;
 	strbuf_vaddf(buf, fmt, args);
+	adjust_git_path(buf, gitdir_len);
 	strbuf_cleanup_path(buf);
 }
 
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index 07c10c8..1d29901 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -19,6 +19,14 @@ relative_path() {
 	"test \"\$(test-path-utils relative_path '$1' '$2')\" = '$expected'"
 }
 
+test_git_path() {
+	test_expect_success "git-path $1 $2 => $3" "
+		$1 git rev-parse --git-path $2 >actual &&
+		echo $3 >expect &&
+		test_cmp expect actual
+	"
+}
+
 # On Windows, we are using MSYS's bash, which mangles the paths.
 # Absolute paths are anchored at the MSYS installation directory,
 # which means that the path / accounts for this many characters:
@@ -223,4 +231,15 @@ relative_path "<null>"		"<empty>"	./
 relative_path "<null>"		"<null>"	./
 relative_path "<null>"		/foo/a/b	./
 
+test_git_path A=B                info/grafts .git/info/grafts
+test_git_path GIT_GRAFT_FILE=foo info/grafts foo
+test_git_path GIT_GRAFT_FILE=foo info/////grafts foo
+test_git_path GIT_INDEX_FILE=foo index foo
+test_git_path GIT_INDEX_FILE=foo index/foo .git/index/foo
+test_git_path GIT_INDEX_FILE=foo index2 .git/index2
+test_expect_success 'setup fake objects directory foo' 'mkdir foo'
+test_git_path GIT_OBJECT_DIRECTORY=foo objects foo
+test_git_path GIT_OBJECT_DIRECTORY=foo objects/foo foo/foo
+test_git_path GIT_OBJECT_DIRECTORY=foo objects2 .git/objects2
+
 test_done
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 06/27] *.sh: respect $GIT_INDEX_FILE
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (4 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 05/27] Make git_path() aware of file relocation in $GIT_DIR Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-01 12:12   ` [PATCH v4 07/27] reflog: avoid constructing .lock path with git_path Nguyễn Thái Ngọc Duy
                     ` (21 subsequent siblings)
  27 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 git-pull.sh  | 2 +-
 git-stash.sh | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/git-pull.sh b/git-pull.sh
index 0a5aa2c..c9dc9ba 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -218,7 +218,7 @@ test true = "$rebase" && {
 	if ! git rev-parse -q --verify HEAD >/dev/null
 	then
 		# On an unborn branch
-		if test -f "$GIT_DIR/index"
+		if test -f "`git rev-parse --git-path index`"
 		then
 			die "$(gettext "updating an unborn branch with changes added to the index")"
 		fi
diff --git a/git-stash.sh b/git-stash.sh
index f0a94ab..ae7d16e 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -20,7 +20,7 @@ require_work_tree
 cd_to_toplevel
 
 TMP="$GIT_DIR/.git-stash.$$"
-TMPindex=${GIT_INDEX_FILE-"$GIT_DIR/index"}.stash.$$
+TMPindex=${GIT_INDEX_FILE-"`git rev-parse --git-path index`"}.stash.$$
 trap 'rm -f "$TMP-"* "$TMPindex"' 0
 
 ref_stash=refs/stash
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 07/27] reflog: avoid constructing .lock path with git_path
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (5 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 06/27] *.sh: respect $GIT_INDEX_FILE Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-01 12:12   ` [PATCH v4 08/27] fast-import: use git_path() for accessing .git dir instead of get_git_dir() Nguyễn Thái Ngọc Duy
                     ` (20 subsequent siblings)
  27 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

git_path() soon understands the path given to it and can transform the
path instead of just prepending $GIT_DIR. So given path "abc",
git_path() may return "$GIT_DIR/abc". But given path "def", git_path()
may return "$GIT_DIR/ghi".

Giving path "def.lock" to git_path() may confuse it and make it
believe "def.lock" should not be transformed because the signature is
"def.lock" not "def". But we want the lock file to have the same base
name with the locked file (e.g. "ghi.lock", not "def.lock"). It's best
to append ".lock" after git_path() has done its conversion.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/reflog.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/reflog.c b/builtin/reflog.c
index 852cff6..ccf2cf6 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -372,7 +372,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
 	if (!file_exists(log_file))
 		goto finish;
 	if (!cmd->dry_run) {
-		newlog_path = git_pathdup("logs/%s.lock", ref);
+		newlog_path = mkpathdup("%s.lock", log_file);
 		cb.newlog = fopen(newlog_path, "w");
 	}
 
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 08/27] fast-import: use git_path() for accessing .git dir instead of get_git_dir()
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (6 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 07/27] reflog: avoid constructing .lock path with git_path Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-01 12:12   ` [PATCH v4 09/27] commit: use SEQ_DIR instead of hardcoding "sequencer" Nguyễn Thái Ngọc Duy
                     ` (19 subsequent siblings)
  27 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

This allows git_path() to redirect info/fast-import to another place
if needed

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 fast-import.c | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/fast-import.c b/fast-import.c
index 4fd18a3..08a1e78 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -3125,12 +3125,9 @@ static void parse_progress(void)
 
 static char* make_fast_import_path(const char *path)
 {
-	struct strbuf abs_path = STRBUF_INIT;
-
 	if (!relative_marks_paths || is_absolute_path(path))
 		return xstrdup(path);
-	strbuf_addf(&abs_path, "%s/info/fast-import/%s", get_git_dir(), path);
-	return strbuf_detach(&abs_path, NULL);
+	return xstrdup(git_path("info/fast-import/%s", path));
 }
 
 static void option_import_marks(const char *marks,
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 09/27] commit: use SEQ_DIR instead of hardcoding "sequencer"
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (7 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 08/27] fast-import: use git_path() for accessing .git dir instead of get_git_dir() Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-01 12:12   ` [PATCH v4 10/27] Add new environment variable $GIT_COMMON_DIR Nguyễn Thái Ngọc Duy
                     ` (18 subsequent siblings)
  27 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/commit.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/commit.c b/builtin/commit.c
index 3767478..ee3ac10 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -155,7 +155,7 @@ static void determine_whence(struct wt_status *s)
 		whence = FROM_MERGE;
 	else if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
 		whence = FROM_CHERRY_PICK;
-		if (file_exists(git_path("sequencer")))
+		if (file_exists(git_path(SEQ_DIR)))
 			sequencer_in_use = 1;
 	}
 	else
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 10/27] Add new environment variable $GIT_COMMON_DIR
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (8 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 09/27] commit: use SEQ_DIR instead of hardcoding "sequencer" Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-03  7:29     ` Eric Sunshine
  2014-03-01 12:12   ` [PATCH v4 11/27] git-sh-setup.sh: use rev-parse --git-path to get $GIT_DIR/objects Nguyễn Thái Ngọc Duy
                     ` (17 subsequent siblings)
  27 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

This variable is intended to support multiple working directories
attached to a repository. Such a repository may have a main working
directory, created by either "git init" or "git clone" and one or more
linked working directories. These working directories and the main
repository share the same repository directory.

In linked working directories, $GIT_COMMON_DIR must be defined to point
to the real repository directory and $GIT_DIR points to an unused
subdirectory inside $GIT_COMMON_DIR. File locations inside the
repository are reorganized from the linked worktree view point:

 - worktree-specific such as HEAD, logs/HEAD, index, other top-level
   refs and unrecognized files are from $GIT_DIR.

 - the rest like objects, refs, info, hooks, packed-refs, shallow...
   are from $GIT_COMMON_DIR

Scripts are supposed to retrieve paths in $GIT_DIR with "git rev-parse
--git-path", which will take care of "$GIT_DIR vs $GIT_COMMON_DIR"
business.

The redirection is done by git_path(), git_pathdup() and
strbuf_git_path(). The selected list of paths goes to $GIT_COMMON_DIR,
not the other way around in case a developer adds a new
worktree-specific file and it's accidentally promoted to be shared
across repositories (this includes unknown files added by third party
commands)

The list of known files that belong to $GIT_DIR are:

ADD_EDIT.patch BISECT_ANCESTORS_OK BISECT_EXPECTED_REV BISECT_LOG
BISECT_NAMES CHERRY_PICK_HEAD COMMIT_MSG FETCH_HEAD HEAD MERGE_HEAD
MERGE_MODE MERGE_RR NOTES_EDITMSG NOTES_MERGE_WORKTREE ORIG_HEAD
REVERT_HEAD SQUASH_MSG TAG_EDITMSG fast_import_crash_* logs/HEAD
next-index-* rebase-apply rebase-merge rsync-refs-* sequencer/*
shallow_*

Path mapping is NOT done for git_path_submodule(). Multi-checkouts are
not supported as submodules.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git.txt                  |  8 +++++++
 Documentation/gitrepository-layout.txt | 42 ++++++++++++++++++++++++++--------
 cache.h                                |  4 +++-
 environment.c                          | 19 +++++++++++----
 path.c                                 | 29 +++++++++++++++++++++++
 t/t0060-path-utils.sh                  | 15 ++++++++++++
 6 files changed, 103 insertions(+), 14 deletions(-)

diff --git a/Documentation/git.txt b/Documentation/git.txt
index 02bbc08..b094b1f 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -773,6 +773,14 @@ Git so take care if using Cogito etc.
 	an explicit repository directory set via 'GIT_DIR' or on the
 	command line.
 
+'GIT_COMMON_DIR'::
+	If this variable is set to a path, non-worktree files that are
+	normally in $GIT_DIR will be taken from this path
+	instead. Worktree-specific files such as HEAD or index are
+	taken from $GIT_DIR. See linkgit:gitrepository-layout[5] for
+	details. This variable has lower precedence than other path
+	variables such as GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY...
+
 Git Commits
 ~~~~~~~~~~~
 'GIT_AUTHOR_NAME'::
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index aa03882..10672a1 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -46,6 +46,9 @@ of incomplete object store is not suitable to be published for
 use with dumb transports but otherwise is OK as long as
 `objects/info/alternates` points at the object stores it
 borrows from.
++
+This directory is ignored $GIT_COMMON_DIR is set and
+"$GIT_COMMON_DIR/objects" will be used instead.
 
 objects/[0-9a-f][0-9a-f]::
 	A newly created object is stored in its own file.
@@ -92,7 +95,8 @@ refs::
 	References are stored in subdirectories of this
 	directory.  The 'git prune' command knows to preserve
 	objects reachable from refs found in this directory and
-	its subdirectories.
+	its subdirectories. This directory is ignored $GIT_COMMON_DIR
+	is set and "$GIT_COMMON_DIR/refs" will be used instead.
 
 refs/heads/`name`::
 	records tip-of-the-tree commit objects of branch `name`
@@ -114,7 +118,8 @@ refs/replace/`<obj-sha1>`::
 packed-refs::
 	records the same information as refs/heads/, refs/tags/,
 	and friends record in a more efficient way.  See
-	linkgit:git-pack-refs[1].
+	linkgit:git-pack-refs[1]. This file is ignored $GIT_COMMON_DIR
+	is set and "$GIT_COMMON_DIR/packed-refs" will be used instead.
 
 HEAD::
 	A symref (see glossary) to the `refs/heads/` namespace
@@ -133,6 +138,11 @@ being a symref to point at the current branch.  Such a state
 is often called 'detached HEAD.'  See linkgit:git-checkout[1]
 for details.
 
+config::
+	Repository specific configuration file. This file is ignored
+	$GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/config" will be
+	used instead.
+
 branches::
 	A slightly deprecated way to store shorthands to be used
 	to specify a URL to 'git fetch', 'git pull' and 'git push'.
@@ -140,7 +150,10 @@ branches::
 	'name' can be given to these commands in place of
 	'repository' argument.  See the REMOTES section in
 	linkgit:git-fetch[1] for details.  This mechanism is legacy
-	and not likely to be found in modern repositories.
+	and not likely to be found in modern repositories. This
+	directory is ignored $GIT_COMMON_DIR is set and
+	"$GIT_COMMON_DIR/branches" will be used instead.
+
 
 hooks::
 	Hooks are customization scripts used by various Git
@@ -149,7 +162,9 @@ hooks::
 	default.  To enable, the `.sample` suffix has to be
 	removed from the filename by renaming.
 	Read linkgit:githooks[5] for more details about
-	each hook.
+	each hook. This directory is ignored $GIT_COMMON_DIR is set
+	and "$GIT_COMMON_DIR/hooks" will be used instead.
+
 
 index::
 	The current index file for the repository.  It is
@@ -157,7 +172,8 @@ index::
 
 info::
 	Additional information about the repository is recorded
-	in this directory.
+	in this directory. This directory is ignored $GIT_COMMON_DIR
+	is set and "$GIT_COMMON_DIR/index" will be used instead.
 
 info/refs::
 	This file helps dumb transports discover what refs are
@@ -193,12 +209,16 @@ remotes::
 	when interacting with remote repositories via 'git fetch',
 	'git pull' and 'git push' commands.  See the REMOTES section
 	in linkgit:git-fetch[1] for details.  This mechanism is legacy
-	and not likely to be found in modern repositories.
+	and not likely to be found in modern repositories. This
+	directory is ignored $GIT_COMMON_DIR is set and
+	"$GIT_COMMON_DIR/remotes" will be used instead.
 
 logs::
 	Records of changes made to refs are stored in this
 	directory.  See linkgit:git-update-ref[1]
-	for more information.
+	for more information. This directory is ignored
+	$GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/logs" will be used
+	instead.
 
 logs/refs/heads/`name`::
 	Records all changes made to the branch tip named `name`.
@@ -209,10 +229,14 @@ logs/refs/tags/`name`::
 shallow::
 	This is similar to `info/grafts` but is internally used
 	and maintained by shallow clone mechanism.  See `--depth`
-	option to linkgit:git-clone[1] and linkgit:git-fetch[1].
+	option to linkgit:git-clone[1] and linkgit:git-fetch[1]. This
+	file is ignored $GIT_COMMON_DIR is set and
+	"$GIT_COMMON_DIR/shallow" will be used instead.
 
 modules::
-	Contains the git-repositories of the submodules.
+	Contains the git-repositories of the submodules. This
+	directory is ignored $GIT_COMMON_DIR is set and
+	"$GIT_COMMON_DIR/modules" will be used instead.
 
 SEE ALSO
 --------
diff --git a/cache.h b/cache.h
index 6c08e4a..51ade32 100644
--- a/cache.h
+++ b/cache.h
@@ -347,6 +347,7 @@ static inline enum object_type object_type(unsigned int mode)
 
 /* Double-check local_repo_env below if you add to this list. */
 #define GIT_DIR_ENVIRONMENT "GIT_DIR"
+#define GIT_COMMON_DIR_ENVIRONMENT "GIT_COMMON_DIR"
 #define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE"
 #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
 #define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX"
@@ -400,6 +401,7 @@ extern int is_inside_git_dir(void);
 extern char *git_work_tree_cfg;
 extern int is_inside_work_tree(void);
 extern const char *get_git_dir(void);
+extern const char *get_git_common_dir(void);
 extern int is_git_directory(const char *path);
 extern char *get_object_directory(void);
 extern char *get_index_file(void);
@@ -585,7 +587,7 @@ extern int fsync_object_files;
 extern int core_preload_index;
 extern int core_apply_sparse_checkout;
 extern int precomposed_unicode;
-extern int git_db_env, git_index_env, git_graft_env;
+extern int git_db_env, git_index_env, git_graft_env, git_common_dir_env;
 
 /*
  * The character that begins a commented line in user-editable file
diff --git a/environment.c b/environment.c
index f513479..c998120 100644
--- a/environment.c
+++ b/environment.c
@@ -80,9 +80,9 @@ static char *work_tree;
 static const char *namespace;
 static size_t namespace_len;
 
-static const char *git_dir;
+static const char *git_dir, *git_common_dir;
 static char *git_object_dir, *git_index_file, *git_graft_file;
-int git_db_env, git_index_env, git_graft_env;
+int git_db_env, git_index_env, git_graft_env, git_common_dir_env;
 
 /*
  * Repository-local GIT_* environment variables; see cache.h for details.
@@ -134,10 +134,16 @@ static void setup_git_env(void)
 		git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
 	gitfile = read_gitfile(git_dir);
 	git_dir = xstrdup(gitfile ? gitfile : git_dir);
+	git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
+	if (git_common_dir) {
+		git_common_dir_env = 1;
+		git_common_dir = xstrdup(git_common_dir);
+	} else
+		git_common_dir = git_dir;
 	git_object_dir = getenv(DB_ENVIRONMENT);
 	if (!git_object_dir) {
-		git_object_dir = xmalloc(strlen(git_dir) + 9);
-		sprintf(git_object_dir, "%s/objects", git_dir);
+		git_object_dir = xmalloc(strlen(git_common_dir) + 9);
+		sprintf(git_object_dir, "%s/objects", git_common_dir);
 	} else
 		git_db_env = 1;
 	git_index_file = getenv(INDEX_ENVIRONMENT);
@@ -173,6 +179,11 @@ const char *get_git_dir(void)
 	return git_dir;
 }
 
+const char *get_git_common_dir(void)
+{
+	return git_common_dir;
+}
+
 const char *get_git_namespace(void)
 {
 	if (!namespace)
diff --git a/path.c b/path.c
index e020530..6129026 100644
--- a/path.c
+++ b/path.c
@@ -90,6 +90,33 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)
 		buf->buf[newlen] = '/';
 }
 
+static void update_common_dir(struct strbuf *buf, int git_dir_len)
+{
+	const char *common_dir_list[] = {
+		"branches", "hooks", "info", "logs", "lost-found", "modules",
+		"objects", "refs", "remotes", "rr-cache", "svn",
+		NULL
+	};
+	const char *common_top_file_list[] = {
+		"config", "gc.pid", "packed-refs", "shallow", NULL
+	};
+	char *base = buf->buf + git_dir_len;
+	const char **p;
+
+	if (is_dir_file(base, "logs", "HEAD"))
+		return;	/* keep this in $GIT_DIR */
+	for (p = common_dir_list; *p; p++)
+		if (dir_prefix(base, *p)) {
+			replace_dir(buf, git_dir_len, get_git_common_dir());
+			return;
+		}
+	for (p = common_top_file_list; *p; p++)
+		if (!strcmp(base, *p)) {
+			replace_dir(buf, git_dir_len, get_git_common_dir());
+			return;
+		}
+}
+
 static void adjust_git_path(struct strbuf *buf, int git_dir_len)
 {
 	const char *base = buf->buf + git_dir_len;
@@ -101,6 +128,8 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)
 			      get_index_file(), strlen(get_index_file()));
 	else if (git_db_env && dir_prefix(base, "objects"))
 		replace_dir(buf, git_dir_len + 7, get_object_directory());
+	else if (git_common_dir_env)
+		update_common_dir(buf, git_dir_len);
 }
 
 static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index 1d29901..f9a77e4 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -241,5 +241,20 @@ test_expect_success 'setup fake objects directory foo' 'mkdir foo'
 test_git_path GIT_OBJECT_DIRECTORY=foo objects foo
 test_git_path GIT_OBJECT_DIRECTORY=foo objects/foo foo/foo
 test_git_path GIT_OBJECT_DIRECTORY=foo objects2 .git/objects2
+test_expect_success 'setup common repository' 'git --git-dir=bar init'
+test_git_path GIT_COMMON_DIR=bar index                    .git/index
+test_git_path GIT_COMMON_DIR=bar HEAD                     .git/HEAD
+test_git_path GIT_COMMON_DIR=bar logs/HEAD                .git/logs/HEAD
+test_git_path GIT_COMMON_DIR=bar objects                  bar/objects
+test_git_path GIT_COMMON_DIR=bar objects/bar              bar/objects/bar
+test_git_path GIT_COMMON_DIR=bar info/exclude             bar/info/exclude
+test_git_path GIT_COMMON_DIR=bar remotes/bar              bar/remotes/bar
+test_git_path GIT_COMMON_DIR=bar branches/bar             bar/branches/bar
+test_git_path GIT_COMMON_DIR=bar logs/refs/heads/master   bar/logs/refs/heads/master
+test_git_path GIT_COMMON_DIR=bar refs/heads/master        bar/refs/heads/master
+test_git_path GIT_COMMON_DIR=bar hooks/me                 bar/hooks/me
+test_git_path GIT_COMMON_DIR=bar config                   bar/config
+test_git_path GIT_COMMON_DIR=bar packed-refs              bar/packed-refs
+test_git_path GIT_COMMON_DIR=bar shallow                  bar/shallow
 
 test_done
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 11/27] git-sh-setup.sh: use rev-parse --git-path to get $GIT_DIR/objects
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (9 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 10/27] Add new environment variable $GIT_COMMON_DIR Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-01 12:12   ` [PATCH v4 12/27] *.sh: avoid hardcoding $GIT_DIR/hooks/ Nguyễn Thái Ngọc Duy
                     ` (16 subsequent siblings)
  27 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

If $GIT_COMMON_DIR is set, $GIT_OBJECT_DIRECTORY should be
$GIT_COMMON_DIR/objects, not $GIT_DIR/objects. Just let rev-parse
--git-path handle it.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 git-sh-setup.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index fffa3c7..475ca43 100644
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -343,7 +343,7 @@ then
 		echo >&2 "Unable to determine absolute path of git directory"
 		exit 1
 	}
-	: ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"}
+	: ${GIT_OBJECT_DIRECTORY="$(git rev-parse --git-path objects)"}
 fi
 
 peel_committish () {
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 12/27] *.sh: avoid hardcoding $GIT_DIR/hooks/...
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (10 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 11/27] git-sh-setup.sh: use rev-parse --git-path to get $GIT_DIR/objects Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-03  8:31     ` Eric Sunshine
  2014-03-01 12:12   ` [PATCH v4 13/27] git-stash: avoid hardcoding $GIT_DIR/logs/ Nguyễn Thái Ngọc Duy
                     ` (15 subsequent siblings)
  27 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

If $GIT_COMMON_DIR is set, it should be $GIT_COMMON_DIR/hooks/, not
$GIT_DIR/hooks/. Just let rev-parse --git-path handle it.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 git-am.sh                              | 22 +++++++++++-----------
 git-rebase--interactive.sh             |  6 +++---
 git-rebase--merge.sh                   |  6 ++----
 git-rebase.sh                          |  4 ++--
 templates/hooks--applypatch-msg.sample |  4 ++--
 templates/hooks--pre-applypatch.sample |  4 ++--
 6 files changed, 22 insertions(+), 24 deletions(-)

diff --git a/git-am.sh b/git-am.sh
index bbea430..dfa0618 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -803,10 +803,10 @@ To restore the original branch and stop patching run \"\$cmdline --abort\"."
 		continue
 	fi
 
-	if test -x "$GIT_DIR"/hooks/applypatch-msg
+	hook="`git rev-parse --git-path hooks/applypatch-msg`"
+	if test -x "$hook"
 	then
-		"$GIT_DIR"/hooks/applypatch-msg "$dotest/final-commit" ||
-		stop_here $this
+		"$hook" "$dotest/final-commit" || stop_here $this
 	fi
 
 	if test -f "$dotest/final-commit"
@@ -880,9 +880,10 @@ did you forget to use 'git add'?"
 		stop_here_user_resolve $this
 	fi
 
-	if test -x "$GIT_DIR"/hooks/pre-applypatch
+	hook="`git rev-parse --git-path hooks/pre-applypatch`"
+	if test -x "$hook"
 	then
-		"$GIT_DIR"/hooks/pre-applypatch || stop_here $this
+		"$hook" || stop_here $this
 	fi
 
 	tree=$(git write-tree) &&
@@ -908,18 +909,17 @@ did you forget to use 'git add'?"
 		echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten"
 	fi
 
-	if test -x "$GIT_DIR"/hooks/post-applypatch
-	then
-		"$GIT_DIR"/hooks/post-applypatch
-	fi
+	hook="`git rev-parse --git-path hooks/post-applypatch`"
+	test -x "$hook" && "$hook"
 
 	go_next
 done
 
 if test -s "$dotest"/rewritten; then
     git notes copy --for-rewrite=rebase < "$dotest"/rewritten
-    if test -x "$GIT_DIR"/hooks/post-rewrite; then
-	"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+    hook="`git rev-parse --git-path hooks/post-rewrite`"
+    if test -x "$hook"; then
+	"$hook" rebase < "$dotest"/rewritten
     fi
 fi
 
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 43c19e0..d741b04 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -632,9 +632,9 @@ do_next () {
 		git notes copy --for-rewrite=rebase < "$rewritten_list" ||
 		true # we don't care if this copying failed
 	} &&
-	if test -x "$GIT_DIR"/hooks/post-rewrite &&
-		test -s "$rewritten_list"; then
-		"$GIT_DIR"/hooks/post-rewrite rebase < "$rewritten_list"
+	hook="`git rev-parse --git-path hooks/post-rewrite`"
+	if test -x "$hook" && test -s "$rewritten_list"; then
+		"$hook" rebase < "$rewritten_list"
 		true # we don't care if this hook failed
 	fi &&
 	warn "Successfully rebased and updated $head_name."
diff --git a/git-rebase--merge.sh b/git-rebase--merge.sh
index e7d96de..68f5d09 100644
--- a/git-rebase--merge.sh
+++ b/git-rebase--merge.sh
@@ -93,10 +93,8 @@ finish_rb_merge () {
 	if test -s "$state_dir"/rewritten
 	then
 		git notes copy --for-rewrite=rebase <"$state_dir"/rewritten
-		if test -x "$GIT_DIR"/hooks/post-rewrite
-		then
-			"$GIT_DIR"/hooks/post-rewrite rebase <"$state_dir"/rewritten
-		fi
+		hook="`git rev-parse --git-path hooks/post-rewrite`"
+		test -x "$hook" && "$hook" rebase <"$state_dir"/rewritten
 	fi
 	say All done.
 }
diff --git a/git-rebase.sh b/git-rebase.sh
index 8a3efa2..1cf8dba 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -195,9 +195,9 @@ run_specific_rebase () {
 
 run_pre_rebase_hook () {
 	if test -z "$ok_to_skip_pre_rebase" &&
-	   test -x "$GIT_DIR/hooks/pre-rebase"
+	   test -x "`git rev-parse --git-path hooks/pre-rebase`"
 	then
-		"$GIT_DIR/hooks/pre-rebase" ${1+"$@"} ||
+		"`git rev-parse --git-path hooks/pre-rebase`" ${1+"$@"} ||
 		die "$(gettext "The pre-rebase hook refused to rebase.")"
 	fi
 }
diff --git a/templates/hooks--applypatch-msg.sample b/templates/hooks--applypatch-msg.sample
index 8b2a2fe..28b843b 100755
--- a/templates/hooks--applypatch-msg.sample
+++ b/templates/hooks--applypatch-msg.sample
@@ -10,6 +10,6 @@
 # To enable this hook, rename this file to "applypatch-msg".
 
 . git-sh-setup
-test -x "$GIT_DIR/hooks/commit-msg" &&
-	exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
+commitmsg="`git rev-parse --git-path hooks/commit-msg`"
+test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
 :
diff --git a/templates/hooks--pre-applypatch.sample b/templates/hooks--pre-applypatch.sample
index b1f187c..51aa244 100755
--- a/templates/hooks--pre-applypatch.sample
+++ b/templates/hooks--pre-applypatch.sample
@@ -9,6 +9,6 @@
 # To enable this hook, rename this file to "pre-applypatch".
 
 . git-sh-setup
-test -x "$GIT_DIR/hooks/pre-commit" &&
-	exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
+precommit="`git rev-parse --git-path hooks/pre-commit`"
+test -x "$precommit" && exec "$precommit" ${1+"$@"}
 :
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 13/27] git-stash: avoid hardcoding $GIT_DIR/logs/....
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (11 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 12/27] *.sh: avoid hardcoding $GIT_DIR/hooks/ Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-01 15:50     ` Torsten Bögershausen
  2014-03-01 12:12   ` [PATCH v4 14/27] setup.c: convert is_git_directory() to use strbuf Nguyễn Thái Ngọc Duy
                     ` (14 subsequent siblings)
  27 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 git-stash.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/git-stash.sh b/git-stash.sh
index ae7d16e..12d9b37 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -183,7 +183,7 @@ store_stash () {
 	fi
 
 	# Make sure the reflog for stash is kept.
-	: >>"$GIT_DIR/logs/$ref_stash"
+	: >>"`git rev-parse --git-path logs/$ref_stash`"
 	git update-ref -m "$stash_msg" $ref_stash $w_commit
 	ret=$?
 	test $ret != 0 && test -z $quiet &&
@@ -258,7 +258,7 @@ save_stash () {
 		say "$(gettext "No local changes to save")"
 		exit 0
 	fi
-	test -f "$GIT_DIR/logs/$ref_stash" ||
+	test -f "`git rev-parse --git-path logs/$ref_stash`" ||
 		clear_stash || die "$(gettext "Cannot initialize stash")"
 
 	create_stash "$stash_msg" $untracked
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 14/27] setup.c: convert is_git_directory() to use strbuf
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (12 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 13/27] git-stash: avoid hardcoding $GIT_DIR/logs/ Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-01 12:12   ` [PATCH v4 15/27] setup.c: detect $GIT_COMMON_DIR in is_git_directory() Nguyễn Thái Ngọc Duy
                     ` (13 subsequent siblings)
  27 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 setup.c | 37 +++++++++++++++++++++----------------
 1 file changed, 21 insertions(+), 16 deletions(-)

diff --git a/setup.c b/setup.c
index 6c3f85f..4994437 100644
--- a/setup.c
+++ b/setup.c
@@ -184,31 +184,36 @@ void verify_non_filename(const char *prefix, const char *arg)
  */
 int is_git_directory(const char *suspect)
 {
-	char path[PATH_MAX];
-	size_t len = strlen(suspect);
+	struct strbuf path = STRBUF_INIT;
+	int ret = 0;
+	size_t len;
 
-	if (PATH_MAX <= len + strlen("/objects"))
-		die("Too long path: %.*s", 60, suspect);
-	strcpy(path, suspect);
+	strbuf_addstr(&path, suspect);
+	len = path.len;
 	if (getenv(DB_ENVIRONMENT)) {
 		if (access(getenv(DB_ENVIRONMENT), X_OK))
-			return 0;
+			goto done;
 	}
 	else {
-		strcpy(path + len, "/objects");
-		if (access(path, X_OK))
-			return 0;
+		strbuf_addstr(&path, "/objects");
+		if (access(path.buf, X_OK))
+			goto done;
 	}
 
-	strcpy(path + len, "/refs");
-	if (access(path, X_OK))
-		return 0;
+	strbuf_setlen(&path, len);
+	strbuf_addstr(&path, "/refs");
+	if (access(path.buf, X_OK))
+		goto done;
 
-	strcpy(path + len, "/HEAD");
-	if (validate_headref(path))
-		return 0;
+	strbuf_setlen(&path, len);
+	strbuf_addstr(&path, "/HEAD");
+	if (validate_headref(path.buf))
+		goto done;
 
-	return 1;
+	ret = 1;
+done:
+	strbuf_release(&path);
+	return ret;
 }
 
 int is_inside_git_dir(void)
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 15/27] setup.c: detect $GIT_COMMON_DIR in is_git_directory()
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (13 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 14/27] setup.c: convert is_git_directory() to use strbuf Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-01 12:12   ` [PATCH v4 16/27] setup.c: convert check_repository_format_gently to use strbuf Nguyễn Thái Ngọc Duy
                     ` (12 subsequent siblings)
  27 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

If the file "$GIT_DIR/commondir" exists, it contains the value of
$GIT_COMMON_DIR.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/gitrepository-layout.txt |  7 ++++++
 setup.c                                | 43 +++++++++++++++++++++++++++++-----
 2 files changed, 44 insertions(+), 6 deletions(-)

diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index 10672a1..f72ce75 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -233,6 +233,13 @@ shallow::
 	file is ignored $GIT_COMMON_DIR is set and
 	"$GIT_COMMON_DIR/shallow" will be used instead.
 
+commondir::
+	If this file exists, $GIT_COMMON_DIR (see linkgit:git[1]) will
+	be set to the path specified in this file if it is not
+	explicitly set. If the specified path is relative, it is
+	relative to $GIT_DIR. The repository with commondir is
+	incomplete without the repository pointed by "commondir".
+
 modules::
 	Contains the git-repositories of the submodules. This
 	directory is ignored $GIT_COMMON_DIR is set and
diff --git a/setup.c b/setup.c
index 4994437..7e5b334 100644
--- a/setup.c
+++ b/setup.c
@@ -170,6 +170,33 @@ void verify_non_filename(const char *prefix, const char *arg)
 	    "'git <command> [<revision>...] -- [<file>...]'", arg);
 }
 
+static void get_common_dir(struct strbuf *sb, const char *gitdir)
+{
+	struct strbuf data = STRBUF_INIT;
+	struct strbuf path = STRBUF_INIT;
+	const char *git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
+	if (git_common_dir) {
+		strbuf_addstr(sb, git_common_dir);
+		return;
+	}
+	strbuf_addf(&path, "%s/commondir", gitdir);
+	if (file_exists(path.buf)) {
+		if (strbuf_read_file(&data, path.buf, 0) <= 0)
+			die_errno(_("failed to read %s"), path.buf);
+		while (data.len && (data.buf[data.len - 1] == '\n' ||
+				    data.buf[data.len - 1] == '\r'))
+			data.len--;
+		data.buf[data.len] = '\0';
+		strbuf_reset(&path);
+		if (!is_absolute_path(data.buf))
+			strbuf_addf(&path, "%s/", gitdir);
+		strbuf_addbuf(&path, &data);
+		strbuf_addstr(sb, real_path(path.buf));
+	} else
+		strbuf_addstr(sb, gitdir);
+	strbuf_release(&data);
+	strbuf_release(&path);
+}
 
 /*
  * Test if it looks like we're at a git directory.
@@ -188,13 +215,22 @@ int is_git_directory(const char *suspect)
 	int ret = 0;
 	size_t len;
 
-	strbuf_addstr(&path, suspect);
+	/* Check worktree-related signatures */
+	strbuf_addf(&path, "%s/HEAD", suspect);
+	if (validate_headref(path.buf))
+		goto done;
+
+	strbuf_reset(&path);
+	get_common_dir(&path, suspect);
 	len = path.len;
+
+	/* Check non-worktree-related signatures */
 	if (getenv(DB_ENVIRONMENT)) {
 		if (access(getenv(DB_ENVIRONMENT), X_OK))
 			goto done;
 	}
 	else {
+		strbuf_setlen(&path, len);
 		strbuf_addstr(&path, "/objects");
 		if (access(path.buf, X_OK))
 			goto done;
@@ -205,11 +241,6 @@ int is_git_directory(const char *suspect)
 	if (access(path.buf, X_OK))
 		goto done;
 
-	strbuf_setlen(&path, len);
-	strbuf_addstr(&path, "/HEAD");
-	if (validate_headref(path.buf))
-		goto done;
-
 	ret = 1;
 done:
 	strbuf_release(&path);
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 16/27] setup.c: convert check_repository_format_gently to use strbuf
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (14 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 15/27] setup.c: detect $GIT_COMMON_DIR in is_git_directory() Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-01 12:12   ` [PATCH v4 17/27] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently() Nguyễn Thái Ngọc Duy
                     ` (11 subsequent siblings)
  27 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 setup.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/setup.c b/setup.c
index 7e5b334..5085ab1 100644
--- a/setup.c
+++ b/setup.c
@@ -288,7 +288,9 @@ void setup_work_tree(void)
 
 static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 {
-	char repo_config[PATH_MAX+1];
+	struct strbuf sb = STRBUF_INIT;
+	const char *repo_config;
+	int ret = 0;
 
 	/*
 	 * git_config() can't be used here because it calls git_pathdup()
@@ -299,7 +301,8 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 	 * Use a gentler version of git_config() to check if this repo
 	 * is a good one.
 	 */
-	snprintf(repo_config, PATH_MAX, "%s/config", gitdir);
+	strbuf_addf(&sb, "%s/config", gitdir);
+	repo_config = sb.buf;
 	git_config_early(check_repository_format_version, NULL, repo_config);
 	if (GIT_REPO_VERSION < repository_format_version) {
 		if (!nongit_ok)
@@ -309,9 +312,10 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 			GIT_REPO_VERSION, repository_format_version);
 		warning("Please upgrade Git");
 		*nongit_ok = -1;
-		return -1;
+		ret = -1;
 	}
-	return 0;
+	strbuf_release(&sb);
+	return ret;
 }
 
 /*
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 17/27] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently()
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (15 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 16/27] setup.c: convert check_repository_format_gently to use strbuf Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-01 12:12   ` [PATCH v4 18/27] setup.c: support multi-checkout repo setup Nguyễn Thái Ngọc Duy
                     ` (10 subsequent siblings)
  27 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 setup.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/setup.c b/setup.c
index 5085ab1..42849f3 100644
--- a/setup.c
+++ b/setup.c
@@ -292,6 +292,10 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 	const char *repo_config;
 	int ret = 0;
 
+	get_common_dir(&sb, gitdir);
+	strbuf_addstr(&sb, "/config");
+	repo_config = sb.buf;
+
 	/*
 	 * git_config() can't be used here because it calls git_pathdup()
 	 * to get $GIT_CONFIG/config. That call will make setup_git_env()
@@ -301,8 +305,6 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 	 * Use a gentler version of git_config() to check if this repo
 	 * is a good one.
 	 */
-	strbuf_addf(&sb, "%s/config", gitdir);
-	repo_config = sb.buf;
 	git_config_early(check_repository_format_version, NULL, repo_config);
 	if (GIT_REPO_VERSION < repository_format_version) {
 		if (!nongit_ok)
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 18/27] setup.c: support multi-checkout repo setup
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (16 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 17/27] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently() Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-05 19:42     ` Junio C Hamano
  2014-03-01 12:12   ` [PATCH v4 19/27] wrapper.c: wrapper to open a file, fprintf then close Nguyễn Thái Ngọc Duy
                     ` (9 subsequent siblings)
  27 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

The repo setup procedure is updated to detect $GIT_DIR/commondir and
set $GIT_COMMON_DIR properly.

The core.worktree is ignored when $GIT_DIR/commondir presents. This is
because "commondir" repos are intended for separate/linked checkouts
and pointing them back to a fixed core.worktree just does not make
sense.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/config.txt        |  2 ++
 Documentation/git-rev-parse.txt |  3 ++
 builtin/rev-parse.c             |  4 +++
 cache.h                         |  1 +
 environment.c                   |  8 ++---
 setup.c                         | 33 +++++++++++++-----
 t/t1501-worktree.sh             | 76 +++++++++++++++++++++++++++++++++++++++++
 t/t1510-repo-setup.sh           |  1 +
 trace.c                         |  1 +
 9 files changed, 115 insertions(+), 14 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 5f4d793..313d4b3 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -381,6 +381,8 @@ false), while all other repositories are assumed to be bare (bare
 
 core.worktree::
 	Set the path to the root of the working tree.
+	If GIT_COMMON_DIR environment variable is set, core.worktree
+	is ignored and not used for determining the root of working tree.
 	This can be overridden by the GIT_WORK_TREE environment
 	variable and the '--work-tree' command line option.
 	The value can be an absolute path or relative to the path to
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 33e4e90..8e6ad32 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -215,6 +215,9 @@ If `$GIT_DIR` is not defined and the current directory
 is not detected to lie in a Git repository or work tree
 print a message to stderr and exit with nonzero status.
 
+--git-common-dir::
+	Show `$GIT_COMMON_DIR` if defined, else `$GIT_DIR`.
+
 --is-inside-git-dir::
 	When the current working directory is below the repository
 	directory print "true", otherwise "false".
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index e50bc65..c7057ce 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -744,6 +744,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
 				printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : "");
 				continue;
 			}
+			if (!strcmp(arg, "--git-common-dir")) {
+				puts(get_git_common_dir());
+				continue;
+			}
 			if (!strcmp(arg, "--resolve-git-dir")) {
 				const char *gitdir = resolve_gitdir(argv[i+1]);
 				if (!gitdir)
diff --git a/cache.h b/cache.h
index 51ade32..98b5dd3 100644
--- a/cache.h
+++ b/cache.h
@@ -407,6 +407,7 @@ extern char *get_object_directory(void);
 extern char *get_index_file(void);
 extern char *get_graft_file(void);
 extern int set_git_dir(const char *path);
+extern int get_common_dir(struct strbuf *sb, const char *gitdir);
 extern const char *get_git_namespace(void);
 extern const char *strip_namespace(const char *namespaced_ref);
 extern const char *get_git_work_tree(void);
diff --git a/environment.c b/environment.c
index c998120..0999fc1 100644
--- a/environment.c
+++ b/environment.c
@@ -126,6 +126,7 @@ static char *expand_namespace(const char *raw_namespace)
 
 static void setup_git_env(void)
 {
+	struct strbuf sb = STRBUF_INIT;
 	const char *gitfile;
 	const char *shallow_file;
 
@@ -134,12 +135,9 @@ static void setup_git_env(void)
 		git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
 	gitfile = read_gitfile(git_dir);
 	git_dir = xstrdup(gitfile ? gitfile : git_dir);
-	git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
-	if (git_common_dir) {
+	if (get_common_dir(&sb, git_dir))
 		git_common_dir_env = 1;
-		git_common_dir = xstrdup(git_common_dir);
-	} else
-		git_common_dir = git_dir;
+	git_common_dir = strbuf_detach(&sb, NULL);
 	git_object_dir = getenv(DB_ENVIRONMENT);
 	if (!git_object_dir) {
 		git_object_dir = xmalloc(strlen(git_common_dir) + 9);
diff --git a/setup.c b/setup.c
index 42849f3..40ce191 100644
--- a/setup.c
+++ b/setup.c
@@ -170,14 +170,15 @@ void verify_non_filename(const char *prefix, const char *arg)
 	    "'git <command> [<revision>...] -- [<file>...]'", arg);
 }
 
-static void get_common_dir(struct strbuf *sb, const char *gitdir)
+int get_common_dir(struct strbuf *sb, const char *gitdir)
 {
 	struct strbuf data = STRBUF_INIT;
 	struct strbuf path = STRBUF_INIT;
 	const char *git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
+	int ret = 0;
 	if (git_common_dir) {
 		strbuf_addstr(sb, git_common_dir);
-		return;
+		return 1;
 	}
 	strbuf_addf(&path, "%s/commondir", gitdir);
 	if (file_exists(path.buf)) {
@@ -192,10 +193,12 @@ static void get_common_dir(struct strbuf *sb, const char *gitdir)
 			strbuf_addf(&path, "%s/", gitdir);
 		strbuf_addbuf(&path, &data);
 		strbuf_addstr(sb, real_path(path.buf));
+		ret = 1;
 	} else
 		strbuf_addstr(sb, gitdir);
 	strbuf_release(&data);
 	strbuf_release(&path);
+	return ret;
 }
 
 /*
@@ -286,13 +289,26 @@ void setup_work_tree(void)
 	initialized = 1;
 }
 
+static int check_repo_format(const char *var, const char *value, void *cb)
+{
+	if (strcmp(var, "core.repositoryformatversion") == 0)
+		repository_format_version = git_config_int(var, value);
+	else if (strcmp(var, "core.sharedrepository") == 0)
+		shared_repository = git_config_perm(var, value);
+	return 0;
+}
+
 static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 {
 	struct strbuf sb = STRBUF_INIT;
 	const char *repo_config;
+	config_fn_t fn;
 	int ret = 0;
 
-	get_common_dir(&sb, gitdir);
+	if (get_common_dir(&sb, gitdir))
+		fn = check_repo_format;
+	else
+		fn = check_repository_format_version;
 	strbuf_addstr(&sb, "/config");
 	repo_config = sb.buf;
 
@@ -305,7 +321,7 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 	 * Use a gentler version of git_config() to check if this repo
 	 * is a good one.
 	 */
-	git_config_early(check_repository_format_version, NULL, repo_config);
+	git_config_early(fn, NULL, repo_config);
 	if (GIT_REPO_VERSION < repository_format_version) {
 		if (!nongit_ok)
 			die ("Expected git repo version <= %d, found %d",
@@ -777,11 +793,10 @@ int git_config_perm(const char *var, const char *value)
 
 int check_repository_format_version(const char *var, const char *value, void *cb)
 {
-	if (strcmp(var, "core.repositoryformatversion") == 0)
-		repository_format_version = git_config_int(var, value);
-	else if (strcmp(var, "core.sharedrepository") == 0)
-		shared_repository = git_config_perm(var, value);
-	else if (strcmp(var, "core.bare") == 0) {
+	int ret = check_repo_format(var, value, cb);
+	if (ret)
+		return ret;
+	if (strcmp(var, "core.bare") == 0) {
 		is_bare_repository_cfg = git_config_bool(var, value);
 		if (is_bare_repository_cfg == 1)
 			inside_work_tree = -1;
diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh
index 8f36aa9..2ac4424 100755
--- a/t/t1501-worktree.sh
+++ b/t/t1501-worktree.sh
@@ -346,4 +346,80 @@ test_expect_success 'relative $GIT_WORK_TREE and git subprocesses' '
 	test_cmp expected actual
 '
 
+test_expect_success 'Multi-worktree setup' '
+	mkdir work &&
+	mkdir -p repo.git/repos/foo &&
+	cp repo.git/HEAD repo.git/index repo.git/repos/foo &&
+	sane_unset GIT_DIR GIT_CONFIG GIT_WORK_TREE
+'
+
+test_expect_success 'GIT_DIR set (1)' '
+	echo "gitdir: repo.git/repos/foo" >gitfile &&
+	echo ../.. >repo.git/repos/foo/commondir &&
+	(
+		cd work &&
+		GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
+		echo "$TRASH_DIRECTORY/repo.git" >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'GIT_DIR set (2)' '
+	echo "gitdir: repo.git/repos/foo" >gitfile &&
+	echo "$TRASH_DIRECTORY/repo.git" >repo.git/repos/foo/commondir &&
+	(
+		cd work &&
+		GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
+		echo "$TRASH_DIRECTORY/repo.git" >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'Auto discovery' '
+	echo "gitdir: repo.git/repos/foo" >.git &&
+	echo ../.. >repo.git/repos/foo/commondir &&
+	(
+		cd work &&
+		git rev-parse --git-common-dir >actual &&
+		echo "$TRASH_DIRECTORY/repo.git" >expect &&
+		test_cmp expect actual &&
+		echo haha >data1 &&
+		git add data1 &&
+		git ls-files --full-name :/ | grep data1 >actual &&
+		echo work/data1 >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '$GIT_DIR/common overrides core.worktree' '
+	mkdir elsewhere &&
+	git --git-dir=repo.git config core.worktree "$TRASH_DIRECTORY/elsewhere" &&
+	echo "gitdir: repo.git/repos/foo" >.git &&
+	echo ../.. >repo.git/repos/foo/commondir &&
+	(
+		cd work &&
+		git rev-parse --git-common-dir >actual &&
+		echo "$TRASH_DIRECTORY/repo.git" >expect &&
+		test_cmp expect actual &&
+		echo haha >data2 &&
+		git add data2 &&
+		git ls-files --full-name :/ | grep data2 >actual &&
+		echo work/data2 >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '$GIT_WORK_TREE overrides $GIT_DIR/common' '
+	echo "gitdir: repo.git/repos/foo" >.git &&
+	echo ../.. >repo.git/repos/foo/commondir &&
+	(
+		cd work &&
+		echo haha >data3 &&
+		git --git-dir=../.git --work-tree=. add data3 &&
+		git ls-files --full-name -- :/ | grep data3 >actual &&
+		echo data3 >expect &&
+		test_cmp expect actual
+	)
+'
+
 test_done
diff --git a/t/t1510-repo-setup.sh b/t/t1510-repo-setup.sh
index cf2ee78..c211ef5 100755
--- a/t/t1510-repo-setup.sh
+++ b/t/t1510-repo-setup.sh
@@ -106,6 +106,7 @@ setup_env () {
 expect () {
 	cat >"$1/expected" <<-EOF
 	setup: git_dir: $2
+	setup: git_common_dir: $2
 	setup: worktree: $3
 	setup: cwd: $4
 	setup: prefix: $5
diff --git a/trace.c b/trace.c
index 08180a9..a594761 100644
--- a/trace.c
+++ b/trace.c
@@ -173,6 +173,7 @@ void trace_repo_setup(const char *prefix)
 		prefix = "(null)";
 
 	trace_printf_key(key, "setup: git_dir: %s\n", quote_crnl(get_git_dir()));
+	trace_printf_key(key, "setup: git_common_dir: %s\n", quote_crnl(get_git_common_dir()));
 	trace_printf_key(key, "setup: worktree: %s\n", quote_crnl(git_work_tree));
 	trace_printf_key(key, "setup: cwd: %s\n", quote_crnl(cwd));
 	trace_printf_key(key, "setup: prefix: %s\n", quote_crnl(prefix));
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 19/27] wrapper.c: wrapper to open a file, fprintf then close
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (17 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 18/27] setup.c: support multi-checkout repo setup Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-01 17:11     ` Torsten Bögershausen
  2014-03-01 12:12   ` [PATCH v4 20/27] use new wrapper write_file() for simple file writing Nguyễn Thái Ngọc Duy
                     ` (8 subsequent siblings)
  27 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 cache.h   |  2 ++
 wrapper.c | 31 +++++++++++++++++++++++++++++++
 2 files changed, 33 insertions(+)

diff --git a/cache.h b/cache.h
index 98b5dd3..99b86d9 100644
--- a/cache.h
+++ b/cache.h
@@ -1239,6 +1239,8 @@ static inline ssize_t write_str_in_full(int fd, const char *str)
 {
 	return write_in_full(fd, str, strlen(str));
 }
+__attribute__((format (printf,3,4)))
+extern int write_file(const char *path, int fatal, const char *fmt, ...);
 
 /* pager.c */
 extern void setup_pager(void);
diff --git a/wrapper.c b/wrapper.c
index 0cc5636..5ced50d 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -455,3 +455,34 @@ struct passwd *xgetpwuid_self(void)
 		    errno ? strerror(errno) : _("no such user"));
 	return pw;
 }
+
+int write_file(const char *path, int fatal, const char *fmt, ...)
+{
+	struct strbuf sb = STRBUF_INIT;
+	int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);
+	va_list params;
+	if (fd < 0) {
+		if (fatal)
+			die_errno(_("could not open %s for writing"), path);
+		return -1;
+	}
+	va_start(params, fmt);
+	strbuf_vaddf(&sb, fmt, params);
+	va_end(params);
+	if (write_in_full(fd, sb.buf, sb.len) != sb.len) {
+		int err = errno;
+		close(fd);
+		errno = err;
+		strbuf_release(&sb);
+		if (fatal)
+			die_errno(_("could not write to %s"), path);
+		return -1;
+	}
+	strbuf_release(&sb);
+	if (close(fd)) {
+		if (fatal)
+			die_errno(_("could not close %s"), path);
+		return -1;
+	}
+	return 0;
+}
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 20/27] use new wrapper write_file() for simple file writing
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (18 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 19/27] wrapper.c: wrapper to open a file, fprintf then close Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-01 12:12   ` [PATCH v4 21/27] checkout: support checking out into a new working directory Nguyễn Thái Ngọc Duy
                     ` (7 subsequent siblings)
  27 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

This fixes common problems in these code about error handling,
forgetting to close the file handle after fprintf() fails, or not
printing out the error string..

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/branch.c  |  4 +---
 builtin/init-db.c |  7 +------
 daemon.c          | 11 +----------
 submodule.c       |  9 ++-------
 transport.c       |  8 +++-----
 5 files changed, 8 insertions(+), 31 deletions(-)

diff --git a/builtin/branch.c b/builtin/branch.c
index b4d7716..3eebdbc 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -754,7 +754,6 @@ static const char edit_description[] = "BRANCH_DESCRIPTION";
 
 static int edit_branch_description(const char *branch_name)
 {
-	FILE *fp;
 	int status;
 	struct strbuf buf = STRBUF_INIT;
 	struct strbuf name = STRBUF_INIT;
@@ -767,8 +766,7 @@ static int edit_branch_description(const char *branch_name)
 		    "  %s\n"
 		    "Lines starting with '%c' will be stripped.\n",
 		    branch_name, comment_line_char);
-	fp = fopen(git_path(edit_description), "w");
-	if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) {
+	if (write_file(git_path(edit_description), 0, "%s", buf.buf)) {
 		strbuf_release(&buf);
 		return error(_("could not write branch description template: %s"),
 			     strerror(errno));
diff --git a/builtin/init-db.c b/builtin/init-db.c
index c7c76bb..081e512 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -342,7 +342,6 @@ int set_git_dir_init(const char *git_dir, const char *real_git_dir,
 static void separate_git_dir(const char *git_dir)
 {
 	struct stat st;
-	FILE *fp;
 
 	if (!stat(git_link, &st)) {
 		const char *src;
@@ -358,11 +357,7 @@ static void separate_git_dir(const char *git_dir)
 			die_errno(_("unable to move %s to %s"), src, git_dir);
 	}
 
-	fp = fopen(git_link, "w");
-	if (!fp)
-		die(_("Could not create git link %s"), git_link);
-	fprintf(fp, "gitdir: %s\n", git_dir);
-	fclose(fp);
+	write_file(git_link, 1, "gitdir: %s\n", git_dir);
 }
 
 int init_db(const char *template_dir, unsigned int flags)
diff --git a/daemon.c b/daemon.c
index 503e039..b880d30 100644
--- a/daemon.c
+++ b/daemon.c
@@ -1122,15 +1122,6 @@ static void daemonize(void)
 }
 #endif
 
-static void store_pid(const char *path)
-{
-	FILE *f = fopen(path, "w");
-	if (!f)
-		die_errno("cannot open pid file '%s'", path);
-	if (fprintf(f, "%"PRIuMAX"\n", (uintmax_t) getpid()) < 0 || fclose(f) != 0)
-		die_errno("failed to write pid file '%s'", path);
-}
-
 static int serve(struct string_list *listen_addr, int listen_port,
     struct credentials *cred)
 {
@@ -1339,7 +1330,7 @@ int main(int argc, char **argv)
 		sanitize_stdfds();
 
 	if (pid_file)
-		store_pid(pid_file);
+		write_file(pid_file, 1, "%"PRIuMAX"\n", (uintmax_t) getpid());
 
 	/* prepare argv for serving-processes */
 	cld_argv = xmalloc(sizeof (char *) * (argc + 2));
diff --git a/submodule.c b/submodule.c
index 613857e..fe5748d 100644
--- a/submodule.c
+++ b/submodule.c
@@ -1135,16 +1135,11 @@ void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir)
 	struct strbuf file_name = STRBUF_INIT;
 	struct strbuf rel_path = STRBUF_INIT;
 	const char *real_work_tree = xstrdup(real_path(work_tree));
-	FILE *fp;
 
 	/* Update gitfile */
 	strbuf_addf(&file_name, "%s/.git", work_tree);
-	fp = fopen(file_name.buf, "w");
-	if (!fp)
-		die(_("Could not create git link %s"), file_name.buf);
-	fprintf(fp, "gitdir: %s\n", relative_path(git_dir, real_work_tree,
-						  &rel_path));
-	fclose(fp);
+	write_file(file_name.buf, 1, "gitdir: %s\n",
+		   relative_path(git_dir, real_work_tree, &rel_path));
 
 	/* Update core.worktree setting */
 	strbuf_reset(&file_name);
diff --git a/transport.c b/transport.c
index ca7bb44..2df8a15 100644
--- a/transport.c
+++ b/transport.c
@@ -294,7 +294,6 @@ static int write_one_ref(const char *name, const unsigned char *sha1,
 {
 	struct strbuf *buf = data;
 	int len = buf->len;
-	FILE *f;
 
 	/* when called via for_each_ref(), flags is non-zero */
 	if (flags && !starts_with(name, "refs/heads/") &&
@@ -303,10 +302,9 @@ static int write_one_ref(const char *name, const unsigned char *sha1,
 
 	strbuf_addstr(buf, name);
 	if (safe_create_leading_directories(buf->buf) ||
-			!(f = fopen(buf->buf, "w")) ||
-			fprintf(f, "%s\n", sha1_to_hex(sha1)) < 0 ||
-			fclose(f))
-		return error("problems writing temporary file %s", buf->buf);
+	    write_file(buf->buf, 0, "%s\n", sha1_to_hex(sha1)))
+		return error("problems writing temporary file %s: %s",
+			     buf->buf, strerror(errno));
 	strbuf_setlen(buf, len);
 	return 0;
 }
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 21/27] checkout: support checking out into a new working directory
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (19 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 20/27] use new wrapper write_file() for simple file writing Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-01 12:12   ` [PATCH v4 22/27] checkout: clean up half-prepared directories in --to mode Nguyễn Thái Ngọc Duy
                     ` (6 subsequent siblings)
  27 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

"git checkout --to" sets up a new working directory with a .git file
pointing to $GIT_DIR/repos/<id>. It then executes "git checkout" again
on the new worktree with the same arguments except "--to" is taken
out. The second checkout execution, which is not contaminated with any
info from the current repository, will actually check out and
everything that normal "git checkout" does.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt         | 34 +++++++++++++
 Documentation/git.txt                  |  3 +-
 Documentation/gitrepository-layout.txt |  7 +++
 builtin/checkout.c                     | 93 +++++++++++++++++++++++++++++++++-
 path.c                                 |  2 +-
 t/t2025-checkout-to.sh (new +x)        | 48 ++++++++++++++++++
 6 files changed, 183 insertions(+), 4 deletions(-)
 create mode 100755 t/t2025-checkout-to.sh

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 33ad2ad..fcf73b2 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -225,6 +225,13 @@ This means that you can use `git checkout -p` to selectively discard
 edits from your current working tree. See the ``Interactive Mode''
 section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
 
+--to=<path>::
+	Check out a new branch in a separate working directory at
+	`<path>`. A new working directory is linked to the current
+	repository, sharing everything except working directory
+	specific files such as HEAD, index... See "MULTIPLE CHECKOUT
+	MODE" section for more information.
+
 <branch>::
 	Branch to checkout; if it refers to a branch (i.e., a name that,
 	when prepended with "refs/heads/", is a valid ref), then that
@@ -388,6 +395,33 @@ $ git reflog -2 HEAD # or
 $ git log -g -2 HEAD
 ------------
 
+MULTIPLE CHECKOUT MODE
+-------------------------------
+Normally a working directory is attached to repository. When "git
+checkout --to" is used, a new working directory is attached to the
+current repository. This new working directory is called "linked
+checkout" as compared to the "main checkout" prepared by "git init" or
+"git clone". A repository has one main checkout and zero or more
+linked checkouts.
+
+All checkouts share the same repository. Linked checkouts see the
+repository a bit different from the main checkout. When the checkout
+"new" reads the path $GIT_DIR/HEAD for example, the actual path
+returned could be $GIT_DIR/repos/new/HEAD. This ensures checkouts
+won't step on each other.
+
+Each linked checkout has a private space in $GIT_DIR/repos, usually
+named after the base name of the working directory with a number added
+to make it unique. The linked checkout's $GIT_DIR points to this
+private space while $GIT_COMMON_DIR points to the main checkout's
+$GIT_DIR. These settings are done by "git checkout --to".
+
+Because in this mode $GIT_DIR becomes a lightweight virtual file
+system where a path could be rewritten to some place else, accessing
+$GIT_DIR from scripts should use `git rev-parse --git-path` to resolve
+a path instead of using it directly unless the path is known to be
+private to the working directory.
+
 EXAMPLES
 --------
 
diff --git a/Documentation/git.txt b/Documentation/git.txt
index b094b1f..bdb9b0f 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -777,7 +777,8 @@ Git so take care if using Cogito etc.
 	If this variable is set to a path, non-worktree files that are
 	normally in $GIT_DIR will be taken from this path
 	instead. Worktree-specific files such as HEAD or index are
-	taken from $GIT_DIR. See linkgit:gitrepository-layout[5] for
+	taken from $GIT_DIR. See linkgit:gitrepository-layout[5] and
+	the section 'MULTIPLE CHECKOUT MODE' in linkgit:checkout[1]
 	details. This variable has lower precedence than other path
 	variables such as GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY...
 
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index f72ce75..418e5c8 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -245,6 +245,13 @@ modules::
 	directory is ignored $GIT_COMMON_DIR is set and
 	"$GIT_COMMON_DIR/modules" will be used instead.
 
+repos::
+	Contains worktree specific information of linked
+	checkouts. Each subdirectory contains the worktree-related
+	part of a linked checkout. This directory is ignored
+	$GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/repos" will be
+	used instead.
+
 SEE ALSO
 --------
 linkgit:git-init[1],
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 0570e41..fa7b54a 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -48,6 +48,10 @@ struct checkout_opts {
 	const char *prefix;
 	struct pathspec pathspec;
 	struct tree *source_tree;
+
+	const char *new_worktree;
+	const char **saved_argv;
+	int new_worktree_mode;
 };
 
 static int post_checkout_hook(struct commit *old, struct commit *new,
@@ -250,6 +254,9 @@ static int checkout_paths(const struct checkout_opts *opts,
 		die(_("Cannot update paths and switch to branch '%s' at the same time."),
 		    opts->new_branch);
 
+	if (opts->new_worktree)
+		die(_("'%s' cannot be used with updating paths"), "--to");
+
 	if (opts->patch_mode)
 		return run_add_interactive(revision, "--patch=checkout",
 					   &opts->pathspec);
@@ -486,7 +493,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
 			topts.dir->flags |= DIR_SHOW_IGNORED;
 			setup_standard_excludes(topts.dir);
 		}
-		tree = parse_tree_indirect(old->commit ?
+		tree = parse_tree_indirect(old->commit && !opts->new_worktree_mode ?
 					   old->commit->object.sha1 :
 					   EMPTY_TREE_SHA1_BIN);
 		init_tree_desc(&trees[0], tree->buffer, tree->size);
@@ -796,7 +803,8 @@ static int switch_branches(const struct checkout_opts *opts,
 		return ret;
 	}
 
-	if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
+	if (!opts->quiet && !old.path && old.commit &&
+	    new->commit != old.commit && !opts->new_worktree_mode)
 		orphaned_commit_warning(old.commit, new->commit);
 
 	update_refs_for_switch(opts, &old, new);
@@ -806,6 +814,74 @@ static int switch_branches(const struct checkout_opts *opts,
 	return ret || writeout_error;
 }
 
+static int prepare_linked_checkout(const struct checkout_opts *opts,
+				   struct branch_info *new)
+{
+	struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
+	struct strbuf sb = STRBUF_INIT;
+	const char *path = opts->new_worktree, *name;
+	struct stat st;
+	struct child_process cp;
+	int counter = 0, len;
+
+	if (!new->commit)
+		die(_("no branch specified"));
+
+	len = strlen(path);
+	while (len && is_dir_sep(path[len - 1]))
+		len--;
+
+	for (name = path + len - 1; name > path; name--)
+		if (is_dir_sep(*name)) {
+			name++;
+			break;
+		}
+	strbuf_addstr(&sb_repo,
+		      git_path("repos/%.*s", (int)(path + len - name), name));
+	len = sb_repo.len;
+	if (safe_create_leading_directories_const(sb_repo.buf))
+		die_errno(_("could not create leading directories of '%s'"),
+			  sb_repo.buf);
+	while (!stat(sb_repo.buf, &st)) {
+		counter++;
+		strbuf_setlen(&sb_repo, len);
+		strbuf_addf(&sb_repo, "%d", counter);
+	}
+	name = strrchr(sb_repo.buf, '/') + 1;
+	if (mkdir(sb_repo.buf, 0777))
+		die_errno(_("could not create directory of '%s'"), sb_repo.buf);
+
+	strbuf_addf(&sb_git, "%s/.git", path);
+	if (safe_create_leading_directories_const(sb_git.buf))
+		die_errno(_("could not create leading directories of '%s'"),
+			  sb_git.buf);
+
+	write_file(sb_git.buf, 1, "gitdir: %s/repos/%s\n",
+		   real_path(get_git_common_dir()), name);
+	/*
+	 * This is to keep resolve_ref() happy. We need a valid HEAD
+	 * or is_git_directory() will reject the directory. Any valid
+	 * value would do because this value will be ignored and
+	 * replaced at the next (real) checkout.
+	 */
+	strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
+	write_file(sb.buf, 1, "%s\n", sha1_to_hex(new->commit->object.sha1));
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
+	write_file(sb.buf, 1, "../..\n");
+
+	if (!opts->quiet)
+		fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
+
+	setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1);
+	setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1);
+	setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1);
+	memset(&cp, 0, sizeof(cp));
+	cp.git_cmd = 1;
+	cp.argv = opts->saved_argv;
+	return run_command(&cp);
+}
+
 static int git_checkout_config(const char *var, const char *value, void *cb)
 {
 	if (!strcmp(var, "diff.ignoresubmodules")) {
@@ -1067,6 +1143,9 @@ static int checkout_branch(struct checkout_opts *opts,
 		die(_("Cannot switch branch to a non-commit '%s'"),
 		    new->name);
 
+	if (opts->new_worktree)
+		return prepare_linked_checkout(opts, new);
+
 	if (!new->commit && opts->new_branch) {
 		unsigned char rev[20];
 		int flag;
@@ -1109,6 +1188,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			 N_("do not limit pathspecs to sparse entries only")),
 		OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
 				N_("second guess 'git checkout no-such-branch'")),
+		OPT_STRING(0, "to", &opts.new_worktree, N_("path"),
+			   N_("check a branch out in a separate working directory")),
 		OPT_END(),
 	};
 
@@ -1117,6 +1198,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts.overwrite_ignore = 1;
 	opts.prefix = prefix;
 
+	opts.saved_argv = xmalloc(sizeof(const char *) * (argc + 2));
+	memcpy(opts.saved_argv, argv, sizeof(const char *) * (argc + 1));
+
 	gitmodules_config();
 	git_config(git_checkout_config, &opts);
 
@@ -1125,6 +1209,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
+	/* recursive execution from checkout_new_worktree() */
+	opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL;
+	if (opts.new_worktree_mode)
+		opts.new_worktree = NULL;
+
 	if (conflict_style) {
 		opts.merge = 1; /* implied */
 		git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
diff --git a/path.c b/path.c
index 6129026..47383ff 100644
--- a/path.c
+++ b/path.c
@@ -94,7 +94,7 @@ static void update_common_dir(struct strbuf *buf, int git_dir_len)
 {
 	const char *common_dir_list[] = {
 		"branches", "hooks", "info", "logs", "lost-found", "modules",
-		"objects", "refs", "remotes", "rr-cache", "svn",
+		"objects", "refs", "remotes", "repos", "rr-cache", "svn",
 		NULL
 	};
 	const char *common_top_file_list[] = {
diff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh
new file mode 100755
index 0000000..5ec49e2
--- /dev/null
+++ b/t/t2025-checkout-to.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+test_description='test git checkout --to'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit init
+'
+
+test_expect_success 'checkout --to not updating paths' '
+	test_must_fail git checkout --to -- init.t
+'
+
+test_expect_success 'checkout --to a new worktree' '
+	git checkout --to here master &&
+	(
+		cd here &&
+		test_cmp ../init.t init.t &&
+		git symbolic-ref HEAD >actual &&
+		echo refs/heads/master >expect &&
+		test_cmp expect actual &&
+		git fsck
+	)
+'
+
+test_expect_success 'checkout --to from a linked checkout' '
+	(
+		cd here &&
+		git checkout --to nested-here master
+		cd nested-here &&
+		git fsck
+	)
+'
+
+test_expect_success 'checkout --to a new worktree creating new branch' '
+	git checkout --to there -b newmaster master &&
+	(
+		cd there &&
+		test_cmp ../init.t init.t &&
+		git symbolic-ref HEAD >actual &&
+		echo refs/heads/newmaster >expect &&
+		test_cmp expect actual &&
+		git fsck
+	)
+'
+
+test_done
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 22/27] checkout: clean up half-prepared directories in --to mode
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (20 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 21/27] checkout: support checking out into a new working directory Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-06 10:06     ` Eric Sunshine
  2014-03-01 12:12   ` [PATCH v4 23/27] checkout: detach if the branch is already checked out elsewhere Nguyễn Thái Ngọc Duy
                     ` (5 subsequent siblings)
  27 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 47 insertions(+), 2 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index fa7b54a..28f9ac1 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -20,6 +20,7 @@
 #include "resolve-undo.h"
 #include "submodule.h"
 #include "argv-array.h"
+#include "sigchain.h"
 
 static const char * const checkout_usage[] = {
 	N_("git checkout [options] <branch>"),
@@ -814,6 +815,35 @@ static int switch_branches(const struct checkout_opts *opts,
 	return ret || writeout_error;
 }
 
+static const char *junk_work_tree;
+static const char *junk_git_dir;
+static int is_junk;
+static pid_t junk_pid;
+
+static void remove_junk(void)
+{
+	struct strbuf sb = STRBUF_INIT;
+	if (!is_junk || getpid() != junk_pid)
+		return;
+	if (junk_git_dir) {
+		strbuf_addstr(&sb, junk_git_dir);
+		remove_dir_recursively(&sb, 0);
+		strbuf_reset(&sb);
+	}
+	if (junk_work_tree) {
+		strbuf_addstr(&sb, junk_work_tree);
+		remove_dir_recursively(&sb, 0);
+		strbuf_reset(&sb);
+	}
+}
+
+static void remove_junk_on_signal(int signo)
+{
+	remove_junk();
+	sigchain_pop(signo);
+	raise(signo);
+}
+
 static int prepare_linked_checkout(const struct checkout_opts *opts,
 				   struct branch_info *new)
 {
@@ -822,7 +852,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	const char *path = opts->new_worktree, *name;
 	struct stat st;
 	struct child_process cp;
-	int counter = 0, len;
+	int counter = 0, len, ret;
 
 	if (!new->commit)
 		die(_("no branch specified"));
@@ -848,13 +878,21 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 		strbuf_addf(&sb_repo, "%d", counter);
 	}
 	name = strrchr(sb_repo.buf, '/') + 1;
+
+	junk_pid = getpid();
+	atexit(remove_junk);
+	sigchain_push_common(remove_junk_on_signal);
+
 	if (mkdir(sb_repo.buf, 0777))
 		die_errno(_("could not create directory of '%s'"), sb_repo.buf);
+	junk_git_dir = sb_repo.buf;
+	is_junk = 1;
 
 	strbuf_addf(&sb_git, "%s/.git", path);
 	if (safe_create_leading_directories_const(sb_git.buf))
 		die_errno(_("could not create leading directories of '%s'"),
 			  sb_git.buf);
+	junk_work_tree = path;
 
 	write_file(sb_git.buf, 1, "gitdir: %s/repos/%s\n",
 		   real_path(get_git_common_dir()), name);
@@ -879,7 +917,14 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	memset(&cp, 0, sizeof(cp));
 	cp.git_cmd = 1;
 	cp.argv = opts->saved_argv;
-	return run_command(&cp);
+	ret = run_command(&cp);
+	if (!ret)
+		is_junk = 0;
+	strbuf_release(&sb);
+	strbuf_release(&sb_repo);
+	strbuf_release(&sb_git);
+	return ret;
+
 }
 
 static int git_checkout_config(const char *var, const char *value, void *cb)
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 23/27] checkout: detach if the branch is already checked out elsewhere
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (21 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 22/27] checkout: clean up half-prepared directories in --to mode Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:12   ` Nguyễn Thái Ngọc Duy
  2014-03-01 12:13   ` [PATCH v4 24/27] prune: strategies for linked checkouts Nguyễn Thái Ngọc Duy
                     ` (4 subsequent siblings)
  27 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:12 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

The normal rule is anything outside refs/heads/ is detached. This
increases strictness of the rule a bit more: if the branch is checked
out (either in $GIT_COMMON_DIR/HEAD or any $GIT_DIR/repos/.../HEAD)
then it's detached as well.

A hint is given so the user knows where to go and do something there
if they still want to checkout undetached here.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c     | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++
 t/t2025-checkout-to.sh | 15 ++++++++--
 2 files changed, 92 insertions(+), 3 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 28f9ac1..1675808 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -433,6 +433,11 @@ struct branch_info {
 	const char *name; /* The short name used */
 	const char *path; /* The full name of a real branch */
 	struct commit *commit; /* The named commit */
+	/*
+	 * if not null the branch is detached because it's already
+	 * checked out in this checkout
+	 */
+	char *checkout;
 };
 
 static void setup_branch_path(struct branch_info *branch)
@@ -640,6 +645,11 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 			if (old->path && advice_detached_head)
 				detach_advice(new->name);
 			describe_detached_head(_("HEAD is now at"), new->commit);
+			if (new->checkout && !*new->checkout)
+				fprintf(stderr, _("hint: the main checkout is holding this branch\n"));
+			else if (new->checkout)
+				fprintf(stderr, _("hint: the linked checkout %s is holding this branch\n"),
+					new->checkout);
 		}
 	} else if (new->path) {	/* Switch branches. */
 		create_symref("HEAD", new->path, msg.buf);
@@ -982,6 +992,73 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1)
 	return NULL;
 }
 
+static int check_linked_checkout(struct branch_info *new,
+				  const char *name, const char *path)
+{
+	struct strbuf sb = STRBUF_INIT;
+	char *start, *end;
+	if (strbuf_read_file(&sb, path, 0) < 0)
+		return 0;
+	if (!starts_with(sb.buf, "ref:")) {
+		strbuf_release(&sb);
+		return 0;
+	}
+
+	start = sb.buf + 4;
+	while (isspace(*start))
+		start++;
+	end = start;
+	while (*end && !isspace(*end))
+		end++;
+	if (!strncmp(start, new->path, end - start) &&
+	    new->path[end - start] == '\0') {
+		strbuf_release(&sb);
+		new->path = NULL; /* detach */
+		new->checkout = xstrdup(name); /* reason */
+		return 1;
+	}
+	strbuf_release(&sb);
+	return 0;
+}
+
+static void check_linked_checkouts(struct branch_info *new)
+{
+	struct strbuf path = STRBUF_INIT;
+	DIR *dir;
+	struct dirent *d;
+
+	strbuf_addf(&path, "%s/repos", get_git_common_dir());
+	if ((dir = opendir(path.buf)) == NULL) {
+		strbuf_release(&path);
+		return;
+	}
+
+	strbuf_reset(&path);
+	strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
+	/*
+	 * $GIT_COMMON_DIR/HEAD is practically outside
+	 * $GIT_DIR so resolve_ref_unsafe() won't work (it
+	 * uses git_path). Parse the ref ourselves.
+	 */
+	if (check_linked_checkout(new, "", path.buf)) {
+		strbuf_release(&path);
+		closedir(dir);
+		return;
+	}
+
+	while ((d = readdir(dir)) != NULL) {
+		if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+			continue;
+		strbuf_reset(&path);
+		strbuf_addf(&path, "%s/repos/%s/HEAD",
+			    get_git_common_dir(), d->d_name);
+		if (check_linked_checkout(new, d->d_name, path.buf))
+			break;
+	}
+	strbuf_release(&path);
+	closedir(dir);
+}
+
 static int parse_branchname_arg(int argc, const char **argv,
 				int dwim_new_local_branch_ok,
 				struct branch_info *new,
@@ -1109,6 +1186,9 @@ static int parse_branchname_arg(int argc, const char **argv,
 	else
 		new->path = NULL; /* not an existing branch */
 
+	if (new->path)
+		check_linked_checkouts(new);
+
 	new->commit = lookup_commit_reference_gently(rev, 1);
 	if (!new->commit) {
 		/* not a commit */
diff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh
index 5ec49e2..2d35a9b 100755
--- a/t/t2025-checkout-to.sh
+++ b/t/t2025-checkout-to.sh
@@ -13,13 +13,14 @@ test_expect_success 'checkout --to not updating paths' '
 '
 
 test_expect_success 'checkout --to a new worktree' '
+	git rev-parse HEAD >expect &&
 	git checkout --to here master &&
 	(
 		cd here &&
 		test_cmp ../init.t init.t &&
-		git symbolic-ref HEAD >actual &&
-		echo refs/heads/master >expect &&
-		test_cmp expect actual &&
+		test_must_fail git symbolic-ref HEAD &&
+		git rev-parse HEAD >actual &&
+		test_cmp ../expect actual &&
 		git fsck
 	)
 '
@@ -45,4 +46,12 @@ test_expect_success 'checkout --to a new worktree creating new branch' '
 	)
 '
 
+test_expect_success 'detach if the same branch is already checked out' '
+	(
+		cd here &&
+		git checkout newmaster &&
+		test_must_fail git symbolic-ref HEAD
+	)
+'
+
 test_done
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 24/27] prune: strategies for linked checkouts
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (22 preceding siblings ...)
  2014-03-01 12:12   ` [PATCH v4 23/27] checkout: detach if the branch is already checked out elsewhere Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:13   ` Nguyễn Thái Ngọc Duy
  2014-03-01 17:12     ` Torsten Bögershausen
  2014-03-05 20:07     ` Junio C Hamano
  2014-03-01 12:13   ` [PATCH v4 25/27] gc: style change -- no SP before closing bracket Nguyễn Thái Ngọc Duy
                     ` (3 subsequent siblings)
  27 siblings, 2 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:13 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

(alias R=$GIT_COMMON_DIR/repos/<id>)

 - linked checkouts are supposed to keep its location in $R/gitdir up
   to date. The use case is auto fixup after a manual checkout move.

 - linked checkouts are supposed to update mtime of $R/gitdir. If
   $R/gitdir's mtime is older than a limit, and it points to nowhere,
   repos/<id> is to be pruned.

 - "git checkout --to" is supposed to create $R/locked if the new repo
   is on a different partition than the shared one. The main use case
   is when the checkout is on a portable device and may not be
   available at prune time.

   If $R/locked exists, repos/<id> is not supposed to be pruned. If
   $R/locked exists and $R/gitdir's mtime is older than a really long
   limit, warn about old unused repo.

 - "git checkout --to" is supposed to make a hard link named $R/link
   pointing to the .git file on supported file systems to help detect
   the user manually deleting the checkout. If $R/link exists and its
   link count is greated than 1, the repo is kept.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-prune.txt            |  3 ++
 Documentation/gitrepository-layout.txt | 19 +++++++++
 builtin/checkout.c                     | 36 ++++++++++++++++-
 builtin/prune.c                        | 74 ++++++++++++++++++++++++++++++++++
 compat/mingw.h                         |  1 +
 git-compat-util.h                      |  4 ++
 setup.c                                | 13 ++++++
 7 files changed, 149 insertions(+), 1 deletion(-)

diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt
index 058ac0d..7babf11 100644
--- a/Documentation/git-prune.txt
+++ b/Documentation/git-prune.txt
@@ -48,6 +48,9 @@ OPTIONS
 --expire <time>::
 	Only expire loose objects older than <time>.
 
+--repos::
+	Prune directories in $GIT_DIR/repos.
+
 <head>...::
 	In addition to objects
 	reachable from any of our references, keep objects
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index 418e5c8..2dc6901 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -252,6 +252,25 @@ repos::
 	$GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/repos" will be
 	used instead.
 
+repos/<id>/gitdir::
+	A text file containing the absolute path back to the .git file
+	that points to here. This is used to check if the linked
+	repository has been manually removed and there is no need to
+	keep this directory any more. mtime of this file should be
+	updated every time the linked repository is accessed.
+
+repos/<id>/locked::
+	If this file exists, the linked repository may be on a
+	portable device and not available. It does not mean that the
+	linked repository is gone and `repos/<id>` could be
+	removed. The file's content contains a reason string on why
+	the repository is locked.
+
+repos/<id>/link::
+	If this file exists, it is a hard link to the linked .git
+	file. It is used to detect if the linked repository is
+	manually removed.
+
 SEE ALSO
 --------
 linkgit:git-init[1],
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 1675808..1fc85d3 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -854,6 +854,17 @@ static void remove_junk_on_signal(int signo)
 	raise(signo);
 }
 
+static dev_t get_device_or_die(const char *path)
+{
+	struct stat buf;
+	if (stat(path, &buf))
+		die_errno("failed to stat '%s'", path);
+	/* Ah Windows! Make different drives different "partitions" */
+	if (is_windows())
+		buf.st_dev = toupper(real_path(path)[0]);
+	return buf.st_dev;
+}
+
 static int prepare_linked_checkout(const struct checkout_opts *opts,
 				   struct branch_info *new)
 {
@@ -862,7 +873,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	const char *path = opts->new_worktree, *name;
 	struct stat st;
 	struct child_process cp;
-	int counter = 0, len, ret;
+	int counter = 0, len, keep_locked = 0, ret;
 
 	if (!new->commit)
 		die(_("no branch specified"));
@@ -898,12 +909,18 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	junk_git_dir = sb_repo.buf;
 	is_junk = 1;
 
+	strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+	write_file(sb.buf, 1, "initializing\n");
+
 	strbuf_addf(&sb_git, "%s/.git", path);
 	if (safe_create_leading_directories_const(sb_git.buf))
 		die_errno(_("could not create leading directories of '%s'"),
 			  sb_git.buf);
 	junk_work_tree = path;
 
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
+	write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf));
 	write_file(sb_git.buf, 1, "gitdir: %s/repos/%s\n",
 		   real_path(get_git_common_dir()), name);
 	/*
@@ -912,12 +929,24 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	 * value would do because this value will be ignored and
 	 * replaced at the next (real) checkout.
 	 */
+	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
 	write_file(sb.buf, 1, "%s\n", sha1_to_hex(new->commit->object.sha1));
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
 	write_file(sb.buf, 1, "../..\n");
 
+	if (get_device_or_die(path) != get_device_or_die(get_git_dir())) {
+		strbuf_reset(&sb);
+		strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+		write_file(sb.buf, 1, "located on a different file system\n");
+		keep_locked = 1;
+	} else {
+		strbuf_reset(&sb);
+		strbuf_addf(&sb, "%s/link", sb_repo.buf);
+		(void)link(sb_git.buf, sb.buf);
+	}
+
 	if (!opts->quiet)
 		fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
 
@@ -930,6 +959,11 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	ret = run_command(&cp);
 	if (!ret)
 		is_junk = 0;
+	if (!keep_locked) {
+		strbuf_reset(&sb);
+		strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+		unlink_or_warn(sb.buf);
+	}
 	strbuf_release(&sb);
 	strbuf_release(&sb_repo);
 	strbuf_release(&sb_git);
diff --git a/builtin/prune.c b/builtin/prune.c
index de43b26..733cb3b 100644
--- a/builtin/prune.c
+++ b/builtin/prune.c
@@ -112,6 +112,70 @@ static void prune_object_dir(const char *path)
 	}
 }
 
+static const char *prune_repo_dir(const char *id, struct stat *st)
+{
+	char *path;
+	int fd, len;
+	if (file_exists(git_path("repos/%s/locked", id)))
+		return NULL;
+	if (stat(git_path("repos/%s/gitdir", id), st)) {
+		st->st_mtime = expire;
+		return _("gitdir does not exist");
+	}
+	fd = open(git_path("repos/%s/gitdir", id), O_RDONLY);
+	len = st->st_size;
+	path = xmalloc(len + 1);
+	read_in_full(fd, path, len);
+	close(fd);
+	while (path[len - 1] == '\n' || path[len - 1] == '\r')
+		len--;
+	path[len] = '\0';
+	if (!file_exists(path)) {
+		struct stat st_link;
+		free(path);
+		/*
+		 * the repo is moved manually and has not been
+		 * accessed since?
+		 */
+		if (!stat(git_path("repos/%s/link", id), &st_link) &&
+		    st_link.st_nlink > 1)
+			return NULL;
+		return _("gitdir points to non-existing file");
+	}
+	free(path);
+	return NULL;
+}
+
+static void prune_repos_dir(void)
+{
+	const char *reason;
+	DIR *dir = opendir(git_path("repos"));
+	struct dirent *d;
+	int removed = 0;
+	struct stat st;
+	if (!dir)
+		return;
+	while ((d = readdir(dir)) != NULL) {
+		if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+			continue;
+		if ((reason = prune_repo_dir(d->d_name, &st)) != NULL &&
+		    st.st_mtime <= expire) {
+			struct strbuf sb = STRBUF_INIT;
+			if (show_only || verbose)
+				printf(_("Removing repos/%s: %s\n"), d->d_name, reason);
+			if (show_only)
+				continue;
+			strbuf_addstr(&sb, git_path("repos/%s", d->d_name));
+			remove_dir_recursively(&sb, 0);
+			strbuf_release(&sb);
+			removed = 1;
+		}
+	}
+	closedir(dir);
+	if (removed)
+		rmdir(git_path("repos"));
+}
+
 /*
  * Write errors (particularly out of space) can result in
  * failed temporary packs (and more rarely indexes and other
@@ -138,10 +202,12 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
 {
 	struct rev_info revs;
 	struct progress *progress = NULL;
+	int prune_repos = 0;
 	const struct option options[] = {
 		OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
 		OPT__VERBOSE(&verbose, N_("report pruned objects")),
 		OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
+		OPT_BOOL(0, "repos", &prune_repos, N_("prune .git/repos/")),
 		OPT_EXPIRY_DATE(0, "expire", &expire,
 				N_("expire objects older than <time>")),
 		OPT_END()
@@ -154,6 +220,14 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
 	init_revisions(&revs, prefix);
 
 	argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
+
+	if (prune_repos) {
+		if (argc)
+			die(_("--repos does not take extra arguments"));
+		prune_repos_dir();
+		return 0;
+	}
+
 	while (argc--) {
 		unsigned char sha1[20];
 		const char *name = *argv++;
diff --git a/compat/mingw.h b/compat/mingw.h
index e033e72..18323c1 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -328,6 +328,7 @@ int winansi_fprintf(FILE *stream, const char *format, ...) __attribute__((format
  * git specific compatibility
  */
 
+#define is_windows() 1
 #define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':')
 #define is_dir_sep(c) ((c) == '/' || (c) == '\\')
 static inline char *mingw_find_last_dir_sep(const char *path)
diff --git a/git-compat-util.h b/git-compat-util.h
index cbd86c3..41f1b74 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -266,6 +266,10 @@ extern char *gitbasename(char *);
 #define STRIP_EXTENSION ""
 #endif
 
+#ifndef is_windows
+#define is_windows() 0
+#endif
+
 #ifndef has_dos_drive_prefix
 #define has_dos_drive_prefix(path) 0
 #endif
diff --git a/setup.c b/setup.c
index 40ce191..5529b26 100644
--- a/setup.c
+++ b/setup.c
@@ -336,6 +336,17 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 	return ret;
 }
 
+static void update_linked_gitdir(const char *gitfile, const char *gitdir)
+{
+	struct strbuf path = STRBUF_INIT;
+	struct stat st;
+
+	strbuf_addf(&path, "%s/gitfile", gitdir);
+	if (stat(path.buf, &st) || st.st_mtime + 24 * 3600 < time(NULL))
+		write_file(path.buf, 0, "%s\n", gitfile);
+	strbuf_release(&path);
+}
+
 /*
  * Try to read the location of the git directory from the .git file,
  * return path to git directory if found.
@@ -384,6 +395,8 @@ const char *read_gitfile(const char *path)
 
 	if (!is_git_directory(dir))
 		die("Not a git repository: %s", dir);
+
+	update_linked_gitdir(path, dir);
 	path = real_path(dir);
 
 	free(buf);
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 25/27] gc: style change -- no SP before closing bracket
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (23 preceding siblings ...)
  2014-03-01 12:13   ` [PATCH v4 24/27] prune: strategies for linked checkouts Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:13   ` Nguyễn Thái Ngọc Duy
  2014-03-01 12:13   ` [PATCH v4 26/27] gc: support prune --repos Nguyễn Thái Ngọc Duy
                     ` (2 subsequent siblings)
  27 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:13 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/gc.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/builtin/gc.c b/builtin/gc.c
index c19545d..39d9b27 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -260,7 +260,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
 		OPT__QUIET(&quiet, N_("suppress progress reporting")),
 		{ OPTION_STRING, 0, "prune", &prune_expire, N_("date"),
 			N_("prune unreferenced objects"),
-			PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },
+			PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire},
 		OPT_BOOL(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")),
 		OPT_BOOL(0, "auto", &auto_gc, N_("enable auto-gc mode")),
 		OPT_BOOL(0, "force", &force, N_("force running gc even if there may be another gc running")),
@@ -273,7 +273,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
 	argv_array_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL);
 	argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
 	argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
-	argv_array_pushl(&prune, "prune", "--expire", NULL );
+	argv_array_pushl(&prune, "prune", "--expire", NULL);
 	argv_array_pushl(&rerere, "rerere", "gc", NULL);
 
 	git_config(gc_config, NULL);
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 26/27] gc: support prune --repos
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (24 preceding siblings ...)
  2014-03-01 12:13   ` [PATCH v4 25/27] gc: style change -- no SP before closing bracket Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:13   ` Nguyễn Thái Ngọc Duy
  2014-03-07  6:40     ` Eric Sunshine
  2014-03-01 12:13   ` [PATCH v4 27/27] count-objects: report unused files in $GIT_DIR/repos/ Nguyễn Thái Ngọc Duy
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
  27 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:13 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/config.txt |  6 ++++++
 builtin/gc.c             | 17 +++++++++++++++++
 2 files changed, 23 insertions(+)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 313d4b3..438b213 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1183,6 +1183,12 @@ gc.pruneexpire::
 	"now" may be used to disable this  grace period and always prune
 	unreachable objects immediately.
 
+gc.prunereposexpire::
+	When 'git gc' is run, it will call 'prune --repos --expire 3.months.ago'.
+	Override the grace period with this config variable.  The value
+	"now" may be used to disable this  grace period and always prune
+	$GIT_DIR/repos immediately.
+
 gc.reflogexpire::
 gc.<pattern>.reflogexpire::
 	'git reflog expire' removes reflog entries older than
diff --git a/builtin/gc.c b/builtin/gc.c
index 39d9b27..85c3c0c 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -30,11 +30,13 @@ static int aggressive_window = 250;
 static int gc_auto_threshold = 6700;
 static int gc_auto_pack_limit = 50;
 static const char *prune_expire = "2.weeks.ago";
+static const char *prune_repos_expire = "3.months.ago";
 
 static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT;
 static struct argv_array reflog = ARGV_ARRAY_INIT;
 static struct argv_array repack = ARGV_ARRAY_INIT;
 static struct argv_array prune = ARGV_ARRAY_INIT;
+static struct argv_array prune_repos = ARGV_ARRAY_INIT;
 static struct argv_array rerere = ARGV_ARRAY_INIT;
 
 static char *pidfile;
@@ -81,6 +83,14 @@ static int gc_config(const char *var, const char *value, void *cb)
 		}
 		return git_config_string(&prune_expire, var, value);
 	}
+	if (!strcmp(var, "gc.prunereposexpire")) {
+		if (value && strcmp(value, "now")) {
+			unsigned long now = approxidate("now");
+			if (approxidate(value) >= now)
+				return error(_("Invalid %s: '%s'"), var, value);
+		}
+		return git_config_string(&prune_repos_expire, var, value);
+	}
 	return git_default_config(var, value, cb);
 }
 
@@ -274,6 +284,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
 	argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
 	argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
 	argv_array_pushl(&prune, "prune", "--expire", NULL);
+	argv_array_pushl(&prune_repos, "prune", "--repos", "--expire", NULL);
 	argv_array_pushl(&rerere, "rerere", "gc", NULL);
 
 	git_config(gc_config, NULL);
@@ -334,6 +345,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
 			return error(FAILED_RUN, prune.argv[0]);
 	}
 
+	if (prune_repos_expire) {
+		argv_array_push(&prune_repos, prune_repos_expire);
+		if (run_command_v_opt(prune_repos.argv, RUN_GIT_CMD))
+			return error(FAILED_RUN, prune_repos.argv[0]);
+	}
+
 	if (run_command_v_opt(rerere.argv, RUN_GIT_CMD))
 		return error(FAILED_RUN, rerere.argv[0]);
 
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v4 27/27] count-objects: report unused files in $GIT_DIR/repos/...
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (25 preceding siblings ...)
  2014-03-01 12:13   ` [PATCH v4 26/27] gc: support prune --repos Nguyễn Thái Ngọc Duy
@ 2014-03-01 12:13   ` Nguyễn Thái Ngọc Duy
  2014-03-05  4:25     ` Eric Sunshine
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
  27 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-01 12:13 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

In linked checkouts, borrowed parts like config is taken from
$GIT_COMMON_DIR. $GIT_DIR/config is never used. Report them as
garbage.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/count-objects.c | 37 ++++++++++++++++++++++++++++++++++++-
 path.c                  |  4 ++++
 2 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/builtin/count-objects.c b/builtin/count-objects.c
index a7f70cb..725cd5f 100644
--- a/builtin/count-objects.c
+++ b/builtin/count-objects.c
@@ -78,6 +78,39 @@ static void count_objects(DIR *d, char *path, int len, int verbose,
 	}
 }
 
+static void report_linked_checkout_garbage(void)
+{
+	/*
+	 * must be more or less in sync with * path.c:update_common_dir().
+	 *
+	 * "logs" is let slip because logs/HEAD is in $GIT_DIR but the
+	 * remaining in $GIT_COMMON_DIR. Probably not worth traversing
+	 * the entire "logs" directory for that.
+	 *
+	 * The same "gc.pid" for because it's a temporary file.
+	 */
+	const char *list[] = {
+		"branches", "hooks", "info", "lost-found", "modules",
+		"objects", "refs", "remotes", "rr-cache", "svn",
+		"config", "packed-refs", "shallow", NULL
+	};
+	struct strbuf sb = STRBUF_INIT;
+	const char **p;
+	int len;
+
+	if (!file_exists(git_path("commondir")))
+		return;
+	strbuf_addf(&sb, "%s/", get_git_dir());
+	len = sb.len;
+	for (p = list; *p; p++) {
+		strbuf_setlen(&sb, len);
+		strbuf_addstr(&sb, *p);
+		if (file_exists(sb.buf))
+			report_garbage("unused in linked checkout", sb.buf);
+	}
+	strbuf_release(&sb);
+}
+
 static char const * const count_objects_usage[] = {
 	N_("git count-objects [-v] [-H | --human-readable]"),
 	NULL
@@ -102,8 +135,10 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
 	/* we do not take arguments other than flags for now */
 	if (argc)
 		usage_with_options(count_objects_usage, opts);
-	if (verbose)
+	if (verbose) {
 		report_garbage = real_report_garbage;
+		report_linked_checkout_garbage();
+	}
 	memcpy(path, objdir, len);
 	if (len && objdir[len-1] != '/')
 		path[len++] = '/';
diff --git a/path.c b/path.c
index 47383ff..2e6035d 100644
--- a/path.c
+++ b/path.c
@@ -92,6 +92,10 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)
 
 static void update_common_dir(struct strbuf *buf, int git_dir_len)
 {
+	/*
+	 * Remember to report_linked_checkout_garbage()
+	 * builtin/count-objects.c
+	 */
 	const char *common_dir_list[] = {
 		"branches", "hooks", "info", "logs", "lost-found", "modules",
 		"objects", "refs", "remotes", "repos", "rr-cache", "svn",
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 13/27] git-stash: avoid hardcoding $GIT_DIR/logs/....
  2014-03-01 12:12   ` [PATCH v4 13/27] git-stash: avoid hardcoding $GIT_DIR/logs/ Nguyễn Thái Ngọc Duy
@ 2014-03-01 15:50     ` Torsten Bögershausen
  0 siblings, 0 replies; 169+ messages in thread
From: Torsten Bögershausen @ 2014-03-01 15:50 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy, git; +Cc: Junio C Hamano

On 2014-03-01 13.12, Nguyễn Thái Ngọc Duy wrote:
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  git-stash.sh | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)
> 
> diff --git a/git-stash.sh b/git-stash.sh
> index ae7d16e..12d9b37 100755
> --- a/git-stash.sh
> +++ b/git-stash.sh
> @@ -183,7 +183,7 @@ store_stash () {
>  	fi
>  
>  	# Make sure the reflog for stash is kept.
> -	: >>"$GIT_DIR/logs/$ref_stash"
> +	: >>"`git rev-parse --git-path logs/$ref_stash`"
            "$(git rev-parse --git-path logs/$ref_stash)"
Shouldn't we prefer $() over `` ?

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 19/27] wrapper.c: wrapper to open a file, fprintf then close
  2014-03-01 12:12   ` [PATCH v4 19/27] wrapper.c: wrapper to open a file, fprintf then close Nguyễn Thái Ngọc Duy
@ 2014-03-01 17:11     ` Torsten Bögershausen
  2014-03-04  2:47       ` Eric Sunshine
  0 siblings, 1 reply; 169+ messages in thread
From: Torsten Bögershausen @ 2014-03-01 17:11 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy, git; +Cc: Junio C Hamano

On 2014-03-01 13.12, Nguyễn Thái Ngọc Duy wrote:
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  cache.h   |  2 ++
>  wrapper.c | 31 +++++++++++++++++++++++++++++++
>  2 files changed, 33 insertions(+)
> 
> diff --git a/cache.h b/cache.h
> index 98b5dd3..99b86d9 100644
> --- a/cache.h
> +++ b/cache.h
> @@ -1239,6 +1239,8 @@ static inline ssize_t write_str_in_full(int fd, const char *str)
>  {
>  	return write_in_full(fd, str, strlen(str));
>  }
> +__attribute__((format (printf,3,4)))
> +extern int write_file(const char *path, int fatal, const char *fmt, ...);
>  
>  /* pager.c */
>  extern void setup_pager(void);
> diff --git a/wrapper.c b/wrapper.c
> index 0cc5636..5ced50d 100644
> --- a/wrapper.c
> +++ b/wrapper.c
> @@ -455,3 +455,34 @@ struct passwd *xgetpwuid_self(void)
>  		    errno ? strerror(errno) : _("no such user"));
>  	return pw;
>  }
> +
> +int write_file(const char *path, int fatal, const char *fmt, ...)
> +{
> +	struct strbuf sb = STRBUF_INIT;
> +	int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);
> +	va_list params;
> +	if (fd < 0) {
> +		if (fatal)
> +			die_errno(_("could not open %s for writing"), path);
> +		return -1;
> +	}
> +	va_start(params, fmt);
> +	strbuf_vaddf(&sb, fmt, params);
> +	va_end(params);
> +	if (write_in_full(fd, sb.buf, sb.len) != sb.len) {
> +		int err = errno;
> +		close(fd);
> +		errno = err;
> +		strbuf_release(&sb);
Micro nit:
Today we now what strbuf_release() is doing, but if we ever change the
implementation, it is 3% safer to keep err a little bit longer like this:
> +		strbuf_release(&sb);
> +		errno = err;

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 24/27] prune: strategies for linked checkouts
  2014-03-01 12:13   ` [PATCH v4 24/27] prune: strategies for linked checkouts Nguyễn Thái Ngọc Duy
@ 2014-03-01 17:12     ` Torsten Bögershausen
  2014-03-02  0:01       ` Duy Nguyen
  2014-03-05 20:07     ` Junio C Hamano
  1 sibling, 1 reply; 169+ messages in thread
From: Torsten Bögershausen @ 2014-03-01 17:12 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy, git; +Cc: Junio C Hamano

On 2014-03-01 13.13, Nguyễn Thái Ngọc Duy wrote:
[]
>  
> +static dev_t get_device_or_die(const char *path)
> +{
> +	struct stat buf;
> +	if (stat(path, &buf))
> +		die_errno("failed to stat '%s'", path);
> +	/* Ah Windows! Make different drives different "partitions" */
> +	if (is_windows())
> +		buf.st_dev = toupper(real_path(path)[0]);
> +	return buf.st_dev;

Is this only related to Windows ?
Do we have other file systems, which return st_dev == 0 ?
Should we check that path[0] != '/', or better !is_dir_sep(path[0]) ?
Do we need has_dos_drive_prefix() ?

As a first suggestion, would this be better:

> +	if (!buf.st_dev)
> +		buf.st_dev = toupper(real_path(path)[0]);

(End of loose thinking)

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 24/27] prune: strategies for linked checkouts
  2014-03-01 17:12     ` Torsten Bögershausen
@ 2014-03-02  0:01       ` Duy Nguyen
  0 siblings, 0 replies; 169+ messages in thread
From: Duy Nguyen @ 2014-03-02  0:01 UTC (permalink / raw)
  To: Torsten Bögershausen; +Cc: Git Mailing List, Junio C Hamano

On Sun, Mar 2, 2014 at 12:12 AM, Torsten Bögershausen <tboegi@web.de> wrote:
> On 2014-03-01 13.13, Nguyễn Thái Ngọc Duy wrote:
> []
>>
>> +static dev_t get_device_or_die(const char *path)
>> +{
>> +     struct stat buf;
>> +     if (stat(path, &buf))
>> +             die_errno("failed to stat '%s'", path);
>> +     /* Ah Windows! Make different drives different "partitions" */
>> +     if (is_windows())
>> +             buf.st_dev = toupper(real_path(path)[0]);
>> +     return buf.st_dev;
>
> Is this only related to Windows ?

Yes. At least the treatment is Windows specific. If st_dev == 0 in
other cases, then we have to deal with them case-by-case.

> Do we have other file systems, which return st_dev == 0 ?
> Should we check that path[0] != '/', or better !is_dir_sep(path[0]) ?
> Do we need has_dos_drive_prefix() ?

real_path() returns an absolute path, so we're guaranteed its first
character is the drive letter, right? (I tried to confirm this by
reading read_path_internal, but it's a bit complex, and I don't have
Windows machine to quickly test it out)

>
> As a first suggestion, would this be better:
>
>> +     if (!buf.st_dev)
>> +             buf.st_dev = toupper(real_path(path)[0]);
>
> (End of loose thinking)



-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 01/27] path.c: make get_pathname() return strbuf instead of static buffer
  2014-03-01 12:12   ` [PATCH v4 01/27] path.c: make get_pathname() return strbuf instead of static buffer Nguyễn Thái Ngọc Duy
@ 2014-03-02 19:51     ` Eric Sunshine
  2014-03-03  0:14       ` Duy Nguyen
  0 siblings, 1 reply; 169+ messages in thread
From: Eric Sunshine @ 2014-03-02 19:51 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: Git List, Junio C Hamano

On Sat, Mar 1, 2014 at 7:12 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> We've been avoiding PATH_MAX whenever possible. This patch makes
> get_pathname() return a strbuf and updates the callers to take
> advantage of this. The code is simplified as we no longer need to
> worry about buffer overflow.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
> diff --git a/path.c b/path.c
> index 24594c4..5346700 100644
> --- a/path.c
> +++ b/path.c
> @@ -49,85 +60,70 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)
>         return cleanup_path(buf);
>  }
>
> -static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)
> +static void vsnpath(struct strbuf *buf, const char *fmt, va_list args)
>  {
>         const char *git_dir = get_git_dir();
> -       size_t len;
> -
> -       len = strlen(git_dir);
> -       if (n < len + 1)
> -               goto bad;
> -       memcpy(buf, git_dir, len);
> -       if (len && !is_dir_sep(git_dir[len-1]))
> -               buf[len++] = '/';
> -       len += vsnprintf(buf + len, n - len, fmt, args);
> -       if (len >= n)
> -               goto bad;
> -       return cleanup_path(buf);
> -bad:
> -       strlcpy(buf, bad_path, n);
> -       return buf;
> +       strbuf_addstr(buf, git_dir);
> +       if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
> +               strbuf_addch(buf, '/');
> +       strbuf_vaddf(buf, fmt, args);
> +       strbuf_cleanup_path(buf);
>  }

There's a slight semantic change here. The old code overwrote 'buf',
but the new code appends to 'buf'. For someone familiar with
sprintf(), or typical va_list or variadic functions there may be an
intuitive expectation that 'buf' will be overwritten. Should this code
be doing strbuf_reset() as its first action (or should that be the
responsibility of the caller who is reusing 'buff')?

>  char *mkpath(const char *fmt, ...)
>  {
>         va_list args;
> -       unsigned len;
> -       char *pathname = get_pathname();
> -
> +       struct strbuf *pathname = get_pathname();
>         va_start(args, fmt);
> -       len = vsnprintf(pathname, PATH_MAX, fmt, args);
> +       strbuf_vaddf(pathname, fmt, args);
>         va_end(args);
> -       if (len >= PATH_MAX)
> -               return bad_path;
> -       return cleanup_path(pathname);
> +       return cleanup_path(pathname->buf);
>  }

Prior to this change, it was possible (though probably not
recommended) for a caller to append gunk to the returned path up to
PATH_MAX without worrying about stomping memory. With the change, this
is no longer true. Should the function be changed to return 'const
char *' to enforce this restriction?

>  char *git_path(const char *fmt, ...)
>  {
> -       char *pathname = get_pathname();
> +       struct strbuf *pathname = get_pathname();
>         va_list args;
> -       char *ret;
> -
>         va_start(args, fmt);
> -       ret = vsnpath(pathname, PATH_MAX, fmt, args);
> +       vsnpath(pathname, fmt, args);
>         va_end(args);
> -       return ret;
> +       return pathname->buf;
>  }

Ditto.

>
>  void home_config_paths(char **global, char **xdg, char *file)
> @@ -158,41 +154,27 @@ void home_config_paths(char **global, char **xdg, char *file)
>
>  char *git_path_submodule(const char *path, const char *fmt, ...)
>  {
> -       char *pathname = get_pathname();
> -       struct strbuf buf = STRBUF_INIT;
> +       struct strbuf *buf = get_pathname();
> ...
> +       strbuf_cleanup_path(buf);
> +       return buf->buf;
>  }

And here?

>
>  int validate_headref(const char *path)
> --
> 1.9.0.40.gaa8c3ea

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 02/27] Convert git_snpath() to strbuf_git_path()
  2014-03-01 12:12   ` [PATCH v4 02/27] Convert git_snpath() to strbuf_git_path() Nguyễn Thái Ngọc Duy
@ 2014-03-03  0:02     ` Eric Sunshine
  2014-03-03  0:15       ` Duy Nguyen
  0 siblings, 1 reply; 169+ messages in thread
From: Eric Sunshine @ 2014-03-03  0:02 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: Git List, Junio C Hamano

On Sat, Mar 1, 2014 at 7:12 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> In the previous patch, git_snpath() is modified to allocate a new
> strbuf buffer because vsnpath() needs that. But that makes it awkward
> because git_snpath() receives a pre-allocated buffer from outside and
> has to copy data back. Rename it to strbuf_git_path() and make it
> receive strbuf directly.
>
> The conversion from git_snpath() to git_path() in
> update_refs_for_switch() is safe because that function does not keep
> any pointer to the round-robin buffer pool allocated by
> get_pathname().
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
> diff --git a/refs.c b/refs.c
> index 89228e2..434bd5e 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -2717,17 +2729,19 @@ static int copy_msg(char *buf, const char *msg)
>         return cp - buf;
>  }
>
> -int log_ref_setup(const char *refname, char *logfile, int bufsize)
> +int log_ref_setup(const char *refname, struct strbuf *sb_logfile)
>  {
>         int logfd, oflags = O_APPEND | O_WRONLY;
> +       const char *logfile;
>
> -       git_snpath(logfile, bufsize, "logs/%s", refname);
> +       strbuf_git_path(sb_logfile, "logs/%s", refname);
> +       logfile = sb_logfile->buf;
>         if (log_all_ref_updates &&
>             (starts_with(refname, "refs/heads/") ||
>              starts_with(refname, "refs/remotes/") ||
>              starts_with(refname, "refs/notes/") ||
>              !strcmp(refname, "HEAD"))) {
> -               if (safe_create_leading_directories(logfile) < 0)
> +               if (safe_create_leading_directories(sb_logfile->buf) < 0)

At this point, 'logfile' is still 'sb_logfile->buf', so do you really
need this change?

>                         return error("unable to create directory for %s",
>                                      logfile);
>                 oflags |= O_CREAT;
> @@ -2762,20 +2776,22 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,
>         int logfd, result, written, oflags = O_APPEND | O_WRONLY;
>         unsigned maxlen, len;
>         int msglen;
> -       char log_file[PATH_MAX];
> +       struct strbuf sb_log_file = STRBUF_INIT;
> +       const char *log_file;
>         char *logrec;
>         const char *committer;
>
>         if (log_all_ref_updates < 0)
>                 log_all_ref_updates = !is_bare_repository();
>
> -       result = log_ref_setup(refname, log_file, sizeof(log_file));
> +       result = log_ref_setup(refname, &sb_log_file);
>         if (result)
> -               return result;
> +               goto done;
> +       log_file = sb_log_file.buf;
>
>         logfd = open(log_file, oflags);
>         if (logfd < 0)
> -               return 0;
> +               goto done;
>         msglen = msg ? strlen(msg) : 0;
>         committer = git_committer_info(0);
>         maxlen = strlen(committer) + msglen + 100;
> --
> 1.9.0.40.gaa8c3ea

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 01/27] path.c: make get_pathname() return strbuf instead of static buffer
  2014-03-02 19:51     ` Eric Sunshine
@ 2014-03-03  0:14       ` Duy Nguyen
  0 siblings, 0 replies; 169+ messages in thread
From: Duy Nguyen @ 2014-03-03  0:14 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: Git List, Junio C Hamano

On Mon, Mar 3, 2014 at 2:51 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
> On Sat, Mar 1, 2014 at 7:12 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
>> We've been avoiding PATH_MAX whenever possible. This patch makes
>> get_pathname() return a strbuf and updates the callers to take
>> advantage of this. The code is simplified as we no longer need to
>> worry about buffer overflow.
>>
>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
>> ---
>> diff --git a/path.c b/path.c
>> index 24594c4..5346700 100644
>> --- a/path.c
>> +++ b/path.c
>> @@ -49,85 +60,70 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)
>>         return cleanup_path(buf);
>>  }
>>
>> -static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)
>> +static void vsnpath(struct strbuf *buf, const char *fmt, va_list args)
>>  {
>>         const char *git_dir = get_git_dir();
>> -       size_t len;
>> -
>> -       len = strlen(git_dir);
>> -       if (n < len + 1)
>> -               goto bad;
>> -       memcpy(buf, git_dir, len);
>> -       if (len && !is_dir_sep(git_dir[len-1]))
>> -               buf[len++] = '/';
>> -       len += vsnprintf(buf + len, n - len, fmt, args);
>> -       if (len >= n)
>> -               goto bad;
>> -       return cleanup_path(buf);
>> -bad:
>> -       strlcpy(buf, bad_path, n);
>> -       return buf;
>> +       strbuf_addstr(buf, git_dir);
>> +       if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
>> +               strbuf_addch(buf, '/');
>> +       strbuf_vaddf(buf, fmt, args);
>> +       strbuf_cleanup_path(buf);
>>  }
>
> There's a slight semantic change here. The old code overwrote 'buf',
> but the new code appends to 'buf'. For someone familiar with
> sprintf(), or typical va_list or variadic functions there may be an
> intuitive expectation that 'buf' will be overwritten. Should this code
> be doing strbuf_reset() as its first action (or should that be the
> responsibility of the caller who is reusing 'buff')?

those callers that use get_pathname() already have strbuf reset, so
I'd say let the remaining callers reset the buffer.

>
>>  char *mkpath(const char *fmt, ...)
>>  {
>>         va_list args;
>> -       unsigned len;
>> -       char *pathname = get_pathname();
>> -
>> +       struct strbuf *pathname = get_pathname();
>>         va_start(args, fmt);
>> -       len = vsnprintf(pathname, PATH_MAX, fmt, args);
>> +       strbuf_vaddf(pathname, fmt, args);
>>         va_end(args);
>> -       if (len >= PATH_MAX)
>> -               return bad_path;
>> -       return cleanup_path(pathname);
>> +       return cleanup_path(pathname->buf);
>>  }
>
> Prior to this change, it was possible (though probably not
> recommended) for a caller to append gunk to the returned path up to
> PATH_MAX without worrying about stomping memory. With the change, this
> is no longer true. Should the function be changed to return 'const
> char *' to enforce this restriction?

Sure. Will do (same for below)
-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 02/27] Convert git_snpath() to strbuf_git_path()
  2014-03-03  0:02     ` Eric Sunshine
@ 2014-03-03  0:15       ` Duy Nguyen
  2014-03-07  5:03         ` Duy Nguyen
  0 siblings, 1 reply; 169+ messages in thread
From: Duy Nguyen @ 2014-03-03  0:15 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: Git List, Junio C Hamano

On Mon, Mar 3, 2014 at 7:02 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
> On Sat, Mar 1, 2014 at 7:12 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
>> In the previous patch, git_snpath() is modified to allocate a new
>> strbuf buffer because vsnpath() needs that. But that makes it awkward
>> because git_snpath() receives a pre-allocated buffer from outside and
>> has to copy data back. Rename it to strbuf_git_path() and make it
>> receive strbuf directly.
>>
>> The conversion from git_snpath() to git_path() in
>> update_refs_for_switch() is safe because that function does not keep
>> any pointer to the round-robin buffer pool allocated by
>> get_pathname().
>>
>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
>> ---
>> diff --git a/refs.c b/refs.c
>> index 89228e2..434bd5e 100644
>> --- a/refs.c
>> +++ b/refs.c
>> @@ -2717,17 +2729,19 @@ static int copy_msg(char *buf, const char *msg)
>>         return cp - buf;
>>  }
>>
>> -int log_ref_setup(const char *refname, char *logfile, int bufsize)
>> +int log_ref_setup(const char *refname, struct strbuf *sb_logfile)
>>  {
>>         int logfd, oflags = O_APPEND | O_WRONLY;
>> +       const char *logfile;
>>
>> -       git_snpath(logfile, bufsize, "logs/%s", refname);
>> +       strbuf_git_path(sb_logfile, "logs/%s", refname);
>> +       logfile = sb_logfile->buf;
>>         if (log_all_ref_updates &&
>>             (starts_with(refname, "refs/heads/") ||
>>              starts_with(refname, "refs/remotes/") ||
>>              starts_with(refname, "refs/notes/") ||
>>              !strcmp(refname, "HEAD"))) {
>> -               if (safe_create_leading_directories(logfile) < 0)
>> +               if (safe_create_leading_directories(sb_logfile->buf) < 0)
>
> At this point, 'logfile' is still 'sb_logfile->buf', so do you really
> need this change?

Junio made the same comment last time and I missed it. Will update.
-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 05/27] Make git_path() aware of file relocation in $GIT_DIR
  2014-03-01 12:12   ` [PATCH v4 05/27] Make git_path() aware of file relocation in $GIT_DIR Nguyễn Thái Ngọc Duy
@ 2014-03-03  1:34     ` Eric Sunshine
  0 siblings, 0 replies; 169+ messages in thread
From: Eric Sunshine @ 2014-03-03  1:34 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: Git List, Junio C Hamano

On Sat, Mar 1, 2014 at 7:12 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> We allow the user to relocate certain paths out of $GIT_DIR via
> environment variables, e.g. GIT_OBJECT_DIRECTORY, GIT_INDEX_FILE and
> GIT_GRAFT_FILE. All callers are not supposed to use git_path() or

"All callers are not" is unusually difficult to understand. Changing
it to "Callers are not" simplifies.

> git_pathdup() to get those paths. Instead they must use
> get_object_directory(), get_index_file() and get_graft_file()
> respectively. This is inconvenient and could be missed in review
> (there's git_path("objects/info/alternates") somewhere in

"(for example, there's..." reads a bit better.

> sha1_file.c).
>
> This patch makes git_path() and git_pathdup() understand those
> environment variables. So if you set GIT_OBJECT_DIRECTORY to /foo/bar,
> git_path("objects/abc") should return /tmp/bar/abc. The same is done

I guess you mean it should return /foo/bar/abc.

> for the two remaining env variables.
>
> "git rev-parse --git-path" is the wrapper for script use.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  Documentation/git-rev-parse.txt |  5 +++++
>  builtin/rev-parse.c             |  7 +++++++
>  cache.h                         |  1 +
>  environment.c                   |  9 ++++++--
>  path.c                          | 46 +++++++++++++++++++++++++++++++++++++++++
>  t/t0060-path-utils.sh           | 19 +++++++++++++++++
>  6 files changed, 85 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
> index 0d2cdcd..33e4e90 100644
> --- a/Documentation/git-rev-parse.txt
> +++ b/Documentation/git-rev-parse.txt
> @@ -232,6 +232,11 @@ print a message to stderr and exit with nonzero status.
>         repository.  If <path> is a gitfile then the resolved path
>         to the real repository is printed.
>
> +--git-path <path>::
> +       Resolve "$GIT_DIR/<path>" and takes other path relocation
> +       variables such as $GIT_OBJECT_DIRECTORY,
> +       $GIT_INDEX_FILE... into account.

Would it help to add a quick illustration here?

    For example, if GIT_OBJECT_DIRECTORY is /foo/bar,
    then "git rev-parse --git-path objects/abc" returns /foo/bar/abc.

>  --show-cdup::
>         When the command is invoked from a subdirectory, show the
>         path of the top-level directory relative to the current
> diff --git a/path.c b/path.c
> index ccd7228..e020530 100644
> --- a/path.c
> +++ b/path.c
> @@ -60,13 +60,59 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)
>         return cleanup_path(buf);
>  }
>
>  static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
>  {
>         const char *git_dir = get_git_dir();
> +       int gitdir_len;
>         strbuf_addstr(buf, git_dir);

Maybe simplify by dropping git_dir and invoking strbuf_addstr(buf,
get_git_dir())?

>         if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
>                 strbuf_addch(buf, '/');
> +       gitdir_len = buf->len;
>         strbuf_vaddf(buf, fmt, args);
> +       adjust_git_path(buf, gitdir_len);
>         strbuf_cleanup_path(buf);
>  }
>

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 10/27] Add new environment variable $GIT_COMMON_DIR
  2014-03-01 12:12   ` [PATCH v4 10/27] Add new environment variable $GIT_COMMON_DIR Nguyễn Thái Ngọc Duy
@ 2014-03-03  7:29     ` Eric Sunshine
  0 siblings, 0 replies; 169+ messages in thread
From: Eric Sunshine @ 2014-03-03  7:29 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: Git List, Junio C Hamano

On Sat, Mar 1, 2014 at 7:12 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> This variable is intended to support multiple working directories
> attached to a repository. Such a repository may have a main working
> directory, created by either "git init" or "git clone" and one or more
> linked working directories. These working directories and the main
> repository share the same repository directory.
> ---
> diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
> index aa03882..10672a1 100644
> --- a/Documentation/gitrepository-layout.txt
> +++ b/Documentation/gitrepository-layout.txt
> @@ -46,6 +46,9 @@ of incomplete object store is not suitable to be published for
>  use with dumb transports but otherwise is OK as long as
>  `objects/info/alternates` points at the object stores it
>  borrows from.
> ++
> +This directory is ignored $GIT_COMMON_DIR is set and

s/ignored \$/ignored if $/g

Note the /g since this error is repeated throughout the rest of the
gitrepository-layout.txt patch.

> +"$GIT_COMMON_DIR/objects" will be used instead.
>
>  objects/[0-9a-f][0-9a-f]::
>         A newly created object is stored in its own file.

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 12/27] *.sh: avoid hardcoding $GIT_DIR/hooks/...
  2014-03-01 12:12   ` [PATCH v4 12/27] *.sh: avoid hardcoding $GIT_DIR/hooks/ Nguyễn Thái Ngọc Duy
@ 2014-03-03  8:31     ` Eric Sunshine
  0 siblings, 0 replies; 169+ messages in thread
From: Eric Sunshine @ 2014-03-03  8:31 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: Git List, Junio C Hamano

On Sat, Mar 1, 2014 at 7:12 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> If $GIT_COMMON_DIR is set, it should be $GIT_COMMON_DIR/hooks/, not
> $GIT_DIR/hooks/. Just let rev-parse --git-path handle it.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  git-am.sh                              | 22 +++++++++++-----------
>  git-rebase--interactive.sh             |  6 +++---
>  git-rebase--merge.sh                   |  6 ++----
>  git-rebase.sh                          |  4 ++--
>  templates/hooks--applypatch-msg.sample |  4 ++--
>  templates/hooks--pre-applypatch.sample |  4 ++--
>  6 files changed, 22 insertions(+), 24 deletions(-)
>
> diff --git a/git-am.sh b/git-am.sh
> index bbea430..dfa0618 100755
> --- a/git-am.sh
> +++ b/git-am.sh
> @@ -803,10 +803,10 @@ To restore the original branch and stop patching run \"\$cmdline --abort\"."
>                 continue
>         fi
>
> -       if test -x "$GIT_DIR"/hooks/applypatch-msg
> +       hook="`git rev-parse --git-path hooks/applypatch-msg`"

Did you want to use $(...) rather than `...`?

Same question for the remainder of the patch.

> +       if test -x "$hook"
>         then
> -               "$GIT_DIR"/hooks/applypatch-msg "$dotest/final-commit" ||
> -               stop_here $this
> +               "$hook" "$dotest/final-commit" || stop_here $this
>         fi
>
>         if test -f "$dotest/final-commit"
> @@ -880,9 +880,10 @@ did you forget to use 'git add'?"
>                 stop_here_user_resolve $this
>         fi
>
> -       if test -x "$GIT_DIR"/hooks/pre-applypatch
> +       hook="`git rev-parse --git-path hooks/pre-applypatch`"
> +       if test -x "$hook"
>         then
> -               "$GIT_DIR"/hooks/pre-applypatch || stop_here $this
> +               "$hook" || stop_here $this
>         fi
>
>         tree=$(git write-tree) &&
> @@ -908,18 +909,17 @@ did you forget to use 'git add'?"
>                 echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten"
>         fi
>
> -       if test -x "$GIT_DIR"/hooks/post-applypatch
> -       then
> -               "$GIT_DIR"/hooks/post-applypatch
> -       fi
> +       hook="`git rev-parse --git-path hooks/post-applypatch`"
> +       test -x "$hook" && "$hook"
>
>         go_next
>  done
>
>  if test -s "$dotest"/rewritten; then
>      git notes copy --for-rewrite=rebase < "$dotest"/rewritten
> -    if test -x "$GIT_DIR"/hooks/post-rewrite; then
> -       "$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
> +    hook="`git rev-parse --git-path hooks/post-rewrite`"
> +    if test -x "$hook"; then
> +       "$hook" rebase < "$dotest"/rewritten
>      fi
>  fi
>
> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
> index 43c19e0..d741b04 100644
> --- a/git-rebase--interactive.sh
> +++ b/git-rebase--interactive.sh
> @@ -632,9 +632,9 @@ do_next () {
>                 git notes copy --for-rewrite=rebase < "$rewritten_list" ||
>                 true # we don't care if this copying failed
>         } &&
> -       if test -x "$GIT_DIR"/hooks/post-rewrite &&
> -               test -s "$rewritten_list"; then
> -               "$GIT_DIR"/hooks/post-rewrite rebase < "$rewritten_list"
> +       hook="`git rev-parse --git-path hooks/post-rewrite`"
> +       if test -x "$hook" && test -s "$rewritten_list"; then
> +               "$hook" rebase < "$rewritten_list"
>                 true # we don't care if this hook failed
>         fi &&
>         warn "Successfully rebased and updated $head_name."
> diff --git a/git-rebase--merge.sh b/git-rebase--merge.sh
> index e7d96de..68f5d09 100644
> --- a/git-rebase--merge.sh
> +++ b/git-rebase--merge.sh
> @@ -93,10 +93,8 @@ finish_rb_merge () {
>         if test -s "$state_dir"/rewritten
>         then
>                 git notes copy --for-rewrite=rebase <"$state_dir"/rewritten
> -               if test -x "$GIT_DIR"/hooks/post-rewrite
> -               then
> -                       "$GIT_DIR"/hooks/post-rewrite rebase <"$state_dir"/rewritten
> -               fi
> +               hook="`git rev-parse --git-path hooks/post-rewrite`"
> +               test -x "$hook" && "$hook" rebase <"$state_dir"/rewritten
>         fi
>         say All done.
>  }
> diff --git a/git-rebase.sh b/git-rebase.sh
> index 8a3efa2..1cf8dba 100755
> --- a/git-rebase.sh
> +++ b/git-rebase.sh
> @@ -195,9 +195,9 @@ run_specific_rebase () {
>
>  run_pre_rebase_hook () {
>         if test -z "$ok_to_skip_pre_rebase" &&
> -          test -x "$GIT_DIR/hooks/pre-rebase"
> +          test -x "`git rev-parse --git-path hooks/pre-rebase`"
>         then
> -               "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} ||
> +               "`git rev-parse --git-path hooks/pre-rebase`" ${1+"$@"} ||
>                 die "$(gettext "The pre-rebase hook refused to rebase.")"
>         fi
>  }
> diff --git a/templates/hooks--applypatch-msg.sample b/templates/hooks--applypatch-msg.sample
> index 8b2a2fe..28b843b 100755
> --- a/templates/hooks--applypatch-msg.sample
> +++ b/templates/hooks--applypatch-msg.sample
> @@ -10,6 +10,6 @@
>  # To enable this hook, rename this file to "applypatch-msg".
>
>  . git-sh-setup
> -test -x "$GIT_DIR/hooks/commit-msg" &&
> -       exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
> +commitmsg="`git rev-parse --git-path hooks/commit-msg`"
> +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
>  :
> diff --git a/templates/hooks--pre-applypatch.sample b/templates/hooks--pre-applypatch.sample
> index b1f187c..51aa244 100755
> --- a/templates/hooks--pre-applypatch.sample
> +++ b/templates/hooks--pre-applypatch.sample
> @@ -9,6 +9,6 @@
>  # To enable this hook, rename this file to "pre-applypatch".
>
>  . git-sh-setup
> -test -x "$GIT_DIR/hooks/pre-commit" &&
> -       exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
> +precommit="`git rev-parse --git-path hooks/pre-commit`"
> +test -x "$precommit" && exec "$precommit" ${1+"$@"}
>  :
> --
> 1.9.0.40.gaa8c3ea
>
> --
> To unsubscribe from this list: send the line "unsubscribe git" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 01/25] path.c: make get_pathname() return strbuf instead of static buffer
  2014-03-01  2:40     ` Duy Nguyen
@ 2014-03-03 17:56       ` Junio C Hamano
  0 siblings, 0 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-03-03 17:56 UTC (permalink / raw)
  To: Duy Nguyen; +Cc: Git Mailing List

Duy Nguyen <pclouds@gmail.com> writes:

> On Thu, Feb 20, 2014 at 6:26 AM, Junio C Hamano <gitster@pobox.com> wrote:
>> Is there a reason not to do just an equivalent of
>>
>>     #define git_pathdup mkpathdup
>>
>> and be done with it?  Am I missing something?
>
> They have a subtle difference: mkpathdup() calls cleanup_path() while
> git_pathdup() does not. Without auditing all call sites, we can't be
> sure this difference is insignificant. So I keep both functions
> separate for now.

Thanks; that is a very good explanation why #define'ing two to be
the same would not be a workable solution to the ownership issue I
raised.

>  char *git_pathdup(const char *fmt, ...)
>  {
> -	char path[PATH_MAX], *ret;
> +	struct strbuf *path = get_pathname();
>  	va_list args;
>  	va_start(args, fmt);
> -	ret = vsnpath(path, sizeof(path), fmt, args);
> +	vsnpath(path, fmt, args);
>  	va_end(args);
> -	return xstrdup(ret);
> +	return strbuf_detach(path, NULL);
>  }

This feels somewhat wrong.

This function is for callers who are willing to take ownership of
the path buffer and promise to free the returned buffer when they
are done, because you are returning strbuf_detach()'ed piece of
memory, giving the ownership away.

The whole point of using get_pathname() is to allow callers not to
care about allocation issues on the paths they scribble on during
their short-and-simple codepaths that do not have too many uses of
similar temporary path buffers.  Why borrow from that round-robin
pool (which may now cause some codepaths to overflow the number of
such active temporary path buffers---have they been all audited)?

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 07/25] reflog: avoid constructing .lock path with git_path
  2014-03-01  3:42     ` Duy Nguyen
@ 2014-03-03 18:07       ` Junio C Hamano
  0 siblings, 0 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-03-03 18:07 UTC (permalink / raw)
  To: Duy Nguyen; +Cc: Git Mailing List

Duy Nguyen <pclouds@gmail.com> writes:

> On Wed, Feb 26, 2014 at 5:44 AM, Junio C Hamano <gitster@pobox.com> wrote:
>> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
>>
>>> git_path() soon understands the path given to it. Some paths "abc" may
>>> become "def" while other "ghi" may become "ijk". We don't want
>>> git_path() to interfere with .lock path construction. Concatenate
>>> ".lock" after the path has been resolved by git_path() so if "abc"
>>> becomes "def", we'll have "def.lock", not "ijk".
>>
>> Hmph.  I am not sure if the above is readable (or if I am reading it
>> correctly).
>
> Definitely not now that I have had my break from the series and reread it.
>
>> If "abc" becomes "def", it would take deliberate work to make
>> "abc.lock" into "ijk", and it would be natural to expect that
>> "abc.lock" would become "def.lock" without any fancy trick, no?
>
> A better explanation may be, while many paths are not converted by
> git_path() ("abc" -> "abc"), some of them will be based on the given
> path ("def" -> "ghi"). Giving path def.lock to git_path() may confuse
> it and make it believe def.lock should not be converted because the
> signature is "def.lock" not "def". But we want the lock file to have
> the same base name with the locked file (e.g. "ghi.lock", not
> "def.lock"). So it's best to append ".lock" after git_path() has done
> its conversion.
>
> The alternative is teach git_path about ".lock", but I don't really
> want to put more logic down there.

I think your attempt to sound generic by using "abc", "def",
etc. backfired and made the description only obscure.  It would have
been immediately understandable if it were more like this:

	Among pathnames in $GIT_DIR, e.g. "index" or "packed-refs",
	we want to automatically and silently map some of them to
	the $GIT_DIR of the repository we are borrowing from via
	$GIT_COMMON_DIR mechanism.  When we formulate the pathname
	for its lockfile, we want it to be in the same location as
	its final destination.  "index" is not shared and needs to
	remain in the borrowing repository, while "packed-refs" is
	shared and needs to go to the borrowed repository.

	git_path() could be taught about the ".lock" suffix and map
	"index.lock" and "packed-refs.lock" the same way their
	basenames are mapped, but instead the caller can help by
	asking where the basename (e.g. "index") is mapped to
	git_path() and then appending ".lock" after the mapping is
	done.

Thanks for an explanation.  With that understanding, the patch makes
sense.

	

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 19/27] wrapper.c: wrapper to open a file, fprintf then close
  2014-03-01 17:11     ` Torsten Bögershausen
@ 2014-03-04  2:47       ` Eric Sunshine
  0 siblings, 0 replies; 169+ messages in thread
From: Eric Sunshine @ 2014-03-04  2:47 UTC (permalink / raw)
  To: Torsten Bögershausen
  Cc: Nguyễn Thái Ngọc Duy, Git List, Junio C Hamano

On Sat, Mar 1, 2014 at 12:11 PM, Torsten Bögershausen <tboegi@web.de> wrote:
> On 2014-03-01 13.12, Nguyễn Thái Ngọc Duy wrote:
>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
>> ---
>>  cache.h   |  2 ++
>>  wrapper.c | 31 +++++++++++++++++++++++++++++++
>>  2 files changed, 33 insertions(+)
>>
>> diff --git a/cache.h b/cache.h
>> index 98b5dd3..99b86d9 100644
>> --- a/cache.h
>> +++ b/cache.h
>> @@ -1239,6 +1239,8 @@ static inline ssize_t write_str_in_full(int fd, const char *str)
>>  {
>>       return write_in_full(fd, str, strlen(str));
>>  }
>> +__attribute__((format (printf,3,4)))
>> +extern int write_file(const char *path, int fatal, const char *fmt, ...);
>>
>>  /* pager.c */
>>  extern void setup_pager(void);
>> diff --git a/wrapper.c b/wrapper.c
>> index 0cc5636..5ced50d 100644
>> --- a/wrapper.c
>> +++ b/wrapper.c
>> @@ -455,3 +455,34 @@ struct passwd *xgetpwuid_self(void)
>>                   errno ? strerror(errno) : _("no such user"));
>>       return pw;
>>  }
>> +
>> +int write_file(const char *path, int fatal, const char *fmt, ...)
>> +{
>> +     struct strbuf sb = STRBUF_INIT;
>> +     int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);
>> +     va_list params;
>> +     if (fd < 0) {

Micro nit atop Torsten's micro nit:

It is 3% easier to understand the code if the check for open() failure
immediately follows the open() attempt:

    va_list params;
    int fd = open(...);
    if (fd < 0) {

>> +             if (fatal)
>> +                     die_errno(_("could not open %s for writing"), path);
>> +             return -1;
>> +     }
>> +     va_start(params, fmt);
>> +     strbuf_vaddf(&sb, fmt, params);
>> +     va_end(params);
>> +     if (write_in_full(fd, sb.buf, sb.len) != sb.len) {
>> +             int err = errno;
>> +             close(fd);
>> +             errno = err;
>> +             strbuf_release(&sb);
> Micro nit:
> Today we now what strbuf_release() is doing, but if we ever change the
> implementation, it is 3% safer to keep err a little bit longer like this:
>> +             strbuf_release(&sb);
>> +             errno = err;

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 27/27] count-objects: report unused files in $GIT_DIR/repos/...
  2014-03-01 12:13   ` [PATCH v4 27/27] count-objects: report unused files in $GIT_DIR/repos/ Nguyễn Thái Ngọc Duy
@ 2014-03-05  4:25     ` Eric Sunshine
  2014-03-05 12:08       ` Duy Nguyen
  0 siblings, 1 reply; 169+ messages in thread
From: Eric Sunshine @ 2014-03-05  4:25 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: Git List, Junio C Hamano

On Sat, Mar 1, 2014 at 7:13 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> In linked checkouts, borrowed parts like config is taken from
> $GIT_COMMON_DIR. $GIT_DIR/config is never used. Report them as
> garbage.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  builtin/count-objects.c | 37 ++++++++++++++++++++++++++++++++++++-
>  path.c                  |  4 ++++
>  2 files changed, 40 insertions(+), 1 deletion(-)
>
> diff --git a/builtin/count-objects.c b/builtin/count-objects.c
> index a7f70cb..725cd5f 100644
> --- a/builtin/count-objects.c
> +++ b/builtin/count-objects.c
> @@ -78,6 +78,39 @@ static void count_objects(DIR *d, char *path, int len, int verbose,
>         }
>  }
>
> +static void report_linked_checkout_garbage(void)
> +{
> +       /*
> +        * must be more or less in sync with * path.c:update_common_dir().
> +        *
> +        * "logs" is let slip because logs/HEAD is in $GIT_DIR but the
> +        * remaining in $GIT_COMMON_DIR. Probably not worth traversing
> +        * the entire "logs" directory for that.
> +        *
> +        * The same "gc.pid" for because it's a temporary file.
> +        */
> +       const char *list[] = {
> +               "branches", "hooks", "info", "lost-found", "modules",
> +               "objects", "refs", "remotes", "rr-cache", "svn",
> +               "config", "packed-refs", "shallow", NULL
> +       };
> +       struct strbuf sb = STRBUF_INIT;
> +       const char **p;
> +       int len;
> +
> +       if (!file_exists(git_path("commondir")))
> +               return;
> +       strbuf_addf(&sb, "%s/", get_git_dir());
> +       len = sb.len;
> +       for (p = list; *p; p++) {
> +               strbuf_setlen(&sb, len);
> +               strbuf_addstr(&sb, *p);
> +               if (file_exists(sb.buf))
> +                       report_garbage("unused in linked checkout", sb.buf);
> +       }
> +       strbuf_release(&sb);
> +}
> +
>  static char const * const count_objects_usage[] = {
>         N_("git count-objects [-v] [-H | --human-readable]"),
>         NULL
> @@ -102,8 +135,10 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
>         /* we do not take arguments other than flags for now */
>         if (argc)
>                 usage_with_options(count_objects_usage, opts);
> -       if (verbose)
> +       if (verbose) {
>                 report_garbage = real_report_garbage;
> +               report_linked_checkout_garbage();
> +       }
>         memcpy(path, objdir, len);
>         if (len && objdir[len-1] != '/')
>                 path[len++] = '/';
> diff --git a/path.c b/path.c
> index 47383ff..2e6035d 100644
> --- a/path.c
> +++ b/path.c
> @@ -92,6 +92,10 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)
>
>  static void update_common_dir(struct strbuf *buf, int git_dir_len)
>  {
> +       /*
> +        * Remember to report_linked_checkout_garbage()
> +        * builtin/count-objects.c
> +        */

I couldn't figure out why this comment was telling me to remember to
report "linked checkout garbage" until I realized that you omitted the
word "update" (as in "remember to update"). It might be clearer to say
something along these lines:

    Keep synchronized with related list in
    builtin/count-objects.c:report_linked_checkout_garbage().

Is it not possible or just too much of a hassle to maintain this list
in just one place, as in a header which is included by these two
files? The exceptions, such as 'log' and 'gc.pid', could be marked by
a special character in the entry ("!gc.pid", for example) or any such
scheme.

>         const char *common_dir_list[] = {
>                 "branches", "hooks", "info", "logs", "lost-found", "modules",
>                 "objects", "refs", "remotes", "repos", "rr-cache", "svn",
> --
> 1.9.0.40.gaa8c3ea

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 27/27] count-objects: report unused files in $GIT_DIR/repos/...
  2014-03-05  4:25     ` Eric Sunshine
@ 2014-03-05 12:08       ` Duy Nguyen
  0 siblings, 0 replies; 169+ messages in thread
From: Duy Nguyen @ 2014-03-05 12:08 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: Git List, Junio C Hamano

On Wed, Mar 5, 2014 at 11:25 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
>>  static void update_common_dir(struct strbuf *buf, int git_dir_len)
>>  {
>> +       /*
>> +        * Remember to report_linked_checkout_garbage()
>> +        * builtin/count-objects.c
>> +        */
>
> I couldn't figure out why this comment was telling me to remember to
> report "linked checkout garbage" until I realized that you omitted the
> word "update" (as in "remember to update"). It might be clearer to say
> something along these lines:
>
>     Keep synchronized with related list in
>     builtin/count-objects.c:report_linked_checkout_garbage().
>

I have a tendency to accidentally important words :)

> Is it not possible or just too much of a hassle to maintain this list
> in just one place, as in a header which is included by these two
> files? The exceptions, such as 'log' and 'gc.pid', could be marked by
> a special character in the entry ("!gc.pid", for example) or any such
> scheme.

It might work. Let me try.
-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 18/27] setup.c: support multi-checkout repo setup
  2014-03-01 12:12   ` [PATCH v4 18/27] setup.c: support multi-checkout repo setup Nguyễn Thái Ngọc Duy
@ 2014-03-05 19:42     ` Junio C Hamano
  2014-03-08  1:55       ` Duy Nguyen
  0 siblings, 1 reply; 169+ messages in thread
From: Junio C Hamano @ 2014-03-05 19:42 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

>  core.worktree::
>  	Set the path to the root of the working tree.
> +	If GIT_COMMON_DIR environment variable is set, core.worktree
> +	is ignored and not used for determining the root of working tree.

Just thinking aloud to see if I got the full implication of the
above right...

If we find ourselves in the multi-checkout mode because we saw
.git/commondir on the filesystem, it is clear that the root of the
working tree is the parent directory of that .git directory.

If the reason we think we are in the multi-checkout mode is not
because of .git/commondir but because $GIT_COMMON_DIR is set, should
we assume the same relationship between the root of the working tree
and the GIT_DIR (however we find it) when the environment variable
$GIT_WORK_TREE is not set?  Or should that configuration be an error?
With $GIT_DIR set without $GIT_WORK_TREE set, the user is telling us
that the $cwd is the root of the working tree, so perhaps we should
do the same?

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 24/27] prune: strategies for linked checkouts
  2014-03-01 12:13   ` [PATCH v4 24/27] prune: strategies for linked checkouts Nguyễn Thái Ngọc Duy
  2014-03-01 17:12     ` Torsten Bögershausen
@ 2014-03-05 20:07     ` Junio C Hamano
  1 sibling, 0 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-03-05 20:07 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> +	if (get_device_or_die(path) != get_device_or_die(get_git_dir())) {
> +		strbuf_reset(&sb);
> +		strbuf_addf(&sb, "%s/locked", sb_repo.buf);
> +		write_file(sb.buf, 1, "located on a different file system\n");
> +		keep_locked = 1;
> +	} else {
> +		strbuf_reset(&sb);
> +		strbuf_addf(&sb, "%s/link", sb_repo.buf);
> +		(void)link(sb_git.buf, sb.buf);
> +	}

Just in case you did not realize, casting the return away with
(void) will not squelch this out of the compiler:

    builtin/checkout.c: In function 'prepare_linked_checkout':
    builtin/checkout.c:947:3: error: ignoring return value of 'link', declared with attribute warn_unused_result [-Werror=unused-result]

It still feels fishy to see "we attempt to link but we do not care
if it works or not" to me, with or without the "unused result"
issue.

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 22/27] checkout: clean up half-prepared directories in --to mode
  2014-03-01 12:12   ` [PATCH v4 22/27] checkout: clean up half-prepared directories in --to mode Nguyễn Thái Ngọc Duy
@ 2014-03-06 10:06     ` Eric Sunshine
  0 siblings, 0 replies; 169+ messages in thread
From: Eric Sunshine @ 2014-03-06 10:06 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: Git List, Junio C Hamano

On Sat, Mar 1, 2014 at 7:12 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  builtin/checkout.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 47 insertions(+), 2 deletions(-)
>
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index fa7b54a..28f9ac1 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -20,6 +20,7 @@
>  #include "resolve-undo.h"
>  #include "submodule.h"
>  #include "argv-array.h"
> +#include "sigchain.h"
>
>  static const char * const checkout_usage[] = {
>         N_("git checkout [options] <branch>"),
> @@ -814,6 +815,35 @@ static int switch_branches(const struct checkout_opts *opts,
>         return ret || writeout_error;
>  }
>
> +static const char *junk_work_tree;
> +static const char *junk_git_dir;
> +static int is_junk;
> +static pid_t junk_pid;
> +
> +static void remove_junk(void)
> +{
> +       struct strbuf sb = STRBUF_INIT;
> +       if (!is_junk || getpid() != junk_pid)
> +               return;
> +       if (junk_git_dir) {
> +               strbuf_addstr(&sb, junk_git_dir);
> +               remove_dir_recursively(&sb, 0);
> +               strbuf_reset(&sb);
> +       }
> +       if (junk_work_tree) {
> +               strbuf_addstr(&sb, junk_work_tree);
> +               remove_dir_recursively(&sb, 0);
> +               strbuf_reset(&sb);
> +       }

strbuf_release(&sb);

> +}
> +
> +static void remove_junk_on_signal(int signo)
> +{
> +       remove_junk();
> +       sigchain_pop(signo);
> +       raise(signo);
> +}
> +
>  static int prepare_linked_checkout(const struct checkout_opts *opts,
>                                    struct branch_info *new)
>  {
> @@ -822,7 +852,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
>         const char *path = opts->new_worktree, *name;
>         struct stat st;
>         struct child_process cp;
> -       int counter = 0, len;
> +       int counter = 0, len, ret;
>
>         if (!new->commit)
>                 die(_("no branch specified"));
> @@ -848,13 +878,21 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
>                 strbuf_addf(&sb_repo, "%d", counter);
>         }
>         name = strrchr(sb_repo.buf, '/') + 1;
> +
> +       junk_pid = getpid();
> +       atexit(remove_junk);
> +       sigchain_push_common(remove_junk_on_signal);
> +
>         if (mkdir(sb_repo.buf, 0777))
>                 die_errno(_("could not create directory of '%s'"), sb_repo.buf);
> +       junk_git_dir = sb_repo.buf;
> +       is_junk = 1;
>
>         strbuf_addf(&sb_git, "%s/.git", path);
>         if (safe_create_leading_directories_const(sb_git.buf))
>                 die_errno(_("could not create leading directories of '%s'"),
>                           sb_git.buf);
> +       junk_work_tree = path;
>
>         write_file(sb_git.buf, 1, "gitdir: %s/repos/%s\n",
>                    real_path(get_git_common_dir()), name);
> @@ -879,7 +917,14 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
>         memset(&cp, 0, sizeof(cp));
>         cp.git_cmd = 1;
>         cp.argv = opts->saved_argv;
> -       return run_command(&cp);
> +       ret = run_command(&cp);
> +       if (!ret)
> +               is_junk = 0;
> +       strbuf_release(&sb);
> +       strbuf_release(&sb_repo);
> +       strbuf_release(&sb_git);
> +       return ret;
> +
>  }
>
>  static int git_checkout_config(const char *var, const char *value, void *cb)
> --
> 1.9.0.40.gaa8c3ea

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 02/27] Convert git_snpath() to strbuf_git_path()
  2014-03-03  0:15       ` Duy Nguyen
@ 2014-03-07  5:03         ` Duy Nguyen
  2014-03-07  5:26           ` Eric Sunshine
  0 siblings, 1 reply; 169+ messages in thread
From: Duy Nguyen @ 2014-03-07  5:03 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: Git List, Junio C Hamano

On Mon, Mar 3, 2014 at 7:15 AM, Duy Nguyen <pclouds@gmail.com> wrote:
> On Mon, Mar 3, 2014 at 7:02 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
>> On Sat, Mar 1, 2014 at 7:12 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
>>> In the previous patch, git_snpath() is modified to allocate a new
>>> strbuf buffer because vsnpath() needs that. But that makes it awkward
>>> because git_snpath() receives a pre-allocated buffer from outside and
>>> has to copy data back. Rename it to strbuf_git_path() and make it
>>> receive strbuf directly.
>>>
>>> The conversion from git_snpath() to git_path() in
>>> update_refs_for_switch() is safe because that function does not keep
>>> any pointer to the round-robin buffer pool allocated by
>>> get_pathname().
>>>
>>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
>>> ---
>>> diff --git a/refs.c b/refs.c
>>> index 89228e2..434bd5e 100644
>>> --- a/refs.c
>>> +++ b/refs.c
>>> @@ -2717,17 +2729,19 @@ static int copy_msg(char *buf, const char *msg)
>>>         return cp - buf;
>>>  }
>>>
>>> -int log_ref_setup(const char *refname, char *logfile, int bufsize)
>>> +int log_ref_setup(const char *refname, struct strbuf *sb_logfile)
>>>  {
>>>         int logfd, oflags = O_APPEND | O_WRONLY;
>>> +       const char *logfile;
>>>
>>> -       git_snpath(logfile, bufsize, "logs/%s", refname);
>>> +       strbuf_git_path(sb_logfile, "logs/%s", refname);
>>> +       logfile = sb_logfile->buf;
>>>         if (log_all_ref_updates &&
>>>             (starts_with(refname, "refs/heads/") ||
>>>              starts_with(refname, "refs/remotes/") ||
>>>              starts_with(refname, "refs/notes/") ||
>>>              !strcmp(refname, "HEAD"))) {
>>> -               if (safe_create_leading_directories(logfile) < 0)
>>> +               if (safe_create_leading_directories(sb_logfile->buf) < 0)
>>
>> At this point, 'logfile' is still 'sb_logfile->buf', so do you really
>> need this change?
>
> Junio made the same comment last time and I missed it. Will update.

No I will not :-) safe_create_leading_directories takes an editable
string, but logfile is now a const string. We could use
s_c_l_d_const() but that one will make a copy of the string
unncessarily. Will make a note in the commit message though.
-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 02/27] Convert git_snpath() to strbuf_git_path()
  2014-03-07  5:03         ` Duy Nguyen
@ 2014-03-07  5:26           ` Eric Sunshine
  0 siblings, 0 replies; 169+ messages in thread
From: Eric Sunshine @ 2014-03-07  5:26 UTC (permalink / raw)
  To: Duy Nguyen; +Cc: Git List, Junio C Hamano

On Fri, Mar 7, 2014 at 12:03 AM, Duy Nguyen <pclouds@gmail.com> wrote:
> On Mon, Mar 3, 2014 at 7:15 AM, Duy Nguyen <pclouds@gmail.com> wrote:
>> On Mon, Mar 3, 2014 at 7:02 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
>>> On Sat, Mar 1, 2014 at 7:12 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
>>>> In the previous patch, git_snpath() is modified to allocate a new
>>>> strbuf buffer because vsnpath() needs that. But that makes it awkward
>>>> because git_snpath() receives a pre-allocated buffer from outside and
>>>> has to copy data back. Rename it to strbuf_git_path() and make it
>>>> receive strbuf directly.
>>>>
>>>> The conversion from git_snpath() to git_path() in
>>>> update_refs_for_switch() is safe because that function does not keep
>>>> any pointer to the round-robin buffer pool allocated by
>>>> get_pathname().
>>>>
>>>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
>>>> ---
>>>> diff --git a/refs.c b/refs.c
>>>> index 89228e2..434bd5e 100644
>>>> --- a/refs.c
>>>> +++ b/refs.c
>>>> @@ -2717,17 +2729,19 @@ static int copy_msg(char *buf, const char *msg)
>>>>         return cp - buf;
>>>>  }
>>>>
>>>> -int log_ref_setup(const char *refname, char *logfile, int bufsize)
>>>> +int log_ref_setup(const char *refname, struct strbuf *sb_logfile)
>>>>  {
>>>>         int logfd, oflags = O_APPEND | O_WRONLY;
>>>> +       const char *logfile;
>>>>
>>>> -       git_snpath(logfile, bufsize, "logs/%s", refname);
>>>> +       strbuf_git_path(sb_logfile, "logs/%s", refname);
>>>> +       logfile = sb_logfile->buf;
>>>>         if (log_all_ref_updates &&
>>>>             (starts_with(refname, "refs/heads/") ||
>>>>              starts_with(refname, "refs/remotes/") ||
>>>>              starts_with(refname, "refs/notes/") ||
>>>>              !strcmp(refname, "HEAD"))) {
>>>> -               if (safe_create_leading_directories(logfile) < 0)
>>>> +               if (safe_create_leading_directories(sb_logfile->buf) < 0)
>>>
>>> At this point, 'logfile' is still 'sb_logfile->buf', so do you really
>>> need this change?
>>
>> Junio made the same comment last time and I missed it. Will update.
>
> No I will not :-) safe_create_leading_directories takes an editable
> string, but logfile is now a const string. We could use
> s_c_l_d_const() but that one will make a copy of the string
> unncessarily. Will make a note in the commit message though.

Rather than explaining it in the commit message, it might be better
eliminate the source of confusion by taking one of these approaches:

1. Drop the 'const char *logfile' variable altogether; rename the
strbuf argument to 'logfile'; and just use logfile->buf everywhere
'logfile' is used in the current code. This makes the diff a bit more
noisy, but eliminates confusion of reviewers reading the patch.

2. Keep the 'const char *logfile' but assign it just before its first
(real) use in 'logfd = open(logfile...)'. (There's one earlier use in
a diagnostic, but sb_logfile->buf could suffice there.)

3. Just declare it 'char *logfile' and use it everywhere in the
function. This is a bit ugly since it's not obvious to the reviewer
that it is non-const only for the sake of
safe_create_leading_directories().

I fiind myself favoring #1 in this particular case.

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 26/27] gc: support prune --repos
  2014-03-01 12:13   ` [PATCH v4 26/27] gc: support prune --repos Nguyễn Thái Ngọc Duy
@ 2014-03-07  6:40     ` Eric Sunshine
  0 siblings, 0 replies; 169+ messages in thread
From: Eric Sunshine @ 2014-03-07  6:40 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: Git List, Junio C Hamano

On Sat, Mar 1, 2014 at 7:13 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  Documentation/config.txt |  6 ++++++
>  builtin/gc.c             | 17 +++++++++++++++++
>  2 files changed, 23 insertions(+)
>
> diff --git a/Documentation/config.txt b/Documentation/config.txt
> index 313d4b3..438b213 100644
> --- a/Documentation/config.txt
> +++ b/Documentation/config.txt
> @@ -1183,6 +1183,12 @@ gc.pruneexpire::
>         "now" may be used to disable this  grace period and always prune
>         unreachable objects immediately.
>
> +gc.prunereposexpire::
> +       When 'git gc' is run, it will call 'prune --repos --expire 3.months.ago'.
> +       Override the grace period with this config variable.  The value
> +       "now" may be used to disable this  grace period and always prune

Extra space between "this" and "grace". However, "the grace period"
sounds a bit better.

> +       $GIT_DIR/repos immediately.
> +
>  gc.reflogexpire::
>  gc.<pattern>.reflogexpire::
>         'git reflog expire' removes reflog entries older than
> diff --git a/builtin/gc.c b/builtin/gc.c
> index 39d9b27..85c3c0c 100644
> --- a/builtin/gc.c
> +++ b/builtin/gc.c
> @@ -30,11 +30,13 @@ static int aggressive_window = 250;
>  static int gc_auto_threshold = 6700;
>  static int gc_auto_pack_limit = 50;
>  static const char *prune_expire = "2.weeks.ago";
> +static const char *prune_repos_expire = "3.months.ago";
>
>  static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT;
>  static struct argv_array reflog = ARGV_ARRAY_INIT;
>  static struct argv_array repack = ARGV_ARRAY_INIT;
>  static struct argv_array prune = ARGV_ARRAY_INIT;
> +static struct argv_array prune_repos = ARGV_ARRAY_INIT;
>  static struct argv_array rerere = ARGV_ARRAY_INIT;
>
>  static char *pidfile;
> @@ -81,6 +83,14 @@ static int gc_config(const char *var, const char *value, void *cb)
>                 }
>                 return git_config_string(&prune_expire, var, value);
>         }
> +       if (!strcmp(var, "gc.prunereposexpire")) {
> +               if (value && strcmp(value, "now")) {
> +                       unsigned long now = approxidate("now");
> +                       if (approxidate(value) >= now)
> +                               return error(_("Invalid %s: '%s'"), var, value);
> +               }
> +               return git_config_string(&prune_repos_expire, var, value);
> +       }
>         return git_default_config(var, value, cb);
>  }
>
> @@ -274,6 +284,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
>         argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
>         argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
>         argv_array_pushl(&prune, "prune", "--expire", NULL);
> +       argv_array_pushl(&prune_repos, "prune", "--repos", "--expire", NULL);
>         argv_array_pushl(&rerere, "rerere", "gc", NULL);
>
>         git_config(gc_config, NULL);
> @@ -334,6 +345,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
>                         return error(FAILED_RUN, prune.argv[0]);
>         }
>
> +       if (prune_repos_expire) {
> +               argv_array_push(&prune_repos, prune_repos_expire);
> +               if (run_command_v_opt(prune_repos.argv, RUN_GIT_CMD))
> +                       return error(FAILED_RUN, prune_repos.argv[0]);
> +       }
> +
>         if (run_command_v_opt(rerere.argv, RUN_GIT_CMD))
>                 return error(FAILED_RUN, rerere.argv[0]);
>
> --
> 1.9.0.40.gaa8c3ea
>
> --
> To unsubscribe from this list: send the line "unsubscribe git" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v4 18/27] setup.c: support multi-checkout repo setup
  2014-03-05 19:42     ` Junio C Hamano
@ 2014-03-08  1:55       ` Duy Nguyen
  0 siblings, 0 replies; 169+ messages in thread
From: Duy Nguyen @ 2014-03-08  1:55 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git Mailing List

On Thu, Mar 6, 2014 at 2:42 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
>
>>  core.worktree::
>>       Set the path to the root of the working tree.
>> +     If GIT_COMMON_DIR environment variable is set, core.worktree
>> +     is ignored and not used for determining the root of working tree.
>
> Just thinking aloud to see if I got the full implication of the
> above right...
>
> If we find ourselves in the multi-checkout mode because we saw
> .git/commondir on the filesystem, it is clear that the root of the
> working tree is the parent directory of that .git directory.
>
> If the reason we think we are in the multi-checkout mode is not
> because of .git/commondir but because $GIT_COMMON_DIR is set,

I tend to think so, .git/commondir is just a convenient way to set
$GIT_COMMON_DIR. $GIT_COMMON_DIR is the key. config.txt correctly
states so, but the commit message is misleading.

>  should we assume the same relationship between the root of the working tree
> and the GIT_DIR (however we find it) when the environment variable
> $GIT_WORK_TREE is not set?  Or should that configuration be an error?
> With $GIT_DIR set without $GIT_WORK_TREE set, the user is telling us
> that the $cwd is the root of the working tree, so perhaps we should
> do the same?

It should work exactly like how normal worktree does, so if $GIT_DIR
is set and $GIT_WORK_TREE is not, $PWD is the worktree root.
-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* [PATCH v5 00/28] Support multiple checkouts
  2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
                     ` (26 preceding siblings ...)
  2014-03-01 12:13   ` [PATCH v4 27/27] count-objects: report unused files in $GIT_DIR/repos/ Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:47   ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:47     ` [PATCH v5 01/28] path.c: make get_pathname() return strbuf instead of static buffer Nguyễn Thái Ngọc Duy
                       ` (29 more replies)
  27 siblings, 30 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:47 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

The diff against v4 is kinda big but it's mostly about converting
`...` to $(...) and making git_path() and friends return a const
string.

Another notable change is I no longer attempt to support checkouts on
portable devices. Torsten pointed out (privately) that my dealing with
Windows drives was insufficient. And Junio was not so happy with how
link() was handled either. We can revisit it later.

Many thanks to Eric, who was very patient to go through the series
carefully and pointed out problems that I overlooked.

v4..v5 diff below for convenience
-- 8< --
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 438b213..c635b8d 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1184,9 +1184,10 @@ gc.pruneexpire::
 	unreachable objects immediately.
 
 gc.prunereposexpire::
-	When 'git gc' is run, it will call 'prune --repos --expire 3.months.ago'.
-	Override the grace period with this config variable.  The value
-	"now" may be used to disable this  grace period and always prune
+	When 'git gc' is run, it will call
+	'prune --repos --expire 3.months.ago'.
+	Override the grace period with this config variable. The value
+	"now" may be used to disable the grace period and always prune
 	$GIT_DIR/repos immediately.
 
 gc.reflogexpire::
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 8e6ad32..8a9cac8 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -238,7 +238,9 @@ print a message to stderr and exit with nonzero status.
 --git-path <path>::
 	Resolve "$GIT_DIR/<path>" and takes other path relocation
 	variables such as $GIT_OBJECT_DIRECTORY,
-	$GIT_INDEX_FILE... into account.
+	$GIT_INDEX_FILE... into account. For example, if
+	$GIT_OBJECT_DIRECTORY is set to /foo/bar then "git rev-parse
+	--git-path objects/abc" returns /tmp/bar/abc.
 
 --show-cdup::
 	When the command is invoked from a subdirectory, show the
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index 2dc6901..dff6c47 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -47,7 +47,7 @@ use with dumb transports but otherwise is OK as long as
 `objects/info/alternates` points at the object stores it
 borrows from.
 +
-This directory is ignored $GIT_COMMON_DIR is set and
+This directory is ignored if $GIT_COMMON_DIR is set and
 "$GIT_COMMON_DIR/objects" will be used instead.
 
 objects/[0-9a-f][0-9a-f]::
@@ -95,7 +95,7 @@ refs::
 	References are stored in subdirectories of this
 	directory.  The 'git prune' command knows to preserve
 	objects reachable from refs found in this directory and
-	its subdirectories. This directory is ignored $GIT_COMMON_DIR
+	its subdirectories. This directory is ignored if $GIT_COMMON_DIR
 	is set and "$GIT_COMMON_DIR/refs" will be used instead.
 
 refs/heads/`name`::
@@ -118,7 +118,7 @@ refs/replace/`<obj-sha1>`::
 packed-refs::
 	records the same information as refs/heads/, refs/tags/,
 	and friends record in a more efficient way.  See
-	linkgit:git-pack-refs[1]. This file is ignored $GIT_COMMON_DIR
+	linkgit:git-pack-refs[1]. This file is ignored if $GIT_COMMON_DIR
 	is set and "$GIT_COMMON_DIR/packed-refs" will be used instead.
 
 HEAD::
@@ -151,7 +151,7 @@ branches::
 	'repository' argument.  See the REMOTES section in
 	linkgit:git-fetch[1] for details.  This mechanism is legacy
 	and not likely to be found in modern repositories. This
-	directory is ignored $GIT_COMMON_DIR is set and
+	directory is ignored if $GIT_COMMON_DIR is set and
 	"$GIT_COMMON_DIR/branches" will be used instead.
 
 
@@ -162,7 +162,7 @@ hooks::
 	default.  To enable, the `.sample` suffix has to be
 	removed from the filename by renaming.
 	Read linkgit:githooks[5] for more details about
-	each hook. This directory is ignored $GIT_COMMON_DIR is set
+	each hook. This directory is ignored if $GIT_COMMON_DIR is set
 	and "$GIT_COMMON_DIR/hooks" will be used instead.
 
 
@@ -172,7 +172,7 @@ index::
 
 info::
 	Additional information about the repository is recorded
-	in this directory. This directory is ignored $GIT_COMMON_DIR
+	in this directory. This directory is ignored if $GIT_COMMON_DIR
 	is set and "$GIT_COMMON_DIR/index" will be used instead.
 
 info/refs::
@@ -210,7 +210,7 @@ remotes::
 	'git pull' and 'git push' commands.  See the REMOTES section
 	in linkgit:git-fetch[1] for details.  This mechanism is legacy
 	and not likely to be found in modern repositories. This
-	directory is ignored $GIT_COMMON_DIR is set and
+	directory is ignored if $GIT_COMMON_DIR is set and
 	"$GIT_COMMON_DIR/remotes" will be used instead.
 
 logs::
@@ -230,7 +230,7 @@ shallow::
 	This is similar to `info/grafts` but is internally used
 	and maintained by shallow clone mechanism.  See `--depth`
 	option to linkgit:git-clone[1] and linkgit:git-fetch[1]. This
-	file is ignored $GIT_COMMON_DIR is set and
+	file is ignored if $GIT_COMMON_DIR is set and
 	"$GIT_COMMON_DIR/shallow" will be used instead.
 
 commondir::
@@ -242,7 +242,7 @@ commondir::
 
 modules::
 	Contains the git-repositories of the submodules. This
-	directory is ignored $GIT_COMMON_DIR is set and
+	directory is ignored if $GIT_COMMON_DIR is set and
 	"$GIT_COMMON_DIR/modules" will be used instead.
 
 repos::
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 24b7f36..9dc80f1 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -599,9 +599,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 			if (opts->new_branch_log && !log_all_ref_updates) {
 				int temp;
 				struct strbuf log_file = STRBUF_INIT;
-				char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
 				int ret;
+				const char *ref_name;
 
+				ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
 				temp = log_all_ref_updates;
 				log_all_ref_updates = 1;
 				ret = log_ref_setup(ref_name, &log_file);
@@ -843,8 +844,8 @@ static void remove_junk(void)
 	if (junk_work_tree) {
 		strbuf_addstr(&sb, junk_work_tree);
 		remove_dir_recursively(&sb, 0);
-		strbuf_reset(&sb);
 	}
+	strbuf_release(&sb);
 }
 
 static void remove_junk_on_signal(int signo)
@@ -854,17 +855,6 @@ static void remove_junk_on_signal(int signo)
 	raise(signo);
 }
 
-static dev_t get_device_or_die(const char *path)
-{
-	struct stat buf;
-	if (stat(path, &buf))
-		die_errno("failed to stat '%s'", path);
-	/* Ah Windows! Make different drives different "partitions" */
-	if (is_windows())
-		buf.st_dev = toupper(real_path(path)[0]);
-	return buf.st_dev;
-}
-
 static int prepare_linked_checkout(const struct checkout_opts *opts,
 				   struct branch_info *new)
 {
@@ -873,7 +863,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	const char *path = opts->new_worktree, *name;
 	struct stat st;
 	struct child_process cp;
-	int counter = 0, len, keep_locked = 0, ret;
+	int counter = 0, len, ret;
 
 	if (!new->commit)
 		die(_("no branch specified"));
@@ -909,6 +899,10 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	junk_git_dir = sb_repo.buf;
 	is_junk = 1;
 
+	/*
+	 * lock the incomplete repo so prunt won't delete it, unlock
+	 * after the preparation is over.
+	 */
 	strbuf_addf(&sb, "%s/locked", sb_repo.buf);
 	write_file(sb.buf, 1, "initializing\n");
 
@@ -936,18 +930,6 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
 	write_file(sb.buf, 1, "../..\n");
 
-	if (get_device_or_die(path) != get_device_or_die(get_git_dir())) {
-		strbuf_reset(&sb);
-		strbuf_addf(&sb, "%s/locked", sb_repo.buf);
-		write_file(sb.buf, 1, "located on a different file system\n");
-		keep_locked = 1;
-	} else {
-		strbuf_reset(&sb);
-		strbuf_addf(&sb, "%s/link", sb_repo.buf);
-		if (link(sb_git.buf, sb.buf))
-			; /* ignore silently, without a clear reason */
-	}
-
 	if (!opts->quiet)
 		fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
 
@@ -960,11 +942,9 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	ret = run_command(&cp);
 	if (!ret)
 		is_junk = 0;
-	if (!keep_locked) {
-		strbuf_reset(&sb);
-		strbuf_addf(&sb, "%s/locked", sb_repo.buf);
-		unlink_or_warn(sb.buf);
-	}
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+	unlink_or_warn(sb.buf);
 	strbuf_release(&sb);
 	strbuf_release(&sb_repo);
 	strbuf_release(&sb_git);
diff --git a/builtin/clone.c b/builtin/clone.c
index 43e772c..9339371 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -289,16 +289,17 @@ static void copy_alternates(struct strbuf *src, struct strbuf *dst,
 	struct strbuf line = STRBUF_INIT;
 
 	while (strbuf_getline(&line, in, '\n') != EOF) {
-		char *abs_path, abs_buf[PATH_MAX];
+		char *abs_path;
 		if (!line.len || line.buf[0] == '#')
 			continue;
 		if (is_absolute_path(line.buf)) {
 			add_to_alternates_file(line.buf);
 			continue;
 		}
-		abs_path = mkpath("%s/objects/%s", src_repo, line.buf);
-		normalize_path_copy(abs_buf, abs_path);
-		add_to_alternates_file(abs_buf);
+		abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
+		normalize_path_copy(abs_path, abs_path);
+		add_to_alternates_file(abs_path);
+		free(abs_path);
 	}
 	strbuf_release(&line);
 	fclose(in);
diff --git a/builtin/count-objects.c b/builtin/count-objects.c
index 725cd5f..d3a1620 100644
--- a/builtin/count-objects.c
+++ b/builtin/count-objects.c
@@ -78,39 +78,6 @@ static void count_objects(DIR *d, char *path, int len, int verbose,
 	}
 }
 
-static void report_linked_checkout_garbage(void)
-{
-	/*
-	 * must be more or less in sync with * path.c:update_common_dir().
-	 *
-	 * "logs" is let slip because logs/HEAD is in $GIT_DIR but the
-	 * remaining in $GIT_COMMON_DIR. Probably not worth traversing
-	 * the entire "logs" directory for that.
-	 *
-	 * The same "gc.pid" for because it's a temporary file.
-	 */
-	const char *list[] = {
-		"branches", "hooks", "info", "lost-found", "modules",
-		"objects", "refs", "remotes", "rr-cache", "svn",
-		"config", "packed-refs", "shallow", NULL
-	};
-	struct strbuf sb = STRBUF_INIT;
-	const char **p;
-	int len;
-
-	if (!file_exists(git_path("commondir")))
-		return;
-	strbuf_addf(&sb, "%s/", get_git_dir());
-	len = sb.len;
-	for (p = list; *p; p++) {
-		strbuf_setlen(&sb, len);
-		strbuf_addstr(&sb, *p);
-		if (file_exists(sb.buf))
-			report_garbage("unused in linked checkout", sb.buf);
-	}
-	strbuf_release(&sb);
-}
-
 static char const * const count_objects_usage[] = {
 	N_("git count-objects [-v] [-H | --human-readable]"),
 	NULL
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 025bc3e..04b51ca 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -544,7 +544,8 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
 	struct strbuf note = STRBUF_INIT;
 	const char *what, *kind;
 	struct ref *rm;
-	char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
+	char *url;
+	const char *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
 	int want_status;
 
 	fp = fopen(filename, "a");
@@ -778,7 +779,7 @@ static void check_not_current_branch(struct ref *ref_map)
 
 static int truncate_fetch_head(void)
 {
-	char *filename = git_path("FETCH_HEAD");
+	const char *filename = git_path("FETCH_HEAD");
 	FILE *fp = fopen(filename, "w");
 
 	if (!fp)
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 1affdd5..643c980 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -225,12 +225,12 @@ static void check_unreachable_object(struct object *obj)
 			printf("dangling %s %s\n", typename(obj->type),
 			       sha1_to_hex(obj->sha1));
 		if (write_lost_and_found) {
-			char *filename = git_path("lost-found/%s/%s",
+			const char *filename = git_path("lost-found/%s/%s",
 				obj->type == OBJ_COMMIT ? "commit" : "other",
 				sha1_to_hex(obj->sha1));
 			FILE *f;
 
-			if (safe_create_leading_directories(filename)) {
+			if (safe_create_leading_directories_const(filename)) {
 				error("Could not create lost-found");
 				return;
 			}
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 85bba35..4d27acb 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -599,7 +599,7 @@ static void run_update_post_hook(struct command *commands)
 	int argc;
 	const char **argv;
 	struct child_process proc;
-	char *hook;
+	const char *hook;
 
 	hook = find_hook("post-update");
 	for (argc = 0, cmd = commands; cmd; cmd = cmd->next) {
diff --git a/builtin/remote.c b/builtin/remote.c
index b3ab4cf..ca3cb3b 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -582,7 +582,7 @@ static int migrate_file(struct remote *remote)
 {
 	struct strbuf buf = STRBUF_INIT;
 	int i;
-	char *path = NULL;
+	const char *path = NULL;
 
 	strbuf_addf(&buf, "remote.%s.url", remote->name);
 	for (i = 0; i < remote->url_nr; i++)
diff --git a/builtin/repack.c b/builtin/repack.c
index bb2314c..4d35349 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -257,7 +257,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
 	failed = 0;
 	for_each_string_list_item(item, &names) {
 		for (ext = 0; ext < 2; ext++) {
-			char *fname, *fname_old;
+			const char *fname_old;
+			char *fname;
 			fname = mkpathdup("%s/pack-%s%s", packdir,
 						item->string, exts[ext]);
 			if (!file_exists(fname)) {
@@ -285,7 +286,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
 	if (failed) {
 		struct string_list rollback_failure = STRING_LIST_INIT_DUP;
 		for_each_string_list_item(item, &rollback) {
-			char *fname, *fname_old;
+			const char *fname_old;
+			char *fname;
 			fname = mkpathdup("%s/%s", packdir, item->string);
 			fname_old = mkpath("%s/old-%s", packdir, item->string);
 			if (rename(fname_old, fname))
@@ -334,7 +336,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
 	/* Remove the "old-" files */
 	for_each_string_list_item(item, &names) {
 		for (ext = 0; ext < 2; ext++) {
-			char *fname;
+			const char *fname;
 			fname = mkpath("%s/old-%s%s",
 					packdir,
 					item->string,
diff --git a/cache.h b/cache.h
index 99b86d9..a0ff430 100644
--- a/cache.h
+++ b/cache.h
@@ -658,10 +658,11 @@ extern char *mkpathdup(const char *fmt, ...)
 	__attribute__((format (printf, 1, 2)));
 
 /* Return a statically allocated filename matching the sha1 signature */
-extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
-extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
-extern char *git_path_submodule(const char *path, const char *fmt, ...)
+extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern const char *git_path_submodule(const char *path, const char *fmt, ...)
 	__attribute__((format (printf, 2, 3)));
+extern void report_linked_checkout_garbage(void);
 
 extern char *sha1_file_name(const unsigned char *sha1);
 extern char *sha1_pack_name(const unsigned char *sha1);
diff --git a/compat/mingw.h b/compat/mingw.h
index 18323c1..e033e72 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -328,7 +328,6 @@ int winansi_fprintf(FILE *stream, const char *format, ...) __attribute__((format
  * git specific compatibility
  */
 
-#define is_windows() 1
 #define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':')
 #define is_dir_sep(c) ((c) == '/' || (c) == '\\')
 static inline char *mingw_find_last_dir_sep(const char *path)
diff --git a/fast-import.c b/fast-import.c
index 08a1e78..28e7a63 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -403,7 +403,7 @@ static void dump_marks_helper(FILE *, uintmax_t, struct mark_set *);
 
 static void write_crash_report(const char *err)
 {
-	char *loc = git_path("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid());
+	const char *loc = git_path("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid());
 	FILE *rpt = fopen(loc, "w");
 	struct branch *b;
 	unsigned long lu;
diff --git a/git-am.sh b/git-am.sh
index dfa0618..10babd2 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -803,7 +803,7 @@ To restore the original branch and stop patching run \"\$cmdline --abort\"."
 		continue
 	fi
 
-	hook="`git rev-parse --git-path hooks/applypatch-msg`"
+	hook="$(git rev-parse --git-path hooks/applypatch-msg)"
 	if test -x "$hook"
 	then
 		"$hook" "$dotest/final-commit" || stop_here $this
@@ -880,7 +880,7 @@ did you forget to use 'git add'?"
 		stop_here_user_resolve $this
 	fi
 
-	hook="`git rev-parse --git-path hooks/pre-applypatch`"
+	hook="$(git rev-parse --git-path hooks/pre-applypatch)"
 	if test -x "$hook"
 	then
 		"$hook" || stop_here $this
@@ -909,7 +909,7 @@ did you forget to use 'git add'?"
 		echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten"
 	fi
 
-	hook="`git rev-parse --git-path hooks/post-applypatch`"
+	hook="$(git rev-parse --git-path hooks/post-applypatch)"
 	test -x "$hook" && "$hook"
 
 	go_next
@@ -917,7 +917,7 @@ done
 
 if test -s "$dotest"/rewritten; then
     git notes copy --for-rewrite=rebase < "$dotest"/rewritten
-    hook="`git rev-parse --git-path hooks/post-rewrite`"
+    hook="$(git rev-parse --git-path hooks/post-rewrite)"
     if test -x "$hook"; then
 	"$hook" rebase < "$dotest"/rewritten
     fi
diff --git a/git-compat-util.h b/git-compat-util.h
index 41f1b74..cbd86c3 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -266,10 +266,6 @@ extern char *gitbasename(char *);
 #define STRIP_EXTENSION ""
 #endif
 
-#ifndef is_windows
-#define is_windows() 0
-#endif
-
 #ifndef has_dos_drive_prefix
 #define has_dos_drive_prefix(path) 0
 #endif
diff --git a/git-pull.sh b/git-pull.sh
index c9dc9ba..2a90262 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -218,7 +218,7 @@ test true = "$rebase" && {
 	if ! git rev-parse -q --verify HEAD >/dev/null
 	then
 		# On an unborn branch
-		if test -f "`git rev-parse --git-path index`"
+		if test -f "$(git rev-parse --git-path index)"
 		then
 			die "$(gettext "updating an unborn branch with changes added to the index")"
 		fi
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index d741b04..dd84f9d 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -632,7 +632,7 @@ do_next () {
 		git notes copy --for-rewrite=rebase < "$rewritten_list" ||
 		true # we don't care if this copying failed
 	} &&
-	hook="`git rev-parse --git-path hooks/post-rewrite`"
+	hook="$(git rev-parse --git-path hooks/post-rewrite)"
 	if test -x "$hook" && test -s "$rewritten_list"; then
 		"$hook" rebase < "$rewritten_list"
 		true # we don't care if this hook failed
diff --git a/git-rebase--merge.sh b/git-rebase--merge.sh
index 68f5d09..93d7bcf 100644
--- a/git-rebase--merge.sh
+++ b/git-rebase--merge.sh
@@ -93,7 +93,7 @@ finish_rb_merge () {
 	if test -s "$state_dir"/rewritten
 	then
 		git notes copy --for-rewrite=rebase <"$state_dir"/rewritten
-		hook="`git rev-parse --git-path hooks/post-rewrite`"
+		hook="$(git rev-parse --git-path hooks/post-rewrite)"
 		test -x "$hook" && "$hook" rebase <"$state_dir"/rewritten
 	fi
 	say All done.
diff --git a/git-rebase.sh b/git-rebase.sh
index 1cf8dba..b90a6d9 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -195,9 +195,9 @@ run_specific_rebase () {
 
 run_pre_rebase_hook () {
 	if test -z "$ok_to_skip_pre_rebase" &&
-	   test -x "`git rev-parse --git-path hooks/pre-rebase`"
+	   test -x "$(git rev-parse --git-path hooks/pre-rebase)"
 	then
-		"`git rev-parse --git-path hooks/pre-rebase`" ${1+"$@"} ||
+		"$(git rev-parse --git-path hooks/pre-rebase)" ${1+"$@"} ||
 		die "$(gettext "The pre-rebase hook refused to rebase.")"
 	fi
 }
diff --git a/git-stash.sh b/git-stash.sh
index 12d9b37..8093326 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -20,7 +20,7 @@ require_work_tree
 cd_to_toplevel
 
 TMP="$GIT_DIR/.git-stash.$$"
-TMPindex=${GIT_INDEX_FILE-"`git rev-parse --git-path index`"}.stash.$$
+TMPindex=${GIT_INDEX_FILE-"$(git rev-parse --git-path index)"}.stash.$$
 trap 'rm -f "$TMP-"* "$TMPindex"' 0
 
 ref_stash=refs/stash
@@ -183,7 +183,7 @@ store_stash () {
 	fi
 
 	# Make sure the reflog for stash is kept.
-	: >>"`git rev-parse --git-path logs/$ref_stash`"
+	: >>"$(git rev-parse --git-path logs/$ref_stash)"
 	git update-ref -m "$stash_msg" $ref_stash $w_commit
 	ret=$?
 	test $ret != 0 && test -z $quiet &&
@@ -258,7 +258,7 @@ save_stash () {
 		say "$(gettext "No local changes to save")"
 		exit 0
 	fi
-	test -f "`git rev-parse --git-path logs/$ref_stash`" ||
+	test -f "$(git rev-parse --git-path logs/$ref_stash)" ||
 		clear_stash || die "$(gettext "Cannot initialize stash")"
 
 	create_stash "$stash_msg" $untracked
diff --git a/notes-merge.c b/notes-merge.c
index 94a1a8a..d59bcc2 100644
--- a/notes-merge.c
+++ b/notes-merge.c
@@ -280,7 +280,7 @@ static void check_notes_merge_worktree(struct notes_merge_options *o)
 				    "(%s exists).", git_path("NOTES_MERGE_*"));
 		}
 
-		if (safe_create_leading_directories(git_path(
+		if (safe_create_leading_directories_const(git_path(
 				NOTES_MERGE_WORKTREE "/.test")))
 			die_errno("unable to create directory %s",
 				  git_path(NOTES_MERGE_WORKTREE));
@@ -295,8 +295,8 @@ static void write_buf_to_worktree(const unsigned char *obj,
 				  const char *buf, unsigned long size)
 {
 	int fd;
-	char *path = git_path(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj));
-	if (safe_create_leading_directories(path))
+	const char *path = git_path(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj));
+	if (safe_create_leading_directories_const(path))
 		die_errno("unable to create directory for '%s'", path);
 	if (file_exists(path))
 		die("found existing file at '%s'", path);
diff --git a/path.c b/path.c
index 2e6035d..5a7dc45 100644
--- a/path.c
+++ b/path.c
@@ -4,6 +4,7 @@
 #include "cache.h"
 #include "strbuf.h"
 #include "string-list.h"
+#include "dir.h"
 
 static int get_st_mode_bits(const char *path, int *mode)
 {
@@ -90,35 +91,60 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)
 		buf->buf[newlen] = '/';
 }
 
+static const char *common_list[] = {
+	"/branches", "/hooks", "/info", "!/logs", "/lost-found", "/modules",
+	"/objects", "/refs", "/remotes", "/repos", "/rr-cache", "/svn",
+	"config", "!gc.pid", "packed-refs", "shallow",
+	NULL
+};
+
 static void update_common_dir(struct strbuf *buf, int git_dir_len)
 {
-	/*
-	 * Remember to report_linked_checkout_garbage()
-	 * builtin/count-objects.c
-	 */
-	const char *common_dir_list[] = {
-		"branches", "hooks", "info", "logs", "lost-found", "modules",
-		"objects", "refs", "remotes", "repos", "rr-cache", "svn",
-		NULL
-	};
-	const char *common_top_file_list[] = {
-		"config", "gc.pid", "packed-refs", "shallow", NULL
-	};
 	char *base = buf->buf + git_dir_len;
 	const char **p;
 
 	if (is_dir_file(base, "logs", "HEAD"))
 		return;	/* keep this in $GIT_DIR */
-	for (p = common_dir_list; *p; p++)
-		if (dir_prefix(base, *p)) {
+	for (p = common_list; *p; p++) {
+		const char *path = *p;
+		int is_dir = 0;
+		if (*path == '!')
+			path++;
+		if (*path == '/') {
+			path++;
+			is_dir = 1;
+		}
+		if (is_dir && dir_prefix(base, path)) {
 			replace_dir(buf, git_dir_len, get_git_common_dir());
 			return;
 		}
-	for (p = common_top_file_list; *p; p++)
-		if (!strcmp(base, *p)) {
+		if (!is_dir && !strcmp(base, path)) {
 			replace_dir(buf, git_dir_len, get_git_common_dir());
 			return;
 		}
+	}
+}
+
+void report_linked_checkout_garbage(void)
+{
+	struct strbuf sb = STRBUF_INIT;
+	const char **p;
+	int len;
+
+	if (!git_common_dir_env)
+		return;
+	strbuf_addf(&sb, "%s/", get_git_dir());
+	len = sb.len;
+	for (p = common_list; *p; p++) {
+		const char *path = *p;
+		if (*path == '!')
+			continue;
+		strbuf_setlen(&sb, len);
+		strbuf_addstr(&sb, path);
+		if (file_exists(sb.buf))
+			report_garbage("unused in linked checkout", sb.buf);
+	}
+	strbuf_release(&sb);
 }
 
 static void adjust_git_path(struct strbuf *buf, int git_dir_len)
@@ -138,9 +164,8 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)
 
 static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
 {
-	const char *git_dir = get_git_dir();
 	int gitdir_len;
-	strbuf_addstr(buf, git_dir);
+	strbuf_addstr(buf, get_git_dir());
 	if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
 		strbuf_addch(buf, '/');
 	gitdir_len = buf->len;
@@ -157,7 +182,7 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 	va_end(args);
 }
 
-char *git_path(const char *fmt, ...)
+const char *git_path(const char *fmt, ...)
 {
 	struct strbuf *pathname = get_pathname();
 	va_list args;
@@ -188,7 +213,7 @@ char *mkpathdup(const char *fmt, ...)
 	return strbuf_detach(&sb, NULL);
 }
 
-char *mkpath(const char *fmt, ...)
+const char *mkpath(const char *fmt, ...)
 {
 	va_list args;
 	struct strbuf *pathname = get_pathname();
@@ -224,7 +249,7 @@ void home_config_paths(char **global, char **xdg, char *file)
 	free(to_free);
 }
 
-char *git_path_submodule(const char *path, const char *fmt, ...)
+const char *git_path_submodule(const char *path, const char *fmt, ...)
 {
 	struct strbuf *buf = get_pathname();
 	const char *git_dir;
diff --git a/refs.c b/refs.c
index 434bd5e..c5613b0 100644
--- a/refs.c
+++ b/refs.c
@@ -1232,7 +1232,7 @@ static int resolve_gitlink_ref_recursive(struct ref_cache *refs,
 {
 	int fd, len;
 	char buffer[128], *p;
-	char *path;
+	const char *path;
 
 	if (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN)
 		return -1;
@@ -2044,7 +2044,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
 					    const unsigned char *old_sha1,
 					    int flags, int *type_p)
 {
-	char *ref_file;
+	const char *ref_file;
 	const char *orig_refname = refname;
 	struct ref_lock *lock;
 	int last_errno = 0;
@@ -2107,7 +2107,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
 		lock->force_write = 1;
 
  retry:
-	switch (safe_create_leading_directories(ref_file)) {
+	switch (safe_create_leading_directories_const(ref_file)) {
 	case SCLD_OK:
 		break; /* success */
 	case SCLD_VANISHED:
@@ -2545,7 +2545,7 @@ static int rename_tmp_log(const char *newrefname)
 	int attempts_remaining = 4;
 
  retry:
-	switch (safe_create_leading_directories(git_path("logs/%s", newrefname))) {
+	switch (safe_create_leading_directories_const(git_path("logs/%s", newrefname))) {
 	case SCLD_OK:
 		break; /* success */
 	case SCLD_VANISHED:
@@ -2729,43 +2729,41 @@ static int copy_msg(char *buf, const char *msg)
 	return cp - buf;
 }
 
-int log_ref_setup(const char *refname, struct strbuf *sb_logfile)
+int log_ref_setup(const char *refname, struct strbuf *logfile)
 {
 	int logfd, oflags = O_APPEND | O_WRONLY;
-	const char *logfile;
 
-	strbuf_git_path(sb_logfile, "logs/%s", refname);
-	logfile = sb_logfile->buf;
+	strbuf_git_path(logfile, "logs/%s", refname);
 	if (log_all_ref_updates &&
 	    (starts_with(refname, "refs/heads/") ||
 	     starts_with(refname, "refs/remotes/") ||
 	     starts_with(refname, "refs/notes/") ||
 	     !strcmp(refname, "HEAD"))) {
-		if (safe_create_leading_directories(sb_logfile->buf) < 0)
+		if (safe_create_leading_directories(logfile->buf) < 0)
 			return error("unable to create directory for %s",
-				     logfile);
+				     logfile->buf);
 		oflags |= O_CREAT;
 	}
 
-	logfd = open(logfile, oflags, 0666);
+	logfd = open(logfile->buf, oflags, 0666);
 	if (logfd < 0) {
 		if (!(oflags & O_CREAT) && errno == ENOENT)
 			return 0;
 
 		if ((oflags & O_CREAT) && errno == EISDIR) {
-			if (remove_empty_directories(logfile)) {
+			if (remove_empty_directories(logfile->buf)) {
 				return error("There are still logs under '%s'",
-					     logfile);
+					     logfile->buf);
 			}
-			logfd = open(logfile, oflags, 0666);
+			logfd = open(logfile->buf, oflags, 0666);
 		}
 
 		if (logfd < 0)
 			return error("Unable to append to %s: %s",
-				     logfile, strerror(errno));
+				     logfile->buf, strerror(errno));
 	}
 
-	adjust_shared_perm(logfile);
+	adjust_shared_perm(logfile->buf);
 	close(logfd);
 	return 0;
 }
diff --git a/run-command.c b/run-command.c
index 3914d9c..a94a612 100644
--- a/run-command.c
+++ b/run-command.c
@@ -751,9 +751,9 @@ int finish_async(struct async *async)
 #endif
 }
 
-char *find_hook(const char *name)
+const char *find_hook(const char *name)
 {
-	char *path = git_path("hooks/%s", name);
+	const char *path = git_path("hooks/%s", name);
 	if (access(path, X_OK) < 0)
 		path = NULL;
 
diff --git a/run-command.h b/run-command.h
index 6b985af..da01954 100644
--- a/run-command.h
+++ b/run-command.h
@@ -45,7 +45,7 @@ int start_command(struct child_process *);
 int finish_command(struct child_process *);
 int run_command(struct child_process *);
 
-extern char *find_hook(const char *name);
+extern const char *find_hook(const char *name);
 LAST_ARG_MUST_BE_NULL
 extern int run_hook(const char *index_file, const char *name, ...);
 
diff --git a/sha1_file.c b/sha1_file.c
index 6e8c05d..8af0e18 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -402,7 +402,7 @@ void add_to_alternates_file(const char *reference)
 {
 	struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 	int fd = hold_lock_file_for_append(lock, git_path("objects/info/alternates"), LOCK_DIE_ON_ERROR);
-	char *alt = mkpath("%s\n", reference);
+	const char *alt = mkpath("%s\n", reference);
 	write_or_die(fd, alt, strlen(alt));
 	if (commit_lock_file(lock))
 		die("could not close alternates file");
diff --git a/templates/hooks--applypatch-msg.sample b/templates/hooks--applypatch-msg.sample
index 28b843b..a5d7b84 100755
--- a/templates/hooks--applypatch-msg.sample
+++ b/templates/hooks--applypatch-msg.sample
@@ -10,6 +10,6 @@
 # To enable this hook, rename this file to "applypatch-msg".
 
 . git-sh-setup
-commitmsg="`git rev-parse --git-path hooks/commit-msg`"
+commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
 test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
 :
diff --git a/templates/hooks--pre-applypatch.sample b/templates/hooks--pre-applypatch.sample
index 51aa244..4142082 100755
--- a/templates/hooks--pre-applypatch.sample
+++ b/templates/hooks--pre-applypatch.sample
@@ -9,6 +9,6 @@
 # To enable this hook, rename this file to "pre-applypatch".
 
 . git-sh-setup
-precommit="`git rev-parse --git-path hooks/pre-commit`"
+precommit="$(git rev-parse --git-path hooks/pre-commit)"
 test -x "$precommit" && exec "$precommit" ${1+"$@"}
 :
diff --git a/wrapper.c b/wrapper.c
index 5ced50d..5ad628c 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -459,8 +459,8 @@ struct passwd *xgetpwuid_self(void)
 int write_file(const char *path, int fatal, const char *fmt, ...)
 {
 	struct strbuf sb = STRBUF_INIT;
-	int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);
 	va_list params;
+	int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);
 	if (fd < 0) {
 		if (fatal)
 			die_errno(_("could not open %s for writing"), path);
@@ -472,8 +472,8 @@ int write_file(const char *path, int fatal, const char *fmt, ...)
 	if (write_in_full(fd, sb.buf, sb.len) != sb.len) {
 		int err = errno;
 		close(fd);
-		errno = err;
 		strbuf_release(&sb);
+		errno = err;
 		if (fatal)
 			die_errno(_("could not write to %s"), path);
 		return -1;
-- 8< --
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 01/28] path.c: make get_pathname() return strbuf instead of static buffer
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:47     ` Nguyễn Thái Ngọc Duy
  2014-03-08  6:11       ` Torsten Bögershausen
  2014-03-08  2:47     ` [PATCH v5 02/28] path.c: make get_pathname() call sites return const char * Nguyễn Thái Ngọc Duy
                       ` (28 subsequent siblings)
  29 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:47 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

We've been avoiding PATH_MAX whenever possible. This patch makes
get_pathname() return a strbuf and updates the callers to take
advantage of this. The code is simplified as we no longer need to
worry about buffer overflow.

vsnpath() behavior is changed slightly: previously it always clears
the buffer before writing, now it just appends. Fortunately this is a
static function and all of its callers prepare the buffer properly:
git_path() gets the buffer from get_pathname() which resets the
buffer, the remaining call sites start with STRBUF_INIT'd buffer.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 path.c | 120 ++++++++++++++++++++++++++++-------------------------------------
 1 file changed, 51 insertions(+), 69 deletions(-)

diff --git a/path.c b/path.c
index 24594c4..5346700 100644
--- a/path.c
+++ b/path.c
@@ -16,11 +16,15 @@ static int get_st_mode_bits(const char *path, int *mode)
 
 static char bad_path[] = "/bad-path/";
 
-static char *get_pathname(void)
+static struct strbuf *get_pathname(void)
 {
-	static char pathname_array[4][PATH_MAX];
+	static struct strbuf pathname_array[4] = {
+		STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
+	};
 	static int index;
-	return pathname_array[3 & ++index];
+	struct strbuf *sb = &pathname_array[3 & ++index];
+	strbuf_reset(sb);
+	return sb;
 }
 
 static char *cleanup_path(char *path)
@@ -34,6 +38,13 @@ static char *cleanup_path(char *path)
 	return path;
 }
 
+static void strbuf_cleanup_path(struct strbuf *sb)
+{
+	char *path = cleanup_path(sb->buf);
+	if (path > sb->buf)
+		strbuf_remove(sb, 0, path - sb->buf);
+}
+
 char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 {
 	va_list args;
@@ -49,85 +60,70 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 	return cleanup_path(buf);
 }
 
-static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)
+static void vsnpath(struct strbuf *buf, const char *fmt, va_list args)
 {
 	const char *git_dir = get_git_dir();
-	size_t len;
-
-	len = strlen(git_dir);
-	if (n < len + 1)
-		goto bad;
-	memcpy(buf, git_dir, len);
-	if (len && !is_dir_sep(git_dir[len-1]))
-		buf[len++] = '/';
-	len += vsnprintf(buf + len, n - len, fmt, args);
-	if (len >= n)
-		goto bad;
-	return cleanup_path(buf);
-bad:
-	strlcpy(buf, bad_path, n);
-	return buf;
+	strbuf_addstr(buf, git_dir);
+	if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
+		strbuf_addch(buf, '/');
+	strbuf_vaddf(buf, fmt, args);
+	strbuf_cleanup_path(buf);
 }
 
 char *git_snpath(char *buf, size_t n, const char *fmt, ...)
 {
-	char *ret;
+	struct strbuf sb = STRBUF_INIT;
 	va_list args;
 	va_start(args, fmt);
-	ret = vsnpath(buf, n, fmt, args);
+	vsnpath(&sb, fmt, args);
 	va_end(args);
-	return ret;
+	if (sb.len >= n)
+		strlcpy(buf, bad_path, n);
+	else
+		memcpy(buf, sb.buf, sb.len + 1);
+	strbuf_release(&sb);
+	return buf;
 }
 
 char *git_pathdup(const char *fmt, ...)
 {
-	char path[PATH_MAX], *ret;
+	struct strbuf path = STRBUF_INIT;
 	va_list args;
 	va_start(args, fmt);
-	ret = vsnpath(path, sizeof(path), fmt, args);
+	vsnpath(&path, fmt, args);
 	va_end(args);
-	return xstrdup(ret);
+	return strbuf_detach(&path, NULL);
 }
 
 char *mkpathdup(const char *fmt, ...)
 {
-	char *path;
 	struct strbuf sb = STRBUF_INIT;
 	va_list args;
-
 	va_start(args, fmt);
 	strbuf_vaddf(&sb, fmt, args);
 	va_end(args);
-	path = xstrdup(cleanup_path(sb.buf));
-
-	strbuf_release(&sb);
-	return path;
+	strbuf_cleanup_path(&sb);
+	return strbuf_detach(&sb, NULL);
 }
 
 char *mkpath(const char *fmt, ...)
 {
 	va_list args;
-	unsigned len;
-	char *pathname = get_pathname();
-
+	struct strbuf *pathname = get_pathname();
 	va_start(args, fmt);
-	len = vsnprintf(pathname, PATH_MAX, fmt, args);
+	strbuf_vaddf(pathname, fmt, args);
 	va_end(args);
-	if (len >= PATH_MAX)
-		return bad_path;
-	return cleanup_path(pathname);
+	return cleanup_path(pathname->buf);
 }
 
 char *git_path(const char *fmt, ...)
 {
-	char *pathname = get_pathname();
+	struct strbuf *pathname = get_pathname();
 	va_list args;
-	char *ret;
-
 	va_start(args, fmt);
-	ret = vsnpath(pathname, PATH_MAX, fmt, args);
+	vsnpath(pathname, fmt, args);
 	va_end(args);
-	return ret;
+	return pathname->buf;
 }
 
 void home_config_paths(char **global, char **xdg, char *file)
@@ -158,41 +154,27 @@ void home_config_paths(char **global, char **xdg, char *file)
 
 char *git_path_submodule(const char *path, const char *fmt, ...)
 {
-	char *pathname = get_pathname();
-	struct strbuf buf = STRBUF_INIT;
+	struct strbuf *buf = get_pathname();
 	const char *git_dir;
 	va_list args;
-	unsigned len;
-
-	len = strlen(path);
-	if (len > PATH_MAX-100)
-		return bad_path;
 
-	strbuf_addstr(&buf, path);
-	if (len && path[len-1] != '/')
-		strbuf_addch(&buf, '/');
-	strbuf_addstr(&buf, ".git");
+	strbuf_addstr(buf, path);
+	if (buf->len && buf->buf[buf->len - 1] != '/')
+		strbuf_addch(buf, '/');
+	strbuf_addstr(buf, ".git");
 
-	git_dir = read_gitfile(buf.buf);
+	git_dir = read_gitfile(buf->buf);
 	if (git_dir) {
-		strbuf_reset(&buf);
-		strbuf_addstr(&buf, git_dir);
+		strbuf_reset(buf);
+		strbuf_addstr(buf, git_dir);
 	}
-	strbuf_addch(&buf, '/');
-
-	if (buf.len >= PATH_MAX)
-		return bad_path;
-	memcpy(pathname, buf.buf, buf.len + 1);
-
-	strbuf_release(&buf);
-	len = strlen(pathname);
+	strbuf_addch(buf, '/');
 
 	va_start(args, fmt);
-	len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
+	strbuf_vaddf(buf, fmt, args);
 	va_end(args);
-	if (len >= PATH_MAX)
-		return bad_path;
-	return cleanup_path(pathname);
+	strbuf_cleanup_path(buf);
+	return buf->buf;
 }
 
 int validate_headref(const char *path)
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 02/28] path.c: make get_pathname() call sites return const char *
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
  2014-03-08  2:47     ` [PATCH v5 01/28] path.c: make get_pathname() return strbuf instead of static buffer Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:47     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:47     ` [PATCH v5 03/28] Convert git_snpath() to strbuf_git_path() Nguyễn Thái Ngọc Duy
                       ` (27 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:47 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Before the previous commit, get_pathname returns an array of PATH_MAX
length. Even if git_path() and similar functions does not use the
whole array, git_path() caller can, in theory.

After the commit, get_pathname() may return a buffer that has just
enough room for the returned string and git_path() caller should never
write beyond that.

Make git_path(), mkpath() and git_path_submodule() return a const
buffer to make sure callers do not write in it at all.

This could have been part of the previous commit, but the "const"
conversion is too much distraction from the core changes in path.c.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c     | 2 +-
 builtin/clone.c        | 9 +++++----
 builtin/fetch.c        | 5 +++--
 builtin/fsck.c         | 4 ++--
 builtin/receive-pack.c | 2 +-
 builtin/remote.c       | 2 +-
 builtin/repack.c       | 8 +++++---
 cache.h                | 6 +++---
 fast-import.c          | 2 +-
 notes-merge.c          | 6 +++---
 path.c                 | 6 +++---
 refs.c                 | 8 ++++----
 run-command.c          | 4 ++--
 run-command.h          | 2 +-
 sha1_file.c            | 2 +-
 15 files changed, 36 insertions(+), 32 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 5df3837..64c2aca 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -586,7 +586,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 			if (opts->new_branch_log && !log_all_ref_updates) {
 				int temp;
 				char log_file[PATH_MAX];
-				char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
+				const char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
 
 				temp = log_all_ref_updates;
 				log_all_ref_updates = 1;
diff --git a/builtin/clone.c b/builtin/clone.c
index 43e772c..9339371 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -289,16 +289,17 @@ static void copy_alternates(struct strbuf *src, struct strbuf *dst,
 	struct strbuf line = STRBUF_INIT;
 
 	while (strbuf_getline(&line, in, '\n') != EOF) {
-		char *abs_path, abs_buf[PATH_MAX];
+		char *abs_path;
 		if (!line.len || line.buf[0] == '#')
 			continue;
 		if (is_absolute_path(line.buf)) {
 			add_to_alternates_file(line.buf);
 			continue;
 		}
-		abs_path = mkpath("%s/objects/%s", src_repo, line.buf);
-		normalize_path_copy(abs_buf, abs_path);
-		add_to_alternates_file(abs_buf);
+		abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
+		normalize_path_copy(abs_path, abs_path);
+		add_to_alternates_file(abs_path);
+		free(abs_path);
 	}
 	strbuf_release(&line);
 	fclose(in);
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 025bc3e..04b51ca 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -544,7 +544,8 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
 	struct strbuf note = STRBUF_INIT;
 	const char *what, *kind;
 	struct ref *rm;
-	char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
+	char *url;
+	const char *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
 	int want_status;
 
 	fp = fopen(filename, "a");
@@ -778,7 +779,7 @@ static void check_not_current_branch(struct ref *ref_map)
 
 static int truncate_fetch_head(void)
 {
-	char *filename = git_path("FETCH_HEAD");
+	const char *filename = git_path("FETCH_HEAD");
 	FILE *fp = fopen(filename, "w");
 
 	if (!fp)
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 1affdd5..643c980 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -225,12 +225,12 @@ static void check_unreachable_object(struct object *obj)
 			printf("dangling %s %s\n", typename(obj->type),
 			       sha1_to_hex(obj->sha1));
 		if (write_lost_and_found) {
-			char *filename = git_path("lost-found/%s/%s",
+			const char *filename = git_path("lost-found/%s/%s",
 				obj->type == OBJ_COMMIT ? "commit" : "other",
 				sha1_to_hex(obj->sha1));
 			FILE *f;
 
-			if (safe_create_leading_directories(filename)) {
+			if (safe_create_leading_directories_const(filename)) {
 				error("Could not create lost-found");
 				return;
 			}
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 85bba35..4d27acb 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -599,7 +599,7 @@ static void run_update_post_hook(struct command *commands)
 	int argc;
 	const char **argv;
 	struct child_process proc;
-	char *hook;
+	const char *hook;
 
 	hook = find_hook("post-update");
 	for (argc = 0, cmd = commands; cmd; cmd = cmd->next) {
diff --git a/builtin/remote.c b/builtin/remote.c
index b3ab4cf..ca3cb3b 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -582,7 +582,7 @@ static int migrate_file(struct remote *remote)
 {
 	struct strbuf buf = STRBUF_INIT;
 	int i;
-	char *path = NULL;
+	const char *path = NULL;
 
 	strbuf_addf(&buf, "remote.%s.url", remote->name);
 	for (i = 0; i < remote->url_nr; i++)
diff --git a/builtin/repack.c b/builtin/repack.c
index bb2314c..4d35349 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -257,7 +257,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
 	failed = 0;
 	for_each_string_list_item(item, &names) {
 		for (ext = 0; ext < 2; ext++) {
-			char *fname, *fname_old;
+			const char *fname_old;
+			char *fname;
 			fname = mkpathdup("%s/pack-%s%s", packdir,
 						item->string, exts[ext]);
 			if (!file_exists(fname)) {
@@ -285,7 +286,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
 	if (failed) {
 		struct string_list rollback_failure = STRING_LIST_INIT_DUP;
 		for_each_string_list_item(item, &rollback) {
-			char *fname, *fname_old;
+			const char *fname_old;
+			char *fname;
 			fname = mkpathdup("%s/%s", packdir, item->string);
 			fname_old = mkpath("%s/old-%s", packdir, item->string);
 			if (rename(fname_old, fname))
@@ -334,7 +336,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
 	/* Remove the "old-" files */
 	for_each_string_list_item(item, &names) {
 		for (ext = 0; ext < 2; ext++) {
-			char *fname;
+			const char *fname;
 			fname = mkpath("%s/old-%s%s",
 					packdir,
 					item->string,
diff --git a/cache.h b/cache.h
index dc040fb..a344a5f 100644
--- a/cache.h
+++ b/cache.h
@@ -654,9 +654,9 @@ extern char *mkpathdup(const char *fmt, ...)
 	__attribute__((format (printf, 1, 2)));
 
 /* Return a statically allocated filename matching the sha1 signature */
-extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
-extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
-extern char *git_path_submodule(const char *path, const char *fmt, ...)
+extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern const char *git_path_submodule(const char *path, const char *fmt, ...)
 	__attribute__((format (printf, 2, 3)));
 
 extern char *sha1_file_name(const unsigned char *sha1);
diff --git a/fast-import.c b/fast-import.c
index 4fd18a3..a9f328d 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -403,7 +403,7 @@ static void dump_marks_helper(FILE *, uintmax_t, struct mark_set *);
 
 static void write_crash_report(const char *err)
 {
-	char *loc = git_path("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid());
+	const char *loc = git_path("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid());
 	FILE *rpt = fopen(loc, "w");
 	struct branch *b;
 	unsigned long lu;
diff --git a/notes-merge.c b/notes-merge.c
index 94a1a8a..d59bcc2 100644
--- a/notes-merge.c
+++ b/notes-merge.c
@@ -280,7 +280,7 @@ static void check_notes_merge_worktree(struct notes_merge_options *o)
 				    "(%s exists).", git_path("NOTES_MERGE_*"));
 		}
 
-		if (safe_create_leading_directories(git_path(
+		if (safe_create_leading_directories_const(git_path(
 				NOTES_MERGE_WORKTREE "/.test")))
 			die_errno("unable to create directory %s",
 				  git_path(NOTES_MERGE_WORKTREE));
@@ -295,8 +295,8 @@ static void write_buf_to_worktree(const unsigned char *obj,
 				  const char *buf, unsigned long size)
 {
 	int fd;
-	char *path = git_path(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj));
-	if (safe_create_leading_directories(path))
+	const char *path = git_path(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj));
+	if (safe_create_leading_directories_const(path))
 		die_errno("unable to create directory for '%s'", path);
 	if (file_exists(path))
 		die("found existing file at '%s'", path);
diff --git a/path.c b/path.c
index 5346700..36d461e 100644
--- a/path.c
+++ b/path.c
@@ -106,7 +106,7 @@ char *mkpathdup(const char *fmt, ...)
 	return strbuf_detach(&sb, NULL);
 }
 
-char *mkpath(const char *fmt, ...)
+const char *mkpath(const char *fmt, ...)
 {
 	va_list args;
 	struct strbuf *pathname = get_pathname();
@@ -116,7 +116,7 @@ char *mkpath(const char *fmt, ...)
 	return cleanup_path(pathname->buf);
 }
 
-char *git_path(const char *fmt, ...)
+const char *git_path(const char *fmt, ...)
 {
 	struct strbuf *pathname = get_pathname();
 	va_list args;
@@ -152,7 +152,7 @@ void home_config_paths(char **global, char **xdg, char *file)
 	free(to_free);
 }
 
-char *git_path_submodule(const char *path, const char *fmt, ...)
+const char *git_path_submodule(const char *path, const char *fmt, ...)
 {
 	struct strbuf *buf = get_pathname();
 	const char *git_dir;
diff --git a/refs.c b/refs.c
index 89228e2..f846f2f 100644
--- a/refs.c
+++ b/refs.c
@@ -1232,7 +1232,7 @@ static int resolve_gitlink_ref_recursive(struct ref_cache *refs,
 {
 	int fd, len;
 	char buffer[128], *p;
-	char *path;
+	const char *path;
 
 	if (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN)
 		return -1;
@@ -2032,7 +2032,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
 					    const unsigned char *old_sha1,
 					    int flags, int *type_p)
 {
-	char *ref_file;
+	const char *ref_file;
 	const char *orig_refname = refname;
 	struct ref_lock *lock;
 	int last_errno = 0;
@@ -2095,7 +2095,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
 		lock->force_write = 1;
 
  retry:
-	switch (safe_create_leading_directories(ref_file)) {
+	switch (safe_create_leading_directories_const(ref_file)) {
 	case SCLD_OK:
 		break; /* success */
 	case SCLD_VANISHED:
@@ -2533,7 +2533,7 @@ static int rename_tmp_log(const char *newrefname)
 	int attempts_remaining = 4;
 
  retry:
-	switch (safe_create_leading_directories(git_path("logs/%s", newrefname))) {
+	switch (safe_create_leading_directories_const(git_path("logs/%s", newrefname))) {
 	case SCLD_OK:
 		break; /* success */
 	case SCLD_VANISHED:
diff --git a/run-command.c b/run-command.c
index 3914d9c..a94a612 100644
--- a/run-command.c
+++ b/run-command.c
@@ -751,9 +751,9 @@ int finish_async(struct async *async)
 #endif
 }
 
-char *find_hook(const char *name)
+const char *find_hook(const char *name)
 {
-	char *path = git_path("hooks/%s", name);
+	const char *path = git_path("hooks/%s", name);
 	if (access(path, X_OK) < 0)
 		path = NULL;
 
diff --git a/run-command.h b/run-command.h
index 6b985af..da01954 100644
--- a/run-command.h
+++ b/run-command.h
@@ -45,7 +45,7 @@ int start_command(struct child_process *);
 int finish_command(struct child_process *);
 int run_command(struct child_process *);
 
-extern char *find_hook(const char *name);
+extern const char *find_hook(const char *name);
 LAST_ARG_MUST_BE_NULL
 extern int run_hook(const char *index_file, const char *name, ...);
 
diff --git a/sha1_file.c b/sha1_file.c
index 6e8c05d..8af0e18 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -402,7 +402,7 @@ void add_to_alternates_file(const char *reference)
 {
 	struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 	int fd = hold_lock_file_for_append(lock, git_path("objects/info/alternates"), LOCK_DIE_ON_ERROR);
-	char *alt = mkpath("%s\n", reference);
+	const char *alt = mkpath("%s\n", reference);
 	write_or_die(fd, alt, strlen(alt));
 	if (commit_lock_file(lock))
 		die("could not close alternates file");
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 03/28] Convert git_snpath() to strbuf_git_path()
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
  2014-03-08  2:47     ` [PATCH v5 01/28] path.c: make get_pathname() return strbuf instead of static buffer Nguyễn Thái Ngọc Duy
  2014-03-08  2:47     ` [PATCH v5 02/28] path.c: make get_pathname() call sites return const char * Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:47     ` Nguyễn Thái Ngọc Duy
  2014-03-17 18:11       ` Junio C Hamano
  2014-03-08  2:47     ` [PATCH v5 04/28] path.c: rename vsnpath() to do_git_path() Nguyễn Thái Ngọc Duy
                       ` (26 subsequent siblings)
  29 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:47 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

In the previous patch, git_snpath() is modified to allocate a new
strbuf buffer because vsnpath() needs that. But that makes it awkward
because git_snpath() receives a pre-allocated buffer from outside and
has to copy data back. Rename it to strbuf_git_path() and make it
receive strbuf directly.

The conversion from git_snpath() to git_path() in
update_refs_for_switch() is safe because that function does not keep
any pointer to the round-robin buffer pool allocated by
get_pathname().

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 25 +++++++++--------
 cache.h            |  4 +--
 path.c             | 11 ++------
 refs.c             | 78 +++++++++++++++++++++++++++++++++---------------------
 refs.h             |  2 +-
 5 files changed, 65 insertions(+), 55 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 64c2aca..efb5e2f 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -585,18 +585,21 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 		if (opts->new_orphan_branch) {
 			if (opts->new_branch_log && !log_all_ref_updates) {
 				int temp;
-				char log_file[PATH_MAX];
-				const char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
+				struct strbuf log_file = STRBUF_INIT;
+				int ret;
+				const char *ref_name;
 
+				ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
 				temp = log_all_ref_updates;
 				log_all_ref_updates = 1;
-				if (log_ref_setup(ref_name, log_file, sizeof(log_file))) {
+				ret = log_ref_setup(ref_name, &log_file);
+				log_all_ref_updates = temp;
+				strbuf_release(&log_file);
+				if (ret) {
 					fprintf(stderr, _("Can not do reflog for '%s'\n"),
 					    opts->new_orphan_branch);
-					log_all_ref_updates = temp;
 					return;
 				}
-				log_all_ref_updates = temp;
 			}
 		}
 		else
@@ -651,14 +654,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 					new->name);
 			}
 		}
-		if (old->path && old->name) {
-			char log_file[PATH_MAX], ref_file[PATH_MAX];
-
-			git_snpath(log_file, sizeof(log_file), "logs/%s", old->path);
-			git_snpath(ref_file, sizeof(ref_file), "%s", old->path);
-			if (!file_exists(ref_file) && file_exists(log_file))
-				remove_path(log_file);
-		}
+		if (old->path && old->name &&
+		    !file_exists(git_path("%s", old->path)) &&
+		     file_exists(git_path("logs/%s", old->path)))
+			remove_path(git_path("logs/%s", old->path));
 	}
 	remove_branch_state();
 	strbuf_release(&msg);
diff --git a/cache.h b/cache.h
index a344a5f..0fae730 100644
--- a/cache.h
+++ b/cache.h
@@ -646,8 +646,8 @@ extern int check_repository_format(void);
 
 extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 	__attribute__((format (printf, 3, 4)));
-extern char *git_snpath(char *buf, size_t n, const char *fmt, ...)
-	__attribute__((format (printf, 3, 4)));
+extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
+	__attribute__((format (printf, 2, 3)));
 extern char *git_pathdup(const char *fmt, ...)
 	__attribute__((format (printf, 1, 2)));
 extern char *mkpathdup(const char *fmt, ...)
diff --git a/path.c b/path.c
index 36d461e..417df76 100644
--- a/path.c
+++ b/path.c
@@ -70,19 +70,12 @@ static void vsnpath(struct strbuf *buf, const char *fmt, va_list args)
 	strbuf_cleanup_path(buf);
 }
 
-char *git_snpath(char *buf, size_t n, const char *fmt, ...)
+void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 {
-	struct strbuf sb = STRBUF_INIT;
 	va_list args;
 	va_start(args, fmt);
-	vsnpath(&sb, fmt, args);
+	vsnpath(sb, fmt, args);
 	va_end(args);
-	if (sb.len >= n)
-		strlcpy(buf, bad_path, n);
-	else
-		memcpy(buf, sb.buf, sb.len + 1);
-	strbuf_release(&sb);
-	return buf;
 }
 
 char *git_pathdup(const char *fmt, ...)
diff --git a/refs.c b/refs.c
index f846f2f..c5613b0 100644
--- a/refs.c
+++ b/refs.c
@@ -1325,10 +1325,12 @@ static const char *handle_missing_loose_ref(const char *refname,
 
 const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)
 {
+	struct strbuf sb_path = STRBUF_INIT;
 	int depth = MAXDEPTH;
 	ssize_t len;
 	char buffer[256];
 	static char refname_buffer[256];
+	const char *ret;
 
 	if (flag)
 		*flag = 0;
@@ -1337,15 +1339,17 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 		return NULL;
 
 	for (;;) {
-		char path[PATH_MAX];
+		const char *path;
 		struct stat st;
 		char *buf;
 		int fd;
 
 		if (--depth < 0)
-			return NULL;
+			goto fail;
 
-		git_snpath(path, sizeof(path), "%s", refname);
+		strbuf_reset(&sb_path);
+		strbuf_git_path(&sb_path, "%s", refname);
+		path = sb_path.buf;
 
 		/*
 		 * We might have to loop back here to avoid a race
@@ -1359,10 +1363,11 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 	stat_ref:
 		if (lstat(path, &st) < 0) {
 			if (errno == ENOENT)
-				return handle_missing_loose_ref(refname, sha1,
-								reading, flag);
+				ret = handle_missing_loose_ref(refname, sha1,
+							       reading, flag);
 			else
-				return NULL;
+				ret = NULL;
+			goto done;
 		}
 
 		/* Follow "normalized" - ie "refs/.." symlinks by hand */
@@ -1373,7 +1378,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 					/* inconsistent with lstat; retry */
 					goto stat_ref;
 				else
-					return NULL;
+					goto fail;
 			}
 			buffer[len] = 0;
 			if (starts_with(buffer, "refs/") &&
@@ -1389,7 +1394,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 		/* Is it a directory? */
 		if (S_ISDIR(st.st_mode)) {
 			errno = EISDIR;
-			return NULL;
+			goto fail;
 		}
 
 		/*
@@ -1402,12 +1407,13 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 				/* inconsistent with lstat; retry */
 				goto stat_ref;
 			else
-				return NULL;
+				goto fail;
 		}
+
 		len = read_in_full(fd, buffer, sizeof(buffer)-1);
 		close(fd);
 		if (len < 0)
-			return NULL;
+			goto fail;
 		while (len && isspace(buffer[len-1]))
 			len--;
 		buffer[len] = '\0';
@@ -1424,9 +1430,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 			    (buffer[40] != '\0' && !isspace(buffer[40]))) {
 				if (flag)
 					*flag |= REF_ISBROKEN;
-				return NULL;
+				goto fail;
 			}
-			return refname;
+			ret = refname;
+			goto done;
 		}
 		if (flag)
 			*flag |= REF_ISSYMREF;
@@ -1436,10 +1443,15 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 		if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
 			if (flag)
 				*flag |= REF_ISBROKEN;
-			return NULL;
+			goto fail;
 		}
 		refname = strcpy(refname_buffer, buf);
 	}
+fail:
+	ret = NULL;
+done:
+	strbuf_release(&sb_path);
+	return ret;
 }
 
 char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag)
@@ -2717,41 +2729,41 @@ static int copy_msg(char *buf, const char *msg)
 	return cp - buf;
 }
 
-int log_ref_setup(const char *refname, char *logfile, int bufsize)
+int log_ref_setup(const char *refname, struct strbuf *logfile)
 {
 	int logfd, oflags = O_APPEND | O_WRONLY;
 
-	git_snpath(logfile, bufsize, "logs/%s", refname);
+	strbuf_git_path(logfile, "logs/%s", refname);
 	if (log_all_ref_updates &&
 	    (starts_with(refname, "refs/heads/") ||
 	     starts_with(refname, "refs/remotes/") ||
 	     starts_with(refname, "refs/notes/") ||
 	     !strcmp(refname, "HEAD"))) {
-		if (safe_create_leading_directories(logfile) < 0)
+		if (safe_create_leading_directories(logfile->buf) < 0)
 			return error("unable to create directory for %s",
-				     logfile);
+				     logfile->buf);
 		oflags |= O_CREAT;
 	}
 
-	logfd = open(logfile, oflags, 0666);
+	logfd = open(logfile->buf, oflags, 0666);
 	if (logfd < 0) {
 		if (!(oflags & O_CREAT) && errno == ENOENT)
 			return 0;
 
 		if ((oflags & O_CREAT) && errno == EISDIR) {
-			if (remove_empty_directories(logfile)) {
+			if (remove_empty_directories(logfile->buf)) {
 				return error("There are still logs under '%s'",
-					     logfile);
+					     logfile->buf);
 			}
-			logfd = open(logfile, oflags, 0666);
+			logfd = open(logfile->buf, oflags, 0666);
 		}
 
 		if (logfd < 0)
 			return error("Unable to append to %s: %s",
-				     logfile, strerror(errno));
+				     logfile->buf, strerror(errno));
 	}
 
-	adjust_shared_perm(logfile);
+	adjust_shared_perm(logfile->buf);
 	close(logfd);
 	return 0;
 }
@@ -2762,20 +2774,22 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,
 	int logfd, result, written, oflags = O_APPEND | O_WRONLY;
 	unsigned maxlen, len;
 	int msglen;
-	char log_file[PATH_MAX];
+	struct strbuf sb_log_file = STRBUF_INIT;
+	const char *log_file;
 	char *logrec;
 	const char *committer;
 
 	if (log_all_ref_updates < 0)
 		log_all_ref_updates = !is_bare_repository();
 
-	result = log_ref_setup(refname, log_file, sizeof(log_file));
+	result = log_ref_setup(refname, &sb_log_file);
 	if (result)
-		return result;
+		goto done;
+	log_file = sb_log_file.buf;
 
 	logfd = open(log_file, oflags);
 	if (logfd < 0)
-		return 0;
+		goto done;
 	msglen = msg ? strlen(msg) : 0;
 	committer = git_committer_info(0);
 	maxlen = strlen(committer) + msglen + 100;
@@ -2788,9 +2802,13 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,
 		len += copy_msg(logrec + len - 1, msg) - 1;
 	written = len <= maxlen ? write_in_full(logfd, logrec, len) : -1;
 	free(logrec);
-	if (close(logfd) != 0 || written != len)
-		return error("Unable to append to %s", log_file);
-	return 0;
+	if (close(logfd) != 0 || written != len) {
+		error("Unable to append to %s", log_file);
+		result = -1;
+	}
+done:
+	strbuf_release(&sb_log_file);
+	return result;
 }
 
 static int is_branch(const char *refname)
diff --git a/refs.h b/refs.h
index 87a1a79..783033a 100644
--- a/refs.h
+++ b/refs.h
@@ -166,7 +166,7 @@ extern void unlock_ref(struct ref_lock *lock);
 extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
 
 /** Setup reflog before using. **/
-int log_ref_setup(const char *ref_name, char *logfile, int bufsize);
+int log_ref_setup(const char *ref_name, struct strbuf *logfile);
 
 /** Reads log for the value of ref during at_time. **/
 extern int read_ref_at(const char *refname, unsigned long at_time, int cnt,
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 04/28] path.c: rename vsnpath() to do_git_path()
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (2 preceding siblings ...)
  2014-03-08  2:47     ` [PATCH v5 03/28] Convert git_snpath() to strbuf_git_path() Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:47     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:47     ` [PATCH v5 05/28] path.c: group git_path(), git_pathdup() and strbuf_git_path() together Nguyễn Thái Ngọc Duy
                       ` (25 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:47 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

The name vsnpath() gives an impression that this is general path
handling function. It's not. This is the underlying implementation of
git_path(), git_pathdup() and strbuf_git_path() which will prefix
$GIT_DIR in the result string.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 path.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/path.c b/path.c
index 417df76..987fdec 100644
--- a/path.c
+++ b/path.c
@@ -60,7 +60,7 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 	return cleanup_path(buf);
 }
 
-static void vsnpath(struct strbuf *buf, const char *fmt, va_list args)
+static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
 {
 	const char *git_dir = get_git_dir();
 	strbuf_addstr(buf, git_dir);
@@ -74,7 +74,7 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 {
 	va_list args;
 	va_start(args, fmt);
-	vsnpath(sb, fmt, args);
+	do_git_path(sb, fmt, args);
 	va_end(args);
 }
 
@@ -83,7 +83,7 @@ char *git_pathdup(const char *fmt, ...)
 	struct strbuf path = STRBUF_INIT;
 	va_list args;
 	va_start(args, fmt);
-	vsnpath(&path, fmt, args);
+	do_git_path(&path, fmt, args);
 	va_end(args);
 	return strbuf_detach(&path, NULL);
 }
@@ -114,7 +114,7 @@ const char *git_path(const char *fmt, ...)
 	struct strbuf *pathname = get_pathname();
 	va_list args;
 	va_start(args, fmt);
-	vsnpath(pathname, fmt, args);
+	do_git_path(pathname, fmt, args);
 	va_end(args);
 	return pathname->buf;
 }
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 05/28] path.c: group git_path(), git_pathdup() and strbuf_git_path() together
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (3 preceding siblings ...)
  2014-03-08  2:47     ` [PATCH v5 04/28] path.c: rename vsnpath() to do_git_path() Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:47     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:47     ` [PATCH v5 06/28] Make git_path() aware of file relocation in $GIT_DIR Nguyễn Thái Ngọc Duy
                       ` (24 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:47 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 path.c | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/path.c b/path.c
index 987fdec..1c0f160 100644
--- a/path.c
+++ b/path.c
@@ -78,6 +78,16 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 	va_end(args);
 }
 
+const char *git_path(const char *fmt, ...)
+{
+	struct strbuf *pathname = get_pathname();
+	va_list args;
+	va_start(args, fmt);
+	do_git_path(pathname, fmt, args);
+	va_end(args);
+	return pathname->buf;
+}
+
 char *git_pathdup(const char *fmt, ...)
 {
 	struct strbuf path = STRBUF_INIT;
@@ -109,16 +119,6 @@ const char *mkpath(const char *fmt, ...)
 	return cleanup_path(pathname->buf);
 }
 
-const char *git_path(const char *fmt, ...)
-{
-	struct strbuf *pathname = get_pathname();
-	va_list args;
-	va_start(args, fmt);
-	do_git_path(pathname, fmt, args);
-	va_end(args);
-	return pathname->buf;
-}
-
 void home_config_paths(char **global, char **xdg, char *file)
 {
 	char *xdg_home = getenv("XDG_CONFIG_HOME");
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 06/28] Make git_path() aware of file relocation in $GIT_DIR
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (4 preceding siblings ...)
  2014-03-08  2:47     ` [PATCH v5 05/28] path.c: group git_path(), git_pathdup() and strbuf_git_path() together Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:47     ` Nguyễn Thái Ngọc Duy
  2014-03-09  8:19       ` Eric Sunshine
  2014-03-08  2:47     ` [PATCH v5 07/28] *.sh: respect $GIT_INDEX_FILE Nguyễn Thái Ngọc Duy
                       ` (23 subsequent siblings)
  29 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:47 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

We allow the user to relocate certain paths out of $GIT_DIR via
environment variables, e.g. GIT_OBJECT_DIRECTORY, GIT_INDEX_FILE and
GIT_GRAFT_FILE. Callers are not supposed to use git_path() or
git_pathdup() to get those paths. Instead they must use
get_object_directory(), get_index_file() and get_graft_file()
respectively. This is inconvenient and could be missed in review (for
example, there's git_path("objects/info/alternates") somewhere in
sha1_file.c).

This patch makes git_path() and git_pathdup() understand those
environment variables. So if you set GIT_OBJECT_DIRECTORY to /foo/bar,
git_path("objects/abc") should return /foo/bar/abc. The same is done
for the two remaining env variables.

"git rev-parse --git-path" is the wrapper for script use.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-rev-parse.txt |  7 ++++++
 builtin/rev-parse.c             |  7 ++++++
 cache.h                         |  1 +
 environment.c                   |  9 ++++++--
 path.c                          | 49 +++++++++++++++++++++++++++++++++++++++--
 t/t0060-path-utils.sh           | 19 ++++++++++++++++
 6 files changed, 88 insertions(+), 4 deletions(-)

diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 0d2cdcd..46020d9 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -232,6 +232,13 @@ print a message to stderr and exit with nonzero status.
 	repository.  If <path> is a gitfile then the resolved path
 	to the real repository is printed.
 
+--git-path <path>::
+	Resolve "$GIT_DIR/<path>" and takes other path relocation
+	variables such as $GIT_OBJECT_DIRECTORY,
+	$GIT_INDEX_FILE... into account. For example, if
+	$GIT_OBJECT_DIRECTORY is set to /foo/bar then "git rev-parse
+	--git-path objects/abc" returns /tmp/bar/abc.
+
 --show-cdup::
 	When the command is invoked from a subdirectory, show the
 	path of the top-level directory relative to the current
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index aaeb611..e50bc65 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -518,6 +518,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
 	for (i = 1; i < argc; i++) {
 		const char *arg = argv[i];
 
+		if (!strcmp(arg, "--git-path")) {
+			if (!argv[i + 1])
+				die("--git-path requires an argument");
+			puts(git_path("%s", argv[i + 1]));
+			i++;
+			continue;
+		}
 		if (as_is) {
 			if (show_file(arg, output_prefix) && as_is < 2)
 				verify_filename(prefix, arg, 0);
diff --git a/cache.h b/cache.h
index 0fae730..0f5f44f 100644
--- a/cache.h
+++ b/cache.h
@@ -585,6 +585,7 @@ extern int fsync_object_files;
 extern int core_preload_index;
 extern int core_apply_sparse_checkout;
 extern int precomposed_unicode;
+extern int git_db_env, git_index_env, git_graft_env;
 
 /*
  * The character that begins a commented line in user-editable file
diff --git a/environment.c b/environment.c
index 4a3437d..f513479 100644
--- a/environment.c
+++ b/environment.c
@@ -82,6 +82,7 @@ static size_t namespace_len;
 
 static const char *git_dir;
 static char *git_object_dir, *git_index_file, *git_graft_file;
+int git_db_env, git_index_env, git_graft_env;
 
 /*
  * Repository-local GIT_* environment variables; see cache.h for details.
@@ -137,15 +138,19 @@ static void setup_git_env(void)
 	if (!git_object_dir) {
 		git_object_dir = xmalloc(strlen(git_dir) + 9);
 		sprintf(git_object_dir, "%s/objects", git_dir);
-	}
+	} else
+		git_db_env = 1;
 	git_index_file = getenv(INDEX_ENVIRONMENT);
 	if (!git_index_file) {
 		git_index_file = xmalloc(strlen(git_dir) + 7);
 		sprintf(git_index_file, "%s/index", git_dir);
-	}
+	} else
+		git_index_env = 1;
 	git_graft_file = getenv(GRAFT_ENVIRONMENT);
 	if (!git_graft_file)
 		git_graft_file = git_pathdup("info/grafts");
+	else
+		git_graft_env = 1;
 	if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
 		read_replace_refs = 0;
 	namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));
diff --git a/path.c b/path.c
index 1c0f160..1069ae0 100644
--- a/path.c
+++ b/path.c
@@ -60,13 +60,58 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 	return cleanup_path(buf);
 }
 
+static int dir_prefix(const char *buf, const char *dir)
+{
+	int len = strlen(dir);
+	return !strncmp(buf, dir, len) &&
+		(is_dir_sep(buf[len]) || buf[len] == '\0');
+}
+
+/* $buf =~ m|$dir/+$file| but without regex */
+static int is_dir_file(const char *buf, const char *dir, const char *file)
+{
+	int len = strlen(dir);
+	if (strncmp(buf, dir, len) || !is_dir_sep(buf[len]))
+		return 0;
+	while (is_dir_sep(buf[len]))
+		len++;
+	return !strcmp(buf + len, file);
+}
+
+static void replace_dir(struct strbuf *buf, int len, const char *newdir)
+{
+	int newlen = strlen(newdir);
+	int need_sep = (buf->buf[len] && !is_dir_sep(buf->buf[len])) &&
+		!is_dir_sep(newdir[newlen - 1]);
+	if (need_sep)
+		len--;	 /* keep one char, to be replaced with '/'  */
+	strbuf_splice(buf, 0, len, newdir, newlen);
+	if (need_sep)
+		buf->buf[newlen] = '/';
+}
+
+static void adjust_git_path(struct strbuf *buf, int git_dir_len)
+{
+	const char *base = buf->buf + git_dir_len;
+	if (git_graft_env && is_dir_file(base, "info", "grafts"))
+		strbuf_splice(buf, 0, buf->len,
+			      get_graft_file(), strlen(get_graft_file()));
+	else if (git_index_env && !strcmp(base, "index"))
+		strbuf_splice(buf, 0, buf->len,
+			      get_index_file(), strlen(get_index_file()));
+	else if (git_db_env && dir_prefix(base, "objects"))
+		replace_dir(buf, git_dir_len + 7, get_object_directory());
+}
+
 static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
 {
-	const char *git_dir = get_git_dir();
-	strbuf_addstr(buf, git_dir);
+	int gitdir_len;
+	strbuf_addstr(buf, get_git_dir());
 	if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
 		strbuf_addch(buf, '/');
+	gitdir_len = buf->len;
 	strbuf_vaddf(buf, fmt, args);
+	adjust_git_path(buf, gitdir_len);
 	strbuf_cleanup_path(buf);
 }
 
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index 07c10c8..1d29901 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -19,6 +19,14 @@ relative_path() {
 	"test \"\$(test-path-utils relative_path '$1' '$2')\" = '$expected'"
 }
 
+test_git_path() {
+	test_expect_success "git-path $1 $2 => $3" "
+		$1 git rev-parse --git-path $2 >actual &&
+		echo $3 >expect &&
+		test_cmp expect actual
+	"
+}
+
 # On Windows, we are using MSYS's bash, which mangles the paths.
 # Absolute paths are anchored at the MSYS installation directory,
 # which means that the path / accounts for this many characters:
@@ -223,4 +231,15 @@ relative_path "<null>"		"<empty>"	./
 relative_path "<null>"		"<null>"	./
 relative_path "<null>"		/foo/a/b	./
 
+test_git_path A=B                info/grafts .git/info/grafts
+test_git_path GIT_GRAFT_FILE=foo info/grafts foo
+test_git_path GIT_GRAFT_FILE=foo info/////grafts foo
+test_git_path GIT_INDEX_FILE=foo index foo
+test_git_path GIT_INDEX_FILE=foo index/foo .git/index/foo
+test_git_path GIT_INDEX_FILE=foo index2 .git/index2
+test_expect_success 'setup fake objects directory foo' 'mkdir foo'
+test_git_path GIT_OBJECT_DIRECTORY=foo objects foo
+test_git_path GIT_OBJECT_DIRECTORY=foo objects/foo foo/foo
+test_git_path GIT_OBJECT_DIRECTORY=foo objects2 .git/objects2
+
 test_done
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 07/28] *.sh: respect $GIT_INDEX_FILE
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (5 preceding siblings ...)
  2014-03-08  2:47     ` [PATCH v5 06/28] Make git_path() aware of file relocation in $GIT_DIR Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:47     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:48     ` [PATCH v5 08/28] reflog: avoid constructing .lock path with git_path Nguyễn Thái Ngọc Duy
                       ` (22 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:47 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 git-pull.sh  | 2 +-
 git-stash.sh | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/git-pull.sh b/git-pull.sh
index 0a5aa2c..2a90262 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -218,7 +218,7 @@ test true = "$rebase" && {
 	if ! git rev-parse -q --verify HEAD >/dev/null
 	then
 		# On an unborn branch
-		if test -f "$GIT_DIR/index"
+		if test -f "$(git rev-parse --git-path index)"
 		then
 			die "$(gettext "updating an unborn branch with changes added to the index")"
 		fi
diff --git a/git-stash.sh b/git-stash.sh
index f0a94ab..0de9a6c 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -20,7 +20,7 @@ require_work_tree
 cd_to_toplevel
 
 TMP="$GIT_DIR/.git-stash.$$"
-TMPindex=${GIT_INDEX_FILE-"$GIT_DIR/index"}.stash.$$
+TMPindex=${GIT_INDEX_FILE-"$(git rev-parse --git-path index)"}.stash.$$
 trap 'rm -f "$TMP-"* "$TMPindex"' 0
 
 ref_stash=refs/stash
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 08/28] reflog: avoid constructing .lock path with git_path
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (6 preceding siblings ...)
  2014-03-08  2:47     ` [PATCH v5 07/28] *.sh: respect $GIT_INDEX_FILE Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:48     ` [PATCH v5 09/28] fast-import: use git_path() for accessing .git dir instead of get_git_dir() Nguyễn Thái Ngọc Duy
                       ` (21 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Among pathnames in $GIT_DIR, e.g. "index" or "packed-refs", we want to
automatically and silently map some of them to the $GIT_DIR of the
repository we are borrowing from via $GIT_COMMON_DIR mechanism.  When
we formulate the pathname for its lockfile, we want it to be in the
same location as its final destination.  "index" is not shared and
needs to remain in the borrowing repository, while "packed-refs" is
shared and needs to go to the borrowed repository.

git_path() could be taught about the ".lock" suffix and map
"index.lock" and "packed-refs.lock" the same way their basenames are
mapped, but instead the caller can help by asking where the basename
(e.g. "index") is mapped to git_path() and then appending ".lock"
after the mapping is done.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/reflog.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/reflog.c b/builtin/reflog.c
index 852cff6..ccf2cf6 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -372,7 +372,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
 	if (!file_exists(log_file))
 		goto finish;
 	if (!cmd->dry_run) {
-		newlog_path = git_pathdup("logs/%s.lock", ref);
+		newlog_path = mkpathdup("%s.lock", log_file);
 		cb.newlog = fopen(newlog_path, "w");
 	}
 
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 09/28] fast-import: use git_path() for accessing .git dir instead of get_git_dir()
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (7 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 08/28] reflog: avoid constructing .lock path with git_path Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:48     ` [PATCH v5 10/28] commit: use SEQ_DIR instead of hardcoding "sequencer" Nguyễn Thái Ngọc Duy
                       ` (20 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

This allows git_path() to redirect info/fast-import to another place
if needed

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 fast-import.c | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/fast-import.c b/fast-import.c
index a9f328d..28e7a63 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -3125,12 +3125,9 @@ static void parse_progress(void)
 
 static char* make_fast_import_path(const char *path)
 {
-	struct strbuf abs_path = STRBUF_INIT;
-
 	if (!relative_marks_paths || is_absolute_path(path))
 		return xstrdup(path);
-	strbuf_addf(&abs_path, "%s/info/fast-import/%s", get_git_dir(), path);
-	return strbuf_detach(&abs_path, NULL);
+	return xstrdup(git_path("info/fast-import/%s", path));
 }
 
 static void option_import_marks(const char *marks,
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 10/28] commit: use SEQ_DIR instead of hardcoding "sequencer"
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (8 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 09/28] fast-import: use git_path() for accessing .git dir instead of get_git_dir() Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:48     ` [PATCH v5 11/28] Add new environment variable $GIT_COMMON_DIR Nguyễn Thái Ngọc Duy
                       ` (19 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/commit.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/commit.c b/builtin/commit.c
index 3767478..ee3ac10 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -155,7 +155,7 @@ static void determine_whence(struct wt_status *s)
 		whence = FROM_MERGE;
 	else if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
 		whence = FROM_CHERRY_PICK;
-		if (file_exists(git_path("sequencer")))
+		if (file_exists(git_path(SEQ_DIR)))
 			sequencer_in_use = 1;
 	}
 	else
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 11/28] Add new environment variable $GIT_COMMON_DIR
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (9 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 10/28] commit: use SEQ_DIR instead of hardcoding "sequencer" Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:48     ` [PATCH v5 12/28] git-sh-setup.sh: use rev-parse --git-path to get $GIT_DIR/objects Nguyễn Thái Ngọc Duy
                       ` (18 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

This variable is intended to support multiple working directories
attached to a repository. Such a repository may have a main working
directory, created by either "git init" or "git clone" and one or more
linked working directories. These working directories and the main
repository share the same repository directory.

In linked working directories, $GIT_COMMON_DIR must be defined to point
to the real repository directory and $GIT_DIR points to an unused
subdirectory inside $GIT_COMMON_DIR. File locations inside the
repository are reorganized from the linked worktree view point:

 - worktree-specific such as HEAD, logs/HEAD, index, other top-level
   refs and unrecognized files are from $GIT_DIR.

 - the rest like objects, refs, info, hooks, packed-refs, shallow...
   are from $GIT_COMMON_DIR

Scripts are supposed to retrieve paths in $GIT_DIR with "git rev-parse
--git-path", which will take care of "$GIT_DIR vs $GIT_COMMON_DIR"
business.

The redirection is done by git_path(), git_pathdup() and
strbuf_git_path(). The selected list of paths goes to $GIT_COMMON_DIR,
not the other way around in case a developer adds a new
worktree-specific file and it's accidentally promoted to be shared
across repositories (this includes unknown files added by third party
commands)

The list of known files that belong to $GIT_DIR are:

ADD_EDIT.patch BISECT_ANCESTORS_OK BISECT_EXPECTED_REV BISECT_LOG
BISECT_NAMES CHERRY_PICK_HEAD COMMIT_MSG FETCH_HEAD HEAD MERGE_HEAD
MERGE_MODE MERGE_RR NOTES_EDITMSG NOTES_MERGE_WORKTREE ORIG_HEAD
REVERT_HEAD SQUASH_MSG TAG_EDITMSG fast_import_crash_* logs/HEAD
next-index-* rebase-apply rebase-merge rsync-refs-* sequencer/*
shallow_*

Path mapping is NOT done for git_path_submodule(). Multi-checkouts are
not supported as submodules.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git.txt                  |  8 +++++++
 Documentation/gitrepository-layout.txt | 42 ++++++++++++++++++++++++++--------
 cache.h                                |  4 +++-
 environment.c                          | 19 +++++++++++----
 path.c                                 | 34 +++++++++++++++++++++++++++
 t/t0060-path-utils.sh                  | 15 ++++++++++++
 6 files changed, 108 insertions(+), 14 deletions(-)

diff --git a/Documentation/git.txt b/Documentation/git.txt
index 02bbc08..b094b1f 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -773,6 +773,14 @@ Git so take care if using Cogito etc.
 	an explicit repository directory set via 'GIT_DIR' or on the
 	command line.
 
+'GIT_COMMON_DIR'::
+	If this variable is set to a path, non-worktree files that are
+	normally in $GIT_DIR will be taken from this path
+	instead. Worktree-specific files such as HEAD or index are
+	taken from $GIT_DIR. See linkgit:gitrepository-layout[5] for
+	details. This variable has lower precedence than other path
+	variables such as GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY...
+
 Git Commits
 ~~~~~~~~~~~
 'GIT_AUTHOR_NAME'::
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index aa03882..556da09 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -46,6 +46,9 @@ of incomplete object store is not suitable to be published for
 use with dumb transports but otherwise is OK as long as
 `objects/info/alternates` points at the object stores it
 borrows from.
++
+This directory is ignored if $GIT_COMMON_DIR is set and
+"$GIT_COMMON_DIR/objects" will be used instead.
 
 objects/[0-9a-f][0-9a-f]::
 	A newly created object is stored in its own file.
@@ -92,7 +95,8 @@ refs::
 	References are stored in subdirectories of this
 	directory.  The 'git prune' command knows to preserve
 	objects reachable from refs found in this directory and
-	its subdirectories.
+	its subdirectories. This directory is ignored if $GIT_COMMON_DIR
+	is set and "$GIT_COMMON_DIR/refs" will be used instead.
 
 refs/heads/`name`::
 	records tip-of-the-tree commit objects of branch `name`
@@ -114,7 +118,8 @@ refs/replace/`<obj-sha1>`::
 packed-refs::
 	records the same information as refs/heads/, refs/tags/,
 	and friends record in a more efficient way.  See
-	linkgit:git-pack-refs[1].
+	linkgit:git-pack-refs[1]. This file is ignored if $GIT_COMMON_DIR
+	is set and "$GIT_COMMON_DIR/packed-refs" will be used instead.
 
 HEAD::
 	A symref (see glossary) to the `refs/heads/` namespace
@@ -133,6 +138,11 @@ being a symref to point at the current branch.  Such a state
 is often called 'detached HEAD.'  See linkgit:git-checkout[1]
 for details.
 
+config::
+	Repository specific configuration file. This file is ignored
+	$GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/config" will be
+	used instead.
+
 branches::
 	A slightly deprecated way to store shorthands to be used
 	to specify a URL to 'git fetch', 'git pull' and 'git push'.
@@ -140,7 +150,10 @@ branches::
 	'name' can be given to these commands in place of
 	'repository' argument.  See the REMOTES section in
 	linkgit:git-fetch[1] for details.  This mechanism is legacy
-	and not likely to be found in modern repositories.
+	and not likely to be found in modern repositories. This
+	directory is ignored if $GIT_COMMON_DIR is set and
+	"$GIT_COMMON_DIR/branches" will be used instead.
+
 
 hooks::
 	Hooks are customization scripts used by various Git
@@ -149,7 +162,9 @@ hooks::
 	default.  To enable, the `.sample` suffix has to be
 	removed from the filename by renaming.
 	Read linkgit:githooks[5] for more details about
-	each hook.
+	each hook. This directory is ignored if $GIT_COMMON_DIR is set
+	and "$GIT_COMMON_DIR/hooks" will be used instead.
+
 
 index::
 	The current index file for the repository.  It is
@@ -157,7 +172,8 @@ index::
 
 info::
 	Additional information about the repository is recorded
-	in this directory.
+	in this directory. This directory is ignored if $GIT_COMMON_DIR
+	is set and "$GIT_COMMON_DIR/index" will be used instead.
 
 info/refs::
 	This file helps dumb transports discover what refs are
@@ -193,12 +209,16 @@ remotes::
 	when interacting with remote repositories via 'git fetch',
 	'git pull' and 'git push' commands.  See the REMOTES section
 	in linkgit:git-fetch[1] for details.  This mechanism is legacy
-	and not likely to be found in modern repositories.
+	and not likely to be found in modern repositories. This
+	directory is ignored if $GIT_COMMON_DIR is set and
+	"$GIT_COMMON_DIR/remotes" will be used instead.
 
 logs::
 	Records of changes made to refs are stored in this
 	directory.  See linkgit:git-update-ref[1]
-	for more information.
+	for more information. This directory is ignored
+	$GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/logs" will be used
+	instead.
 
 logs/refs/heads/`name`::
 	Records all changes made to the branch tip named `name`.
@@ -209,10 +229,14 @@ logs/refs/tags/`name`::
 shallow::
 	This is similar to `info/grafts` but is internally used
 	and maintained by shallow clone mechanism.  See `--depth`
-	option to linkgit:git-clone[1] and linkgit:git-fetch[1].
+	option to linkgit:git-clone[1] and linkgit:git-fetch[1]. This
+	file is ignored if $GIT_COMMON_DIR is set and
+	"$GIT_COMMON_DIR/shallow" will be used instead.
 
 modules::
-	Contains the git-repositories of the submodules.
+	Contains the git-repositories of the submodules. This
+	directory is ignored if $GIT_COMMON_DIR is set and
+	"$GIT_COMMON_DIR/modules" will be used instead.
 
 SEE ALSO
 --------
diff --git a/cache.h b/cache.h
index 0f5f44f..9182427 100644
--- a/cache.h
+++ b/cache.h
@@ -347,6 +347,7 @@ static inline enum object_type object_type(unsigned int mode)
 
 /* Double-check local_repo_env below if you add to this list. */
 #define GIT_DIR_ENVIRONMENT "GIT_DIR"
+#define GIT_COMMON_DIR_ENVIRONMENT "GIT_COMMON_DIR"
 #define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE"
 #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
 #define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX"
@@ -400,6 +401,7 @@ extern int is_inside_git_dir(void);
 extern char *git_work_tree_cfg;
 extern int is_inside_work_tree(void);
 extern const char *get_git_dir(void);
+extern const char *get_git_common_dir(void);
 extern int is_git_directory(const char *path);
 extern char *get_object_directory(void);
 extern char *get_index_file(void);
@@ -585,7 +587,7 @@ extern int fsync_object_files;
 extern int core_preload_index;
 extern int core_apply_sparse_checkout;
 extern int precomposed_unicode;
-extern int git_db_env, git_index_env, git_graft_env;
+extern int git_db_env, git_index_env, git_graft_env, git_common_dir_env;
 
 /*
  * The character that begins a commented line in user-editable file
diff --git a/environment.c b/environment.c
index f513479..c998120 100644
--- a/environment.c
+++ b/environment.c
@@ -80,9 +80,9 @@ static char *work_tree;
 static const char *namespace;
 static size_t namespace_len;
 
-static const char *git_dir;
+static const char *git_dir, *git_common_dir;
 static char *git_object_dir, *git_index_file, *git_graft_file;
-int git_db_env, git_index_env, git_graft_env;
+int git_db_env, git_index_env, git_graft_env, git_common_dir_env;
 
 /*
  * Repository-local GIT_* environment variables; see cache.h for details.
@@ -134,10 +134,16 @@ static void setup_git_env(void)
 		git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
 	gitfile = read_gitfile(git_dir);
 	git_dir = xstrdup(gitfile ? gitfile : git_dir);
+	git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
+	if (git_common_dir) {
+		git_common_dir_env = 1;
+		git_common_dir = xstrdup(git_common_dir);
+	} else
+		git_common_dir = git_dir;
 	git_object_dir = getenv(DB_ENVIRONMENT);
 	if (!git_object_dir) {
-		git_object_dir = xmalloc(strlen(git_dir) + 9);
-		sprintf(git_object_dir, "%s/objects", git_dir);
+		git_object_dir = xmalloc(strlen(git_common_dir) + 9);
+		sprintf(git_object_dir, "%s/objects", git_common_dir);
 	} else
 		git_db_env = 1;
 	git_index_file = getenv(INDEX_ENVIRONMENT);
@@ -173,6 +179,11 @@ const char *get_git_dir(void)
 	return git_dir;
 }
 
+const char *get_git_common_dir(void)
+{
+	return git_common_dir;
+}
+
 const char *get_git_namespace(void)
 {
 	if (!namespace)
diff --git a/path.c b/path.c
index 1069ae0..8453687 100644
--- a/path.c
+++ b/path.c
@@ -90,6 +90,38 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)
 		buf->buf[newlen] = '/';
 }
 
+static const char *common_list[] = {
+	"/branches", "/hooks", "/info", "/logs", "/lost-found", "/modules",
+	"/objects", "/refs", "/remotes", "/rr-cache", "/svn",
+	"config", "gc.pid", "packed-refs", "shallow",
+	NULL
+};
+
+static void update_common_dir(struct strbuf *buf, int git_dir_len)
+{
+	char *base = buf->buf + git_dir_len;
+	const char **p;
+
+	if (is_dir_file(base, "logs", "HEAD"))
+		return;	/* keep this in $GIT_DIR */
+	for (p = common_list; *p; p++) {
+		const char *path = *p;
+		int is_dir = 0;
+		if (*path == '/') {
+			path++;
+			is_dir = 1;
+		}
+		if (is_dir && dir_prefix(base, path)) {
+			replace_dir(buf, git_dir_len, get_git_common_dir());
+			return;
+		}
+		if (!is_dir && !strcmp(base, path)) {
+			replace_dir(buf, git_dir_len, get_git_common_dir());
+			return;
+		}
+	}
+}
+
 static void adjust_git_path(struct strbuf *buf, int git_dir_len)
 {
 	const char *base = buf->buf + git_dir_len;
@@ -101,6 +133,8 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)
 			      get_index_file(), strlen(get_index_file()));
 	else if (git_db_env && dir_prefix(base, "objects"))
 		replace_dir(buf, git_dir_len + 7, get_object_directory());
+	else if (git_common_dir_env)
+		update_common_dir(buf, git_dir_len);
 }
 
 static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index 1d29901..f9a77e4 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -241,5 +241,20 @@ test_expect_success 'setup fake objects directory foo' 'mkdir foo'
 test_git_path GIT_OBJECT_DIRECTORY=foo objects foo
 test_git_path GIT_OBJECT_DIRECTORY=foo objects/foo foo/foo
 test_git_path GIT_OBJECT_DIRECTORY=foo objects2 .git/objects2
+test_expect_success 'setup common repository' 'git --git-dir=bar init'
+test_git_path GIT_COMMON_DIR=bar index                    .git/index
+test_git_path GIT_COMMON_DIR=bar HEAD                     .git/HEAD
+test_git_path GIT_COMMON_DIR=bar logs/HEAD                .git/logs/HEAD
+test_git_path GIT_COMMON_DIR=bar objects                  bar/objects
+test_git_path GIT_COMMON_DIR=bar objects/bar              bar/objects/bar
+test_git_path GIT_COMMON_DIR=bar info/exclude             bar/info/exclude
+test_git_path GIT_COMMON_DIR=bar remotes/bar              bar/remotes/bar
+test_git_path GIT_COMMON_DIR=bar branches/bar             bar/branches/bar
+test_git_path GIT_COMMON_DIR=bar logs/refs/heads/master   bar/logs/refs/heads/master
+test_git_path GIT_COMMON_DIR=bar refs/heads/master        bar/refs/heads/master
+test_git_path GIT_COMMON_DIR=bar hooks/me                 bar/hooks/me
+test_git_path GIT_COMMON_DIR=bar config                   bar/config
+test_git_path GIT_COMMON_DIR=bar packed-refs              bar/packed-refs
+test_git_path GIT_COMMON_DIR=bar shallow                  bar/shallow
 
 test_done
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 12/28] git-sh-setup.sh: use rev-parse --git-path to get $GIT_DIR/objects
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (10 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 11/28] Add new environment variable $GIT_COMMON_DIR Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:48     ` [PATCH v5 13/28] *.sh: avoid hardcoding $GIT_DIR/hooks/ Nguyễn Thái Ngọc Duy
                       ` (17 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

If $GIT_COMMON_DIR is set, $GIT_OBJECT_DIRECTORY should be
$GIT_COMMON_DIR/objects, not $GIT_DIR/objects. Just let rev-parse
--git-path handle it.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 git-sh-setup.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index fffa3c7..475ca43 100644
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -343,7 +343,7 @@ then
 		echo >&2 "Unable to determine absolute path of git directory"
 		exit 1
 	}
-	: ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"}
+	: ${GIT_OBJECT_DIRECTORY="$(git rev-parse --git-path objects)"}
 fi
 
 peel_committish () {
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 13/28] *.sh: avoid hardcoding $GIT_DIR/hooks/...
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (11 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 12/28] git-sh-setup.sh: use rev-parse --git-path to get $GIT_DIR/objects Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:48     ` [PATCH v5 14/28] git-stash: avoid hardcoding $GIT_DIR/logs/ Nguyễn Thái Ngọc Duy
                       ` (16 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

If $GIT_COMMON_DIR is set, it should be $GIT_COMMON_DIR/hooks/, not
$GIT_DIR/hooks/. Just let rev-parse --git-path handle it.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 git-am.sh                              | 22 +++++++++++-----------
 git-rebase--interactive.sh             |  6 +++---
 git-rebase--merge.sh                   |  6 ++----
 git-rebase.sh                          |  4 ++--
 templates/hooks--applypatch-msg.sample |  4 ++--
 templates/hooks--pre-applypatch.sample |  4 ++--
 6 files changed, 22 insertions(+), 24 deletions(-)

diff --git a/git-am.sh b/git-am.sh
index bbea430..10babd2 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -803,10 +803,10 @@ To restore the original branch and stop patching run \"\$cmdline --abort\"."
 		continue
 	fi
 
-	if test -x "$GIT_DIR"/hooks/applypatch-msg
+	hook="$(git rev-parse --git-path hooks/applypatch-msg)"
+	if test -x "$hook"
 	then
-		"$GIT_DIR"/hooks/applypatch-msg "$dotest/final-commit" ||
-		stop_here $this
+		"$hook" "$dotest/final-commit" || stop_here $this
 	fi
 
 	if test -f "$dotest/final-commit"
@@ -880,9 +880,10 @@ did you forget to use 'git add'?"
 		stop_here_user_resolve $this
 	fi
 
-	if test -x "$GIT_DIR"/hooks/pre-applypatch
+	hook="$(git rev-parse --git-path hooks/pre-applypatch)"
+	if test -x "$hook"
 	then
-		"$GIT_DIR"/hooks/pre-applypatch || stop_here $this
+		"$hook" || stop_here $this
 	fi
 
 	tree=$(git write-tree) &&
@@ -908,18 +909,17 @@ did you forget to use 'git add'?"
 		echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten"
 	fi
 
-	if test -x "$GIT_DIR"/hooks/post-applypatch
-	then
-		"$GIT_DIR"/hooks/post-applypatch
-	fi
+	hook="$(git rev-parse --git-path hooks/post-applypatch)"
+	test -x "$hook" && "$hook"
 
 	go_next
 done
 
 if test -s "$dotest"/rewritten; then
     git notes copy --for-rewrite=rebase < "$dotest"/rewritten
-    if test -x "$GIT_DIR"/hooks/post-rewrite; then
-	"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+    hook="$(git rev-parse --git-path hooks/post-rewrite)"
+    if test -x "$hook"; then
+	"$hook" rebase < "$dotest"/rewritten
     fi
 fi
 
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 43c19e0..dd84f9d 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -632,9 +632,9 @@ do_next () {
 		git notes copy --for-rewrite=rebase < "$rewritten_list" ||
 		true # we don't care if this copying failed
 	} &&
-	if test -x "$GIT_DIR"/hooks/post-rewrite &&
-		test -s "$rewritten_list"; then
-		"$GIT_DIR"/hooks/post-rewrite rebase < "$rewritten_list"
+	hook="$(git rev-parse --git-path hooks/post-rewrite)"
+	if test -x "$hook" && test -s "$rewritten_list"; then
+		"$hook" rebase < "$rewritten_list"
 		true # we don't care if this hook failed
 	fi &&
 	warn "Successfully rebased and updated $head_name."
diff --git a/git-rebase--merge.sh b/git-rebase--merge.sh
index e7d96de..93d7bcf 100644
--- a/git-rebase--merge.sh
+++ b/git-rebase--merge.sh
@@ -93,10 +93,8 @@ finish_rb_merge () {
 	if test -s "$state_dir"/rewritten
 	then
 		git notes copy --for-rewrite=rebase <"$state_dir"/rewritten
-		if test -x "$GIT_DIR"/hooks/post-rewrite
-		then
-			"$GIT_DIR"/hooks/post-rewrite rebase <"$state_dir"/rewritten
-		fi
+		hook="$(git rev-parse --git-path hooks/post-rewrite)"
+		test -x "$hook" && "$hook" rebase <"$state_dir"/rewritten
 	fi
 	say All done.
 }
diff --git a/git-rebase.sh b/git-rebase.sh
index 8a3efa2..b90a6d9 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -195,9 +195,9 @@ run_specific_rebase () {
 
 run_pre_rebase_hook () {
 	if test -z "$ok_to_skip_pre_rebase" &&
-	   test -x "$GIT_DIR/hooks/pre-rebase"
+	   test -x "$(git rev-parse --git-path hooks/pre-rebase)"
 	then
-		"$GIT_DIR/hooks/pre-rebase" ${1+"$@"} ||
+		"$(git rev-parse --git-path hooks/pre-rebase)" ${1+"$@"} ||
 		die "$(gettext "The pre-rebase hook refused to rebase.")"
 	fi
 }
diff --git a/templates/hooks--applypatch-msg.sample b/templates/hooks--applypatch-msg.sample
index 8b2a2fe..a5d7b84 100755
--- a/templates/hooks--applypatch-msg.sample
+++ b/templates/hooks--applypatch-msg.sample
@@ -10,6 +10,6 @@
 # To enable this hook, rename this file to "applypatch-msg".
 
 . git-sh-setup
-test -x "$GIT_DIR/hooks/commit-msg" &&
-	exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
+commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
+test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
 :
diff --git a/templates/hooks--pre-applypatch.sample b/templates/hooks--pre-applypatch.sample
index b1f187c..4142082 100755
--- a/templates/hooks--pre-applypatch.sample
+++ b/templates/hooks--pre-applypatch.sample
@@ -9,6 +9,6 @@
 # To enable this hook, rename this file to "pre-applypatch".
 
 . git-sh-setup
-test -x "$GIT_DIR/hooks/pre-commit" &&
-	exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
+precommit="$(git rev-parse --git-path hooks/pre-commit)"
+test -x "$precommit" && exec "$precommit" ${1+"$@"}
 :
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 14/28] git-stash: avoid hardcoding $GIT_DIR/logs/....
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (12 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 13/28] *.sh: avoid hardcoding $GIT_DIR/hooks/ Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:48     ` [PATCH v5 15/28] setup.c: convert is_git_directory() to use strbuf Nguyễn Thái Ngọc Duy
                       ` (15 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 git-stash.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/git-stash.sh b/git-stash.sh
index 0de9a6c..8093326 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -183,7 +183,7 @@ store_stash () {
 	fi
 
 	# Make sure the reflog for stash is kept.
-	: >>"$GIT_DIR/logs/$ref_stash"
+	: >>"$(git rev-parse --git-path logs/$ref_stash)"
 	git update-ref -m "$stash_msg" $ref_stash $w_commit
 	ret=$?
 	test $ret != 0 && test -z $quiet &&
@@ -258,7 +258,7 @@ save_stash () {
 		say "$(gettext "No local changes to save")"
 		exit 0
 	fi
-	test -f "$GIT_DIR/logs/$ref_stash" ||
+	test -f "$(git rev-parse --git-path logs/$ref_stash)" ||
 		clear_stash || die "$(gettext "Cannot initialize stash")"
 
 	create_stash "$stash_msg" $untracked
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 15/28] setup.c: convert is_git_directory() to use strbuf
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (13 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 14/28] git-stash: avoid hardcoding $GIT_DIR/logs/ Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:48     ` [PATCH v5 16/28] setup.c: detect $GIT_COMMON_DIR in is_git_directory() Nguyễn Thái Ngọc Duy
                       ` (14 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 setup.c | 37 +++++++++++++++++++++----------------
 1 file changed, 21 insertions(+), 16 deletions(-)

diff --git a/setup.c b/setup.c
index 6c3f85f..4994437 100644
--- a/setup.c
+++ b/setup.c
@@ -184,31 +184,36 @@ void verify_non_filename(const char *prefix, const char *arg)
  */
 int is_git_directory(const char *suspect)
 {
-	char path[PATH_MAX];
-	size_t len = strlen(suspect);
+	struct strbuf path = STRBUF_INIT;
+	int ret = 0;
+	size_t len;
 
-	if (PATH_MAX <= len + strlen("/objects"))
-		die("Too long path: %.*s", 60, suspect);
-	strcpy(path, suspect);
+	strbuf_addstr(&path, suspect);
+	len = path.len;
 	if (getenv(DB_ENVIRONMENT)) {
 		if (access(getenv(DB_ENVIRONMENT), X_OK))
-			return 0;
+			goto done;
 	}
 	else {
-		strcpy(path + len, "/objects");
-		if (access(path, X_OK))
-			return 0;
+		strbuf_addstr(&path, "/objects");
+		if (access(path.buf, X_OK))
+			goto done;
 	}
 
-	strcpy(path + len, "/refs");
-	if (access(path, X_OK))
-		return 0;
+	strbuf_setlen(&path, len);
+	strbuf_addstr(&path, "/refs");
+	if (access(path.buf, X_OK))
+		goto done;
 
-	strcpy(path + len, "/HEAD");
-	if (validate_headref(path))
-		return 0;
+	strbuf_setlen(&path, len);
+	strbuf_addstr(&path, "/HEAD");
+	if (validate_headref(path.buf))
+		goto done;
 
-	return 1;
+	ret = 1;
+done:
+	strbuf_release(&path);
+	return ret;
 }
 
 int is_inside_git_dir(void)
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 16/28] setup.c: detect $GIT_COMMON_DIR in is_git_directory()
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (14 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 15/28] setup.c: convert is_git_directory() to use strbuf Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:48     ` [PATCH v5 17/28] setup.c: convert check_repository_format_gently to use strbuf Nguyễn Thái Ngọc Duy
                       ` (13 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

If the file "$GIT_DIR/commondir" exists, it contains the value of
$GIT_COMMON_DIR.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/gitrepository-layout.txt |  7 ++++++
 setup.c                                | 43 +++++++++++++++++++++++++++++-----
 2 files changed, 44 insertions(+), 6 deletions(-)

diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index 556da09..c6f3649 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -233,6 +233,13 @@ shallow::
 	file is ignored if $GIT_COMMON_DIR is set and
 	"$GIT_COMMON_DIR/shallow" will be used instead.
 
+commondir::
+	If this file exists, $GIT_COMMON_DIR (see linkgit:git[1]) will
+	be set to the path specified in this file if it is not
+	explicitly set. If the specified path is relative, it is
+	relative to $GIT_DIR. The repository with commondir is
+	incomplete without the repository pointed by "commondir".
+
 modules::
 	Contains the git-repositories of the submodules. This
 	directory is ignored if $GIT_COMMON_DIR is set and
diff --git a/setup.c b/setup.c
index 4994437..7e5b334 100644
--- a/setup.c
+++ b/setup.c
@@ -170,6 +170,33 @@ void verify_non_filename(const char *prefix, const char *arg)
 	    "'git <command> [<revision>...] -- [<file>...]'", arg);
 }
 
+static void get_common_dir(struct strbuf *sb, const char *gitdir)
+{
+	struct strbuf data = STRBUF_INIT;
+	struct strbuf path = STRBUF_INIT;
+	const char *git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
+	if (git_common_dir) {
+		strbuf_addstr(sb, git_common_dir);
+		return;
+	}
+	strbuf_addf(&path, "%s/commondir", gitdir);
+	if (file_exists(path.buf)) {
+		if (strbuf_read_file(&data, path.buf, 0) <= 0)
+			die_errno(_("failed to read %s"), path.buf);
+		while (data.len && (data.buf[data.len - 1] == '\n' ||
+				    data.buf[data.len - 1] == '\r'))
+			data.len--;
+		data.buf[data.len] = '\0';
+		strbuf_reset(&path);
+		if (!is_absolute_path(data.buf))
+			strbuf_addf(&path, "%s/", gitdir);
+		strbuf_addbuf(&path, &data);
+		strbuf_addstr(sb, real_path(path.buf));
+	} else
+		strbuf_addstr(sb, gitdir);
+	strbuf_release(&data);
+	strbuf_release(&path);
+}
 
 /*
  * Test if it looks like we're at a git directory.
@@ -188,13 +215,22 @@ int is_git_directory(const char *suspect)
 	int ret = 0;
 	size_t len;
 
-	strbuf_addstr(&path, suspect);
+	/* Check worktree-related signatures */
+	strbuf_addf(&path, "%s/HEAD", suspect);
+	if (validate_headref(path.buf))
+		goto done;
+
+	strbuf_reset(&path);
+	get_common_dir(&path, suspect);
 	len = path.len;
+
+	/* Check non-worktree-related signatures */
 	if (getenv(DB_ENVIRONMENT)) {
 		if (access(getenv(DB_ENVIRONMENT), X_OK))
 			goto done;
 	}
 	else {
+		strbuf_setlen(&path, len);
 		strbuf_addstr(&path, "/objects");
 		if (access(path.buf, X_OK))
 			goto done;
@@ -205,11 +241,6 @@ int is_git_directory(const char *suspect)
 	if (access(path.buf, X_OK))
 		goto done;
 
-	strbuf_setlen(&path, len);
-	strbuf_addstr(&path, "/HEAD");
-	if (validate_headref(path.buf))
-		goto done;
-
 	ret = 1;
 done:
 	strbuf_release(&path);
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 17/28] setup.c: convert check_repository_format_gently to use strbuf
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (15 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 16/28] setup.c: detect $GIT_COMMON_DIR in is_git_directory() Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:48     ` [PATCH v5 18/28] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently() Nguyễn Thái Ngọc Duy
                       ` (12 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 setup.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/setup.c b/setup.c
index 7e5b334..5085ab1 100644
--- a/setup.c
+++ b/setup.c
@@ -288,7 +288,9 @@ void setup_work_tree(void)
 
 static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 {
-	char repo_config[PATH_MAX+1];
+	struct strbuf sb = STRBUF_INIT;
+	const char *repo_config;
+	int ret = 0;
 
 	/*
 	 * git_config() can't be used here because it calls git_pathdup()
@@ -299,7 +301,8 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 	 * Use a gentler version of git_config() to check if this repo
 	 * is a good one.
 	 */
-	snprintf(repo_config, PATH_MAX, "%s/config", gitdir);
+	strbuf_addf(&sb, "%s/config", gitdir);
+	repo_config = sb.buf;
 	git_config_early(check_repository_format_version, NULL, repo_config);
 	if (GIT_REPO_VERSION < repository_format_version) {
 		if (!nongit_ok)
@@ -309,9 +312,10 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 			GIT_REPO_VERSION, repository_format_version);
 		warning("Please upgrade Git");
 		*nongit_ok = -1;
-		return -1;
+		ret = -1;
 	}
-	return 0;
+	strbuf_release(&sb);
+	return ret;
 }
 
 /*
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 18/28] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently()
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (16 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 17/28] setup.c: convert check_repository_format_gently to use strbuf Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:48     ` [PATCH v5 19/28] setup.c: support multi-checkout repo setup Nguyễn Thái Ngọc Duy
                       ` (11 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 setup.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/setup.c b/setup.c
index 5085ab1..42849f3 100644
--- a/setup.c
+++ b/setup.c
@@ -292,6 +292,10 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 	const char *repo_config;
 	int ret = 0;
 
+	get_common_dir(&sb, gitdir);
+	strbuf_addstr(&sb, "/config");
+	repo_config = sb.buf;
+
 	/*
 	 * git_config() can't be used here because it calls git_pathdup()
 	 * to get $GIT_CONFIG/config. That call will make setup_git_env()
@@ -301,8 +305,6 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 	 * Use a gentler version of git_config() to check if this repo
 	 * is a good one.
 	 */
-	strbuf_addf(&sb, "%s/config", gitdir);
-	repo_config = sb.buf;
 	git_config_early(check_repository_format_version, NULL, repo_config);
 	if (GIT_REPO_VERSION < repository_format_version) {
 		if (!nongit_ok)
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 19/28] setup.c: support multi-checkout repo setup
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (17 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 18/28] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently() Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:48     ` [PATCH v5 20/28] wrapper.c: wrapper to open a file, fprintf then close Nguyễn Thái Ngọc Duy
                       ` (10 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

The repo setup procedure is updated to detect $GIT_DIR/commondir and
set $GIT_COMMON_DIR properly.

The core.worktree is ignored when $GIT_COMMON_DIR is set. This is
because the config file is shared in multi-checkout setup, but
checkout directories _are_ different. Making core.worktree effective
in all checkouts mean it's back to a single checkout.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/config.txt        |  2 ++
 Documentation/git-rev-parse.txt |  3 ++
 builtin/rev-parse.c             |  4 +++
 cache.h                         |  1 +
 environment.c                   |  8 ++---
 setup.c                         | 33 +++++++++++++-----
 t/t1501-worktree.sh             | 76 +++++++++++++++++++++++++++++++++++++++++
 t/t1510-repo-setup.sh           |  1 +
 trace.c                         |  1 +
 9 files changed, 115 insertions(+), 14 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 5f4d793..313d4b3 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -381,6 +381,8 @@ false), while all other repositories are assumed to be bare (bare
 
 core.worktree::
 	Set the path to the root of the working tree.
+	If GIT_COMMON_DIR environment variable is set, core.worktree
+	is ignored and not used for determining the root of working tree.
 	This can be overridden by the GIT_WORK_TREE environment
 	variable and the '--work-tree' command line option.
 	The value can be an absolute path or relative to the path to
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 46020d9..8a9cac8 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -215,6 +215,9 @@ If `$GIT_DIR` is not defined and the current directory
 is not detected to lie in a Git repository or work tree
 print a message to stderr and exit with nonzero status.
 
+--git-common-dir::
+	Show `$GIT_COMMON_DIR` if defined, else `$GIT_DIR`.
+
 --is-inside-git-dir::
 	When the current working directory is below the repository
 	directory print "true", otherwise "false".
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index e50bc65..c7057ce 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -744,6 +744,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
 				printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : "");
 				continue;
 			}
+			if (!strcmp(arg, "--git-common-dir")) {
+				puts(get_git_common_dir());
+				continue;
+			}
 			if (!strcmp(arg, "--resolve-git-dir")) {
 				const char *gitdir = resolve_gitdir(argv[i+1]);
 				if (!gitdir)
diff --git a/cache.h b/cache.h
index 9182427..992e241 100644
--- a/cache.h
+++ b/cache.h
@@ -407,6 +407,7 @@ extern char *get_object_directory(void);
 extern char *get_index_file(void);
 extern char *get_graft_file(void);
 extern int set_git_dir(const char *path);
+extern int get_common_dir(struct strbuf *sb, const char *gitdir);
 extern const char *get_git_namespace(void);
 extern const char *strip_namespace(const char *namespaced_ref);
 extern const char *get_git_work_tree(void);
diff --git a/environment.c b/environment.c
index c998120..0999fc1 100644
--- a/environment.c
+++ b/environment.c
@@ -126,6 +126,7 @@ static char *expand_namespace(const char *raw_namespace)
 
 static void setup_git_env(void)
 {
+	struct strbuf sb = STRBUF_INIT;
 	const char *gitfile;
 	const char *shallow_file;
 
@@ -134,12 +135,9 @@ static void setup_git_env(void)
 		git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
 	gitfile = read_gitfile(git_dir);
 	git_dir = xstrdup(gitfile ? gitfile : git_dir);
-	git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
-	if (git_common_dir) {
+	if (get_common_dir(&sb, git_dir))
 		git_common_dir_env = 1;
-		git_common_dir = xstrdup(git_common_dir);
-	} else
-		git_common_dir = git_dir;
+	git_common_dir = strbuf_detach(&sb, NULL);
 	git_object_dir = getenv(DB_ENVIRONMENT);
 	if (!git_object_dir) {
 		git_object_dir = xmalloc(strlen(git_common_dir) + 9);
diff --git a/setup.c b/setup.c
index 42849f3..40ce191 100644
--- a/setup.c
+++ b/setup.c
@@ -170,14 +170,15 @@ void verify_non_filename(const char *prefix, const char *arg)
 	    "'git <command> [<revision>...] -- [<file>...]'", arg);
 }
 
-static void get_common_dir(struct strbuf *sb, const char *gitdir)
+int get_common_dir(struct strbuf *sb, const char *gitdir)
 {
 	struct strbuf data = STRBUF_INIT;
 	struct strbuf path = STRBUF_INIT;
 	const char *git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
+	int ret = 0;
 	if (git_common_dir) {
 		strbuf_addstr(sb, git_common_dir);
-		return;
+		return 1;
 	}
 	strbuf_addf(&path, "%s/commondir", gitdir);
 	if (file_exists(path.buf)) {
@@ -192,10 +193,12 @@ static void get_common_dir(struct strbuf *sb, const char *gitdir)
 			strbuf_addf(&path, "%s/", gitdir);
 		strbuf_addbuf(&path, &data);
 		strbuf_addstr(sb, real_path(path.buf));
+		ret = 1;
 	} else
 		strbuf_addstr(sb, gitdir);
 	strbuf_release(&data);
 	strbuf_release(&path);
+	return ret;
 }
 
 /*
@@ -286,13 +289,26 @@ void setup_work_tree(void)
 	initialized = 1;
 }
 
+static int check_repo_format(const char *var, const char *value, void *cb)
+{
+	if (strcmp(var, "core.repositoryformatversion") == 0)
+		repository_format_version = git_config_int(var, value);
+	else if (strcmp(var, "core.sharedrepository") == 0)
+		shared_repository = git_config_perm(var, value);
+	return 0;
+}
+
 static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 {
 	struct strbuf sb = STRBUF_INIT;
 	const char *repo_config;
+	config_fn_t fn;
 	int ret = 0;
 
-	get_common_dir(&sb, gitdir);
+	if (get_common_dir(&sb, gitdir))
+		fn = check_repo_format;
+	else
+		fn = check_repository_format_version;
 	strbuf_addstr(&sb, "/config");
 	repo_config = sb.buf;
 
@@ -305,7 +321,7 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 	 * Use a gentler version of git_config() to check if this repo
 	 * is a good one.
 	 */
-	git_config_early(check_repository_format_version, NULL, repo_config);
+	git_config_early(fn, NULL, repo_config);
 	if (GIT_REPO_VERSION < repository_format_version) {
 		if (!nongit_ok)
 			die ("Expected git repo version <= %d, found %d",
@@ -777,11 +793,10 @@ int git_config_perm(const char *var, const char *value)
 
 int check_repository_format_version(const char *var, const char *value, void *cb)
 {
-	if (strcmp(var, "core.repositoryformatversion") == 0)
-		repository_format_version = git_config_int(var, value);
-	else if (strcmp(var, "core.sharedrepository") == 0)
-		shared_repository = git_config_perm(var, value);
-	else if (strcmp(var, "core.bare") == 0) {
+	int ret = check_repo_format(var, value, cb);
+	if (ret)
+		return ret;
+	if (strcmp(var, "core.bare") == 0) {
 		is_bare_repository_cfg = git_config_bool(var, value);
 		if (is_bare_repository_cfg == 1)
 			inside_work_tree = -1;
diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh
index 8f36aa9..2ac4424 100755
--- a/t/t1501-worktree.sh
+++ b/t/t1501-worktree.sh
@@ -346,4 +346,80 @@ test_expect_success 'relative $GIT_WORK_TREE and git subprocesses' '
 	test_cmp expected actual
 '
 
+test_expect_success 'Multi-worktree setup' '
+	mkdir work &&
+	mkdir -p repo.git/repos/foo &&
+	cp repo.git/HEAD repo.git/index repo.git/repos/foo &&
+	sane_unset GIT_DIR GIT_CONFIG GIT_WORK_TREE
+'
+
+test_expect_success 'GIT_DIR set (1)' '
+	echo "gitdir: repo.git/repos/foo" >gitfile &&
+	echo ../.. >repo.git/repos/foo/commondir &&
+	(
+		cd work &&
+		GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
+		echo "$TRASH_DIRECTORY/repo.git" >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'GIT_DIR set (2)' '
+	echo "gitdir: repo.git/repos/foo" >gitfile &&
+	echo "$TRASH_DIRECTORY/repo.git" >repo.git/repos/foo/commondir &&
+	(
+		cd work &&
+		GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
+		echo "$TRASH_DIRECTORY/repo.git" >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'Auto discovery' '
+	echo "gitdir: repo.git/repos/foo" >.git &&
+	echo ../.. >repo.git/repos/foo/commondir &&
+	(
+		cd work &&
+		git rev-parse --git-common-dir >actual &&
+		echo "$TRASH_DIRECTORY/repo.git" >expect &&
+		test_cmp expect actual &&
+		echo haha >data1 &&
+		git add data1 &&
+		git ls-files --full-name :/ | grep data1 >actual &&
+		echo work/data1 >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '$GIT_DIR/common overrides core.worktree' '
+	mkdir elsewhere &&
+	git --git-dir=repo.git config core.worktree "$TRASH_DIRECTORY/elsewhere" &&
+	echo "gitdir: repo.git/repos/foo" >.git &&
+	echo ../.. >repo.git/repos/foo/commondir &&
+	(
+		cd work &&
+		git rev-parse --git-common-dir >actual &&
+		echo "$TRASH_DIRECTORY/repo.git" >expect &&
+		test_cmp expect actual &&
+		echo haha >data2 &&
+		git add data2 &&
+		git ls-files --full-name :/ | grep data2 >actual &&
+		echo work/data2 >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '$GIT_WORK_TREE overrides $GIT_DIR/common' '
+	echo "gitdir: repo.git/repos/foo" >.git &&
+	echo ../.. >repo.git/repos/foo/commondir &&
+	(
+		cd work &&
+		echo haha >data3 &&
+		git --git-dir=../.git --work-tree=. add data3 &&
+		git ls-files --full-name -- :/ | grep data3 >actual &&
+		echo data3 >expect &&
+		test_cmp expect actual
+	)
+'
+
 test_done
diff --git a/t/t1510-repo-setup.sh b/t/t1510-repo-setup.sh
index cf2ee78..c211ef5 100755
--- a/t/t1510-repo-setup.sh
+++ b/t/t1510-repo-setup.sh
@@ -106,6 +106,7 @@ setup_env () {
 expect () {
 	cat >"$1/expected" <<-EOF
 	setup: git_dir: $2
+	setup: git_common_dir: $2
 	setup: worktree: $3
 	setup: cwd: $4
 	setup: prefix: $5
diff --git a/trace.c b/trace.c
index 08180a9..a594761 100644
--- a/trace.c
+++ b/trace.c
@@ -173,6 +173,7 @@ void trace_repo_setup(const char *prefix)
 		prefix = "(null)";
 
 	trace_printf_key(key, "setup: git_dir: %s\n", quote_crnl(get_git_dir()));
+	trace_printf_key(key, "setup: git_common_dir: %s\n", quote_crnl(get_git_common_dir()));
 	trace_printf_key(key, "setup: worktree: %s\n", quote_crnl(git_work_tree));
 	trace_printf_key(key, "setup: cwd: %s\n", quote_crnl(cwd));
 	trace_printf_key(key, "setup: prefix: %s\n", quote_crnl(prefix));
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 20/28] wrapper.c: wrapper to open a file, fprintf then close
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (18 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 19/28] setup.c: support multi-checkout repo setup Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:48     ` [PATCH v5 21/28] use new wrapper write_file() for simple file writing Nguyễn Thái Ngọc Duy
                       ` (9 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 cache.h   |  2 ++
 wrapper.c | 31 +++++++++++++++++++++++++++++++
 2 files changed, 33 insertions(+)

diff --git a/cache.h b/cache.h
index 992e241..979ac6e 100644
--- a/cache.h
+++ b/cache.h
@@ -1239,6 +1239,8 @@ static inline ssize_t write_str_in_full(int fd, const char *str)
 {
 	return write_in_full(fd, str, strlen(str));
 }
+__attribute__((format (printf,3,4)))
+extern int write_file(const char *path, int fatal, const char *fmt, ...);
 
 /* pager.c */
 extern void setup_pager(void);
diff --git a/wrapper.c b/wrapper.c
index 0cc5636..5ad628c 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -455,3 +455,34 @@ struct passwd *xgetpwuid_self(void)
 		    errno ? strerror(errno) : _("no such user"));
 	return pw;
 }
+
+int write_file(const char *path, int fatal, const char *fmt, ...)
+{
+	struct strbuf sb = STRBUF_INIT;
+	va_list params;
+	int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);
+	if (fd < 0) {
+		if (fatal)
+			die_errno(_("could not open %s for writing"), path);
+		return -1;
+	}
+	va_start(params, fmt);
+	strbuf_vaddf(&sb, fmt, params);
+	va_end(params);
+	if (write_in_full(fd, sb.buf, sb.len) != sb.len) {
+		int err = errno;
+		close(fd);
+		strbuf_release(&sb);
+		errno = err;
+		if (fatal)
+			die_errno(_("could not write to %s"), path);
+		return -1;
+	}
+	strbuf_release(&sb);
+	if (close(fd)) {
+		if (fatal)
+			die_errno(_("could not close %s"), path);
+		return -1;
+	}
+	return 0;
+}
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 21/28] use new wrapper write_file() for simple file writing
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (19 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 20/28] wrapper.c: wrapper to open a file, fprintf then close Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:48     ` [PATCH v5 22/28] checkout: support checking out into a new working directory Nguyễn Thái Ngọc Duy
                       ` (8 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

This fixes common problems in these code about error handling,
forgetting to close the file handle after fprintf() fails, or not
printing out the error string..

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/branch.c  |  4 +---
 builtin/init-db.c |  7 +------
 daemon.c          | 11 +----------
 submodule.c       |  9 ++-------
 transport.c       |  8 +++-----
 5 files changed, 8 insertions(+), 31 deletions(-)

diff --git a/builtin/branch.c b/builtin/branch.c
index b4d7716..3eebdbc 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -754,7 +754,6 @@ static const char edit_description[] = "BRANCH_DESCRIPTION";
 
 static int edit_branch_description(const char *branch_name)
 {
-	FILE *fp;
 	int status;
 	struct strbuf buf = STRBUF_INIT;
 	struct strbuf name = STRBUF_INIT;
@@ -767,8 +766,7 @@ static int edit_branch_description(const char *branch_name)
 		    "  %s\n"
 		    "Lines starting with '%c' will be stripped.\n",
 		    branch_name, comment_line_char);
-	fp = fopen(git_path(edit_description), "w");
-	if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) {
+	if (write_file(git_path(edit_description), 0, "%s", buf.buf)) {
 		strbuf_release(&buf);
 		return error(_("could not write branch description template: %s"),
 			     strerror(errno));
diff --git a/builtin/init-db.c b/builtin/init-db.c
index c7c76bb..081e512 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -342,7 +342,6 @@ int set_git_dir_init(const char *git_dir, const char *real_git_dir,
 static void separate_git_dir(const char *git_dir)
 {
 	struct stat st;
-	FILE *fp;
 
 	if (!stat(git_link, &st)) {
 		const char *src;
@@ -358,11 +357,7 @@ static void separate_git_dir(const char *git_dir)
 			die_errno(_("unable to move %s to %s"), src, git_dir);
 	}
 
-	fp = fopen(git_link, "w");
-	if (!fp)
-		die(_("Could not create git link %s"), git_link);
-	fprintf(fp, "gitdir: %s\n", git_dir);
-	fclose(fp);
+	write_file(git_link, 1, "gitdir: %s\n", git_dir);
 }
 
 int init_db(const char *template_dir, unsigned int flags)
diff --git a/daemon.c b/daemon.c
index 503e039..b880d30 100644
--- a/daemon.c
+++ b/daemon.c
@@ -1122,15 +1122,6 @@ static void daemonize(void)
 }
 #endif
 
-static void store_pid(const char *path)
-{
-	FILE *f = fopen(path, "w");
-	if (!f)
-		die_errno("cannot open pid file '%s'", path);
-	if (fprintf(f, "%"PRIuMAX"\n", (uintmax_t) getpid()) < 0 || fclose(f) != 0)
-		die_errno("failed to write pid file '%s'", path);
-}
-
 static int serve(struct string_list *listen_addr, int listen_port,
     struct credentials *cred)
 {
@@ -1339,7 +1330,7 @@ int main(int argc, char **argv)
 		sanitize_stdfds();
 
 	if (pid_file)
-		store_pid(pid_file);
+		write_file(pid_file, 1, "%"PRIuMAX"\n", (uintmax_t) getpid());
 
 	/* prepare argv for serving-processes */
 	cld_argv = xmalloc(sizeof (char *) * (argc + 2));
diff --git a/submodule.c b/submodule.c
index 613857e..fe5748d 100644
--- a/submodule.c
+++ b/submodule.c
@@ -1135,16 +1135,11 @@ void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir)
 	struct strbuf file_name = STRBUF_INIT;
 	struct strbuf rel_path = STRBUF_INIT;
 	const char *real_work_tree = xstrdup(real_path(work_tree));
-	FILE *fp;
 
 	/* Update gitfile */
 	strbuf_addf(&file_name, "%s/.git", work_tree);
-	fp = fopen(file_name.buf, "w");
-	if (!fp)
-		die(_("Could not create git link %s"), file_name.buf);
-	fprintf(fp, "gitdir: %s\n", relative_path(git_dir, real_work_tree,
-						  &rel_path));
-	fclose(fp);
+	write_file(file_name.buf, 1, "gitdir: %s\n",
+		   relative_path(git_dir, real_work_tree, &rel_path));
 
 	/* Update core.worktree setting */
 	strbuf_reset(&file_name);
diff --git a/transport.c b/transport.c
index ca7bb44..2df8a15 100644
--- a/transport.c
+++ b/transport.c
@@ -294,7 +294,6 @@ static int write_one_ref(const char *name, const unsigned char *sha1,
 {
 	struct strbuf *buf = data;
 	int len = buf->len;
-	FILE *f;
 
 	/* when called via for_each_ref(), flags is non-zero */
 	if (flags && !starts_with(name, "refs/heads/") &&
@@ -303,10 +302,9 @@ static int write_one_ref(const char *name, const unsigned char *sha1,
 
 	strbuf_addstr(buf, name);
 	if (safe_create_leading_directories(buf->buf) ||
-			!(f = fopen(buf->buf, "w")) ||
-			fprintf(f, "%s\n", sha1_to_hex(sha1)) < 0 ||
-			fclose(f))
-		return error("problems writing temporary file %s", buf->buf);
+	    write_file(buf->buf, 0, "%s\n", sha1_to_hex(sha1)))
+		return error("problems writing temporary file %s: %s",
+			     buf->buf, strerror(errno));
 	strbuf_setlen(buf, len);
 	return 0;
 }
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 22/28] checkout: support checking out into a new working directory
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (20 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 21/28] use new wrapper write_file() for simple file writing Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:48     ` [PATCH v5 23/28] checkout: clean up half-prepared directories in --to mode Nguyễn Thái Ngọc Duy
                       ` (7 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

"git checkout --to" sets up a new working directory with a .git file
pointing to $GIT_DIR/repos/<id>. It then executes "git checkout" again
on the new worktree with the same arguments except "--to" is taken
out. The second checkout execution, which is not contaminated with any
info from the current repository, will actually check out and
everything that normal "git checkout" does.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt         | 34 +++++++++++++
 Documentation/git.txt                  |  3 +-
 Documentation/gitrepository-layout.txt |  7 +++
 builtin/checkout.c                     | 93 +++++++++++++++++++++++++++++++++-
 path.c                                 |  2 +-
 t/t2025-checkout-to.sh (new +x)        | 48 ++++++++++++++++++
 6 files changed, 183 insertions(+), 4 deletions(-)
 create mode 100755 t/t2025-checkout-to.sh

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 33ad2ad..fcf73b2 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -225,6 +225,13 @@ This means that you can use `git checkout -p` to selectively discard
 edits from your current working tree. See the ``Interactive Mode''
 section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
 
+--to=<path>::
+	Check out a new branch in a separate working directory at
+	`<path>`. A new working directory is linked to the current
+	repository, sharing everything except working directory
+	specific files such as HEAD, index... See "MULTIPLE CHECKOUT
+	MODE" section for more information.
+
 <branch>::
 	Branch to checkout; if it refers to a branch (i.e., a name that,
 	when prepended with "refs/heads/", is a valid ref), then that
@@ -388,6 +395,33 @@ $ git reflog -2 HEAD # or
 $ git log -g -2 HEAD
 ------------
 
+MULTIPLE CHECKOUT MODE
+-------------------------------
+Normally a working directory is attached to repository. When "git
+checkout --to" is used, a new working directory is attached to the
+current repository. This new working directory is called "linked
+checkout" as compared to the "main checkout" prepared by "git init" or
+"git clone". A repository has one main checkout and zero or more
+linked checkouts.
+
+All checkouts share the same repository. Linked checkouts see the
+repository a bit different from the main checkout. When the checkout
+"new" reads the path $GIT_DIR/HEAD for example, the actual path
+returned could be $GIT_DIR/repos/new/HEAD. This ensures checkouts
+won't step on each other.
+
+Each linked checkout has a private space in $GIT_DIR/repos, usually
+named after the base name of the working directory with a number added
+to make it unique. The linked checkout's $GIT_DIR points to this
+private space while $GIT_COMMON_DIR points to the main checkout's
+$GIT_DIR. These settings are done by "git checkout --to".
+
+Because in this mode $GIT_DIR becomes a lightweight virtual file
+system where a path could be rewritten to some place else, accessing
+$GIT_DIR from scripts should use `git rev-parse --git-path` to resolve
+a path instead of using it directly unless the path is known to be
+private to the working directory.
+
 EXAMPLES
 --------
 
diff --git a/Documentation/git.txt b/Documentation/git.txt
index b094b1f..bdb9b0f 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -777,7 +777,8 @@ Git so take care if using Cogito etc.
 	If this variable is set to a path, non-worktree files that are
 	normally in $GIT_DIR will be taken from this path
 	instead. Worktree-specific files such as HEAD or index are
-	taken from $GIT_DIR. See linkgit:gitrepository-layout[5] for
+	taken from $GIT_DIR. See linkgit:gitrepository-layout[5] and
+	the section 'MULTIPLE CHECKOUT MODE' in linkgit:checkout[1]
 	details. This variable has lower precedence than other path
 	variables such as GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY...
 
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index c6f3649..2206f35 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -245,6 +245,13 @@ modules::
 	directory is ignored if $GIT_COMMON_DIR is set and
 	"$GIT_COMMON_DIR/modules" will be used instead.
 
+repos::
+	Contains worktree specific information of linked
+	checkouts. Each subdirectory contains the worktree-related
+	part of a linked checkout. This directory is ignored
+	$GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/repos" will be
+	used instead.
+
 SEE ALSO
 --------
 linkgit:git-init[1],
diff --git a/builtin/checkout.c b/builtin/checkout.c
index efb5e2f..aa4ffd1 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -48,6 +48,10 @@ struct checkout_opts {
 	const char *prefix;
 	struct pathspec pathspec;
 	struct tree *source_tree;
+
+	const char *new_worktree;
+	const char **saved_argv;
+	int new_worktree_mode;
 };
 
 static int post_checkout_hook(struct commit *old, struct commit *new,
@@ -250,6 +254,9 @@ static int checkout_paths(const struct checkout_opts *opts,
 		die(_("Cannot update paths and switch to branch '%s' at the same time."),
 		    opts->new_branch);
 
+	if (opts->new_worktree)
+		die(_("'%s' cannot be used with updating paths"), "--to");
+
 	if (opts->patch_mode)
 		return run_add_interactive(revision, "--patch=checkout",
 					   &opts->pathspec);
@@ -486,7 +493,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
 			topts.dir->flags |= DIR_SHOW_IGNORED;
 			setup_standard_excludes(topts.dir);
 		}
-		tree = parse_tree_indirect(old->commit ?
+		tree = parse_tree_indirect(old->commit && !opts->new_worktree_mode ?
 					   old->commit->object.sha1 :
 					   EMPTY_TREE_SHA1_BIN);
 		init_tree_desc(&trees[0], tree->buffer, tree->size);
@@ -797,7 +804,8 @@ static int switch_branches(const struct checkout_opts *opts,
 		return ret;
 	}
 
-	if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
+	if (!opts->quiet && !old.path && old.commit &&
+	    new->commit != old.commit && !opts->new_worktree_mode)
 		orphaned_commit_warning(old.commit, new->commit);
 
 	update_refs_for_switch(opts, &old, new);
@@ -807,6 +815,74 @@ static int switch_branches(const struct checkout_opts *opts,
 	return ret || writeout_error;
 }
 
+static int prepare_linked_checkout(const struct checkout_opts *opts,
+				   struct branch_info *new)
+{
+	struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
+	struct strbuf sb = STRBUF_INIT;
+	const char *path = opts->new_worktree, *name;
+	struct stat st;
+	struct child_process cp;
+	int counter = 0, len;
+
+	if (!new->commit)
+		die(_("no branch specified"));
+
+	len = strlen(path);
+	while (len && is_dir_sep(path[len - 1]))
+		len--;
+
+	for (name = path + len - 1; name > path; name--)
+		if (is_dir_sep(*name)) {
+			name++;
+			break;
+		}
+	strbuf_addstr(&sb_repo,
+		      git_path("repos/%.*s", (int)(path + len - name), name));
+	len = sb_repo.len;
+	if (safe_create_leading_directories_const(sb_repo.buf))
+		die_errno(_("could not create leading directories of '%s'"),
+			  sb_repo.buf);
+	while (!stat(sb_repo.buf, &st)) {
+		counter++;
+		strbuf_setlen(&sb_repo, len);
+		strbuf_addf(&sb_repo, "%d", counter);
+	}
+	name = strrchr(sb_repo.buf, '/') + 1;
+	if (mkdir(sb_repo.buf, 0777))
+		die_errno(_("could not create directory of '%s'"), sb_repo.buf);
+
+	strbuf_addf(&sb_git, "%s/.git", path);
+	if (safe_create_leading_directories_const(sb_git.buf))
+		die_errno(_("could not create leading directories of '%s'"),
+			  sb_git.buf);
+
+	write_file(sb_git.buf, 1, "gitdir: %s/repos/%s\n",
+		   real_path(get_git_common_dir()), name);
+	/*
+	 * This is to keep resolve_ref() happy. We need a valid HEAD
+	 * or is_git_directory() will reject the directory. Any valid
+	 * value would do because this value will be ignored and
+	 * replaced at the next (real) checkout.
+	 */
+	strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
+	write_file(sb.buf, 1, "%s\n", sha1_to_hex(new->commit->object.sha1));
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
+	write_file(sb.buf, 1, "../..\n");
+
+	if (!opts->quiet)
+		fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
+
+	setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1);
+	setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1);
+	setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1);
+	memset(&cp, 0, sizeof(cp));
+	cp.git_cmd = 1;
+	cp.argv = opts->saved_argv;
+	return run_command(&cp);
+}
+
 static int git_checkout_config(const char *var, const char *value, void *cb)
 {
 	if (!strcmp(var, "diff.ignoresubmodules")) {
@@ -1068,6 +1144,9 @@ static int checkout_branch(struct checkout_opts *opts,
 		die(_("Cannot switch branch to a non-commit '%s'"),
 		    new->name);
 
+	if (opts->new_worktree)
+		return prepare_linked_checkout(opts, new);
+
 	if (!new->commit && opts->new_branch) {
 		unsigned char rev[20];
 		int flag;
@@ -1110,6 +1189,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			 N_("do not limit pathspecs to sparse entries only")),
 		OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
 				N_("second guess 'git checkout no-such-branch'")),
+		OPT_STRING(0, "to", &opts.new_worktree, N_("path"),
+			   N_("check a branch out in a separate working directory")),
 		OPT_END(),
 	};
 
@@ -1118,6 +1199,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts.overwrite_ignore = 1;
 	opts.prefix = prefix;
 
+	opts.saved_argv = xmalloc(sizeof(const char *) * (argc + 2));
+	memcpy(opts.saved_argv, argv, sizeof(const char *) * (argc + 1));
+
 	gitmodules_config();
 	git_config(git_checkout_config, &opts);
 
@@ -1126,6 +1210,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
+	/* recursive execution from checkout_new_worktree() */
+	opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL;
+	if (opts.new_worktree_mode)
+		opts.new_worktree = NULL;
+
 	if (conflict_style) {
 		opts.merge = 1; /* implied */
 		git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
diff --git a/path.c b/path.c
index 8453687..ddb5962 100644
--- a/path.c
+++ b/path.c
@@ -92,7 +92,7 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)
 
 static const char *common_list[] = {
 	"/branches", "/hooks", "/info", "/logs", "/lost-found", "/modules",
-	"/objects", "/refs", "/remotes", "/rr-cache", "/svn",
+	"/objects", "/refs", "/remotes", "/repos", "/rr-cache", "/svn",
 	"config", "gc.pid", "packed-refs", "shallow",
 	NULL
 };
diff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh
new file mode 100755
index 0000000..5ec49e2
--- /dev/null
+++ b/t/t2025-checkout-to.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+test_description='test git checkout --to'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit init
+'
+
+test_expect_success 'checkout --to not updating paths' '
+	test_must_fail git checkout --to -- init.t
+'
+
+test_expect_success 'checkout --to a new worktree' '
+	git checkout --to here master &&
+	(
+		cd here &&
+		test_cmp ../init.t init.t &&
+		git symbolic-ref HEAD >actual &&
+		echo refs/heads/master >expect &&
+		test_cmp expect actual &&
+		git fsck
+	)
+'
+
+test_expect_success 'checkout --to from a linked checkout' '
+	(
+		cd here &&
+		git checkout --to nested-here master
+		cd nested-here &&
+		git fsck
+	)
+'
+
+test_expect_success 'checkout --to a new worktree creating new branch' '
+	git checkout --to there -b newmaster master &&
+	(
+		cd there &&
+		test_cmp ../init.t init.t &&
+		git symbolic-ref HEAD >actual &&
+		echo refs/heads/newmaster >expect &&
+		test_cmp expect actual &&
+		git fsck
+	)
+'
+
+test_done
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 23/28] checkout: clean up half-prepared directories in --to mode
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (21 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 22/28] checkout: support checking out into a new working directory Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:48     ` [PATCH v5 24/28] checkout: detach if the branch is already checked out elsewhere Nguyễn Thái Ngọc Duy
                       ` (6 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 47 insertions(+), 2 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index aa4ffd1..97fdfcc 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -20,6 +20,7 @@
 #include "resolve-undo.h"
 #include "submodule.h"
 #include "argv-array.h"
+#include "sigchain.h"
 
 static const char * const checkout_usage[] = {
 	N_("git checkout [options] <branch>"),
@@ -815,6 +816,35 @@ static int switch_branches(const struct checkout_opts *opts,
 	return ret || writeout_error;
 }
 
+static const char *junk_work_tree;
+static const char *junk_git_dir;
+static int is_junk;
+static pid_t junk_pid;
+
+static void remove_junk(void)
+{
+	struct strbuf sb = STRBUF_INIT;
+	if (!is_junk || getpid() != junk_pid)
+		return;
+	if (junk_git_dir) {
+		strbuf_addstr(&sb, junk_git_dir);
+		remove_dir_recursively(&sb, 0);
+		strbuf_reset(&sb);
+	}
+	if (junk_work_tree) {
+		strbuf_addstr(&sb, junk_work_tree);
+		remove_dir_recursively(&sb, 0);
+	}
+	strbuf_release(&sb);
+}
+
+static void remove_junk_on_signal(int signo)
+{
+	remove_junk();
+	sigchain_pop(signo);
+	raise(signo);
+}
+
 static int prepare_linked_checkout(const struct checkout_opts *opts,
 				   struct branch_info *new)
 {
@@ -823,7 +853,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	const char *path = opts->new_worktree, *name;
 	struct stat st;
 	struct child_process cp;
-	int counter = 0, len;
+	int counter = 0, len, ret;
 
 	if (!new->commit)
 		die(_("no branch specified"));
@@ -849,13 +879,21 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 		strbuf_addf(&sb_repo, "%d", counter);
 	}
 	name = strrchr(sb_repo.buf, '/') + 1;
+
+	junk_pid = getpid();
+	atexit(remove_junk);
+	sigchain_push_common(remove_junk_on_signal);
+
 	if (mkdir(sb_repo.buf, 0777))
 		die_errno(_("could not create directory of '%s'"), sb_repo.buf);
+	junk_git_dir = sb_repo.buf;
+	is_junk = 1;
 
 	strbuf_addf(&sb_git, "%s/.git", path);
 	if (safe_create_leading_directories_const(sb_git.buf))
 		die_errno(_("could not create leading directories of '%s'"),
 			  sb_git.buf);
+	junk_work_tree = path;
 
 	write_file(sb_git.buf, 1, "gitdir: %s/repos/%s\n",
 		   real_path(get_git_common_dir()), name);
@@ -880,7 +918,14 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	memset(&cp, 0, sizeof(cp));
 	cp.git_cmd = 1;
 	cp.argv = opts->saved_argv;
-	return run_command(&cp);
+	ret = run_command(&cp);
+	if (!ret)
+		is_junk = 0;
+	strbuf_release(&sb);
+	strbuf_release(&sb_repo);
+	strbuf_release(&sb_git);
+	return ret;
+
 }
 
 static int git_checkout_config(const char *var, const char *value, void *cb)
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 24/28] checkout: detach if the branch is already checked out elsewhere
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (22 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 23/28] checkout: clean up half-prepared directories in --to mode Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:48     ` [PATCH v5 25/28] prune: strategies for linked checkouts Nguyễn Thái Ngọc Duy
                       ` (5 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

The normal rule is anything outside refs/heads/ is detached. This
increases strictness of the rule a bit more: if the branch is checked
out (either in $GIT_COMMON_DIR/HEAD or any $GIT_DIR/repos/.../HEAD)
then it's detached as well.

A hint is given so the user knows where to go and do something there
if they still want to checkout undetached here.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c     | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++
 t/t2025-checkout-to.sh | 15 ++++++++--
 2 files changed, 92 insertions(+), 3 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 97fdfcc..598b43d 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -433,6 +433,11 @@ struct branch_info {
 	const char *name; /* The short name used */
 	const char *path; /* The full name of a real branch */
 	struct commit *commit; /* The named commit */
+	/*
+	 * if not null the branch is detached because it's already
+	 * checked out in this checkout
+	 */
+	char *checkout;
 };
 
 static void setup_branch_path(struct branch_info *branch)
@@ -641,6 +646,11 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 			if (old->path && advice_detached_head)
 				detach_advice(new->name);
 			describe_detached_head(_("HEAD is now at"), new->commit);
+			if (new->checkout && !*new->checkout)
+				fprintf(stderr, _("hint: the main checkout is holding this branch\n"));
+			else if (new->checkout)
+				fprintf(stderr, _("hint: the linked checkout %s is holding this branch\n"),
+					new->checkout);
 		}
 	} else if (new->path) {	/* Switch branches. */
 		create_symref("HEAD", new->path, msg.buf);
@@ -983,6 +993,73 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1)
 	return NULL;
 }
 
+static int check_linked_checkout(struct branch_info *new,
+				  const char *name, const char *path)
+{
+	struct strbuf sb = STRBUF_INIT;
+	char *start, *end;
+	if (strbuf_read_file(&sb, path, 0) < 0)
+		return 0;
+	if (!starts_with(sb.buf, "ref:")) {
+		strbuf_release(&sb);
+		return 0;
+	}
+
+	start = sb.buf + 4;
+	while (isspace(*start))
+		start++;
+	end = start;
+	while (*end && !isspace(*end))
+		end++;
+	if (!strncmp(start, new->path, end - start) &&
+	    new->path[end - start] == '\0') {
+		strbuf_release(&sb);
+		new->path = NULL; /* detach */
+		new->checkout = xstrdup(name); /* reason */
+		return 1;
+	}
+	strbuf_release(&sb);
+	return 0;
+}
+
+static void check_linked_checkouts(struct branch_info *new)
+{
+	struct strbuf path = STRBUF_INIT;
+	DIR *dir;
+	struct dirent *d;
+
+	strbuf_addf(&path, "%s/repos", get_git_common_dir());
+	if ((dir = opendir(path.buf)) == NULL) {
+		strbuf_release(&path);
+		return;
+	}
+
+	strbuf_reset(&path);
+	strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
+	/*
+	 * $GIT_COMMON_DIR/HEAD is practically outside
+	 * $GIT_DIR so resolve_ref_unsafe() won't work (it
+	 * uses git_path). Parse the ref ourselves.
+	 */
+	if (check_linked_checkout(new, "", path.buf)) {
+		strbuf_release(&path);
+		closedir(dir);
+		return;
+	}
+
+	while ((d = readdir(dir)) != NULL) {
+		if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+			continue;
+		strbuf_reset(&path);
+		strbuf_addf(&path, "%s/repos/%s/HEAD",
+			    get_git_common_dir(), d->d_name);
+		if (check_linked_checkout(new, d->d_name, path.buf))
+			break;
+	}
+	strbuf_release(&path);
+	closedir(dir);
+}
+
 static int parse_branchname_arg(int argc, const char **argv,
 				int dwim_new_local_branch_ok,
 				struct branch_info *new,
@@ -1110,6 +1187,9 @@ static int parse_branchname_arg(int argc, const char **argv,
 	else
 		new->path = NULL; /* not an existing branch */
 
+	if (new->path)
+		check_linked_checkouts(new);
+
 	new->commit = lookup_commit_reference_gently(rev, 1);
 	if (!new->commit) {
 		/* not a commit */
diff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh
index 5ec49e2..2d35a9b 100755
--- a/t/t2025-checkout-to.sh
+++ b/t/t2025-checkout-to.sh
@@ -13,13 +13,14 @@ test_expect_success 'checkout --to not updating paths' '
 '
 
 test_expect_success 'checkout --to a new worktree' '
+	git rev-parse HEAD >expect &&
 	git checkout --to here master &&
 	(
 		cd here &&
 		test_cmp ../init.t init.t &&
-		git symbolic-ref HEAD >actual &&
-		echo refs/heads/master >expect &&
-		test_cmp expect actual &&
+		test_must_fail git symbolic-ref HEAD &&
+		git rev-parse HEAD >actual &&
+		test_cmp ../expect actual &&
 		git fsck
 	)
 '
@@ -45,4 +46,12 @@ test_expect_success 'checkout --to a new worktree creating new branch' '
 	)
 '
 
+test_expect_success 'detach if the same branch is already checked out' '
+	(
+		cd here &&
+		git checkout newmaster &&
+		test_must_fail git symbolic-ref HEAD
+	)
+'
+
 test_done
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 25/28] prune: strategies for linked checkouts
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (23 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 24/28] checkout: detach if the branch is already checked out elsewhere Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-09  8:20       ` Eric Sunshine
  2014-03-08  2:48     ` [PATCH v5 26/28] gc: style change -- no SP before closing bracket Nguyễn Thái Ngọc Duy
                       ` (4 subsequent siblings)
  29 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

(alias R=$GIT_COMMON_DIR/repos/<id>)

 - linked checkouts are supposed to keep its location in $R/gitdir up
   to date. The use case is auto fixup after a manual checkout move.

 - linked checkouts are supposed to update mtime of $R/gitdir. If
   $R/gitdir's mtime is older than a limit, and it points to nowhere,
   repos/<id> is to be pruned.

 - If $R/locked exists, repos/<id> is not supposed to be pruned. If
   $R/locked exists and $R/gitdir's mtime is older than a really long
   limit, warn about old unused repo.

 - "git checkout --to" is supposed to make a hard link named $R/link
   pointing to the .git file on supported file systems to help detect
   the user manually deleting the checkout. If $R/link exists and its
   link count is greated than 1, the repo is kept.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-prune.txt            |  3 ++
 Documentation/gitrepository-layout.txt | 19 +++++++++
 builtin/checkout.c                     | 14 +++++++
 builtin/prune.c                        | 74 ++++++++++++++++++++++++++++++++++
 setup.c                                | 13 ++++++
 5 files changed, 123 insertions(+)

diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt
index 058ac0d..7babf11 100644
--- a/Documentation/git-prune.txt
+++ b/Documentation/git-prune.txt
@@ -48,6 +48,9 @@ OPTIONS
 --expire <time>::
 	Only expire loose objects older than <time>.
 
+--repos::
+	Prune directories in $GIT_DIR/repos.
+
 <head>...::
 	In addition to objects
 	reachable from any of our references, keep objects
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index 2206f35..dff6c47 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -252,6 +252,25 @@ repos::
 	$GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/repos" will be
 	used instead.
 
+repos/<id>/gitdir::
+	A text file containing the absolute path back to the .git file
+	that points to here. This is used to check if the linked
+	repository has been manually removed and there is no need to
+	keep this directory any more. mtime of this file should be
+	updated every time the linked repository is accessed.
+
+repos/<id>/locked::
+	If this file exists, the linked repository may be on a
+	portable device and not available. It does not mean that the
+	linked repository is gone and `repos/<id>` could be
+	removed. The file's content contains a reason string on why
+	the repository is locked.
+
+repos/<id>/link::
+	If this file exists, it is a hard link to the linked .git
+	file. It is used to detect if the linked repository is
+	manually removed.
+
 SEE ALSO
 --------
 linkgit:git-init[1],
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 598b43d..9dc80f1 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -899,12 +899,22 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	junk_git_dir = sb_repo.buf;
 	is_junk = 1;
 
+	/*
+	 * lock the incomplete repo so prunt won't delete it, unlock
+	 * after the preparation is over.
+	 */
+	strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+	write_file(sb.buf, 1, "initializing\n");
+
 	strbuf_addf(&sb_git, "%s/.git", path);
 	if (safe_create_leading_directories_const(sb_git.buf))
 		die_errno(_("could not create leading directories of '%s'"),
 			  sb_git.buf);
 	junk_work_tree = path;
 
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
+	write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf));
 	write_file(sb_git.buf, 1, "gitdir: %s/repos/%s\n",
 		   real_path(get_git_common_dir()), name);
 	/*
@@ -913,6 +923,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	 * value would do because this value will be ignored and
 	 * replaced at the next (real) checkout.
 	 */
+	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
 	write_file(sb.buf, 1, "%s\n", sha1_to_hex(new->commit->object.sha1));
 	strbuf_reset(&sb);
@@ -931,6 +942,9 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
 	ret = run_command(&cp);
 	if (!ret)
 		is_junk = 0;
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+	unlink_or_warn(sb.buf);
 	strbuf_release(&sb);
 	strbuf_release(&sb_repo);
 	strbuf_release(&sb_git);
diff --git a/builtin/prune.c b/builtin/prune.c
index de43b26..733cb3b 100644
--- a/builtin/prune.c
+++ b/builtin/prune.c
@@ -112,6 +112,70 @@ static void prune_object_dir(const char *path)
 	}
 }
 
+static const char *prune_repo_dir(const char *id, struct stat *st)
+{
+	char *path;
+	int fd, len;
+	if (file_exists(git_path("repos/%s/locked", id)))
+		return NULL;
+	if (stat(git_path("repos/%s/gitdir", id), st)) {
+		st->st_mtime = expire;
+		return _("gitdir does not exist");
+	}
+	fd = open(git_path("repos/%s/gitdir", id), O_RDONLY);
+	len = st->st_size;
+	path = xmalloc(len + 1);
+	read_in_full(fd, path, len);
+	close(fd);
+	while (path[len - 1] == '\n' || path[len - 1] == '\r')
+		len--;
+	path[len] = '\0';
+	if (!file_exists(path)) {
+		struct stat st_link;
+		free(path);
+		/*
+		 * the repo is moved manually and has not been
+		 * accessed since?
+		 */
+		if (!stat(git_path("repos/%s/link", id), &st_link) &&
+		    st_link.st_nlink > 1)
+			return NULL;
+		return _("gitdir points to non-existing file");
+	}
+	free(path);
+	return NULL;
+}
+
+static void prune_repos_dir(void)
+{
+	const char *reason;
+	DIR *dir = opendir(git_path("repos"));
+	struct dirent *d;
+	int removed = 0;
+	struct stat st;
+	if (!dir)
+		return;
+	while ((d = readdir(dir)) != NULL) {
+		if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+			continue;
+		if ((reason = prune_repo_dir(d->d_name, &st)) != NULL &&
+		    st.st_mtime <= expire) {
+			struct strbuf sb = STRBUF_INIT;
+			if (show_only || verbose)
+				printf(_("Removing repos/%s: %s\n"), d->d_name, reason);
+			if (show_only)
+				continue;
+			strbuf_addstr(&sb, git_path("repos/%s", d->d_name));
+			remove_dir_recursively(&sb, 0);
+			strbuf_release(&sb);
+			removed = 1;
+		}
+	}
+	closedir(dir);
+	if (removed)
+		rmdir(git_path("repos"));
+}
+
 /*
  * Write errors (particularly out of space) can result in
  * failed temporary packs (and more rarely indexes and other
@@ -138,10 +202,12 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
 {
 	struct rev_info revs;
 	struct progress *progress = NULL;
+	int prune_repos = 0;
 	const struct option options[] = {
 		OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
 		OPT__VERBOSE(&verbose, N_("report pruned objects")),
 		OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
+		OPT_BOOL(0, "repos", &prune_repos, N_("prune .git/repos/")),
 		OPT_EXPIRY_DATE(0, "expire", &expire,
 				N_("expire objects older than <time>")),
 		OPT_END()
@@ -154,6 +220,14 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
 	init_revisions(&revs, prefix);
 
 	argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
+
+	if (prune_repos) {
+		if (argc)
+			die(_("--repos does not take extra arguments"));
+		prune_repos_dir();
+		return 0;
+	}
+
 	while (argc--) {
 		unsigned char sha1[20];
 		const char *name = *argv++;
diff --git a/setup.c b/setup.c
index 40ce191..5529b26 100644
--- a/setup.c
+++ b/setup.c
@@ -336,6 +336,17 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 	return ret;
 }
 
+static void update_linked_gitdir(const char *gitfile, const char *gitdir)
+{
+	struct strbuf path = STRBUF_INIT;
+	struct stat st;
+
+	strbuf_addf(&path, "%s/gitfile", gitdir);
+	if (stat(path.buf, &st) || st.st_mtime + 24 * 3600 < time(NULL))
+		write_file(path.buf, 0, "%s\n", gitfile);
+	strbuf_release(&path);
+}
+
 /*
  * Try to read the location of the git directory from the .git file,
  * return path to git directory if found.
@@ -384,6 +395,8 @@ const char *read_gitfile(const char *path)
 
 	if (!is_git_directory(dir))
 		die("Not a git repository: %s", dir);
+
+	update_linked_gitdir(path, dir);
 	path = real_path(dir);
 
 	free(buf);
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 26/28] gc: style change -- no SP before closing bracket
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (24 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 25/28] prune: strategies for linked checkouts Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:48     ` [PATCH v5 27/28] gc: support prune --repos Nguyễn Thái Ngọc Duy
                       ` (3 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/gc.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/builtin/gc.c b/builtin/gc.c
index c19545d..39d9b27 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -260,7 +260,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
 		OPT__QUIET(&quiet, N_("suppress progress reporting")),
 		{ OPTION_STRING, 0, "prune", &prune_expire, N_("date"),
 			N_("prune unreferenced objects"),
-			PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },
+			PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire},
 		OPT_BOOL(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")),
 		OPT_BOOL(0, "auto", &auto_gc, N_("enable auto-gc mode")),
 		OPT_BOOL(0, "force", &force, N_("force running gc even if there may be another gc running")),
@@ -273,7 +273,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
 	argv_array_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL);
 	argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
 	argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
-	argv_array_pushl(&prune, "prune", "--expire", NULL );
+	argv_array_pushl(&prune, "prune", "--expire", NULL);
 	argv_array_pushl(&rerere, "rerere", "gc", NULL);
 
 	git_config(gc_config, NULL);
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 27/28] gc: support prune --repos
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (25 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 26/28] gc: style change -- no SP before closing bracket Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-08  2:48     ` [PATCH v5 28/28] count-objects: report unused files in $GIT_DIR/repos/ Nguyễn Thái Ngọc Duy
                       ` (2 subsequent siblings)
  29 siblings, 0 replies; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/config.txt |  7 +++++++
 builtin/gc.c             | 17 +++++++++++++++++
 2 files changed, 24 insertions(+)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 313d4b3..c635b8d 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1183,6 +1183,13 @@ gc.pruneexpire::
 	"now" may be used to disable this  grace period and always prune
 	unreachable objects immediately.
 
+gc.prunereposexpire::
+	When 'git gc' is run, it will call
+	'prune --repos --expire 3.months.ago'.
+	Override the grace period with this config variable. The value
+	"now" may be used to disable the grace period and always prune
+	$GIT_DIR/repos immediately.
+
 gc.reflogexpire::
 gc.<pattern>.reflogexpire::
 	'git reflog expire' removes reflog entries older than
diff --git a/builtin/gc.c b/builtin/gc.c
index 39d9b27..85c3c0c 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -30,11 +30,13 @@ static int aggressive_window = 250;
 static int gc_auto_threshold = 6700;
 static int gc_auto_pack_limit = 50;
 static const char *prune_expire = "2.weeks.ago";
+static const char *prune_repos_expire = "3.months.ago";
 
 static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT;
 static struct argv_array reflog = ARGV_ARRAY_INIT;
 static struct argv_array repack = ARGV_ARRAY_INIT;
 static struct argv_array prune = ARGV_ARRAY_INIT;
+static struct argv_array prune_repos = ARGV_ARRAY_INIT;
 static struct argv_array rerere = ARGV_ARRAY_INIT;
 
 static char *pidfile;
@@ -81,6 +83,14 @@ static int gc_config(const char *var, const char *value, void *cb)
 		}
 		return git_config_string(&prune_expire, var, value);
 	}
+	if (!strcmp(var, "gc.prunereposexpire")) {
+		if (value && strcmp(value, "now")) {
+			unsigned long now = approxidate("now");
+			if (approxidate(value) >= now)
+				return error(_("Invalid %s: '%s'"), var, value);
+		}
+		return git_config_string(&prune_repos_expire, var, value);
+	}
 	return git_default_config(var, value, cb);
 }
 
@@ -274,6 +284,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
 	argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
 	argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
 	argv_array_pushl(&prune, "prune", "--expire", NULL);
+	argv_array_pushl(&prune_repos, "prune", "--repos", "--expire", NULL);
 	argv_array_pushl(&rerere, "rerere", "gc", NULL);
 
 	git_config(gc_config, NULL);
@@ -334,6 +345,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
 			return error(FAILED_RUN, prune.argv[0]);
 	}
 
+	if (prune_repos_expire) {
+		argv_array_push(&prune_repos, prune_repos_expire);
+		if (run_command_v_opt(prune_repos.argv, RUN_GIT_CMD))
+			return error(FAILED_RUN, prune_repos.argv[0]);
+	}
+
 	if (run_command_v_opt(rerere.argv, RUN_GIT_CMD))
 		return error(FAILED_RUN, rerere.argv[0]);
 
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH v5 28/28] count-objects: report unused files in $GIT_DIR/repos/...
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (26 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 27/28] gc: support prune --repos Nguyễn Thái Ngọc Duy
@ 2014-03-08  2:48     ` Nguyễn Thái Ngọc Duy
  2014-03-09  8:21       ` Eric Sunshine
  2014-03-09  8:18     ` [PATCH v5 00/28] Support multiple checkouts Eric Sunshine
  2014-07-06 20:46     ` Max Kirillov
  29 siblings, 1 reply; 169+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2014-03-08  2:48 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

In linked checkouts, borrowed parts like config is taken from
$GIT_COMMON_DIR. $GIT_DIR/config is never used. Report them as
garbage.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/count-objects.c |  4 +++-
 cache.h                 |  1 +
 path.c                  | 29 +++++++++++++++++++++++++++--
 3 files changed, 31 insertions(+), 3 deletions(-)

diff --git a/builtin/count-objects.c b/builtin/count-objects.c
index a7f70cb..d3a1620 100644
--- a/builtin/count-objects.c
+++ b/builtin/count-objects.c
@@ -102,8 +102,10 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
 	/* we do not take arguments other than flags for now */
 	if (argc)
 		usage_with_options(count_objects_usage, opts);
-	if (verbose)
+	if (verbose) {
 		report_garbage = real_report_garbage;
+		report_linked_checkout_garbage();
+	}
 	memcpy(path, objdir, len);
 	if (len && objdir[len-1] != '/')
 		path[len++] = '/';
diff --git a/cache.h b/cache.h
index 979ac6e..a0ff430 100644
--- a/cache.h
+++ b/cache.h
@@ -662,6 +662,7 @@ extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1
 extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 extern const char *git_path_submodule(const char *path, const char *fmt, ...)
 	__attribute__((format (printf, 2, 3)));
+extern void report_linked_checkout_garbage(void);
 
 extern char *sha1_file_name(const unsigned char *sha1);
 extern char *sha1_pack_name(const unsigned char *sha1);
diff --git a/path.c b/path.c
index ddb5962..5a7dc45 100644
--- a/path.c
+++ b/path.c
@@ -4,6 +4,7 @@
 #include "cache.h"
 #include "strbuf.h"
 #include "string-list.h"
+#include "dir.h"
 
 static int get_st_mode_bits(const char *path, int *mode)
 {
@@ -91,9 +92,9 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)
 }
 
 static const char *common_list[] = {
-	"/branches", "/hooks", "/info", "/logs", "/lost-found", "/modules",
+	"/branches", "/hooks", "/info", "!/logs", "/lost-found", "/modules",
 	"/objects", "/refs", "/remotes", "/repos", "/rr-cache", "/svn",
-	"config", "gc.pid", "packed-refs", "shallow",
+	"config", "!gc.pid", "packed-refs", "shallow",
 	NULL
 };
 
@@ -107,6 +108,8 @@ static void update_common_dir(struct strbuf *buf, int git_dir_len)
 	for (p = common_list; *p; p++) {
 		const char *path = *p;
 		int is_dir = 0;
+		if (*path == '!')
+			path++;
 		if (*path == '/') {
 			path++;
 			is_dir = 1;
@@ -122,6 +125,28 @@ static void update_common_dir(struct strbuf *buf, int git_dir_len)
 	}
 }
 
+void report_linked_checkout_garbage(void)
+{
+	struct strbuf sb = STRBUF_INIT;
+	const char **p;
+	int len;
+
+	if (!git_common_dir_env)
+		return;
+	strbuf_addf(&sb, "%s/", get_git_dir());
+	len = sb.len;
+	for (p = common_list; *p; p++) {
+		const char *path = *p;
+		if (*path == '!')
+			continue;
+		strbuf_setlen(&sb, len);
+		strbuf_addstr(&sb, path);
+		if (file_exists(sb.buf))
+			report_garbage("unused in linked checkout", sb.buf);
+	}
+	strbuf_release(&sb);
+}
+
 static void adjust_git_path(struct strbuf *buf, int git_dir_len)
 {
 	const char *base = buf->buf + git_dir_len;
-- 
1.9.0.40.gaa8c3ea

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* Re: [PATCH v5 01/28] path.c: make get_pathname() return strbuf instead of static buffer
  2014-03-08  2:47     ` [PATCH v5 01/28] path.c: make get_pathname() return strbuf instead of static buffer Nguyễn Thái Ngọc Duy
@ 2014-03-08  6:11       ` Torsten Bögershausen
  2014-03-08  8:57         ` Duy Nguyen
  0 siblings, 1 reply; 169+ messages in thread
From: Torsten Bögershausen @ 2014-03-08  6:11 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy, git; +Cc: Junio C Hamano

On 2014-03-08 03.47, Nguyễn Thái Ngọc Duy wrote:
> We've been avoiding PATH_MAX whenever possible. This patch makes
> get_pathname() return a strbuf and updates the callers to take
> advantage of this. The code is simplified as we no longer need to
> worry about buffer overflow.
> 
> vsnpath() behavior is changed slightly: previously it always clears
Minor question:
Is the function name vsnpath() appropriate any more ?
How about renaming the function into strbuf_vaddpath() ?

> the buffer before writing, now it just appends. Fortunately this is a
> static function and all of its callers prepare the buffer properly:
> git_path() gets the buffer from get_pathname() which resets the
> buffer, the remaining call sites start with STRBUF_INIT'd buffer.
> 
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v5 01/28] path.c: make get_pathname() return strbuf instead of static buffer
  2014-03-08  6:11       ` Torsten Bögershausen
@ 2014-03-08  8:57         ` Duy Nguyen
  0 siblings, 0 replies; 169+ messages in thread
From: Duy Nguyen @ 2014-03-08  8:57 UTC (permalink / raw)
  To: Torsten Bögershausen; +Cc: Git Mailing List, Junio C Hamano

On Sat, Mar 8, 2014 at 1:11 PM, Torsten Bögershausen <tboegi@web.de> wrote:
> On 2014-03-08 03.47, Nguyễn Thái Ngọc Duy wrote:
>> We've been avoiding PATH_MAX whenever possible. This patch makes
>> get_pathname() return a strbuf and updates the callers to take
>> advantage of this. The code is simplified as we no longer need to
>> worry about buffer overflow.
>>
>> vsnpath() behavior is changed slightly: previously it always clears
> Minor question:
> Is the function name vsnpath() appropriate any more ?
> How about renaming the function into strbuf_vaddpath() ?

It is renamed in 04/28 to do_git_path().
-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v5 00/28] Support multiple checkouts
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (27 preceding siblings ...)
  2014-03-08  2:48     ` [PATCH v5 28/28] count-objects: report unused files in $GIT_DIR/repos/ Nguyễn Thái Ngọc Duy
@ 2014-03-09  8:18     ` Eric Sunshine
  2014-07-06 20:46     ` Max Kirillov
  29 siblings, 0 replies; 169+ messages in thread
From: Eric Sunshine @ 2014-03-09  8:18 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: Git List, Junio C Hamano

On Fri, Mar 7, 2014 at 9:47 PM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> The diff against v4 is kinda big but it's mostly about converting
> `...` to $(...) and making git_path() and friends return a const
> string.
>
> Another notable change is I no longer attempt to support checkouts on
> portable devices. Torsten pointed out (privately) that my dealing with
> Windows drives was insufficient. And Junio was not so happy with how
> link() was handled either. We can revisit it later.
>
> Many thanks to Eric, who was very patient to go through the series
> carefully and pointed out problems that I overlooked.
>
> v4..v5 diff below for convenience

Thanks for providing an interdiff between the two versions. It
simplified things considerably, especially with the v4 reviews still
relatively fresh in my brain.

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v5 06/28] Make git_path() aware of file relocation in $GIT_DIR
  2014-03-08  2:47     ` [PATCH v5 06/28] Make git_path() aware of file relocation in $GIT_DIR Nguyễn Thái Ngọc Duy
@ 2014-03-09  8:19       ` Eric Sunshine
  0 siblings, 0 replies; 169+ messages in thread
From: Eric Sunshine @ 2014-03-09  8:19 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: Git List, Junio C Hamano

On Fri, Mar 7, 2014 at 9:47 PM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> We allow the user to relocate certain paths out of $GIT_DIR via
> environment variables, e.g. GIT_OBJECT_DIRECTORY, GIT_INDEX_FILE and
> GIT_GRAFT_FILE. Callers are not supposed to use git_path() or
> git_pathdup() to get those paths. Instead they must use
> get_object_directory(), get_index_file() and get_graft_file()
> respectively. This is inconvenient and could be missed in review (for
> example, there's git_path("objects/info/alternates") somewhere in
> sha1_file.c).
>
> This patch makes git_path() and git_pathdup() understand those
> environment variables. So if you set GIT_OBJECT_DIRECTORY to /foo/bar,
> git_path("objects/abc") should return /foo/bar/abc. The same is done
> for the two remaining env variables.
>
> "git rev-parse --git-path" is the wrapper for script use.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  Documentation/git-rev-parse.txt |  7 ++++++
>  builtin/rev-parse.c             |  7 ++++++
>  cache.h                         |  1 +
>  environment.c                   |  9 ++++++--
>  path.c                          | 49 +++++++++++++++++++++++++++++++++++++++--
>  t/t0060-path-utils.sh           | 19 ++++++++++++++++
>  6 files changed, 88 insertions(+), 4 deletions(-)
>
> diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
> index 0d2cdcd..46020d9 100644
> --- a/Documentation/git-rev-parse.txt
> +++ b/Documentation/git-rev-parse.txt
> @@ -232,6 +232,13 @@ print a message to stderr and exit with nonzero status.
>         repository.  If <path> is a gitfile then the resolved path
>         to the real repository is printed.
>
> +--git-path <path>::
> +       Resolve "$GIT_DIR/<path>" and takes other path relocation
> +       variables such as $GIT_OBJECT_DIRECTORY,
> +       $GIT_INDEX_FILE... into account. For example, if
> +       $GIT_OBJECT_DIRECTORY is set to /foo/bar then "git rev-parse
> +       --git-path objects/abc" returns /tmp/bar/abc.

s/tmp/foo/

> +
>  --show-cdup::
>         When the command is invoked from a subdirectory, show the
>         path of the top-level directory relative to the current
> diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
> index aaeb611..e50bc65 100644
> --- a/builtin/rev-parse.c
> +++ b/builtin/rev-parse.c
> @@ -518,6 +518,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
>         for (i = 1; i < argc; i++) {
>                 const char *arg = argv[i];
>
> +               if (!strcmp(arg, "--git-path")) {
> +                       if (!argv[i + 1])
> +                               die("--git-path requires an argument");
> +                       puts(git_path("%s", argv[i + 1]));
> +                       i++;
> +                       continue;
> +               }
>                 if (as_is) {
>                         if (show_file(arg, output_prefix) && as_is < 2)
>                                 verify_filename(prefix, arg, 0);

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v5 25/28] prune: strategies for linked checkouts
  2014-03-08  2:48     ` [PATCH v5 25/28] prune: strategies for linked checkouts Nguyễn Thái Ngọc Duy
@ 2014-03-09  8:20       ` Eric Sunshine
  0 siblings, 0 replies; 169+ messages in thread
From: Eric Sunshine @ 2014-03-09  8:20 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: Git List, Junio C Hamano

On Fri, Mar 7, 2014 at 9:48 PM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> (alias R=$GIT_COMMON_DIR/repos/<id>)
>
>  - linked checkouts are supposed to keep its location in $R/gitdir up
>    to date. The use case is auto fixup after a manual checkout move.
>
>  - linked checkouts are supposed to update mtime of $R/gitdir. If
>    $R/gitdir's mtime is older than a limit, and it points to nowhere,
>    repos/<id> is to be pruned.
>
>  - If $R/locked exists, repos/<id> is not supposed to be pruned. If
>    $R/locked exists and $R/gitdir's mtime is older than a really long
>    limit, warn about old unused repo.
>
>  - "git checkout --to" is supposed to make a hard link named $R/link
>    pointing to the .git file on supported file systems to help detect
>    the user manually deleting the checkout. If $R/link exists and its
>    link count is greated than 1, the repo is kept.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index 598b43d..9dc80f1 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -899,12 +899,22 @@ static int prepare_linked_checkout(const struct checkout_opts *opts,
>         junk_git_dir = sb_repo.buf;
>         is_junk = 1;
>
> +       /*
> +        * lock the incomplete repo so prunt won't delete it, unlock

s/prunt/prune/

> +        * after the preparation is over.
> +        */
> +       strbuf_addf(&sb, "%s/locked", sb_repo.buf);
> +       write_file(sb.buf, 1, "initializing\n");
> +
>         strbuf_addf(&sb_git, "%s/.git", path);
>         if (safe_create_leading_directories_const(sb_git.buf))
>                 die_errno(_("could not create leading directories of '%s'"),
>                           sb_git.buf);
>         junk_work_tree = path;
>
> +       strbuf_reset(&sb);
> +       strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
> +       write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf));
>         write_file(sb_git.buf, 1, "gitdir: %s/repos/%s\n",
>                    real_path(get_git_common_dir()), name);
>         /*

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v5 28/28] count-objects: report unused files in $GIT_DIR/repos/...
  2014-03-08  2:48     ` [PATCH v5 28/28] count-objects: report unused files in $GIT_DIR/repos/ Nguyễn Thái Ngọc Duy
@ 2014-03-09  8:21       ` Eric Sunshine
  2014-03-09 11:25         ` Duy Nguyen
  0 siblings, 1 reply; 169+ messages in thread
From: Eric Sunshine @ 2014-03-09  8:21 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: Git List, Junio C Hamano

On Fri, Mar 7, 2014 at 9:48 PM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> In linked checkouts, borrowed parts like config is taken from
> $GIT_COMMON_DIR. $GIT_DIR/config is never used. Report them as
> garbage.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
> diff --git a/path.c b/path.c
> index ddb5962..5a7dc45 100644
> --- a/path.c
> +++ b/path.c
> @@ -4,6 +4,7 @@
>  #include "cache.h"
>  #include "strbuf.h"
>  #include "string-list.h"
> +#include "dir.h"
>
>  static int get_st_mode_bits(const char *path, int *mode)
>  {
> @@ -91,9 +92,9 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)
>  }

Do you want to add a comment here explaining what the "!" prefix on
some entries means, or is it sufficiently self-evident to anyone
looking at the consumers of this array?

>  static const char *common_list[] = {
> -       "/branches", "/hooks", "/info", "/logs", "/lost-found", "/modules",
> +       "/branches", "/hooks", "/info", "!/logs", "/lost-found", "/modules",
>         "/objects", "/refs", "/remotes", "/repos", "/rr-cache", "/svn",
> -       "config", "gc.pid", "packed-refs", "shallow",
> +       "config", "!gc.pid", "packed-refs", "shallow",
>         NULL
>  };
>
> @@ -107,6 +108,8 @@ static void update_common_dir(struct strbuf *buf, int git_dir_len)
>         for (p = common_list; *p; p++) {
>                 const char *path = *p;
>                 int is_dir = 0;
> +               if (*path == '!')
> +                       path++;
>                 if (*path == '/') {
>                         path++;
>                         is_dir = 1;
> @@ -122,6 +125,28 @@ static void update_common_dir(struct strbuf *buf, int git_dir_len)
>         }
>  }
>
> +void report_linked_checkout_garbage(void)
> +{
> +       struct strbuf sb = STRBUF_INIT;
> +       const char **p;
> +       int len;
> +
> +       if (!git_common_dir_env)
> +               return;
> +       strbuf_addf(&sb, "%s/", get_git_dir());
> +       len = sb.len;
> +       for (p = common_list; *p; p++) {
> +               const char *path = *p;
> +               if (*path == '!')
> +                       continue;
> +               strbuf_setlen(&sb, len);
> +               strbuf_addstr(&sb, path);
> +               if (file_exists(sb.buf))
> +                       report_garbage("unused in linked checkout", sb.buf);
> +       }
> +       strbuf_release(&sb);
> +}
> +
>  static void adjust_git_path(struct strbuf *buf, int git_dir_len)
>  {
>         const char *base = buf->buf + git_dir_len;
> --
> 1.9.0.40.gaa8c3ea

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v5 28/28] count-objects: report unused files in $GIT_DIR/repos/...
  2014-03-09  8:21       ` Eric Sunshine
@ 2014-03-09 11:25         ` Duy Nguyen
  0 siblings, 0 replies; 169+ messages in thread
From: Duy Nguyen @ 2014-03-09 11:25 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: Git List, Junio C Hamano

On Sun, Mar 9, 2014 at 3:21 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:
> On Fri, Mar 7, 2014 at 9:48 PM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
>> In linked checkouts, borrowed parts like config is taken from
>> $GIT_COMMON_DIR. $GIT_DIR/config is never used. Report them as
>> garbage.
>>
>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
>> ---
>> diff --git a/path.c b/path.c
>> index ddb5962..5a7dc45 100644
>> --- a/path.c
>> +++ b/path.c
>> @@ -4,6 +4,7 @@
>>  #include "cache.h"
>>  #include "strbuf.h"
>>  #include "string-list.h"
>> +#include "dir.h"
>>
>>  static int get_st_mode_bits(const char *path, int *mode)
>>  {
>> @@ -91,9 +92,9 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)
>>  }
>
> Do you want to add a comment here explaining what the "!" prefix on
> some entries means, or is it sufficiently self-evident to anyone
> looking at the consumers of this array?

I was hoping it was clear from the patch how this "!" was used (or "/"
in the patch that introduces common_list[]). But if any reader thinks
otherwise, I'm happy to add a comment.
-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v5 03/28] Convert git_snpath() to strbuf_git_path()
  2014-03-08  2:47     ` [PATCH v5 03/28] Convert git_snpath() to strbuf_git_path() Nguyễn Thái Ngọc Duy
@ 2014-03-17 18:11       ` Junio C Hamano
  0 siblings, 0 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-03-17 18:11 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> In the previous patch, git_snpath() is modified to allocate a new
> strbuf buffer because vsnpath() needs that. But that makes it awkward
> because git_snpath() receives a pre-allocated buffer from outside and
> has to copy data back. Rename it to strbuf_git_path() and make it
> receive strbuf directly.
>
> The conversion from git_snpath() to git_path() in
> update_refs_for_switch() is safe because that function does not keep
> any pointer to the round-robin buffer pool allocated by
> get_pathname().

s/that function does not/that function and all of its callers do not/

is the guarantee we need to say it is safe.  And I think that holds
true.

Thanks.

>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  builtin/checkout.c | 25 +++++++++--------
>  cache.h            |  4 +--
>  path.c             | 11 ++------
>  refs.c             | 78 +++++++++++++++++++++++++++++++++---------------------
>  refs.h             |  2 +-
>  5 files changed, 65 insertions(+), 55 deletions(-)
>
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index 64c2aca..efb5e2f 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -585,18 +585,21 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
>  		if (opts->new_orphan_branch) {
>  			if (opts->new_branch_log && !log_all_ref_updates) {
>  				int temp;
> -				char log_file[PATH_MAX];
> -				const char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
> +				struct strbuf log_file = STRBUF_INIT;
> +				int ret;
> +				const char *ref_name;
>  
> +				ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
>  				temp = log_all_ref_updates;
>  				log_all_ref_updates = 1;
> -				if (log_ref_setup(ref_name, log_file, sizeof(log_file))) {
> +				ret = log_ref_setup(ref_name, &log_file);
> +				log_all_ref_updates = temp;
> +				strbuf_release(&log_file);
> +				if (ret) {
>  					fprintf(stderr, _("Can not do reflog for '%s'\n"),
>  					    opts->new_orphan_branch);
> -					log_all_ref_updates = temp;
>  					return;
>  				}
> -				log_all_ref_updates = temp;
>  			}
>  		}
>  		else
> @@ -651,14 +654,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
>  					new->name);
>  			}
>  		}
> -		if (old->path && old->name) {
> -			char log_file[PATH_MAX], ref_file[PATH_MAX];
> -
> -			git_snpath(log_file, sizeof(log_file), "logs/%s", old->path);
> -			git_snpath(ref_file, sizeof(ref_file), "%s", old->path);
> -			if (!file_exists(ref_file) && file_exists(log_file))
> -				remove_path(log_file);
> -		}
> +		if (old->path && old->name &&
> +		    !file_exists(git_path("%s", old->path)) &&
> +		     file_exists(git_path("logs/%s", old->path)))
> +			remove_path(git_path("logs/%s", old->path));
>  	}
>  	remove_branch_state();
>  	strbuf_release(&msg);
> diff --git a/cache.h b/cache.h
> index a344a5f..0fae730 100644
> --- a/cache.h
> +++ b/cache.h
> @@ -646,8 +646,8 @@ extern int check_repository_format(void);
>  
>  extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
>  	__attribute__((format (printf, 3, 4)));
> -extern char *git_snpath(char *buf, size_t n, const char *fmt, ...)
> -	__attribute__((format (printf, 3, 4)));
> +extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
> +	__attribute__((format (printf, 2, 3)));
>  extern char *git_pathdup(const char *fmt, ...)
>  	__attribute__((format (printf, 1, 2)));
>  extern char *mkpathdup(const char *fmt, ...)
> diff --git a/path.c b/path.c
> index 36d461e..417df76 100644
> --- a/path.c
> +++ b/path.c
> @@ -70,19 +70,12 @@ static void vsnpath(struct strbuf *buf, const char *fmt, va_list args)
>  	strbuf_cleanup_path(buf);
>  }
>  
> -char *git_snpath(char *buf, size_t n, const char *fmt, ...)
> +void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
>  {
> -	struct strbuf sb = STRBUF_INIT;
>  	va_list args;
>  	va_start(args, fmt);
> -	vsnpath(&sb, fmt, args);
> +	vsnpath(sb, fmt, args);
>  	va_end(args);
> -	if (sb.len >= n)
> -		strlcpy(buf, bad_path, n);
> -	else
> -		memcpy(buf, sb.buf, sb.len + 1);
> -	strbuf_release(&sb);
> -	return buf;
>  }
>  
>  char *git_pathdup(const char *fmt, ...)
> diff --git a/refs.c b/refs.c
> index f846f2f..c5613b0 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -1325,10 +1325,12 @@ static const char *handle_missing_loose_ref(const char *refname,
>  
>  const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)
>  {
> +	struct strbuf sb_path = STRBUF_INIT;
>  	int depth = MAXDEPTH;
>  	ssize_t len;
>  	char buffer[256];
>  	static char refname_buffer[256];
> +	const char *ret;
>  
>  	if (flag)
>  		*flag = 0;
> @@ -1337,15 +1339,17 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
>  		return NULL;
>  
>  	for (;;) {
> -		char path[PATH_MAX];
> +		const char *path;
>  		struct stat st;
>  		char *buf;
>  		int fd;
>  
>  		if (--depth < 0)
> -			return NULL;
> +			goto fail;
>  
> -		git_snpath(path, sizeof(path), "%s", refname);
> +		strbuf_reset(&sb_path);
> +		strbuf_git_path(&sb_path, "%s", refname);
> +		path = sb_path.buf;
>  
>  		/*
>  		 * We might have to loop back here to avoid a race
> @@ -1359,10 +1363,11 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
>  	stat_ref:
>  		if (lstat(path, &st) < 0) {
>  			if (errno == ENOENT)
> -				return handle_missing_loose_ref(refname, sha1,
> -								reading, flag);
> +				ret = handle_missing_loose_ref(refname, sha1,
> +							       reading, flag);
>  			else
> -				return NULL;
> +				ret = NULL;
> +			goto done;
>  		}
>  
>  		/* Follow "normalized" - ie "refs/.." symlinks by hand */
> @@ -1373,7 +1378,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
>  					/* inconsistent with lstat; retry */
>  					goto stat_ref;
>  				else
> -					return NULL;
> +					goto fail;
>  			}
>  			buffer[len] = 0;
>  			if (starts_with(buffer, "refs/") &&
> @@ -1389,7 +1394,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
>  		/* Is it a directory? */
>  		if (S_ISDIR(st.st_mode)) {
>  			errno = EISDIR;
> -			return NULL;
> +			goto fail;
>  		}
>  
>  		/*
> @@ -1402,12 +1407,13 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
>  				/* inconsistent with lstat; retry */
>  				goto stat_ref;
>  			else
> -				return NULL;
> +				goto fail;
>  		}
> +
>  		len = read_in_full(fd, buffer, sizeof(buffer)-1);
>  		close(fd);
>  		if (len < 0)
> -			return NULL;
> +			goto fail;
>  		while (len && isspace(buffer[len-1]))
>  			len--;
>  		buffer[len] = '\0';
> @@ -1424,9 +1430,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
>  			    (buffer[40] != '\0' && !isspace(buffer[40]))) {
>  				if (flag)
>  					*flag |= REF_ISBROKEN;
> -				return NULL;
> +				goto fail;
>  			}
> -			return refname;
> +			ret = refname;
> +			goto done;
>  		}
>  		if (flag)
>  			*flag |= REF_ISSYMREF;
> @@ -1436,10 +1443,15 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
>  		if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
>  			if (flag)
>  				*flag |= REF_ISBROKEN;
> -			return NULL;
> +			goto fail;
>  		}
>  		refname = strcpy(refname_buffer, buf);
>  	}
> +fail:
> +	ret = NULL;
> +done:
> +	strbuf_release(&sb_path);
> +	return ret;
>  }
>  
>  char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag)
> @@ -2717,41 +2729,41 @@ static int copy_msg(char *buf, const char *msg)
>  	return cp - buf;
>  }
>  
> -int log_ref_setup(const char *refname, char *logfile, int bufsize)
> +int log_ref_setup(const char *refname, struct strbuf *logfile)
>  {
>  	int logfd, oflags = O_APPEND | O_WRONLY;
>  
> -	git_snpath(logfile, bufsize, "logs/%s", refname);
> +	strbuf_git_path(logfile, "logs/%s", refname);
>  	if (log_all_ref_updates &&
>  	    (starts_with(refname, "refs/heads/") ||
>  	     starts_with(refname, "refs/remotes/") ||
>  	     starts_with(refname, "refs/notes/") ||
>  	     !strcmp(refname, "HEAD"))) {
> -		if (safe_create_leading_directories(logfile) < 0)
> +		if (safe_create_leading_directories(logfile->buf) < 0)
>  			return error("unable to create directory for %s",
> -				     logfile);
> +				     logfile->buf);
>  		oflags |= O_CREAT;
>  	}
>  
> -	logfd = open(logfile, oflags, 0666);
> +	logfd = open(logfile->buf, oflags, 0666);
>  	if (logfd < 0) {
>  		if (!(oflags & O_CREAT) && errno == ENOENT)
>  			return 0;
>  
>  		if ((oflags & O_CREAT) && errno == EISDIR) {
> -			if (remove_empty_directories(logfile)) {
> +			if (remove_empty_directories(logfile->buf)) {
>  				return error("There are still logs under '%s'",
> -					     logfile);
> +					     logfile->buf);
>  			}
> -			logfd = open(logfile, oflags, 0666);
> +			logfd = open(logfile->buf, oflags, 0666);
>  		}
>  
>  		if (logfd < 0)
>  			return error("Unable to append to %s: %s",
> -				     logfile, strerror(errno));
> +				     logfile->buf, strerror(errno));
>  	}
>  
> -	adjust_shared_perm(logfile);
> +	adjust_shared_perm(logfile->buf);
>  	close(logfd);
>  	return 0;
>  }
> @@ -2762,20 +2774,22 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,
>  	int logfd, result, written, oflags = O_APPEND | O_WRONLY;
>  	unsigned maxlen, len;
>  	int msglen;
> -	char log_file[PATH_MAX];
> +	struct strbuf sb_log_file = STRBUF_INIT;
> +	const char *log_file;
>  	char *logrec;
>  	const char *committer;
>  
>  	if (log_all_ref_updates < 0)
>  		log_all_ref_updates = !is_bare_repository();
>  
> -	result = log_ref_setup(refname, log_file, sizeof(log_file));
> +	result = log_ref_setup(refname, &sb_log_file);
>  	if (result)
> -		return result;
> +		goto done;
> +	log_file = sb_log_file.buf;
>  
>  	logfd = open(log_file, oflags);
>  	if (logfd < 0)
> -		return 0;
> +		goto done;
>  	msglen = msg ? strlen(msg) : 0;
>  	committer = git_committer_info(0);
>  	maxlen = strlen(committer) + msglen + 100;
> @@ -2788,9 +2802,13 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,
>  		len += copy_msg(logrec + len - 1, msg) - 1;
>  	written = len <= maxlen ? write_in_full(logfd, logrec, len) : -1;
>  	free(logrec);
> -	if (close(logfd) != 0 || written != len)
> -		return error("Unable to append to %s", log_file);
> -	return 0;
> +	if (close(logfd) != 0 || written != len) {
> +		error("Unable to append to %s", log_file);
> +		result = -1;
> +	}
> +done:
> +	strbuf_release(&sb_log_file);
> +	return result;
>  }
>  
>  static int is_branch(const char *refname)
> diff --git a/refs.h b/refs.h
> index 87a1a79..783033a 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -166,7 +166,7 @@ extern void unlock_ref(struct ref_lock *lock);
>  extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
>  
>  /** Setup reflog before using. **/
> -int log_ref_setup(const char *ref_name, char *logfile, int bufsize);
> +int log_ref_setup(const char *ref_name, struct strbuf *logfile);
>  
>  /** Reads log for the value of ref during at_time. **/
>  extern int read_ref_at(const char *refname, unsigned long at_time, int cnt,

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 18/25] setup.c: support multi-checkout repo setup
  2014-02-18 13:40 ` [PATCH v3 18/25] setup.c: support multi-checkout repo setup Nguyễn Thái Ngọc Duy
  2014-02-19 20:19   ` Junio C Hamano
  2014-02-27 20:28   ` Junio C Hamano
@ 2014-03-24 14:52   ` Torsten Bögershausen
  2014-03-25 13:52     ` Duy Nguyen
  2 siblings, 1 reply; 169+ messages in thread
From: Torsten Bögershausen @ 2014-03-24 14:52 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

On 02/18/2014 02:40 PM, Nguyễn Thái Ngọc Duy wrote:
> The repo setup procedure is updated to detect $GIT_DIR/commondir and
> set $GIT_COMMON_DIR properly.
>
> The core.worktree is ignored when $GIT_DIR/commondir presents. This is
> because "commondir" repos are intended for separate/linked checkouts
> and pointing them back to a fixed core.worktree just does not make
> sense.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  Documentation/config.txt        |  3 +-
>  Documentation/git-rev-parse.txt |  3 ++
>  builtin/rev-parse.c             |  4 +++
>  cache.h                         |  1 +
>  environment.c                   |  8 ++---
>  setup.c                         | 33 +++++++++++++-----
>  t/t1501-worktree.sh             | 76 +++++++++++++++++++++++++++++++++++++++++
>  t/t1510-repo-setup.sh           |  1 +
>  trace.c                         |  1 +
>  9 files changed, 115 insertions(+), 15 deletions(-)
>
> diff --git a/Documentation/config.txt b/Documentation/config.txt
> index 5f4d793..cbf4d97 100644
> --- a/Documentation/config.txt
> +++ b/Documentation/config.txt
> @@ -381,7 +381,8 @@ false), while all other repositories are assumed to be bare (bare
>  
>  core.worktree::
>  	Set the path to the root of the working tree.
> -	This can be overridden by the GIT_WORK_TREE environment
> +	This can be overridden by the GIT_WORK_TREE
> +	or GIT_COMMON_DIR environment
>  	variable and the '--work-tree' command line option.
>  	The value can be an absolute path or relative to the path to
>  	the .git directory, which is either specified by --git-dir
> diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
> index 33e4e90..8e6ad32 100644
> --- a/Documentation/git-rev-parse.txt
> +++ b/Documentation/git-rev-parse.txt
> @@ -215,6 +215,9 @@ If `$GIT_DIR` is not defined and the current directory
>  is not detected to lie in a Git repository or work tree
>  print a message to stderr and exit with nonzero status.
>  
> +--git-common-dir::
> +	Show `$GIT_COMMON_DIR` if defined, else `$GIT_DIR`.
> +
>  --is-inside-git-dir::
>  	When the current working directory is below the repository
>  	directory print "true", otherwise "false".
> diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
> index e50bc65..c7057ce 100644
> --- a/builtin/rev-parse.c
> +++ b/builtin/rev-parse.c
> @@ -744,6 +744,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
>  				printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : "");
>  				continue;
>  			}
> +			if (!strcmp(arg, "--git-common-dir")) {
> +				puts(get_git_common_dir());
> +				continue;
> +			}
>  			if (!strcmp(arg, "--resolve-git-dir")) {
>  				const char *gitdir = resolve_gitdir(argv[i+1]);
>  				if (!gitdir)
> diff --git a/cache.h b/cache.h
> index 51ade32..98b5dd3 100644
> --- a/cache.h
> +++ b/cache.h
> @@ -407,6 +407,7 @@ extern char *get_object_directory(void);
>  extern char *get_index_file(void);
>  extern char *get_graft_file(void);
>  extern int set_git_dir(const char *path);
> +extern int get_common_dir(struct strbuf *sb, const char *gitdir);
>  extern const char *get_git_namespace(void);
>  extern const char *strip_namespace(const char *namespaced_ref);
>  extern const char *get_git_work_tree(void);
> diff --git a/environment.c b/environment.c
> index c998120..0999fc1 100644
> --- a/environment.c
> +++ b/environment.c
> @@ -126,6 +126,7 @@ static char *expand_namespace(const char *raw_namespace)
>  
>  static void setup_git_env(void)
>  {
> +	struct strbuf sb = STRBUF_INIT;
>  	const char *gitfile;
>  	const char *shallow_file;
>  
> @@ -134,12 +135,9 @@ static void setup_git_env(void)
>  		git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
>  	gitfile = read_gitfile(git_dir);
>  	git_dir = xstrdup(gitfile ? gitfile : git_dir);
> -	git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
> -	if (git_common_dir) {
> +	if (get_common_dir(&sb, git_dir))
>  		git_common_dir_env = 1;
> -		git_common_dir = xstrdup(git_common_dir);
> -	} else
> -		git_common_dir = git_dir;
> +	git_common_dir = strbuf_detach(&sb, NULL);
>  	git_object_dir = getenv(DB_ENVIRONMENT);
>  	if (!git_object_dir) {
>  		git_object_dir = xmalloc(strlen(git_common_dir) + 9);
> diff --git a/setup.c b/setup.c
> index e56ec11..d4ac878 100644
> --- a/setup.c
> +++ b/setup.c
> @@ -170,14 +170,15 @@ void verify_non_filename(const char *prefix, const char *arg)
>  	    "'git <command> [<revision>...] -- [<file>...]'", arg);
>  }
>  
> -static void get_common_dir(struct strbuf *sb, const char *gitdir)
> +int get_common_dir(struct strbuf *sb, const char *gitdir)
>  {
>  	struct strbuf data = STRBUF_INIT;
>  	struct strbuf path = STRBUF_INIT;
>  	const char *git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
> +	int ret = 0;
>  	if (git_common_dir) {
>  		strbuf_addstr(sb, git_common_dir);
> -		return;
> +		return 1;
>  	}
>  	strbuf_addf(&path, "%s/commondir", gitdir);
>  	if (file_exists(path.buf)) {
> @@ -189,10 +190,12 @@ static void get_common_dir(struct strbuf *sb, const char *gitdir)
>  			strbuf_addf(&path, "%s/", gitdir);
>  		strbuf_addbuf(&path, &data);
>  		strbuf_addstr(sb, real_path(path.buf));
> +		ret = 1;
>  	} else
>  		strbuf_addstr(sb, gitdir);
>  	strbuf_release(&data);
>  	strbuf_release(&path);
> +	return ret;
>  }
>  
>  /*
> @@ -279,13 +282,26 @@ void setup_work_tree(void)
>  	initialized = 1;
>  }
>  
> +static int check_repo_format(const char *var, const char *value, void *cb)
> +{
> +	if (strcmp(var, "core.repositoryformatversion") == 0)
> +		repository_format_version = git_config_int(var, value);
> +	else if (strcmp(var, "core.sharedrepository") == 0)
> +		shared_repository = git_config_perm(var, value);
> +	return 0;
> +}
> +
>  static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
>  {
>  	struct strbuf sb = STRBUF_INIT;
>  	const char *repo_config;
> +	config_fn_t fn;
>  	int ret = 0;
>  
> -	get_common_dir(&sb, gitdir);
> +	if (get_common_dir(&sb, gitdir))
> +		fn = check_repo_format;
> +	else
> +		fn = check_repository_format_version;
>  	strbuf_addstr(&sb, "/config");
>  	repo_config = sb.buf;
>  
> @@ -298,7 +314,7 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
>  	 * Use a gentler version of git_config() to check if this repo
>  	 * is a good one.
>  	 */
> -	git_config_early(check_repository_format_version, NULL, repo_config);
> +	git_config_early(fn, NULL, repo_config);
>  	if (GIT_REPO_VERSION < repository_format_version) {
>  		if (!nongit_ok)
>  			die ("Expected git repo version <= %d, found %d",
> @@ -770,11 +786,10 @@ int git_config_perm(const char *var, const char *value)
>  
>  int check_repository_format_version(const char *var, const char *value, void *cb)
>  {
> -	if (strcmp(var, "core.repositoryformatversion") == 0)
> -		repository_format_version = git_config_int(var, value);
> -	else if (strcmp(var, "core.sharedrepository") == 0)
> -		shared_repository = git_config_perm(var, value);
> -	else if (strcmp(var, "core.bare") == 0) {
> +	int ret = check_repo_format(var, value, cb);
> +	if (ret)
> +		return ret;
> +	if (strcmp(var, "core.bare") == 0) {
>  		is_bare_repository_cfg = git_config_bool(var, value);
>  		if (is_bare_repository_cfg == 1)
>  			inside_work_tree = -1;
> diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh
> index 8f36aa9..d8bdaf4 100755
> --- a/t/t1501-worktree.sh
> +++ b/t/t1501-worktree.sh
> @@ -346,4 +346,80 @@ test_expect_success 'relative $GIT_WORK_TREE and git subprocesses' '
>  	test_cmp expected actual
>  '
>  
> +test_expect_success 'Multi-worktree setup' '
> +	mkdir work &&
> +	mkdir -p repo.git/repos/foo &&
> +	cp repo.git/HEAD repo.git/index repo.git/repos/foo &&
> +	unset GIT_DIR GIT_CONFIG GIT_WORK_TREE
> +'
> +
> +test_expect_success 'GIT_DIR set (1)' '
> +	echo "gitdir: repo.git/repos/foo" >gitfile &&
> +	echo ../.. >repo.git/repos/foo/commondir &&
> +	(
> +		cd work &&
> +		GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
> +		echo "$TRASH_DIRECTORY/repo.git" >expect &&
> +		test_cmp expect actual
> +	)
> +'
> +
> +test_expect_success 'GIT_DIR set (2)' '
> +	echo "gitdir: repo.git/repos/foo" >gitfile &&
> +	echo "$TRASH_DIRECTORY/repo.git" >repo.git/repos/foo/commondir &&
> +	(
> +		cd work &&
> +		GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
> +		echo "$TRASH_DIRECTORY/repo.git" >expect &&
> +		test_cmp expect actual
> +	)
> +'
> +
> +test_expect_success 'Auto discovery' '
> +	echo "gitdir: repo.git/repos/foo" >.git &&
> +	echo ../.. >repo.git/repos/foo/commondir &&
> +	(
> +		cd work &&
> +		git rev-parse --git-common-dir >actual &&
> +		echo "$TRASH_DIRECTORY/repo.git" >expect &&
> +		test_cmp expect actual &&
> +		echo haha >data1 &&
> +		git add data1 &&
> +		git ls-files --full-name :/ | grep data1 >actual &&
> +		echo work/data1 >expect &&
> +		test_cmp expect actual
> +	)
> +'
> +
> +test_expect_success '$GIT_DIR/common overrides core.worktree' '
> +	mkdir elsewhere &&
> +	git --git-dir=repo.git config core.worktree "$TRASH_DIRECTORY/elsewhere" &&
> +	echo "gitdir: repo.git/repos/foo" >.git &&
> +	echo ../.. >repo.git/repos/foo/commondir &&
> +	(
> +		cd work &&
> +		git rev-parse --git-common-dir >actual &&
> +		echo "$TRASH_DIRECTORY/repo.git" >expect &&
> +		test_cmp expect actual &&
> +		echo haha >data2 &&
> +		git add data2 &&
> +		git ls-files --full-name :/ | grep data2 >actual &&
> +		echo work/data2 >expect &&
> +		test_cmp expect actual
> +	)
> +'
> +
> +test_expect_success '$GIT_WORK_TREE overrides $GIT_DIR/common' '
> +	echo "gitdir: repo.git/repos/foo" >.git &&
> +	echo ../.. >repo.git/repos/foo/commondir &&
> +	(
> +		cd work &&
> +		echo haha >data3 &&
> +		git --git-dir=../.git --work-tree=. add data3 &&
> +		git ls-files --full-name -- :/ | grep data3 >actual &&
> +		echo data3 >expect &&
> +		test_cmp expect actual
> +	)
> +'
> +
>  test_done
> diff --git a/t/t1510-repo-setup.sh b/t/t1510-repo-setup.sh
> index cf2ee78..c211ef5 100755
> --- a/t/t1510-repo-setup.sh
> +++ b/t/t1510-repo-setup.sh
> @@ -106,6 +106,7 @@ setup_env () {
>  expect () {
>  	cat >"$1/expected" <<-EOF
>  	setup: git_dir: $2
> +	setup: git_common_dir: $2
>  	setup: worktree: $3
>  	setup: cwd: $4
>  	setup: prefix: $5
> diff --git a/trace.c b/trace.c
> index 08180a9..a594761 100644
> --- a/trace.c
> +++ b/trace.c
> @@ -173,6 +173,7 @@ void trace_repo_setup(const char *prefix)
>  		prefix = "(null)";
>  
>  	trace_printf_key(key, "setup: git_dir: %s\n", quote_crnl(get_git_dir()));
> +	trace_printf_key(key, "setup: git_common_dir: %s\n", quote_crnl(get_git_common_dir()));
>  	trace_printf_key(key, "setup: worktree: %s\n", quote_crnl(git_work_tree));
>  	trace_printf_key(key, "setup: cwd: %s\n", quote_crnl(cwd));
>  	trace_printf_key(key, "setup: prefix: %s\n", quote_crnl(prefix));

Did I report that t1501  fails when  there is a softlink in $PWD ?
/home/tb/projects is a softlink to /disc5/projects/
(And in total 4 TC failed)
--------------

expecting success:
        echo "gitdir: repo.git/repos/foo" >gitfile &&
        echo ../.. >repo.git/repos/foo/commondir &&
        (
                cd work &&
                GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
                echo "$TRASH_DIRECTORY/repo.git" >expect &&
                test_cmp expect actual
        )

--- expect      2014-03-24 07:55:29.895870540 +0000
+++ actual      2014-03-24 07:55:29.895870540 +0000
@@ -1 +1 @@
-/home/tb/projects/git/git.pu/t/trash directory.t1501-worktree/repo.git
+/disc5/projects/git/git.pu/t/trash directory.t1501-worktree/repo.git
not ok 33 - GIT_DIR set (1)

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 18/25] setup.c: support multi-checkout repo setup
  2014-03-24 14:52   ` Torsten Bögershausen
@ 2014-03-25 13:52     ` Duy Nguyen
  2014-03-25 14:07       ` Duy Nguyen
  0 siblings, 1 reply; 169+ messages in thread
From: Duy Nguyen @ 2014-03-25 13:52 UTC (permalink / raw)
  To: Torsten Bögershausen; +Cc: Git Mailing List

On Mon, Mar 24, 2014 at 9:52 PM, Torsten Bögershausen <tboegi@web.de> wrote:
> Did I report that t1501  fails when  there is a softlink in $PWD ?
> /home/tb/projects is a softlink to /disc5/projects/

Yes you did and I forgot. I have fixed it, running test suite and will
send the reroll soon.
-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 18/25] setup.c: support multi-checkout repo setup
  2014-03-25 13:52     ` Duy Nguyen
@ 2014-03-25 14:07       ` Duy Nguyen
  2014-03-26 21:03         ` Junio C Hamano
  0 siblings, 1 reply; 169+ messages in thread
From: Duy Nguyen @ 2014-03-25 14:07 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git Mailing List, Torsten Bögershausen

On Tue, Mar 25, 2014 at 08:52:13PM +0700, Duy Nguyen wrote:
> On Mon, Mar 24, 2014 at 9:52 PM, Torsten Bögershausen <tboegi@web.de> wrote:
> > Did I report that t1501  fails when  there is a softlink in $PWD ?
> > /home/tb/projects is a softlink to /disc5/projects/
> 
> Yes you did and I forgot. I have fixed it, running test suite and will
> send the reroll soon.

Junio, it seems you have picked up all minor changes after
v5. Resending the whole series for one fix seems overkill. Could you
just --autosquash this one in?

-- 8< --
Subject: [PATCH] fixup! setup.c: support multi-checkout repo setup

---
 t/t1501-worktree.sh | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh
index 2ac4424..e6ac7a4 100755
--- a/t/t1501-worktree.sh
+++ b/t/t1501-worktree.sh
@@ -359,7 +359,7 @@ test_expect_success 'GIT_DIR set (1)' '
 	(
 		cd work &&
 		GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
-		echo "$TRASH_DIRECTORY/repo.git" >expect &&
+		test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
 		test_cmp expect actual
 	)
 '
@@ -370,7 +370,7 @@ test_expect_success 'GIT_DIR set (2)' '
 	(
 		cd work &&
 		GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
-		echo "$TRASH_DIRECTORY/repo.git" >expect &&
+		test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
 		test_cmp expect actual
 	)
 '
@@ -381,7 +381,7 @@ test_expect_success 'Auto discovery' '
 	(
 		cd work &&
 		git rev-parse --git-common-dir >actual &&
-		echo "$TRASH_DIRECTORY/repo.git" >expect &&
+		test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
 		test_cmp expect actual &&
 		echo haha >data1 &&
 		git add data1 &&
@@ -399,7 +399,7 @@ test_expect_success '$GIT_DIR/common overrides core.worktree' '
 	(
 		cd work &&
 		git rev-parse --git-common-dir >actual &&
-		echo "$TRASH_DIRECTORY/repo.git" >expect &&
+		test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
 		test_cmp expect actual &&
 		echo haha >data2 &&
 		git add data2 &&
-- 
1.9.1.345.ga1a145c
-- 8< --

^ permalink raw reply related	[flat|nested] 169+ messages in thread

* Re: [PATCH v3 18/25] setup.c: support multi-checkout repo setup
  2014-03-25 14:07       ` Duy Nguyen
@ 2014-03-26 21:03         ` Junio C Hamano
  0 siblings, 0 replies; 169+ messages in thread
From: Junio C Hamano @ 2014-03-26 21:03 UTC (permalink / raw)
  To: Duy Nguyen; +Cc: Git Mailing List, Torsten Bögershausen

Duy Nguyen <pclouds@gmail.com> writes:

> On Tue, Mar 25, 2014 at 08:52:13PM +0700, Duy Nguyen wrote:
>> On Mon, Mar 24, 2014 at 9:52 PM, Torsten Bögershausen <tboegi@web.de> wrote:
>> > Did I report that t1501  fails when  there is a softlink in $PWD ?
>> > /home/tb/projects is a softlink to /disc5/projects/
>> 
>> Yes you did and I forgot. I have fixed it, running test suite and will
>> send the reroll soon.
>
> Junio, it seems you have picked up all minor changes after
> v5. Resending the whole series for one fix seems overkill. Could you
> just --autosquash this one in?

Gladly; thanks for a quick turnaround.

> -- 8< --
> Subject: [PATCH] fixup! setup.c: support multi-checkout repo setup
>
> ---
>  t/t1501-worktree.sh | 8 ++++----
>  1 file changed, 4 insertions(+), 4 deletions(-)
>
> diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh
> index 2ac4424..e6ac7a4 100755
> --- a/t/t1501-worktree.sh
> +++ b/t/t1501-worktree.sh
> @@ -359,7 +359,7 @@ test_expect_success 'GIT_DIR set (1)' '
>  	(
>  		cd work &&
>  		GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
> -		echo "$TRASH_DIRECTORY/repo.git" >expect &&
> +		test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
>  		test_cmp expect actual
>  	)
>  '
> @@ -370,7 +370,7 @@ test_expect_success 'GIT_DIR set (2)' '
>  	(
>  		cd work &&
>  		GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
> -		echo "$TRASH_DIRECTORY/repo.git" >expect &&
> +		test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
>  		test_cmp expect actual
>  	)
>  '
> @@ -381,7 +381,7 @@ test_expect_success 'Auto discovery' '
>  	(
>  		cd work &&
>  		git rev-parse --git-common-dir >actual &&
> -		echo "$TRASH_DIRECTORY/repo.git" >expect &&
> +		test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
>  		test_cmp expect actual &&
>  		echo haha >data1 &&
>  		git add data1 &&
> @@ -399,7 +399,7 @@ test_expect_success '$GIT_DIR/common overrides core.worktree' '
>  	(
>  		cd work &&
>  		git rev-parse --git-common-dir >actual &&
> -		echo "$TRASH_DIRECTORY/repo.git" >expect &&
> +		test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
>  		test_cmp expect actual &&
>  		echo haha >data2 &&
>  		git add data2 &&

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v5 00/28] Support multiple checkouts
  2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
                       ` (28 preceding siblings ...)
  2014-03-09  8:18     ` [PATCH v5 00/28] Support multiple checkouts Eric Sunshine
@ 2014-07-06 20:46     ` Max Kirillov
  2014-07-07 10:25       ` Duy Nguyen
  29 siblings, 1 reply; 169+ messages in thread
From: Max Kirillov @ 2014-07-06 20:46 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git, Junio C Hamano

Hi.

What future does this have? Currently it is marked as
"Stalled", but still mergeable with some trivial conflicts
and seem to be working (except some bugs in interaction with
submodules, see below). It would be very nice if this
feature is officially supported.

I also have a comment about how it interacts with submodules.
Would it be more appropriate to mark "modules" as a
per-checkout directory? Because each of the working tree's
submodule is obviously a separated directory in filesystem,
and in most cases (at least in my practice) they are
checked-out to different revisions.

So, currently (before proper linking of submodules checkouts
implemented), if make submodules per-checkout (actually it
appears to somehow work even with current code, maybe
because some submodule code ignores the common_dir), one
could run "git submodule update" if necessary, and get fully
separated clones, which would work normally.

It still may break if submodules are removed, added or
renamed, but this seems to be inevitable until config is
separated to per-checkout and common parts, which I suppose
is a much bigger task.

-- 
Max

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v5 00/28] Support multiple checkouts
  2014-07-06 20:46     ` Max Kirillov
@ 2014-07-07 10:25       ` Duy Nguyen
  2014-07-07 10:49         ` Dennis Kaarsemaker
  0 siblings, 1 reply; 169+ messages in thread
From: Duy Nguyen @ 2014-07-07 10:25 UTC (permalink / raw)
  To: Max Kirillov; +Cc: Git Mailing List, Junio C Hamano, Dennis Kaarsemaker

On Mon, Jul 7, 2014 at 3:46 AM, Max Kirillov <max@max630.net> wrote:
> Hi.
>
> What future does this have? Currently it is marked as
> "Stalled", but still mergeable with some trivial conflicts
> and seem to be working (except some bugs in interaction with
> submodules, see below). It would be very nice if this
> feature is officially supported.

It's to be re-rolled soon. I have a patch about sparse-checkout and
Dennis may contribute another one to enable "checkout --to" from a
bare repository. By the way Dennis has been testing this feature and
he reported (off-list) it worked fine, which is good news. I have done
nothing so far because my git time (or energy to be precise) is
limited these days, and I wanted to see if Dennis reported any new
bugs.

> I also have a comment about how it interacts with submodules.
> Would it be more appropriate to mark "modules" as a
> per-checkout directory? Because each of the working tree's
> submodule is obviously a separated directory in filesystem,
> and in most cases (at least in my practice) they are
> checked-out to different revisions.

Submodule interaction is something I have avoided so far because I'm
not a big user and admittedly does not follow its development closely.
I think we could get this in first, then let submodule people aware
about this feature and let them decide how to use it.

> So, currently (before proper linking of submodules checkouts
> implemented), if make submodules per-checkout (actually it
> appears to somehow work even with current code, maybe
> because some submodule code ignores the common_dir), one
> could run "git submodule update" if necessary, and get fully
> separated clones, which would work normally.
>
> It still may break if submodules are removed, added or
> renamed, but this seems to be inevitable until config is
> separated to per-checkout and common parts, which I suppose
> is a much bigger task.
-- 
Duy

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v5 00/28] Support multiple checkouts
  2014-07-07 10:25       ` Duy Nguyen
@ 2014-07-07 10:49         ` Dennis Kaarsemaker
  2014-07-07 17:32           ` Max Kirillov
  0 siblings, 1 reply; 169+ messages in thread
From: Dennis Kaarsemaker @ 2014-07-07 10:49 UTC (permalink / raw)
  To: Duy Nguyen; +Cc: Max Kirillov, Git Mailing List, Junio C Hamano

On ma, 2014-07-07 at 17:25 +0700, Duy Nguyen wrote:

> > I also have a comment about how it interacts with submodules.
> > Would it be more appropriate to mark "modules" as a
> > per-checkout directory? Because each of the working tree's
> > submodule is obviously a separated directory in filesystem,
> > and in most cases (at least in my practice) they are
> > checked-out to different revisions.
> 
> Submodule interaction is something I have avoided so far because I'm
> not a big user and admittedly does not follow its development closely.
> I think we could get this in first, then let submodule people aware
> about this feature and let them decide how to use it.

I do intend to use checkout --to and submodule update on the same
repository, but have not yet done so. I will poke at that later this
month. If you can easily reproduce errors, I would appreciate to know
how, because my use of submodules is very limited.
-- 
Dennis Kaarsemaker
http://www.kaarsemaker.net

^ permalink raw reply	[flat|nested] 169+ messages in thread

* Re: [PATCH v5 00/28] Support multiple checkouts
  2014-07-07 10:49         ` Dennis Kaarsemaker
@ 2014-07-07 17:32           ` Max Kirillov
  0 siblings, 0 replies; 169+ messages in thread
From: Max Kirillov @ 2014-07-07 17:32 UTC (permalink / raw)
  To: Dennis Kaarsemaker; +Cc: Duy Nguyen, Git Mailing List, Junio C Hamano

On Mon, Jul 07, 2014 at 12:49:01PM +0200, Dennis Kaarsemaker wrote:
> I do intend to use checkout --to and submodule update on the same
> repository, but have not yet done so. I will poke at that later this
> month. If you can easily reproduce errors, I would appreciate to know
> how, because my use of submodules is very limited.

I have collected all my tests to this script:
https://raw.githubusercontent.com/max630/git/tmp/multiple_work_trees_dev/t/t7410-submodule-checkout-to.sh

-- 
Max

^ permalink raw reply	[flat|nested] 169+ messages in thread

end of thread, other threads:[~2014-07-07 17:33 UTC | newest]

Thread overview: 169+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-02-18 13:39 [PATCH v3 00/25] Support multiple checkouts Nguyễn Thái Ngọc Duy
2014-02-18 13:39 ` [PATCH v3 01/25] path.c: make get_pathname() return strbuf instead of static buffer Nguyễn Thái Ngọc Duy
2014-02-19 20:28   ` Junio C Hamano
2014-02-19 23:26   ` Junio C Hamano
2014-03-01  2:40     ` Duy Nguyen
2014-03-03 17:56       ` Junio C Hamano
2014-02-18 13:39 ` [PATCH v3 02/25] Convert git_snpath() to strbuf_git_path() Nguyễn Thái Ngọc Duy
2014-02-19 23:48   ` Junio C Hamano
2014-02-19 23:54     ` Duy Nguyen
2014-02-20  3:41       ` Junio C Hamano
2014-02-20  3:55         ` Duy Nguyen
2014-02-20 18:54           ` Junio C Hamano
2014-02-20 19:44   ` Junio C Hamano
2014-02-18 13:39 ` [PATCH v3 03/25] path.c: rename vsnpath() to do_git_path() Nguyễn Thái Ngọc Duy
2014-02-18 13:39 ` [PATCH v3 04/25] path.c: group git_path(), git_pathdup() and strbuf_git_path() together Nguyễn Thái Ngọc Duy
2014-02-18 13:39 ` [PATCH v3 05/25] Make git_path() aware of file relocation in $GIT_DIR Nguyễn Thái Ngọc Duy
2014-02-18 13:39 ` [PATCH v3 06/25] *.sh: respect $GIT_INDEX_FILE Nguyễn Thái Ngọc Duy
2014-02-18 13:39 ` [PATCH v3 07/25] reflog: avoid constructing .lock path with git_path Nguyễn Thái Ngọc Duy
2014-02-25 22:44   ` Junio C Hamano
2014-03-01  3:42     ` Duy Nguyen
2014-03-03 18:07       ` Junio C Hamano
2014-02-18 13:39 ` [PATCH v3 08/25] fast-import: use git_path() for accessing .git dir instead of get_git_dir() Nguyễn Thái Ngọc Duy
2014-02-18 13:39 ` [PATCH v3 09/25] commit: use SEQ_DIR instead of hardcoding "sequencer" Nguyễn Thái Ngọc Duy
2014-02-18 13:39 ` [PATCH v3 10/25] Add new environment variable $GIT_COMMON_DIR Nguyễn Thái Ngọc Duy
2014-02-26  1:24   ` Eric Sunshine
2014-02-26 10:55     ` Duy Nguyen
2014-02-26 16:12       ` Philip Oakley
2014-02-26 17:23         ` Eric Sunshine
2014-02-26 19:43       ` Eric Sunshine
2014-02-26 23:58   ` Junio C Hamano
2014-02-27  3:03     ` Duy Nguyen
2014-02-18 13:40 ` [PATCH v3 11/25] git-sh-setup.sh: use rev-parse --git-path to get $GIT_DIR/objects Nguyễn Thái Ngọc Duy
2014-02-27  0:00   ` Junio C Hamano
2014-02-18 13:40 ` [PATCH v3 12/25] *.sh: avoid hardcoding $GIT_DIR/hooks/ Nguyễn Thái Ngọc Duy
2014-02-18 13:40 ` [PATCH v3 13/25] git-stash: avoid hardcoding $GIT_DIR/logs/ Nguyễn Thái Ngọc Duy
2014-02-18 13:40 ` [PATCH v3 14/25] setup.c: convert is_git_directory() to use strbuf Nguyễn Thái Ngọc Duy
2014-02-19 20:17   ` Junio C Hamano
2014-02-20 13:04     ` Duy Nguyen
2014-02-20 19:06       ` Junio C Hamano
2014-02-20 19:36         ` Junio C Hamano
2014-03-01  2:50     ` [PATCH] strbuf: style fix -- top opening bracket on a separate line Nguyễn Thái Ngọc Duy
2014-02-21  3:38   ` [PATCH v3 14/25] setup.c: convert is_git_directory() to use strbuf Eric Sunshine
2014-02-18 13:40 ` [PATCH v3 15/25] setup.c: detect $GIT_COMMON_DIR in is_git_directory() Nguyễn Thái Ngọc Duy
2014-02-27  0:16   ` Junio C Hamano
2014-03-01  3:33     ` Duy Nguyen
2014-02-18 13:40 ` [PATCH v3 16/25] setup.c: convert check_repository_format_gently to use strbuf Nguyễn Thái Ngọc Duy
2014-02-27  0:18   ` Junio C Hamano
2014-02-18 13:40 ` [PATCH v3 17/25] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently() Nguyễn Thái Ngọc Duy
2014-02-27  0:22   ` Junio C Hamano
2014-02-27  2:43     ` Duy Nguyen
2014-02-27 17:48       ` Junio C Hamano
2014-02-18 13:40 ` [PATCH v3 18/25] setup.c: support multi-checkout repo setup Nguyễn Thái Ngọc Duy
2014-02-19 20:19   ` Junio C Hamano
2014-02-27 20:28   ` Junio C Hamano
2014-03-24 14:52   ` Torsten Bögershausen
2014-03-25 13:52     ` Duy Nguyen
2014-03-25 14:07       ` Duy Nguyen
2014-03-26 21:03         ` Junio C Hamano
2014-02-18 13:40 ` [PATCH v3 19/25] wrapper.c: wrapper to open a file, fprintf then close Nguyễn Thái Ngọc Duy
2014-02-18 13:40 ` [PATCH v3 20/25] use new wrapper write_file() for simple file writing Nguyễn Thái Ngọc Duy
2014-02-18 13:40 ` [PATCH v3 21/25] checkout: support checking out into a new working directory Nguyễn Thái Ngọc Duy
2014-02-26 20:06   ` Eric Sunshine
2014-02-26 23:19     ` Duy Nguyen
2014-02-27  6:59       ` Eric Sunshine
2014-02-18 13:40 ` [PATCH v3 22/25] checkout: clean up half-prepared directories in --to mode Nguyễn Thái Ngọc Duy
2014-02-18 13:40 ` [PATCH v3 23/25] checkout: detach if the branch is already checked out elsewhere Nguyễn Thái Ngọc Duy
2014-02-19 20:20   ` Junio C Hamano
2014-02-19 21:52   ` Eric Sunshine
2014-02-18 13:40 ` [PATCH v3 24/25] prune: strategies for linked checkouts Nguyễn Thái Ngọc Duy
2014-02-19 20:32   ` Junio C Hamano
2014-02-19 20:42     ` Junio C Hamano
2014-02-20 13:15     ` Duy Nguyen
2014-02-20 19:55       ` Junio C Hamano
2014-02-19 22:08   ` Eric Sunshine
2014-02-19 22:53     ` Eric Sunshine
2014-02-20 13:19     ` Duy Nguyen
2014-02-18 13:40 ` [PATCH v3 25/25] gc: support prune --repos Nguyễn Thái Ngọc Duy
2014-02-19 20:22   ` Junio C Hamano
2014-02-19 20:57 ` [PATCH v3 00/25] Support multiple checkouts Junio C Hamano
2014-03-01 12:12 ` [PATCH v4 00/27] " Nguyễn Thái Ngọc Duy
2014-03-01 12:12   ` [PATCH v4 01/27] path.c: make get_pathname() return strbuf instead of static buffer Nguyễn Thái Ngọc Duy
2014-03-02 19:51     ` Eric Sunshine
2014-03-03  0:14       ` Duy Nguyen
2014-03-01 12:12   ` [PATCH v4 02/27] Convert git_snpath() to strbuf_git_path() Nguyễn Thái Ngọc Duy
2014-03-03  0:02     ` Eric Sunshine
2014-03-03  0:15       ` Duy Nguyen
2014-03-07  5:03         ` Duy Nguyen
2014-03-07  5:26           ` Eric Sunshine
2014-03-01 12:12   ` [PATCH v4 03/27] path.c: rename vsnpath() to do_git_path() Nguyễn Thái Ngọc Duy
2014-03-01 12:12   ` [PATCH v4 04/27] path.c: group git_path(), git_pathdup() and strbuf_git_path() together Nguyễn Thái Ngọc Duy
2014-03-01 12:12   ` [PATCH v4 05/27] Make git_path() aware of file relocation in $GIT_DIR Nguyễn Thái Ngọc Duy
2014-03-03  1:34     ` Eric Sunshine
2014-03-01 12:12   ` [PATCH v4 06/27] *.sh: respect $GIT_INDEX_FILE Nguyễn Thái Ngọc Duy
2014-03-01 12:12   ` [PATCH v4 07/27] reflog: avoid constructing .lock path with git_path Nguyễn Thái Ngọc Duy
2014-03-01 12:12   ` [PATCH v4 08/27] fast-import: use git_path() for accessing .git dir instead of get_git_dir() Nguyễn Thái Ngọc Duy
2014-03-01 12:12   ` [PATCH v4 09/27] commit: use SEQ_DIR instead of hardcoding "sequencer" Nguyễn Thái Ngọc Duy
2014-03-01 12:12   ` [PATCH v4 10/27] Add new environment variable $GIT_COMMON_DIR Nguyễn Thái Ngọc Duy
2014-03-03  7:29     ` Eric Sunshine
2014-03-01 12:12   ` [PATCH v4 11/27] git-sh-setup.sh: use rev-parse --git-path to get $GIT_DIR/objects Nguyễn Thái Ngọc Duy
2014-03-01 12:12   ` [PATCH v4 12/27] *.sh: avoid hardcoding $GIT_DIR/hooks/ Nguyễn Thái Ngọc Duy
2014-03-03  8:31     ` Eric Sunshine
2014-03-01 12:12   ` [PATCH v4 13/27] git-stash: avoid hardcoding $GIT_DIR/logs/ Nguyễn Thái Ngọc Duy
2014-03-01 15:50     ` Torsten Bögershausen
2014-03-01 12:12   ` [PATCH v4 14/27] setup.c: convert is_git_directory() to use strbuf Nguyễn Thái Ngọc Duy
2014-03-01 12:12   ` [PATCH v4 15/27] setup.c: detect $GIT_COMMON_DIR in is_git_directory() Nguyễn Thái Ngọc Duy
2014-03-01 12:12   ` [PATCH v4 16/27] setup.c: convert check_repository_format_gently to use strbuf Nguyễn Thái Ngọc Duy
2014-03-01 12:12   ` [PATCH v4 17/27] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently() Nguyễn Thái Ngọc Duy
2014-03-01 12:12   ` [PATCH v4 18/27] setup.c: support multi-checkout repo setup Nguyễn Thái Ngọc Duy
2014-03-05 19:42     ` Junio C Hamano
2014-03-08  1:55       ` Duy Nguyen
2014-03-01 12:12   ` [PATCH v4 19/27] wrapper.c: wrapper to open a file, fprintf then close Nguyễn Thái Ngọc Duy
2014-03-01 17:11     ` Torsten Bögershausen
2014-03-04  2:47       ` Eric Sunshine
2014-03-01 12:12   ` [PATCH v4 20/27] use new wrapper write_file() for simple file writing Nguyễn Thái Ngọc Duy
2014-03-01 12:12   ` [PATCH v4 21/27] checkout: support checking out into a new working directory Nguyễn Thái Ngọc Duy
2014-03-01 12:12   ` [PATCH v4 22/27] checkout: clean up half-prepared directories in --to mode Nguyễn Thái Ngọc Duy
2014-03-06 10:06     ` Eric Sunshine
2014-03-01 12:12   ` [PATCH v4 23/27] checkout: detach if the branch is already checked out elsewhere Nguyễn Thái Ngọc Duy
2014-03-01 12:13   ` [PATCH v4 24/27] prune: strategies for linked checkouts Nguyễn Thái Ngọc Duy
2014-03-01 17:12     ` Torsten Bögershausen
2014-03-02  0:01       ` Duy Nguyen
2014-03-05 20:07     ` Junio C Hamano
2014-03-01 12:13   ` [PATCH v4 25/27] gc: style change -- no SP before closing bracket Nguyễn Thái Ngọc Duy
2014-03-01 12:13   ` [PATCH v4 26/27] gc: support prune --repos Nguyễn Thái Ngọc Duy
2014-03-07  6:40     ` Eric Sunshine
2014-03-01 12:13   ` [PATCH v4 27/27] count-objects: report unused files in $GIT_DIR/repos/ Nguyễn Thái Ngọc Duy
2014-03-05  4:25     ` Eric Sunshine
2014-03-05 12:08       ` Duy Nguyen
2014-03-08  2:47   ` [PATCH v5 00/28] Support multiple checkouts Nguyễn Thái Ngọc Duy
2014-03-08  2:47     ` [PATCH v5 01/28] path.c: make get_pathname() return strbuf instead of static buffer Nguyễn Thái Ngọc Duy
2014-03-08  6:11       ` Torsten Bögershausen
2014-03-08  8:57         ` Duy Nguyen
2014-03-08  2:47     ` [PATCH v5 02/28] path.c: make get_pathname() call sites return const char * Nguyễn Thái Ngọc Duy
2014-03-08  2:47     ` [PATCH v5 03/28] Convert git_snpath() to strbuf_git_path() Nguyễn Thái Ngọc Duy
2014-03-17 18:11       ` Junio C Hamano
2014-03-08  2:47     ` [PATCH v5 04/28] path.c: rename vsnpath() to do_git_path() Nguyễn Thái Ngọc Duy
2014-03-08  2:47     ` [PATCH v5 05/28] path.c: group git_path(), git_pathdup() and strbuf_git_path() together Nguyễn Thái Ngọc Duy
2014-03-08  2:47     ` [PATCH v5 06/28] Make git_path() aware of file relocation in $GIT_DIR Nguyễn Thái Ngọc Duy
2014-03-09  8:19       ` Eric Sunshine
2014-03-08  2:47     ` [PATCH v5 07/28] *.sh: respect $GIT_INDEX_FILE Nguyễn Thái Ngọc Duy
2014-03-08  2:48     ` [PATCH v5 08/28] reflog: avoid constructing .lock path with git_path Nguyễn Thái Ngọc Duy
2014-03-08  2:48     ` [PATCH v5 09/28] fast-import: use git_path() for accessing .git dir instead of get_git_dir() Nguyễn Thái Ngọc Duy
2014-03-08  2:48     ` [PATCH v5 10/28] commit: use SEQ_DIR instead of hardcoding "sequencer" Nguyễn Thái Ngọc Duy
2014-03-08  2:48     ` [PATCH v5 11/28] Add new environment variable $GIT_COMMON_DIR Nguyễn Thái Ngọc Duy
2014-03-08  2:48     ` [PATCH v5 12/28] git-sh-setup.sh: use rev-parse --git-path to get $GIT_DIR/objects Nguyễn Thái Ngọc Duy
2014-03-08  2:48     ` [PATCH v5 13/28] *.sh: avoid hardcoding $GIT_DIR/hooks/ Nguyễn Thái Ngọc Duy
2014-03-08  2:48     ` [PATCH v5 14/28] git-stash: avoid hardcoding $GIT_DIR/logs/ Nguyễn Thái Ngọc Duy
2014-03-08  2:48     ` [PATCH v5 15/28] setup.c: convert is_git_directory() to use strbuf Nguyễn Thái Ngọc Duy
2014-03-08  2:48     ` [PATCH v5 16/28] setup.c: detect $GIT_COMMON_DIR in is_git_directory() Nguyễn Thái Ngọc Duy
2014-03-08  2:48     ` [PATCH v5 17/28] setup.c: convert check_repository_format_gently to use strbuf Nguyễn Thái Ngọc Duy
2014-03-08  2:48     ` [PATCH v5 18/28] setup.c: detect $GIT_COMMON_DIR check_repository_format_gently() Nguyễn Thái Ngọc Duy
2014-03-08  2:48     ` [PATCH v5 19/28] setup.c: support multi-checkout repo setup Nguyễn Thái Ngọc Duy
2014-03-08  2:48     ` [PATCH v5 20/28] wrapper.c: wrapper to open a file, fprintf then close Nguyễn Thái Ngọc Duy
2014-03-08  2:48     ` [PATCH v5 21/28] use new wrapper write_file() for simple file writing Nguyễn Thái Ngọc Duy
2014-03-08  2:48     ` [PATCH v5 22/28] checkout: support checking out into a new working directory Nguyễn Thái Ngọc Duy
2014-03-08  2:48     ` [PATCH v5 23/28] checkout: clean up half-prepared directories in --to mode Nguyễn Thái Ngọc Duy
2014-03-08  2:48     ` [PATCH v5 24/28] checkout: detach if the branch is already checked out elsewhere Nguyễn Thái Ngọc Duy
2014-03-08  2:48     ` [PATCH v5 25/28] prune: strategies for linked checkouts Nguyễn Thái Ngọc Duy
2014-03-09  8:20       ` Eric Sunshine
2014-03-08  2:48     ` [PATCH v5 26/28] gc: style change -- no SP before closing bracket Nguyễn Thái Ngọc Duy
2014-03-08  2:48     ` [PATCH v5 27/28] gc: support prune --repos Nguyễn Thái Ngọc Duy
2014-03-08  2:48     ` [PATCH v5 28/28] count-objects: report unused files in $GIT_DIR/repos/ Nguyễn Thái Ngọc Duy
2014-03-09  8:21       ` Eric Sunshine
2014-03-09 11:25         ` Duy Nguyen
2014-03-09  8:18     ` [PATCH v5 00/28] Support multiple checkouts Eric Sunshine
2014-07-06 20:46     ` Max Kirillov
2014-07-07 10:25       ` Duy Nguyen
2014-07-07 10:49         ` Dennis Kaarsemaker
2014-07-07 17:32           ` Max Kirillov

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.