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:
commit
a9f388adb7
|
@ -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>.
|
||||
|
|
|
@ -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
|
||||
|
|
2
branch.c
2
branch.c
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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
78
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) ||
|
||||
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
23
refs.h
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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,
|
||||
¤t_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(¤t_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(¤t_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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
2
walker.c
2
walker.c
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue