git-send-email: Do not make @-less message ID
[gitweb.git] / refs.c
diff --git a/refs.c b/refs.c
index f9b88020038487eb5dbc76bbfefd7fba15fc87fd..67ac97c713d071790f2d627f4c0435af23430fb8 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -150,7 +150,7 @@ static struct ref_list *sort_ref_list(struct ref_list *list)
  * Future: need to be in "struct repository"
  * when doing a full libification.
  */
-struct cached_refs {
+static struct cached_refs {
        char did_loose;
        char did_packed;
        struct ref_list *loose;
@@ -283,6 +283,86 @@ static struct ref_list *get_loose_refs(void)
 
 /* We allow "recursive" symbolic refs. Only within reason, though */
 #define MAXDEPTH 5
+#define MAXREFLEN (1024)
+
+static int resolve_gitlink_packed_ref(char *name, int pathlen, const char *refname, unsigned char *result)
+{
+       FILE *f;
+       struct cached_refs refs;
+       struct ref_list *ref;
+       int retval;
+
+       strcpy(name + pathlen, "packed-refs");
+       f = fopen(name, "r");
+       if (!f)
+               return -1;
+       read_packed_refs(f, &refs);
+       fclose(f);
+       ref = refs.packed;
+       retval = -1;
+       while (ref) {
+               if (!strcmp(ref->name, refname)) {
+                       retval = 0;
+                       memcpy(result, ref->sha1, 20);
+                       break;
+               }
+               ref = ref->next;
+       }
+       free_ref_list(refs.packed);
+       return retval;
+}
+
+static int resolve_gitlink_ref_recursive(char *name, int pathlen, const char *refname, unsigned char *result, int recursion)
+{
+       int fd, len = strlen(refname);
+       char buffer[128], *p;
+
+       if (recursion > MAXDEPTH || len > MAXREFLEN)
+               return -1;
+       memcpy(name + pathlen, refname, len+1);
+       fd = open(name, O_RDONLY);
+       if (fd < 0)
+               return resolve_gitlink_packed_ref(name, pathlen, refname, result);
+
+       len = read(fd, buffer, sizeof(buffer)-1);
+       close(fd);
+       if (len < 0)
+               return -1;
+       while (len && isspace(buffer[len-1]))
+               len--;
+       buffer[len] = 0;
+
+       /* Was it a detached head or an old-fashioned symlink? */
+       if (!get_sha1_hex(buffer, result))
+               return 0;
+
+       /* Symref? */
+       if (strncmp(buffer, "ref:", 4))
+               return -1;
+       p = buffer + 4;
+       while (isspace(*p))
+               p++;
+
+       return resolve_gitlink_ref_recursive(name, pathlen, p, result, recursion+1);
+}
+
+int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *result)
+{
+       int len = strlen(path), retval;
+       char *gitdir;
+
+       while (len && path[len-1] == '/')
+               len--;
+       if (!len)
+               return -1;
+       gitdir = xmalloc(len + MAXREFLEN + 8);
+       memcpy(gitdir, path, len);
+       memcpy(gitdir + len, "/.git/", 7);
+
+       retval = resolve_gitlink_ref_recursive(gitdir, len+6, refname, result, 0);
+       free(gitdir);
+       return retval;
+}
 
 const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag)
 {
@@ -523,15 +603,20 @@ int get_ref_sha1(const char *ref, unsigned char *sha1)
 
 static inline int bad_ref_char(int ch)
 {
-       return (((unsigned) ch) <= ' ' ||
-               ch == '~' || ch == '^' || ch == ':' ||
-               /* 2.13 Pattern Matching Notation */
-               ch == '?' || ch == '*' || ch == '[');
+       if (((unsigned) ch) <= ' ' ||
+           ch == '~' || ch == '^' || ch == ':')
+               return 1;
+       /* 2.13 Pattern Matching Notation */
+       if (ch == '?' || ch == '[') /* Unsupported */
+               return 1;
+       if (ch == '*') /* Supported at the end */
+               return 2;
+       return 0;
 }
 
 int check_ref_format(const char *ref)
 {
-       int ch, level;
+       int ch, level, bad_type;
        const char *cp = ref;
 
        level = 0;
@@ -542,13 +627,19 @@ int check_ref_format(const char *ref)
                        return -1; /* should not end with slashes */
 
                /* we are at the beginning of the path component */
-               if (ch == '.' || bad_ref_char(ch))
+               if (ch == '.')
                        return -1;
+               bad_type = bad_ref_char(ch);
+               if (bad_type) {
+                       return (bad_type == 2 && !*cp) ? -3 : -1;
+               }
 
                /* scan the rest of the path component */
                while ((ch = *cp++) != 0) {
-                       if (bad_ref_char(ch))
-                               return -1;
+                       bad_type = bad_ref_char(ch);
+                       if (bad_type) {
+                               return (bad_type == 2 && !*cp) ? -3 : -1;
+                       }
                        if (ch == '/')
                                break;
                        if (ch == '.' && *cp == '.')
@@ -656,19 +747,20 @@ static int is_refname_available(const char *ref, const char *oldref,
        return 1;
 }
 
-static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int *flag)
+static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int flags, int *type_p)
 {
        char *ref_file;
        const char *orig_ref = ref;
        struct ref_lock *lock;
        struct stat st;
        int last_errno = 0;
+       int type;
        int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
 
        lock = xcalloc(1, sizeof(struct ref_lock));
        lock->lock_fd = -1;
 
-       ref = resolve_ref(ref, lock->old_sha1, mustexist, flag);
+       ref = resolve_ref(ref, lock->old_sha1, mustexist, &type);
        if (!ref && errno == EISDIR) {
                /* we are trying to lock foo but we used to
                 * have foo/bar which now does not exist;
@@ -681,8 +773,10 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
                        error("there are still refs under '%s'", orig_ref);
                        goto error_return;
                }
-               ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, flag);
+               ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, &type);
        }
+       if (type_p)
+           *type_p = type;
        if (!ref) {
                last_errno = errno;
                error("unable to resolve reference %s: %s",
@@ -700,10 +794,15 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
 
        lock->lk = xcalloc(1, sizeof(struct lock_file));
 
+       if (flags & REF_NODEREF)
+               ref = orig_ref;
        lock->ref_name = xstrdup(ref);
        lock->orig_ref_name = xstrdup(orig_ref);
        ref_file = git_path("%s", ref);
-       lock->force_write = lstat(ref_file, &st) && errno == ENOENT;
+       if (lstat(ref_file, &st) && errno == ENOENT)
+               lock->force_write = 1;
+       if ((flags & REF_NODEREF) && (type & REF_ISSYMREF))
+               lock->force_write = 1;
 
        if (safe_create_leading_directories(ref_file)) {
                last_errno = errno;
@@ -726,14 +825,14 @@ struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
        if (check_ref_format(ref))
                return NULL;
        strcpy(refpath, mkpath("refs/%s", ref));
-       return lock_ref_sha1_basic(refpath, old_sha1, NULL);
+       return lock_ref_sha1_basic(refpath, old_sha1, 0, NULL);
 }
 
-struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1)
+struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int flags)
 {
        if (check_ref_format(ref) == -1)
                return NULL;
-       return lock_ref_sha1_basic(ref, old_sha1, NULL);
+       return lock_ref_sha1_basic(ref, old_sha1, flags, NULL);
 }
 
 static struct lock_file packlock;
@@ -778,7 +877,7 @@ int delete_ref(const char *refname, const unsigned char *sha1)
        struct ref_lock *lock;
        int err, i, ret = 0, flag = 0;
 
-       lock = lock_ref_sha1_basic(refname, sha1, &flag);
+       lock = lock_ref_sha1_basic(refname, sha1, 0, &flag);
        if (!lock)
                return 1;
        if (!(flag & REF_ISPACKED)) {
@@ -829,7 +928,7 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
        if (!is_refname_available(newref, oldref, get_loose_refs(), 0))
                return 1;
 
-       lock = lock_ref_sha1_basic(renamed_ref, NULL, NULL);
+       lock = lock_ref_sha1_basic(renamed_ref, NULL, 0, NULL);
        if (!lock)
                return error("unable to lock %s", renamed_ref);
        lock->force_write = 1;
@@ -883,7 +982,7 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
        }
        logmoved = log;
 
-       lock = lock_ref_sha1_basic(newref, NULL, NULL);
+       lock = lock_ref_sha1_basic(newref, NULL, 0, NULL);
        if (!lock) {
                error("unable to lock %s for update", newref);
                goto rollback;
@@ -899,7 +998,7 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
        return 0;
 
  rollback:
-       lock = lock_ref_sha1_basic(oldref, NULL, NULL);
+       lock = lock_ref_sha1_basic(oldref, NULL, 0, NULL);
        if (!lock) {
                error("unable to lock %s for rollback", oldref);
                goto rollbacklog;