Discussion:
[PATCH v3 00/25] Support multiple checkouts
(too old to reply)
Nguyễn Thái Ngọc Duy
2014-02-18 13:39:49 UTC
Permalink
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=3D2=
39581

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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc 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() togethe=
r
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

--=20
1.8.5.2.240.g8478abd
Nguyễn Thái Ngọc Duy
2014-02-18 13:39:50 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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)
=20
static char bad_path[] =3D "/bad-path/";
=20
-static char *get_pathname(void)
+static struct strbuf *get_pathname()
{
- static char pathname_array[4][PATH_MAX];
+ static struct strbuf pathname_array[4] =3D {
+ STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
+ };
static int index;
- return pathname_array[3 & ++index];
+ struct strbuf *sb =3D &pathname_array[3 & ++index];
+ strbuf_reset(sb);
+ return sb;
}
=20
static char *cleanup_path(char *path)
@@ -34,6 +38,13 @@ static char *cleanup_path(char *path)
return path;
}
=20
+static void strbuf_cleanup_path(struct strbuf *sb)
+{
+ char *path =3D 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);
}
=20
-static char *vsnpath(char *buf, size_t n, const char *fmt, va_list arg=
s)
+static void vsnpath(struct strbuf *buf, const char *fmt, va_list args)
{
const char *git_dir =3D get_git_dir();
- size_t len;
-
- len =3D 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++] =3D '/';
- len +=3D vsnprintf(buf + len, n - len, fmt, args);
- if (len >=3D 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);
}
=20
char *git_snpath(char *buf, size_t n, const char *fmt, ...)
{
- char *ret;
+ struct strbuf *sb =3D get_pathname();
va_list args;
va_start(args, fmt);
- ret =3D vsnpath(buf, n, fmt, args);
+ vsnpath(sb, fmt, args);
va_end(args);
- return ret;
+ if (sb->len >=3D n)
+ strlcpy(buf, bad_path, n);
+ else
+ memcpy(buf, sb->buf, sb->len + 1);
+ return buf;
}
=20
char *git_pathdup(const char *fmt, ...)
{
- char path[PATH_MAX], *ret;
+ struct strbuf *path =3D get_pathname();
va_list args;
va_start(args, fmt);
- ret =3D vsnpath(path, sizeof(path), fmt, args);
+ vsnpath(path, fmt, args);
va_end(args);
- return xstrdup(ret);
+ return strbuf_detach(path, NULL);
}
=20
char *mkpathdup(const char *fmt, ...)
{
- char *path;
struct strbuf sb =3D STRBUF_INIT;
va_list args;
-
va_start(args, fmt);
strbuf_vaddf(&sb, fmt, args);
va_end(args);
- path =3D xstrdup(cleanup_path(sb.buf));
-
- strbuf_release(&sb);
- return path;
+ strbuf_cleanup_path(&sb);
+ return strbuf_detach(&sb, NULL);
}
=20
char *mkpath(const char *fmt, ...)
{
va_list args;
- unsigned len;
- char *pathname =3D get_pathname();
-
+ struct strbuf *pathname =3D get_pathname();
va_start(args, fmt);
- len =3D vsnprintf(pathname, PATH_MAX, fmt, args);
+ strbuf_vaddf(pathname, fmt, args);
va_end(args);
- if (len >=3D PATH_MAX)
- return bad_path;
- return cleanup_path(pathname);
+ return cleanup_path(pathname->buf);
}
=20
char *git_path(const char *fmt, ...)
{
- char *pathname =3D get_pathname();
+ struct strbuf *pathname =3D get_pathname();
va_list args;
- char *ret;
-
va_start(args, fmt);
- ret =3D vsnpath(pathname, PATH_MAX, fmt, args);
+ vsnpath(pathname, fmt, args);
va_end(args);
- return ret;
+ return pathname->buf;
}
=20
void home_config_paths(char **global, char **xdg, char *file)
@@ -158,41 +153,27 @@ void home_config_paths(char **global, char **xdg,=
char *file)
=20
char *git_path_submodule(const char *path, const char *fmt, ...)
{
- char *pathname =3D get_pathname();
- struct strbuf buf =3D STRBUF_INIT;
+ struct strbuf *buf =3D get_pathname();
const char *git_dir;
va_list args;
- unsigned len;
-
- len =3D strlen(path);
- if (len > PATH_MAX-100)
- return bad_path;
=20
- strbuf_addstr(&buf, path);
- if (len && path[len-1] !=3D '/')
- strbuf_addch(&buf, '/');
- strbuf_addstr(&buf, ".git");
+ strbuf_addstr(buf, path);
+ if (buf->len && buf->buf[buf->len - 1] !=3D '/')
+ strbuf_addch(buf, '/');
+ strbuf_addstr(buf, ".git");
=20
- git_dir =3D read_gitfile(buf.buf);
+ git_dir =3D 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 >=3D PATH_MAX)
- return bad_path;
- memcpy(pathname, buf.buf, buf.len + 1);
-
- strbuf_release(&buf);
- len =3D strlen(pathname);
+ strbuf_addch(buf, '/');
=20
va_start(args, fmt);
- len +=3D vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
+ strbuf_vaddf(buf, fmt, args);
va_end(args);
- if (len >=3D PATH_MAX)
- return bad_path;
- return cleanup_path(pathname);
+ strbuf_cleanup_path(buf);
+ return buf->buf;
}
=20
int validate_headref(const char *path)
--=20
1.8.5.2.240.g8478abd
Junio C Hamano
2014-02-19 20:28:59 UTC
Permalink
Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail.com> writes:

