remove_leading_path: use a strbuf for internal storage
[gitweb.git] / refs.c
diff --git a/refs.c b/refs.c
index 835801905b597769aa5f3b5dd216075c263e6df5..c2709de2a0ac512e32eb18f70ca2f45d8f1d98c8 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -1579,16 +1579,15 @@ static int resolve_missing_loose_ref(const char *refname,
 }
 
 /* This function needs to return a meaningful errno on failure */
-static const char *resolve_ref_unsafe_1(const char *refname,
-                                       int resolve_flags,
-                                       unsigned char *sha1,
-                                       int *flags,
-                                       struct strbuf *sb_path)
+static const char *resolve_ref_1(const char *refname,
+                                int resolve_flags,
+                                unsigned char *sha1,
+                                int *flags,
+                                struct strbuf *sb_refname,
+                                struct strbuf *sb_path,
+                                struct strbuf *sb_contents)
 {
        int depth = MAXDEPTH;
-       ssize_t len;
-       char buffer[256];
-       static char refname_buffer[256];
        int bad_name = 0;
 
        if (flags)
@@ -1654,19 +1653,18 @@ static const char *resolve_ref_unsafe_1(const char *refname,
 
                /* Follow "normalized" - ie "refs/.." symlinks by hand */
                if (S_ISLNK(st.st_mode)) {
-                       len = readlink(path, buffer, sizeof(buffer)-1);
-                       if (len < 0) {
+                       strbuf_reset(sb_contents);
+                       if (strbuf_readlink(sb_contents, path, 0) < 0) {
                                if (errno == ENOENT || errno == EINVAL)
                                        /* inconsistent with lstat; retry */
                                        goto stat_ref;
                                else
                                        return NULL;
                        }
-                       buffer[len] = 0;
-                       if (starts_with(buffer, "refs/") &&
-                                       !check_refname_format(buffer, 0)) {
-                               strcpy(refname_buffer, buffer);
-                               refname = refname_buffer;
+                       if (starts_with(sb_contents->buf, "refs/") &&
+                           !check_refname_format(sb_contents->buf, 0)) {
+                               strbuf_swap(sb_refname, sb_contents);
+                               refname = sb_refname->buf;
                                if (flags)
                                        *flags |= REF_ISSYMREF;
                                if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
@@ -1695,28 +1693,26 @@ static const char *resolve_ref_unsafe_1(const char *refname,
                        else
                                return NULL;
                }
-               len = read_in_full(fd, buffer, sizeof(buffer)-1);
-               if (len < 0) {
+               strbuf_reset(sb_contents);
+               if (strbuf_read(sb_contents, fd, 256) < 0) {
                        int save_errno = errno;
                        close(fd);
                        errno = save_errno;
                        return NULL;
                }
                close(fd);
-               while (len && isspace(buffer[len-1]))
-                       len--;
-               buffer[len] = '\0';
+               strbuf_rtrim(sb_contents);
 
                /*
                 * Is it a symbolic ref?
                 */
-               if (!starts_with(buffer, "ref:")) {
+               if (!starts_with(sb_contents->buf, "ref:")) {
                        /*
                         * Please note that FETCH_HEAD has a second
                         * line containing other data.
                         */
-                       if (get_sha1_hex(buffer, sha1) ||
-                           (buffer[40] != '\0' && !isspace(buffer[40]))) {
+                       if (get_sha1_hex(sb_contents->buf, sha1) ||
+                           (sb_contents->buf[40] != '\0' && !isspace(sb_contents->buf[40]))) {
                                if (flags)
                                        *flags |= REF_ISBROKEN;
                                errno = EINVAL;
@@ -1731,10 +1727,12 @@ static const char *resolve_ref_unsafe_1(const char *refname,
                }
                if (flags)
                        *flags |= REF_ISSYMREF;
-               buf = buffer + 4;
+               buf = sb_contents->buf + 4;
                while (isspace(*buf))
                        buf++;
-               refname = strcpy(refname_buffer, buf);
+               strbuf_reset(sb_refname);
+               strbuf_addstr(sb_refname, buf);
+               refname = sb_refname->buf;
                if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
                        hashclr(sha1);
                        return refname;
@@ -1756,10 +1754,15 @@ static const char *resolve_ref_unsafe_1(const char *refname,
 const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
                               unsigned char *sha1, int *flags)
 {
+       static struct strbuf sb_refname = STRBUF_INIT;
+       struct strbuf sb_contents = STRBUF_INIT;
        struct strbuf sb_path = STRBUF_INIT;
-       const char *ret = resolve_ref_unsafe_1(refname, resolve_flags,
-                                              sha1, flags, &sb_path);
+       const char *ret;
+
+       ret = resolve_ref_1(refname, resolve_flags, sha1, flags,
+                           &sb_refname, &sb_path, &sb_contents);
        strbuf_release(&sb_path);
+       strbuf_release(&sb_contents);
        return ret;
 }
 
@@ -2854,12 +2857,117 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
        return 0;
 }
 
+static int is_per_worktree_ref(const char *refname)
+{
+       return !strcmp(refname, "HEAD");
+}
+
+static int is_pseudoref_syntax(const char *refname)
+{
+       const char *c;
+
+       for (c = refname; *c; c++) {
+               if (!isupper(*c) && *c != '-' && *c != '_')
+                       return 0;
+       }
+
+       return 1;
+}
+
+enum ref_type ref_type(const char *refname)
+{
+       if (is_per_worktree_ref(refname))
+               return REF_TYPE_PER_WORKTREE;
+       if (is_pseudoref_syntax(refname))
+               return REF_TYPE_PSEUDOREF;
+       return REF_TYPE_NORMAL;
+}
+
+static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,
+                          const unsigned char *old_sha1, struct strbuf *err)
+{
+       const char *filename;
+       int fd;
+       static struct lock_file lock;
+       struct strbuf buf = STRBUF_INIT;
+       int ret = -1;
+
+       strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1));
+
+       filename = git_path("%s", pseudoref);
+       fd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR);
+       if (fd < 0) {
+               strbuf_addf(err, "Could not open '%s' for writing: %s",
+                           filename, strerror(errno));
+               return -1;
+       }
+
+       if (old_sha1) {
+               unsigned char actual_old_sha1[20];
+
+               if (read_ref(pseudoref, actual_old_sha1))
+                       die("could not read ref '%s'", pseudoref);
+               if (hashcmp(actual_old_sha1, old_sha1)) {
+                       strbuf_addf(err, "Unexpected sha1 when writing %s", pseudoref);
+                       rollback_lock_file(&lock);
+                       goto done;
+               }
+       }
+
+       if (write_in_full(fd, buf.buf, buf.len) != buf.len) {
+               strbuf_addf(err, "Could not write to '%s'", filename);
+               rollback_lock_file(&lock);
+               goto done;
+       }
+
+       commit_lock_file(&lock);
+       ret = 0;
+done:
+       strbuf_release(&buf);
+       return ret;
+}
+
+static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1)
+{
+       static struct lock_file lock;
+       const char *filename;
+
+       filename = git_path("%s", pseudoref);
+
+       if (old_sha1 && !is_null_sha1(old_sha1)) {
+               int fd;
+               unsigned char actual_old_sha1[20];
+
+               fd = hold_lock_file_for_update(&lock, filename,
+                                              LOCK_DIE_ON_ERROR);
+               if (fd < 0)
+                       die_errno(_("Could not open '%s' for writing"), filename);
+               if (read_ref(pseudoref, actual_old_sha1))
+                       die("could not read ref '%s'", pseudoref);
+               if (hashcmp(actual_old_sha1, old_sha1)) {
+                       warning("Unexpected sha1 when deleting %s", pseudoref);
+                       rollback_lock_file(&lock);
+                       return -1;
+               }
+
+               unlink(filename);
+               rollback_lock_file(&lock);
+       } else {
+               unlink(filename);
+       }
+
+       return 0;
+}
+
 int delete_ref(const char *refname, const unsigned char *old_sha1,
               unsigned int flags)
 {
        struct ref_transaction *transaction;
        struct strbuf err = STRBUF_INIT;
 
+       if (ref_type(refname) == REF_TYPE_PSEUDOREF)
+               return delete_pseudoref(refname, old_sha1);
+
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
            ref_transaction_delete(transaction, refname, old_sha1,
@@ -3221,10 +3329,10 @@ static int log_ref_write_fd(int fd, const unsigned char *old_sha1,
        msglen = msg ? strlen(msg) : 0;
        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);
+       len = xsnprintf(logrec, maxlen, "%s %s %s\n",
+                       sha1_to_hex(old_sha1),
+                       sha1_to_hex(new_sha1),
+                       committer);
        if (msglen)
                len += copy_msg(logrec + len - 1, msg) - 1;
 
@@ -3296,6 +3404,7 @@ static int write_ref_to_lockfile(struct ref_lock *lock,
 {
        static char term = '\n';
        struct object *o;
+       int fd;
 
        o = parse_object(sha1);
        if (!o) {
@@ -3312,11 +3421,12 @@ static int write_ref_to_lockfile(struct ref_lock *lock,
                unlock_ref(lock);
                return -1;
        }
-       if (write_in_full(lock->lk->fd, sha1_to_hex(sha1), 40) != 40 ||
-           write_in_full(lock->lk->fd, &term, 1) != 1 ||
+       fd = get_lock_file_fd(lock->lk);
+       if (write_in_full(fd, sha1_to_hex(sha1), 40) != 40 ||
+           write_in_full(fd, &term, 1) != 1 ||
            close_ref(lock) < 0) {
                strbuf_addf(err,
-                           "Couldn't write %s", lock->lk->filename.buf);
+                           "Couldn't write %s", get_lock_file_path(lock->lk));
                unlock_ref(lock);
                return -1;
        }
@@ -3961,17 +4071,25 @@ int update_ref(const char *msg, const char *refname,
               const unsigned char *new_sha1, const unsigned char *old_sha1,
               unsigned int flags, enum action_on_err onerr)
 {
-       struct ref_transaction *t;
+       struct ref_transaction *t = NULL;
        struct strbuf err = STRBUF_INIT;
+       int ret = 0;
 
-       t = ref_transaction_begin(&err);
-       if (!t ||
-           ref_transaction_update(t, refname, new_sha1, old_sha1,
-                                  flags, msg, &err) ||
-           ref_transaction_commit(t, &err)) {
+       if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
+               ret = write_pseudoref(refname, new_sha1, old_sha1, &err);
+       } else {
+               t = ref_transaction_begin(&err);
+               if (!t ||
+                   ref_transaction_update(t, refname, new_sha1, old_sha1,
+                                          flags, msg, &err) ||
+                   ref_transaction_commit(t, &err)) {
+                       ret = 1;
+                       ref_transaction_free(t);
+               }
+       }
+       if (ret) {
                const char *str = "update_ref failed for ref '%s': %s";
 
-               ref_transaction_free(t);
                switch (onerr) {
                case UPDATE_REFS_MSG_ON_ERR:
                        error(str, refname, err.buf);
@@ -3986,7 +4104,8 @@ int update_ref(const char *msg, const char *refname,
                return 1;
        }
        strbuf_release(&err);
-       ref_transaction_free(t);
+       if (t)
+               ref_transaction_free(t);
        return 0;
 }
 
@@ -4494,7 +4613,7 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
                cb.newlog = fdopen_lock_file(&reflog_lock, "w");
                if (!cb.newlog) {
                        error("cannot fdopen %s (%s)",
-                             reflog_lock.filename.buf, strerror(errno));
+                             get_lock_file_path(&reflog_lock), strerror(errno));
                        goto failure;
                }
        }
@@ -4519,12 +4638,12 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
                        status |= error("couldn't write %s: %s", log_file,
                                        strerror(errno));
                } else if (update &&
-                          (write_in_full(lock->lk->fd,
+                          (write_in_full(get_lock_file_fd(lock->lk),
                                sha1_to_hex(cb.last_kept_sha1), 40) != 40 ||
-                        write_str_in_full(lock->lk->fd, "\n") != 1 ||
-                        close_ref(lock) < 0)) {
+                           write_str_in_full(get_lock_file_fd(lock->lk), "\n") != 1 ||
+                           close_ref(lock) < 0)) {
                        status |= error("couldn't write %s",
-                                       lock->lk->filename.buf);
+                                       get_lock_file_path(lock->lk));
                        rollback_lock_file(&reflog_lock);
                } else if (commit_lock_file(&reflog_lock)) {
                        status |= error("unable to commit reflog '%s' (%s)",