gitweb: Convert project name to UTF-8
[gitweb.git] / refs.c
diff --git a/refs.c b/refs.c
index aab14fc107353099756403c1bf08a0786e184d98..03e8dfec9fcb2c9fffc7c809048fa90a0e02b0af 100644 (file)
--- a/refs.c
+++ b/refs.c
-#include "refs.h"
 #include "cache.h"
+#include "refs.h"
+#include "object.h"
+#include "tag.h"
+
+/* ISSYMREF=01 and ISPACKED=02 are public interfaces */
+#define REF_KNOWS_PEELED 04
+
+struct ref_list {
+       struct ref_list *next;
+       unsigned char flag; /* ISSYMREF? ISPACKED? */
+       unsigned char sha1[20];
+       unsigned char peeled[20];
+       char name[FLEX_ARRAY];
+};
+
+static const char *parse_ref_line(char *line, unsigned char *sha1)
+{
+       /*
+        * 42: the answer to everything.
+        *
+        * In this case, it happens to be the answer to
+        *  40 (length of sha1 hex representation)
+        *  +1 (space in between hex and name)
+        *  +1 (newline at the end of the line)
+        */
+       int len = strlen(line) - 42;
+
+       if (len <= 0)
+               return NULL;
+       if (get_sha1_hex(line, sha1) < 0)
+               return NULL;
+       if (!isspace(line[40]))
+               return NULL;
+       line += 41;
+       if (isspace(*line))
+               return NULL;
+       if (line[len] != '\n')
+               return NULL;
+       line[len] = 0;
+
+       return line;
+}
+
+static struct ref_list *add_ref(const char *name, const unsigned char *sha1,
+                               int flag, struct ref_list *list,
+                               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;
+       }
+
+       /* Allocate it and add it in.. */
+       len = strlen(name) + 1;
+       entry = xmalloc(sizeof(struct ref_list) + len);
+       hashcpy(entry->sha1, sha1);
+       hashclr(entry->peeled);
+       memcpy(entry->name, name, len);
+       entry->flag = flag;
+       entry->next = *p;
+       *p = entry;
+       if (new_entry)
+               *new_entry = entry;
+       return list;
+}
+
+/*
+ * Future: need to be in "struct repository"
+ * when doing a full libification.
+ */
+struct cached_refs {
+       char did_loose;
+       char did_packed;
+       struct ref_list *loose;
+       struct ref_list *packed;
+} cached_refs;
+
+static void free_ref_list(struct ref_list *list)
+{
+       struct ref_list *next;
+       for ( ; list; list = next) {
+               next = list->next;
+               free(list);
+       }
+}
+
+static void invalidate_cached_refs(void)
+{
+       struct cached_refs *ca = &cached_refs;
+
+       if (ca->did_loose && ca->loose)
+               free_ref_list(ca->loose);
+       if (ca->did_packed && ca->packed)
+               free_ref_list(ca->packed);
+       ca->loose = ca->packed = NULL;
+       ca->did_loose = ca->did_packed = 0;
+}
+
+static void read_packed_refs(FILE *f, struct cached_refs *cached_refs)
+{
+       struct ref_list *list = NULL;
+       struct ref_list *last = NULL;
+       char refline[PATH_MAX];
+       int flag = REF_ISPACKED;
+
+       while (fgets(refline, sizeof(refline), f)) {
+               unsigned char sha1[20];
+               const char *name;
+               static const char header[] = "# pack-refs with:";
+
+               if (!strncmp(refline, header, sizeof(header)-1)) {
+                       const char *traits = refline + sizeof(header) - 1;
+                       if (strstr(traits, " peeled "))
+                               flag |= REF_KNOWS_PEELED;
+                       /* perhaps other traits later as well */
+                       continue;
+               }
+
+               name = parse_ref_line(refline, sha1);
+               if (name) {
+                       list = add_ref(name, sha1, flag, list, &last);
+                       continue;
+               }
+               if (last &&
+                   refline[0] == '^' &&
+                   strlen(refline) == 42 &&
+                   refline[41] == '\n' &&
+                   !get_sha1_hex(refline + 1, sha1))
+                       hashcpy(last->peeled, sha1);
+       }
+       cached_refs->packed = list;
+}
+
+static struct ref_list *get_packed_refs(void)
+{
+       if (!cached_refs.did_packed) {
+               FILE *f = fopen(git_path("packed-refs"), "r");
+               cached_refs.packed = NULL;
+               if (f) {
+                       read_packed_refs(f, &cached_refs);
+                       fclose(f);
+               }
+               cached_refs.did_packed = 1;
+       }
+       return cached_refs.packed;
+}
+
+static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
+{
+       DIR *dir = opendir(git_path("%s", base));
+
+       if (dir) {
+               struct dirent *de;
+               int baselen = strlen(base);
+               char *ref = xmalloc(baselen + 257);
+
+               memcpy(ref, base, baselen);
+               if (baselen && base[baselen-1] != '/')
+                       ref[baselen++] = '/';
+
+               while ((de = readdir(dir)) != NULL) {
+                       unsigned char sha1[20];
+                       struct stat st;
+                       int flag;
+                       int namelen;
+
+                       if (de->d_name[0] == '.')
+                               continue;
+                       namelen = strlen(de->d_name);
+                       if (namelen > 255)
+                               continue;
+                       if (has_extension(de->d_name, ".lock"))
+                               continue;
+                       memcpy(ref + baselen, de->d_name, namelen+1);
+                       if (stat(git_path("%s", ref), &st) < 0)
+                               continue;
+                       if (S_ISDIR(st.st_mode)) {
+                               list = get_ref_dir(ref, list);
+                               continue;
+                       }
+                       if (!resolve_ref(ref, sha1, 1, &flag)) {
+                               error("%s points nowhere!", ref);
+                               continue;
+                       }
+                       list = add_ref(ref, sha1, flag, list, NULL);
+               }
+               free(ref);
+               closedir(dir);
+       }
+       return list;
+}
 
