initial_ref_transaction_commit(): check for ref D/F conflicts
authorMichael Haggerty <mhagger@alum.mit.edu>
Mon, 22 Jun 2015 14:03:04 +0000 (16:03 +0200)
committerJunio C Hamano <gitster@pobox.com>
Mon, 22 Jun 2015 20:17:12 +0000 (13:17 -0700)
In initial_ref_transaction_commit(), check for D/F conflicts (i.e.,
the type of conflict that exists between "refs/foo" and
"refs/foo/bar") among the references being created and between the
references being created and any hypothetical existing references.

Ideally, there shouldn't *be* any existing references when this
function is called. But, at least in the case of the "testgit" remote
helper, "clone" can be called after the remote-tracking "HEAD" and
"master" branches have already been created. So let's just do the
full-blown check.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
refs.c
diff --git a/refs.c b/refs.c
index 388ac3ed357212fd40139ebc1533c80b78a3fb60..1e762fb8bc6fc935246bf13a45bbd646ddb8427c 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -4093,9 +4093,19 @@ int ref_transaction_commit(struct ref_transaction *transaction,
        return ret;
 }
 
+static int ref_present(const char *refname,
+                      const struct object_id *oid, int flags, void *cb_data)
+{
+       struct string_list *affected_refnames = cb_data;
+
+       return string_list_has_string(affected_refnames, refname);
+}
+
 int initial_ref_transaction_commit(struct ref_transaction *transaction,
                                   struct strbuf *err)
 {
+       struct ref_dir *loose_refs = get_loose_refs(&ref_cache);
+       struct ref_dir *packed_refs = get_packed_refs(&ref_cache);
        int ret = 0, i;
        int n = transaction->nr;
        struct ref_update **updates = transaction->updates;
@@ -4115,12 +4125,36 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction,
                goto cleanup;
        }
 
+       /*
+        * It's really undefined to call this function in an active
+        * repository or when there are existing references: we are
+        * only locking and changing packed-refs, so (1) any
+        * simultaneous processes might try to change a reference at
+        * the same time we do, and (2) any existing loose versions of
+        * the references that we are setting would have precedence
+        * over our values. But some remote helpers create the remote
+        * "HEAD" and "master" branches before calling this function,
+        * so here we really only check that none of the references
+        * that we are creating already exists.
+        */
+       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];
 
                if ((update->flags & REF_HAVE_OLD) &&
                    !is_null_sha1(update->old_sha1))
                        die("BUG: initial ref transaction with old_sha1 set");
+               if (verify_refname_available(update->refname,
+                                            &affected_refnames, NULL,
+                                            loose_refs, err) ||
+                   verify_refname_available(update->refname,
+                                            &affected_refnames, NULL,
+                                            packed_refs, err)) {
+                       ret = TRANSACTION_NAME_CONFLICT;
+                       goto cleanup;
+               }
        }
 
        if (lock_packed_refs(0)) {