{"thread":{"id":"1971","subject":"[PATCH] Add git-symbolic-ref","startedAt":"2005-09-29T04:24:08Z","lastAt":"2005-09-29T04:24:08Z","messageCount":1,"participants":["Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"9449","messageId":"7vek78pklz.fsf@assigned-by-dhcp.cox.net","threadId":"1971","inReplyTo":null,"subject":"[PATCH] Add git-symbolic-ref","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2005-09-29T04:24:08Z","receivedAt":"2005-09-29T04:24:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This adds the counterpart of git-update-ref that lets you read\nand create \"symbolic refs\".  By default it uses a symbolic link\nto represent \".git/HEAD -> refs/heads/master\", but it can be\ncompiled to use the textfile symbolic ref.  In that case, the\n.git/HEAD file would typically read \"ref: refs/heads/master\".\n\nThe places that did 'readlink .git/HEAD' and 'ln -s\nrefs/heads/blah .git/HEAD' have been converted to use new\ngit-symbolic-ref command, so that they can deal with either\nimplementation.\n\nI suspect that I ended up wasting my git day of this week\nworking on this, given that Cygwin seems to be capable of\nemulating symlinks using .lnk files.  On the other hand, even\nLinux folks may be on vfat or similar fs that does not have\nsymlinks so who knows.  On yet another hand, a project can have\nsymlinks stored in the repository, and in order to check things\nout the filesystem needs to have symlinks anyway, so maybe this\nis a big wasted effort.  Mumble mumble mumble...\n\nSigned-off-by: Junio C Hamano <junkio@cox.net>\n\n---\n\n Makefile             |    2 +-\n cache.h              |    1 +\n fsck-objects.c       |   28 ++++++++++------------------\n git-bisect.sh        |    7 ++++---\n git-branch.sh        |    6 ++++--\n git-checkout.sh      |    3 ++-\n git-commit.sh        |   17 ++++++++---------\n git-sh-setup.sh      |    4 ++--\n git-status.sh        |    4 ++--\n init-db.c            |   10 +++++-----\n refs.c               |   46 ++++++++++++++++++++++++++++++++++++++++++++--\n setup.c              |   16 ++++++++++------\n show-branch.c        |   13 +++++++++----\n symbolic-ref.c       |   34 ++++++++++++++++++++++++++++++++++\n t/t5400-send-pack.sh |    6 +++---\n 15 files changed, 139 insertions(+), 58 deletions(-)\n create mode 100644 symbolic-ref.c\n\n15de68abcb00ede12ac4b67c3f0ecac7cfffd0b4\ndiff --git a/Makefile b/Makefile\n--- a/Makefile\n+++ b/Makefile\n@@ -116,7 +116,7 @@ PROGRAMS = \\\n \tgit-ssh-upload git-tar-tree git-unpack-file \\\n \tgit-unpack-objects git-update-index git-update-server-info \\\n \tgit-upload-pack git-verify-pack git-write-tree \\\n-\tgit-update-ref \\\n+\tgit-update-ref git-symbolic-ref \\\n \t$(SIMPLE_PROGRAMS)\n \n # Backward compatibility -- to be removed in 0.99.8\ndiff --git a/cache.h b/cache.h\n--- a/cache.h\n+++ b/cache.h\n@@ -231,6 +231,7 @@ extern int get_sha1_hex(const char *hex,\n extern char *sha1_to_hex(const unsigned char *sha1);\t/* static buffer result! */\n extern int read_ref(const char *filename, unsigned char *sha1);\n extern const char *resolve_ref(const char *path, unsigned char *sha1);\n+extern int create_symref(const char *git_HEAD, const char *refs_heads_master);\n \n /* General helper functions */\n extern void usage(const char *err) NORETURN;\ndiff --git a/fsck-objects.c b/fsck-objects.c\n--- a/fsck-objects.c\n+++ b/fsck-objects.c\n@@ -402,25 +402,17 @@ static void fsck_object_dir(const char *\n \n static int fsck_head_link(void)\n {\n-\tint fd, count;\n-\tchar hex[40];\n \tunsigned char sha1[20];\n-\tstatic char path[PATH_MAX], link[PATH_MAX];\n-\tconst char *git_dir = get_git_dir();\n-\n-\tsnprintf(path, sizeof(path), \"%s/HEAD\", git_dir);\n-\tif (readlink(path, link, sizeof(link)) < 0)\n-\t\treturn error(\"HEAD is not a symlink\");\n-\tif (strncmp(\"refs/heads/\", link, 11))\n-\t\treturn error(\"HEAD points to something strange (%s)\", link);\n-\tfd = open(path, O_RDONLY);\n-\tif (fd < 0)\n-\t\treturn error(\"HEAD: %s\", strerror(errno));\n-\tcount = read(fd, hex, sizeof(hex));\n-\tclose(fd);\n-\tif (count < 0)\n-\t\treturn error(\"HEAD: %s\", strerror(errno));\n-\tif (count < 40 || get_sha1_hex(hex, sha1))\n+\tconst char *git_HEAD = strdup(git_path(\"HEAD\"));\n+\tconst char *git_refs_heads_master = resolve_ref(git_HEAD, sha1);\n+\tint pfxlen = strlen(git_HEAD) - 4; /* strip .../.git/ part */\n+\n+\tif (!git_refs_heads_master)\n+\t\treturn error(\"HEAD is not a symbolic ref\");\n+\tif (strncmp(git_refs_heads_master + pfxlen, \"refs/heads/\", 11))\n+\t\treturn error(\"HEAD points to something strange (%s)\",\n+\t\t\t     git_refs_heads_master + pfxlen);\n+\tif (!memcmp(null_sha1, sha1, 20))\n \t\treturn error(\"HEAD: not a valid git pointer\");\n \treturn 0;\n }\ndiff --git a/git-bisect.sh b/git-bisect.sh\n--- a/git-bisect.sh\n+++ b/git-bisect.sh\n@@ -38,7 +38,8 @@ bisect_start() {\n \t# Verify HEAD. If we were bisecting before this, reset to the\n \t# top-of-line master first!\n \t#\n-\thead=$(readlink $GIT_DIR/HEAD) || die \"Bad HEAD - I need a symlink\"\n+\thead=$(GIT_DIR=\"$GIT_DIR\" git-symbolic-ref HEAD) ||\n+\tdie \"Bad HEAD - I need a symbolic ref\"\n \tcase \"$head\" in\n \trefs/heads/bisect*)\n \t\tgit checkout master || exit\n@@ -46,7 +47,7 @@ bisect_start() {\n \trefs/heads/*)\n \t\t;;\n \t*)\n-\t\tdie \"Bad HEAD - strange symlink\"\n+\t\tdie \"Bad HEAD - strange symbolic ref\"\n \t\t;;\n \tesac\n \n@@ -135,7 +136,7 @@ bisect_next() {\n \techo \"$rev\" > \"$GIT_DIR/refs/heads/new-bisect\"\n \tgit checkout new-bisect || exit\n \tmv \"$GIT_DIR/refs/heads/new-bisect\" \"$GIT_DIR/refs/heads/bisect\" &&\n-\tln -sf refs/heads/bisect \"$GIT_DIR/HEAD\"\n+\tGIT_DIR=\"$GIT_DIR\" git-symbolic-ref HEAD refs/heads/bisect\n \tgit-show-branch \"$rev\"\n }\n \ndiff --git a/git-branch.sh b/git-branch.sh\n--- a/git-branch.sh\n+++ b/git-branch.sh\n@@ -14,7 +14,8 @@ If two arguments, create a new branch <b\n \n delete_branch () {\n     option=\"$1\" branch_name=\"$2\"\n-    headref=$(readlink \"$GIT_DIR/HEAD\" | sed -e 's|^refs/heads/||')\n+    headref=$(GIT_DIR=\"$GIT_DIR\" git-symbolic-ref HEAD |\n+    \t       sed -e 's|^refs/heads/||')\n     case \",$headref,\" in\n     \",$branch_name,\")\n \tdie \"Cannot delete the branch you are on.\" ;;\n@@ -67,7 +68,8 @@ done\n \n case \"$#\" in\n 0)\n-\theadref=$(readlink \"$GIT_DIR/HEAD\" | sed -e 's|^refs/heads/||')\n+\theadref=$(GIT_DIR=\"$GIT_DIR\" git-symbolic-ref HEAD |\n+\t\t  sed -e 's|^refs/heads/||')\n \tgit-rev-parse --symbolic --all |\n \tsed -ne 's|^refs/heads/||p' |\n \tsort |\ndiff --git a/git-checkout.sh b/git-checkout.sh\n--- a/git-checkout.sh\n+++ b/git-checkout.sh\n@@ -71,7 +71,8 @@ if [ \"$?\" -eq 0 ]; then\n \t\techo $new > \"$GIT_DIR/refs/heads/$newbranch\"\n \t\tbranch=\"$newbranch\"\n \tfi\n-\t[ \"$branch\" ] && ln -sf \"refs/heads/$branch\" \"$GIT_DIR/HEAD\"\n+\t[ \"$branch\" ] &&\n+\tGIT_DIR=\"$GIT_DIR\" git-symbolic-ref HEAD \"refs/heads/$branch\"\n \trm -f \"$GIT_DIR/MERGE_HEAD\"\n else\n \texit 1\ndiff --git a/git-commit.sh b/git-commit.sh\n--- a/git-commit.sh\n+++ b/git-commit.sh\n@@ -153,15 +153,8 @@ if [ -f \"$GIT_DIR/MERGE_HEAD\" ]; then\n fi >>.editmsg\n \n PARENTS=\"-p HEAD\"\n-if [ ! -r \"$GIT_DIR/HEAD\" ]; then\n-\tif [ -z \"$(git-ls-files)\" ]; then\n-\t\techo Nothing to commit 1>&2\n-\t\texit 1\n-\tfi\n-\tPARENTS=\"\"\n-\tcurrent=\n-else\n-\tcurrent=$(git-rev-parse --verify HEAD)\n+if GIT_DIR=\"$GIT_DIR\" git-rev-parse --verify HEAD >/dev/null 2>&1\n+then\n \tif [ -f \"$GIT_DIR/MERGE_HEAD\" ]; then\n \t\tPARENTS=\"-p HEAD \"`sed -e 's/^/-p /' \"$GIT_DIR/MERGE_HEAD\"`\n \tfi\n@@ -194,6 +187,12 @@ else\n \t\texport GIT_AUTHOR_EMAIL\n \t\texport GIT_AUTHOR_DATE\n \tfi\n+else\n+\tif [ -z \"$(git-ls-files)\" ]; then\n+\t\techo Nothing to commit 1>&2\n+\t\texit 1\n+\tfi\n+\tPARENTS=\"\"\n fi\n git-status >>.editmsg\n if [ \"$?\" != \"0\" -a ! -f $GIT_DIR/MERGE_HEAD ]\ndiff --git a/git-sh-setup.sh b/git-sh-setup.sh\n--- a/git-sh-setup.sh\n+++ b/git-sh-setup.sh\n@@ -13,10 +13,10 @@\n unset CDPATH\n \n die() {\n-\techo \"$@\" >&2\n+\techo >&2 \"$@\"\n \texit 1\n }\n \n-[ -h \"$GIT_DIR/HEAD\" ] &&\n+GIT_DIR=\"$GIT_DIR\" git-symbolic-ref HEAD >/dev/null 2>&1 &&\n [ -d \"$GIT_DIR/refs\" ] &&\n [ -d \"$GIT_OBJECT_DIRECTORY/00\" ]\ndiff --git a/git-status.sh b/git-status.sh\n--- a/git-status.sh\n+++ b/git-status.sh\n@@ -31,7 +31,7 @@ report () {\n   [ \"$header\" ]\n }\n \n-branch=`readlink \"$GIT_DIR/HEAD\"`\n+branch=$(GIT_DIR=\"$GIT_DIR\" git-symbolic-ref HEAD)\n case \"$branch\" in\n refs/heads/master) ;;\n *)\techo \"# On branch $branch\" ;;\n@@ -39,7 +39,7 @@ esac\n \n git-update-index --refresh >/dev/null 2>&1\n \n-if test -f \"$GIT_DIR/HEAD\"\n+if GIT_DIR=\"$GIT_DIR\" git-rev-parse --verify HEAD >/dev/null 2>&1\n then\n \tgit-diff-index -M --cached HEAD |\n \tsed 's/^://' |\ndiff --git a/init-db.c b/init-db.c\n--- a/init-db.c\n+++ b/init-db.c\n@@ -166,6 +166,7 @@ static void create_default_files(const c\n {\n \tunsigned len = strlen(git_dir);\n \tstatic char path[PATH_MAX];\n+\tunsigned char sha1[20];\n \n \tif (len > sizeof(path)-50)\n \t\tdie(\"insane git directory %s\", git_dir);\n@@ -186,15 +187,14 @@ static void create_default_files(const c\n \n \t/*\n \t * Create the default symlink from \".git/HEAD\" to the \"master\"\n-\t * branch\n+\t * branch, if it does not exist yet.\n \t */\n \tstrcpy(path + len, \"HEAD\");\n-\tif (symlink(\"refs/heads/master\", path) < 0) {\n-\t\tif (errno != EEXIST) {\n-\t\t\tperror(path);\n+\tif (read_ref(path, sha1) < 0) {\n+\t\tif (create_symref(path, \"refs/heads/master\") < 0)\n \t\t\texit(1);\n-\t\t}\n \t}\n+\tpath[len] = 0;\n \tcopy_templates(path, len, template_path);\n }\n \ndiff --git a/refs.c b/refs.c\n--- a/refs.c\n+++ b/refs.c\n@@ -9,6 +9,10 @@ const unsigned char null_sha1[20] = { 0,\n /* We allow \"recursive\" symbolic refs. Only within reason, though */\n #define MAXDEPTH 5\n \n+#ifndef USE_SYMLINK_HEAD\n+#define USE_SYMLINK_HEAD 1\n+#endif\n+\n const char *resolve_ref(const char *path, unsigned char *sha1)\n {\n \tint depth = MAXDEPTH, len;\n@@ -22,9 +26,14 @@ const char *resolve_ref(const char *path\n \t\tif (--depth < 0)\n \t\t\treturn NULL;\n \n-\t\t/* Special case: non-existing file */\n+\t\t/* Special case: non-existing file.\n+\t\t * But if this is the first round, i.e. the user\n+\t\t * gave us \".git/NO_SUCH_SYMLINK\" which does not even\n+\t\t * exist, we should barf.  \".git/HEAD\" symlink which\n+\t\t * points at \"refs/heads/master\" not yet born is OK.\n+\t\t */\n \t\tif (lstat(path, &st) < 0) {\n-\t\t\tif (errno != ENOENT)\n+\t\t\tif (errno != ENOENT || depth == MAXDEPTH - 1)\n \t\t\t\treturn NULL;\n \t\t\tmemset(sha1, 0, 20);\n \t\t\treturn path;\n@@ -67,6 +76,39 @@ const char *resolve_ref(const char *path\n \treturn path;\n }\n \n+int create_symref(const char *git_HEAD, const char *refs_heads_master)\n+{\n+#if USE_SYMLINK_HEAD\n+\tunlink(git_HEAD);\n+\treturn symlink(refs_heads_master, git_HEAD);\n+#else\n+\tconst char *lockpath;\n+\tchar ref[1000];\n+\tint fd, len, written;\n+\n+\tlen = snprintf(ref, sizeof(ref), \"ref: %s\\n\", refs_heads_master);\n+\tif (sizeof(ref) <= len) {\n+\t\terror(\"refname too long: %s\", refs_heads_master);\n+\t\treturn -1;\n+\t}\n+\tlockpath = mkpath(\"%s.lock\", git_HEAD);\n+\tfd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666);\t\n+\twritten = write(fd, ref, len);\n+\tclose(fd);\n+\tif (written != len) {\n+\t\tunlink(lockpath);\n+\t\terror(\"Unable to write to %s\", lockpath);\n+\t\treturn -2;\n+\t}\n+\tif (rename(lockpath, git_HEAD) < 0) {\n+\t\tunlink(lockpath);\n+\t\terror(\"Unable to create %s\", git_HEAD);\n+\t\treturn -3;\n+\t}\n+\treturn 0;\n+#endif\n+}\n+\n int read_ref(const char *filename, unsigned char *sha1)\n {\n \tif (resolve_ref(filename, sha1) && memcmp(sha1, null_sha1, 20))\ndiff --git a/setup.c b/setup.c\n--- a/setup.c\n+++ b/setup.c\n@@ -76,18 +76,22 @@ const char **get_pathspec(const char *pr\n  * Test it it looks like we're at the top\n  * level git directory. We want to see a\n  *\n- *  - a HEAD symlink and a refs/ directory under \".git\"\n  *  - either a .git/objects/ directory _or_ the proper\n  *    GIT_OBJECT_DIRECTORY environment variable\n+ *  - a refs/ directory under \".git\"\n+ *  - either a HEAD symlink or a HEAD file that is formatted as\n+ *    a proper \"ref:\".\n  */\n static int is_toplevel_directory(void)\n {\n-\tstruct stat st;\n+\tunsigned char sha1[20];\n \n-\treturn\t!lstat(\".git/HEAD\", &st) &&\n-\t\tS_ISLNK(st.st_mode) &&\n-\t\t!access(\".git/refs/\", X_OK) &&\n-\t\t(getenv(DB_ENVIRONMENT) || !access(\".git/objects/\", X_OK));\n+\tif (access(\".git/refs/\", X_OK) ||\n+\t    access(getenv(DB_ENVIRONMENT) ?\n+\t\t   getenv(DB_ENVIRONMENT) : \".git/objects/\", X_OK) ||\n+\t    (resolve_ref(\".git/HEAD\", sha1) == NULL))\n+\t\treturn 0;\n+\treturn 1;\n }\n \n const char *setup_git_directory(void)\ndiff --git a/show-branch.c b/show-branch.c\n--- a/show-branch.c\n+++ b/show-branch.c\n@@ -349,6 +349,7 @@ int main(int ac, char **av)\n \tint all_heads = 0, all_tags = 0;\n \tint all_mask, all_revs, shown_merge_point;\n \tchar head_path[128];\n+\tconst char *head_path_p;\n \tint head_path_len;\n \tunsigned char head_sha1[20];\n \tint merge_base = 0;\n@@ -430,11 +431,15 @@ int main(int ac, char **av)\n \tif (0 <= extra)\n \t\tjoin_revs(&list, &seen, num_rev, extra);\n \n-\thead_path_len = readlink(\".git/HEAD\", head_path, sizeof(head_path)-1);\n-\tif ((head_path_len < 0) || get_sha1(\"HEAD\", head_sha1))\n+\thead_path_p = resolve_ref(git_path(\"HEAD\"), head_sha1);\n+\tif (head_path_p) {\n+\t\thead_path_len = strlen(head_path_p);\n+\t\tmemcpy(head_path, head_path_p, head_path_len + 1);\n+\t}\n+\telse {\n+\t\thead_path_len = 0;\n \t\thead_path[0] = 0;\n-\telse\n-\t\thead_path[head_path_len] = 0;\n+\t}\n \n \tif (merge_base)\n \t\treturn show_merge_base(seen, num_rev);\ndiff --git a/symbolic-ref.c b/symbolic-ref.c\nnew file mode 100644\n--- /dev/null\n+++ b/symbolic-ref.c\n@@ -0,0 +1,34 @@\n+#include \"cache.h\"\n+\n+static const char git_symbolic_ref_usage[] =\n+\"git-symbolic-ref name [ref]\";\n+\n+static void check_symref(const char *HEAD)\n+{\n+\tunsigned char sha1[20];\n+\tconst char *git_HEAD = strdup(git_path(\"%s\", HEAD));\n+\tconst char *git_refs_heads_master = resolve_ref(git_HEAD, sha1);\n+\tif (git_refs_heads_master) {\n+\t\t/* we want to strip the .git/ part */\n+\t\tint pfxlen = strlen(git_HEAD) - strlen(HEAD);\n+\t\tputs(git_refs_heads_master + pfxlen);\n+\t}\n+\telse\n+\t\tdie(\"No such ref: %s\", HEAD);\n+}\n+\n+int main(int argc, const char **argv)\n+{\n+\tsetup_git_directory();\n+\tswitch (argc) {\n+\tcase 2:\n+\t\tcheck_symref(argv[1]);\n+\t\tbreak;\n+\tcase 3:\n+\t\tcreate_symref(strdup(git_path(\"%s\", argv[1])), argv[2]);\n+\t\tbreak;\n+\tdefault:\n+\t\tusage(git_symbolic_ref_usage);\n+\t}\n+\treturn 0;\n+}\ndiff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh\n--- a/t/t5400-send-pack.sh\n+++ b/t/t5400-send-pack.sh\n@@ -20,12 +20,12 @@ test_expect_success setup '\n \t    commit=$(echo \"Commit #$i\" | git-commit-tree $tree -p $parent) &&\n \t    parent=$commit || return 1\n \tdone &&\n-\techo \"$commit\" >.git/HEAD &&\n+\tgit-update-ref HEAD \"$commit\" &&\n \tgit-clone -l ./. victim &&\n \tcd victim &&\n \tgit-log &&\n \tcd .. &&\n-\techo $zero >.git/HEAD &&\n+\tgit-update-ref HEAD \"$zero\" &&\n \tparent=$zero &&\n \tfor i in $cnt\n \tdo\n@@ -33,7 +33,7 @@ test_expect_success setup '\n \t    commit=$(echo \"Rebase #$i\" | git-commit-tree $tree -p $parent) &&\n \t    parent=$commit || return 1\n \tdone &&\n-\techo \"$commit\" >.git/HEAD &&\n+\tgit-update-ref HEAD \"$commit\" &&\n \techo Rebase &&\n \tgit-log'\n \n"}]}