-#include <errno.h>
+static struct ref_list *get_loose_refs(void)
+{
+       if (!cached_refs.did_loose) {
+               cached_refs.loose = get_ref_dir("refs", NULL);
+               cached_refs.did_loose = 1;
+       }
+       return cached_refs.loose;
+}
 
 /* We allow "recursive" symbolic refs. Only within reason, though */
 #define MAXDEPTH 5
 
-const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
+const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag)
 {
        int depth = MAXDEPTH, len;
        char buffer[256];
+       static char ref_buffer[256];
+
+       if (flag)
+               *flag = 0;
 
        for (;;) {
+               const char *path = git_path("%s", ref);
                struct stat st;
                char *buf;
                int fd;
@@ -27,21 +242,41 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
                 * reading.
                 */
                if (lstat(path, &st) < 0) {
+                       struct ref_list *list = get_packed_refs();
+                       while (list) {
+                               if (!strcmp(ref, list->name)) {
+                                       hashcpy(sha1, list->sha1);
+                                       if (flag)
+                                               *flag |= REF_ISPACKED;
+                                       return ref;
+                               }
+                               list = list->next;
+                       }
                        if (reading || errno != ENOENT)
                                return NULL;
                        hashclr(sha1);
-                       return path;
+                       return ref;
                }
 
                /* Follow "normalized" - ie "refs/.." symlinks by hand */
                if (S_ISLNK(st.st_mode)) {
                        len = readlink(path, buffer, sizeof(buffer)-1);
                        if (len >= 5 && !memcmp("refs/", buffer, 5)) {
-                               path = git_path("%.*s", len, buffer);
+                               buffer[len] = 0;
+                               strcpy(ref_buffer, buffer);
+                               ref = ref_buffer;
+                               if (flag)
+                                       *flag |= REF_ISSYMREF;
                                continue;
                        }
                }
 
+               /* Is it a directory? */
+               if (S_ISDIR(st.st_mode)) {
+                       errno = EISDIR;
+                       return NULL;
+               }
+
                /*
                 * Anything else, just open it and try to use it as
                 * a ref
@@ -49,7 +284,7 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
                fd = open(path, O_RDONLY);
                if (fd < 0)
                        return NULL;
-               len = read(fd, buffer, sizeof(buffer)-1);
+               len = read_in_full(fd, buffer, sizeof(buffer)-1);
                close(fd);
 
                /*
@@ -62,154 +297,150 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
                while (len && isspace(*buf))
                        buf++, len--;
                while (len && isspace(buf[len-1]))
-                       buf[--len] = 0;
-               path = git_path("%.*s", len, buf);
+                       len--;
+               buf[len] = 0;
+               memcpy(ref_buffer, buf, len + 1);
+               ref = ref_buffer;
+               if (flag)
+                       *flag |= REF_ISSYMREF;
        }
        if (len < 40 || get_sha1_hex(buffer, sha1))
                return NULL;
-       return path;
+       return ref;
 }
 
-int create_symref(const char *git_HEAD, const char *refs_heads_master)
+int read_ref(const char *ref, unsigned char *sha1)
 {
-       const char *lockpath;
-       char ref[1000];
-       int fd, len, written;
-
-#ifndef NO_SYMLINK_HEAD
-       if (prefer_symlink_refs) {
-               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);
-               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;
-       }
-       if (adjust_shared_perm(git_HEAD)) {
-               unlink(lockpath);
-               error("Unable to fix permissions on %s", lockpath);
-               return -4;
-       }
-       return 0;
+       if (resolve_ref(ref, sha1, 1, NULL))
+               return 0;
+       return -1;
 }
 
-int read_ref(const char *filename, unsigned char *sha1)
+static int do_one_ref(const char *base, each_ref_fn fn, int trim,
+                     void *cb_data, struct ref_list *entry)
 {
-       if (resolve_ref(filename, sha1, 1))
+       if (strncmp(base, entry->name, trim))
                return 0;
-       return -1;
+       if (is_null_sha1(entry->sha1))
+               return 0;
+       if (!has_sha1_file(entry->sha1)) {
+               error("%s does not point to a valid object!", entry->name);
+               return 0;
+       }
+       return fn(entry->name + trim, entry->sha1, entry->flag, cb_data);
 }
 
-static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1), int trim)
+int peel_ref(const char *ref, unsigned char *sha1)
 {
-       int retval = 0;
-       DIR *dir = opendir(git_path("%s", base));
+       int flag;
+       unsigned char base[20];
+       struct object *o;
 
-       if (dir) {
-               struct dirent *de;
-               int baselen = strlen(base);
-               char *path = xmalloc(baselen + 257);
+       if (!resolve_ref(ref, base, 1, &flag))
+               return -1;
+
+       if ((flag & REF_ISPACKED)) {
+               struct ref_list *list = get_packed_refs();
+
+               while (list) {
+                       if (!strcmp(list->name, ref)) {
+                               if (list->flag & REF_KNOWS_PEELED) {
+                                       hashcpy(sha1, list->peeled);
+                                       return 0;
+                               }
+                               /* older pack-refs did not leave peeled ones */
+                               break;
+                       }
+                       list = list->next;
+               }
+       }
 
