Add git-symbolic-ref
authorJunio C Hamano <junio@twinsun.com>
Fri, 30 Sep 2005 21:26:57 +0000 (14:26 -0700)
committerJunio C Hamano <junkio@cox.net>
Sun, 2 Oct 2005 06:19:33 +0000 (23:19 -0700)
This adds the counterpart of git-update-ref that lets you read
and create "symbolic refs".  By default it uses a symbolic link
to represent ".git/HEAD -> refs/heads/master", but it can be compiled
to use the textfile symbolic ref.

The places that did 'readlink .git/HEAD' and 'ln -s refs/heads/blah
.git/HEAD' have been converted to use new git-symbolic-ref command, so
that they can deal with either implementation.

Signed-off-by: Junio C Hamano <junio@twinsun.com>
16 files changed:
.gitignore
Makefile
cache.h
fsck-objects.c
git-bisect.sh
git-branch.sh
git-checkout.sh
git-commit.sh
git-sh-setup.sh
git-status.sh
init-db.c
refs.c
setup.c
show-branch.c
symbolic-ref.c [new file with mode: 0644]
t/t5400-send-pack.sh

index c3eb9543ff2b8a2fcd0c9e48cbd9f9faf83f711d..e90e2c350371b4fdf865f2d63935b3681f09648f 100644 (file)
@@ -82,6 +82,7 @@ git-ssh-push
 git-ssh-upload
 git-status
 git-stripspace
+git-symbolic-ref
 git-tag
 git-tar-tree
 git-unpack-file
index e943954cf83da8732cfd2c1508929e5a21a76664..5648296a8dd7a4b9fe5014e9556328d9b1bdbfac 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -116,7 +116,7 @@ PROGRAMS = \
        git-ssh-upload git-tar-tree git-unpack-file \
        git-unpack-objects git-update-index git-update-server-info \
        git-upload-pack git-verify-pack git-write-tree \
-       git-update-ref \
+       git-update-ref git-symbolic-ref \
        $(SIMPLE_PROGRAMS)
 
 # Backward compatibility -- to be removed after 1.0
diff --git a/cache.h b/cache.h
index 63823c3529b06e2a913fc962282cc64b51c7340b..ec2a1610b2fd6edec6c95847d4377f9c0241b738 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -231,6 +231,8 @@ extern int get_sha1_hex(const char *hex, unsigned char *sha1);
 extern char *sha1_to_hex(const unsigned char *sha1);   /* static buffer result! */
 extern int read_ref(const char *filename, unsigned char *sha1);
 extern const char *resolve_ref(const char *path, unsigned char *sha1, int);
+extern int create_symref(const char *git_HEAD, const char *refs_heads_master);
+extern int validate_symref(const char *git_HEAD);
 
 /* General helper functions */
 extern void usage(const char *err) NORETURN;
index 247edf052900e22c958595f34b7bce886c369377..65cec7d12b6fecc95e842069d2afa1e87bceebc1 100644 (file)
@@ -402,25 +402,17 @@ static void fsck_object_dir(const char *path)
 
 static int fsck_head_link(void)
 {
-       int fd, count;
-       char hex[40];
        unsigned char sha1[20];
-       static char path[PATH_MAX], link[PATH_MAX];
-       const char *git_dir = get_git_dir();
-
-       snprintf(path, sizeof(path), "%s/HEAD", git_dir);
-       if (readlink(path, link, sizeof(link)) < 0)
-               return error("HEAD is not a symlink");
-       if (strncmp("refs/heads/", link, 11))
-               return error("HEAD points to something strange (%s)", link);
-       fd = open(path, O_RDONLY);
-       if (fd < 0)
-               return error("HEAD: %s", strerror(errno));
-       count = read(fd, hex, sizeof(hex));
-       close(fd);
-       if (count < 0)
-               return error("HEAD: %s", strerror(errno));
-       if (count < 40 || get_sha1_hex(hex, sha1))
+       const char *git_HEAD = strdup(git_path("HEAD"));
+       const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 1);
+       int pfxlen = strlen(git_HEAD) - 4; /* strip .../.git/ part */
+
+       if (!git_refs_heads_master)
+               return error("HEAD is not a symbolic ref");
+       if (strncmp(git_refs_heads_master + pfxlen, "refs/heads/", 11))
+               return error("HEAD points to something strange (%s)",
+                            git_refs_heads_master + pfxlen);
+       if (!memcmp(null_sha1, sha1, 20))
                return error("HEAD: not a valid git pointer");
        return 0;
 }