(Only nitpicks during this round of review).
Post by Nguyễn Thái Ngọc Duy
-static char *get_pathname(void)
+static struct strbuf *get_pathname()
static struct strbuf *get_pathname(void)
Junio C Hamano
2014-02-19 23:26:33 UTC
Permalink
Post by 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.
il.com>
Post by Nguyễn Thái Ngọc Duy
---
path.c | 119 +++++++++++++++++++++++++++----------------------------=
----------
Post by Nguyễn Thái Ngọc Duy
1 file changed, 50 insertions(+), 69 deletions(-)
Nice.
Post by Nguyễn Thái Ngọc Duy
char *git_pathdup(const char *fmt, ...)
{
- char path[PATH_MAX], *ret;
+ struct strbuf *path =3D get_pathname();
va_list args;
va_start(args, fmt);
- ret =3D 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?
Post by Nguyễn Thái Ngọc Duy
char *mkpathdup(const char *fmt, ...)
{
- char *path;
struct strbuf sb =3D STRBUF_INIT;
va_list args;
-
va_start(args, fmt);
strbuf_vaddf(&sb, fmt, args);
va_end(args);
- path =3D xstrdup(cleanup_path(sb.buf));
-
- strbuf_release(&sb);
- return path;
+ strbuf_cleanup_path(&sb);
+ return strbuf_detach(&sb, NULL);
}
=20
char *mkpath(const char *fmt, ...)
{
va_list args;
- unsigned len;
- char *pathname =3D get_pathname();
-
+ struct strbuf *pathname =3D get_pathname();
va_start(args, fmt);
- len =3D vsnprintf(pathname, PATH_MAX, fmt, args);
+ strbuf_vaddf(pathname, fmt, args);
va_end(args);
- if (len >=3D PATH_MAX)
- return bad_path;
- return cleanup_path(pathname);
+ return cleanup_path(pathname->buf);
}
On the other hand, this one does seem correct.
Post by Nguyễn Thái Ngọc Duy
char *git_path(const char *fmt, ...)
{
- char *pathname =3D get_pathname();
+ struct strbuf *pathname =3D get_pathname();
va_list args;
- char *ret;
-
va_start(args, fmt);
- ret =3D vsnpath(pathname, PATH_MAX, fmt, args);
+ vsnpath(pathname, fmt, args);
va_end(args);
- return ret;
+ return pathname->buf;
}
So does this.

Thanks.
Duy Nguyen
2014-03-01 02:40:23 UTC
Permalink
Post by Junio C Hamano
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
Nguyễn Thái Ngọc Duy
2014-02-18 13:39:52 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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, =
=2E..)
return cleanup_path(buf);
}
=20
-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 a=
rgs)
{
const char *git_dir =3D get_git_dir();
strbuf_addstr(buf, git_dir);
@@ -74,7 +74,7 @@ void strbuf_git_path(struct strbuf *sb, const char *f=
mt, ...)
{
va_list args;
va_start(args, fmt);
- vsnpath(sb, fmt, args);
+ do_git_path(sb, fmt, args);
va_end(args);
}
=20
@@ -83,7 +83,7 @@ char *git_pathdup(const char *fmt, ...)
struct strbuf *path =3D 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 =3D 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;
}
--=20
1.8.5.2.240.g8478abd
Nguyễn Thái Ngọc Duy
2014-02-18 13:39:51 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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 c=
heckout_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 =3D STRBUF_INIT;
char *ref_name =3D mkpath("refs/heads/%s", opts->new_orphan_branch=
);
+ int ret;
=20
temp =3D log_all_ref_updates;
log_all_ref_updates =3D 1;
- if (log_ref_setup(ref_name, log_file, sizeof(log_file))) {
+ ret =3D log_ref_setup(ref_name, &log_file);
+ log_all_ref_updates =3D temp;
+ strbuf_release(&log_file);
+ if (ret) {
fprintf(stderr, _("Can not do reflog for '%s'\n"),
opts->new_orphan_branch);
- log_all_ref_updates =3D temp;
return;
}
- log_all_ref_updates =3D temp;
}
}
else
@@ -651,14 +653,10 @@ static void update_refs_for_switch(const struct c=
heckout_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);
=20
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);
}
=20
-char *git_snpath(char *buf, size_t n, const char *fmt, ...)
+void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
{
- struct strbuf *sb =3D get_pathname();
va_list args;
va_start(args, fmt);
vsnpath(sb, fmt, args);
va_end(args);
- if (sb->len >=3D n)
- strlcpy(buf, bad_path, n);
- else
- memcpy(buf, sb->buf, sb->len + 1);
- return buf;
}
=20
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(con=
st char *refname,
=20
const char *resolve_ref_unsafe(const char *refname, unsigned char *sha=
1, int reading, int *flag)
{
+ struct strbuf sb_path =3D STRBUF_INIT;
int depth =3D MAXDEPTH;
ssize_t len;
char buffer[256];
static char refname_buffer[256];
+ const char *ret;
=20
if (flag)
*flag =3D 0;
@@ -1337,15 +1339,17 @@ const char *resolve_ref_unsafe(const char *refn=
ame, unsigned char *sha1, int rea
return NULL;
=20
for (;;) {
- char path[PATH_MAX];
+ const char *path;
struct stat st;
char *buf;
int fd;
=20
if (--depth < 0)
- return NULL;
+ goto fail;
=20
- git_snpath(path, sizeof(path), "%s", refname);
+ strbuf_reset(&sb_path);
+ strbuf_git_path(&sb_path, "%s", refname);
+ path =3D sb_path.buf;
=20
/*
* We might have to loop back here to avoid a race
@@ -1359,10 +1363,11 @@ const char *resolve_ref_unsafe(const char *refn=
ame, unsigned char *sha1, int rea
stat_ref:
if (lstat(path, &st) < 0) {
if (errno =3D=3D ENOENT)
- return handle_missing_loose_ref(refname, sha1,
- reading, flag);
+ ret =3D handle_missing_loose_ref(refname, sha1,
+ reading, flag);
else
- return NULL;
+ ret =3D NULL;
+ goto done;
}
=20
/* Follow "normalized" - ie "refs/.." symlinks by hand */
@@ -1373,7 +1378,7 @@ const char *resolve_ref_unsafe(const char *refnam=
e, unsigned char *sha1, int rea
/* inconsistent with lstat; retry */
goto stat_ref;
else
- return NULL;
+ goto fail;
}
buffer[len] =3D 0;
if (starts_with(buffer, "refs/") &&
@@ -1389,7 +1394,7 @@ const char *resolve_ref_unsafe(const char *refnam=
e, unsigned char *sha1, int rea
/* Is it a directory? */
if (S_ISDIR(st.st_mode)) {
errno =3D EISDIR;
- return NULL;
+ goto fail;
}
=20
/*
@@ -1402,12 +1407,13 @@ const char *resolve_ref_unsafe(const char *refn=
ame, unsigned char *sha1, int rea
/* inconsistent with lstat; retry */
goto stat_ref;
else
- return NULL;
+ goto fail;
}
+
len =3D 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] =3D '\0';
@@ -1424,9 +1430,10 @@ const char *resolve_ref_unsafe(const char *refna=
me, unsigned char *sha1, int rea
(buffer[40] !=3D '\0' && !isspace(buffer[40]))) {
if (flag)
*flag |=3D REF_ISBROKEN;
- return NULL;
+ goto fail;
}
- return refname;
+ ret =3D refname;
+ goto done;
}
if (flag)
*flag |=3D REF_ISSYMREF;
@@ -1436,10 +1443,15 @@ const char *resolve_ref_unsafe(const char *refn=
ame, unsigned char *sha1, int rea
if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
if (flag)
*flag |=3D REF_ISBROKEN;
- return NULL;
+ goto fail;
}
refname =3D strcpy(refname_buffer, buf);
}
+fail:
+ ret =3D NULL;
+done:
+ strbuf_release(&sb_path);
+ return ret;
}
=20
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;
}
=20
-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 =3D O_APPEND | O_WRONLY;
+ const char *logfile;
=20
- git_snpath(logfile, bufsize, "logs/%s", refname);
+ strbuf_git_path(sb_logfile, "logs/%s", refname);
+ logfile =3D 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 |=3D O_CREAT;
@@ -2762,20 +2776,22 @@ static int log_ref_write(const char *refname, c=
onst unsigned char *old_sha1,
int logfd, result, written, oflags =3D O_APPEND | O_WRONLY;
unsigned maxlen, len;
int msglen;
- char log_file[PATH_MAX];
+ struct strbuf sb_log_file =3D STRBUF_INIT;
+ const char *log_file;
char *logrec;
const char *committer;
=20
if (log_all_ref_updates < 0)
log_all_ref_updates =3D !is_bare_repository();
=20
- result =3D log_ref_setup(refname, log_file, sizeof(log_file));
+ result =3D log_ref_setup(refname, &sb_log_file);
if (result)
- return result;
+ goto done;
+ log_file =3D sb_log_file.buf;
=20
logfd =3D open(log_file, oflags);
if (logfd < 0)
- return 0;
+ goto done;
msglen =3D msg ? strlen(msg) : 0;
committer =3D git_committer_info(0);
maxlen =3D strlen(committer) + msglen + 100;
@@ -2788,9 +2804,13 @@ static int log_ref_write(const char *refname, co=
nst unsigned char *old_sha1,
len +=3D copy_msg(logrec + len - 1, msg) - 1;
written =3D len <=3D maxlen ? write_in_full(logfd, logrec, len) : -1;
free(logrec);
- if (close(logfd) !=3D 0 || written !=3D len)
- return error("Unable to append to %s", log_file);
- return 0;
+ if (close(logfd) !=3D 0 || written !=3D len) {
+ error("Unable to append to %s", log_file);
+ result =3D -1;
+ }
+done:
+ strbuf_release(&sb_log_file);
+ return result;
}
=20
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);
=20
/** 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);
=20
/** Reads log for the value of ref during at_time. **/
extern int read_ref_at(const char *refname, unsigned long at_time, int=
cnt,
--=20
1.8.5.2.240.g8478abd
Junio C Hamano
2014-02-19 23:48:52 UTC
Permalink
@@ -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.
Duy Nguyen
2014-02-19 23:54:39 UTC
Permalink
Post by Junio C Hamano
@@ -651,14 +653,10 @@ static void update_refs_for_switch(const struc=
t checkout_opts *opts,
Post by Junio C Hamano
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);
Post by Junio C Hamano
- git_snpath(ref_file, sizeof(ref_file), "%s", o=
ld->path);
Post by Junio C Hamano
- if (!file_exists(ref_file) && file_exists(log_=
file))
Post by Junio C Hamano
- 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.
--=20
Duy
Junio C Hamano
2014-02-20 03:41:52 UTC
Permalink
Post by Duy Nguyen
Post by Junio C Hamano
- }
+ 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.
Duy Nguyen
2014-02-20 03:55:29 UTC
Permalink
Post by Junio C Hamano
Post by Duy Nguyen
Post by Junio C Hamano
- }
+ 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
Junio C Hamano
2014-02-20 18:54:50 UTC
Permalink
Post by Duy Nguyen
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.
Junio C Hamano
2014-02-20 19:44:10 UTC
Permalink
@@ -2717,17 +2729,19 @@ static int copy_msg(char *buf, const char *ms=
g)
return cp - buf;
}
=20
-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 =3D O_APPEND | O_WRONLY;
+ const char *logfile;
=20
- git_snpath(logfile, bufsize, "logs/%s", refname);
+ strbuf_git_path(sb_logfile, "logs/%s", refname);
+ logfile =3D 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?
Nguyễn Thái Ngọc Duy
2014-02-18 13:39:53 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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);
}
=20
+char *git_path(const char *fmt, ...)
+{
+ struct strbuf *pathname =3D 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 =3D get_pathname();
@@ -109,16 +119,6 @@ char *mkpath(const char *fmt, ...)
return cleanup_path(pathname->buf);
}
=20
-char *git_path(const char *fmt, ...)
-{
- struct strbuf *pathname =3D 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 =3D getenv("XDG_CONFIG_HOME");
--=20
1.8.5.2.240.g8478abd
Nguyễn Thái Ngọc Duy
2014-02-18 13:39:54 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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-pa=
rse.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 st=
atus.
repository. If <path> is a gitfile then the resolved path
to the real repository is printed.
=20
+--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, con=
st char *prefix)
for (i =3D 1; i < argc; i++) {
const char *arg =3D argv[i];
=20
+ 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;
=20
/*
* 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;
=20
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;
=20
/*
* Repository-local GIT_* environment variables; see cache.h for detai=
ls.
@@ -137,15 +138,19 @@ static void setup_git_env(void)
if (!git_object_dir) {
git_object_dir =3D xmalloc(strlen(git_dir) + 9);
sprintf(git_object_dir, "%s/objects", git_dir);
- }
+ } else
+ git_db_env =3D 1;
git_index_file =3D getenv(INDEX_ENVIRONMENT);
if (!git_index_file) {
git_index_file =3D xmalloc(strlen(git_dir) + 7);
sprintf(git_index_file, "%s/index", git_dir);
- }
+ } else
+ git_index_env =3D 1;
git_graft_file =3D getenv(GRAFT_ENVIRONMENT);
if (!git_graft_file)
git_graft_file =3D git_pathdup("info/grafts");
+ else
+ git_graft_env =3D 1;
if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
read_replace_refs =3D 0;
namespace =3D 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);
}
=20
+static int dir_prefix(const char *buf, const char *dir)
+{
+ int len =3D strlen(dir);
+ return !strncmp(buf, dir, len) &&
+ (is_dir_sep(buf[len]) || buf[len] =3D=3D '\0');
+}
+
+/* $buf =3D~ m|$dir/+$file| but without regex */
+static int is_dir_file(const char *buf, const char *dir, const char *f=
ile)
+{
+ int len =3D 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 *newdi=
r)
+{
+ int newlen =3D strlen(newdir);
+ int need_sep =3D (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] =3D '/';
+}
+
+static void adjust_git_path(struct strbuf *buf, int git_dir_len)
+{
+ const char *base =3D 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 a=
rgs)
{
const char *git_dir =3D 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 =3D buf->len;
strbuf_vaddf(buf, fmt, args);
+ adjust_git_path(buf, gitdir_len);
strbuf_cleanup_path(buf);
}
=20
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')\" =3D '$expected'=
"
}
=20
+test_git_path() {
+ test_expect_success "git-path $1 $2 =3D> $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 ./
=20
+test_git_path A=3DB info/grafts .git/info/grafts
+test_git_path GIT_GRAFT_FILE=3Dfoo info/grafts foo
+test_git_path GIT_GRAFT_FILE=3Dfoo info/////grafts foo
+test_git_path GIT_INDEX_FILE=3Dfoo index foo
+test_git_path GIT_INDEX_FILE=3Dfoo index/foo .git/index/foo
+test_git_path GIT_INDEX_FILE=3Dfoo index2 .git/index2
+test_expect_success 'setup fake objects directory foo' 'mkdir foo'
+test_git_path GIT_OBJECT_DIRECTORY=3Dfoo objects foo
+test_git_path GIT_OBJECT_DIRECTORY=3Dfoo objects/foo foo/foo
+test_git_path GIT_OBJECT_DIRECTORY=3Dfoo objects2 .git/objects2
+
test_done
--=20
1.8.5.2.240.g8478abd
Nguyễn Thái Ngọc Duy
2014-02-18 13:39:58 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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 =3D FROM_MERGE;
else if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
whence =3D FROM_CHERRY_PICK;
- if (file_exists(git_path("sequencer")))
+ if (file_exists(git_path(SEQ_DIR)))
sequencer_in_use =3D 1;
}
else
--=20
1.8.5.2.240.g8478abd
Nguyễn Thái Ngọc Duy
2014-02-18 13:39:57 UTC
Permalink
This allows git_path() to redirect info/fast-import to another place
if needed

Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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)
=20
static char* make_fast_import_path(const char *path)
{
- struct strbuf abs_path =3D 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));
}
=20
static void option_import_marks(const char *marks,
--=20
1.8.5.2.240.g8478abd
Nguyễn Thái Ngọc Duy
2014-02-18 13:39:56 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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 uns=
igned char *sha1, int unused,
if (!file_exists(log_file))
goto finish;
if (!cmd->dry_run) {
- newlog_path =3D git_pathdup("logs/%s.lock", ref);
+ newlog_path =3D mkpathdup("%s.lock", log_file);
cb.newlog =3D fopen(newlog_path, "w");
}
=20
--=20
1.8.5.2.240.g8478abd
Junio C Hamano
2014-02-25 22:44:37 UTC
Permalink
git_path() soon understands the path given to it. Some paths "abc" ma=
y
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?
il.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 u=
nsigned char *sha1, int unused,
if (!file_exists(log_file))
goto finish;
if (!cmd->dry_run) {
- newlog_path =3D git_pathdup("logs/%s.lock", ref);
+ newlog_path =3D mkpathdup("%s.lock", log_file);
cb.newlog =3D fopen(newlog_path, "w");
}
Duy Nguyen
2014-03-01 03:42:51 UTC
Permalink
Post by Junio C Hamano
git_path() soon understands the path given to it. Some paths "abc" m=
ay
Post by Junio C Hamano
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.
Post by Junio C Hamano
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.
--=20
Duy
Nguyễn Thái Ngọc Duy
2014-02-18 13:40:00 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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=3D"$GIT_DIR/objects"}
+ : ${GIT_OBJECT_DIRECTORY=3D"`git rev-parse --git-path objects`"}
fi
=20
peel_committish () {
--=20
1.8.5.2.240.g8478abd
Junio C Hamano
2014-02-27 00:00:06 UTC
Permalink
Post by 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.
il.com>
Post by Nguyễn Thái Ngọc Duy
---
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=3D"$GIT_DIR/objects"}
+ : ${GIT_OBJECT_DIRECTORY=3D"`git rev-parse --git-path objects`"}
$(...) is the preferred way over `...` in this codebase.
Post by Nguyễn Thái Ngọc Duy
fi
=20
peel_committish () {
Nguyễn Thái Ngọc Duy
2014-02-18 13:39:59 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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.
=20
+'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)
=20
/* 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_en=
v;
=20
/*
* 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;
=20
-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;
=20
/*
* Repository-local GIT_* environment variables; see cache.h for detai=
ls.
@@ -134,10 +134,16 @@ static void setup_git_env(void)
git_dir =3D DEFAULT_GIT_DIR_ENVIRONMENT;
gitfile =3D read_gitfile(git_dir);
git_dir =3D xstrdup(gitfile ? gitfile : git_dir);
+ git_common_dir =3D getenv(GIT_COMMON_DIR_ENVIRONMENT);
+ if (git_common_dir) {
+ git_common_dir_env =3D 1;
+ git_common_dir =3D xstrdup(git_common_dir);
+ } else
+ git_common_dir =3D git_dir;
git_object_dir =3D getenv(DB_ENVIRONMENT);
if (!git_object_dir) {
- git_object_dir =3D xmalloc(strlen(git_dir) + 9);
- sprintf(git_object_dir, "%s/objects", git_dir);
+ git_object_dir =3D xmalloc(strlen(git_common_dir) + 9);
+ sprintf(git_object_dir, "%s/objects", git_common_dir);
} else
git_db_env =3D 1;
git_index_file =3D getenv(INDEX_ENVIRONMENT);
@@ -173,6 +179,11 @@ const char *get_git_dir(void)
return git_dir;
}
=20
+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] =3D '/';
}
=20
+static void update_common_dir(struct strbuf *buf, int git_dir_len)
+{
+ const char *common_dir_list[] =3D {
+ "branches", "hooks", "info", "logs", "lost-found", "modules",
+ "objects", "refs", "remotes", "rr-cache", "svn",
+ NULL
+ };
+ const char *common_top_file_list[] =3D {
+ "config", "gc.pid", "packed-refs", "shallow", NULL
+ };
+ char *base =3D buf->buf + git_dir_len;
+ const char **p;
+ if (is_dir_file(base, "logs", "HEAD"))
+ return; /* keep this in $GIT_DIR */
+ for (p =3D common_dir_list; *p; p++)
+ if (dir_prefix(base, *p)) {
+ replace_dir(buf, git_dir_len, get_git_common_dir());
+ return;
+ }
+ for (p =3D 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 =3D 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);
}
=20
static void do_git_path(struct strbuf *buf, const char *fmt, va_list a=
rgs)
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=3Dfoo objects foo
test_git_path GIT_OBJECT_DIRECTORY=3Dfoo objects/foo foo/foo
test_git_path GIT_OBJECT_DIRECTORY=3Dfoo objects2 .git/objects2
+test_expect_success 'setup common repository' 'git --git-dir=3Dbar ini=
t'
+test_git_path GIT_COMMON_DIR=3Dbar index .git/index
+test_git_path GIT_COMMON_DIR=3Dbar HEAD .git/HEAD
+test_git_path GIT_COMMON_DIR=3Dbar logs/HEAD .git/logs/=
HEAD
+test_git_path GIT_COMMON_DIR=3Dbar objects bar/object=
s
+test_git_path GIT_COMMON_DIR=3Dbar objects/bar bar/object=
s/bar
+test_git_path GIT_COMMON_DIR=3Dbar info/exclude bar/info/e=
xclude
+test_git_path GIT_COMMON_DIR=3Dbar remotes/bar bar/remote=
s/bar
+test_git_path GIT_COMMON_DIR=3Dbar branches/bar bar/branch=
es/bar
+test_git_path GIT_COMMON_DIR=3Dbar logs/refs/heads/master bar/logs/r=
efs/heads/master
+test_git_path GIT_COMMON_DIR=3Dbar refs/heads/master bar/refs/h=
eads/master
+test_git_path GIT_COMMON_DIR=3Dbar hooks/me bar/hooks/=
me
+test_git_path GIT_COMMON_DIR=3Dbar config bar/config
+test_git_path GIT_COMMON_DIR=3Dbar packed-refs bar/packed=
-refs
+test_git_path GIT_COMMON_DIR=3Dbar shallow bar/shallo=
w
=20
test_done
--=20
1.8.5.2.240.g8478abd
Eric Sunshine
2014-02-26 01:24:47 UTC
Permalink
On Tue, Feb 18, 2014 at 8:39 AM, Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc =
Post by 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 mor=
e
Post by Nguyễn Thái Ngọc Duy
linked working directories. These working directories and the main
repository share the same repository directory.
il.com>
Post by Nguyễn Thái Ngọc Duy
---
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.
+ If this variable is set to a path, non-worktree files that ar=
e
Post by Nguyễn Thái Ngọc Duy
+ 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...
=46or 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?
Post by Nguyễn Thái Ngọc Duy
Git Commits
~~~~~~~~~~~
Duy Nguyen
2014-02-26 10:55:54 UTC
Permalink
+ 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
Philip Oakley
2014-02-26 16:12:01 UTC
Permalink
On Wed, Feb 26, 2014 at 8:24 AM, Eric Sunshine
+ 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
Eric Sunshine
2014-02-26 17:23:27 UTC
Permalink
Post by Philip Oakley
Post by Duy Nguyen
+ 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
Eric Sunshine
2014-02-26 19:43:59 UTC
Permalink
Post by Duy Nguyen
+ 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.
Junio C Hamano
2014-02-26 23:58:42 UTC
Permalink
Post by Nguyễn Thái Ngọc Duy
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.
Post by Nguyễn Thái Ngọc Duy
The redirection is done by git_path(), git_pathdup() and
strbuf_git_path(). The selected list of paths goes to $GIT_COMMON_DIR=
,
Post by Nguyễn Thái Ngọc Duy
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)
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 ar=
e
Post by Nguyễn Thái Ngọc Duy
not supported as submodules.
il.com>
Post by Nguyễn Thái Ngọc Duy
---
Other than the "I do not understand why logs/HEAD is an issue", the
other aspect of the above design feels sound to me.
Post by Nguyễn Thái Ngọc Duy
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.
=20
+ 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.
Post by Nguyễn Thái Ngọc Duy
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 le=
n, const char *newdir)
Post by Nguyễn Thái Ngọc Duy
buf->buf[newlen] =3D '/';
}
=20
+static void update_common_dir(struct strbuf *buf, int git_dir_len)
+{
+ const char *common_dir_list[] =3D {
+ "branches", "hooks", "info", "logs", "lost-found", "modules",
+ "objects", "refs", "remotes", "rr-cache", "svn",
+ NULL
+ };
+ const char *common_top_file_list[] =3D {
+ "config", "gc.pid", "packed-refs", "shallow", NULL
+ };
+ char *base =3D 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.
Post by Nguyễn Thái Ngọc Duy
+ return; /* keep this in $GIT_DIR */
+ for (p =3D common_dir_list; *p; p++)
+ if (dir_prefix(base, *p)) {
+ replace_dir(buf, git_dir_len, get_git_common_dir());
+ return;
+ }
+ for (p =3D 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 =3D buf->buf + git_dir_len;
@@ -101,6 +127,8 @@ static void adjust_git_path(struct strbuf *buf, i=
nt git_dir_len)
Post by Nguyễn Thái Ngọc Duy
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);
}
=20
static void do_git_path(struct strbuf *buf, const char *fmt, va_list=
args)
Post by Nguyễn Thái Ngọc Duy
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 director=
y foo' 'mkdir foo'
Post by Nguyễn Thái Ngọc Duy
test_git_path GIT_OBJECT_DIRECTORY=3Dfoo objects foo
test_git_path GIT_OBJECT_DIRECTORY=3Dfoo objects/foo foo/foo
test_git_path GIT_OBJECT_DIRECTORY=3Dfoo objects2 .git/objects2
+test_expect_success 'setup common repository' 'git --git-dir=3Dbar i=
nit'
Post by Nguyễn Thái Ngọc Duy
+test_git_path GIT_COMMON_DIR=3Dbar index .git/ind=
ex
Post by Nguyễn Thái Ngọc Duy
+test_git_path GIT_COMMON_DIR=3Dbar HEAD .git/HEA=
D
Post by Nguyễn Thái Ngọc Duy
+test_git_path GIT_COMMON_DIR=3Dbar logs/HEAD .git/log=
s/HEAD
Post by Nguyễn Thái Ngọc Duy
+test_git_path GIT_COMMON_DIR=3Dbar objects bar/obje=
cts
Post by Nguyễn Thái Ngọc Duy
+test_git_path GIT_COMMON_DIR=3Dbar objects/bar bar/obje=
cts/bar
Post by Nguyễn Thái Ngọc Duy
+test_git_path GIT_COMMON_DIR=3Dbar info/exclude bar/info=
/exclude
Post by Nguyễn Thái Ngọc Duy
+test_git_path GIT_COMMON_DIR=3Dbar remotes/bar bar/remo=
tes/bar
Post by Nguyễn Thái Ngọc Duy
+test_git_path GIT_COMMON_DIR=3Dbar branches/bar bar/bran=
ches/bar
Post by Nguyễn Thái Ngọc Duy
+test_git_path GIT_COMMON_DIR=3Dbar logs/refs/heads/master bar/logs=
/refs/heads/master
Post by Nguyễn Thái Ngọc Duy
+test_git_path GIT_COMMON_DIR=3Dbar refs/heads/master bar/refs=
/heads/master
Post by Nguyễn Thái Ngọc Duy
+test_git_path GIT_COMMON_DIR=3Dbar hooks/me bar/hook=
s/me
Post by Nguyễn Thái Ngọc Duy
+test_git_path GIT_COMMON_DIR=3Dbar config bar/conf=
ig
Post by Nguyễn Thái Ngọc Duy
+test_git_path GIT_COMMON_DIR=3Dbar packed-refs bar/pack=
ed-refs
Post by Nguyễn Thái Ngọc Duy
+test_git_path GIT_COMMON_DIR=3Dbar shallow bar/shal=
low
Post by Nguyễn Thái Ngọc Duy
=20
test_done
Duy Nguyen
2014-02-27 03:03:15 UTC
Permalink
Post by Junio C Hamano
Post by Nguyễn Thái Ngọc Duy
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.
* 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.
--=20
Duy
Nguyễn Thái Ngọc Duy
2014-02-18 13:40:02 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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
=20
# 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=3D$?
test $ret !=3D 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")"
=20
create_stash "$stash_msg" $untracked
--=20
1.8.5.2.240.g8478abd
Nguyễn Thái Ngọc Duy
2014-02-18 13:40:01 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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
=20
- if test -x "$GIT_DIR"/hooks/applypatch-msg
+ hook=3D"`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
=20
if test -f "$dotest/final-commit"
@@ -880,9 +880,10 @@ did you forget to use 'git add'?"
stop_here_user_resolve $this
fi
=20
- if test -x "$GIT_DIR"/hooks/pre-applypatch
+ hook=3D"`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
=20
tree=3D$(git write-tree) &&
@@ -908,18 +909,17 @@ did you forget to use 'git add'?"
echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritte=
n"
fi
=20
- if test -x "$GIT_DIR"/hooks/post-applypatch
- then
- "$GIT_DIR"/hooks/post-applypatch
- fi
+ hook=3D"`git rev-parse --git-path hooks/post-applypatch`"
+ test -x "$hook" && "$hook"
=20
go_next
done
=20
if test -s "$dotest"/rewritten; then
git notes copy --for-rewrite=3Drebase < "$dotest"/rewritten
- if test -x "$GIT_DIR"/hooks/post-rewrite; then
- "$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+ hook=3D"`git rev-parse --git-path hooks/post-rewrite`"
+ if test -x "$hook"; then
+ "$hook" rebase < "$dotest"/rewritten
fi
fi
=20
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=3Drebase < "$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=3D"`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=3Drebase <"$state_dir"/rewritten
- if test -x "$GIT_DIR"/hooks/post-rewrite
- then
- "$GIT_DIR"/hooks/post-rewrite rebase <"$state_dir"/rewritten
- fi
+ hook=3D"`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 () {
=20
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".
=20
. git-sh-setup
-test -x "$GIT_DIR/hooks/commit-msg" &&
- exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
+commitmsg=3D"`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".
=20
. git-sh-setup
-test -x "$GIT_DIR/hooks/pre-commit" &&
- exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
+precommit=3D"`git rev-parse --git-path hooks/pre-commit`"
+test -x "$precommit" && exec "$precommit" ${1+"$@"}
:
--=20
1.8.5.2.240.g8478abd
Nguyễn Thái Ngọc Duy
2014-02-18 13:40:03 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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, cons=
t char *arg)
*/
int is_git_directory(const char *suspect)
{
- char path[PATH_MAX];
- size_t len =3D strlen(suspect);
+ struct strbuf path =3D STRBUF_INIT;
+ int ret =3D 0;
+ size_t len;
=20
- if (PATH_MAX <=3D len + strlen("/objects"))
- die("Too long path: %.*s", 60, suspect);
- strcpy(path, suspect);
+ strbuf_addstr(&path, suspect);
+ len =3D 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;
}
=20
- 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;
=20
- strcpy(path + len, "/HEAD");
- if (validate_headref(path))
- return 0;
+ strbuf_addstr_at(&path, len, "/HEAD");
+ if (validate_headref(path.buf))
+ goto done;
=20
- return 1;
+ ret =3D 1;
+done:
+ strbuf_release(&path);
+ return ret;
}
=20
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, con=
st char *s) {
+ strbuf_setlen(sb, len);
+ strbuf_add(sb, s, strlen(s));
+}
static inline void strbuf_addbuf(struct strbuf *sb, const struct strbu=
f *sb2) {
strbuf_grow(sb, sb2->len);
strbuf_add(sb, sb2->buf, sb2->len);
--=20
1.8.5.2.240.g8478abd
Junio C Hamano
2014-02-19 20:17:32 UTC
Permalink
il.com>
Post by Nguyễn Thái Ngọc Duy
---
(Only minor nits first during this round of review)
Post by Nguyễn Thái Ngọc Duy
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 vo=
id *, size_t);
Post by Nguyễn Thái Ngọc Duy
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, c=
onst 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).
Post by Nguyễn Thái Ngọc Duy
+ 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.
Post by Nguyễn Thái Ngọc Duy
+}
static inline void strbuf_addbuf(struct strbuf *sb, const struct str=
buf *sb2) {
Post by Nguyễn Thái Ngọc Duy
strbuf_grow(sb, sb2->len);
strbuf_add(sb, sb2->buf, sb2->len);
Duy Nguyen
2014-02-20 13:04:31 UTC
Permalink
Post by Junio C Hamano
+ 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.
Post by Junio C Hamano
+}
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
Junio C Hamano
2014-02-20 19:06:46 UTC
Permalink
Post by Duy Nguyen
Post by Junio C Hamano
+ 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))
Junio C Hamano
2014-02-20 19:36:57 UTC
Permalink
Post by Junio C Hamano
Post by Duy Nguyen
Post by Junio C Hamano
+ 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
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.
Nguyễn Thái Ngọc Duy
2014-03-01 02:50:55 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
Post by Junio C Hamano
Post by Nguyễn Thái Ngọc Duy
--- a/strbuf.h
+++ b/strbuf.h
@@ -116,6 +116,10 @@ extern void strbuf_add(struct strbuf *, const =
void *, size_t);
Post by Junio C Hamano
Post by Nguyễn Thái Ngọc Duy
=C2=A0static inline void strbuf_addstr(struct strbuf *sb, const cha=
r *s) {
Post by Junio C Hamano
Post by Nguyễn Thái Ngọc Duy
=C2=A0 =C2=A0 =C2=A0 strbuf_add(sb, s, strlen(s));
=C2=A0}
+static inline void strbuf_addstr_at(struct strbuf *sb, size_t len,=
const char *s) {
Post by Junio C Hamano
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 =3D *a;
*a =3D *b;
*b =3D tmp;
}
=20
/*----- 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;
}
=20
extern void strbuf_grow(struct strbuf *, size_t);
=20
-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 =3D len;
@@ -97,7 +100,8 @@ static inline struct strbuf **strbuf_split(const str=
uct strbuf *sb,
extern void strbuf_list_free(struct strbuf **);
=20
/*----- 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++] =3D c;
sb->buf[sb->len] =3D '\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);
=20
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 strbu=
f *sb2) {
+static inline void strbuf_addbuf(struct strbuf *sb, const struct strbu=
f *sb2)
+{
strbuf_grow(sb, sb2->len);
strbuf_add(sb, sb2->buf, sb2->len);
}
--=20
1.9.0.40.gaa8c3ea
Eric Sunshine
2014-02-21 03:38:00 UTC
Permalink
On Tue, Feb 18, 2014 at 8:40 AM, Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc =
il.com>
Post by Nguyễn Thái Ngọc Duy
---
index 73e80ce..aec9fdb 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -116,6 +116,10 @@ extern void strbuf_add(struct strbuf *, const vo=
id *, size_t);
Post by Nguyễn Thái Ngọc Duy
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, c=
onst char *s) {
Post by Nguyễn Thái Ngọc Duy
+ strbuf_setlen(sb, len);
+ strbuf_add(sb, s, strlen(s));
+}
Update Documentation/technical/api-strbuf.txt?
Post by Nguyễn Thái Ngọc Duy
static inline void strbuf_addbuf(struct strbuf *sb, const struct str=
buf *sb2) {
Post by Nguyễn Thái Ngọc Duy
strbuf_grow(sb, sb2->len);
strbuf_add(sb, sb2->buf, sb2->len);
--
1.8.5.2.240.g8478abd
Nguyễn Thái Ngọc Duy
2014-02-18 13:40:04 UTC
Permalink
If the file "$GIT_DIR/commondir" exists, it contains the value of
$GIT_COMMON_DIR.

Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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/git=
repository-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].
=20
+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.
=20
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);
}
=20
+static void get_common_dir(struct strbuf *sb, const char *gitdir)
+{
+ struct strbuf data =3D STRBUF_INIT;
+ struct strbuf path =3D STRBUF_INIT;
+ const char *git_common_dir =3D 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) <=3D 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);
+}
=20
/*
* Test if it looks like we're at a git directory.
@@ -188,14 +212,20 @@ int is_git_directory(const char *suspect)
int ret =3D 0;
size_t len;
=20
- 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 =3D 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;
=20
- strbuf_addstr_at(&path, len, "/HEAD");
- if (validate_headref(path.buf))
- goto done;
-
ret =3D 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] =3D=3D '\n' ||
+ sb->buf[sb->len - 1] =3D=3D '\r'))
+ sb->len--;
+ sb->buf[sb->len] =3D '\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 p=
os, 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);
=20
extern void strbuf_add_commented_lines(struct strbuf *out, const char =
*buf, size_t size);
=20
--=20
1.8.5.2.240.g8478abd
Junio C Hamano
2014-02-27 00:16:27 UTC
Permalink
Post by Nguyễn Thái Ngọc Duy
If the file "$GIT_DIR/commondir" exists, it contains the value of
$GIT_COMMON_DIR.
il.com>
Post by Nguyễn Thái Ngọc Duy
---
Documentation/gitrepository-layout.txt | 4 ++++
setup.c | 38 ++++++++++++++++++++++++=
++++------
Post by Nguyễn Thái Ngọc Duy
strbuf.c | 8 +++++++
strbuf.h | 1 +
4 files changed, 45 insertions(+), 6 deletions(-)
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/g=
itrepository-layout.txt
Post by Nguyễn Thái Ngọc Duy
index aa03882..9bfe0f1 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
and maintained by shallow clone mechanism. See `--depth`
option to linkgit:git-clone[1] and linkgit:git-fetch[1].
=20
+ If this file exists, $GIT_COMMON_DIR will be set to the path
+ specified in this file if it is not set.
+
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").
Post by Nguyễn Thái Ngọc Duy
+ if (file_exists(path.buf)) {
+ if (strbuf_read_file(&data, path.buf, 0) <=3D 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?
Post by Nguyễn Thái Ngọc Duy
+ 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.
Post by Nguyễn Thái Ngọc Duy
@@ -188,14 +212,20 @@ int is_git_directory(const char *suspect)
int ret =3D 0;
size_t len;
=20
- 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).
Post by Nguyễn Thái Ngọc Duy
+void strbuf_chomp(struct strbuf *sb)
+{
+ while (sb->len && (sb->buf[sb->len - 1] =3D=3D '\n' ||
+ sb->buf[sb->len - 1] =3D=3D '\r'))
+ sb->len--;
+ sb->buf[sb->len] =3D '\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.
Duy Nguyen
2014-03-01 03:33:23 UTC
Permalink
Post by Junio C Hamano
+ 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.
Post by Junio C Hamano
@@ -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
Nguyễn Thái Ngọc Duy
2014-02-18 13:40:06 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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 ch=
ar *gitdir, int *nongit_ok)
const char *repo_config;
int ret =3D 0;
=20
+ get_common_dir(&sb, gitdir);
+ strbuf_addstr(&sb, "/config");
+ repo_config =3D 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 cha=
r *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 =3D sb.buf;
git_config_early(check_repository_format_version, NULL, repo_config);
if (GIT_REPO_VERSION < repository_format_version) {
if (!nongit_ok)
--=20
1.8.5.2.240.g8478abd
Junio C Hamano
2014-02-27 00:22:56 UTC
Permalink
il.com>
Post by Nguyễn Thái Ngọc Duy
---
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.
Post by Nguyễn Thái Ngọc Duy
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)
Post by Nguyễn Thái Ngọc Duy
const char *repo_config;
int ret =3D 0;
=20
+ get_common_dir(&sb, gitdir);
+ strbuf_addstr(&sb, "/config");
+ repo_config =3D 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 c=
har *gitdir, int *nongit_ok)
Post by Nguyễn Thái Ngọc Duy
* Use a gentler version of git_config() to check if this repo
* is a good one.
*/
- strbuf_addf(&sb, "%s/config", gitdir);
- repo_config =3D sb.buf;
git_config_early(check_repository_format_version, NULL, repo_config=
);
Post by Nguyễn Thái Ngọc Duy
if (GIT_REPO_VERSION < repository_format_version) {
if (!nongit_ok)
Duy Nguyen
2014-02-27 02:43:56 UTC
Permalink
ail.com>
Post by Junio C Hamano
---
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.
--=20
Duy
Junio C Hamano
2014-02-27 17:48:19 UTC
Permalink
mail.com>
Post by Duy Nguyen
Post by Junio C Hamano
---
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.
Nguyễn Thái Ngọc Duy
2014-02-18 13:40:05 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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)
=20
static int check_repository_format_gently(const char *gitdir, int *non=
git_ok)
{
- char repo_config[PATH_MAX+1];
+ struct strbuf sb =3D STRBUF_INIT;
+ const char *repo_config;
+ int ret =3D 0;
=20
/*
* git_config() can't be used here because it calls git_pathdup()
@@ -292,7 +294,8 @@ static int check_repository_format_gently(const cha=
r *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 =3D 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 ch=
ar *gitdir, int *nongit_ok)
GIT_REPO_VERSION, repository_format_version);
warning("Please upgrade Git");
*nongit_ok =3D -1;
- return -1;
+ ret =3D -1;
}
- return 0;
+ strbuf_release(&sb);
+ return ret;
}
=20
/*
--=20
1.8.5.2.240.g8478abd
Junio C Hamano
2014-02-27 00:18:04 UTC
Permalink
il.com>
---
Nice.
Nguyễn Thái Ngọc Duy
2014-02-18 13:39:55 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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 =3D "$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
=20
TMP=3D"$GIT_DIR/.git-stash.$$"
-TMPindex=3D${GIT_INDEX_FILE-"$GIT_DIR/index"}.stash.$$
+TMPindex=3D${GIT_INDEX_FILE-"`git rev-parse --git-path index`"}.stash.=
$$
trap 'rm -f "$TMP-"* "$TMPindex"' 0
=20
ref_stash=3Drefs/stash
--=20
1.8.5.2.240.g8478abd
Nguyễn Thái Ngọc Duy
2014-02-18 13:40:10 UTC
Permalink
"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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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-checkou=
t.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 s=
electively discard
edits from your current working tree. See the ``Interactive Mode''
section of linkgit:git-add[1] to learn how to operate the `--patch` mo=
de.
=20
+--to=3D<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
------------
=20
+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
--------
=20
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/git=
repository-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.
=20
+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;
};
=20
static int post_checkout_hook(struct commit *old, struct commit *new,
@@ -250,6 +254,9 @@ static int checkout_paths(const struct checkout_opt=
s *opts,
die(_("Cannot update paths and switch to branch '%s' at the same tim=
e."),
opts->new_branch);
=20
+ if (opts->new_worktree)
+ die(_("'%s' cannot be used with updating paths"), "--to");
+
if (opts->patch_mode)
return run_add_interactive(revision, "--patch=3Dcheckout",
&opts->pathspec);
@@ -486,7 +493,7 @@ static int merge_working_tree(const struct checkout=
_opts *opts,
topts.dir->flags |=3D DIR_SHOW_IGNORED;
setup_standard_excludes(topts.dir);
}
- tree =3D parse_tree_indirect(old->commit ?
+ tree =3D 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_op=
ts *opts,
return ret;
}
=20
- if (!opts->quiet && !old.path && old.commit && new->commit !=3D old.c=
ommit)
+ if (!opts->quiet && !old.path && old.commit &&
+ new->commit !=3D old.commit && !opts->new_worktree_mode)
orphaned_commit_warning(old.commit, new->commit);
=20
update_refs_for_switch(opts, &old, new);
@@ -806,6 +814,74 @@ static int switch_branches(const struct checkout_o=
pts *opts,
return ret || writeout_error;
}
=20
+static int prepare_linked_checkout(const struct checkout_opts *opts,
+ struct branch_info *new)
+{
+ struct strbuf sb_git =3D STRBUF_INIT, sb_repo =3D STRBUF_INIT;
+ struct strbuf sb =3D STRBUF_INIT;
+ const char *path =3D opts->new_worktree;
+ struct stat st;
+ const char *name;
+ struct child_process cp;
+ int counter =3D 0, len;
+
+ if (!new->commit)
+ die(_("no branch specified"));
+
+ len =3D strlen(path);
+ if (!len || is_dir_sep(path[len - 1]))
+ die(_("'--to' argument '%s' cannot end with a slash"), path);
+
+ for (name =3D path + len - 1; name > path; name--)
+ if (is_dir_sep(*name)) {
+ name++;
+ break;
+ }
+ strbuf_addstr(&sb_repo, git_path("repos/%s", name));
+ len =3D 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 =3D 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 =3D 1;
+ cp.argv =3D opts->saved_argv;
+ return run_command(&cp);
+}
+
static int git_checkout_config(const char *var, const char *value, voi=
d *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);
=20
+ 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, con=
st 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(),
};
=20
@@ -1117,6 +1198,9 @@ int cmd_checkout(int argc, const char **argv, con=
st char *prefix)
opts.overwrite_ignore =3D 1;
opts.prefix =3D prefix;
=20
+ opts.saved_argv =3D xmalloc(sizeof(const char *) * (argc + 2));
+ memcpy(opts.saved_argv, argv, sizeof(const char *) * (argc + 1));
+
gitmodules_config();
git_config(git_checkout_config, &opts);
=20
@@ -1125,6 +1209,11 @@ int cmd_checkout(int argc, const char **argv, co=
nst char *prefix)
argc =3D parse_options(argc, argv, prefix, options, checkout_usage,
PARSE_OPT_KEEP_DASHDASH);
=20
+ /* recursive execution from checkout_new_worktree() */
+ opts.new_worktree_mode =3D getenv("GIT_CHECKOUT_NEW_WORKTREE") !=3D N=
ULL;
+ if (opts.new_worktree_mode)
+ opts.new_worktree =3D NULL;
+
if (conflict_style) {
opts.merge =3D 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=3D'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
--=20
1.8.5.2.240.g8478abd
Eric Sunshine
2014-02-26 20:06:49 UTC
Permalink
On Tue, Feb 18, 2014 at 8:40 AM, Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc =
Post by 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" agai=
n
Post by Nguyễn Thái Ngọc Duy
on the new worktree with the same arguments except "--to" is taken
out. The second checkout execution, which is not contaminated with an=
y
Post by Nguyễn Thái Ngọc Duy
info from the current repository, will actually check out and
everything that normal "git checkout" does.
il.com>
Post by Nguyễn Thái Ngọc Duy
---
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,
Post by Nguyễn Thái Ngọc Duy
return ret || writeout_error;
}
+static int prepare_linked_checkout(const struct checkout_opts *opts,
+ struct branch_info *new)
+{
+ struct strbuf sb_git =3D STRBUF_INIT, sb_repo =3D STRBUF_INIT=
;
Post by Nguyễn Thái Ngọc Duy
+ struct strbuf sb =3D STRBUF_INIT;
+ const char *path =3D opts->new_worktree;
+ struct stat st;
+ const char *name;
+ struct child_process cp;
+ int counter =3D 0, len;
+
+ if (!new->commit)
+ die(_("no branch specified"));
+
+ len =3D 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?
Post by Nguyễn Thái Ngọc Duy
+ for (name =3D path + len - 1; name > path; name--)
+ if (is_dir_sep(*name)) {
+ name++;
+ break;
+ }
+ strbuf_addstr(&sb_repo, git_path("repos/%s", name));
+ len =3D sb_repo.len;
+ if (safe_create_leading_directories_const(sb_repo.buf))
+ die_errno(_("could not create leading directories of =
'%s'"),
Post by Nguyễn Thái Ngọc Duy
+ sb_repo.buf);
+ while (!stat(sb_repo.buf, &st)) {
+ counter++;
+ strbuf_setlen(&sb_repo, len);
+ strbuf_addf(&sb_repo, "%d", counter);
+ }
+ name =3D sb_repo.buf + len - strlen(name);
+ if (mkdir(sb_repo.buf, 0777))
+ die_errno(_("could not create directory of '%s'"), sb=
_repo.buf);
Post by Nguyễn Thái Ngọc Duy
+
+ 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'"),
Post by Nguyễn Thái Ngọc Duy
+ 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=
=2Esha1));
Post by Nguyễn Thái Ngọc Duy
+ 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)"), pat=
h, name);
Post by Nguyễn Thái Ngọc Duy
+
+ 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 =3D 1;
+ cp.argv =3D opts->saved_argv;
+ return run_command(&cp);
+}
+
Duy Nguyen
2014-02-26 23:19:48 UTC
Permalink
Post by Eric Sunshine
+ 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.
Post by Eric Sunshine
+ for (name = path + len - 1; name > path; name--)
+ if (is_dir_sep(*name)) {
+ name++;
+ break;
+ }
--
Duy
Eric Sunshine
2014-02-27 06:59:03 UTC
Permalink
Post by Duy Nguyen
Post by Eric Sunshine
+ 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.
Post by Duy Nguyen
Post by Eric Sunshine
+ for (name = path + len - 1; name > path; name--)
+ if (is_dir_sep(*name)) {
+ name++;
+ break;
+ }
Nguyễn Thái Ngọc Duy
2014-02-18 13:40:11 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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"
=20
static const char * const checkout_usage[] =3D {
N_("git checkout [options] <branch>"),
@@ -814,6 +815,35 @@ static int switch_branches(const struct checkout_o=
pts *opts,
return ret || writeout_error;
}
=20
+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 =3D STRBUF_INIT;
+ if (!is_junk || getpid() !=3D 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 che=
ckout_opts *opts,
struct stat st;
const char *name;
struct child_process cp;
- int counter =3D 0, len;
+ int counter =3D 0, len, ret;
=20
if (!new->commit)
die(_("no branch specified"));
@@ -848,13 +878,21 @@ static int prepare_linked_checkout(const struct c=
heckout_opts *opts,
strbuf_addf(&sb_repo, "%d", counter);
}
name =3D sb_repo.buf + len - strlen(name);
+
+ junk_pid =3D 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 =3D sb_repo.buf;
+ is_junk =3D 1;
=20
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 =3D path;
=20
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 ch=
eckout_opts *opts,
memset(&cp, 0, sizeof(cp));
cp.git_cmd =3D 1;
cp.argv =3D opts->saved_argv;
- return run_command(&cp);
+ ret =3D run_command(&cp);
+ if (!ret)
+ is_junk =3D 0;
+ strbuf_release(&sb);
+ strbuf_release(&sb_repo);
+ strbuf_release(&sb_git);
+ return ret;
+
}
=20
static int git_checkout_config(const char *var, const char *value, voi=
d *cb)
--=20
1.8.5.2.240.g8478abd
Nguyễn Thái Ngọc Duy
2014-02-18 13:40:08 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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, c=
onst 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, ..=
=2E);
=20
/* 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 =3D STRBUF_INIT;
+ int fd =3D 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) !=3D sb.len) {
+ int err =3D errno;
+ close(fd);
+ errno =3D 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;
+}
--=20
1.8.5.2.240.g8478abd
Nguyễn Thái Ngọc Duy
2014-02-18 13:40:09 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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[] =3D "BRANCH_DE=
SCRIPTION";
=20
static int edit_branch_description(const char *branch_name)
{
- FILE *fp;
int status;
struct strbuf buf =3D STRBUF_INIT;
struct strbuf name =3D STRBUF_INIT;
@@ -767,8 +766,7 @@ static int edit_branch_description(const char *bran=
ch_name)
" %s\n"
"Lines starting with '%c' will be stripped.\n",
branch_name, comment_line_char);
- fp =3D 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 cha=
r *real_git_dir,
static void separate_git_dir(const char *git_dir)
{
struct stat st;
- FILE *fp;
=20
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);
}
=20
- fp =3D 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);
}
=20
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
=20
-static void store_pid(const char *path)
-{
- FILE *f =3D fopen(path, "w");
- if (!f)
- die_errno("cannot open pid file '%s'", path);
- if (fprintf(f, "%"PRIuMAX"\n", (uintmax_t) getpid()) < 0 || fclose(f)=
!=3D 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();
=20
if (pid_file)
- store_pid(pid_file);
+ write_file(pid_file, 1, "%"PRIuMAX"\n", (uintmax_t) getpid());
=20
/* prepare argv for serving-processes */
cld_argv =3D 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 =3D STRBUF_INIT;
struct strbuf rel_path =3D STRBUF_INIT;
const char *real_work_tree =3D xstrdup(real_path(work_tree));
- FILE *fp;
=20
/* Update gitfile */
strbuf_addf(&file_name, "%s/.git", work_tree);
- fp =3D 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));
=20
/* 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 un=
signed char *sha1,
{
struct strbuf *buf =3D data;
int len =3D buf->len;
- FILE *f;
=20
/* 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 u=
nsigned char *sha1,
=20
strbuf_addstr(buf, name);
if (safe_create_leading_directories(buf->buf) ||
- !(f =3D 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;
}
--=20
1.8.5.2.240.g8478abd
Nguyễn Thái Ngọc Duy
2014-02-18 13:40:14 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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.
=20
+gc.prunereposexpire::
+ When 'git gc' is run, it will call 'prune --repos --expire 3.months.a=
go'.
+ 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 =3D 250;
static int gc_auto_threshold =3D 6700;
static int gc_auto_pack_limit =3D 50;
static const char *prune_expire =3D "2.weeks.ago";
+static const char *prune_repos_expire =3D "3.months.ago";
=20
static struct argv_array pack_refs_cmd =3D ARGV_ARRAY_INIT;
static struct argv_array reflog =3D ARGV_ARRAY_INIT;
static struct argv_array repack =3D ARGV_ARRAY_INIT;
static struct argv_array prune =3D ARGV_ARRAY_INIT;
+static struct argv_array prune_repos =3D ARGV_ARRAY_INIT;
static struct argv_array rerere =3D ARGV_ARRAY_INIT;
=20
static char *pidfile;
@@ -81,6 +83,14 @@ static int gc_config(const char *var, const char *va=
lue, void *cb)
}
return git_config_string(&prune_expire, var, value);
}
+ if (!strcmp(var, "gc.prunereposexpire")) {
+ if (value && strcmp(value, "now")) {
+ unsigned long now =3D approxidate("now");
+ if (approxidate(value) >=3D now)
+ return error(_("Invalid %s: '%s'"), var, value);
+ }
+ return git_config_string(&prune_repos_expire, var, value);
+ }
return git_default_config(var, value, cb);
}
=20
@@ -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);
=20
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]);
}
=20
+ 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]);
=20
--=20
1.8.5.2.240.g8478abd
Junio C Hamano
2014-02-19 20:22:35 UTC
Permalink
(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 ")".
Nguyễn Thái Ngọc Duy
2014-02-18 13:40:07 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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
=20
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-pa=
rse.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 direct=
ory
is not detected to lie in a Git repository or work tree
print a message to stderr and exit with nonzero status.
=20
+--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, con=
st char *prefix)
printf("%s%s.git\n", cwd, len && cwd[len-1] !=3D '/' ? "/" : "");
continue;
}
+ if (!strcmp(arg, "--git-common-dir")) {
+ puts(get_git_common_dir());
+ continue;
+ }
if (!strcmp(arg, "--resolve-git-dir")) {
const char *gitdir =3D 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_names=
pace)
=20
static void setup_git_env(void)
{
+ struct strbuf sb =3D STRBUF_INIT;
const char *gitfile;
const char *shallow_file;
=20
@@ -134,12 +135,9 @@ static void setup_git_env(void)
git_dir =3D DEFAULT_GIT_DIR_ENVIRONMENT;
gitfile =3D read_gitfile(git_dir);
git_dir =3D xstrdup(gitfile ? gitfile : git_dir);
- git_common_dir =3D getenv(GIT_COMMON_DIR_ENVIRONMENT);
- if (git_common_dir) {
+ if (get_common_dir(&sb, git_dir))
git_common_dir_env =3D 1;
- git_common_dir =3D xstrdup(git_common_dir);
- } else
- git_common_dir =3D git_dir;
+ git_common_dir =3D strbuf_detach(&sb, NULL);
git_object_dir =3D getenv(DB_ENVIRONMENT);
if (!git_object_dir) {
git_object_dir =3D 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, cons=
t char *arg)
"'git <command> [<revision>...] -- [<file>...]'", arg);
}
=20
-static void get_common_dir(struct strbuf *sb, const char *gitdir)
+int get_common_dir(struct strbuf *sb, const char *gitdir)
{
struct strbuf data =3D STRBUF_INIT;
struct strbuf path =3D STRBUF_INIT;
const char *git_common_dir =3D getenv(GIT_COMMON_DIR_ENVIRONMENT);
+ int ret =3D 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, con=
st char *gitdir)
strbuf_addf(&path, "%s/", gitdir);
strbuf_addbuf(&path, &data);
strbuf_addstr(sb, real_path(path.buf));
+ ret =3D 1;
} else
strbuf_addstr(sb, gitdir);
strbuf_release(&data);
strbuf_release(&path);
+ return ret;
}
=20
/*
@@ -279,13 +282,26 @@ void setup_work_tree(void)
initialized =3D 1;
}
=20
+static int check_repo_format(const char *var, const char *value, void =
*cb)
+{
+ if (strcmp(var, "core.repositoryformatversion") =3D=3D 0)
+ repository_format_version =3D git_config_int(var, value);
+ else if (strcmp(var, "core.sharedrepository") =3D=3D 0)
+ shared_repository =3D git_config_perm(var, value);
+ return 0;
+}
+
static int check_repository_format_gently(const char *gitdir, int *non=
git_ok)
{
struct strbuf sb =3D STRBUF_INIT;
const char *repo_config;
+ config_fn_t fn;
int ret =3D 0;
=20
- get_common_dir(&sb, gitdir);
+ if (get_common_dir(&sb, gitdir))
+ fn =3D check_repo_format;
+ else
+ fn =3D check_repository_format_version;
strbuf_addstr(&sb, "/config");
repo_config =3D sb.buf;
=20
@@ -298,7 +314,7 @@ static int check_repository_format_gently(const cha=
r *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 <=3D %d, found %d",
@@ -770,11 +786,10 @@ int git_config_perm(const char *var, const char *=
value)
=20
int check_repository_format_version(const char *var, const char *value=
, void *cb)
{
- if (strcmp(var, "core.repositoryformatversion") =3D=3D 0)
- repository_format_version =3D git_config_int(var, value);
- else if (strcmp(var, "core.sharedrepository") =3D=3D 0)
- shared_repository =3D git_config_perm(var, value);
- else if (strcmp(var, "core.bare") =3D=3D 0) {
+ int ret =3D check_repo_format(var, value, cb);
+ if (ret)
+ return ret;
+ if (strcmp(var, "core.bare") =3D=3D 0) {
is_bare_repository_cfg =3D git_config_bool(var, value);
if (is_bare_repository_cfg =3D=3D 1)
inside_work_tree =3D -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 g=
it subprocesses' '
test_cmp expected actual
'
=20
+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=3D../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=3D../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=3Drepo.git config core.worktree "$TRASH_DIRECTORY/elsew=
here" &&
+ 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=3D../.git --work-tree=3D. 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 =3D "(null)";
=20
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_g=
it_common_dir()));
trace_printf_key(key, "setup: worktree: %s\n", quote_crnl(git_work_tr=
ee));
trace_printf_key(key, "setup: cwd: %s\n", quote_crnl(cwd));
trace_printf_key(key, "setup: prefix: %s\n", quote_crnl(prefix));
--=20
1.8.5.2.240.g8478abd
Junio C Hamano
2014-02-19 20:19:41 UTC
Permalink
Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail.com> writes:

(Only nitpicks during this round of review).
Post by Nguyễn Thái Ngọc Duy
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' '
Post by Nguyễn Thái Ngọc Duy
test_cmp expected actual
'
=20
+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.
Junio C Hamano
2014-02-27 20:28:50 UTC
Permalink
Post by 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 i=
s
Post by Nguyễn Thái Ngọc Duy
because "commondir" repos are intended for separate/linked checkouts
and pointing them back to a fixed core.worktree just does not make
sense.
il.com>
Post by Nguyễn Thái Ngọc Duy
---
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 +++++++++++++++++++++++++++++++=
++++++++++
Post by Nguyễn Thái Ngọc Duy
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
Post by Nguyễn Thái Ngọc Duy
=20
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?
Nguyễn Thái Ngọc Duy
2014-02-18 13:40:12 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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;
};
=20
static void setup_branch_path(struct branch_info *branch)
@@ -640,6 +645,11 @@ static void update_refs_for_switch(const struct ch=
eckout_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 br=
anch\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;
}
=20
+static int check_linked_checkout(struct branch_info *new,
+ const char *name, const char *path)
+{
+ struct strbuf sb =3D 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 =3D sb.buf + 4;
+ while (isspace(*start))
+ start++;
+ end =3D start;
+ while (*end && !isspace(*end))
+ end++;
+ if (!strncmp(start, new->path, end - start) &&
+ new->path[end - start] =3D=3D '\0') {
+ strbuf_release(&sb);
+ new->path =3D NULL; /* detach */
+ new->checkout =3D xstrdup(name); /* reason */
+ return 1;
+ }
+ strbuf_release(&sb);
+ return 0;
+}
+
+static void check_linked_checkouts(struct branch_info *new)
+{
+ struct strbuf path =3D STRBUF_INIT;
+ DIR *dir;
+ struct dirent *d;
+
+ strbuf_addf(&path, "%s/repos", get_git_common_dir());
+ if ((dir =3D opendir(path.buf)) =3D=3D 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 =3D readdir(dir)) !=3D 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 c=
har **argv,
else
new->path =3D NULL; /* not an existing branch */
=20
+ if (new->path)
+ check_linked_checkouts(new);
+
new->commit =3D 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 pat=
hs' '
'
=20
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 cr=
eating new branch' '
)
'
=20
+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
--=20
1.8.5.2.240.g8478abd
Junio C Hamano
2014-02-19 20:20:36 UTC
Permalink
Post by 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 i=
n
Post by Nguyễn Thái Ngọc Duy
$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.
il.com>