-               if (!strncmp(base, "./", 2)) {
-                       base += 2;
-                       baselen -= 2;
+       /* fallback - callers should not call this for unpacked refs */
+       o = parse_object(base);
+       if (o->type == OBJ_TAG) {
+               o = deref_tag(o, ref, 0);
+               if (o) {
+                       hashcpy(sha1, o->sha1);
+                       return 0;
                }
-               memcpy(path, base, baselen);
-               if (baselen && base[baselen-1] != '/')
-                       path[baselen++] = '/';
+       }
+       return -1;
+}
 
-               while ((de = readdir(dir)) != NULL) {
-                       unsigned char sha1[20];
-                       struct stat st;
-                       int namelen;
+static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
+                          void *cb_data)
+{
+       int retval;
+       struct ref_list *packed = get_packed_refs();
+       struct ref_list *loose = get_loose_refs();
 
-                       if (de->d_name[0] == '.')
-                               continue;
-                       namelen = strlen(de->d_name);
-                       if (namelen > 255)
-                               continue;
-                       if (has_extension(de->d_name, ".lock"))
-                               continue;
-                       memcpy(path + baselen, de->d_name, namelen+1);
-                       if (stat(git_path("%s", path), &st) < 0)
-                               continue;
-                       if (S_ISDIR(st.st_mode)) {
-                               retval = do_for_each_ref(path, fn, trim);
-                               if (retval)
-                                       break;
-                               continue;
-                       }
-                       if (read_ref(git_path("%s", path), sha1) < 0) {
-                               error("%s points nowhere!", path);
-                               continue;
-                       }
-                       if (!has_sha1_file(sha1)) {
-                               error("%s does not point to a valid "
-                                     "commit object!", path);
-                               continue;
-                       }
-                       retval = fn(path + trim, sha1);
-                       if (retval)
-                               break;
+       while (packed && loose) {
+               struct ref_list *entry;
+               int cmp = strcmp(packed->name, loose->name);
+               if (!cmp) {
+                       packed = packed->next;
+                       continue;
                }
-               free(path);
-               closedir(dir);
+               if (cmp > 0) {
+                       entry = loose;
+                       loose = loose->next;
+               } else {
+                       entry = packed;
+                       packed = packed->next;
+               }
+               retval = do_one_ref(base, fn, trim, cb_data, entry);
+               if (retval)
+                       return retval;
        }
-       return retval;
+
+       for (packed = packed ? packed : loose; packed; packed = packed->next) {
+               retval = do_one_ref(base, fn, trim, cb_data, packed);
+               if (retval)
+                       return retval;
+       }
+       return 0;
 }
 
-int head_ref(int (*fn)(const char *path, const unsigned char *sha1))
+int head_ref(each_ref_fn fn, void *cb_data)
 {
        unsigned char sha1[20];
-       if (!read_ref(git_path("HEAD"), sha1))
-               return fn("HEAD", sha1);
+       int flag;
+
+       if (resolve_ref("HEAD", sha1, 1, &flag))
+               return fn("HEAD", sha1, flag, cb_data);
        return 0;
 }
 
-int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1))
+int for_each_ref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref("refs", fn, 0);
+       return do_for_each_ref("refs/", fn, 0, cb_data);
 }
 
-int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1))
+int for_each_tag_ref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref("refs/tags", fn, 10);
+       return do_for_each_ref("refs/tags/", fn, 10, cb_data);
 }
 
-int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1))
+int for_each_branch_ref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref("refs/heads", fn, 11);
+       return do_for_each_ref("refs/heads/", fn, 11, cb_data);
 }
 
-int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1))
+int for_each_remote_ref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref("refs/remotes", fn, 13);
+       return do_for_each_ref("refs/remotes/", fn, 13, cb_data);
 }
 
+/* NEEDSWORK: This is only used by ssh-upload and it should go; the
+ * caller should do resolve_ref or read_ref like everybody else.  Or
+ * maybe everybody else should use get_ref_sha1() instead of doing
+ * read_ref().
+ */
 int get_ref_sha1(const char *ref, unsigned char *sha1)
 {
        if (check_ref_format(ref))
                return -1;
-       return read_ref(git_path("refs/%s", ref), sha1);
+       return read_ref(mkpath("refs/%s", ref), sha1);
 }
 
 /*
@@ -258,7 +489,7 @@ int check_ref_format(const char *ref)
                level++;
                if (!ch) {
                        if (level < 2)
-                               return -1; /* at least of form "heads/blah" */
+                               return -2; /* at least of form "heads/blah" */
                        return 0;
                }
        }
