diff options
| -rw-r--r-- | refs/files-backend.c | 34 | ||||
| -rw-r--r-- | refs/reftable-backend.c | 30 | ||||
| -rwxr-xr-x | t/t1400-update-ref.sh | 21 | ||||
| -rwxr-xr-x | t/t5510-fetch.sh | 9 |
4 files changed, 87 insertions, 7 deletions
diff --git a/refs/files-backend.c b/refs/files-backend.c index 905555365b..a4419ef62d 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -2512,13 +2512,37 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update, */ 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", @@ -2658,7 +2682,8 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re 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 { @@ -2690,7 +2715,8 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re 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; } diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 99fafd75eb..ef98584bf9 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -1272,9 +1272,33 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor 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)); diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index d29d23cb89..29b31e3b9b 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -2310,4 +2310,25 @@ test_expect_success 'update-ref should also create reflog for HEAD' ' 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 diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 24379ec7aa..83d1aadf9f 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -232,6 +232,15 @@ test_expect_success 'followRemoteHEAD does not kick in with refspecs' ' 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 && ( |
