{"thread":{"id":"12223","subject":"[PATCH 2/4] Teach resolve_gitlink_ref() about the .git file","startedAt":"2008-02-20T22:13:12Z","lastAt":"2008-02-20T22:13:16Z","messageCount":5,"participants":["Lars Hjemli"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"69408","messageId":"1203545596-6337-1-git-send-email-hjemli@gmail.com","threadId":"12223","inReplyTo":null,"subject":"[PATCH 0/4 v3] Introducing the .git file","fromName":"Lars Hjemli","fromEmail":"hjemli@gmail.com","sentAt":"2008-02-20T22:13:12Z","receivedAt":"2008-02-20T22:13:12Z","isPatch":true,"sender":{"key":"hjemli@gmail.com","avatar":null},"body":"This is a resend of my previous series implementing the .git file,\nrebased on top of todays master (e3c58f8b). The patches are identical\nto the final suggestions in http://thread.gmane.org/gmane.comp.version-control.git/74258\nexcept for the previous 1/5 which is dropped from the series (since it\nalready appears in master). There are also some minor touch-ups to the\ncommit messages.\n\nShortlog:\n Add platform-independent .git \"symlink\"\n Teach resolve_gitlink_ref() about the .git file\n Teach git-submodule.sh about the .git file\n Teach GIT-VERSION-GEN about the .git file\n\nDiffstat:\n Documentation/repository-layout.txt |    5 ++-\n GIT-VERSION-GEN                     |    2 +-\n cache.h                             |    1 +\n environment.c                       |    2 +\n git-submodule.sh                    |    4 +-\n refs.c                              |   17 +++++-\n setup.c                             |   47 ++++++++++++++++\n t/t0002-gitfile.sh                  |  103 +++++++++++++++++++++++++++++++++++\n 8 files changed, 174 insertions(+), 7 deletions(-)\n"},{"id":"69414","messageId":"1203545596-6337-2-git-send-email-hjemli@gmail.com","threadId":"12223","inReplyTo":"1203545596-6337-1-git-send-email-hjemli@gmail.com","subject":"[PATCH 1/4] Add platform-independent .git \"symlink\"","fromName":"Lars Hjemli","fromEmail":"hjemli@gmail.com","sentAt":"2008-02-20T22:13:13Z","receivedAt":"2008-02-20T22:13:13Z","isPatch":true,"sender":{"key":"hjemli@gmail.com","avatar":null},"body":"This patch allows .git to be a regular textfile containing the path of\nthe real git directory (prefixed with \"gitdir: \"), which can be useful on\nplatforms lacking support for real symlinks.\n\nSigned-off-by: Lars Hjemli <hjemli@gmail.com>\n---\n Documentation/repository-layout.txt |    5 ++-\n cache.h                             |    1 +\n environment.c                       |    2 +\n setup.c                             |   47 ++++++++++++++++\n t/t0002-gitfile.sh                  |  103 +++++++++++++++++++++++++++++++++++\n 5 files changed, 157 insertions(+), 1 deletions(-)\n create mode 100755 t/t0002-gitfile.sh\n\ndiff --git a/Documentation/repository-layout.txt b/Documentation/repository-layout.txt\nindex 6939130..bbaed2e 100644\n--- a/Documentation/repository-layout.txt\n+++ b/Documentation/repository-layout.txt\n@@ -3,7 +3,10 @@ git repository layout\n \n You may find these things in your git repository (`.git`\n directory for a repository associated with your working tree, or\n-`'project'.git` directory for a public 'bare' repository).\n+`'project'.git` directory for a public 'bare' repository. It is\n+also possible to have a working tree where `.git` is a plain\n+ascii file containing `gitdir: <path>`, i.e. the path to the\n+real git repository).\n \n objects::\n \tObject store associated with this repository.  Usually\ndiff --git a/cache.h b/cache.h\nindex e1000bc..1ad822a 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -277,6 +277,7 @@ extern char *get_index_file(void);\n extern char *get_graft_file(void);\n extern int set_git_dir(const char *path);\n extern const char *get_git_work_tree(void);\n+extern const char *read_gitfile_gently(const char *path);\n \n #define ALTERNATE_DB_ENVIRONMENT \"GIT_ALTERNATE_OBJECT_DIRECTORIES\"\n \ndiff --git a/environment.c b/environment.c\nindex 3527f16..8058e7b 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -49,6 +49,8 @@ static void setup_git_env(void)\n {\n \tgit_dir = getenv(GIT_DIR_ENVIRONMENT);\n \tif (!git_dir)\n+\t\tgit_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT);\n+\tif (!git_dir)\n \t\tgit_dir = DEFAULT_GIT_DIR_ENVIRONMENT;\n \tgit_object_dir = getenv(DB_ENVIRONMENT);\n \tif (!git_object_dir) {\ndiff --git a/setup.c b/setup.c\nindex 4509598..20502be 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -239,6 +239,44 @@ static int check_repository_format_gently(int *nongit_ok)\n }\n \n /*\n+ * Try to read the location of the git directory from the .git file,\n+ * return path to git directory if found.\n+ */\n+const char *read_gitfile_gently(const char *path)\n+{\n+\tchar *buf;\n+\tstruct stat st;\n+\tint fd;\n+\tsize_t len;\n+\n+\tif (stat(path, &st))\n+\t\treturn NULL;\n+\tif (!S_ISREG(st.st_mode))\n+\t\treturn NULL;\n+\tfd = open(path, O_RDONLY);\n+\tif (fd < 0)\n+\t\tdie(\"Error opening %s: %s\", path, strerror(errno));\n+\tbuf = xmalloc(st.st_size + 1);\n+\tlen = read_in_full(fd, buf, st.st_size);\n+\tclose(fd);\n+\tif (len != st.st_size)\n+\t\tdie(\"Error reading %s\", path);\n+\tbuf[len] = '\\0';\n+\tif (prefixcmp(buf, \"gitdir: \"))\n+\t\tdie(\"Invalid gitfile format: %s\", path);\n+\twhile (buf[len - 1] == '\\n' || buf[len - 1] == '\\r')\n+\t\tlen--;\n+\tif (len < 9)\n+\t\tdie(\"No path in gitfile: %s\", path);\n+\tbuf[len] = '\\0';\n+\tif (!is_git_directory(buf + 8))\n+\t\tdie(\"Not a git repository: %s\", buf + 8);\n+\tpath = make_absolute_path(buf + 8);\n+\tfree(buf);\n+\treturn path;\n+}\n+\n+/*\n  * We cannot decide in this function whether we are in the work tree or\n  * not, since the config can only be read _after_ this function was called.\n  */\n@@ -247,6 +285,7 @@ const char *setup_git_directory_gently(int *nongit_ok)\n \tconst char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);\n \tstatic char cwd[PATH_MAX+1];\n \tconst char *gitdirenv;\n+\tconst char *gitfile_dir;\n \tint len, offset;\n \n \t/*\n@@ -293,8 +332,10 @@ const char *setup_git_directory_gently(int *nongit_ok)\n \n \t/*\n \t * Test in the following order (relative to the cwd):\n+\t * - .git (file containing \"gitdir: <path>\")\n \t * - .git/\n \t * - ./ (bare)\n+\t * - ../.git\n \t * - ../.git/\n \t * - ../ (bare)\n \t * - ../../.git/\n@@ -302,6 +343,12 @@ const char *setup_git_directory_gently(int *nongit_ok)\n \t */\n \toffset = len = strlen(cwd);\n \tfor (;;) {\n+\t\tgitfile_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT);\n+\t\tif (gitfile_dir) {\n+\t\t\tif (set_git_dir(gitfile_dir))\n+\t\t\t\tdie(\"Repository setup failed\");\n+\t\t\tbreak;\n+\t\t}\n \t\tif (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))\n \t\t\tbreak;\n \t\tif (is_git_directory(\".\")) {\ndiff --git a/t/t0002-gitfile.sh b/t/t0002-gitfile.sh\nnew file mode 100755\nindex 0000000..c5dbc72\n--- /dev/null\n+++ b/t/t0002-gitfile.sh\n@@ -0,0 +1,103 @@\n+#!/bin/sh\n+\n+test_description='.git file\n+\n+Verify that plumbing commands work when .git is a file\n+'\n+. ./test-lib.sh\n+\n+objpath() {\n+    echo \"$1\" | sed -e 's|\\(..\\)|\\1/|'\n+}\n+\n+objck() {\n+\tp=$(objpath \"$1\")\n+\tif test ! -f \"$REAL/objects/$p\"\n+\tthen\n+\t\techo \"Object not found: $REAL/objects/$p\"\n+\t\tfalse\n+\tfi\n+}\n+\n+\n+test_expect_success 'initial setup' '\n+\tREAL=\"$(pwd)/.real\" &&\n+\tmv .git \"$REAL\"\n+'\n+\n+test_expect_success 'bad setup: invalid .git file format' '\n+\techo \"gitdir $REAL\" >.git &&\n+\tif git rev-parse 2>.err\n+\tthen\n+\t\techo \"git rev-parse accepted an invalid .git file\"\n+\t\tfalse\n+\tfi &&\n+\tif ! grep -qe \"Invalid gitfile format\" .err\n+\tthen\n+\t\techo \"git rev-parse returned wrong error\"\n+\t\tfalse\n+\tfi\n+'\n+\n+test_expect_success 'bad setup: invalid .git file path' '\n+\techo \"gitdir: $REAL.not\" >.git &&\n+\tif git rev-parse 2>.err\n+\tthen\n+\t\techo \"git rev-parse accepted an invalid .git file path\"\n+\t\tfalse\n+\tfi &&\n+\tif ! grep -qe \"Not a git repository\" .err\n+\tthen\n+\t\techo \"git rev-parse returned wrong error\"\n+\t\tfalse\n+\tfi\n+'\n+\n+test_expect_success 'final setup + check rev-parse --git-dir' '\n+\techo \"gitdir: $REAL\" >.git &&\n+\ttest \"$REAL\" = \"$(git rev-parse --git-dir)\"\n+'\n+\n+test_expect_success 'check hash-object' '\n+\techo \"foo\" >bar &&\n+\tSHA=$(cat bar | git hash-object -w --stdin) &&\n+\tobjck $SHA\n+'\n+\n+test_expect_success 'check cat-file' '\n+\tgit cat-file blob $SHA >actual &&\n+\tdiff -u bar actual\n+'\n+\n+test_expect_success 'check update-index' '\n+\tif test -f \"$REAL/index\"\n+\tthen\n+\t\techo \"Hmm, $REAL/index exists?\"\n+\t\tfalse\n+\tfi &&\n+\trm -f \"$REAL/objects/$(objpath $SHA)\" &&\n+\tgit update-index --add bar &&\n+\tif ! test -f \"$REAL/index\"\n+\tthen\n+\t\techo \"$REAL/index not found\"\n+\t\tfalse\n+\tfi &&\n+\tobjck $SHA\n+'\n+\n+test_expect_success 'check write-tree' '\n+\tSHA=$(git write-tree) &&\n+\tobjck $SHA\n+'\n+\n+test_expect_success 'check commit-tree' '\n+\tSHA=$(echo \"commit bar\" | git commit-tree $SHA) &&\n+\tobjck $SHA\n+'\n+\n+test_expect_success 'check rev-list' '\n+\techo $SHA >\"$REAL/HEAD\" &&\n+\ttest \"$SHA\" = \"$(git rev-list HEAD)\"\n+'\n+\n+test_done\n-- \n1.5.4.1.188.g3ea1f5\n"},{"id":"69407","messageId":"1203545596-6337-3-git-send-email-hjemli@gmail.com","threadId":"12223","inReplyTo":"1203545596-6337-2-git-send-email-hjemli@gmail.com","subject":"[PATCH 2/4] Teach resolve_gitlink_ref() about the .git file","fromName":"Lars Hjemli","fromEmail":"hjemli@gmail.com","sentAt":"2008-02-20T22:13:14Z","receivedAt":"2008-02-20T22:13:14Z","isPatch":true,"sender":{"key":"hjemli@gmail.com","avatar":null},"body":"When .git in a submodule is a file, resolve_gitlink_ref() needs to pick up\nthe real GIT_DIR of the submodule from that file.\n\nSigned-off-by: Lars Hjemli <hjemli@gmail.com>\n---\n refs.c |   17 ++++++++++++++---\n 1 files changed, 14 insertions(+), 3 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 67d2a50..56de5cf 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -351,6 +351,7 @@ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *re\n {\n \tint len = strlen(path), retval;\n \tchar *gitdir;\n+\tconst char *tmp;\n \n \twhile (len && path[len-1] == '/')\n \t\tlen--;\n@@ -358,9 +359,19 @@ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *re\n \t\treturn -1;\n \tgitdir = xmalloc(len + MAXREFLEN + 8);\n \tmemcpy(gitdir, path, len);\n-\tmemcpy(gitdir + len, \"/.git/\", 7);\n-\n-\tretval = resolve_gitlink_ref_recursive(gitdir, len+6, refname, result, 0);\n+\tmemcpy(gitdir + len, \"/.git\", 6);\n+\tlen += 5;\n+\n+\ttmp = read_gitfile_gently(gitdir);\n+\tif (tmp) {\n+\t\tfree(gitdir);\n+\t\tlen = strlen(tmp);\n+\t\tgitdir = xmalloc(len + MAXREFLEN + 3);\n+\t\tmemcpy(gitdir, tmp, len);\n+\t}\n+\tgitdir[len] = '/';\n+\tgitdir[++len] = '\\0';\n+\tretval = resolve_gitlink_ref_recursive(gitdir, len, refname, result, 0);\n \tfree(gitdir);\n \treturn retval;\n }\n-- \n1.5.4.1.188.g3ea1f5\n"},{"id":"69410","messageId":"1203545596-6337-4-git-send-email-hjemli@gmail.com","threadId":"12223","inReplyTo":"1203545596-6337-3-git-send-email-hjemli@gmail.com","subject":"[PATCH 3/4] Teach git-submodule.sh about the .git file","fromName":"Lars Hjemli","fromEmail":"hjemli@gmail.com","sentAt":"2008-02-20T22:13:15Z","receivedAt":"2008-02-20T22:13:15Z","isPatch":true,"sender":{"key":"hjemli@gmail.com","avatar":null},"body":"When git-submodule tries to detect 'active' submodules, it checks for the\nexistence of a directory named '.git'. This isn't good enough now that .git\ncan be a file pointing to the real $GIT_DIR so the tests are changed to\nreflect this.\n\nSigned-off-by: Lars Hjemli <hjemli@gmail.com>\n---\n git-submodule.sh |    4 ++--\n 1 files changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/git-submodule.sh b/git-submodule.sh\nindex a6aaf40..e7c08b5 100755\n--- a/git-submodule.sh\n+++ b/git-submodule.sh\n@@ -288,7 +288,7 @@ cmd_update()\n \t\t\tcontinue\n \t\tfi\n \n-\t\tif ! test -d \"$path\"/.git\n+\t\tif ! test -d \"$path\"/.git -o -f \"$path\"/.git\n \t\tthen\n \t\t\tmodule_clone \"$path\" \"$url\" || exit\n \t\t\tsubsha1=\n@@ -362,7 +362,7 @@ cmd_status()\n \tdo\n \t\tname=$(module_name \"$path\") || exit\n \t\turl=$(git config submodule.\"$name\".url)\n-\t\tif test -z \"url\" || ! test -d \"$path\"/.git\n+\t\tif test -z \"url\" || ! test -d \"$path\"/.git -o -f \"$path\"/.git\n \t\tthen\n \t\t\tsay \"-$sha1 $path\"\n \t\t\tcontinue;\n-- \n1.5.4.1.188.g3ea1f5\n"},{"id":"69409","messageId":"1203545596-6337-5-git-send-email-hjemli@gmail.com","threadId":"12223","inReplyTo":"1203545596-6337-4-git-send-email-hjemli@gmail.com","subject":"[PATCH 4/4] Teach GIT-VERSION-GEN about the .git file","fromName":"Lars Hjemli","fromEmail":"hjemli@gmail.com","sentAt":"2008-02-20T22:13:16Z","receivedAt":"2008-02-20T22:13:16Z","isPatch":true,"sender":{"key":"hjemli@gmail.com","avatar":null},"body":"The presence of a .git directory used to be good enough evidence that\nGIT-VERSION-GEN could use 'git describe' to get a version number. But\nnow .git might as well be a file so the test must be extended to cater for\nsuch setups.\n\nSigned-off-by: Lars Hjemli <hjemli@gmail.com>\n---\n GIT-VERSION-GEN |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN\nindex 1ad324e..10fe488 100755\n--- a/GIT-VERSION-GEN\n+++ b/GIT-VERSION-GEN\n@@ -11,7 +11,7 @@ LF='\n if test -f version\n then\n \tVN=$(cat version) || VN=\"$DEF_VER\"\n-elif test -d .git &&\n+elif test -d .git -o -f .git &&\n \tVN=$(git describe --abbrev=4 HEAD 2>/dev/null) &&\n \tcase \"$VN\" in\n \t*$LF*) (exit 1) ;;\n-- \n1.5.4.1.188.g3ea1f5\n"}]}