1
0
Fork 0
mirror of https://github.com/git/git.git synced 2024-05-09 18:26:08 +02:00

Merge branch 'kn/update-ref-symrefs' into seen

"update-ref" learns to also handle symbolic refs.

The design adds unnecessary ambiguities and needs rethought.
cf. <20240423220308.GC1172807@coredump.intra.peff.net>

* kn/update-ref-symrefs:
  SQUASH??? (sparse fix)
  ref: support symrefs in 'reference-transaction' hook
  update-ref: support symrefs in the update command
  update-ref: support symrefs in the create command
  update-ref: support symrefs in the delete command
  update-ref: support symrefs in the verify command
  files-backend: extract out `create_symref_lock`
  update-ref: support parsing ref targets in `parse_next_oid`
  refs: accept symref values in `ref_transaction[_add]_update`
This commit is contained in:
Junio C Hamano 2024-04-26 09:28:43 -07:00
commit a9f388adb7
20 changed files with 817 additions and 105 deletions

View File

@ -61,10 +61,10 @@ still contains <old-oid>.
With `--stdin`, update-ref reads instructions from standard input and
performs all modifications together. Specify commands of the form:
update SP <ref> SP <new-oid> [SP <old-oid>] LF
create SP <ref> SP <new-oid> LF
delete SP <ref> [SP <old-oid>] LF
verify SP <ref> [SP <old-oid>] LF
update SP <ref> SP (<new-oid> | ref:<new-target>) [SP (<old-oid> | ref:<old-target>)] LF
create SP <ref> SP (<new-oid> | ref:<new-target>) LF
delete SP <ref> [SP (<old-oid> | ref:<old-target>)] LF
verify SP <ref> [SP (<old-oid> | ref:<old-target>)] LF
option SP <opt> LF
start LF
prepare LF
@ -82,10 +82,10 @@ specify a missing value, omit the value and its preceding SP entirely.
Alternatively, use `-z` to specify in NUL-terminated format, without
quoting:
update SP <ref> NUL <new-oid> NUL [<old-oid>] NUL
create SP <ref> NUL <new-oid> NUL
delete SP <ref> NUL [<old-oid>] NUL
verify SP <ref> NUL [<old-oid>] NUL
update SP <ref> NUL (<new-oid> | ref:<new-target>) NUL [(<old-oid> | ref:<old-target>)] NUL
create SP <ref> NUL (<new-oid> | ref:<new-target>) NUL
delete SP <ref> NUL [(<old-oid> | ref:<old-target>)] NUL
verify SP <ref> NUL [(<old-oid> | ref:<old-target>)] NUL
option SP <opt> NUL
start NUL
prepare NUL
@ -95,6 +95,12 @@ quoting:
In this format, use 40 "0" to specify a zero value, and use the empty
string to specify a missing value.
For commands which support it, substituting the <old-oid> value with
ref:<old-target> will ensure that the <ref> targets the specified
old-target before the update. Similarly, substituting the <new-oid>
with ref:<new-target> will ensure that the <ref> is a symbolic ref
targeting the new-target after the update.
In either format, values can be specified in any form that Git
recognizes as an object name. Commands in any other format or a
repeated <ref> produce an error. Command meanings are:
@ -103,19 +109,28 @@ update::
Set <ref> to <new-oid> after verifying <old-oid>, if given.
Specify a zero <new-oid> to ensure the ref does not exist
after the update and/or a zero <old-oid> to make sure the
ref does not exist before the update.
ref does not exist before the update. If ref:<old-target>
is provided, we verify that the <ref> is an existing symbolic
ref which targets <old-target>. If ref:<new-target> is given,
the update ensures <ref> is a symbolic ref which targets
<new-target>.
create::
Create <ref> with <new-oid> after verifying it does not
exist. The given <new-oid> may not be zero.
exist. The given <new-oid> may not be zero. If instead
ref:<new-target> is provided, a symbolic ref is created
which targets <new-target>.
delete::
Delete <ref> after verifying it exists with <old-oid>, if
given. If given, <old-oid> may not be zero.
Delete <ref> after verifying it exists with <old-oid>, if given.
If given, <old-oid> may not be zero. If instead, ref:<old-target>
is provided, verify that the symbolic ref <ref> targets
<old-target> before deleting it.
verify::
Verify <ref> against <old-oid> but do not change it. If
<old-oid> is zero or missing, the ref must not exist.
<old-oid> is zero or missing, the ref must not exist. For
verifying symbolic refs, provide ref:<old-target>.
option::
Modify the behavior of the next command naming a <ref>.

View File

@ -486,7 +486,7 @@ reference-transaction
This hook is invoked by any Git command that performs reference
updates. It executes whenever a reference transaction is prepared,
committed or aborted and may thus get called multiple times. The hook
does not cover symbolic references (but that may change in the future).
also cover symbolic references.
The hook takes exactly one argument, which is the current state the
given reference transaction is in:
@ -503,16 +503,20 @@ given reference transaction is in:
For each reference update that was added to the transaction, the hook
receives on standard input a line of the format:
<old-oid> SP <new-oid> SP <ref-name> LF
<old-value> SP <new-value> SP <ref-name> LF
where `<old-oid>` is the old object name passed into the reference
transaction, `<new-oid>` is the new object name to be stored in the
where `<old-value>` is the old object name passed into the reference
transaction, `<new-value>` is the new object name to be stored in the
ref and `<ref-name>` is the full name of the ref. When force updating
the reference regardless of its current value or when the reference is
to be created anew, `<old-oid>` is the all-zeroes object name. To
to be created anew, `<old-value>` is the all-zeroes object name. To
distinguish these cases, you can inspect the current value of
`<ref-name>` via `git rev-parse`.
For symbolic reference updates the `<old_value>` and `<new-value>`
fields could denote references instead of objects, denoted via the
`ref:<ref-target>` format.
The exit status of the hook is ignored for any state except for the
"prepared" state. In the "prepared" state, a non-zero exit status will
cause the transaction to be aborted. The hook will not be called with

View File

@ -627,7 +627,7 @@ void create_branch(struct repository *r,
if (!transaction ||
ref_transaction_update(transaction, ref.buf,
&oid, forcing ? NULL : null_oid(),
0, msg, &err) ||
NULL, NULL, 0, msg, &err) ||
ref_transaction_commit(transaction, &err))
die("%s", err.buf);
ref_transaction_free(transaction);

View File

@ -546,7 +546,7 @@ static void write_remote_refs(const struct ref *local_refs)
if (!r->peer_ref)
continue;
if (ref_transaction_create(t, r->peer_ref->name, &r->old_oid,
0, NULL, &err))
NULL, 0, NULL, &err))
die("%s", err.buf);
}

View File

@ -1634,7 +1634,7 @@ static int update_branch(struct branch *b)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, b->name, &b->oid, &old_oid,
0, msg, &err) ||
NULL, NULL, 0, msg, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
error("%s", err.buf);
@ -1675,7 +1675,8 @@ static void dump_tags(void)
strbuf_addf(&ref_name, "refs/tags/%s", t->name);
if (ref_transaction_update(transaction, ref_name.buf,
&t->oid, NULL, 0, msg, &err)) {
&t->oid, NULL, NULL, NULL,
0, msg, &err)) {
failure |= error("%s", err.buf);
goto cleanup;
}

