Whip post 1.5.3.3 maintenance series into shape.
[gitweb.git] / refs.c
diff --git a/refs.c b/refs.c
index 7a1f89caad17cfb090e683c9888537666e8398b9..09a2c87fc23e4298bb3bcddc5c2cc649c3206a04 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -47,22 +47,7 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1,
                                struct ref_list **new_entry)
 {
        int len;
-       struct ref_list **p = &list, *entry;
-
-       /* Find the place to insert the ref into.. */
-       while ((entry = *p) != NULL) {
-               int cmp = strcmp(entry->name, name);
-               if (cmp > 0)
-                       break;
-
-               /* Same as existing entry? */
-               if (!cmp) {
-                       if (new_entry)
-                               *new_entry = entry;
-                       return list;
-               }
-               p = &entry->next;
-       }
+       struct ref_list *entry;
 
        /* Allocate it and add it in.. */
        len = strlen(name) + 1;
@@ -71,18 +56,101 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1,
        hashclr(entry->peeled);
        memcpy(entry->name, name, len);
        entry->flag = flag;
-       entry->next = *p;
-       *p = entry;
+       entry->next = list;
        if (new_entry)
                *new_entry = entry;
-       return list;
+       return entry;
+}
+
+/* merge sort the ref list */
+static struct ref_list *sort_ref_list(struct ref_list *list)
+{
+       int psize, qsize, last_merge_count, cmp;
+       struct ref_list *p, *q, *l, *e;
+       struct ref_list *new_list = list;
+       int k = 1;
+       int merge_count = 0;
+
+       if (!list)
+               return list;
+
+       do {
+               last_merge_count = merge_count;
+               merge_count = 0;
+
+               psize = 0;
+
+               p = new_list;
+               q = new_list;
+               new_list = NULL;
+               l = NULL;
+
+               while (p) {
+                       merge_count++;
+
+                       while (psize < k && q->next) {
+                               q = q->next;
+                               psize++;
+                       }
+                       qsize = k;
+
+                       while ((psize > 0) || (qsize > 0 && q)) {
+                               if (qsize == 0 || !q) {
+                                       e = p;
+                                       p = p->next;
+                                       psize--;
+                               } else if (psize == 0) {
+                                       e = q;
+                                       q = q->next;
+                                       qsize--;
+                               } else {
+                                       cmp = strcmp(q->name, p->name);
+                                       if (cmp < 0) {
+                                               e = q;
+                                               q = q->next;
+                                               qsize--;
+                                       } else if (cmp > 0) {
+                                               e = p;
+                                               p = p->next;
+                                               psize--;
+                                       } else {
+                                               if (hashcmp(q->sha1, p->sha1))
+                                                       die("Duplicated ref, and SHA1s don't match: %s",
+                                                           q->name);
+                                               warning("Duplicated ref: %s", q->name);
+                                               e = q;
+                                               q = q->next;
+                                               qsize--;
+                                               free(e);
+                                               e = p;
+                                               p = p->next;
+                                               psize--;
+                                       }
+                               }
+
+                               e->next = NULL;
+
+                               if (l)
+                                       l->next = e;
+                               if (!new_list)
+                                       new_list = e;
+                               l = e;
+                       }
+
+                       p = q;
+               };
+
+               k = k * 2;
+       } while ((last_merge_count != merge_count) || (last_merge_count != 1));
+
+       return new_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;
@@ -142,7 +210,7 @@ static void read_packed_refs(FILE *f, struct cached_refs *cached_refs)
                    !get_sha1_hex(refline + 1, sha1))
                        hashcpy(last->peeled, sha1);
        }
-       cached_refs->packed = list;
+       cached_refs->packed = sort_ref_list(list);
 }
 
 static struct ref_list *get_packed_refs(void)
@@ -201,7 +269,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
                free(ref);
                closedir(dir);
        }
-       return list;
+       return sort_ref_list(list);
 }
 
 static struct ref_list *get_loose_refs(void)
@@ -215,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)
 {
@@ -455,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;
@@ -474,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 == '.')
@@ -588,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;
@@ -613,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",
@@ -632,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;
@@ -658,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;
@@ -702,15 +869,16 @@ static int repack_without_ref(const char *refname)
                        die("too long a refname '%s'", list->name);
                write_or_die(fd, line, len);
        }
+       close(fd);
        return commit_lock_file(&packlock);
 }
 
-int delete_ref(const char *refname, unsigned char *sha1)
+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)) {
@@ -761,7 +929,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;
@@ -815,7 +983,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;
@@ -828,20 +996,10 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
                goto rollback;
        }
 
-       if (!prefixcmp(oldref, "refs/heads/") &&
-                       !prefixcmp(newref, "refs/heads/")) {
-               char oldsection[1024], newsection[1024];
-
-               snprintf(oldsection, 1024, "branch.%s", oldref + 11);
-               snprintf(newsection, 1024, "branch.%s", newref + 11);
-               if (git_config_rename_section(oldsection, newsection) < 0)
-                       return 1;
-       }
-
        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;
@@ -879,6 +1037,32 @@ void unlock_ref(struct ref_lock *lock)
        free(lock);
 }
 