index 8dc77c991c0e8c5a618383052306f9ef1a018352..1ab2f187dcfd1ab9b81bbc3ebb3605601b4d29f0 100755 (executable)
@@ -38,7 +38,8 @@ bisect_start() {
        # Verify HEAD. If we were bisecting before this, reset to the
        # top-of-line master first!
        #
-       head=$(readlink $GIT_DIR/HEAD) || die "Bad HEAD - I need a symlink"
+       head=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD) ||
+       die "Bad HEAD - I need a symbolic ref"
        case "$head" in
        refs/heads/bisect*)
                git checkout master || exit
@@ -46,7 +47,7 @@ bisect_start() {
        refs/heads/*)
                ;;
        *)
-               die "Bad HEAD - strange symlink"
+               die "Bad HEAD - strange symbolic ref"
                ;;
        esac
 
@@ -135,7 +136,7 @@ bisect_next() {
        echo "$rev" > "$GIT_DIR/refs/heads/new-bisect"
        git checkout new-bisect || exit
        mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" &&
-       ln -sf refs/heads/bisect "$GIT_DIR/HEAD"
+       GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD refs/heads/bisect
        git-show-branch "$rev"
 }
 
index dcec2a9f2f0e379cff152cb70ddf6c2c942d1c99..074229c206d01b8ca097b4ced902e9bf40987a06 100755 (executable)
@@ -14,7 +14,8 @@ If two arguments, create a new branch <branchname> based off of <start-point>.
 
 delete_branch () {
     option="$1" branch_name="$2"
-    headref=$(readlink "$GIT_DIR/HEAD" | sed -e 's|^refs/heads/||')
+    headref=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD |
+              sed -e 's|^refs/heads/||')
     case ",$headref," in
     ",$branch_name,")
        die "Cannot delete the branch you are on." ;;
@@ -67,7 +68,8 @@ done
 
 case "$#" in
 0)
-       headref=$(readlink "$GIT_DIR/HEAD" | sed -e 's|^refs/heads/||')
+       headref=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD |
+                 sed -e 's|^refs/heads/||')
        git-rev-parse --symbolic --all |
        sed -ne 's|^refs/heads/||p' |
        sort |
index 37afcdda309ca8df52bb5033e5e7f0b771dfa067..c3825904b60d36edca96506d7f451cf0349b262d 100755 (executable)
@@ -71,7 +71,8 @@ if [ "$?" -eq 0 ]; then
                echo $new > "$GIT_DIR/refs/heads/$newbranch"
                branch="$newbranch"
        fi
-       [ "$branch" ] && ln -sf "refs/heads/$branch" "$GIT_DIR/HEAD"
+       [ "$branch" ] &&
+       GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD "refs/heads/$branch"
        rm -f "$GIT_DIR/MERGE_HEAD"
 else
        exit 1
index 18b259c7086224f70cfd69798dc6332df040a61d..1206c20c2eca1edfb9318891d8237685d237c945 100755 (executable)
@@ -153,15 +153,8 @@ if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
 fi >>.editmsg
 
 PARENTS="-p HEAD"
-if [ ! -r "$GIT_DIR/HEAD" ]; then
-       if [ -z "$(git-ls-files)" ]; then
-               echo Nothing to commit 1>&2
-               exit 1
-       fi
-       PARENTS=""
-       current=
-else
-       current=$(git-rev-parse --verify HEAD)
+if GIT_DIR="$GIT_DIR" git-rev-parse --verify HEAD >/dev/null 2>&1
+then
        if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
                PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"`
        fi
@@ -194,6 +187,12 @@ else
                export GIT_AUTHOR_EMAIL
                export GIT_AUTHOR_DATE
        fi
+else
+       if [ -z "$(git-ls-files)" ]; then
+               echo Nothing to commit 1>&2
+               exit 1
+       fi
+       PARENTS=""
 fi
 git-status >>.editmsg
 if [ "$?" != "0" -a ! -f $GIT_DIR/MERGE_HEAD ]
index 55db795843514571450acf7da600b6aba10c15d4..a0172686a90c58a454656f9e6d49d6c694aec2fc 100755 (executable)
 unset CDPATH
 
 die() {
-       echo "$@" >&2
+       echo >&2 "$@"
        exit 1
 }
 
-[ -h "$GIT_DIR/HEAD" ] &&
+case "$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD 2>/dev/null)" in
+refs/*)        : ;;
+*)     false ;;
+esac &&
 [ -d "$GIT_DIR/refs" ] &&
 [ -d "$GIT_OBJECT_DIRECTORY/00" ]
index 621fa49d2bcad6c5343ac5c172fb9ca6a855c18d..ca9a15459fc55c8a03aabf2bed85bc34817df450 100755 (executable)
@@ -31,7 +31,7 @@ report () {
   [ "$header" ]
 }
 
-branch=`readlink "$GIT_DIR/HEAD"`
+branch=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD)
 case "$branch" in
 refs/heads/master) ;;
 *)     echo "# On branch $branch" ;;
@@ -39,7 +39,7 @@ esac
 
 git-update-index --refresh >/dev/null 2>&1
 
-if test -f "$GIT_DIR/HEAD"
+if GIT_DIR="$GIT_DIR" git-rev-parse --verify HEAD >/dev/null 2>&1
 then
        git-diff-index -M --cached HEAD |
        sed 's/^://' |
index da2bc8f42b2ea6804c79097778f3105afcd87da6..aabc09f4e1f714f5b4c1adba388fc47b41e16b0b 100644 (file)
--- a/init-db.c
+++ b/init-db.c
@@ -166,6 +166,7 @@ static void create_default_files(const char *git_dir,
 {
        unsigned len = strlen(git_dir);
        static char path[PATH_MAX];
+       unsigned char sha1[20];
 
        if (len > sizeof(path)-50)
                die("insane git directory %s", git_dir);
@@ -186,15 +187,14 @@ static void create_default_files(const char *git_dir,
 
        /*
         * Create the default symlink from ".git/HEAD" to the "master"
-        * branch
+        * branch, if it does not exist yet.
         */
        strcpy(path + len, "HEAD");
-       if (symlink("refs/heads/master", path) < 0) {
-               if (errno != EEXIST) {
-                       perror(path);
+       if (read_ref(path, sha1) < 0) {
+               if (create_symref(path, "refs/heads/master") < 0)
                        exit(1);
-               }
        }
+       path[len] = 0;
        copy_templates(path, len, template_path);
 }
 
diff --git a/refs.c b/refs.c
index 6aa6aec82dbd5510d43dea07362399f57669eeef..2aac90ca54f3fe207d7b05dda56f0f04badf460e 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -7,6 +7,50 @@
 /* We allow "recursive" symbolic refs. Only within reason, though */
 #define MAXDEPTH 5
 
+#ifndef USE_SYMLINK_HEAD
+#define USE_SYMLINK_HEAD 1
+#endif
+
+int validate_symref(const char *path)
+{
+       struct stat st;
+       char *buf, buffer[256];
+       int len, fd;
+
+       if (lstat(path, &st) < 0)
+               return -1;
+
+       /* Make sure it is a "refs/.." symlink */
+       if (S_ISLNK(st.st_mode)) {
+               len = readlink(path, buffer, sizeof(buffer)-1);
+               if (len >= 5 && !memcmp("refs/", buffer, 5))
+                       return 0;
+               return -1;
+       }
+
+       /*
+        * Anything else, just open it and try to see if it is a symbolic ref.
+        */
+       fd = open(path, O_RDONLY);
+       if (fd < 0)
+               return -1;
+       len = read(fd, buffer, sizeof(buffer)-1);
+       close(fd);
+
+       /*
+        * Is it a symbolic ref?
+        */
+       if (len < 4 || memcmp("ref:", buffer, 4))
+               return -1;
+       buf = buffer + 4;
+       len -= 4;
+       while (len && isspace(*buf))
+               buf++, len--;
+       if (len >= 5 && !memcmp("refs/", buffer, 5))
+               return 0;
+       return -1;
+}
+
 const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
 {
        int depth = MAXDEPTH, len;
@@ -71,6 +115,39 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
        return path;
 }
 
+int create_symref(const char *git_HEAD, const char *refs_heads_master)
+{
+#if USE_SYMLINK_HEAD
+       unlink(git_HEAD);
+       return symlink(refs_heads_master, git_HEAD);
+#else
+       const char *lockpath;
+       char ref[1000];
+       int fd, len, written;
+
+       len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master);
+       if (sizeof(ref) <= len) {
+               error("refname too long: %s", refs_heads_master);
+               return -1;
+       }
+       lockpath = mkpath("%s.lock", git_HEAD);
+       fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); 
+       written = write(fd, ref, len);
+       close(fd);
+       if (written != len) {
+               unlink(lockpath);
+               error("Unable to write to %s", lockpath);
+               return -2;
+       }
+       if (rename(lockpath, git_HEAD) < 0) {
+               unlink(lockpath);
+               error("Unable to create %s", git_HEAD);
+               return -3;
+       }
+       return 0;
+#endif
+}
+
 int read_ref(const char *filename, unsigned char *sha1)
 {
        if (resolve_ref(filename, sha1, 1))
diff --git a/setup.c b/setup.c
index 9e20160d941bd610ca8f752627a4481a728d9a8a..c487d7eb9de70c651ef06a6bfd4a7571337fad84 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -76,18 +76,20 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
  * Test it it looks like we're at the top
  * level git directory. We want to see a
  *
- *  - a HEAD symlink and a refs/ directory under ".git"
  *  - either a .git/objects/ directory _or_ the proper
  *    GIT_OBJECT_DIRECTORY environment variable
+ *  - a refs/ directory under ".git"
+ *  - either a HEAD symlink or a HEAD file that is formatted as
+ *    a proper "ref:".
  */
 static int is_toplevel_directory(void)
 {
-       struct stat st;
-
-       return  !lstat(".git/HEAD", &st) &&
-               S_ISLNK(st.st_mode) &&
-               !access(".git/refs/", X_OK) &&
-               (getenv(DB_ENVIRONMENT) || !access(".git/objects/", X_OK));
+       if (access(".git/refs/", X_OK) ||
+           access(getenv(DB_ENVIRONMENT) ?
+                  getenv(DB_ENVIRONMENT) : ".git/objects/", X_OK) ||
+           validate_symref(".git/HEAD"))
+               return 0;
+       return 1;
 }
 
 const char *setup_git_directory(void)
index 5778a594f44bd5fe06cd4b69eb37b3007a551385..8429c171cf6e6ddbac2f1a95da4a05f7ce8d34a3 100644 (file)
@@ -349,6 +349,7 @@ int main(int ac, char **av)
        int all_heads = 0, all_tags = 0;
        int all_mask, all_revs, shown_merge_point;
        char head_path[128];
+       const char *head_path_p;
        int head_path_len;
        unsigned char head_sha1[20];
        int merge_base = 0;
@@ -430,11 +431,15 @@ int main(int ac, char **av)
        if (0 <= extra)
                join_revs(&list, &seen, num_rev, extra);
 
-       head_path_len = readlink(".git/HEAD", head_path, sizeof(head_path)-1);
-       if ((head_path_len < 0) || get_sha1("HEAD", head_sha1))
+       head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1);
+       if (head_path_p) {
+               head_path_len = strlen(head_path_p);
+               memcpy(head_path, head_path_p, head_path_len + 1);
+       }
+       else {
+               head_path_len = 0;
                head_path[0] = 0;
-       else
-               head_path[head_path_len] = 0;
+       }
 
        if (merge_base)
                return show_merge_base(seen, num_rev);