View File

@ -668,7 +668,7 @@ static int s_update_ref(const char *action,
ret = ref_transaction_update(transaction, ref->name, &ref->new_oid,
check_old ? &ref->old_oid : NULL,
0, msg, &err);
NULL, NULL, 0, msg, &err);
if (ret) {
ret = STORE_REF_ERROR_OTHER;
goto out;
@ -1383,7 +1383,7 @@ static int prune_refs(struct display_state *display_state,
if (transaction) {
for (ref = stale_refs; ref; ref = ref->next) {
result = ref_transaction_delete(transaction, ref->name, NULL, 0,
"fetch: prune", &err);
NULL, "fetch: prune", &err);
if (result)
goto cleanup;
}

View File

@ -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)) {
0, NULL,
"push", &err)) {
rp_error("%s", err.buf);
ret = "failed to delete";
} else {
@ -1595,6 +1596,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
if (ref_transaction_update(transaction,
namespaced_name,
new_oid, old_oid,
NULL, NULL,
0, "push",
&err)) {
rp_error("%s", err.buf);

View File

@ -201,7 +201,7 @@ static int replace_object_oid(const char *object_ref,
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, repl, &prev,
0, NULL, &err) ||
NULL, NULL, 0, NULL, &err) ||
ref_transaction_commit(transaction, &err))
res = error("%s", err.buf);

View File

