*/
static enum ref_transaction_error check_old_oid(struct ref_update *update,
struct object_id *oid,
+ struct strbuf *referent,
struct strbuf *err)
{
if (update->flags & REF_LOG_ONLY ||
- !(update->flags & REF_HAVE_OLD) ||
- oideq(oid, &update->old_oid))
+ !(update->flags & REF_HAVE_OLD))
return 0;
+ if (oideq(oid, &update->old_oid)) {
+ /*
+ * Normally matching the expected old oid is enough. Either we
+ * found the ref at the expected state, or we are creating and
+ * expect the null oid (and likewise found nothing).
+ *
+ * But there is one exception for the null oid: if we found a
+ * symref pointing to nothing we'll also get the null oid. In
+ * regular recursive mode, that's good (we'll write to what the
+ * symref points to, which doesn't exist). But in no-deref
+ * mode, it means we'll clobber the symref, even though the
+ * caller asked for this to be a creation event. So flag
+ * that case to preserve the dangling symref.
+ */
+ if ((update->flags & REF_NO_DEREF) && referent->len &&
+ is_null_oid(oid)) {
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "dangling symref already exists",
+ ref_update_original_update_refname(update));
+ return REF_TRANSACTION_ERROR_CREATE_EXISTS;
+ }
+ return 0;
+ }
+
if (is_null_oid(&update->old_oid)) {
strbuf_addf(err, "cannot lock ref '%s': "
"reference already exists",
if (update->old_target)
ret = ref_update_check_old_target(referent.buf, update, err);
else
- ret = check_old_oid(update, &lock->old_oid, err);
+ ret = check_old_oid(update, &lock->old_oid,
+ &referent, err);
if (ret)
goto out;
} else {
ret = REF_TRANSACTION_ERROR_EXPECTED_SYMREF;
goto out;
} else {
- ret = check_old_oid(update, &lock->old_oid, err);
+ ret = check_old_oid(update, &lock->old_oid,
+ &referent, err);
if (ret) {
goto out;
}
ret = ref_update_check_old_target(referent->buf, u, err);
if (ret)
return ret;
- } else if ((u->flags & (REF_LOG_ONLY | REF_HAVE_OLD)) == REF_HAVE_OLD &&
- !oideq(¤t_oid, &u->old_oid)) {
- if (is_null_oid(&u->old_oid)) {
+ } else if ((u->flags & (REF_LOG_ONLY | REF_HAVE_OLD)) == REF_HAVE_OLD) {
+ if (oideq(¤t_oid, &u->old_oid)) {
+ /*
+ * Normally matching the expected old oid is enough. Either we
+ * found the ref at the expected state, or we are creating and
+ * expect the null oid (and likewise found nothing).
+ *
+ * But there is one exception for the null oid: if we found a
+ * symref pointing to nothing we'll also get the null oid. In
+ * regular recursive mode, that's good (we'll write to what the
+ * symref points to, which doesn't exist). But in no-deref
+ * mode, it means we'll clobber the symref, even though the
+ * caller asked for this to be a creation event. So flag
+ * that case to preserve the dangling symref.
+ *
+ * Everything else is OK and we can fall through to the
+ * end of the conditional chain.
+ */
+ if ((u->flags & REF_NO_DEREF) &&
+ referent->len &&
+ is_null_oid(&u->old_oid)) {
+ strbuf_addf(err, _("cannot lock ref '%s': "
+ "dangling symref already exists"),
+ ref_update_original_update_refname(u));
+ return REF_TRANSACTION_ERROR_CREATE_EXISTS;
+ }
+ } else if (is_null_oid(&u->old_oid)) {
strbuf_addf(err, _("cannot lock ref '%s': "
"reference already exists"),
ref_update_original_update_refname(u));
test_cmp expect actual
'
+test_expect_success 'dangling symref not overwritten by creation' '
+ test_when_finished "git update-ref -d refs/heads/dangling" &&
+ git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
+ test_must_fail git update-ref --no-deref --stdin 2>err <<-\EOF &&
+ create refs/heads/dangling HEAD
+ EOF
+ test_grep "cannot lock.*dangling symref already exists" err &&
+ test_must_fail git rev-parse --verify refs/heads/dangling &&
+ test_must_fail git rev-parse --verify refs/heads/does-not-exist
+'
+
+test_expect_success 'dangling symref overwritten without old oid' '
+ test_when_finished "git update-ref -d refs/heads/dangling" &&
+ git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
+ git update-ref --no-deref --stdin <<-\EOF &&
+ update refs/heads/dangling HEAD
+ EOF
+ git rev-parse --verify refs/heads/dangling &&
+ test_must_fail git rev-parse --verify refs/heads/does-not-exist
+'
+
test_done
test_cmp expect actual
'
+test_expect_success 'followRemoteHEAD create does not overwrite dangling symref' '
+ git -C two remote add -m does-not-exist custom-head ../one &&
+ test_config -C two remote.custom-head.followRemoteHEAD create &&
+ git -C two fetch custom-head &&
+ echo refs/remotes/custom-head/does-not-exist >expect &&
+ git -C two symbolic-ref refs/remotes/custom-head/HEAD >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'fetch --prune on its own works as expected' '
git clone . prune &&
(