diff --git a/symbolic-ref.c b/symbolic-ref.c
new file mode 100644 (file)
index 0000000..af087d2
--- /dev/null
@@ -0,0 +1,34 @@
+#include "cache.h"
+
+static const char git_symbolic_ref_usage[] =
+"git-symbolic-ref name [ref]";
+
+static int check_symref(const char *HEAD)
+{
+       unsigned char sha1[20];
+       const char *git_HEAD = strdup(git_path("%s", HEAD));
+       const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 0);
+       if (git_refs_heads_master) {
+               /* we want to strip the .git/ part */
+               int pfxlen = strlen(git_HEAD) - strlen(HEAD);
+               puts(git_refs_heads_master + pfxlen);
+       }
+       else
+               die("No such ref: %s", HEAD);
+}
+
+int main(int argc, const char **argv)
+{
+       setup_git_directory();
+       switch (argc) {
+       case 2:
+               check_symref(argv[1]);
+               break;
+       case 3:
+               create_symref(strdup(git_path("%s", argv[1])), argv[2]);
+               break;
+       default:
+               usage(git_symbolic_ref_usage);
+       }
+       return 0;
+}
index 59ce77b6b42eceaf953e955b79723753deb10033..1a4d2f2f13d4875d92e94cfa686ac42d3556ee24 100755 (executable)
@@ -20,12 +20,12 @@ test_expect_success setup '
            commit=$(echo "Commit #$i" | git-commit-tree $tree -p $parent) &&
            parent=$commit || return 1
        done &&
-       echo "$commit" >.git/HEAD &&
+       git-update-ref HEAD "$commit" &&
        git-clone -l ./. victim &&
        cd victim &&
        git-log &&
        cd .. &&
-       echo $zero >.git/HEAD &&
+       git-update-ref HEAD "$zero" &&
        parent=$zero &&
        for i in $cnt
        do
@@ -33,7 +33,7 @@ test_expect_success setup '
            commit=$(echo "Rebase #$i" | git-commit-tree $tree -p $parent) &&
            parent=$commit || return 1
        done &&
-       echo "$commit" >.git/HEAD &&
+       git-update-ref HEAD "$commit" &&
        echo Rebase &&
        git-log'