Skip to content

Commit 2343720

Browse files
KarthikNayakgitster
authored andcommitted
update-ref: add support for 'symref-delete' command
Add a new command 'symref-delete' to allow deletions of symbolic refs in a transaction via the '--stdin' mode of the 'git-update-ref' command. The 'symref-delete' command can, when given an <old-target>, delete the provided <ref> only when it points to <old-target>. This command is only compatible with the 'no-deref' mode because we optionally want to check the 'old_target' of the ref being deleted. De-referencing a symbolic ref would provide a regular ref and we already have the 'delete' command for regular refs. While users can also use 'git symbolic-ref -d' to delete symbolic refs, the 'symref-delete' command in 'git-update-ref' allows users to do so within a transaction, which promises atomicity of the operation and can be batched with other commands. When no 'old_target' is provided it can also delete regular refs, similar to how the 'delete' command can delete symrefs when no 'old_oid' is provided. Helped-by: Patrick Steinhardt <[email protected]> Signed-off-by: Karthik Nayak <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 1451ac7 commit 2343720

File tree

8 files changed

+140
-10
lines changed

8 files changed

+140
-10
lines changed

Documentation/git-update-ref.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ performs all modifications together. Specify commands of the form:
6565
create SP <ref> SP <new-oid> LF
6666
delete SP <ref> [SP <old-oid>] LF
6767
verify SP <ref> [SP <old-oid>] LF
68+
symref-delete SP <ref> [SP <old-target>] LF
6869
symref-verify SP <ref> [SP <old-target>] LF
6970
option SP <opt> LF
7071
start LF
@@ -87,6 +88,7 @@ quoting:
8788
create SP <ref> NUL <new-oid> NUL
8889
delete SP <ref> NUL [<old-oid>] NUL
8990
verify SP <ref> NUL [<old-oid>] NUL
91+
symref-delete SP <ref> [NUL <old-target>] NUL
9092
symref-verify SP <ref> [NUL <old-target>] NUL
9193
option SP <opt> NUL
9294
start NUL
@@ -119,6 +121,9 @@ verify::
119121
Verify <ref> against <old-oid> but do not change it. If
120122
<old-oid> is zero or missing, the ref must not exist.
121123

124+
symref-delete::
125+
Delete <ref> after verifying it exists with <old-target>, if given.
126+
122127
symref-verify::
123128
Verify symbolic <ref> against <old-target> but do not change it.
124129
If <old-target> is missing, the ref must not exist. Can only be

builtin/fetch.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1382,8 +1382,8 @@ static int prune_refs(struct display_state *display_state,
13821382
if (!dry_run) {
13831383
if (transaction) {
13841384
for (ref = stale_refs; ref; ref = ref->next) {
1385-
result = ref_transaction_delete(transaction, ref->name, NULL, 0,
1386-
"fetch: prune", &err);
1385+
result = ref_transaction_delete(transaction, ref->name, NULL,
1386+
NULL, 0, "fetch: prune", &err);
13871387
if (result)
13881388
goto cleanup;
13891389
}

builtin/receive-pack.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1576,7 +1576,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)
15761576
if (ref_transaction_delete(transaction,
15771577
namespaced_name,
15781578
old_oid,
1579-
0, "push", &err)) {
1579+
NULL, 0,
1580+
"push", &err)) {
15801581
rp_error("%s", err.buf);
15811582
ret = "failed to delete";
15821583
} else {

builtin/update-ref.c

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,14 +293,44 @@ static void parse_cmd_delete(struct ref_transaction *transaction,
293293

294294
if (ref_transaction_delete(transaction, refname,
295295
have_old ? &old_oid : NULL,
296-
update_flags, msg, &err))
296+
NULL, update_flags, msg, &err))
297297
die("%s", err.buf);
298298

299299
update_flags = default_flags;
300300
free(refname);
301301
strbuf_release(&err);
302302
}
303303