@@ -267,22 +498,13 @@ int check_ref_format(const char *ref)
 static struct ref_lock *verify_lock(struct ref_lock *lock,
        const unsigned char *old_sha1, int mustexist)
 {
-       char buf[40];
-       int nr, fd = open(lock->ref_file, O_RDONLY);
-       if (fd < 0 && (mustexist || errno != ENOENT)) {
-               error("Can't verify ref %s", lock->ref_file);
-               unlock_ref(lock);
-               return NULL;
-       }
-       nr = read(fd, buf, 40);
-       close(fd);
-       if (nr != 40 || get_sha1_hex(buf, lock->old_sha1) < 0) {
-               error("Can't verify ref %s", lock->ref_file);
+       if (!resolve_ref(lock->ref_name, lock->old_sha1, mustexist, NULL)) {
+               error("Can't verify ref %s", lock->ref_name);
                unlock_ref(lock);
                return NULL;
        }
        if (hashcmp(lock->old_sha1, old_sha1)) {
-               error("Ref %s is at %s but expected %s", lock->ref_file,
+               error("Ref %s is at %s but expected %s", lock->ref_name,
                        sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1));
                unlock_ref(lock);
                return NULL;
@@ -290,54 +512,358 @@ static struct ref_lock *verify_lock(struct ref_lock *lock,
        return lock;
 }
 
-static struct ref_lock *lock_ref_sha1_basic(const char *path,
-       int plen,
-       const unsigned char *old_sha1, int mustexist)
+static int remove_empty_dir_recursive(char *path, int len)
+{
+       DIR *dir = opendir(path);
+       struct dirent *e;
+       int ret = 0;
+
+       if (!dir)
+               return -1;
+       if (path[len-1] != '/')
+               path[len++] = '/';
+       while ((e = readdir(dir)) != NULL) {
+               struct stat st;
+               int namlen;
+               if ((e->d_name[0] == '.') &&
+                   ((e->d_name[1] == 0) ||
+                    ((e->d_name[1] == '.') && e->d_name[2] == 0)))
+                       continue; /* "." and ".." */
+
+               namlen = strlen(e->d_name);
+               if ((len + namlen < PATH_MAX) &&
+                   strcpy(path + len, e->d_name) &&
+                   !lstat(path, &st) &&
+                   S_ISDIR(st.st_mode) &&
+                   !remove_empty_dir_recursive(path, len + namlen))
+                       continue; /* happy */
+
+               /* path too long, stat fails, or non-directory still exists */
+               ret = -1;
+               break;
+       }
+       closedir(dir);
+       if (!ret) {
+               path[len] = 0;
+               ret = rmdir(path);
+       }
+       return ret;
+}
+
+static int remove_empty_directories(char *file)
+{
+       /* we want to create a file but there is a directory there;
+        * if that is an empty directory (or a directory that contains
+        * only empty directories), remove them.
+        */
+       char path[PATH_MAX];
+       int len = strlen(file);
+
+       if (len >= PATH_MAX) /* path too long ;-) */
+               return -1;
+       strcpy(path, file);
+       return remove_empty_dir_recursive(path, len);
+}
+
+static int is_refname_available(const char *ref, const char *oldref,
+                               struct ref_list *list, int quiet)
 {
-       const char *orig_path = path;
+       int namlen = strlen(ref); /* e.g. 'foo/bar' */
+       while (list) {
+               /* list->name could be 'foo' or 'foo/bar/baz' */
+               if (!oldref || strcmp(oldref, list->name)) {
+                       int len = strlen(list->name);
+                       int cmplen = (namlen < len) ? namlen : len;
+                       const char *lead = (namlen < len) ? list->name : ref;
+                       if (!strncmp(ref, list->name, cmplen) &&
+                           lead[cmplen] == '/') {
+                               if (!quiet)
+                                       error("'%s' exists; cannot create '%s'",
+                                             list->name, ref);
+                               return 0;
+                       }
+               }
+               list = list->next;
+       }
+       return 1;
+}
+
+static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int *flag)
+{
+       char *ref_file;
+       const char *orig_ref = ref;
        struct ref_lock *lock;
        struct stat st;
+       int last_errno = 0;
+       int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
 
        lock = xcalloc(1, sizeof(struct ref_lock));
        lock->lock_fd = -1;
 
-       plen = strlen(path) - plen;
-       path = resolve_ref(path, lock->old_sha1, mustexist);
-       if (!path) {
-               int last_errno = errno;
+       ref = resolve_ref(ref, lock->old_sha1, mustexist, flag);
+       if (!ref && errno == EISDIR) {
+               /* we are trying to lock foo but we used to
+                * have foo/bar which now does not exist;
+                * it is normal for the empty directory 'foo'
+                * to remain.
+                */
+               ref_file = git_path("%s", orig_ref);
+               if (remove_empty_directories(ref_file)) {
+                       last_errno = errno;
+                       error("there are still refs under '%s'", orig_ref);
+                       goto error_return;
+               }
+               ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, flag);
+       }
+       if (!ref) {
+               last_errno = errno;
                error("unable to resolve reference %s: %s",
-                       orig_path, strerror(errno));
-               unlock_ref(lock);
-               errno = last_errno;
-               return NULL;
+                       orig_ref, strerror(errno));
+               goto error_return;
        }
+       /* When the ref did not exist and we are creating it,
+        * make sure there is no existing ref that is packed
+        * whose name begins with our refname, nor a ref whose
+        * name is a proper prefix of our refname.
+        */
+       if (is_null_sha1(lock->old_sha1) &&
+            !is_refname_available(ref, NULL, get_packed_refs(), 0))
+               goto error_return;
+
        lock->lk = xcalloc(1, sizeof(struct lock_file));
 
-       lock->ref_file = strdup(path);
-       lock->log_file = strdup(git_path("logs/%s", lock->ref_file + plen));
-       lock->force_write = lstat(lock->ref_file, &st) && errno == ENOENT;
+       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 (safe_create_leading_directories(lock->ref_file))
-               die("unable to create directory for %s", lock->ref_file);
-       lock->lock_fd = hold_lock_file_for_update(lock->lk, lock->ref_file, 1);
+       if (safe_create_leading_directories(ref_file)) {
+               last_errno = errno;
+               error("unable to create directory for %s", ref_file);
+               goto error_return;
+       }
+       lock->lock_fd = hold_lock_file_for_update(lock->lk, ref_file, 1);
 
        return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
+
+ error_return:
+       unlock_ref(lock);
+       errno = last_errno;
+       return NULL;
 }
 
-struct ref_lock *lock_ref_sha1(const char *ref,
-       const unsigned char *old_sha1, int mustexist)
+struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
 {
+       char refpath[PATH_MAX];
        if (check_ref_format(ref))
                return NULL;
-       return lock_ref_sha1_basic(git_path("refs/%s", ref),
-               5 + strlen(ref), old_sha1, mustexist);
+       strcpy(refpath, mkpath("refs/%s", ref));
+       return lock_ref_sha1_basic(refpath, old_sha1, NULL);
 }
 
-struct ref_lock *lock_any_ref_for_update(const char *ref,
-       const unsigned char *old_sha1, int mustexist)
+struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1)
+{
+       if (check_ref_format(ref) == -1)
+               return NULL;
+       return lock_ref_sha1_basic(ref, old_sha1, NULL);
+}
+
+static struct lock_file packlock;
+
+static int repack_without_ref(const char *refname)
+{
+       struct ref_list *list, *packed_ref_list;
+       int fd;
+       int found = 0;
+
+       packed_ref_list = get_packed_refs();
+       for (list = packed_ref_list; list; list = list->next) {
+               if (!strcmp(refname, list->name)) {
+                       found = 1;
+                       break;
+               }
+       }
+       if (!found)
+               return 0;
+       fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
+       if (fd < 0)
+               return error("cannot delete '%s' from packed refs", refname);
+
+       for (list = packed_ref_list; list; list = list->next) {
+               char line[PATH_MAX + 100];
+               int len;
+
+               if (!strcmp(refname, list->name))
+                       continue;
+               len = snprintf(line, sizeof(line), "%s %s\n",
+                              sha1_to_hex(list->sha1), list->name);
+               /* this should not happen but just being defensive */
+               if (len > sizeof(line))
+                       die("too long a refname '%s'", list->name);
+               write_or_die(fd, line, len);
+       }
+       return commit_lock_file(&packlock);
+}
+
+int delete_ref(const char *refname, unsigned char *sha1)
+{
+       struct ref_lock *lock;
+       int err, i, ret = 0, flag = 0;
+
+       lock = lock_ref_sha1_basic(refname, sha1, &flag);
+       if (!lock)
+               return 1;
+       if (!(flag & REF_ISPACKED)) {
+               /* loose */
+               i = strlen(lock->lk->filename) - 5; /* .lock */
+               lock->lk->filename[i] = 0;
+               err = unlink(lock->lk->filename);
+               if (err) {
+                       ret = 1;
+                       error("unlink(%s) failed: %s",
+                             lock->lk->filename, strerror(errno));
+               }
+               lock->lk->filename[i] = '.';
+       }
+       /* removing the loose one could have resurrected an earlier
+        * packed one.  Also, if it was not loose we need to repack
+        * without it.
+        */
+       ret |= repack_without_ref(refname);
+
+       err = unlink(git_path("logs/%s", lock->ref_name));
+       if (err && errno != ENOENT)
+               fprintf(stderr, "warning: unlink(%s) failed: %s",
+                       git_path("logs/%s", lock->ref_name), strerror(errno));
+       invalidate_cached_refs();
+       unlock_ref(lock);
+       return ret;
+}
+
+int rename_ref(const char *oldref, const char *newref, const char *logmsg)
 {
-       return lock_ref_sha1_basic(git_path("%s", ref),
-               strlen(ref), old_sha1, mustexist);
+       static const char renamed_ref[] = "RENAMED-REF";
+       unsigned char sha1[20], orig_sha1[20];
+       int flag = 0, logmoved = 0;
+       struct ref_lock *lock;
+       struct stat loginfo;
+       int log = !lstat(git_path("logs/%s", oldref), &loginfo);
+
+       if (S_ISLNK(loginfo.st_mode))
+               return error("reflog for %s is a symlink", oldref);
+
+       if (!resolve_ref(oldref, orig_sha1, 1, &flag))
+               return error("refname %s not found", oldref);
+
+       if (!is_refname_available(newref, oldref, get_packed_refs(), 0))
+               return 1;
+
+       if (!is_refname_available(newref, oldref, get_loose_refs(), 0))
+               return 1;
+
+       lock = lock_ref_sha1_basic(renamed_ref, NULL, NULL);
+       if (!lock)
+               return error("unable to lock %s", renamed_ref);
+       lock->force_write = 1;
+       if (write_ref_sha1(lock, orig_sha1, logmsg))
+               return error("unable to save current sha1 in %s", renamed_ref);
+
+       if (log && rename(git_path("logs/%s", oldref), git_path("tmp-renamed-log")))
+               return error("unable to move logfile logs/%s to tmp-renamed-log: %s",
+                       oldref, strerror(errno));
+
+       if (delete_ref(oldref, orig_sha1)) {
+               error("unable to delete old %s", oldref);
+               goto rollback;
+       }
+
+       if (resolve_ref(newref, sha1, 1, &flag) && delete_ref(newref, sha1)) {
+               if (errno==EISDIR) {
+                       if (remove_empty_directories(git_path("%s", newref))) {
+                               error("Directory not empty: %s", newref);
+                               goto rollback;
+                       }
+               } else {
+                       error("unable to delete existing %s", newref);
+                       goto rollback;
+               }
+       }
+
+       if (log && safe_create_leading_directories(git_path("logs/%s", newref))) {
+               error("unable to create directory for %s", newref);
+               goto rollback;
+       }
+
+ retry:
+       if (log && rename(git_path("tmp-renamed-log"), git_path("logs/%s", newref))) {
+               if (errno==EISDIR || errno==ENOTDIR) {
+                       /*
+                        * rename(a, b) when b is an existing
+                        * directory ought to result in ISDIR, but
+                        * Solaris 5.8 gives ENOTDIR.  Sheesh.
+                        */
+                       if (remove_empty_directories(git_path("logs/%s", newref))) {
+                               error("Directory not empty: logs/%s", newref);
+                               goto rollback;
+                       }
+                       goto retry;
+               } else {
+                       error("unable to move logfile tmp-renamed-log to logs/%s: %s",
+                               newref, strerror(errno));
+                       goto rollback;
+               }
+       }
+       logmoved = log;
+
+       lock = lock_ref_sha1_basic(newref, NULL, NULL);
+       if (!lock) {
+               error("unable to lock %s for update", newref);
+               goto rollback;
+       }
+
+       lock->force_write = 1;
+       hashcpy(lock->old_sha1, orig_sha1);
+       if (write_ref_sha1(lock, orig_sha1, logmsg)) {
+               error("unable to write current sha1 into %s", newref);
+               goto rollback;
+       }
+
+       if (!strncmp(oldref, "refs/heads/", 11) &&
+                       !strncmp(newref, "refs/heads/", 11)) {
+               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);
+       if (!lock) {
+               error("unable to lock %s for rollback", oldref);
+               goto rollbacklog;
+       }
+
+       lock->force_write = 1;
+       flag = log_all_ref_updates;
+       log_all_ref_updates = 0;
+       if (write_ref_sha1(lock, orig_sha1, NULL))
+               error("unable to write current sha1 into %s", oldref);
+       log_all_ref_updates = flag;
+
+ rollbacklog:
+       if (logmoved && rename(git_path("logs/%s", newref), git_path("logs/%s", oldref)))
+               error("unable to restore logfile %s from %s: %s",
+                       oldref, newref, strerror(errno));
+       if (!logmoved && log &&
+           rename(git_path("tmp-renamed-log"), git_path("logs/%s", oldref)))
+               error("unable to restore logfile %s from tmp-renamed-log: %s",
+                       oldref, strerror(errno));
+
+       return 1;
 }
 
 void unlock_ref(struct ref_lock *lock)
