Skip to content

Commit 1451ac7

Browse files
KarthikNayakgitster
authored andcommitted
update-ref: add support for 'symref-verify' command
The 'symref-verify' command allows users to verify if a provided <ref> contains the provided <old-target> without changing the <ref>. If <old-target> is not provided, the command will verify that the <ref> doesn't exist. The command allows users to verify symbolic refs within a transaction, and this means users can perform a set of changes in a transaction only when the verification holds good. Since we're checking for symbolic refs, this command will only work with the 'no-deref' mode. This is because any dereferenced symbolic ref will point to an object and not a ref and the regular 'verify' command can be used in such situations. Add required tests for symref support in 'verify'. Since we're here, also add reflog checks for the pre-existing 'verify' tests, there is no divergence from behavior, but we never tested to ensure that reflog wasn't affected by the 'verify' command. Helped-by: Patrick Steinhardt <[email protected]> Signed-off-by: Karthik Nayak <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent aa6e99f commit 1451ac7

File tree

6 files changed

+208
-15
lines changed

6 files changed

+208
-15
lines changed

Documentation/git-update-ref.txt

Lines changed: 7 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-verify SP <ref> [SP <old-target>] LF
6869
option SP <opt> LF
6970
start LF
7071
prepare LF
@@ -86,6 +87,7 @@ quoting:
8687
create SP <ref> NUL <new-oid> NUL
8788
delete SP <ref> NUL [<old-oid>] NUL
8889
verify SP <ref> NUL [<old-oid>] NUL
90+
symref-verify SP <ref> [NUL <old-target>] NUL
8991
option SP <opt> NUL
9092
start NUL
9193
prepare NUL
@@ -117,6 +119,11 @@ verify::
117119
Verify <ref> against <old-oid> but do not change it. If
118120
<old-oid> is zero or missing, the ref must not exist.
119121

122+
symref-verify::
123+
Verify symbolic <ref> against <old-target> but do not change it.
124+
If <old-target> is missing, the ref must not exist. Can only be
125+
used in `no-deref` mode.
126+
120127
option::
121128
Modify the behavior of the next command naming a <ref>.
122129
The only valid option is `no-deref` to avoid dereferencing

builtin/update-ref.c

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,29 @@ static char *parse_refname(const char **next)
7676
return strbuf_detach(&ref, NULL);
7777
}
7878

79+
/*
80+
* Wrapper around parse_refname which skips the next delimiter.
81+
*/
82+
static char *parse_next_refname(const char **next)
83+
{
84+
if (line_termination) {
85+
/* Without -z, consume SP and use next argument */
86+
if (!**next || **next == line_termination)
87+
return NULL;
88+
if (**next != ' ')
89+
die("expected SP but got: %s", *next);
90+
} else {
91+
/* With -z, read the next NUL-terminated line */
92+
if (**next)
93+
return NULL;
94+
}
95+
/* Skip the delimiter */
96+
(*next)++;
97+
98+
return parse_refname(next);
99+
}
100+
101+
79102
/*
80103
* The value being parsed is <old-oid> (as opposed to <new-oid>; the
81104
* difference affects which error messages are generated):
@@ -297,11 +320,47 @@ static void parse_cmd_verify(struct ref_transaction *transaction,
297320
die("verify %s: extra input: %s", refname, next);
298321

299322
if (ref_transaction_verify(transaction, refname, &old_oid,
300-
update_flags, &err))
323+
NULL, update_flags, &err))
324+
die("%s", err.buf);
325+
326+
update_flags = default_flags;
327+
free(refname);
328+
strbuf_release(&err);
329+
}
330+
331+
static void parse_cmd_symref_verify(struct ref_transaction *transaction,
332+
const char *next, const char *end)
333+
{
334+
struct strbuf err = STRBUF_INIT;
335+
struct object_id old_oid;
336+
char *refname, *old_target;
337+
338+
if (!(update_flags & REF_NO_DEREF))
339+
die("symref-verify: cannot operate with deref mode");
340+
341+
refname = parse_refname(&next);
342+
if (!refname)
343+
die("symref-verify: missing <ref>");
344+
345+
/*
346+
* old_ref is optional, if not provided, we need to ensure that the
347+
* ref doesn't exist.
348+
*/
349+
old_target = parse_next_refname(&next);
350+
if (!old_target)
351+
oidcpy(&old_oid, null_oid());
352+
353+
if (*next != line_termination)
354+
die("symref-verify %s: extra input: %s", refname, next);
355+
356+
if (ref_transaction_verify(transaction, refname,
357+
old_target ? NULL : &old_oid,
358+
old_target, update_flags, &err))
301359
die("%s", err.buf);
302360

303361
update_flags = default_flags;
304362
free(refname);
363+
free(old_target);
305364
strbuf_release(&err);
306365
}
307366

