Add platform-independent .git "symlink"
authorLars Hjemli <hjemli@gmail.com>
Wed, 20 Feb 2008 22:13:13 +0000 (23:13 +0100)
committerJunio C Hamano <gitster@pobox.com>
Wed, 9 Apr 2008 08:22:50 +0000 (01:22 -0700)
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 <hjemli@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/repository-layout.txt
cache.h
environment.c
setup.c
t/t0002-gitfile.sh [new file with mode: 0755]

index 69391300944438576e11cff19b4ce1aa2215b97b..bbaed2e129fb9ced1752183099fdb40ea8e341b3 100644 (file)
@@ -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: <path>`, 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 2a1e7ec6b2bf712af80813936b5aed434d02090e..2bda40c6166f9b7d33e1ee1ef1e8e1ca931bc996 100644 (file)
--- 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"
 
index 6739a3f41745fe1c01dc767e09af8746d6f2815f..fcd1ee5ef8c0999fce0095a2d6a792e5d221c50a 100644 (file)
@@ -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 3d2d9580f3283b4e4bc741d98863777510bd5173..53580fde43ce01f12dc326b419d94573ea14787a 100644 (file)
--- 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: <path>")
         * - .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 (executable)
index 0000000..c5dbc72
--- /dev/null
@@ -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