304+
305+
static void parse_cmd_symref_delete(struct ref_transaction *transaction,
306+
const char *next, const char *end)
307+
{
308+
struct strbuf err = STRBUF_INIT;
309+
char *refname, *old_target;
310+
311+
if (!(update_flags & REF_NO_DEREF))
312+
die("symref-delete: cannot operate with deref mode");
313+
314+
refname = parse_refname(&next);
315+
if (!refname)
316+
die("symref-delete: missing <ref>");
317+
318+
old_target = parse_next_refname(&next);
319+
320+
if (*next != line_termination)
321+
die("symref-delete %s: extra input: %s", refname, next);
322+
323+
if (ref_transaction_delete(transaction, refname, NULL,
324+
old_target, update_flags, msg, &err))
325+
die("%s", err.buf);
326+
327+
update_flags = default_flags;
328+
free(refname);
329+
free(old_target);
330+
strbuf_release(&err);
331+
}
332+
333+
304334
static void parse_cmd_verify(struct ref_transaction *transaction,
305335
const char *next, const char *end)
306336
{
@@ -443,6 +473,7 @@ static const struct parse_cmd {
443473
{ "create", parse_cmd_create, 2, UPDATE_REFS_OPEN },
444474
{ "delete", parse_cmd_delete, 2, UPDATE_REFS_OPEN },
445475
{ "verify", parse_cmd_verify, 2, UPDATE_REFS_OPEN },
476+
{ "symref-delete", parse_cmd_symref_delete, 2, UPDATE_REFS_OPEN },
446477
{ "symref-verify", parse_cmd_symref_verify, 2, UPDATE_REFS_OPEN },
447478
{ "option", parse_cmd_option, 1, UPDATE_REFS_OPEN },
448479
{ "start", parse_cmd_start, 0, UPDATE_REFS_STARTED },

refs.c

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -979,7 +979,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg,
979979
transaction = ref_store_transaction_begin(refs, &err);
980980
if (!transaction ||
981981
ref_transaction_delete(transaction, refname, old_oid,
982-
flags, msg, &err) ||
982+
NULL, flags, msg, &err) ||
983983
ref_transaction_commit(transaction, &err)) {
984984
error("%s", err.buf);
985985
ref_transaction_free(transaction);
@@ -1317,14 +1317,20 @@ int ref_transaction_create(struct ref_transaction *transaction,
13171317
int ref_transaction_delete(struct ref_transaction *transaction,
13181318
const char *refname,
13191319
const struct object_id *old_oid,
1320-
unsigned int flags, const char *msg,
1320+
const char *old_target,
1321+
unsigned int flags,
1322+
const char *msg,
13211323
struct strbuf *err)
13221324
{
13231325
if (old_oid && is_null_oid(old_oid))
13241326
BUG("delete called with old_oid set to zeros");
1327+
if (old_oid && old_target)
1328+
BUG("delete called with both old_oid and old_target set");
1329+
if (old_target && !(flags & REF_NO_DEREF))
1330+
BUG("delete cannot operate on symrefs with deref mode");
13251331
return ref_transaction_update(transaction, refname,
13261332
null_oid(), old_oid,
1327-
NULL, NULL, flags,
1333+
NULL, old_target, flags,
13281334
msg, err);
13291335
}
13301336

@@ -2767,7 +2773,7 @@ int refs_delete_refs(struct ref_store *refs, const char *logmsg,
27672773

27682774
for_each_string_list_item(item, refnames) {
27692775
ret = ref_transaction_delete(transaction, item->string,
2770-
NULL, flags, msg, &err);
2776+
NULL, NULL, flags, msg, &err);
27712777
if (ret) {
27722778
warning(_("could not delete reference %s: %s"),
27732779
item->string, err.buf);

refs.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -767,7 +767,9 @@ int ref_transaction_create(struct ref_transaction *transaction,
767767
int ref_transaction_delete(struct ref_transaction *transaction,
768768
const char *refname,
769769
const struct object_id *old_oid,
770-
unsigned int flags, const char *msg,
770+
const char *old_target,
771+
unsigned int flags,
772+
const char *msg,
771773
struct strbuf *err);
772774

773775
/*

t/t1400-update-ref.sh

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1729,6 +1729,74 @@ do
17291729
test_cmp expect actual
17301730
'
17311731

1732+
test_expect_success "stdin $type symref-delete fails without --no-deref" '
1733+
git symbolic-ref refs/heads/symref $a &&
1734+
format_command $type "symref-delete refs/heads/symref" "$a" >stdin &&
1735+
test_must_fail git update-ref --stdin $type <stdin 2>err &&
1736+
grep "fatal: symref-delete: cannot operate with deref mode" err
1737+
'
1738+
1739+
test_expect_success "stdin $type symref-delete fails with no ref" '
1740+
format_command $type "symref-delete " >stdin &&
1741+
test_must_fail git update-ref --stdin $type --no-deref <stdin 2>err &&
1742+
grep "fatal: symref-delete: missing <ref>" err
1743+
'
1744+
1745+
test_expect_success "stdin $type symref-delete fails deleting regular ref" '
1746+
test_when_finished "git update-ref -d refs/heads/regularref" &&
1747+
git update-ref refs/heads/regularref $a &&
1748+
format_command $type "symref-delete refs/heads/regularref" "$a" >stdin &&
1749+
test_must_fail git update-ref --stdin $type --no-deref <stdin 2>err &&
1750+
grep "fatal: cannot lock ref ${SQ}refs/heads/regularref${SQ}: expected symref with target ${SQ}$a${SQ}: but is a regular ref" err
1751+
'
1752+
1753+
test_expect_success "stdin $type symref-delete fails with too many arguments" '
1754+
format_command $type "symref-delete refs/heads/symref" "$a" "$a" >stdin &&
1755+
test_must_fail git update-ref --stdin $type --no-deref <stdin 2>err &&
1756+
if test "$type" = "-z"
1757+
then
1758+
grep "fatal: unknown command: $a" err
1759+
else
1760+
grep "fatal: symref-delete refs/heads/symref: extra input: $a" err
1761+
fi
1762+
'
1763+
1764+
test_expect_success "stdin $type symref-delete fails with wrong old value" '
1765+
format_command $type "symref-delete refs/heads/symref" "$m" >stdin &&
1766+
test_must_fail git update-ref --stdin $type --no-deref <stdin 2>err &&
1767+
grep "fatal: verifying symref target: ${SQ}refs/heads/symref${SQ}: is at $a but expected refs/heads/main" err &&
1768+
git symbolic-ref refs/heads/symref >expect &&
1769+
echo $a >actual &&
1770+
test_cmp expect actual
1771+
'
1772+
1773+
test_expect_success "stdin $type symref-delete works with right old value" '
1774+
format_command $type "symref-delete refs/heads/symref" "$a" >stdin &&
1775+
git update-ref --stdin $type --no-deref <stdin &&
1776+
test_must_fail git rev-parse --verify -q refs/heads/symref
1777+
'
1778+
1779+
test_expect_success "stdin $type symref-delete works with empty old value" '
1780+
git symbolic-ref refs/heads/symref $a >stdin &&
1781+
format_command $type "symref-delete refs/heads/symref" "" >stdin &&
1782+
git update-ref --stdin $type --no-deref <stdin &&
1783+
test_must_fail git rev-parse --verify -q $b
1784+
'
1785+
1786+
test_expect_success "stdin $type symref-delete succeeds for dangling reference" '
1787+
test_must_fail git symbolic-ref refs/heads/nonexistent &&
1788+
git symbolic-ref refs/heads/symref2 refs/heads/nonexistent &&
1789+
format_command $type "symref-delete refs/heads/symref2" "refs/heads/nonexistent" >stdin &&
1790+
git update-ref --stdin $type --no-deref <stdin &&
1791+
test_must_fail git symbolic-ref -d refs/heads/symref2
1792+
'
1793+
1794+
test_expect_success "stdin $type symref-delete deletes regular ref without target" '
1795+
git update-ref refs/heads/regularref $a &&
1796+
format_command $type "symref-delete refs/heads/regularref" >stdin &&
1797+
git update-ref --stdin $type --no-deref <stdin
1798+
'
1799+
17321800
done
17331801

17341802
test_done

t/t1416-ref-transaction-hooks.sh

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ test_expect_success 'hook gets all queued symref updates' '
162162
163163
git update-ref refs/heads/branch $POST_OID &&
164164
git symbolic-ref refs/heads/symref refs/heads/main &&
165+
git symbolic-ref refs/heads/symrefd refs/heads/main &&
165166
166167
test_hook reference-transaction <<-\EOF &&
167168
echo "$*" >>actual
@@ -171,16 +172,32 @@ test_expect_success 'hook gets all queued symref updates' '
171172
done >>actual
172173
EOF
173174
174-
cat >expect <<-EOF &&
175+
# In the files backend, "delete" also triggers an additional transaction
176+
# update on the packed-refs backend, which constitutes additional reflog
177+
# entries.
178+
if test_have_prereq REFFILES
179+
then
180+
cat >expect <<-EOF
181+
aborted
182+
$ZERO_OID $ZERO_OID refs/heads/symrefd
183+
EOF
184+
else
185+
>expect
186+
fi &&
187+
188+
cat >>expect <<-EOF &&
175189
prepared
176190
ref:refs/heads/main $ZERO_OID refs/heads/symref
191+
ref:refs/heads/main $ZERO_OID refs/heads/symrefd
177192
committed
178193
ref:refs/heads/main $ZERO_OID refs/heads/symref
194+
ref:refs/heads/main $ZERO_OID refs/heads/symrefd
179195
EOF
180196
181197
git update-ref --no-deref --stdin <<-EOF &&
182198
start
183199
symref-verify refs/heads/symref refs/heads/main
200+
symref-delete refs/heads/symrefd refs/heads/main
184201
prepare
185202
commit
186203
EOF

0 commit comments

Comments
 (0)