Skip to content

Commit fea9d18

Browse files
committed
Merge branch 'jk/no-clobber-dangling-symref-with-fetch'
"git fetch" can clobber a symref that is dangling when the remote-tracking HEAD is set to auto update, which has been corrected. * jk/no-clobber-dangling-symref-with-fetch: refs: do not clobber dangling symrefs t5510: prefer "git -C" to subshell for followRemoteHEAD tests t5510: stop changing top-level working directory t5510: make confusing config cleanup more explicit
2 parents 040f05e + 450fc2b commit fea9d18

File tree

4 files changed

+319
-309
lines changed

4 files changed

+319
-309
lines changed

refs/files-backend.c

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2515,13 +2515,37 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update,
25152515
*/
25162516
static enum ref_transaction_error check_old_oid(struct ref_update *update,
25172517
struct object_id *oid,
2518+
struct strbuf *referent,
25182519
struct strbuf *err)
25192520
{
25202521
if (update->flags & REF_LOG_ONLY ||
2521-
!(update->flags & REF_HAVE_OLD) ||
2522-
oideq(oid, &update->old_oid))
2522+
!(update->flags & REF_HAVE_OLD))
25232523
return 0;
25242524

2525+
if (oideq(oid, &update->old_oid)) {
2526+
/*
2527+
* Normally matching the expected old oid is enough. Either we
2528+
* found the ref at the expected state, or we are creating and
2529+
* expect the null oid (and likewise found nothing).
2530+
*
2531+
* But there is one exception for the null oid: if we found a
2532+
* symref pointing to nothing we'll also get the null oid. In
2533+
* regular recursive mode, that's good (we'll write to what the
2534+
* symref points to, which doesn't exist). But in no-deref
2535+
* mode, it means we'll clobber the symref, even though the
2536+
* caller asked for this to be a creation event. So flag
2537+
* that case to preserve the dangling symref.
2538+
*/
2539+
if ((update->flags & REF_NO_DEREF) && referent->len &&
2540+
is_null_oid(oid)) {
2541+
strbuf_addf(err, "cannot lock ref '%s': "
2542+
"dangling symref already exists",
2543+
ref_update_original_update_refname(update));
2544+
return REF_TRANSACTION_ERROR_CREATE_EXISTS;
2545+
}
2546+
return 0;
2547+
}
2548+
25252549
if (is_null_oid(&update->old_oid)) {
25262550
strbuf_addf(err, "cannot lock ref '%s': "
25272551
"reference already exists",
@@ -2661,7 +2685,8 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re
26612685
if (update->old_target)
26622686
ret = ref_update_check_old_target(referent.buf, update, err);
26632687
else
2664-
ret = check_old_oid(update, &lock->old_oid, err);
2688+
ret = check_old_oid(update, &lock->old_oid,
2689+
&referent, err);
26652690
if (ret)
26662691
goto out;
26672692
} else {
@@ -2693,7 +2718,8 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re
26932718
ret = REF_TRANSACTION_ERROR_EXPECTED_SYMREF;
26942719
goto out;
26952720
} else {
2696-
ret = check_old_oid(update, &lock->old_oid, err);
2721+
ret = check_old_oid(update, &lock->old_oid,
2722+
&referent, err);
26972723
if (ret) {
26982724
goto out;
26992725
}

refs/reftable-backend.c

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,9 +1274,33 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
12741274
ret = ref_update_check_old_target(referent->buf, u, err);
12751275
if (ret)
12761276
return ret;
1277-
} else if ((u->flags & (REF_LOG_ONLY | REF_HAVE_OLD)) == REF_HAVE_OLD &&
1278-
!oideq(&current_oid, &u->old_oid)) {
1279-
if (is_null_oid(&u->old_oid)) {
1277+
} else if ((u->flags & (REF_LOG_ONLY | REF_HAVE_OLD)) == REF_HAVE_OLD) {
1278+
if (oideq(&current_oid, &u->old_oid)) {
1279+
/*
1280+
* Normally matching the expected old oid is enough. Either we
1281+
* found the ref at the expected state, or we are creating and
1282+
* expect the null oid (and likewise found nothing).
1283+
*
1284+
* But there is one exception for the null oid: if we found a
1285+
* symref pointing to nothing we'll also get the null oid. In
1286+
* regular recursive mode, that's good (we'll write to what the
1287+
* symref points to, which doesn't exist). But in no-deref
1288+
* mode, it means we'll clobber the symref, even though the
1289+
* caller asked for this to be a creation event. So flag
1290+
* that case to preserve the dangling symref.
1291+
*
1292+
* Everything else is OK and we can fall through to the
1293+
* end of the conditional chain.
1294+
*/
1295+
if ((u->flags & REF_NO_DEREF) &&
1296+
referent->len &&
1297+
is_null_oid(&u->old_oid)) {
1298+
strbuf_addf(err, _("cannot lock ref '%s': "
1299+
"dangling symref already exists"),
1300+
ref_update_original_update_refname(u));
1301+
return REF_TRANSACTION_ERROR_CREATE_EXISTS;
1302+
}
1303+
} else if (is_null_oid(&u->old_oid)) {
12801304
strbuf_addf(err, _("cannot lock ref '%s': "
12811305
"reference already exists"),
12821306
ref_update_original_update_refname(u));

t/t1400-update-ref.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2368,4 +2368,25 @@ test_expect_success REFFILES 'empty directories are pruned when not committing'
23682368
test_path_is_missing .git/refs/heads/nested
23692369
'
23702370

2371+
test_expect_success 'dangling symref not overwritten by creation' '
2372+
test_when_finished "git update-ref -d refs/heads/dangling" &&
2373+
git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
2374+
test_must_fail git update-ref --no-deref --stdin 2>err <<-\EOF &&
2375+
create refs/heads/dangling HEAD
2376+
EOF
2377+
test_grep "cannot lock.*dangling symref already exists" err &&
2378+
test_must_fail git rev-parse --verify refs/heads/dangling &&
2379+
test_must_fail git rev-parse --verify refs/heads/does-not-exist
2380+
'
2381+
2382+
test_expect_success 'dangling symref overwritten without old oid' '
2383+
test_when_finished "git update-ref -d refs/heads/dangling" &&
2384+
git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
2385+
git update-ref --no-deref --stdin <<-\EOF &&
2386+
update refs/heads/dangling HEAD
2387+
EOF
2388+
git rev-parse --verify refs/heads/dangling &&
2389+
test_must_fail git rev-parse --verify refs/heads/does-not-exist
2390+
'
2391+
23712392
test_done

0 commit comments

Comments
 (0)