(Only nitpicks during this round of review).
Post by Nguyễn Thái Ngọc Duy
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 p=
aths' '
Post by Nguyễn Thái Ngọc Duy
'
=20
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?
Eric Sunshine
2014-02-19 21:52:58 UTC
Permalink
On Tue, Feb 18, 2014 at 8:40 AM, Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc =
Post by 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 i=
n

s/strictens/increases strictness of/
Post by Nguyễn Thái Ngọc Duy
$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.
il.com>
Post by Nguyễn Thái Ngọc Duy
---
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/
Post by Nguyễn Thái Ngọc Duy
+ * checked out in this checkout
+ */
+ char *checkout;
};
+static void check_linked_checkouts(struct branch_info *new)
+{
+ struct strbuf path =3D STRBUF_INIT;
+ DIR *dir;
+ struct dirent *d;
+
+ strbuf_addf(&path, "%s/repos", get_git_common_dir());
+ if ((dir =3D opendir(path.buf)) =3D=3D NULL)
strbuf_release(&path);
Post by Nguyễn Thái Ngọc Duy
+ 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 =3D readdir(dir)) !=3D NULL) {
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..=
"))
Post by Nguyễn Thái Ngọc Duy
+ 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);
+}
Nguyễn Thái Ngọc Duy
2014-02-18 13:40:13 UTC
Permalink
alias REPO=3D$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 u=
p 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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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>.
=20
+--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/git=
repository-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.
=20
+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);
}
=20
+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 =3D=3D 0 && has_dos_drive_prefix("c:\\"))
+ buf.st_dev =3D 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 che=
ckout_opts *opts,
struct stat st;
const char *name;
struct child_process cp;
- int counter =3D 0, len, ret;
+ int counter =3D 0, len, keep_locked =3D 0, ret;
=20
if (!new->commit)
die(_("no branch specified"));
@@ -898,12 +909,18 @@ static int prepare_linked_checkout(const struct c=
heckout_opts *opts,
junk_git_dir =3D sb_repo.buf;
is_junk =3D 1;
=20
+ 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 =3D path;
=20
+ 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 c=
heckout_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");
=20
+ if (get_device_or_die(path) !=3D 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 =3D 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);
=20
@@ -930,6 +959,11 @@ static int prepare_linked_checkout(const struct ch=
eckout_opts *opts,
ret =3D run_command(&cp);
if (!ret)
is_junk =3D 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)
}
}
=20
+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 =3D expire;
+ return _("gitdir does not exist");
+ }
+ fd =3D open(git_path("repos/%s/gitdir", id), O_RDONLY);
+ len =3D st->st_size;
+ path =3D xmalloc(len + 1);
+ read_in_full(fd, path, len);
+ close(fd);
+ while (path[len - 1] =3D=3D '\n' || path[len - 1] =3D=3D '\r')
+ len--;
+ path[len] =3D '\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 =3D opendir(git_path("repos"));
+ struct dirent *d;
+ int removed =3D 0;
+ struct stat st;
+ if (!dir)
+ return;
+ while ((d =3D readdir(dir)) !=3D NULL) {
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+ if ((reason =3D prune_repo_dir(d->d_name, &st)) !=3D NULL &&
+ st.st_mtime <=3D expire) {
+ struct strbuf sb =3D 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 =3D 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 =3D NULL;
+ int prune_repos =3D 0;
const struct option options[] =3D {
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 c=
har *prefix)
init_revisions(&revs, prefix);
=20
argc =3D 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 =3D *argv++;
@@ -166,6 +240,7 @@ int cmd_prune(int argc, const char **argv, const ch=
ar *prefix)
die("unrecognized argument: %s", name);
}
=20
+
if (show_progress =3D=3D -1)
show_progress =3D 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 ch=
ar *gitdir, int *nongit_ok)
return ret;
}
=20
+static void update_linked_gitdir(const char *gitfile, const char *gitd=
ir)
+{
+ struct strbuf path =3D 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)
=20
if (!is_git_directory(dir))
die("Not a git repository: %s", dir);
+
+ update_linked_gitdir(path, dir);
path =3D real_path(dir);
=20
free(buf);
--=20
1.8.5.2.240.g8478abd
Junio C Hamano
2014-02-19 20:32:26 UTC
Permalink
Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail.com> writes:

(Only nitpicks during this round of review).
+ if (get_device_or_die(path) !=3D 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 =3D 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?
Junio C Hamano
2014-02-19 20:42:59 UTC
Permalink
Post by Junio C Hamano
(Only nitpicks during this round of review).
+ if (get_device_or_die(path) !=3D get_device_or_die(get_git_dir()))=
{
Post by Junio C Hamano
+ 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 =3D 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 =3D 1;
+ }
+
Duy Nguyen
2014-02-20 13:15:06 UTC
Permalink
Post by Junio C Hamano
(Only nitpicks during this round of review).
+ if (get_device_or_die(path) !=3D get_device_or_die(get_git_dir=
())) {
Post by Junio C Hamano
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+ write_file(sb.buf, 1, "located on a different file sys=
tem\n");
Post by Junio C Hamano
+ keep_locked =3D 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?
=46ilesystems 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.
--=20
Duy
Junio C Hamano
2014-02-20 19:55:39 UTC
Permalink
Post by Junio C Hamano
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.
Eric Sunshine
2014-02-19 22:08:02 UTC
Permalink
On Tue, Feb 18, 2014 at 8:40 AM, Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc =
Post by Nguyễn Thái Ngọc Duy
- 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/
Post by Nguyễn Thái Ngọc Duy
really long limit, warn about old unused repo.
il.com>
Post by Nguyễn Thái Ngọc Duy
---
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" *=
/
Post by Nguyễn Thái Ngọc Duy
+ if (buf.st_dev =3D=3D 0 && has_dos_drive_prefix("c:\\"))
+ buf.st_dev =3D 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 =3D buf->st_rdev =3D 0; /* not used by Git */
Post by Nguyễn Thái Ngọc Duy
+ return buf.st_dev;
+}
Eric Sunshine
2014-02-19 22:53:12 UTC
Permalink
On Tue, Feb 18, 2014 at 8:40 AM, Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8D=
Post by Nguyễn Thái Ngọc Duy
+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" =
*/
Post by Nguyễn Thái Ngọc Duy
+ if (buf.st_dev =3D=3D 0 && has_dos_drive_prefix("c:\\"))
+ buf.st_dev =3D 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
buf->st_dev =3D buf->st_rdev =3D 0; /* not used by Git */
Post by Nguyễn Thái Ngọc Duy
+ 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 =3D toupper(real_path(...)), thus also
making the code easier to understand.
Duy Nguyen
2014-02-20 13:19:40 UTC
Permalink
Post by Eric Sunshine
Post by Nguyễn Thái Ngọc Duy
+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
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
Junio C Hamano
2014-02-19 20:57:52 UTC
Permalink
Post by 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.
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.
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:36 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc 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() togethe=
r
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(-)

--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:37 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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)
=20
static char bad_path[] =3D "/bad-path/";
=20
-static char *get_pathname(void)
+static struct strbuf *get_pathname(void)
{
- static char pathname_array[4][PATH_MAX];
+ static struct strbuf pathname_array[4] =3D {
+ STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
+ };
static int index;
- return pathname_array[3 & ++index];
+ struct strbuf *sb =3D &pathname_array[3 & ++index];
+ strbuf_reset(sb);
+ return sb;
}
=20
static char *cleanup_path(char *path)
@@ -34,6 +38,13 @@ static char *cleanup_path(char *path)
return path;
}
=20
+static void strbuf_cleanup_path(struct strbuf *sb)
+{
+ char *path =3D 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);
}
=20
-static char *vsnpath(char *buf, size_t n, const char *fmt, va_list arg=
s)
+static void vsnpath(struct strbuf *buf, const char *fmt, va_list args)
{
const char *git_dir =3D get_git_dir();
- size_t len;
-
- len =3D 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++] =3D '/';
- len +=3D vsnprintf(buf + len, n - len, fmt, args);
- if (len >=3D 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);
}
=20
char *git_snpath(char *buf, size_t n, const char *fmt, ...)
{
- char *ret;
+ struct strbuf sb =3D STRBUF_INIT;
va_list args;
va_start(args, fmt);
- ret =3D vsnpath(buf, n, fmt, args);
+ vsnpath(&sb, fmt, args);
va_end(args);
- return ret;
+ if (sb.len >=3D n)
+ strlcpy(buf, bad_path, n);
+ else
+ memcpy(buf, sb.buf, sb.len + 1);
+ strbuf_release(&sb);
+ return buf;
}
=20
char *git_pathdup(const char *fmt, ...)
{
- char path[PATH_MAX], *ret;
+ struct strbuf path =3D STRBUF_INIT;
va_list args;
va_start(args, fmt);
- ret =3D vsnpath(path, sizeof(path), fmt, args);
+ vsnpath(&path, fmt, args);
va_end(args);
- return xstrdup(ret);
+ return strbuf_detach(&path, NULL);
}
=20
char *mkpathdup(const char *fmt, ...)
{
- char *path;
struct strbuf sb =3D STRBUF_INIT;
va_list args;
-
va_start(args, fmt);
strbuf_vaddf(&sb, fmt, args);
va_end(args);
- path =3D xstrdup(cleanup_path(sb.buf));
-
- strbuf_release(&sb);
- return path;
+ strbuf_cleanup_path(&sb);
+ return strbuf_detach(&sb, NULL);
}
=20
char *mkpath(const char *fmt, ...)
{
va_list args;
- unsigned len;
- char *pathname =3D get_pathname();
-
+ struct strbuf *pathname =3D get_pathname();
va_start(args, fmt);
- len =3D vsnprintf(pathname, PATH_MAX, fmt, args);
+ strbuf_vaddf(pathname, fmt, args);
va_end(args);
- if (len >=3D PATH_MAX)
- return bad_path;
- return cleanup_path(pathname);
+ return cleanup_path(pathname->buf);
}
=20
char *git_path(const char *fmt, ...)
{
- char *pathname =3D get_pathname();
+ struct strbuf *pathname =3D get_pathname();
va_list args;
- char *ret;
-
va_start(args, fmt);
- ret =3D vsnpath(pathname, PATH_MAX, fmt, args);
+ vsnpath(pathname, fmt, args);
va_end(args);
- return ret;
+ return pathname->buf;
}
=20
void home_config_paths(char **global, char **xdg, char *file)
@@ -158,41 +154,27 @@ void home_config_paths(char **global, char **xdg,=
char *file)
=20
char *git_path_submodule(const char *path, const char *fmt, ...)
{
- char *pathname =3D get_pathname();
- struct strbuf buf =3D STRBUF_INIT;
+ struct strbuf *buf =3D get_pathname();
const char *git_dir;
va_list args;
- unsigned len;
-
- len =3D strlen(path);
- if (len > PATH_MAX-100)
- return bad_path;
=20
- strbuf_addstr(&buf, path);
- if (len && path[len-1] !=3D '/')
- strbuf_addch(&buf, '/');
- strbuf_addstr(&buf, ".git");
+ strbuf_addstr(buf, path);
+ if (buf->len && buf->buf[buf->len - 1] !=3D '/')
+ strbuf_addch(buf, '/');
+ strbuf_addstr(buf, ".git");
=20
- git_dir =3D read_gitfile(buf.buf);
+ git_dir =3D 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 >=3D PATH_MAX)
- return bad_path;
- memcpy(pathname, buf.buf, buf.len + 1);
-
- strbuf_release(&buf);
- len =3D strlen(pathname);
+ strbuf_addch(buf, '/');
=20
va_start(args, fmt);
- len +=3D vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
+ strbuf_vaddf(buf, fmt, args);
va_end(args);
- if (len >=3D PATH_MAX)
- return bad_path;
- return cleanup_path(pathname);
+ strbuf_cleanup_path(buf);
+ return buf->buf;
}
=20
int validate_headref(const char *path)
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:38 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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 c=
heckout_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 =3D STRBUF_INIT;
char *ref_name =3D mkpath("refs/heads/%s", opts->new_orphan_branch=
);
+ int ret;
=20
temp =3D log_all_ref_updates;
log_all_ref_updates =3D 1;
- if (log_ref_setup(ref_name, log_file, sizeof(log_file))) {
+ ret =3D log_ref_setup(ref_name, &log_file);
+ log_all_ref_updates =3D temp;
+ strbuf_release(&log_file);
+ if (ret) {
fprintf(stderr, _("Can not do reflog for '%s'\n"),
opts->new_orphan_branch);
- log_all_ref_updates =3D temp;
return;
}
- log_all_ref_updates =3D temp;
}
}
else
@@ -651,14 +653,10 @@ static void update_refs_for_switch(const struct c=
heckout_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);
=20
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);
}
=20
-char *git_snpath(char *buf, size_t n, const char *fmt, ...)
+void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
{
- struct strbuf sb =3D STRBUF_INIT;
va_list args;
va_start(args, fmt);
- vsnpath(&sb, fmt, args);
+ vsnpath(sb, fmt, args);
va_end(args);
- if (sb.len >=3D n)
- strlcpy(buf, bad_path, n);
- else
- memcpy(buf, sb.buf, sb.len + 1);
- strbuf_release(&sb);
- return buf;
}
=20
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(con=
st char *refname,
=20
const char *resolve_ref_unsafe(const char *refname, unsigned char *sha=
1, int reading, int *flag)
{
+ struct strbuf sb_path =3D STRBUF_INIT;
int depth =3D MAXDEPTH;
ssize_t len;
char buffer[256];
static char refname_buffer[256];
+ const char *ret;
=20
if (flag)
*flag =3D 0;
@@ -1337,15 +1339,17 @@ const char *resolve_ref_unsafe(const char *refn=
ame, unsigned char *sha1, int rea
return NULL;
=20
for (;;) {
- char path[PATH_MAX];
+ const char *path;
struct stat st;
char *buf;
int fd;
=20
if (--depth < 0)
- return NULL;
+ goto fail;
=20
- git_snpath(path, sizeof(path), "%s", refname);
+ strbuf_reset(&sb_path);
+ strbuf_git_path(&sb_path, "%s", refname);
+ path =3D sb_path.buf;
=20
/*
* We might have to loop back here to avoid a race
@@ -1359,10 +1363,11 @@ const char *resolve_ref_unsafe(const char *refn=
ame, unsigned char *sha1, int rea
stat_ref:
if (lstat(path, &st) < 0) {
if (errno =3D=3D ENOENT)
- return handle_missing_loose_ref(refname, sha1,
- reading, flag);
+ ret =3D handle_missing_loose_ref(refname, sha1,
+ reading, flag);
else
- return NULL;
+ ret =3D NULL;
+ goto done;
}
=20
/* Follow "normalized" - ie "refs/.." symlinks by hand */
@@ -1373,7 +1378,7 @@ const char *resolve_ref_unsafe(const char *refnam=
e, unsigned char *sha1, int rea
/* inconsistent with lstat; retry */
goto stat_ref;
else
- return NULL;
+ goto fail;
}
buffer[len] =3D 0;
if (starts_with(buffer, "refs/") &&
@@ -1389,7 +1394,7 @@ const char *resolve_ref_unsafe(const char *refnam=
e, unsigned char *sha1, int rea
/* Is it a directory? */
if (S_ISDIR(st.st_mode)) {
errno =3D EISDIR;
- return NULL;
+ goto fail;
}
=20
/*
@@ -1402,12 +1407,13 @@ const char *resolve_ref_unsafe(const char *refn=
ame, unsigned char *sha1, int rea
/* inconsistent with lstat; retry */
goto stat_ref;
else
- return NULL;
+ goto fail;
}
+
len =3D 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] =3D '\0';
@@ -1424,9 +1430,10 @@ const char *resolve_ref_unsafe(const char *refna=
me, unsigned char *sha1, int rea
(buffer[40] !=3D '\0' && !isspace(buffer[40]))) {
if (flag)
*flag |=3D REF_ISBROKEN;
- return NULL;
+ goto fail;
}
- return refname;
+ ret =3D refname;
+ goto done;
}
if (flag)
*flag |=3D REF_ISSYMREF;
@@ -1436,10 +1443,15 @@ const char *resolve_ref_unsafe(const char *refn=
ame, unsigned char *sha1, int rea
if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
if (flag)
*flag |=3D REF_ISBROKEN;
- return NULL;
+ goto fail;
}
refname =3D strcpy(refname_buffer, buf);
}
+fail:
+ ret =3D NULL;
+done:
+ strbuf_release(&sb_path);
+ return ret;
}
=20
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;
}
=20
-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 =3D O_APPEND | O_WRONLY;
+ const char *logfile;
=20
- git_snpath(logfile, bufsize, "logs/%s", refname);
+ strbuf_git_path(sb_logfile, "logs/%s", refname);
+ logfile =3D 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 |=3D O_CREAT;
@@ -2762,20 +2776,22 @@ static int log_ref_write(const char *refname, c=
onst unsigned char *old_sha1,
int logfd, result, written, oflags =3D O_APPEND | O_WRONLY;
unsigned maxlen, len;
int msglen;
- char log_file[PATH_MAX];
+ struct strbuf sb_log_file =3D STRBUF_INIT;
+ const char *log_file;
char *logrec;
const char *committer;
=20
if (log_all_ref_updates < 0)
log_all_ref_updates =3D !is_bare_repository();
=20
- result =3D log_ref_setup(refname, log_file, sizeof(log_file));
+ result =3D log_ref_setup(refname, &sb_log_file);
if (result)
- return result;
+ goto done;
+ log_file =3D sb_log_file.buf;
=20
logfd =3D open(log_file, oflags);
if (logfd < 0)
- return 0;
+ goto done;
msglen =3D msg ? strlen(msg) : 0;
committer =3D git_committer_info(0);
maxlen =3D strlen(committer) + msglen + 100;
@@ -2788,9 +2804,13 @@ static int log_ref_write(const char *refname, co=
nst unsigned char *old_sha1,
len +=3D copy_msg(logrec + len - 1, msg) - 1;
written =3D len <=3D maxlen ? write_in_full(logfd, logrec, len) : -1;
free(logrec);
- if (close(logfd) !=3D 0 || written !=3D len)
- return error("Unable to append to %s", log_file);
- return 0;
+ if (close(logfd) !=3D 0 || written !=3D len) {
+ error("Unable to append to %s", log_file);
+ result =3D -1;
+ }
+done:
+ strbuf_release(&sb_log_file);
+ return result;
}
=20
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);
=20
/** 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);
=20
/** Reads log for the value of ref during at_time. **/
extern int read_ref_at(const char *refname, unsigned long at_time, int=
cnt,
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:39 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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, =
=2E..)
return cleanup_path(buf);
}
=20
-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 a=
rgs)
{
const char *git_dir =3D get_git_dir();
strbuf_addstr(buf, git_dir);
@@ -74,7 +74,7 @@ void strbuf_git_path(struct strbuf *sb, const char *f=
mt, ...)
{
va_list args;
va_start(args, fmt);
- vsnpath(sb, fmt, args);
+ do_git_path(sb, fmt, args);
va_end(args);
}
=20
@@ -83,7 +83,7 @@ char *git_pathdup(const char *fmt, ...)
struct strbuf path =3D 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 =3D 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;
}
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:40 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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);
}
=20
+char *git_path(const char *fmt, ...)
+{
+ struct strbuf *pathname =3D 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 =3D STRBUF_INIT;
@@ -109,16 +119,6 @@ char *mkpath(const char *fmt, ...)
return cleanup_path(pathname->buf);
}
=20
-char *git_path(const char *fmt, ...)
-{
- struct strbuf *pathname =3D 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 =3D getenv("XDG_CONFIG_HOME");
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:41 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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-pa=
rse.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 st=
atus.
repository. If <path> is a gitfile then the resolved path
to the real repository is printed.
=20
+--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, con=
st char *prefix)
for (i =3D 1; i < argc; i++) {
const char *arg =3D argv[i];
=20
+ 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;
=20
/*
* 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;
=20
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;
=20
/*
* Repository-local GIT_* environment variables; see cache.h for detai=
ls.
@@ -137,15 +138,19 @@ static void setup_git_env(void)
if (!git_object_dir) {
git_object_dir =3D xmalloc(strlen(git_dir) + 9);
sprintf(git_object_dir, "%s/objects", git_dir);
- }
+ } else
+ git_db_env =3D 1;
git_index_file =3D getenv(INDEX_ENVIRONMENT);
if (!git_index_file) {
git_index_file =3D xmalloc(strlen(git_dir) + 7);
sprintf(git_index_file, "%s/index", git_dir);
- }
+ } else
+ git_index_env =3D 1;
git_graft_file =3D getenv(GRAFT_ENVIRONMENT);
if (!git_graft_file)
git_graft_file =3D git_pathdup("info/grafts");
+ else
+ git_graft_env =3D 1;
if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
read_replace_refs =3D 0;
namespace =3D 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);
}
=20
+static int dir_prefix(const char *buf, const char *dir)
+{
+ int len =3D strlen(dir);
+ return !strncmp(buf, dir, len) &&
+ (is_dir_sep(buf[len]) || buf[len] =3D=3D '\0');
+}
+
+/* $buf =3D~ m|$dir/+$file| but without regex */
+static int is_dir_file(const char *buf, const char *dir, const char *f=
ile)
+{
+ int len =3D 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 *newdi=
r)
+{
+ int newlen =3D strlen(newdir);
+ int need_sep =3D (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] =3D '/';
+}
+
+static void adjust_git_path(struct strbuf *buf, int git_dir_len)
+{
+ const char *base =3D 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 a=
rgs)
{
const char *git_dir =3D 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 =3D buf->len;
strbuf_vaddf(buf, fmt, args);
+ adjust_git_path(buf, gitdir_len);
strbuf_cleanup_path(buf);
}
=20
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')\" =3D '$expected'=
"
}
=20
+test_git_path() {
+ test_expect_success "git-path $1 $2 =3D> $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 ./
=20
+test_git_path A=3DB info/grafts .git/info/grafts
+test_git_path GIT_GRAFT_FILE=3Dfoo info/grafts foo
+test_git_path GIT_GRAFT_FILE=3Dfoo info/////grafts foo
+test_git_path GIT_INDEX_FILE=3Dfoo index foo
+test_git_path GIT_INDEX_FILE=3Dfoo index/foo .git/index/foo
+test_git_path GIT_INDEX_FILE=3Dfoo index2 .git/index2
+test_expect_success 'setup fake objects directory foo' 'mkdir foo'
+test_git_path GIT_OBJECT_DIRECTORY=3Dfoo objects foo
+test_git_path GIT_OBJECT_DIRECTORY=3Dfoo objects/foo foo/foo
+test_git_path GIT_OBJECT_DIRECTORY=3Dfoo objects2 .git/objects2
+
test_done
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:42 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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 =3D "$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
=20
TMP=3D"$GIT_DIR/.git-stash.$$"
-TMPindex=3D${GIT_INDEX_FILE-"$GIT_DIR/index"}.stash.$$
+TMPindex=3D${GIT_INDEX_FILE-"`git rev-parse --git-path index`"}.stash.=
$$
trap 'rm -f "$TMP-"* "$TMPindex"' 0
=20
ref_stash=3Drefs/stash
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:43 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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 uns=
igned char *sha1, int unused,
if (!file_exists(log_file))
goto finish;
if (!cmd->dry_run) {
- newlog_path =3D git_pathdup("logs/%s.lock", ref);
+ newlog_path =3D mkpathdup("%s.lock", log_file);
cb.newlog =3D fopen(newlog_path, "w");
}
=20
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:44 UTC
Permalink
This allows git_path() to redirect info/fast-import to another place
if needed

Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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)
=20
static char* make_fast_import_path(const char *path)
{
- struct strbuf abs_path =3D 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));
}
=20
static void option_import_marks(const char *marks,
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:45 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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 =3D FROM_MERGE;
else if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
whence =3D FROM_CHERRY_PICK;
- if (file_exists(git_path("sequencer")))
+ if (file_exists(git_path(SEQ_DIR)))
sequencer_in_use =3D 1;
}
else
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:46 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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.
=20
+'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/git=
repository-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 publ=
ished 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.
=20
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.
=20
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.
=20
HEAD::
A symref (see glossary) to the `refs/heads/` namespace
@@ -133,6 +138,11 @@ being a symref to point at the current branch. Su=
ch a state
is often called 'detached HEAD.' See linkgit:git-checkout[1]
for details.
=20
+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.
+
=20
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.
+
=20
index::
The current index file for the repository. It is
@@ -157,7 +172,8 @@ index::
=20
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.
=20
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.
=20
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.
=20
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.
=20
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.
=20
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)
=20
/* 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_en=
v;
=20
/*
* 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;
=20
-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;
=20
/*
* Repository-local GIT_* environment variables; see cache.h for detai=
ls.
@@ -134,10 +134,16 @@ static void setup_git_env(void)
git_dir =3D DEFAULT_GIT_DIR_ENVIRONMENT;
gitfile =3D read_gitfile(git_dir);
git_dir =3D xstrdup(gitfile ? gitfile : git_dir);
+ git_common_dir =3D getenv(GIT_COMMON_DIR_ENVIRONMENT);
+ if (git_common_dir) {
+ git_common_dir_env =3D 1;
+ git_common_dir =3D xstrdup(git_common_dir);
+ } else
+ git_common_dir =3D git_dir;
git_object_dir =3D getenv(DB_ENVIRONMENT);
if (!git_object_dir) {
- git_object_dir =3D xmalloc(strlen(git_dir) + 9);
- sprintf(git_object_dir, "%s/objects", git_dir);
+ git_object_dir =3D xmalloc(strlen(git_common_dir) + 9);
+ sprintf(git_object_dir, "%s/objects", git_common_dir);
} else
git_db_env =3D 1;
git_index_file =3D getenv(INDEX_ENVIRONMENT);
@@ -173,6 +179,11 @@ const char *get_git_dir(void)
return git_dir;
}
=20
+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] =3D '/';
}
=20
+static void update_common_dir(struct strbuf *buf, int git_dir_len)
+{
+ const char *common_dir_list[] =3D {
+ "branches", "hooks", "info", "logs", "lost-found", "modules",
+ "objects", "refs", "remotes", "rr-cache", "svn",
+ NULL
+ };
+ const char *common_top_file_list[] =3D {
+ "config", "gc.pid", "packed-refs", "shallow", NULL
+ };
+ char *base =3D buf->buf + git_dir_len;
+ const char **p;
+
+ if (is_dir_file(base, "logs", "HEAD"))
+ return; /* keep this in $GIT_DIR */
+ for (p =3D common_dir_list; *p; p++)
+ if (dir_prefix(base, *p)) {
+ replace_dir(buf, git_dir_len, get_git_common_dir());
+ return;
+ }
+ for (p =3D 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 =3D 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);
}
=20
static void do_git_path(struct strbuf *buf, const char *fmt, va_list a=
rgs)
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=3Dfoo objects foo
test_git_path GIT_OBJECT_DIRECTORY=3Dfoo objects/foo foo/foo
test_git_path GIT_OBJECT_DIRECTORY=3Dfoo objects2 .git/objects2
+test_expect_success 'setup common repository' 'git --git-dir=3Dbar ini=
t'
+test_git_path GIT_COMMON_DIR=3Dbar index .git/index
+test_git_path GIT_COMMON_DIR=3Dbar HEAD .git/HEAD
+test_git_path GIT_COMMON_DIR=3Dbar logs/HEAD .git/logs/=
HEAD
+test_git_path GIT_COMMON_DIR=3Dbar objects bar/object=
s
+test_git_path GIT_COMMON_DIR=3Dbar objects/bar bar/object=
s/bar
+test_git_path GIT_COMMON_DIR=3Dbar info/exclude bar/info/e=
xclude
+test_git_path GIT_COMMON_DIR=3Dbar remotes/bar bar/remote=
s/bar
+test_git_path GIT_COMMON_DIR=3Dbar branches/bar bar/branch=
es/bar
+test_git_path GIT_COMMON_DIR=3Dbar logs/refs/heads/master bar/logs/r=
efs/heads/master
+test_git_path GIT_COMMON_DIR=3Dbar refs/heads/master bar/refs/h=
eads/master
+test_git_path GIT_COMMON_DIR=3Dbar hooks/me bar/hooks/=
me
+test_git_path GIT_COMMON_DIR=3Dbar config bar/config
+test_git_path GIT_COMMON_DIR=3Dbar packed-refs bar/packed=
-refs
+test_git_path GIT_COMMON_DIR=3Dbar shallow bar/shallo=
w
=20
test_done
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:47 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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=3D"$GIT_DIR/objects"}
+ : ${GIT_OBJECT_DIRECTORY=3D"$(git rev-parse --git-path objects)"}
fi
=20
peel_committish () {
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:48 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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
=20
- if test -x "$GIT_DIR"/hooks/applypatch-msg
+ hook=3D"`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
=20
if test -f "$dotest/final-commit"
@@ -880,9 +880,10 @@ did you forget to use 'git add'?"
stop_here_user_resolve $this
fi
=20
- if test -x "$GIT_DIR"/hooks/pre-applypatch
+ hook=3D"`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
=20
tree=3D$(git write-tree) &&
@@ -908,18 +909,17 @@ did you forget to use 'git add'?"
echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritte=
n"
fi
=20
- if test -x "$GIT_DIR"/hooks/post-applypatch
- then
- "$GIT_DIR"/hooks/post-applypatch
- fi
+ hook=3D"`git rev-parse --git-path hooks/post-applypatch`"
+ test -x "$hook" && "$hook"
=20
go_next
done
=20
if test -s "$dotest"/rewritten; then
git notes copy --for-rewrite=3Drebase < "$dotest"/rewritten
- if test -x "$GIT_DIR"/hooks/post-rewrite; then
- "$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+ hook=3D"`git rev-parse --git-path hooks/post-rewrite`"
+ if test -x "$hook"; then
+ "$hook" rebase < "$dotest"/rewritten
fi
fi
=20
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=3Drebase < "$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=3D"`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=3Drebase <"$state_dir"/rewritten
- if test -x "$GIT_DIR"/hooks/post-rewrite
- then
- "$GIT_DIR"/hooks/post-rewrite rebase <"$state_dir"/rewritten
- fi
+ hook=3D"`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 () {
=20
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".
=20
. git-sh-setup
-test -x "$GIT_DIR/hooks/commit-msg" &&
- exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
+commitmsg=3D"`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".
=20
. git-sh-setup
-test -x "$GIT_DIR/hooks/pre-commit" &&
- exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
+precommit=3D"`git rev-parse --git-path hooks/pre-commit`"
+test -x "$precommit" && exec "$precommit" ${1+"$@"}
:
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:49 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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
=20
# 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=3D$?
test $ret !=3D 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")"
=20
create_stash "$stash_msg" $untracked
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:50 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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, cons=
t char *arg)
*/
int is_git_directory(const char *suspect)
{
- char path[PATH_MAX];
- size_t len =3D strlen(suspect);
+ struct strbuf path =3D STRBUF_INIT;
+ int ret =3D 0;
+ size_t len;
=20
- if (PATH_MAX <=3D len + strlen("/objects"))
- die("Too long path: %.*s", 60, suspect);
- strcpy(path, suspect);
+ strbuf_addstr(&path, suspect);
+ len =3D 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;
}
=20
- 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;
=20
- 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;
=20
- return 1;
+ ret =3D 1;
+done:
+ strbuf_release(&path);
+ return ret;
}
=20
int is_inside_git_dir(void)
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:51 UTC
Permalink
If the file "$GIT_DIR/commondir" exists, it contains the value of
$GIT_COMMON_DIR.

Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
Documentation/gitrepository-layout.txt | 7 ++++++
setup.c | 43 ++++++++++++++++++++++++++=
+++-----
2 files changed, 44 insertions(+), 6 deletions(-)

