From b44ebb19e3234c5dffe9869ceac5408bb44c2e20 Mon Sep 17 00:00:00 2001 From: Lars Hjemli Date: Wed, 20 Feb 2008 23:13:13 +0100 Subject: [PATCH 1/4] Add platform-independent .git "symlink" This patch allows .git to be a regular textfile containing the path of the real git directory (prefixed with "gitdir: "), which can be useful on platforms lacking support for real symlinks. Signed-off-by: Lars Hjemli Signed-off-by: Junio C Hamano --- Documentation/repository-layout.txt | 5 +- cache.h | 1 + environment.c | 2 + setup.c | 47 +++++++++++++ t/t0002-gitfile.sh | 103 ++++++++++++++++++++++++++++ 5 files changed, 157 insertions(+), 1 deletion(-) create mode 100755 t/t0002-gitfile.sh diff --git a/Documentation/repository-layout.txt b/Documentation/repository-layout.txt index 6939130094..bbaed2e129 100644 --- a/Documentation/repository-layout.txt +++ b/Documentation/repository-layout.txt @@ -3,7 +3,10 @@ git repository layout You may find these things in your git repository (`.git` directory for a repository associated with your working tree, or -`'project'.git` directory for a public 'bare' repository). +`'project'.git` directory for a public 'bare' repository. It is +also possible to have a working tree where `.git` is a plain +ascii file containing `gitdir: `, i.e. the path to the +real git repository). objects:: Object store associated with this repository. Usually diff --git a/cache.h b/cache.h index 2a1e7ec6b2..2bda40c616 100644 --- a/cache.h +++ b/cache.h @@ -311,6 +311,7 @@ extern char *get_index_file(void); extern char *get_graft_file(void); extern int set_git_dir(const char *path); extern const char *get_git_work_tree(void); +extern const char *read_gitfile_gently(const char *path); #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES" diff --git a/environment.c b/environment.c index 6739a3f417..fcd1ee5ef8 100644 --- a/environment.c +++ b/environment.c @@ -49,6 +49,8 @@ static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file; static void setup_git_env(void) { git_dir = getenv(GIT_DIR_ENVIRONMENT); + if (!git_dir) + git_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT); if (!git_dir) git_dir = DEFAULT_GIT_DIR_ENVIRONMENT; git_object_dir = getenv(DB_ENVIRONMENT); diff --git a/setup.c b/setup.c index 3d2d9580f3..53580fde43 100644 --- a/setup.c +++ b/setup.c @@ -314,6 +314,44 @@ static int check_repository_format_gently(int *nongit_ok) return 0; } +/* + * Try to read the location of the git directory from the .git file, + * return path to git directory if found. + */ +const char *read_gitfile_gently(const char *path) +{ + char *buf; + struct stat st; + int fd; + size_t len; + + if (stat(path, &st)) + return NULL; + if (!S_ISREG(st.st_mode)) + return NULL; + fd = open(path, O_RDONLY); + if (fd < 0) + die("Error opening %s: %s", path, strerror(errno)); + buf = xmalloc(st.st_size + 1); + len = read_in_full(fd, buf, st.st_size); + close(fd); + if (len != st.st_size) + die("Error reading %s", path); + buf[len] = '\0'; + if (prefixcmp(buf, "gitdir: ")) + die("Invalid gitfile format: %s", path); + while (buf[len - 1] == '\n' || buf[len - 1] == '\r') + len--; + if (len < 9) + die("No path in gitfile: %s", path); + buf[len] = '\0'; + if (!is_git_directory(buf + 8)) + die("Not a git repository: %s", buf + 8); + path = make_absolute_path(buf + 8); + free(buf); + return path; +} + /* * We cannot decide in this function whether we are in the work tree or * not, since the config can only be read _after_ this function was called. @@ -323,6 +361,7 @@ const char *setup_git_directory_gently(int *nongit_ok) const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT); static char cwd[PATH_MAX+1]; const char *gitdirenv; + const char *gitfile_dir; int len, offset; /* @@ -377,8 +416,10 @@ const char *setup_git_directory_gently(int *nongit_ok) /* * Test in the following order (relative to the cwd): + * - .git (file containing "gitdir: ") * - .git/ * - ./ (bare) + * - ../.git * - ../.git/ * - ../ (bare) * - ../../.git/ @@ -386,6 +427,12 @@ const char *setup_git_directory_gently(int *nongit_ok) */ offset = len = strlen(cwd); for (;;) { + gitfile_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT); + if (gitfile_dir) { + if (set_git_dir(gitfile_dir)) + die("Repository setup failed"); + break; + } if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT)) break; if (is_git_directory(".")) { diff --git a/t/t0002-gitfile.sh b/t/t0002-gitfile.sh new file mode 100755 index 0000000000..c5dbc724b6 --- /dev/null +++ b/t/t0002-gitfile.sh @@ -0,0 +1,103 @@ +#!/bin/sh + +test_description='.git file + +Verify that plumbing commands work when .git is a file +' +. ./test-lib.sh + +objpath() { + echo "$1" | sed -e 's|\(..\)|\1/|' +} + +objck() { + p=$(objpath "$1") + if test ! -f "$REAL/objects/$p" + then + echo "Object not found: $REAL/objects/$p" + false + fi +} + + +test_expect_success 'initial setup' ' + REAL="$(pwd)/.real" && + mv .git "$REAL" +' + +test_expect_success 'bad setup: invalid .git file format' ' + echo "gitdir $REAL" >.git && + if git rev-parse 2>.err + then + echo "git rev-parse accepted an invalid .git file" + false + fi && + if ! grep -qe "Invalid gitfile format" .err + then + echo "git rev-parse returned wrong error" + false + fi +' + +test_expect_success 'bad setup: invalid .git file path' ' + echo "gitdir: $REAL.not" >.git && + if git rev-parse 2>.err + then + echo "git rev-parse accepted an invalid .git file path" + false + fi && + if ! grep -qe "Not a git repository" .err + then + echo "git rev-parse returned wrong error" + false + fi +' + +test_expect_success 'final setup + check rev-parse --git-dir' ' + echo "gitdir: $REAL" >.git && + test "$REAL" = "$(git rev-parse --git-dir)" +' + +test_expect_success 'check hash-object' ' + echo "foo" >bar && + SHA=$(cat bar | git hash-object -w --stdin) && + objck $SHA +' + +test_expect_success 'check cat-file' ' + git cat-file blob $SHA >actual && + diff -u bar actual +' + +test_expect_success 'check update-index' ' + if test -f "$REAL/index" + then + echo "Hmm, $REAL/index exists?" + false + fi && + rm -f "$REAL/objects/$(objpath $SHA)" && + git update-index --add bar && + if ! test -f "$REAL/index" + then + echo "$REAL/index not found" + false + fi && + objck $SHA +' + +test_expect_success 'check write-tree' ' + SHA=$(git write-tree) && + objck $SHA +' + +test_expect_success 'check commit-tree' ' + SHA=$(echo "commit bar" | git commit-tree $SHA) && + objck $SHA +' + +test_expect_success 'check rev-list' ' + echo $SHA >"$REAL/HEAD" && + test "$SHA" = "$(git rev-list HEAD)" +' + +test_done From 842abf06f36b5b31050db6406265972e3e1cc189 Mon Sep 17 00:00:00 2001 From: Lars Hjemli Date: Wed, 20 Feb 2008 23:13:14 +0100 Subject: [PATCH 2/4] Teach resolve_gitlink_ref() about the .git file When .git in a submodule is a file, resolve_gitlink_ref() needs to pick up the real GIT_DIR of the submodule from that file. Signed-off-by: Lars Hjemli Signed-off-by: Junio C Hamano --- refs.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/refs.c b/refs.c index 1b0050eee4..a81fb291f8 100644 --- a/refs.c +++ b/refs.c @@ -352,6 +352,7 @@ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *re { int len = strlen(path), retval; char *gitdir; + const char *tmp; while (len && path[len-1] == '/') len--; @@ -359,9 +360,19 @@ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *re return -1; gitdir = xmalloc(len + MAXREFLEN + 8); memcpy(gitdir, path, len); - memcpy(gitdir + len, "/.git/", 7); + memcpy(gitdir + len, "/.git", 6); + len += 5; - retval = resolve_gitlink_ref_recursive(gitdir, len+6, refname, result, 0); + tmp = read_gitfile_gently(gitdir); + if (tmp) { + free(gitdir); + len = strlen(tmp); + gitdir = xmalloc(len + MAXREFLEN + 3); + memcpy(gitdir, tmp, len); + } + gitdir[len] = '/'; + gitdir[++len] = '\0'; + retval = resolve_gitlink_ref_recursive(gitdir, len, refname, result, 0); free(gitdir); return retval; } From ba88a1fee476a7895634beb9f33faf7e7b7908af Mon Sep 17 00:00:00 2001 From: Lars Hjemli Date: Wed, 20 Feb 2008 23:13:15 +0100 Subject: [PATCH 3/4] Teach git-submodule.sh about the .git file When git-submodule tries to detect 'active' submodules, it checks for the existence of a directory named '.git'. This isn't good enough now that .git can be a file pointing to the real $GIT_DIR so the tests are changed to reflect this. Signed-off-by: Lars Hjemli Signed-off-by: Junio C Hamano --- git-submodule.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git-submodule.sh b/git-submodule.sh index 56ec3536e0..5bff86022c 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -300,7 +300,7 @@ cmd_update() continue fi - if ! test -d "$path"/.git + if ! test -d "$path"/.git -o -f "$path"/.git then module_clone "$path" "$url" || exit subsha1= @@ -542,7 +542,7 @@ cmd_status() do name=$(module_name "$path") || exit url=$(git config submodule."$name".url) - if test -z "$url" || ! test -d "$path"/.git + if test -z "$url" || ! test -d "$path"/.git -o -f "$path"/.git then say "-$sha1 $path" continue; From c48799e560c826905e87c8a8a71effeb0ca5e897 Mon Sep 17 00:00:00 2001 From: Lars Hjemli Date: Wed, 20 Feb 2008 23:13:16 +0100 Subject: [PATCH 4/4] Teach GIT-VERSION-GEN about the .git file The presence of a .git directory used to be good enough evidence that GIT-VERSION-GEN could use 'git describe' to get a version number. But now .git might as well be a file so the test must be extended to cater for such setups. Signed-off-by: Lars Hjemli Signed-off-by: Junio C Hamano --- GIT-VERSION-GEN | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index f60bab896b..3cabc92e7a 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -11,7 +11,7 @@ LF=' if test -f version then VN=$(cat version) || VN="$DEF_VER" -elif test -d .git && +elif test -d .git -o -f .git && VN=$(git describe --abbrev=4 HEAD 2>/dev/null) && case "$VN" in *$LF*) (exit 1) ;;