http-push: support for updating remote info/refs
[gitweb.git] / refs.c
diff --git a/refs.c b/refs.c
index 5a8cbd4ef386da60d3f7a2cac4563fc993e38b94..03398ccc531c15e549b66c43a176fd7b2bde4aca 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -2,55 +2,10 @@
 #include "cache.h"
 
 #include <errno.h>
-#include <ctype.h>
 
 /* 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/", buf, 5))
-               return 0;
-       return -1;
-}
-
 const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
 {
        int depth = MAXDEPTH, len;
@@ -117,14 +72,19 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
 
 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;
 
+#ifdef USE_SYMLINK_HEAD
+       if (!only_use_symrefs) {
+               unlink(git_HEAD);
+               if (!symlink(refs_heads_master, git_HEAD))
+                       return 0;
+               fprintf(stderr, "no symlink - falling back to symbolic ref\n");
+       }
+#endif
+
        len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master);
        if (sizeof(ref) <= len) {
                error("refname too long: %s", refs_heads_master);
@@ -145,7 +105,6 @@ int create_symref(const char *git_HEAD, const char *refs_heads_master)
                return -3;
        }
        return 0;
-#endif
 }
 
 int read_ref(const char *filename, unsigned char *sha1)
@@ -192,10 +151,15 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u
                                        break;
                                continue;
                        }
-                       if (read_ref(git_path("%s", path), sha1) < 0)
+                       if (read_ref(git_path("%s", path), sha1) < 0) {
+                               error("%s points nowhere!", path);
                                continue;
-                       if (!has_sha1_file(sha1))
+                       }
+                       if (!has_sha1_file(sha1)) {
+                               error("%s does not point to a valid "
+                                     "commit object!", path);
                                continue;
+                       }
                        retval = fn(path, sha1);
                        if (retval)
                                break;
@@ -309,7 +273,7 @@ static int write_ref_file(const char *filename,
        char term = '\n';
        if (write(fd, hex, 40) < 40 ||
            write(fd, &term, 1) < 1) {
-               error("Couldn't write %s\n", filename);
+               error("Couldn't write %s", filename);
                close(fd);
                return -1;
        }
@@ -329,23 +293,64 @@ int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1)
                return -1;
        filename = ref_file_name(ref);
        lock_filename = ref_lock_file_name(ref);
+       if (safe_create_leading_directories(filename))
+               die("unable to create leading directory for %s", filename);
        retval = write_ref_file(filename, lock_filename, fd, sha1);
        free(filename);
        free(lock_filename);
        return retval;
 }
 
+/*
+ * Make sure "ref" is something reasonable to have under ".git/refs/";
+ * We do not like it if:
+ *
+ * - any path component of it begins with ".", or
+ * - it has double dots "..", or
+ * - it has ASCII control character, "~", "^", ":" or SP, anywhere, or
+ * - it ends with a "/".
+ */
+
+static inline int bad_ref_char(int ch)
+{
+       return (((unsigned) ch) <= ' ' ||
+               ch == '~' || ch == '^' || ch == ':' ||
+               /* 2.13 Pattern Matching Notation */
+               ch == '?' || ch == '*' || ch == '[');
+}
+
 int check_ref_format(const char *ref)
 {
-       char *middle;
-       if (ref[0] == '.' || ref[0] == '/')
-               return -1;
-       middle = strchr(ref, '/');
-       if (!middle || !middle[1])
-               return -1;
-       if (strchr(middle + 1, '/'))
-               return -1;
-       return 0;
+       int ch, level;
+       const char *cp = ref;
+
+       level = 0;
+       while (1) {
+               while ((ch = *cp++) == '/')
+                       ; /* tolerate duplicated slashes */
+               if (!ch)
+                       return -1; /* should not end with slashes */
+
+               /* we are at the beginning of the path component */
+               if (ch == '.' || bad_ref_char(ch))
+                       return -1;
+
+               /* scan the rest of the path component */
+               while ((ch = *cp++) != 0) {
+                       if (bad_ref_char(ch))
+                               return -1;
+                       if (ch == '/')
+                               break;
+                       if (ch == '.' && *cp == '.')
+                               return -1;
+               }
+               level++;
+               if (!ch) {
+                       if (level < 2)
+                               return -1; /* at least of form "heads/blah" */
+                       return 0;
+               }
+       }
 }
 
 int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1)
@@ -358,6 +363,8 @@ int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1)
                return -1;
        filename = ref_file_name(ref);
        lock_filename = ref_lock_file_name(ref);
+       if (safe_create_leading_directories(filename))
+               die("unable to create leading directory for %s", filename);
        fd = open(lock_filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
        if (fd < 0) {
                error("Writing %s", lock_filename);