@@ -380,15 +439,16 @@ static const struct parse_cmd {
380439
unsigned args;
381440
enum update_refs_state state;
382441
} command[] = {
383-
{ "update", parse_cmd_update, 3, UPDATE_REFS_OPEN },
384-
{ "create", parse_cmd_create, 2, UPDATE_REFS_OPEN },
385-
{ "delete", parse_cmd_delete, 2, UPDATE_REFS_OPEN },
386-
{ "verify", parse_cmd_verify, 2, UPDATE_REFS_OPEN },
387-
{ "option", parse_cmd_option, 1, UPDATE_REFS_OPEN },
388-
{ "start", parse_cmd_start, 0, UPDATE_REFS_STARTED },
389-
{ "prepare", parse_cmd_prepare, 0, UPDATE_REFS_PREPARED },
390-
{ "abort", parse_cmd_abort, 0, UPDATE_REFS_CLOSED },
391-
{ "commit", parse_cmd_commit, 0, UPDATE_REFS_CLOSED },
442+
{ "update", parse_cmd_update, 3, UPDATE_REFS_OPEN },
443+
{ "create", parse_cmd_create, 2, UPDATE_REFS_OPEN },
444+
{ "delete", parse_cmd_delete, 2, UPDATE_REFS_OPEN },
445+
{ "verify", parse_cmd_verify, 2, UPDATE_REFS_OPEN },
446+
{ "symref-verify", parse_cmd_symref_verify, 2, UPDATE_REFS_OPEN },
447+
{ "option", parse_cmd_option, 1, UPDATE_REFS_OPEN },
448+
{ "start", parse_cmd_start, 0, UPDATE_REFS_STARTED },
449+
{ "prepare", parse_cmd_prepare, 0, UPDATE_REFS_PREPARED },
450+
{ "abort", parse_cmd_abort, 0, UPDATE_REFS_CLOSED },
451+
{ "commit", parse_cmd_commit, 0, UPDATE_REFS_CLOSED },
392452
};
393453

394454
static void update_refs_stdin(void)

