create_symref: if symlink fails, fall back to writing a "symbolic ref"
[gitweb.git] / refs.c
diff --git a/refs.c b/refs.c
index 42240d27690b7a32cb06cd0ea96ce8fb13226459..a52b038eefdf22fce20d1a81180c97def0613cdb 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -116,14 +116,17 @@ 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;
 
+#if USE_SYMLINK_HEAD
+       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);
@@ -144,7 +147,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)
@@ -334,17 +336,54 @@ int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1)
        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 == ':');
+}
+
 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)