+/*
+ * copy the reflog message msg to buf, which has been allocated sufficiently
+ * large, while cleaning up the whitespaces.  Especially, convert LF to space,
+ * because reflog file is one line per entry.
+ */
+static int copy_msg(char *buf, const char *msg)
+{
+       char *cp = buf;
+       char c;
+       int wasspace = 1;
+
+       *cp++ = '\t';
+       while ((c = *msg++)) {
+               if (wasspace && isspace(c))
+                       continue;
+               wasspace = isspace(c);
+               if (wasspace)
+                       c = ' ';
+               *cp++ = c;
+       }
+       while (buf < cp && isspace(cp[-1]))
+               cp--;
+       *cp++ = '\n';
+       return cp - buf;
+}
+
 static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
                         const unsigned char *new_sha1, const char *msg)
 {
@@ -921,21 +1105,9 @@ static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
                                     log_file, strerror(errno));
        }
 
-       msglen = 0;
-       if (msg) {
-               /* clean up the message and make sure it is a single line */
-               for ( ; *msg; msg++)
-                       if (!isspace(*msg))
-                               break;
-               if (*msg) {
-                       const char *ep = strchr(msg, '\n');
-                       if (ep)
-                               msglen = ep - msg;
-                       else
-                               msglen = strlen(msg);
-               }
-       }
+       adjust_shared_perm(log_file);
 
+       msglen = msg ? strlen(msg) : 0;
        committer = git_committer_info(-1);
        maxlen = strlen(committer) + msglen + 100;
        logrec = xmalloc(maxlen);
@@ -944,11 +1116,10 @@ static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
                      sha1_to_hex(new_sha1),
                      committer);
        if (msglen)
-               len += sprintf(logrec + len - 1, "\t%.*s\n", msglen, msg) - 1;
+               len += copy_msg(logrec + len - 1, msg) - 1;
        written = len <= maxlen ? write_in_full(logfd, logrec, len) : -1;
        free(logrec);
-       close(logfd);
-       if (written != len)
+       if (close(logfd) != 0 || written != len)
                return error("Unable to append to %s", log_file);
        return 0;
 }
@@ -978,6 +1149,27 @@ int write_ref_sha1(struct ref_lock *lock,
                unlock_ref(lock);
                return -1;
        }
+       if (strcmp(lock->orig_ref_name, "HEAD") != 0) {
+               /*
+                * Special hack: If a branch is updated directly and HEAD
+                * points to it (may happen on the remote side of a push
+                * for example) then logically the HEAD reflog should be
+                * updated too.
+                * A generic solution implies reverse symref information,
+                * but finding all symrefs pointing to the given branch
+                * would be rather costly for this rare event (the direct
+                * update of a branch) to be worth it.  So let's cheat and
+                * check with HEAD only which should cover 99% of all usage
+                * scenarios (even 100% of the default ones).
+                */
+               unsigned char head_sha1[20];
+               int head_flag;
+               const char *head_ref;
+               head_ref = resolve_ref("HEAD", head_sha1, 1, &head_flag);
+               if (head_ref && (head_flag & REF_ISSYMREF) &&
+                   !strcmp(head_ref, lock->ref_name))
+                       log_ref_write("HEAD", lock->old_sha1, sha1, logmsg);
+       }
        if (commit_lock_file(lock->lk)) {
                error("Couldn't set %s", lock->ref_name);
                unlock_ref(lock);
@@ -1024,8 +1216,7 @@ int create_symref(const char *ref_target, const char *refs_heads_master,
                goto error_free_return;
        }
        written = write_in_full(fd, ref, len);
-       close(fd);
-       if (written != len) {
+       if (close(fd) != 0 || written != len) {
                error("Unable to write to %s", lockpath);
                goto error_unlink_return;
        }
@@ -1075,6 +1266,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
        unsigned long date;
        unsigned char logged_sha1[20];
        void *log_mapped;
+       size_t mapsz;
 
        logfile = git_path("logs/%s", ref);
        logfd = open(logfile, O_RDONLY, 0);
@@ -1083,7 +1275,8 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
        fstat(logfd, &st);
        if (!st.st_size)
                die("Log %s is empty.", logfile);
-       log_mapped = xmmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, logfd, 0);
+       mapsz = xsize_t(st.st_size);
+       log_mapped = xmmap(NULL, mapsz, PROT_READ, MAP_PRIVATE, logfd, 0);
        logdata = log_mapped;
        close(logfd);
 
@@ -1120,7 +1313,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
                                if (hashcmp(logged_sha1, sha1)) {
                                        fprintf(stderr,
                                                "warning: Log %s has gap after %s.\n",
-                                               logfile, show_rfc2822_date(date, tz));
+                                               logfile, show_date(date, tz, DATE_RFC2822));
                                }
                        }
                        else if (date == at_time) {
@@ -1133,10 +1326,10 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
                                if (hashcmp(logged_sha1, sha1)) {
                                        fprintf(stderr,
                                                "warning: Log %s unexpectedly ended on %s.\n",
-                                               logfile, show_rfc2822_date(date, tz));
+                                               logfile, show_date(date, tz, DATE_RFC2822));
                                }
                        }
-                       munmap(log_mapped, st.st_size);
+                       munmap(log_mapped, mapsz);
                        return 0;
                }
                lastrec = rec;
@@ -1155,7 +1348,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
                die("Log %s is corrupt.", logfile);
        if (msg)
                *msg = ref_msg(logdata, logend);
-       munmap(log_mapped, st.st_size);
+       munmap(log_mapped, mapsz);
 
        if (cutoff_time)
                *cutoff_time = date;