mirror of
https://github.com/git/git
synced 2024-11-05 18:59:29 +00:00
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 <ps@pks.im> Signed-off-by: Karthik Nayak <karthik.188@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
1451ac734f
commit
2343720967
8 changed files with 140 additions and 10 deletions
|
@ -65,6 +65,7 @@ performs all modifications together. Specify commands of the form:
|
|||
create SP <ref> SP <new-oid> LF
|
||||
delete SP <ref> [SP <old-oid>] LF
|
||||
verify SP <ref> [SP <old-oid>] LF
|
||||
symref-delete SP <ref> [SP <old-target>] LF
|
||||
symref-verify SP <ref> [SP <old-target>] LF
|
||||
option SP <opt> LF
|
||||
start LF
|
||||
|
@ -87,6 +88,7 @@ quoting:
|
|||
create SP <ref> NUL <new-oid> NUL
|
||||
delete SP <ref> NUL [<old-oid>] NUL
|
||||
verify SP <ref> NUL [<old-oid>] NUL
|
||||
symref-delete SP <ref> [NUL <old-target>] NUL
|
||||
symref-verify SP <ref> [NUL <old-target>] NUL
|
||||
option SP <opt> NUL
|
||||
start NUL
|
||||
|
@ -119,6 +121,9 @@ verify::
|
|||
Verify <ref> against <old-oid> but do not change it. If
|
||||
<old-oid> is zero or missing, the ref must not exist.
|
||||
|
||||
symref-delete::
|
||||
Delete <ref> after verifying it exists with <old-target>, if given.
|
||||
|
||||
symref-verify::
|
||||
Verify symbolic <ref> against <old-target> but do not change it.
|
||||
If <old-target> is missing, the ref must not exist. Can only be
|
||||
|
|
|
@ -1382,8 +1382,8 @@ static int prune_refs(struct display_state *display_state,
|
|||
if (!dry_run) {
|
||||
if (transaction) {
|
||||
for (ref = stale_refs; ref; ref = ref->next) {
|
||||
result = ref_transaction_delete(transaction, ref->name, NULL, 0,
|
||||
"fetch: prune", &err);
|
||||
result = ref_transaction_delete(transaction, ref->name, NULL,
|
||||
NULL, 0, "fetch: prune", &err);
|
||||
if (result)
|
||||
goto cleanup;
|
||||
}
|
||||
|
|
|
@ -1576,7 +1576,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)
|
|||
if (ref_transaction_delete(transaction,
|
||||
namespaced_name,
|
||||
old_oid,
|
||||
0, "push", &err)) {
|
||||
NULL, 0,
|
||||
"push", &err)) {
|
||||
rp_error("%s", err.buf);
|
||||
ret = "failed to delete";
|
||||
} else {
|
||||
|
|
|
@ -293,7 +293,7 @@ static void parse_cmd_delete(struct ref_transaction *transaction,
|
|||
|
||||
if (ref_transaction_delete(transaction, refname,
|
||||
have_old ? &old_oid : NULL,
|
||||
update_flags, msg, &err))
|
||||
NULL, update_flags, msg, &err))
|
||||
die("%s", err.buf);
|
||||
|
||||
update_flags = default_flags;
|
||||
|
@ -301,6 +301,36 @@ static void parse_cmd_delete(struct ref_transaction *transaction,
|
|||
strbuf_release(&err);
|
||||
}
|
||||
|
||||
|
||||
static void parse_cmd_symref_delete(struct ref_transaction *transaction,
|
||||
const char *next, const char *end)
|
||||
{
|
||||
struct strbuf err = STRBUF_INIT;
|
||||
char *refname, *old_target;
|
||||
|
||||
if (!(update_flags & REF_NO_DEREF))
|
||||
die("symref-delete: cannot operate with deref mode");
|
||||
|
||||
refname = parse_refname(&next);
|
||||
if (!refname)
|
||||
die("symref-delete: missing <ref>");
|
||||
|
||||
old_target = parse_next_refname(&next);
|
||||
|
||||
if (*next != line_termination)
|
||||
die("symref-delete %s: extra input: %s", refname, next);
|
||||
|
||||
if (ref_transaction_delete(transaction, refname, NULL,
|
||||
old_target, update_flags, msg, &err))
|
||||
die("%s", err.buf);
|
||||
|
||||
update_flags = default_flags;
|
||||
free(refname);
|
||||
free(old_target);
|
||||
strbuf_release(&err);
|
||||
}
|
||||
|
||||
|
||||
static void parse_cmd_verify(struct ref_transaction *transaction,
|
||||
const char *next, const char *end)
|
||||
{
|
||||
|
@ -443,6 +473,7 @@ static const struct parse_cmd {
|
|||
{ "create", parse_cmd_create, 2, UPDATE_REFS_OPEN },
|
||||
{ "delete", parse_cmd_delete, 2, UPDATE_REFS_OPEN },
|
||||
{ "verify", parse_cmd_verify, 2, UPDATE_REFS_OPEN },
|
||||
{ "symref-delete", parse_cmd_symref_delete, 2, UPDATE_REFS_OPEN },
|
||||
{ "symref-verify", parse_cmd_symref_verify, 2, UPDATE_REFS_OPEN },
|
||||
{ "option", parse_cmd_option, 1, UPDATE_REFS_OPEN },
|
||||
{ "start", parse_cmd_start, 0, UPDATE_REFS_STARTED },
|
||||
|
|
14
refs.c
14
refs.c
|
@ -979,7 +979,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg,
|
|||
transaction = ref_store_transaction_begin(refs, &err);
|
||||
if (!transaction ||
|
||||
ref_transaction_delete(transaction, refname, old_oid,
|
||||
flags, msg, &err) ||
|
||||
NULL, flags, msg, &err) ||
|
||||
ref_transaction_commit(transaction, &err)) {
|
||||
error("%s", err.buf);
|
||||
ref_transaction_free(transaction);
|
||||
|
@ -1317,14 +1317,20 @@ int ref_transaction_create(struct ref_transaction *transaction,
|
|||
int ref_transaction_delete(struct ref_transaction *transaction,
|
||||
const char *refname,
|
||||
const struct object_id *old_oid,
|
||||
unsigned int flags, const char *msg,
|
||||
const char *old_target,
|
||||
unsigned int flags,
|
||||
const char *msg,
|
||||
struct strbuf *err)
|
||||
{
|
||||
if (old_oid && is_null_oid(old_oid))
|
||||
BUG("delete called with old_oid set to zeros");
|
||||
if (old_oid && old_target)
|
||||
BUG("delete called with both old_oid and old_target set");
|
||||
if (old_target && !(flags & REF_NO_DEREF))
|
||||
BUG("delete cannot operate on symrefs with deref mode");
|
||||
return ref_transaction_update(transaction, refname,
|
||||
null_oid(), old_oid,
|
||||
NULL, NULL, flags,
|
||||
NULL, old_target, flags,
|
||||
msg, err);
|
||||
}
|
||||
|
||||
|
@ -2767,7 +2773,7 @@ int refs_delete_refs(struct ref_store *refs, const char *logmsg,
|
|||
|
||||
for_each_string_list_item(item, refnames) {
|
||||
ret = ref_transaction_delete(transaction, item->string,
|
||||
NULL, flags, msg, &err);
|
||||
NULL, NULL, flags, msg, &err);
|
||||
if (ret) {
|
||||
warning(_("could not delete reference %s: %s"),
|
||||
item->string, err.buf);
|
||||
|
|
4
refs.h
4
refs.h
|
@ -767,7 +767,9 @@ int ref_transaction_create(struct ref_transaction *transaction,
|
|||
int ref_transaction_delete(struct ref_transaction *transaction,
|
||||
const char *refname,
|
||||
const struct object_id *old_oid,
|
||||
unsigned int flags, const char *msg,
|
||||
const char *old_target,
|
||||
unsigned int flags,
|
||||
const char *msg,
|
||||
struct strbuf *err);
|
||||
|
||||
/*
|
||||
|
|
|
@ -1729,6 +1729,74 @@ do
|
|||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success "stdin $type symref-delete fails without --no-deref" '
|
||||
git symbolic-ref refs/heads/symref $a &&
|
||||
format_command $type "symref-delete refs/heads/symref" "$a" >stdin &&
|
||||
test_must_fail git update-ref --stdin $type <stdin 2>err &&
|
||||
grep "fatal: symref-delete: cannot operate with deref mode" err
|
||||
'
|
||||
|
||||
test_expect_success "stdin $type symref-delete fails with no ref" '
|
||||
format_command $type "symref-delete " >stdin &&
|
||||
test_must_fail git update-ref --stdin $type --no-deref <stdin 2>err &&
|
||||
grep "fatal: symref-delete: missing <ref>" err
|
||||
'
|
||||
|
||||
test_expect_success "stdin $type symref-delete fails deleting regular ref" '
|
||||
test_when_finished "git update-ref -d refs/heads/regularref" &&
|
||||
git update-ref refs/heads/regularref $a &&
|
||||
format_command $type "symref-delete refs/heads/regularref" "$a" >stdin &&
|
||||
test_must_fail git update-ref --stdin $type --no-deref <stdin 2>err &&
|
||||
grep "fatal: cannot lock ref ${SQ}refs/heads/regularref${SQ}: expected symref with target ${SQ}$a${SQ}: but is a regular ref" err
|
||||
'
|
||||
|
||||
test_expect_success "stdin $type symref-delete fails with too many arguments" '
|
||||
format_command $type "symref-delete refs/heads/symref" "$a" "$a" >stdin &&
|
||||
test_must_fail git update-ref --stdin $type --no-deref <stdin 2>err &&
|
||||
if test "$type" = "-z"
|
||||
then
|
||||
grep "fatal: unknown command: $a" err
|
||||
else
|
||||
grep "fatal: symref-delete refs/heads/symref: extra input: $a" err
|
||||
fi
|
||||
'
|
||||
|
||||
test_expect_success "stdin $type symref-delete fails with wrong old value" '
|
||||
format_command $type "symref-delete refs/heads/symref" "$m" >stdin &&
|
||||
test_must_fail git update-ref --stdin $type --no-deref <stdin 2>err &&
|
||||
grep "fatal: verifying symref target: ${SQ}refs/heads/symref${SQ}: is at $a but expected refs/heads/main" err &&
|
||||
git symbolic-ref refs/heads/symref >expect &&
|
||||
echo $a >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success "stdin $type symref-delete works with right old value" '
|
||||
format_command $type "symref-delete refs/heads/symref" "$a" >stdin &&
|
||||
git update-ref --stdin $type --no-deref <stdin &&
|
||||
test_must_fail git rev-parse --verify -q refs/heads/symref
|
||||
'
|
||||
|
||||
test_expect_success "stdin $type symref-delete works with empty old value" '
|
||||
git symbolic-ref refs/heads/symref $a >stdin &&
|
||||
format_command $type "symref-delete refs/heads/symref" "" >stdin &&
|
||||
git update-ref --stdin $type --no-deref <stdin &&
|
||||
test_must_fail git rev-parse --verify -q $b
|
||||
'
|
||||
|
||||
test_expect_success "stdin $type symref-delete succeeds for dangling reference" '
|
||||
test_must_fail git symbolic-ref refs/heads/nonexistent &&
|
||||
git symbolic-ref refs/heads/symref2 refs/heads/nonexistent &&
|
||||
format_command $type "symref-delete refs/heads/symref2" "refs/heads/nonexistent" >stdin &&
|
||||
git update-ref --stdin $type --no-deref <stdin &&
|
||||
test_must_fail git symbolic-ref -d refs/heads/symref2
|
||||
'
|
||||
|
||||
test_expect_success "stdin $type symref-delete deletes regular ref without target" '
|
||||
git update-ref refs/heads/regularref $a &&
|
||||
format_command $type "symref-delete refs/heads/regularref" >stdin &&
|
||||
git update-ref --stdin $type --no-deref <stdin
|
||||
'
|
||||
|
||||
done
|
||||
|
||||
test_done
|
||||
|
|
|
@ -162,6 +162,7 @@ test_expect_success 'hook gets all queued symref updates' '
|
|||
|
||||
git update-ref refs/heads/branch $POST_OID &&
|
||||
git symbolic-ref refs/heads/symref refs/heads/main &&
|
||||
git symbolic-ref refs/heads/symrefd refs/heads/main &&
|
||||
|
||||
test_hook reference-transaction <<-\EOF &&
|
||||
echo "$*" >>actual
|
||||
|
@ -171,16 +172,32 @@ test_expect_success 'hook gets all queued symref updates' '
|
|||
done >>actual
|
||||
EOF
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
# In the files backend, "delete" also triggers an additional transaction
|
||||
# update on the packed-refs backend, which constitutes additional reflog
|
||||
# entries.
|
||||
if test_have_prereq REFFILES
|
||||
then
|
||||
cat >expect <<-EOF
|
||||
aborted
|
||||
$ZERO_OID $ZERO_OID refs/heads/symrefd
|
||||
EOF
|
||||
else
|
||||
>expect
|
||||
fi &&
|
||||
|
||||
cat >>expect <<-EOF &&
|
||||
prepared
|
||||
ref:refs/heads/main $ZERO_OID refs/heads/symref
|
||||
ref:refs/heads/main $ZERO_OID refs/heads/symrefd
|
||||
committed
|
||||
ref:refs/heads/main $ZERO_OID refs/heads/symref
|
||||
ref:refs/heads/main $ZERO_OID refs/heads/symrefd
|
||||
EOF
|
||||
|
||||
git update-ref --no-deref --stdin <<-EOF &&
|
||||
start
|
||||
symref-verify refs/heads/symref refs/heads/main
|
||||
symref-delete refs/heads/symrefd refs/heads/main
|
||||
prepare
|
||||
commit
|
||||
EOF
|
||||
|
|
Loading…
Reference in a new issue