Merge branch 'jk/commit-date-approxidate'
[gitweb.git] / refs.c
diff --git a/refs.c b/refs.c
index 28d5eca8eaff7be48a4b89e6217886b8c96eb3b2..728a76164830368637e95390d95ce3f7c7df659e 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -3243,9 +3243,9 @@ static struct ref_lock *update_ref_lock(const char *refname,
        if (!lock) {
                const char *str = "Cannot lock the ref '%s'.";
                switch (onerr) {
-               case MSG_ON_ERR: error(str, refname); break;
-               case DIE_ON_ERR: die(str, refname); break;
-               case QUIET_ON_ERR: break;
+               case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
+               case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
+               case UPDATE_REFS_QUIET_ON_ERR: break;
                }
        }
        return lock;
@@ -3258,15 +3258,118 @@ static int update_ref_write(const char *action, const char *refname,
        if (write_ref_sha1(lock, sha1, action) < 0) {
                const char *str = "Cannot update the ref '%s'.";
                switch (onerr) {
-               case MSG_ON_ERR: error(str, refname); break;
-               case DIE_ON_ERR: die(str, refname); break;
-               case QUIET_ON_ERR: break;
+               case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
+               case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
+               case UPDATE_REFS_QUIET_ON_ERR: break;
                }
                return 1;
        }
        return 0;
 }
 
+/**
+ * Information needed for a single ref update.  Set new_sha1 to the
+ * new value or to zero to delete the ref.  To check the old value
+ * while locking the ref, set have_old to 1 and set old_sha1 to the
+ * value or to zero to ensure the ref does not exist before update.
+ */
+struct ref_update {
+       unsigned char new_sha1[20];
+       unsigned char old_sha1[20];
+       int flags; /* REF_NODEREF? */
+       int have_old; /* 1 if old_sha1 is valid, 0 otherwise */
+       struct ref_lock *lock;
+       int type;
+       const char refname[FLEX_ARRAY];
+};
+
+/*
+ * Data structure for holding a reference transaction, which can
+ * consist of checks and updates to multiple references, carried out
+ * as atomically as possible.  This structure is opaque to callers.
+ */
+struct ref_transaction {
+       struct ref_update **updates;
+       size_t alloc;
+       size_t nr;
+};
+
+struct ref_transaction *ref_transaction_begin(void)
+{
+       return xcalloc(1, sizeof(struct ref_transaction));
+}
+
+static void ref_transaction_free(struct ref_transaction *transaction)
+{
+       int i;
+
+       for (i = 0; i < transaction->nr; i++)
+               free(transaction->updates[i]);
+
+       free(transaction->updates);
+       free(transaction);
+}
+
+void ref_transaction_rollback(struct ref_transaction *transaction)
+{
+       ref_transaction_free(transaction);
+}
+
+static struct ref_update *add_update(struct ref_transaction *transaction,
+                                    const char *refname)
+{
+       size_t len = strlen(refname);
+       struct ref_update *update = xcalloc(1, sizeof(*update) + len + 1);
+
+       strcpy((char *)update->refname, refname);
+       ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
+       transaction->updates[transaction->nr++] = update;
+       return update;
+}
+
+void ref_transaction_update(struct ref_transaction *transaction,
+                           const char *refname,
+                           unsigned char *new_sha1, unsigned char *old_sha1,
+                           int flags, int have_old)
+{
+       struct ref_update *update = add_update(transaction, refname);
+
+       hashcpy(update->new_sha1, new_sha1);
+       update->flags = flags;
+       update->have_old = have_old;
+       if (have_old)
+               hashcpy(update->old_sha1, old_sha1);
+}
+
+void ref_transaction_create(struct ref_transaction *transaction,
+                           const char *refname,
+                           unsigned char *new_sha1,
+                           int flags)
+{
+       struct ref_update *update = add_update(transaction, refname);
+
+       assert(!is_null_sha1(new_sha1));
+       hashcpy(update->new_sha1, new_sha1);
+       hashclr(update->old_sha1);
+       update->flags = flags;
+       update->have_old = 1;
+}
+
+void ref_transaction_delete(struct ref_transaction *transaction,
+                           const char *refname,
+                           unsigned char *old_sha1,
+                           int flags, int have_old)
+{
+       struct ref_update *update = add_update(transaction, refname);
+
+       update->flags = flags;
+       update->have_old = have_old;
+       if (have_old) {
+               assert(!is_null_sha1(old_sha1));
+               hashcpy(update->old_sha1, old_sha1);
+       }
+}
+
 int update_ref(const char *action, const char *refname,
               const unsigned char *sha1, const unsigned char *oldval,
               int flags, enum action_on_err onerr)
@@ -3282,7 +3385,7 @@ static int ref_update_compare(const void *r1, const void *r2)
 {
        const struct ref_update * const *u1 = r1;
        const struct ref_update * const *u2 = r2;
-       return strcmp((*u1)->ref_name, (*u2)->ref_name);
+       return strcmp((*u1)->refname, (*u2)->refname);
 }
 
 static int ref_update_reject_duplicates(struct ref_update **updates, int n,
@@ -3290,15 +3393,15 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n,
 {
        int i;
        for (i = 1; i < n; i++)
-               if (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) {
+               if (!strcmp(updates[i - 1]->refname, updates[i]->refname)) {
                        const char *str =
                                "Multiple updates for ref '%s' not allowed.";
                        switch (onerr) {
-                       case MSG_ON_ERR:
-                               error(str, updates[i]->ref_name); break;
-                       case DIE_ON_ERR:
-                               die(str, updates[i]->ref_name); break;
-                       case QUIET_ON_ERR:
+                       case UPDATE_REFS_MSG_ON_ERR:
+                               error(str, updates[i]->refname); break;
+                       case UPDATE_REFS_DIE_ON_ERR:
+                               die(str, updates[i]->refname); break;
+                       case UPDATE_REFS_QUIET_ON_ERR:
                                break;
                        }
                        return 1;
@@ -3306,26 +3409,21 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n,
        return 0;
 }
 
-int update_refs(const char *action, const struct ref_update **updates_orig,
-               int n, enum action_on_err onerr)
+int ref_transaction_commit(struct ref_transaction *transaction,
+                          const char *msg, enum action_on_err onerr)
 {
        int ret = 0, delnum = 0, i;
-       struct ref_update **updates;
-       int *types;
-       struct ref_lock **locks;
        const char **delnames;
+       int n = transaction->nr;
+       struct ref_update **updates = transaction->updates;
 
-       if (!updates_orig || !n)
+       if (!n)
                return 0;
 
        /* Allocate work space */
-       updates = xmalloc(sizeof(*updates) * n);
-       types = xmalloc(sizeof(*types) * n);
-       locks = xcalloc(n, sizeof(*locks));
        delnames = xmalloc(sizeof(*delnames) * n);
 
        /* Copy, sort, and reject duplicate refs */
-       memcpy(updates, updates_orig, sizeof(*updates) * n);
        qsort(updates, n, sizeof(*updates), ref_update_compare);
        ret = ref_update_reject_duplicates(updates, n, onerr);
        if (ret)
@@ -3333,35 +3431,44 @@ int update_refs(const char *action, const struct ref_update **updates_orig,
 
        /* Acquire all locks while verifying old values */
        for (i = 0; i < n; i++) {
-               locks[i] = update_ref_lock(updates[i]->ref_name,
-                                          (updates[i]->have_old ?
-                                           updates[i]->old_sha1 : NULL),
-                                          updates[i]->flags,
-                                          &types[i], onerr);
-               if (!locks[i]) {
+               struct ref_update *update = updates[i];
+
+               update->lock = update_ref_lock(update->refname,
+                                              (update->have_old ?
+                                               update->old_sha1 : NULL),
+                                              update->flags,
+                                              &update->type, onerr);
+               if (!update->lock) {
                        ret = 1;
                        goto cleanup;
                }
        }
 
        /* Perform updates first so live commits remain referenced */
-       for (i = 0; i < n; i++)
-               if (!is_null_sha1(updates[i]->new_sha1)) {
-                       ret = update_ref_write(action,
-                                              updates[i]->ref_name,
-                                              updates[i]->new_sha1,
-                                              locks[i], onerr);
-                       locks[i] = NULL; /* freed by update_ref_write */
+       for (i = 0; i < n; i++) {
+               struct ref_update *update = updates[i];
+
+               if (!is_null_sha1(update->new_sha1)) {
+                       ret = update_ref_write(msg,
+                                              update->refname,
+                                              update->new_sha1,
+                                              update->lock, onerr);
+                       update->lock = NULL; /* freed by update_ref_write */
                        if (ret)
                                goto cleanup;
                }
+       }
 
        /* Perform deletes now that updates are safely completed */
-       for (i = 0; i < n; i++)
-               if (locks[i]) {
-                       delnames[delnum++] = locks[i]->ref_name;
-                       ret |= delete_ref_loose(locks[i], types[i]);
+       for (i = 0; i < n; i++) {
+               struct ref_update *update = updates[i];
+
+               if (update->lock) {
+                       delnames[delnum++] = update->lock->ref_name;
+                       ret |= delete_ref_loose(update->lock, update->type);
                }
+       }
+
        ret |= repack_without_refs(delnames, delnum);
        for (i = 0; i < delnum; i++)
                unlink_or_warn(git_path("logs/%s", delnames[i]));
@@ -3369,12 +3476,10 @@ int update_refs(const char *action, const struct ref_update **updates_orig,
 
 cleanup:
        for (i = 0; i < n; i++)
-               if (locks[i])
-                       unlock_ref(locks[i]);
-       free(updates);
-       free(types);
-       free(locks);
+               if (updates[i]->lock)
+                       unlock_ref(updates[i]->lock);
        free(delnames);
+       ref_transaction_free(transaction);
        return ret;
 }