@@ -348,57 +874,82 @@ void unlock_ref(struct ref_lock *lock)
                if (lock->lk)
                        rollback_lock_file(lock->lk);
        }
-       free(lock->ref_file);
-       free(lock->log_file);
+       free(lock->ref_name);
+       free(lock->orig_ref_name);
        free(lock);
 }
 
-static int log_ref_write(struct ref_lock *lock,
-       const unsigned char *sha1, const char *msg)
+static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
+                        const unsigned char *new_sha1, const char *msg)
 {
        int logfd, written, oflags = O_APPEND | O_WRONLY;
        unsigned maxlen, len;
-       char *logrec;
+       int msglen;
+       char *log_file, *logrec;
        const char *committer;
 
-       if (log_all_ref_updates) {
-               if (safe_create_leading_directories(lock->log_file) < 0)
+       if (log_all_ref_updates < 0)
+               log_all_ref_updates = !is_bare_repository();
+
+       log_file = git_path("logs/%s", ref_name);
+
+       if (log_all_ref_updates &&
+           (!strncmp(ref_name, "refs/heads/", 11) ||
+            !strncmp(ref_name, "refs/remotes/", 13) ||
+            !strcmp(ref_name, "HEAD"))) {
+               if (safe_create_leading_directories(log_file) < 0)
                        return error("unable to create directory for %s",
-                               lock->log_file);
+                                    log_file);
                oflags |= O_CREAT;
        }
 
-       logfd = open(lock->log_file, oflags, 0666);
+       logfd = open(log_file, oflags, 0666);
        if (logfd < 0) {
-               if (!log_all_ref_updates && errno == ENOENT)
+               if (!(oflags & O_CREAT) && errno == ENOENT)
                        return 0;
-               return error("Unable to append to %s: %s",
-                       lock->log_file, strerror(errno));
+
+               if ((oflags & O_CREAT) && errno == EISDIR) {
+                       if (remove_empty_directories(log_file)) {
+                               return error("There are still logs under '%s'",
+                                            log_file);
+                       }
+                       logfd = open(log_file, oflags, 0666);
+               }
+
+               if (logfd < 0)
+                       return error("Unable to append to %s: %s",
+                                    log_file, strerror(errno));
        }
 
-       committer = git_committer_info(1);
+       msglen = 0;
        if (msg) {
-               maxlen = strlen(committer) + strlen(msg) + 2*40 + 5;
-               logrec = xmalloc(maxlen);
-               len = snprintf(logrec, maxlen, "%s %s %s\t%s\n",
-                       sha1_to_hex(lock->old_sha1),
-                       sha1_to_hex(sha1),
-                       committer,
-                       msg);
-       }
-       else {
-               maxlen = strlen(committer) + 2*40 + 4;
-               logrec = xmalloc(maxlen);
-               len = snprintf(logrec, maxlen, "%s %s %s\n",
-                       sha1_to_hex(lock->old_sha1),
-                       sha1_to_hex(sha1),
-                       committer);
-       }
-       written = len <= maxlen ? write(logfd, logrec, len) : -1;
+               /* 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);
+               }
+       }
+
+       committer = git_committer_info(-1);
+       maxlen = strlen(committer) + msglen + 100;
+       logrec = xmalloc(maxlen);
+       len = sprintf(logrec, "%s %s %s\n",
+                     sha1_to_hex(old_sha1),
+                     sha1_to_hex(new_sha1),
+                     committer);
+       if (msglen)
+               len += sprintf(logrec + len - 1, "\t%.*s\n", msglen, msg) - 1;
+       written = len <= maxlen ? write_in_full(logfd, logrec, len) : -1;
        free(logrec);
        close(logfd);
        if (written != len)
-               return error("Unable to append to %s", lock->log_file);
+               return error("Unable to append to %s", log_file);
        return 0;
 }
 
@@ -413,19 +964,22 @@ int write_ref_sha1(struct ref_lock *lock,
                unlock_ref(lock);
                return 0;
        }
-       if (write(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 ||
-           write(lock->lock_fd, &term, 1) != 1
+       if (write_in_full(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 ||
+           write_in_full(lock->lock_fd, &term, 1) != 1
                || close(lock->lock_fd) < 0) {
                error("Couldn't write %s", lock->lk->filename);
                unlock_ref(lock);
                return -1;
        }
-       if (log_ref_write(lock, sha1, logmsg) < 0) {
+       invalidate_cached_refs();
+       if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0 ||
+           (strcmp(lock->ref_name, lock->orig_ref_name) &&
+            log_ref_write(lock->orig_ref_name, lock->old_sha1, sha1, logmsg) < 0)) {
                unlock_ref(lock);
                return -1;
        }
        if (commit_lock_file(lock->lk)) {
-               error("Couldn't set %s", lock->ref_file);
+               error("Couldn't set %s", lock->ref_name);
                unlock_ref(lock);
                return -1;
        }
@@ -434,14 +988,88 @@ int write_ref_sha1(struct ref_lock *lock,
        return 0;
 }
 
-int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
+int create_symref(const char *ref_target, const char *refs_heads_master,
+                 const char *logmsg)
+{
+       const char *lockpath;
+       char ref[1000];
+       int fd, len, written;
+       char *git_HEAD = xstrdup(git_path("%s", ref_target));
+       unsigned char old_sha1[20], new_sha1[20];
+
+       if (logmsg && read_ref(ref_target, old_sha1))
+               hashclr(old_sha1);
+
+#ifndef NO_SYMLINK_HEAD
+       if (prefer_symlink_refs) {
+               unlink(git_HEAD);
+               if (!symlink(refs_heads_master, git_HEAD))
+                       goto done;
+               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);
+               goto error_free_return;
+       }
+       lockpath = mkpath("%s.lock", git_HEAD);
+       fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666);
+       if (fd < 0) {
+               error("Unable to open %s for writing", lockpath);
+               goto error_free_return;
+       }
+       written = write_in_full(fd, ref, len);
+       close(fd);
+       if (written != len) {
+               error("Unable to write to %s", lockpath);
+               goto error_unlink_return;
+       }
+       if (rename(lockpath, git_HEAD) < 0) {
+               error("Unable to create %s", git_HEAD);
+               goto error_unlink_return;
+       }
+       if (adjust_shared_perm(git_HEAD)) {
+               error("Unable to fix permissions on %s", lockpath);
+       error_unlink_return:
+               unlink(lockpath);
+       error_free_return:
+               free(git_HEAD);
+               return -1;
+       }
+
+       done:
+       if (logmsg && !read_ref(refs_heads_master, new_sha1))
+               log_ref_write(ref_target, old_sha1, new_sha1, logmsg);
+
+       free(git_HEAD);
+       return 0;
+}
+
+static char *ref_msg(const char *line, const char *endp)
+{
+       const char *ep;
+       char *msg;
+
+       line += 82;
+       for (ep = line; ep < endp && *ep != '\n'; ep++)
+               ;
+       msg = xmalloc(ep - line + 1);
+       memcpy(msg, line, ep - line);
+       msg[ep - line] = 0;
+       return msg;
+}
+
+int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)
 {
        const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
        char *tz_c;
-       int logfd, tz;
+       int logfd, tz, reccnt = 0;
        struct stat st;
        unsigned long date;
        unsigned char logged_sha1[20];
+       void *log_mapped;
 
        logfile = git_path("logs/%s", ref);
        logfd = open(logfile, O_RDONLY, 0);
@@ -450,12 +1078,14 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
        fstat(logfd, &st);
        if (!st.st_size)
                die("Log %s is empty.", logfile);
-       logdata = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, logfd, 0);
+       log_mapped = xmmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, logfd, 0);
+       logdata = log_mapped;
        close(logfd);
 
        lastrec = NULL;
        rec = logend = logdata + st.st_size;
        while (logdata < rec) {
+               reccnt++;
                if (logdata < rec && *(rec-1) == '\n')
                        rec--;
                lastgt = NULL;
@@ -467,14 +1097,22 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
                if (!lastgt)
                        die("Log %s is corrupt.", logfile);
                date = strtoul(lastgt + 1, &tz_c, 10);
-               if (date <= at_time) {
+               if (date <= at_time || cnt == 0) {
+                       tz = strtoul(tz_c, NULL, 10);
+                       if (msg)
+                               *msg = ref_msg(rec, logend);
+                       if (cutoff_time)
+                               *cutoff_time = date;
+                       if (cutoff_tz)
+                               *cutoff_tz = tz;
+                       if (cutoff_cnt)
+                               *cutoff_cnt = reccnt - 1;
                        if (lastrec) {
                                if (get_sha1_hex(lastrec, logged_sha1))
                                        die("Log %s is corrupt.", logfile);
                                if (get_sha1_hex(rec + 41, sha1))
                                        die("Log %s is corrupt.", logfile);
                                if (hashcmp(logged_sha1, sha1)) {
-                                       tz = strtoul(tz_c, NULL, 10);
                                        fprintf(stderr,
                                                "warning: Log %s has gap after %s.\n",
                                                logfile, show_rfc2822_date(date, tz));
@@ -488,16 +1126,17 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
                                if (get_sha1_hex(rec + 41, logged_sha1))
                                        die("Log %s is corrupt.", logfile);
                                if (hashcmp(logged_sha1, sha1)) {
-                                       tz = strtoul(tz_c, NULL, 10);
                                        fprintf(stderr,
                                                "warning: Log %s unexpectedly ended on %s.\n",
                                                logfile, show_rfc2822_date(date, tz));
                                }
                        }
-                       munmap((void*)logdata, st.st_size);
+                       munmap(log_mapped, st.st_size);
                        return 0;
                }
                lastrec = rec;
+               if (cnt > 0)
+                       cnt--;
        }
 
        rec = logdata;
@@ -509,8 +1148,108 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
        tz = strtoul(tz_c, NULL, 10);
        if (get_sha1_hex(logdata, sha1))
                die("Log %s is corrupt.", logfile);
-       munmap((void*)logdata, st.st_size);
-       fprintf(stderr, "warning: Log %s only goes back to %s.\n",
-               logfile, show_rfc2822_date(date, tz));
-       return 0;
+       if (msg)
+               *msg = ref_msg(logdata, logend);
+       munmap(log_mapped, st.st_size);
+
+       if (cutoff_time)
+               *cutoff_time = date;
+       if (cutoff_tz)
+               *cutoff_tz = tz;
+       if (cutoff_cnt)
+               *cutoff_cnt = reccnt;
+       return 1;
+}
+
+int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+{
+       const char *logfile;
+       FILE *logfp;
+       char buf[1024];
+       int ret = 0;
+
+       logfile = git_path("logs/%s", ref);
+       logfp = fopen(logfile, "r");
+       if (!logfp)
+               return -1;
+       while (fgets(buf, sizeof(buf), logfp)) {
+               unsigned char osha1[20], nsha1[20];
+               char *email_end, *message;
+               unsigned long timestamp;
+               int len, tz;
+
+               /* old SP new SP name <email> SP time TAB msg LF */
+               len = strlen(buf);
+               if (len < 83 || buf[len-1] != '\n' ||
+                   get_sha1_hex(buf, osha1) || buf[40] != ' ' ||
+                   get_sha1_hex(buf + 41, nsha1) || buf[81] != ' ' ||
+                   !(email_end = strchr(buf + 82, '>')) ||
+                   email_end[1] != ' ' ||
+                   !(timestamp = strtoul(email_end + 2, &message, 10)) ||
+                   !message || message[0] != ' ' ||
+                   (message[1] != '+' && message[1] != '-') ||
+                   !isdigit(message[2]) || !isdigit(message[3]) ||
+                   !isdigit(message[4]) || !isdigit(message[5]) ||
+                   message[6] != '\t')
+                       continue; /* corrupt? */
+               email_end[1] = '\0';
+               tz = strtol(message + 1, NULL, 10);
+               message += 7;
+               ret = fn(osha1, nsha1, buf+82, timestamp, tz, message, cb_data);
+               if (ret)
+                       break;
+       }
+       fclose(logfp);
+       return ret;
+}
+
+static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data)
+{
+       DIR *dir = opendir(git_path("logs/%s", base));
+       int retval = errno;
+
+       if (dir) {
+               struct dirent *de;
+               int baselen = strlen(base);
+               char *log = xmalloc(baselen + 257);
+
+               memcpy(log, base, baselen);
+               if (baselen && base[baselen-1] != '/')
+                       log[baselen++] = '/';
+
+               while ((de = readdir(dir)) != NULL) {
+                       struct stat st;
+                       int namelen;
+
+                       if (de->d_name[0] == '.')
+                               continue;
+                       namelen = strlen(de->d_name);
+                       if (namelen > 255)
+                               continue;
+                       if (has_extension(de->d_name, ".lock"))
+                               continue;
+                       memcpy(log + baselen, de->d_name, namelen+1);
+                       if (stat(git_path("logs/%s", log), &st) < 0)
+                               continue;
+                       if (S_ISDIR(st.st_mode)) {
+                               retval = do_for_each_reflog(log, fn, cb_data);
+                       } else {
+                               unsigned char sha1[20];
+                               if (!resolve_ref(log, sha1, 0, NULL))
+                                       retval = error("bad ref for %s", log);
+                               else
+                                       retval = fn(log, sha1, 0, cb_data);
+                       }
+                       if (retval)
+                               break;
+               }
+               free(log);
+               closedir(dir);
+       }
+       return retval;
+}
+
+int for_each_reflog(each_ref_fn fn, void *cb_data)
+{
+       return do_for_each_reflog("", fn, cb_data);
 }