refs.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,14 +1331,19 @@ int ref_transaction_delete(struct ref_transaction *transaction,
13311331
int ref_transaction_verify(struct ref_transaction *transaction,
13321332
const char *refname,
13331333
const struct object_id *old_oid,
1334+
const char *old_target,
13341335
unsigned int flags,
13351336
struct strbuf *err)
13361337
{
1337-
if (!old_oid)
1338-
BUG("verify called with old_oid set to NULL");
1338+
if (!old_target && !old_oid)
1339+
BUG("verify called with old_oid and old_target set to NULL");
1340+
if (old_oid && old_target)
1341+
BUG("verify called with both old_oid and old_target set");
1342+
if (old_target && !(flags & REF_NO_DEREF))
1343+
BUG("verify cannot operate on symrefs with deref mode");
13391344
return ref_transaction_update(transaction, refname,
13401345
NULL, old_oid,
1341-
NULL, NULL,
1346+
NULL, old_target,
13421347
flags, NULL, err);
13431348
}
13441349

refs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,7 @@ int ref_transaction_delete(struct ref_transaction *transaction,
781781
int ref_transaction_verify(struct ref_transaction *transaction,
782782
const char *refname,
783783
const struct object_id *old_oid,
784+
const char *old_target,
784785
unsigned int flags,
785786
struct strbuf *err);
786787

t/t1400-update-ref.sh

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -890,17 +890,23 @@ test_expect_success 'stdin update/create/verify combination works' '
890890
'
891891

892892
test_expect_success 'stdin verify succeeds for correct value' '
893+
test-tool ref-store main for-each-reflog-ent $m >before &&
893894
git rev-parse $m >expect &&
894895
echo "verify $m $m" >stdin &&
895896
git update-ref --stdin <stdin &&
896897
git rev-parse $m >actual &&
897-
test_cmp expect actual
898+
test_cmp expect actual &&
899+
test-tool ref-store main for-each-reflog-ent $m >after &&
900+
test_cmp before after
898901
'
899902

900903
test_expect_success 'stdin verify succeeds for missing reference' '
904+
test-tool ref-store main for-each-reflog-ent $m >before &&
901905
echo "verify refs/heads/missing $Z" >stdin &&
902906
git update-ref --stdin <stdin &&
903-
test_must_fail git rev-parse --verify -q refs/heads/missing
907+
test_must_fail git rev-parse --verify -q refs/heads/missing &&
908+
test-tool ref-store main for-each-reflog-ent $m >after &&
909+
test_cmp before after
904910
'
905911

906912
test_expect_success 'stdin verify treats no value as missing' '
@@ -1641,4 +1647,88 @@ test_expect_success PIPE 'transaction flushes status updates' '
16411647
test_cmp expected actual
16421648
'
16431649

1650+
format_command () {
1651+
if test "$1" = "-z"
1652+
then
1653+
shift
1654+
printf "$F" "$@"
1655+
else
1656+
echo "$@"
1657+
fi
1658+
}
1659+
1660+
for type in "" "-z"
1661+
do
1662+
1663+
test_expect_success "stdin $type symref-verify fails without --no-deref" '
1664+
git symbolic-ref refs/heads/symref $a &&
1665+
format_command $type "symref-verify refs/heads/symref" "$a" >stdin &&
1666+
test_must_fail git update-ref --stdin $type <stdin 2>err &&
1667+
grep "fatal: symref-verify: cannot operate with deref mode" err
1668+
'
1669+
1670+
test_expect_success "stdin $type symref-verify fails with too many arguments" '
1671+
format_command $type "symref-verify refs/heads/symref" "$a" "$a" >stdin &&
1672+
test_must_fail git update-ref --stdin $type --no-deref <stdin 2>err &&
1673+
if test "$type" = "-z"
1674+
then
1675+
grep "fatal: unknown command: $a" err
1676+
else
1677+
grep "fatal: symref-verify refs/heads/symref: extra input: $a" err
1678+
fi
1679+
'
1680+
1681+
test_expect_success "stdin $type symref-verify succeeds for correct value" '
1682+
git symbolic-ref refs/heads/symref >expect &&
1683+
test-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&
1684+
format_command $type "symref-verify refs/heads/symref" "$a" >stdin &&
1685+
git update-ref --stdin $type --no-deref <stdin &&
1686+
git symbolic-ref refs/heads/symref >actual &&
1687+
test_cmp expect actual &&
1688+
test-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&
1689+
test_cmp before after
1690+
'
1691+
1692+
test_expect_success "stdin $type symref-verify fails with no value" '
1693+
git symbolic-ref refs/heads/symref >expect &&
1694+
format_command $type "symref-verify refs/heads/symref" "" >stdin &&
1695+
test_must_fail git update-ref --stdin $type --no-deref <stdin
1696+
'
1697+
1698+
test_expect_success "stdin $type symref-verify succeeds for dangling reference" '
1699+
test_when_finished "git symbolic-ref -d refs/heads/symref2" &&
1700+
test_must_fail git symbolic-ref refs/heads/nonexistent &&
1701+
git symbolic-ref refs/heads/symref2 refs/heads/nonexistent &&
1702+
format_command $type "symref-verify refs/heads/symref2" "refs/heads/nonexistent" >stdin &&
1703+
git update-ref --stdin $type --no-deref <stdin
1704+
'
1705+
1706+
test_expect_success "stdin $type symref-verify fails for missing reference" '
1707+
test-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&
1708+
format_command $type "symref-verify refs/heads/missing" "refs/heads/unknown" >stdin &&
1709+
test_must_fail git update-ref --stdin $type --no-deref <stdin 2>err &&
1710+
grep "fatal: cannot lock ref ${SQ}refs/heads/missing${SQ}: unable to resolve reference ${SQ}refs/heads/missing${SQ}" err &&
1711+
test_must_fail git rev-parse --verify -q refs/heads/missing &&
1712+
test-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&
1713+
test_cmp before after
1714+
'
1715+
1716+
test_expect_success "stdin $type symref-verify fails for wrong value" '
1717+
git symbolic-ref refs/heads/symref >expect &&
1718+
format_command $type "symref-verify refs/heads/symref" "$b" >stdin &&
1719+
test_must_fail git update-ref --stdin $type --no-deref <stdin &&
1720+
git symbolic-ref refs/heads/symref >actual &&
1721+
test_cmp expect actual
1722+
'
1723+
1724+
test_expect_success "stdin $type symref-verify fails for mistaken null value" '
1725+
git symbolic-ref refs/heads/symref >expect &&
1726+
format_command $type "symref-verify refs/heads/symref" "$Z" >stdin &&
1727+
test_must_fail git update-ref --stdin $type --no-deref <stdin &&
1728+
git symbolic-ref refs/heads/symref >actual &&
1729+
test_cmp expect actual
1730+
'
1731+
1732+
done
1733+
16441734
test_done

t/t1416-ref-transaction-hooks.sh

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,34 @@ test_expect_success 'hook captures git-symbolic-ref updates' '
157157
test_cmp expect actual
158158
'
159159

160+
test_expect_success 'hook gets all queued symref updates' '
161+
test_when_finished "rm actual" &&
162+
163+
git update-ref refs/heads/branch $POST_OID &&
164+
git symbolic-ref refs/heads/symref refs/heads/main &&
165+
166+
test_hook reference-transaction <<-\EOF &&
167+
echo "$*" >>actual
168+
while read -r line
169+
do
170+
printf "%s\n" "$line"
171+
done >>actual
172+
EOF
173+
174+
cat >expect <<-EOF &&
175+
prepared
176+
ref:refs/heads/main $ZERO_OID refs/heads/symref
177+
committed
178+
ref:refs/heads/main $ZERO_OID refs/heads/symref
179+
EOF
180+
181+
git update-ref --no-deref --stdin <<-EOF &&
182+
start
183+
symref-verify refs/heads/symref refs/heads/main
184+
prepare
185+
commit
186+
EOF
187+
test_cmp expect actual
188+
'
189+
160190
test_done

0 commit comments

Comments
 (0)