diff --git a/Documentation/gitrepository-layout.txt b/Documentation/git=
repository-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.
=20
+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);
}
=20
+static void get_common_dir(struct strbuf *sb, const char *gitdir)
+{
+ struct strbuf data =3D STRBUF_INIT;
+ struct strbuf path =3D STRBUF_INIT;
+ const char *git_common_dir =3D 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) <=3D 0)
+ die_errno(_("failed to read %s"), path.buf);
+ while (data.len && (data.buf[data.len - 1] =3D=3D '\n' ||
+ data.buf[data.len - 1] =3D=3D '\r'))
+ data.len--;
+ data.buf[data.len] =3D '\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);
+}
=20
/*
* Test if it looks like we're at a git directory.
@@ -188,13 +215,22 @@ int is_git_directory(const char *suspect)
int ret =3D 0;
size_t len;
=20
- 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 =3D 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;
=20
- strbuf_setlen(&path, len);
- strbuf_addstr(&path, "/HEAD");
- if (validate_headref(path.buf))
- goto done;
-
ret =3D 1;
done:
strbuf_release(&path);
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:52 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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)
=20
static int check_repository_format_gently(const char *gitdir, int *non=
git_ok)
{
- char repo_config[PATH_MAX+1];
+ struct strbuf sb =3D STRBUF_INIT;
+ const char *repo_config;
+ int ret =3D 0;
=20
/*
* git_config() can't be used here because it calls git_pathdup()
@@ -299,7 +301,8 @@ static int check_repository_format_gently(const cha=
r *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 =3D 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 ch=
ar *gitdir, int *nongit_ok)
GIT_REPO_VERSION, repository_format_version);
warning("Please upgrade Git");
*nongit_ok =3D -1;
- return -1;
+ ret =3D -1;
}
- return 0;
+ strbuf_release(&sb);
+ return ret;
}
=20
/*
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:53 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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 ch=
ar *gitdir, int *nongit_ok)
const char *repo_config;
int ret =3D 0;
=20
+ get_common_dir(&sb, gitdir);
+ strbuf_addstr(&sb, "/config");
+ repo_config =3D 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 cha=
r *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 =3D sb.buf;
git_config_early(check_repository_format_version, NULL, repo_config);
if (GIT_REPO_VERSION < repository_format_version) {
if (!nongit_ok)
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:54 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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
=20
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-pa=
rse.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 direct=
ory
is not detected to lie in a Git repository or work tree
print a message to stderr and exit with nonzero status.
=20
+--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, con=
st char *prefix)
printf("%s%s.git\n", cwd, len && cwd[len-1] !=3D '/' ? "/" : "");
continue;
}
+ if (!strcmp(arg, "--git-common-dir")) {
+ puts(get_git_common_dir());
+ continue;
+ }
if (!strcmp(arg, "--resolve-git-dir")) {
const char *gitdir =3D 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_names=
pace)
=20
static void setup_git_env(void)
{
+ struct strbuf sb =3D STRBUF_INIT;
const char *gitfile;
const char *shallow_file;
=20
@@ -134,12 +135,9 @@ static void setup_git_env(void)
git_dir =3D DEFAULT_GIT_DIR_ENVIRONMENT;
gitfile =3D read_gitfile(git_dir);
git_dir =3D xstrdup(gitfile ? gitfile : git_dir);
- git_common_dir =3D getenv(GIT_COMMON_DIR_ENVIRONMENT);
- if (git_common_dir) {
+ if (get_common_dir(&sb, git_dir))
git_common_dir_env =3D 1;
- git_common_dir =3D xstrdup(git_common_dir);
- } else
- git_common_dir =3D git_dir;
+ git_common_dir =3D strbuf_detach(&sb, NULL);
git_object_dir =3D getenv(DB_ENVIRONMENT);
if (!git_object_dir) {
git_object_dir =3D 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, cons=
t char *arg)
"'git <command> [<revision>...] -- [<file>...]'", arg);
}
=20
-static void get_common_dir(struct strbuf *sb, const char *gitdir)
+int get_common_dir(struct strbuf *sb, const char *gitdir)
{
struct strbuf data =3D STRBUF_INIT;
struct strbuf path =3D STRBUF_INIT;
const char *git_common_dir =3D getenv(GIT_COMMON_DIR_ENVIRONMENT);
+ int ret =3D 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, con=
st char *gitdir)
strbuf_addf(&path, "%s/", gitdir);
strbuf_addbuf(&path, &data);
strbuf_addstr(sb, real_path(path.buf));
+ ret =3D 1;
} else
strbuf_addstr(sb, gitdir);
strbuf_release(&data);
strbuf_release(&path);
+ return ret;
}
=20
/*
@@ -286,13 +289,26 @@ void setup_work_tree(void)
initialized =3D 1;
}
=20
+static int check_repo_format(const char *var, const char *value, void =
*cb)
+{
+ if (strcmp(var, "core.repositoryformatversion") =3D=3D 0)
+ repository_format_version =3D git_config_int(var, value);
+ else if (strcmp(var, "core.sharedrepository") =3D=3D 0)
+ shared_repository =3D git_config_perm(var, value);
+ return 0;
+}
+
static int check_repository_format_gently(const char *gitdir, int *non=
git_ok)
{
struct strbuf sb =3D STRBUF_INIT;
const char *repo_config;
+ config_fn_t fn;
int ret =3D 0;
=20
- get_common_dir(&sb, gitdir);
+ if (get_common_dir(&sb, gitdir))
+ fn =3D check_repo_format;
+ else
+ fn =3D check_repository_format_version;
strbuf_addstr(&sb, "/config");
repo_config =3D sb.buf;
=20
@@ -305,7 +321,7 @@ static int check_repository_format_gently(const cha=
r *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 <=3D %d, found %d",
@@ -777,11 +793,10 @@ int git_config_perm(const char *var, const char *=
value)
=20
int check_repository_format_version(const char *var, const char *value=
, void *cb)
{
- if (strcmp(var, "core.repositoryformatversion") =3D=3D 0)
- repository_format_version =3D git_config_int(var, value);
- else if (strcmp(var, "core.sharedrepository") =3D=3D 0)
- shared_repository =3D git_config_perm(var, value);
- else if (strcmp(var, "core.bare") =3D=3D 0) {
+ int ret =3D check_repo_format(var, value, cb);
+ if (ret)
+ return ret;
+ if (strcmp(var, "core.bare") =3D=3D 0) {
is_bare_repository_cfg =3D git_config_bool(var, value);
if (is_bare_repository_cfg =3D=3D 1)
inside_work_tree =3D -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 g=
it subprocesses' '
test_cmp expected actual
'
=20
+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=3D../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=3D../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=3Drepo.git config core.worktree "$TRASH_DIRECTORY/elsew=
here" &&
+ 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=3D../.git --work-tree=3D. 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 =3D "(null)";
=20
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_g=
it_common_dir()));
trace_printf_key(key, "setup: worktree: %s\n", quote_crnl(git_work_tr=
ee));
trace_printf_key(key, "setup: cwd: %s\n", quote_crnl(cwd));
trace_printf_key(key, "setup: prefix: %s\n", quote_crnl(prefix));
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:55 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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, c=
onst 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, ..=
=2E);
=20
/* 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 =3D STRBUF_INIT;
+ int fd =3D 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) !=3D sb.len) {
+ int err =3D errno;
+ close(fd);
+ errno =3D 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;
+}
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:57 UTC
Permalink
"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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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-checkou=
t.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 s=
electively discard
edits from your current working tree. See the ``Interactive Mode''
section of linkgit:git-add[1] to learn how to operate the `--patch` mo=
de.
=20
+--to=3D<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
------------
=20
+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
--------
=20
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...
=20
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/git=
repository-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.
=20
+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;
};
=20
static int post_checkout_hook(struct commit *old, struct commit *new,
@@ -250,6 +254,9 @@ static int checkout_paths(const struct checkout_opt=
s *opts,
die(_("Cannot update paths and switch to branch '%s' at the same tim=
e."),
opts->new_branch);
=20
+ if (opts->new_worktree)
+ die(_("'%s' cannot be used with updating paths"), "--to");
+
if (opts->patch_mode)
return run_add_interactive(revision, "--patch=3Dcheckout",
&opts->pathspec);
@@ -486,7 +493,7 @@ static int merge_working_tree(const struct checkout=
_opts *opts,
topts.dir->flags |=3D DIR_SHOW_IGNORED;
setup_standard_excludes(topts.dir);
}
- tree =3D parse_tree_indirect(old->commit ?
+ tree =3D 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_op=
ts *opts,
return ret;
}
=20
- if (!opts->quiet && !old.path && old.commit && new->commit !=3D old.c=
ommit)
+ if (!opts->quiet && !old.path && old.commit &&
+ new->commit !=3D old.commit && !opts->new_worktree_mode)
orphaned_commit_warning(old.commit, new->commit);
=20
update_refs_for_switch(opts, &old, new);
@@ -806,6 +814,74 @@ static int switch_branches(const struct checkout_o=
pts *opts,
return ret || writeout_error;
}
=20
+static int prepare_linked_checkout(const struct checkout_opts *opts,
+ struct branch_info *new)
+{
+ struct strbuf sb_git =3D STRBUF_INIT, sb_repo =3D STRBUF_INIT;
+ struct strbuf sb =3D STRBUF_INIT;
+ const char *path =3D opts->new_worktree, *name;
+ struct stat st;
+ struct child_process cp;
+ int counter =3D 0, len;
+
+ if (!new->commit)
+ die(_("no branch specified"));
+
+ len =3D strlen(path);
+ while (len && is_dir_sep(path[len - 1]))
+ len--;
+
+ for (name =3D 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 =3D 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 =3D 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 =3D 1;
+ cp.argv =3D opts->saved_argv;
+ return run_command(&cp);
+}
+
static int git_checkout_config(const char *var, const char *value, voi=
d *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);
=20
+ 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, con=
st 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(),
};
=20
@@ -1117,6 +1198,9 @@ int cmd_checkout(int argc, const char **argv, con=
st char *prefix)
opts.overwrite_ignore =3D 1;
opts.prefix =3D prefix;
=20
+ opts.saved_argv =3D xmalloc(sizeof(const char *) * (argc + 2));
+ memcpy(opts.saved_argv, argv, sizeof(const char *) * (argc + 1));
+
gitmodules_config();
git_config(git_checkout_config, &opts);
=20
@@ -1125,6 +1209,11 @@ int cmd_checkout(int argc, const char **argv, co=
nst char *prefix)
argc =3D parse_options(argc, argv, prefix, options, checkout_usage,
PARSE_OPT_KEEP_DASHDASH);
=20
+ /* recursive execution from checkout_new_worktree() */
+ opts.new_worktree_mode =3D getenv("GIT_CHECKOUT_NEW_WORKTREE") !=3D N=
ULL;
+ if (opts.new_worktree_mode)
+ opts.new_worktree =3D NULL;
+
if (conflict_style) {
opts.merge =3D 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[] =3D {
"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[] =3D {
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=3D'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
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:58 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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"
=20
static const char * const checkout_usage[] =3D {
N_("git checkout [options] <branch>"),
@@ -814,6 +815,35 @@ static int switch_branches(const struct checkout_o=
pts *opts,
return ret || writeout_error;
}
=20
+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 =3D STRBUF_INIT;
+ if (!is_junk || getpid() !=3D 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 che=
ckout_opts *opts,
const char *path =3D opts->new_worktree, *name;
struct stat st;
struct child_process cp;
- int counter =3D 0, len;
+ int counter =3D 0, len, ret;
=20
if (!new->commit)
die(_("no branch specified"));
@@ -848,13 +878,21 @@ static int prepare_linked_checkout(const struct c=
heckout_opts *opts,
strbuf_addf(&sb_repo, "%d", counter);
}
name =3D strrchr(sb_repo.buf, '/') + 1;
+
+ junk_pid =3D 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 =3D sb_repo.buf;
+ is_junk =3D 1;
=20
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 =3D path;
=20
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 ch=
eckout_opts *opts,
memset(&cp, 0, sizeof(cp));
cp.git_cmd =3D 1;
cp.argv =3D opts->saved_argv;
- return run_command(&cp);
+ ret =3D run_command(&cp);
+ if (!ret)
+ is_junk =3D 0;
+ strbuf_release(&sb);
+ strbuf_release(&sb_repo);
+ strbuf_release(&sb_git);
+ return ret;
+
}
=20
static int git_checkout_config(const char *var, const char *value, voi=
d *cb)
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:12:59 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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;
};
=20
static void setup_branch_path(struct branch_info *branch)
@@ -640,6 +645,11 @@ static void update_refs_for_switch(const struct ch=
eckout_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 br=
anch\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;
}
=20
+static int check_linked_checkout(struct branch_info *new,
+ const char *name, const char *path)
+{
+ struct strbuf sb =3D 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 =3D sb.buf + 4;
+ while (isspace(*start))
+ start++;
+ end =3D start;
+ while (*end && !isspace(*end))
+ end++;
+ if (!strncmp(start, new->path, end - start) &&
+ new->path[end - start] =3D=3D '\0') {
+ strbuf_release(&sb);
+ new->path =3D NULL; /* detach */
+ new->checkout =3D xstrdup(name); /* reason */
+ return 1;
+ }
+ strbuf_release(&sb);
+ return 0;
+}
+
+static void check_linked_checkouts(struct branch_info *new)
+{
+ struct strbuf path =3D STRBUF_INIT;
+ DIR *dir;
+ struct dirent *d;
+
+ strbuf_addf(&path, "%s/repos", get_git_common_dir());
+ if ((dir =3D opendir(path.buf)) =3D=3D 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 =3D readdir(dir)) !=3D 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 c=
har **argv,
else
new->path =3D NULL; /* not an existing branch */
=20
+ if (new->path)
+ check_linked_checkouts(new);
+
new->commit =3D 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 pat=
hs' '
'
=20
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 cr=
eating new branch' '
)
'
=20
+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
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:13:00 UTC
Permalink
(alias R=3D$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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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>.
=20
+--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/git=
repository-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.
=20
+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);
}
=20
+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 =3D 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 che=
ckout_opts *opts,
const char *path =3D opts->new_worktree, *name;
struct stat st;
struct child_process cp;
- int counter =3D 0, len, ret;
+ int counter =3D 0, len, keep_locked =3D 0, ret;
=20
if (!new->commit)
die(_("no branch specified"));
@@ -898,12 +909,18 @@ static int prepare_linked_checkout(const struct c=
heckout_opts *opts,
junk_git_dir =3D sb_repo.buf;
is_junk =3D 1;
=20
+ 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 =3D path;
=20
+ 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 c=
heckout_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");
=20
+ if (get_device_or_die(path) !=3D 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 =3D 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);
=20
@@ -930,6 +959,11 @@ static int prepare_linked_checkout(const struct ch=
eckout_opts *opts,
ret =3D run_command(&cp);
if (!ret)
is_junk =3D 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)
}
}
=20
+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 =3D expire;
+ return _("gitdir does not exist");
+ }
+ fd =3D open(git_path("repos/%s/gitdir", id), O_RDONLY);
+ len =3D st->st_size;
+ path =3D xmalloc(len + 1);
+ read_in_full(fd, path, len);
+ close(fd);
+ while (path[len - 1] =3D=3D '\n' || path[len - 1] =3D=3D '\r')
+ len--;
+ path[len] =3D '\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 =3D opendir(git_path("repos"));
+ struct dirent *d;
+ int removed =3D 0;
+ struct stat st;
+ if (!dir)
+ return;
+ while ((d =3D readdir(dir)) !=3D NULL) {
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+ if ((reason =3D prune_repo_dir(d->d_name, &st)) !=3D NULL &&
+ st.st_mtime <=3D expire) {
+ struct strbuf sb =3D 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 =3D 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 =3D NULL;
+ int prune_repos =3D 0;
const struct option options[] =3D {
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 c=
har *prefix)
init_revisions(&revs, prefix);
=20
argc =3D 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 =3D *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 *forma=
t, ...) __attribute__((format
* git specific compatibility
*/
=20
+#define is_windows() 1
#define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] =3D=3D=
':')
#define is_dir_sep(c) ((c) =3D=3D '/' || (c) =3D=3D '\\')
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
=20
+#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 ch=
ar *gitdir, int *nongit_ok)
return ret;
}
=20
+static void update_linked_gitdir(const char *gitfile, const char *gitd=
ir)
+{
+ struct strbuf path =3D 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)
=20
if (!is_git_directory(dir))
die("Not a git repository: %s", dir);
+
+ update_linked_gitdir(path, dir);
path =3D real_path(dir);
=20
free(buf);
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:13:01 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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 (increas=
ed 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", NUL=
L);
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);
=20
git_config(gc_config, NULL);
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:13:02 UTC
Permalink
Signed-off-by: Nguy=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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.
=20
+gc.prunereposexpire::
+ When 'git gc' is run, it will call 'prune --repos --expire 3.months.a=
go'.
+ 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 =3D 250;
static int gc_auto_threshold =3D 6700;
static int gc_auto_pack_limit =3D 50;
static const char *prune_expire =3D "2.weeks.ago";
+static const char *prune_repos_expire =3D "3.months.ago";
=20
static struct argv_array pack_refs_cmd =3D ARGV_ARRAY_INIT;
static struct argv_array reflog =3D ARGV_ARRAY_INIT;
static struct argv_array repack =3D ARGV_ARRAY_INIT;
static struct argv_array prune =3D ARGV_ARRAY_INIT;
+static struct argv_array prune_repos =3D ARGV_ARRAY_INIT;
static struct argv_array rerere =3D ARGV_ARRAY_INIT;
=20
static char *pidfile;
@@ -81,6 +83,14 @@ static int gc_config(const char *var, const char *va=
lue, void *cb)
}
return git_config_string(&prune_expire, var, value);
}
+ if (!strcmp(var, "gc.prunereposexpire")) {
+ if (value && strcmp(value, "now")) {
+ unsigned long now =3D approxidate("now");
+ if (approxidate(value) >=3D now)
+ return error(_("Invalid %s: '%s'"), var, value);
+ }
+ return git_config_string(&prune_repos_expire, var, value);
+ }
return git_default_config(var, value, cb);
}
=20
@@ -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);
=20
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]);
}
=20
+ 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]);
=20
--=20
1.9.0.40.gaa8c3ea
Nguyễn Thái Ngọc Duy
2014-03-01 12:13:03 UTC
Permalink
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=E1=BB=85n Th=C3=A1i Ng=E1=BB=8Dc Duy <***@gmail=
=2Ecom>
---
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 le=
n, int verbose,
}
}
=20
+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[] =3D {
+ "branches", "hooks", "info", "lost-found", "modules",
+ "objects", "refs", "remotes", "rr-cache", "svn",
+ "config", "packed-refs", "shallow", NULL
+ };
+ struct strbuf sb =3D STRBUF_INIT;
+ const char **p;
+ int len;
+
+ if (!file_exists(git_path("commondir")))
+ return;
+ strbuf_addf(&sb, "%s/", get_git_dir());
+ len =3D sb.len;
+ for (p =3D 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[] =3D {
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 =3D real_report_garbage;
+ report_linked_checkout_garbage();
+ }
memcpy(path, objdir, len);
if (len && objdir[len-1] !=3D '/')
path[len++] =3D '/';
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)
=20
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[] =3D {
"branches", "hooks", "info", "logs", "lost-found", "modules",
"objects", "refs", "remotes", "repos", "rr-cache", "svn",
--=20
1.9.0.40.gaa8c3ea

Continue reading on narkive:
Loading...