@ -660,6 +660,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, &object, &prev,
NULL, NULL,
create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
reflog_msg.buf, &err) ||
ref_transaction_commit(transaction, &err)) {

View File

@ -88,6 +88,11 @@ static char *parse_refname(const char **next)
*/
#define PARSE_SHA1_ALLOW_EMPTY 0x02
/*
* Parse refname targets using the ref:<ref_target> format.
*/
#define PARSE_REFNAME_TARGETS 0x04
/*
* Parse an argument separator followed by the next argument, if any.
* If there is an argument, convert it to a SHA-1, write it to sha1,
@ -95,10 +100,13 @@ static char *parse_refname(const char **next)
* return 0. If there is no argument at all (not even the empty
* string), return 1 and leave *next unchanged. If the value is
* provided but cannot be converted to a SHA-1, die. flags can
* include PARSE_SHA1_OLD and/or PARSE_SHA1_ALLOW_EMPTY.
* include PARSE_SHA1_OLD and/or PARSE_SHA1_ALLOW_EMPTY and/or
* PARSE_REFNAME_TARGETS. When PARSE_REFNAME_TARGETS is set, parse
* the argument as `ref:<refname>` and store the refname into
* the target strbuf.
*/
static int parse_next_oid(const char **next, const char *end,
struct object_id *oid,
static int parse_next_arg(const char **next, const char *end,
struct object_id *oid, struct strbuf *target,
const char *command, const char *refname,
int flags)
{
@ -118,8 +126,17 @@ static int parse_next_oid(const char **next, const char *end,
(*next)++;
*next = parse_arg(*next, &arg);
if (arg.len) {
if (repo_get_oid(the_repository, arg.buf, oid))
goto invalid;
if (repo_get_oid(the_repository, arg.buf, oid)) {
const char *value;
if (flags & PARSE_REFNAME_TARGETS &&
skip_prefix(arg.buf, "ref:", &value)) {
if (check_refname_format(value, REFNAME_ALLOW_ONELEVEL))
die("invalid ref format: %s", value);
strbuf_addstr(target, value);
} else {
goto invalid;
}
}
} else {
/* Without -z, an empty value means all zeros: */
oidclr(oid);
@ -136,8 +153,17 @@ static int parse_next_oid(const char **next, const char *end,
*next += arg.len;
if (arg.len) {
if (repo_get_oid(the_repository, arg.buf, oid))
goto invalid;
if (repo_get_oid(the_repository, arg.buf, oid)) {
const char *value;
if (flags & PARSE_REFNAME_TARGETS &&
skip_prefix(arg.buf, "ref:", &value)) {
if (check_refname_format(value, REFNAME_ALLOW_ONELEVEL))
die("invalid ref format: %s", value);
strbuf_addstr(target, value);
} else {
goto invalid;
}
}
} else if (flags & PARSE_SHA1_ALLOW_EMPTY) {
/* With -z, treat an empty value as all zeros: */
warning("%s %s: missing <new-oid>, treating as zero",
@ -184,6 +210,8 @@ static void parse_cmd_update(struct ref_transaction *transaction,
const char *next, const char *end)
{
struct strbuf err = STRBUF_INIT;
struct strbuf new_target = STRBUF_INIT;
struct strbuf old_target = STRBUF_INIT;
char *refname;
struct object_id new_oid, old_oid;
int have_old;
@ -192,18 +220,24 @@ static void parse_cmd_update(struct ref_transaction *transaction,
if (!refname)
die("update: missing <ref>");
if (parse_next_oid(&next, end, &new_oid, "update", refname,
PARSE_SHA1_ALLOW_EMPTY))
if (parse_next_arg(&next, end, &new_oid,
&new_target, "update", refname,
PARSE_SHA1_ALLOW_EMPTY | PARSE_REFNAME_TARGETS))
die("update %s: missing <new-oid>", refname);
have_old = !parse_next_oid(&next, end, &old_oid, "update", refname,
PARSE_SHA1_OLD);
have_old = !parse_next_arg(&next, end, &old_oid,
&old_target, "update", refname,
PARSE_SHA1_OLD | PARSE_REFNAME_TARGETS);
have_old = have_old && !old_target.len;
if (*next != line_termination)
die("update %s: extra input: %s", refname, next);
if (ref_transaction_update(transaction, refname,
&new_oid, have_old ? &old_oid : NULL,
new_target.len ? NULL : &new_oid,
have_old ? &old_oid : NULL,
new_target.len ? new_target.buf : NULL,
old_target.len ? old_target.buf : NULL,
update_flags | create_reflog_flag,
msg, &err))
die("%s", err.buf);
@ -211,12 +245,15 @@ static void parse_cmd_update(struct ref_transaction *transaction,
update_flags = default_flags;
free(refname);
strbuf_release(&err);
strbuf_release(&old_target);
strbuf_release(&new_target);
}
static void parse_cmd_create(struct ref_transaction *transaction,
const char *next, const char *end)
{
struct strbuf err = STRBUF_INIT;
struct strbuf new_target = STRBUF_INIT;
char *refname;
struct object_id new_oid;
@ -224,16 +261,22 @@ static void parse_cmd_create(struct ref_transaction *transaction,
if (!refname)
die("create: missing <ref>");
if (parse_next_oid(&next, end, &new_oid, "create", refname, 0))
if (parse_next_arg(&next, end, &new_oid, &new_target,
"create", refname, PARSE_REFNAME_TARGETS))
die("create %s: missing <new-oid>", refname);
if (is_null_oid(&new_oid))
if (!new_target.len && is_null_oid(&new_oid))
die("create %s: zero <new-oid>", refname);
if (new_target.len && !(update_flags & REF_NO_DEREF))
die("create %s: cannot create symrefs in deref mode", refname);
if (*next != line_termination)
die("create %s: extra input: %s", refname, next);
if (ref_transaction_create(transaction, refname, &new_oid,
if (ref_transaction_create(transaction, refname,
new_target.len ? NULL : &new_oid ,
new_target.len ? new_target.buf : NULL,
update_flags | create_reflog_flag,
msg, &err))
die("%s", err.buf);
@ -241,12 +284,14 @@ static void parse_cmd_create(struct ref_transaction *transaction,
update_flags = default_flags;
free(refname);
strbuf_release(&err);
strbuf_release(&new_target);
}
static void parse_cmd_delete(struct ref_transaction *transaction,
const char *next, const char *end)
{
struct strbuf err = STRBUF_INIT;
struct strbuf old_target = STRBUF_INIT;
char *refname;
struct object_id old_oid;
int have_old;
@ -255,32 +300,40 @@ static void parse_cmd_delete(struct ref_transaction *transaction,
if (!refname)
die("delete: missing <ref>");
if (parse_next_oid(&next, end, &old_oid, "delete", refname,
PARSE_SHA1_OLD)) {
if (parse_next_arg(&next, end, &old_oid, &old_target,
"delete", refname, PARSE_SHA1_OLD |
PARSE_REFNAME_TARGETS)) {
have_old = 0;
} else {
if (is_null_oid(&old_oid))
if (!old_target.len && is_null_oid(&old_oid))
die("delete %s: zero <old-oid>", refname);
have_old = 1;
have_old = 1 && !old_target.len;
}
if (old_target.len && !(update_flags & REF_NO_DEREF))
die("delete %s: cannot operate on symrefs in deref mode", refname);
if (*next != line_termination)
die("delete %s: extra input: %s", refname, next);
if (ref_transaction_delete(transaction, refname,
have_old ? &old_oid : NULL,
update_flags, msg, &err))
update_flags,
old_target.len ? old_target.buf : NULL,
msg, &err))
die("%s", err.buf);
update_flags = default_flags;
free(refname);
strbuf_release(&err);
strbuf_release(&old_target);
}
static void parse_cmd_verify(struct ref_transaction *transaction,
const char *next, const char *end)
{
struct strbuf err = STRBUF_INIT;
struct strbuf old_target = STRBUF_INIT;
char *refname;
struct object_id old_oid;
@ -288,20 +341,27 @@ static void parse_cmd_verify(struct ref_transaction *transaction,
if (!refname)
die("verify: missing <ref>");
if (parse_next_oid(&next, end, &old_oid, "verify", refname,
PARSE_SHA1_OLD))
if (parse_next_arg(&next, end, &old_oid, &old_target,
"verify", refname,
PARSE_SHA1_OLD | PARSE_REFNAME_TARGETS))
oidclr(&old_oid);
if (old_target.len && !(update_flags & REF_NO_DEREF))
die("verify %s: cannot operate on symrefs in deref mode", refname);
if (*next != line_termination)
die("verify %s: extra input: %s", refname, next);
if (ref_transaction_verify(transaction, refname, &old_oid,
if (ref_transaction_verify(transaction, refname,
old_target.len ? NULL : &old_oid,
old_target.len ? old_target.buf : NULL,
update_flags, &err))
die("%s", err.buf);
update_flags = default_flags;
free(refname);
strbuf_release(&err);
strbuf_release(&old_target);
}
static void report_ok(const char *command)

78
refs.c
View File

@ -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) ||
flags, NULL, msg, &err) ||
ref_transaction_commit(transaction, &err)) {
error("%s", err.buf);
ref_transaction_free(transaction);
@ -1217,6 +1217,8 @@ void ref_transaction_free(struct ref_transaction *transaction)
for (i = 0; i < transaction->nr; i++) {
free(transaction->updates[i]->msg);
free((void *)transaction->updates[i]->old_target);
free((void *)transaction->updates[i]->new_target);
free(transaction->updates[i]);
}
free(transaction->updates);
@ -1228,6 +1230,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
const char *new_target, const char *old_target,
const char *msg)
{
struct ref_update *update;
@ -1235,15 +1238,24 @@ struct ref_update *ref_transaction_add_update(
if (transaction->state != REF_TRANSACTION_OPEN)
BUG("update called for transaction that is not open");
if (old_oid && !is_null_oid(old_oid) && old_target)
BUG("Only one of old_oid and old_target should be non NULL");
if (new_oid && !is_null_oid(new_oid) && new_target)
BUG("Only one of new_oid and new_target should be non NULL");
FLEX_ALLOC_STR(update, refname, refname);
ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
transaction->updates[transaction->nr++] = update;
update->flags = flags;
if (flags & REF_HAVE_NEW)
if (new_target)
update->new_target = xstrdup(new_target);
if (old_target)
update->old_target = xstrdup(old_target);
if (new_oid && flags & REF_HAVE_NEW)
oidcpy(&update->new_oid, new_oid);
if (flags & REF_HAVE_OLD)
if (old_oid && flags & REF_HAVE_OLD)
oidcpy(&update->old_oid, old_oid);
update->msg = normalize_reflog_message(msg);
return update;
@ -1253,6 +1265,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
const char *new_target,
const char *old_target,
unsigned int flags, const char *msg,
struct strbuf *err)
{
@ -1278,49 +1292,64 @@ int ref_transaction_update(struct ref_transaction *transaction,
flags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;
flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);
ref_transaction_add_update(transaction, refname, flags,
new_oid, old_oid, msg);
new_oid, old_oid, new_target,
old_target, msg);
return 0;
}
int ref_transaction_create(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const char *new_target,
unsigned int flags, const char *msg,
struct strbuf *err)
{
if (!new_oid || is_null_oid(new_oid)) {
strbuf_addf(err, "'%s' has a null OID", refname);
if ((!new_oid || is_null_oid(new_oid)) && !new_target) {
strbuf_addf(err, "'%s' has a null OID or no new target", refname);
return 1;
}
if (new_target && !(flags & REF_NO_DEREF))
BUG("create cannot operate on symrefs with deref mode");
return ref_transaction_update(transaction, refname, new_oid,
null_oid(), flags, msg, err);
null_oid(), new_target, NULL, flags,
msg, err);
}
int ref_transaction_delete(struct ref_transaction *transaction,
const char *refname,
const struct object_id *old_oid,
unsigned int flags, const char *msg,
unsigned int flags,
const char *old_target,
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_target && !(flags & REF_NO_DEREF))
BUG("delete cannot operate on symrefs with deref mode");
return ref_transaction_update(transaction, refname,
null_oid(), old_oid,
flags, msg, err);
NULL, old_target, flags,
msg, err);
}
int ref_transaction_verify(struct ref_transaction *transaction,
const char *refname,
const struct object_id *old_oid,
const char *old_target,
unsigned int flags,
struct strbuf *err)
{
if (!old_oid)
BUG("verify called with old_oid set to NULL");
if (!old_target && !old_oid)
BUG("verify called with old_oid and old_target set to NULL");
if (old_target && !(flags & REF_NO_DEREF))
BUG("verify cannot operate on symrefs with deref mode");
return ref_transaction_update(transaction, refname,
NULL, old_oid,
NULL, old_target,
flags, NULL, err);
}
@ -1335,8 +1364,8 @@ int refs_update_ref(struct ref_store *refs, const char *msg,
t = ref_store_transaction_begin(refs, &err);
if (!t ||
ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,
&err) ||
ref_transaction_update(t, refname, new_oid, old_oid, NULL, NULL,
flags, msg, &err) ||
ref_transaction_commit(t, &err)) {
ret = 1;
ref_transaction_free(t);
@ -2336,12 +2365,19 @@ static int run_transaction_hook(struct ref_transaction *transaction,
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
strbuf_reset(&buf);
strbuf_addf(&buf, "%s %s %s\n",
oid_to_hex(&update->old_oid),
oid_to_hex(&update->new_oid),
update->refname);
if (update->flags & REF_HAVE_OLD && update->old_target)
strbuf_addf(&buf, "ref:%s ", update->old_target);
else
strbuf_addf(&buf, "%s ", oid_to_hex(&update->old_oid));
if (update->flags & REF_HAVE_NEW && update->new_target)
strbuf_addf(&buf, "ref:%s ", update->new_target);
else
strbuf_addf(&buf, "%s ", oid_to_hex(&update->new_oid));
strbuf_addf(&buf, "%s\n", update->refname);
if (write_in_full(proc.in, buf.buf, buf.len) < 0) {
if (errno != EPIPE) {
@ -2724,7 +2760,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, flags, NULL, msg, &err);
if (ret) {
warning(_("could not delete reference %s: %s"),
item->string, err.buf);
@ -2790,3 +2826,7 @@ int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg
{
return refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
}
int ref_update_is_null_new_value(struct ref_update *update) {
return !update->new_target && is_null_oid(&update->new_oid);
}

23
refs.h
View File

@ -648,6 +648,15 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
* before the update. A copy of this value is made in the
* transaction.
*
* new_target -- the target reference that the reference will be
* update to point to. This takes precedence over new_oid when
* set. If the reference is a regular reference, it will be
* converted to a symbolic reference.
*
* old_target -- the reference that the reference must be pointing to.
* Will only be taken into account when the reference is a symbolic
* reference.
*
* flags -- flags affecting the update, passed to
* update_ref_lock(). Possible flags: REF_NO_DEREF,
* REF_FORCE_CREATE_REFLOG. See those constants for more
@ -713,7 +722,11 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
* beforehand. The old value is checked after the lock is taken to
* prevent races. If the old value doesn't agree with old_oid, the
* whole transaction fails. If old_oid is NULL, then the previous
* value is not checked.
* value is not checked. If `old_target` is not NULL, treat the reference
* as a symbolic ref and validate that its target before the update is
* `old_target`. If the `new_target` is not NULL, then the reference
* will be updated to a symbolic ref which targets `new_target`.
* Together, these allow us to update between regular refs and symrefs.
*
* See the above comment "Reference transaction updates" for more
* information.
@ -722,6 +735,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
const char *new_target,
const char *old_target,
unsigned int flags, const char *msg,
struct strbuf *err);
@ -737,6 +752,7 @@ int ref_transaction_update(struct ref_transaction *transaction,
int ref_transaction_create(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const char *new_target,
unsigned int flags, const char *msg,
struct strbuf *err);
@ -751,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,
unsigned int flags,
const char *old_target,
const char *msg,
struct strbuf *err);
/*
@ -765,6 +783,7 @@ int ref_transaction_delete(struct ref_transaction *transaction,
int ref_transaction_verify(struct ref_transaction *transaction,
const char *refname,
const struct object_id *old_oid,
const char *old_target,
unsigned int flags,
struct strbuf *err);

View File

@ -1198,7 +1198,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
ref_transaction_add_update(
transaction, r->name,
REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,
null_oid(), &r->oid, NULL);
null_oid(), &r->oid, NULL, NULL, NULL);
if (ref_transaction_commit(transaction, &err))
goto cleanup;
@ -1292,7 +1292,7 @@ static int files_pack_refs(struct ref_store *ref_store,
* packed-refs transaction:
*/
if (ref_transaction_update(transaction, iter->refname,
iter->oid, NULL,
iter->oid, NULL, NULL, NULL,
REF_NO_DEREF, NULL, &err))
die("failure preparing to create packed reference %s: %s",
iter->refname, err.buf);
@ -1920,26 +1920,39 @@ static void update_symref_reflog(struct files_ref_store *refs,
}
}
static int create_symref_locked(struct files_ref_store *refs,
struct ref_lock *lock, const char *refname,
const char *target, const char *logmsg)
static int create_symref_lock(struct files_ref_store *refs,
struct ref_lock *lock, const char *refname,
const char *target)
{
if (!fdopen_lock_file(&lock->lk, "w"))
return error("unable to fdopen %s: %s",
get_lock_file_path(&lock->lk), strerror(errno));
/* no error check; commit_ref will check ferror */
fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target);
return 0;
}
static int create_and_commit_symref(struct files_ref_store *refs,
struct ref_lock *lock, const char *refname,
const char *target, const char *logmsg)
{
int ret;
if (prefer_symlink_refs && !create_ref_symlink(lock, target)) {
update_symref_reflog(refs, lock, refname, target, logmsg);
return 0;
}
if (!fdopen_lock_file(&lock->lk, "w"))
return error("unable to fdopen %s: %s",
get_lock_file_path(&lock->lk), strerror(errno));
ret = create_symref_lock(refs, lock, refname, target);
if (!ret) {
update_symref_reflog(refs, lock, refname, target, logmsg);
update_symref_reflog(refs, lock, refname, target, logmsg);
if (commit_ref(lock) < 0)
return error("unable to write symref for %s: %s", refname,
strerror(errno));
}
/* no error check; commit_ref will check ferror */
fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target);
if (commit_ref(lock) < 0)
return error("unable to write symref for %s: %s", refname,
strerror(errno));
return 0;
}
@ -1960,7 +1973,8 @@ static int files_create_symref(struct ref_store *ref_store,
return -1;
}
ret = create_symref_locked(refs, lock, refname, target, logmsg);
ret = create_and_commit_symref(refs, lock, refname, target, logmsg);
unlock_ref(lock);
return ret;
}
@ -2309,7 +2323,7 @@ static int split_head_update(struct ref_update *update,
transaction, "HEAD",
update->flags | REF_LOG_ONLY | REF_NO_DEREF,
&update->new_oid, &update->old_oid,
update->msg);
NULL, NULL, update->msg);
/*
* Add "HEAD". This insertion is O(N) in the transaction
@ -2372,6 +2386,7 @@ static int split_symref_update(struct ref_update *update,
new_update = ref_transaction_add_update(
transaction, referent, new_flags,
&update->new_oid, &update->old_oid,
update->new_target, update->old_target,
update->msg);
new_update->parent_update = update;
@ -2411,6 +2426,37 @@ static const char *original_update_refname(struct ref_update *update)
return update->refname;
}
/*
* Check whether the REF_HAVE_OLD and old_target values stored in
* update are consistent with ref, which is the symbolic reference's
* current value. If everything is OK, return 0; otherwise, write an
* error message to err and return -1.
*/
static int check_old_target(struct ref_update *update, char *ref,
struct strbuf *err)
{
if (!(update->flags & REF_HAVE_OLD) ||
!strcmp(update->old_target, ref))
return 0;
if (!strcmp(update->old_target, ""))
strbuf_addf(err, "cannot lock ref '%s': "
"reference already exists",
original_update_refname(update));
else if (!strcmp(ref, ""))
strbuf_addf(err, "cannot lock ref '%s': "
"reference is missing but expected %s",
original_update_refname(update),
update->old_target);
else
strbuf_addf(err, "cannot lock ref '%s': "
"is at %s but expected %s",
original_update_refname(update),
ref, update->old_target);
return -1;
}
/*
* Check whether the REF_HAVE_OLD and old_oid values stored in update
* are consistent with oid, which is the reference's current value. If
@ -2471,7 +2517,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
files_assert_main_repository(refs, "lock_ref_for_update");
if ((update->flags & REF_HAVE_NEW) && is_null_oid(&update->new_oid))
if ((update->flags & REF_HAVE_NEW) && ref_update_is_null_new_value(update))
update->flags |= REF_DELETING;
if (head_ref) {
@ -2514,6 +2560,18 @@ static int lock_ref_for_update(struct files_ref_store *refs,
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
}
/*
* For symref verification, we need to check the reference value
* rather than the oid. If we're dealing with regular refs or we're
* verifying a dereferenced symref, we then check the oid.
*/
if (update->old_target) {
if (check_old_target(update, referent.buf, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
} else if (check_old_oid(update, &lock->old_oid, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto out;
@ -2553,9 +2611,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,
}
}
if ((update->flags & REF_HAVE_NEW) &&
!(update->flags & REF_DELETING) &&
!(update->flags & REF_LOG_ONLY)) {
if (update->new_target && !(update->flags & REF_LOG_ONLY)) {
if (create_symref_lock(refs, lock, update->refname, update->new_target)) {
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
if (close_ref_gently(lock)) {
strbuf_addf(err, "couldn't close '%s.lock'",
update->refname);
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
/*
* Once we have created the symref lock, the commit
* phase of the transaction only needs to commit the lock.
*/
update->flags |= REF_NEEDS_COMMIT;
} else if ((update->flags & REF_HAVE_NEW) &&
!(update->flags & REF_DELETING) &&
!(update->flags & REF_LOG_ONLY)) {
if (!(update->type & REF_ISSYMREF) &&
oideq(&lock->old_oid, &update->new_oid)) {
/*
@ -2763,7 +2839,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
packed_transaction, update->refname,
REF_HAVE_NEW | REF_NO_DEREF,
&update->new_oid, NULL,
NULL);
NULL, NULL, NULL);
}
}
@ -2848,6 +2924,18 @@ static int files_transaction_finish(struct ref_store *ref_store,
if (update->flags & REF_NEEDS_COMMIT ||
update->flags & REF_LOG_ONLY) {
if (update->new_target) {
/*
* We want to get the resolved OID for the target, to ensure
* that the correct value is added to the reflog.
*/
if (!refs_resolve_ref_unsafe(&refs->base, update->new_target,
RESOLVE_REF_READING, &update->new_oid, NULL)) {
/* for dangling symrefs we gracefully set the oid to zero */
update->new_oid = *null_oid();
}
}
if (files_log_ref_write(refs,
lock->ref_name,
&lock->old_oid,
@ -2865,6 +2953,15 @@ static int files_transaction_finish(struct ref_store *ref_store,
goto cleanup;
}
}
/*
* We try creating a symlink, if that succeeds we continue to the
* next updated. If not, we try and create a regular symref.
*/
if (update->new_target && prefer_symlink_refs)
if (!create_ref_symlink(lock, update->new_target))
continue;
if (update->flags & REF_NEEDS_COMMIT) {
clear_loose_ref_cache(refs);
if (commit_ref(lock)) {
@ -3048,7 +3145,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
ref_transaction_add_update(packed_transaction, update->refname,
update->flags & ~REF_HAVE_OLD,
&update->new_oid, &update->old_oid,
NULL);
NULL, NULL, NULL);
}
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {

View File

@ -124,6 +124,18 @@ struct ref_update {
*/
struct object_id old_oid;
/*
* If set, point the reference to this value. This can also be
* used to convert regular references to become symbolic refs.
*/
const char *new_target;
/*
* If set and the reference is a symbolic ref, check that the
* reference previously pointed to this value.
*/
const char *old_target;
/*
* One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,
* REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.
@ -173,6 +185,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
const char *new_target, const char *old_target,
const char *msg);
/*
@ -735,4 +748,11 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo,
*/
struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);
/*
* Helper function to check if the new value is null, this
* takes into consideration that the update could be a regular
* ref or a symbolic ref.
*/
int ref_update_is_null_new_value(struct ref_update *update);
#endif /* REFS_REFS_INTERNAL_H */

View File

@ -827,7 +827,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
new_update = ref_transaction_add_update(
transaction, "HEAD",
u->flags | REF_LOG_ONLY | REF_NO_DEREF,
&u->new_oid, &u->old_oid, u->msg);
&u->new_oid, &u->old_oid, NULL, NULL, u->msg);
string_list_insert(&affected_refnames, new_update->refname);
}
@ -854,7 +854,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* There is no need to write the reference deletion
* when the reference in question doesn't exist.
*/
if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {
if (u->flags & REF_HAVE_NEW && !ref_update_is_null_new_value(u)) {
ret = queue_transaction_update(refs, tx_data, u,
&current_oid, err);
if (ret)
@ -906,7 +906,8 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
*/
new_update = ref_transaction_add_update(
transaction, referent.buf, new_flags,
&u->new_oid, &u->old_oid, u->msg);
&u->new_oid, &u->old_oid, u->new_target,
u->old_target, u->msg);
new_update->parent_update = u;
/*
@ -936,7 +937,26 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* individual refs. But the error messages match what the files
* backend returns, which keeps our tests happy.
*/
if (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {
if ((u->flags & REF_HAVE_OLD) && u->old_target) {
if (strcmp(referent.buf, u->old_target)) {
if (!strcmp(u->old_target, ""))
strbuf_addf(err, "verifying symref target: '%s': "
"provided target is empty",
original_update_refname(u));
else if (!strcmp(referent.buf, ""))
strbuf_addf(err, "verifying symref target: '%s': "
"reference is missing but expected %s",
original_update_refname(u),
u->old_target);
else
strbuf_addf(err, "verifying symref target: '%s': "
"is at %s but expected %s",
original_update_refname(u),
referent.buf, u->old_target);
ret = -1;
goto done;
}
} else if (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {
if (is_null_oid(&u->old_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
"reference already exists"),
@ -1047,7 +1067,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
* - `core.logAllRefUpdates` tells us to create the reflog for
* the given ref.
*/
if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {
if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && ref_update_is_null_new_value(u)) {
struct reftable_log_record log = {0};
struct reftable_iterator it = {0};
@ -1089,6 +1109,12 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
should_write_log(&arg->refs->base, u->refname))) {
struct reftable_log_record *log;
if (u->new_target)
if (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,
RESOLVE_REF_READING, &u->new_oid, NULL))
/* for dangling symrefs we gracefully set the oid to zero */
u->new_oid = *null_oid();
ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
log = &logs[logs_nr++];
memset(log, 0, sizeof(*log));
@ -1105,7 +1131,18 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
if (u->flags & REF_LOG_ONLY)
continue;
if (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {
if (u->flags & REF_HAVE_NEW && u->new_target) {
struct reftable_ref_record ref = {
.refname = (char *)u->refname,
.value_type = REFTABLE_REF_SYMREF,
.value.symref = (char *)u->new_target,
.update_index = ts,
};
ret = reftable_writer_add_ref(writer, &ref);
if (ret < 0)
goto done;
} else if (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {
struct reftable_ref_record ref = {
.refname = (char *)u->refname,
.update_index = ts,

View File

@ -662,7 +662,7 @@ static int fast_forward_to(struct repository *r,
if (!transaction ||
ref_transaction_update(transaction, "HEAD",
to, unborn && !is_rebase_i(opts) ?
null_oid() : from,
null_oid() : from, NULL, NULL,
0, sb.buf, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
@ -1295,7 +1295,7 @@ int update_head_with_reflog(const struct commit *old_head,
if (!transaction ||
ref_transaction_update(transaction, "HEAD", new_head,
old_head ? &old_head->object.oid : null_oid(),
0, sb.buf, err) ||
NULL, NULL, 0, sb.buf, err) ||
ref_transaction_commit(transaction, err)) {
ret = -1;
}
@ -3863,8 +3863,9 @@ static int do_label(struct repository *r, const char *name, int len)
} else if (repo_get_oid(r, "HEAD", &head_oid)) {
error(_("could not read HEAD"));
ret = -1;
} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,
NULL, 0, msg.buf, &err) < 0 ||
} else if (ref_transaction_update(transaction, ref_name.buf,
&head_oid, NULL, NULL, NULL,
0, msg.buf, &err) < 0 ||
ref_transaction_commit(transaction, &err)) {
error("%s", err.buf);
ret = -1;

View File

@ -468,4 +468,36 @@ test_expect_success POSIXPERM 'git reflog expire honors core.sharedRepository' '
esac
'
test_expect_success SYMLINKS 'symref transaction supports symlinks' '
test_when_finished "git symbolic-ref -d TESTSYMREFONE" &&
git update-ref refs/heads/new @ &&
test_config core.prefersymlinkrefs true &&
cat >stdin <<-EOF &&
start
create TESTSYMREFONE ref:refs/heads/new
prepare
commit
EOF
git update-ref --no-deref --stdin <stdin &&
test_path_is_symlink .git/TESTSYMREFONE &&
test "$(test_readlink .git/TESTSYMREFONE)" = refs/heads/new
'
test_expect_success 'symref transaction supports false symlink config' '
test_when_finished "git symbolic-ref -d TESTSYMREFONE" &&
git update-ref refs/heads/new @ &&
test_config core.prefersymlinkrefs false &&
cat >stdin <<-EOF &&
start
create TESTSYMREFONE ref:refs/heads/new
prepare
commit
EOF
git update-ref --no-deref --stdin <stdin &&
test_path_is_file .git/TESTSYMREFONE &&
git symbolic-ref TESTSYMREFONE >actual &&
echo refs/heads/new >expect &&
test_cmp expect actual
'
test_done

View File

@ -890,17 +890,23 @@ test_expect_success 'stdin update/create/verify combination works' '
'
test_expect_success 'stdin verify succeeds for correct value' '
test-tool ref-store main for-each-reflog-ent $m >before &&
git rev-parse $m >expect &&
echo "verify $m $m" >stdin &&
git update-ref --stdin <stdin &&
git rev-parse $m >actual &&
test_cmp expect actual
test_cmp expect actual &&
test-tool ref-store main for-each-reflog-ent $m >after &&
test_cmp before after
'
test_expect_success 'stdin verify succeeds for missing reference' '
test-tool ref-store main for-each-reflog-ent $m >before &&
echo "verify refs/heads/missing $Z" >stdin &&
git update-ref --stdin <stdin &&
test_must_fail git rev-parse --verify -q refs/heads/missing
test_must_fail git rev-parse --verify -q refs/heads/missing &&
test-tool ref-store main for-each-reflog-ent $m >after &&
test_cmp before after
'
test_expect_success 'stdin verify treats no value as missing' '
@ -1354,6 +1360,7 @@ test_expect_success 'fails with duplicate HEAD update' '
'
test_expect_success 'fails with duplicate ref update via symref' '
test_when_finished "git symbolic-ref -d refs/heads/symref2" &&
git branch target2 $A &&
git symbolic-ref refs/heads/symref2 refs/heads/target2 &&
cat >stdin <<-EOF &&
@ -1641,4 +1648,339 @@ test_expect_success PIPE 'transaction flushes status updates' '
test_cmp expected actual
'
create_stdin_buf () {
if test "$1" = "-z"
then
shift
printf "$F" "$@" >stdin
else
echo "$@" >stdin
fi
}
for type in "" "-z"
do
test_expect_success "stdin ${type} verify symref fails without --no-deref" '
git symbolic-ref refs/heads/symref $a &&
create_stdin_buf ${type} "verify refs/heads/symref" "ref:$a" &&
test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
grep "fatal: verify refs/heads/symref: cannot operate on symrefs in deref mode" err
'
test_expect_success "stdin ${type} verify symref fails with too many arguments" '
create_stdin_buf ${type} "verify refs/heads/symref" "ref:$a" "ref:$a" &&
test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
if test "$type" = "-z"
then
grep "fatal: unknown command: ref:$a" err
else
grep "fatal: verify refs/heads/symref: extra input: ref:$a" err
fi
'
test_expect_success "stdin ${type} verify symref succeeds for correct value" '
git symbolic-ref refs/heads/symref >expect &&
test-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&
create_stdin_buf ${type} "verify refs/heads/symref" "ref:$a" &&
git update-ref --stdin ${type} --no-deref <stdin &&
git symbolic-ref refs/heads/symref >actual &&
test_cmp expect actual &&
test-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&
test_cmp before after
'
test_expect_success "stdin ${type} verify symref succeeds for missing reference" '
test-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&
create_stdin_buf ${type} "verify refs/heads/missing" "$Z" &&
git update-ref --stdin ${type} --no-deref <stdin &&
test_must_fail git rev-parse --verify -q refs/heads/missing &&
test-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&
test_cmp before after
'
test_expect_success "stdin ${type} verify symref fails for wrong value" '
git symbolic-ref refs/heads/symref >expect &&
create_stdin_buf ${type} "verify refs/heads/symref" "$b" &&
test_must_fail git update-ref --stdin ${type} --no-deref <stdin &&
git symbolic-ref refs/heads/symref >actual &&
test_cmp expect actual
'
test_expect_success "stdin ${type} verify symref fails for mistaken null value" '
git symbolic-ref refs/heads/symref >expect &&
create_stdin_buf ${type} "verify refs/heads/symref" "$Z" &&
test_must_fail git update-ref --stdin ${type} --no-deref <stdin &&
git symbolic-ref refs/heads/symref >actual &&
test_cmp expect actual
'
test_expect_success "stdin ${type} delete symref fails without --no-deref" '
git symbolic-ref refs/heads/symref $a &&
create_stdin_buf ${type} "delete refs/heads/symref" "ref:$a" &&
test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
grep "fatal: delete refs/heads/symref: cannot operate on symrefs in deref mode" err
'
test_expect_success "stdin ${type} delete symref fails with no ref" '
create_stdin_buf ${type} "delete " &&
test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
grep "fatal: delete: missing <ref>" err
'
test_expect_success "stdin ${type} delete symref fails with too many arguments" '
create_stdin_buf ${type} "delete refs/heads/symref" "ref:$a" "ref:$a" &&
test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
if test "$type" = "-z"
then
grep "fatal: unknown command: ref:$a" err
else
grep "fatal: delete refs/heads/symref: extra input: ref:$a" err
fi
'
test_expect_success "stdin ${type} delete symref fails with wrong old value" '
create_stdin_buf ${type} "delete refs/heads/symref" "ref:$m" &&
test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
if test_have_prereq REFTABLE
then
grep "fatal: verifying symref target: ${SQ}refs/heads/symref${SQ}: is at $a but expected refs/heads/main" err
else
grep "fatal: cannot lock ref ${SQ}refs/heads/symref${SQ}" err
fi &&
git symbolic-ref refs/heads/symref >expect &&
echo $a >actual &&
test_cmp expect actual
'
test_expect_success "stdin ${type} delete symref works with right old value" '
create_stdin_buf ${type} "delete refs/heads/symref" "ref:$a" &&
git update-ref --stdin ${type} --no-deref <stdin &&
test_must_fail git rev-parse --verify -q $b
'
test_expect_success "stdin ${type} create symref fails without --no-deref" '
create_stdin_buf ${type} "create refs/heads/symref" "ref:$a" &&
test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
grep "fatal: create refs/heads/symref: cannot create symrefs in deref mode" err
'
test_expect_success "stdin ${type} create symref fails with too many arguments" '
create_stdin_buf ${type} "create refs/heads/symref" "ref:$a" "ref:$a" >stdin &&
test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
if test "$type" = "-z"
then
grep "fatal: unknown command: ref:$a" err
else
grep "fatal: create refs/heads/symref: extra input: ref:$a" err
fi
'
test_expect_success "stdin ${type} create symref ref works" '
test_when_finished "git symbolic-ref -d refs/heads/symref" &&
create_stdin_buf ${type} "create refs/heads/symref" "ref:$a" >stdin &&
git update-ref --stdin ${type} --no-deref <stdin &&
git symbolic-ref refs/heads/symref >expect &&
echo $a >actual &&
test_cmp expect actual
'
test_expect_success "stdin ${type} create dangling symref ref works" '
test_when_finished "git symbolic-ref -d refs/heads/symref" &&
create_stdin_buf ${type} "create refs/heads/symref" "ref:refs/heads/unkown" >stdin &&
git update-ref --stdin ${type} --no-deref <stdin &&
git symbolic-ref refs/heads/symref >expect &&
echo refs/heads/unkown >actual &&
test_cmp expect actual
'
test_expect_success "stdin ${type} create symref does not create reflogs by default" '
test_when_finished "git symbolic-ref -d refs/symref" &&
create_stdin_buf ${type} "create refs/symref" "ref:$a" >stdin &&
git update-ref --stdin ${type} --no-deref <stdin &&
git symbolic-ref refs/symref >expect &&
echo $a >actual &&
test_cmp expect actual &&
test_must_fail git reflog exists refs/symref
'
test_expect_success "stdin ${type} create symref reflogs with --create-reflog" '
test_when_finished "git symbolic-ref -d refs/heads/symref" &&
create_stdin_buf ${type} "create refs/heads/symref" "ref:$a" >stdin &&
git update-ref --create-reflog --stdin ${type} --no-deref <stdin &&
git symbolic-ref refs/heads/symref >expect &&
echo $a >actual &&
test_cmp expect actual &&
git reflog exists refs/heads/symref
'
test_expect_success "stdin ${type} update symref fails with too many arguments" '
create_stdin_buf ${type} "update refs/heads/symref" "ref:$a" "ref:$a" "ref:$a" >stdin &&
test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
if test "$type" = "-z"
then
grep "fatal: unknown command: ref:$a" err
else
grep "fatal: update refs/heads/symref: extra input: ref:$a" err
fi
'
test_expect_success "stdin ${type} update creates symref with zero old value" '
test_when_finished "git symbolic-ref -d refs/heads/symref" &&
create_stdin_buf ${type} "update refs/heads/symref" "ref:$a" "$Z" >stdin &&
git update-ref --stdin ${type} --no-deref <stdin &&
echo $a >expect &&
git symbolic-ref refs/heads/symref >actual &&
test_cmp expect actual
'
test_expect_success "stdin ${type} update creates symref with empty old value" '
test_when_finished "git symbolic-ref -d refs/heads/symref" &&
create_stdin_buf ${type} "update refs/heads/symref" "ref:$a" "" >stdin &&
git update-ref --stdin ${type} --no-deref <stdin &&
echo $a >expect &&
git symbolic-ref refs/heads/symref >actual &&
test_cmp expect actual
'
test_expect_success "stdin ${type} update symref fails with wrong old value" '
test_when_finished "git symbolic-ref -d refs/heads/symref" &&
git symbolic-ref refs/heads/symref $a &&
create_stdin_buf ${type} "update refs/heads/symref" "ref:$m" "ref:$b" >stdin &&
test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
if test_have_prereq REFTABLE
then
grep "fatal: verifying symref target: ${SQ}refs/heads/symref${SQ}: is at $a but expected $b" err
else
grep "fatal: cannot lock ref ${SQ}refs/heads/symref${SQ}: is at $a but expected $b" err
fi &&
test_must_fail git rev-parse --verify -q $c
'
test_expect_success "stdin ${type} update symref works with right old value" '
test_when_finished "git symbolic-ref -d refs/heads/symref" &&
git symbolic-ref refs/heads/symref $a &&
create_stdin_buf ${type} "update refs/heads/symref" "ref:$m" "ref:$a" >stdin &&
git update-ref --stdin ${type} --no-deref <stdin &&
echo $m >expect &&
git symbolic-ref refs/heads/symref >actual &&
test_cmp expect actual
'
test_expect_success "stdin ${type} update creates symref (with deref)" '
test_when_finished "git symbolic-ref -d refs/heads/symref" &&
create_stdin_buf ${type} "update refs/heads/symref" "ref:$a" "" >stdin &&
git update-ref --stdin ${type} <stdin &&
echo $a >expect &&
git symbolic-ref --no-recurse refs/heads/symref >actual &&
test_cmp expect actual &&
test-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&
grep "$Z $(git rev-parse $a)" actual
'
test_expect_success "stdin ${type} update regular ref to symref with correct old-oid" '
test_when_finished "git symbolic-ref -d --no-recurse refs/heads/regularref" &&
git update-ref --no-deref refs/heads/regularref $a &&
create_stdin_buf ${type} "update refs/heads/regularref" "ref:$a" "$(git rev-parse $a)" >stdin &&
git update-ref --stdin ${type} <stdin &&
echo $a >expect &&
git symbolic-ref --no-recurse refs/heads/regularref >actual &&
test_cmp expect actual &&
test-tool ref-store main for-each-reflog-ent refs/heads/regularref >actual &&
grep "$(git rev-parse $a) $(git rev-parse $a)" actual
'
test_expect_success "stdin ${type} update regular ref to symref fails with wrong old-oid" '
test_when_finished "git update-ref -d refs/heads/regularref" &&
git update-ref --no-deref refs/heads/regularref $a &&
create_stdin_buf ${type} "update refs/heads/regularref" "ref:$a" "$(git rev-parse refs/heads/target2)" >stdin &&
test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
echo $(git rev-parse $a) >expect &&
git rev-parse refs/heads/regularref >actual &&
test_cmp expect actual
'
test_expect_success "stdin ${type} update existing symref with zero old-oid" '
test_when_finished "git symbolic-ref -d --no-recurse refs/heads/symref" &&
git symbolic-ref refs/heads/symref refs/heads/target2 &&
create_stdin_buf ${type} "update refs/heads/symref" "ref:$a" "$Z" >stdin &&
test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
grep "fatal: cannot lock ref ${SQ}refs/heads/symref${SQ}: reference already exists" err &&
echo refs/heads/target2 >expect &&
git symbolic-ref refs/heads/symref >actual &&
test_cmp expect actual
'
test_expect_success "stdin ${type} update existing symref to regular ref" '
test_when_finished "git update-ref -d refs/heads/symref" &&
git symbolic-ref refs/heads/symref refs/heads/target2 &&
create_stdin_buf ${type} "update refs/heads/symref" "$(git rev-parse $a)" "ref:refs/heads/target2" >stdin &&
git update-ref --stdin ${type} --no-deref <stdin &&
echo $(git rev-parse $a) >expect &&
git rev-parse refs/heads/symref >actual &&
test_cmp expect actual &&
test-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&
grep "$(git rev-parse refs/heads/target2) $(git rev-parse $a)" actual
'
done
test_expect_success "stdin update symref (with deref)" '
test_when_finished "git symbolic-ref -d refs/heads/symref" &&
test_when_finished "git update-ref -d --no-deref refs/heads/symref2" &&
git update-ref refs/heads/symref2 $a &&
git symbolic-ref --no-recurse refs/heads/symref refs/heads/symref2 &&
echo "update refs/heads/symref" "ref:$a" >stdin &&
git update-ref --stdin <stdin &&
echo $a >expect &&
git symbolic-ref --no-recurse refs/heads/symref2 >actual &&
test_cmp expect actual &&
echo refs/heads/symref2 >expect &&
git symbolic-ref --no-recurse refs/heads/symref >actual &&
test_cmp expect actual &&
test-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&
grep "$(git rev-parse $a) $(git rev-parse $a)" actual
'
test_expect_success "stdin -z update symref (with deref)" '
test_when_finished "git symbolic-ref -d refs/heads/symref" &&
test_when_finished "git update-ref -d --no-deref refs/heads/symref2" &&
git update-ref refs/heads/symref2 $a &&
git symbolic-ref --no-recurse refs/heads/symref refs/heads/symref2 &&
printf "$F" "update refs/heads/symref" "ref:$a" "" >stdin &&
git update-ref --stdin -z <stdin &&
echo $a >expect &&
git symbolic-ref --no-recurse refs/heads/symref2 >actual &&
test_cmp expect actual &&
echo refs/heads/symref2 >expect &&
git symbolic-ref --no-recurse refs/heads/symref >actual &&
test_cmp expect actual &&
test-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&
grep "$(git rev-parse $a) $(git rev-parse $a)" actual
'
test_expect_success "stdin update regular ref to symref" '
test_when_finished "git symbolic-ref -d --no-recurse refs/heads/regularref" &&
git update-ref --no-deref refs/heads/regularref $a &&
echo "update refs/heads/regularref" "ref:$a" >stdin &&
git update-ref --stdin <stdin &&
echo $a >expect &&
git symbolic-ref --no-recurse refs/heads/regularref >actual &&
test_cmp expect actual &&
test-tool ref-store main for-each-reflog-ent refs/heads/regularref >actual &&
grep "$(git rev-parse $a) $(git rev-parse $a)" actual
'
test_expect_success "stdin -z update regular ref to symref" '
test_when_finished "git symbolic-ref -d --no-recurse refs/heads/regularref" &&
git update-ref --no-deref refs/heads/regularref $a &&
printf "$F" "update refs/heads/regularref" "ref:$a" "" >stdin &&
git update-ref --stdin -z <stdin &&
echo $a >expect &&
git symbolic-ref --no-recurse refs/heads/regularref >actual &&
test_cmp expect actual &&
test-tool ref-store main for-each-reflog-ent refs/heads/regularref >actual &&
grep "$(git rev-parse $a) $(git rev-parse $a)" actual
'
test_done

View File

@ -134,4 +134,45 @@ test_expect_success 'interleaving hook calls succeed' '
test_cmp expect target-repo.git/actual
'
# This test doesn't add a check for symref 'delete' since there is a
# variation between the ref backends WRT 'delete'. In the files backend,
# 'delete' also triggers an additional transaction update on the
# packed-refs backend, which constitutes additional reflog entries.
test_expect_success 'hook gets all queued symref updates' '
test_when_finished "rm actual" &&
git update-ref refs/heads/branch $POST_OID &&
git symbolic-ref refs/heads/symref refs/heads/main &&
git symbolic-ref refs/heads/symrefu refs/heads/main &&
test_hook reference-transaction <<-\EOF &&
echo "$*" >>actual
while read -r line
do
printf "%s\n" "$line"
done >>actual
EOF
cat >expect <<-EOF &&
prepared
ref:refs/heads/main $ZERO_OID refs/heads/symref
$ZERO_OID ref:refs/heads/main refs/heads/symrefc
ref:refs/heads/main ref:refs/heads/branch refs/heads/symrefu
committed
ref:refs/heads/main $ZERO_OID refs/heads/symref
$ZERO_OID ref:refs/heads/main refs/heads/symrefc
ref:refs/heads/main ref:refs/heads/branch refs/heads/symrefu
EOF
git update-ref --no-deref --stdin <<-EOF &&
start
verify refs/heads/symref ref:refs/heads/main
create refs/heads/symrefc ref:refs/heads/main
update refs/heads/symrefu ref:refs/heads/branch ref:refs/heads/main
prepare
commit
EOF
test_cmp expect actual
'
test_done

View File

@ -324,7 +324,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,
strbuf_reset(&refname);
strbuf_addf(&refname, "refs/%s", write_ref[i]);
if (ref_transaction_update(transaction, refname.buf,
oids + i, NULL, 0,
oids + i, NULL, NULL, NULL, 0,
msg ? msg : "fetch (unknown)",
&err)) {
error("%s", err.buf);