read_raw_ref(): improve docstring
[gitweb.git] / refs / files-backend.c
index 1f38076411dc62b82792b677d95fe775effc79cd..c41cf432d13f203a2f3796fc21b7732f8b5750c5 100644 (file)
@@ -1389,37 +1389,45 @@ static int resolve_missing_loose_ref(const char *refname,
 }
 
 /*
- * Read a raw ref from the filesystem or packed refs file.
+ * Read the specified reference from the filesystem or packed refs
+ * file, non-recursively. Set type to describe the reference, and:
  *
- * If the ref is a sha1, fill in sha1 and return 0.
+ * - If refname is the name of a normal reference, fill in sha1
+ *   (leaving referent unchanged).
  *
- * If the ref is symbolic, fill in *symref with the referrent
- * (e.g. "refs/heads/master") and return 0.  The caller is responsible
- * for validating the referrent.  Set REF_ISSYMREF in flags.
+ * - If refname is the name of a symbolic reference, write the full
+ *   name of the reference to which it refers (e.g.
+ *   "refs/heads/master") to referent and set the REF_ISSYMREF bit in
+ *   type (leaving sha1 unchanged). The caller is responsible for
+ *   validating that referent is a valid reference name.
  *
- * If the ref doesn't exist, set errno to ENOENT and return -1.
+ * WARNING: refname might be used as part of a filename, so it is
+ * important from a security standpoint that it be safe in the sense
+ * of refname_is_safe(). Moreover, for symrefs this function sets
+ * referent to whatever the repository says, which might not be a
+ * properly-formatted or even safe reference name. NEITHER INPUT NOR
+ * OUTPUT REFERENCE NAMES ARE VALIDATED WITHIN THIS FUNCTION.
  *
- * If the ref exists but is neither a symbolic ref nor a sha1, it is
- * broken. Set REF_ISBROKEN in flags, set errno to EINVAL, and return
- * -1.
+ * Return 0 on success. If the ref doesn't exist, set errno to ENOENT
+ * and return -1. If the ref exists but is neither a symbolic ref nor
+ * a sha1, it is broken; set REF_ISBROKEN in type, set errno to
+ * EINVAL, and return -1. If there is another error reading the ref,
+ * set errno appropriately and return -1.
  *
- * If there is another error reading the ref, set errno appropriately and
- * return -1.
- *
- * Backend-specific flags might be set in flags as well, regardless of
+ * Backend-specific flags might be set in type as well, regardless of
  * outcome.
  *
- * sb_path is workspace: the caller should allocate and free it.
+ * It is OK for refname to point into referent. If so:
+ *
+ * - if the function succeeds with REF_ISSYMREF, referent will be
+ *   overwritten and the memory formerly pointed to by it might be
+ *   changed or even freed.
  *
- * It is OK for refname to point into symref. In this case:
- * - if the function succeeds with REF_ISSYMREF, symref will be
- *   overwritten and the memory pointed to by refname might be changed
- *   or even freed.
- * - in all other cases, symref will be untouched, and therefore
+ * - in all other cases, referent will be untouched, and therefore
  *   refname will still be valid and unchanged.
  */
 int read_raw_ref(const char *refname, unsigned char *sha1,
-                struct strbuf *symref, unsigned int *flags)
+                struct strbuf *referent, unsigned int *type)
 {
        struct strbuf sb_contents = STRBUF_INIT;
        struct strbuf sb_path = STRBUF_INIT;
@@ -1430,6 +1438,7 @@ int read_raw_ref(const char *refname, unsigned char *sha1,
        int ret = -1;
        int save_errno;
 
+       *type = 0;
        strbuf_reset(&sb_path);
        strbuf_git_path(&sb_path, "%s", refname);
        path = sb_path.buf;
@@ -1448,7 +1457,7 @@ int read_raw_ref(const char *refname, unsigned char *sha1,
        if (lstat(path, &st) < 0) {
                if (errno != ENOENT)
                        goto out;
-               if (resolve_missing_loose_ref(refname, sha1, flags)) {
+               if (resolve_missing_loose_ref(refname, sha1, type)) {
                        errno = ENOENT;
                        goto out;
                }
@@ -1468,8 +1477,8 @@ int read_raw_ref(const char *refname, unsigned char *sha1,
                }
                if (starts_with(sb_contents.buf, "refs/") &&
                    !check_refname_format(sb_contents.buf, 0)) {
-                       strbuf_swap(&sb_contents, symref);
-                       *flags |= REF_ISSYMREF;
+                       strbuf_swap(&sb_contents, referent);
+                       *type |= REF_ISSYMREF;
                        ret = 0;
                        goto out;
                }
@@ -1477,7 +1486,16 @@ int read_raw_ref(const char *refname, unsigned char *sha1,
 
        /* Is it a directory? */
        if (S_ISDIR(st.st_mode)) {
-               errno = EISDIR;
+               /*
+                * Even though there is a directory where the loose
+                * ref is supposed to be, there could still be a
+                * packed ref:
+                */
+               if (resolve_missing_loose_ref(refname, sha1, type)) {
+                       errno = EISDIR;
+                       goto out;
+               }
+               ret = 0;
                goto out;
        }
 
@@ -1508,9 +1526,9 @@ int read_raw_ref(const char *refname, unsigned char *sha1,
                while (isspace(*buf))
                        buf++;
 
-               strbuf_reset(symref);
-               strbuf_addstr(symref, buf);
-               *flags |= REF_ISSYMREF;
+               strbuf_reset(referent);
+               strbuf_addstr(referent, buf);
+               *type |= REF_ISSYMREF;
                ret = 0;
                goto out;
        }
@@ -1521,7 +1539,7 @@ int read_raw_ref(const char *refname, unsigned char *sha1,
         */
        if (get_sha1_hex(buf, sha1) ||
            (buf[40] != '\0' && !isspace(buf[40]))) {
-               *flags |= REF_ISBROKEN;
+               *type |= REF_ISBROKEN;
                errno = EINVAL;
                goto out;
        }
@@ -2351,20 +2369,17 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
        struct ref_lock *lock;
        struct stat loginfo;
        int log = !lstat(git_path("logs/%s", oldrefname), &loginfo);
-       const char *symref = NULL;
        struct strbuf err = STRBUF_INIT;
 
        if (log && S_ISLNK(loginfo.st_mode))
                return error("reflog for %s is a symlink", oldrefname);
 
-       symref = resolve_ref_unsafe(oldrefname, RESOLVE_REF_READING,
-                                   orig_sha1, &flag);
+       if (!resolve_ref_unsafe(oldrefname, RESOLVE_REF_READING, orig_sha1, &flag))
+               return error("refname %s not found", oldrefname);
+
        if (flag & REF_ISSYMREF)
                return error("refname %s is a symbolic ref, renaming it is not supported",
                        oldrefname);
-       if (!symref)
-               return error("refname %s not found", oldrefname);
-
        if (!rename_ref_available(oldrefname, newrefname))
                return 1;
 
@@ -2457,6 +2472,30 @@ static int close_ref(struct ref_lock *lock)
 
 static int commit_ref(struct ref_lock *lock)
 {
+       char *path = get_locked_file_path(lock->lk);
+       struct stat st;
+
+       if (!lstat(path, &st) && S_ISDIR(st.st_mode)) {
+               /*
+                * There is a directory at the path we want to rename
+                * the lockfile to. Hopefully it is empty; try to
+                * delete it.
+                */
+               size_t len = strlen(path);
+               struct strbuf sb_path = STRBUF_INIT;
+
+               strbuf_attach(&sb_path, path, len, len);
+
+               /*
+                * If this fails, commit_lock_file() will also fail
+                * and will report the problem.
+                */
+               remove_empty_directories(&sb_path);
+               strbuf_release(&sb_path);
+       } else {
+               free(path);
+       }
+
        if (commit_lock_file(lock->lk))
                return -1;
        return 0;
@@ -2686,7 +2725,7 @@ static int commit_ref_update(struct ref_lock *lock,
                }
        }
        if (commit_ref(lock)) {
-               error("Couldn't set %s", lock->ref_name);
+               strbuf_addf(err, "Couldn't set %s", lock->ref_name);
                unlock_ref(lock);
                return -1;
        }
@@ -3046,8 +3085,6 @@ int ref_transaction_commit(struct ref_transaction *transaction,
                           struct strbuf *err)
 {
        int ret = 0, i;
-       int n = transaction->nr;
-       struct ref_update **updates = transaction->updates;
        struct string_list refs_to_delete = STRING_LIST_INIT_NODUP;
        struct string_list_item *ref_to_delete;
        struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
@@ -3057,14 +3094,15 @@ int ref_transaction_commit(struct ref_transaction *transaction,
        if (transaction->state != REF_TRANSACTION_OPEN)
                die("BUG: commit called for transaction that is not open");
 
-       if (!n) {
+       if (!transaction->nr) {
                transaction->state = REF_TRANSACTION_CLOSED;
                return 0;
        }
 
        /* Fail if a refname appears more than once in the transaction: */
-       for (i = 0; i < n; i++)
-               string_list_append(&affected_refnames, updates[i]->refname);
+       for (i = 0; i < transaction->nr; i++)
+               string_list_append(&affected_refnames,
+                                  transaction->updates[i]->refname);
        string_list_sort(&affected_refnames);
        if (ref_update_reject_duplicates(&affected_refnames, err)) {
                ret = TRANSACTION_GENERIC_ERROR;
@@ -3077,8 +3115,8 @@ int ref_transaction_commit(struct ref_transaction *transaction,
         * lockfiles, ready to be activated. Only keep one lockfile
         * open at a time to avoid running out of file descriptors.
         */
-       for (i = 0; i < n; i++) {
-               struct ref_update *update = updates[i];
+       for (i = 0; i < transaction->nr; i++) {
+               struct ref_update *update = transaction->updates[i];
 
                if ((update->flags & REF_HAVE_NEW) &&
                    is_null_sha1(update->new_sha1))
@@ -3148,8 +3186,8 @@ int ref_transaction_commit(struct ref_transaction *transaction,
        }
 
        /* Perform updates first so live commits remain referenced */
-       for (i = 0; i < n; i++) {
-               struct ref_update *update = updates[i];
+       for (i = 0; i < transaction->nr; i++) {
+               struct ref_update *update = transaction->updates[i];
 
                if (update->flags & REF_NEEDS_COMMIT) {
                        if (commit_ref_update(update->lock,
@@ -3167,8 +3205,8 @@ int ref_transaction_commit(struct ref_transaction *transaction,
        }
 
        /* Perform deletes now that updates are safely completed */
-       for (i = 0; i < n; i++) {
-               struct ref_update *update = updates[i];
+       for (i = 0; i < transaction->nr; i++) {
+               struct ref_update *update = transaction->updates[i];
 
                if (update->flags & REF_DELETING) {
                        if (delete_ref_loose(update->lock, update->type, err)) {
@@ -3193,9 +3231,9 @@ int ref_transaction_commit(struct ref_transaction *transaction,
 cleanup:
        transaction->state = REF_TRANSACTION_CLOSED;
 
-       for (i = 0; i < n; i++)
-               if (updates[i]->lock)
-                       unlock_ref(updates[i]->lock);
+       for (i = 0; i < transaction->nr; i++)
+               if (transaction->updates[i]->lock)
+                       unlock_ref(transaction->updates[i]->lock);
        string_list_clear(&refs_to_delete, 0);
        string_list_clear(&affected_refnames, 0);
        return ret;
@@ -3213,8 +3251,6 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction,
                                   struct strbuf *err)
 {
        int ret = 0, i;
-       int n = transaction->nr;
-       struct ref_update **updates = transaction->updates;
        struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
 
        assert(err);
@@ -3223,8 +3259,9 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction,
                die("BUG: commit called for transaction that is not open");
 
        /* Fail if a refname appears more than once in the transaction: */
-       for (i = 0; i < n; i++)
-               string_list_append(&affected_refnames, updates[i]->refname);
+       for (i = 0; i < transaction->nr; i++)
+               string_list_append(&affected_refnames,
+                                  transaction->updates[i]->refname);
        string_list_sort(&affected_refnames);
        if (ref_update_reject_duplicates(&affected_refnames, err)) {
                ret = TRANSACTION_GENERIC_ERROR;
@@ -3246,8 +3283,8 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction,
        if (for_each_rawref(ref_present, &affected_refnames))
                die("BUG: initial ref transaction called with existing refs");
 
-       for (i = 0; i < n; i++) {
-               struct ref_update *update = updates[i];
+       for (i = 0; i < transaction->nr; i++) {
+               struct ref_update *update = transaction->updates[i];
 
                if ((update->flags & REF_HAVE_OLD) &&
                    !is_null_sha1(update->old_sha1))
@@ -3267,8 +3304,8 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction,
                goto cleanup;
        }
 
-       for (i = 0; i < n; i++) {
-               struct ref_update *update = updates[i];
+       for (i = 0; i < transaction->nr; i++) {
+               struct ref_update *update = transaction->updates[i];
 
                if ((update->flags & REF_HAVE_NEW) &&
                    !is_null_sha1(update->new_sha1))