Merge branch 'kn/ref-transaction-symref' into kn/update-ref-symref

* kn/ref-transaction-symref:
  refs: remove `create_symref` and associated dead code
  refs: rename `refs_create_symref()` to `refs_update_symref()`
  refs: use transaction in `refs_create_symref()`
  refs: add support for transactional symref updates
  refs: move `original_update_refname` to 'refs.c'
  refs: support symrefs in 'reference-transaction' hook
  files-backend: extract out `create_symref_lock()`
  refs: accept symref values in `ref_transaction_update()`
This commit is contained in:
Junio C Hamano 2024-05-23 09:38:59 -07:00
commit 939d49e9bd
22 changed files with 375 additions and 281 deletions

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 supports symbolic reference updates.
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. A reference will be
denoted with a 'ref:' prefix, like `ref:<ref-target>`.
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

@ -555,7 +555,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,
continue;
refs = get_worktree_ref_store(worktrees[i]);
if (refs_create_symref(refs, "HEAD", newref, logmsg))
if (refs_update_symref(refs, "HEAD", newref, logmsg))
ret = error(_("HEAD of working tree %s is not updated"),
worktrees[i]->path);
}

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;

View file

@ -1595,6 +1595,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

@ -204,6 +204,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,
if (ref_transaction_update(transaction, refname,
&new_oid, have_old ? &old_oid : NULL,
NULL, NULL,
update_flags | create_reflog_flag,
msg, &err))
die("%s", err.buf);

View file

@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,
ret = refs_update_ref(wt_refs, NULL, "HEAD", &commit->object.oid,
NULL, 0, UPDATE_REFS_MSG_ON_ERR);
else
ret = refs_create_symref(wt_refs, "HEAD", symref.buf, NULL);
ret = refs_update_symref(wt_refs, "HEAD", symref.buf, NULL);
if (ret)
goto done;

119
refs.c
View file

@ -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((char *)transaction->updates[i]->new_target);
free((char *)transaction->updates[i]->old_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,16 +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 && old_target)
BUG("only one of old_oid and old_target should be non NULL");
if (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)
update->new_target = xstrdup_or_null(new_target);
update->old_target = xstrdup_or_null(old_target);
if ((flags & REF_HAVE_NEW) && new_oid)
oidcpy(&update->new_oid, new_oid);
if (flags & REF_HAVE_OLD)
if ((flags & REF_HAVE_OLD) && old_oid)
oidcpy(&update->old_oid, old_oid);
update->msg = normalize_reflog_message(msg);
return update;
}
@ -1253,6 +1264,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,9 +1291,11 @@ 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;
}
@ -1295,7 +1310,8 @@ int ref_transaction_create(struct ref_transaction *transaction,
return 1;
}
return ref_transaction_update(transaction, refname, new_oid,
null_oid(), flags, msg, err);
null_oid(), NULL, NULL, flags,
msg, err);
}
int ref_transaction_delete(struct ref_transaction *transaction,
@ -1308,7 +1324,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,
BUG("delete called with old_oid set to zeros");
return ref_transaction_update(transaction, refname,
null_oid(), old_oid,
flags, msg, err);
NULL, NULL, flags,
msg, err);
}
int ref_transaction_verify(struct ref_transaction *transaction,
@ -1321,6 +1338,7 @@ int ref_transaction_verify(struct ref_transaction *transaction,
BUG("verify called with old_oid set to NULL");
return ref_transaction_update(transaction, refname,
NULL, old_oid,
NULL, NULL,
flags, NULL, err);
}
@ -1335,8 +1353,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);
@ -2266,25 +2284,33 @@ int peel_iterated_oid(const struct object_id *base, struct object_id *peeled)
return peel_object(base, peeled) ? -1 : 0;
}
int refs_create_symref(struct ref_store *refs,
const char *ref_target,
const char *refs_heads_master,
const char *logmsg)
int refs_update_symref(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg)
{
char *msg;
int retval;
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
int ret = 0;
msg = normalize_reflog_message(logmsg);
retval = refs->be->create_symref(refs, ref_target, refs_heads_master,
msg);
free(msg);
return retval;
transaction = ref_store_transaction_begin(refs, &err);
if (!transaction ||
ref_transaction_update(transaction, ref, NULL, NULL,
target, NULL, REF_NO_DEREF,
logmsg, &err) ||
ref_transaction_commit(transaction, &err)) {
ret = error("%s", err.buf);
}
strbuf_release(&err);
if (transaction)
ref_transaction_free(transaction);
return ret;
}
int create_symref(const char *ref_target, const char *refs_heads_master,
const char *logmsg)
{
return refs_create_symref(get_main_ref_store(the_repository), ref_target,
return refs_update_symref(get_main_ref_store(the_repository), ref_target,
refs_heads_master, logmsg);
}
@ -2338,10 +2364,22 @@ static int run_transaction_hook(struct ref_transaction *transaction,
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))
strbuf_addf(&buf, "%s ", oid_to_hex(null_oid()));
else if (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))
strbuf_addf(&buf, "%s ", oid_to_hex(null_oid()));
else if (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) {
@ -2790,3 +2828,38 @@ 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);
}
const char *ref_update_original_update_refname(struct ref_update *update)
{
while (update->parent_update)
update = update->parent_update;
return update->refname;
}
int ref_update_has_null_new_value(struct ref_update *update)
{
return !update->new_target && is_null_oid(&update->new_oid);
}
int ref_update_check_old_target(const char *referent, struct ref_update *update,
struct strbuf *err)
{
if (!update->old_target)
BUG("called without old_target set");
if (!strcmp(referent, update->old_target))
return 0;
if (!strcmp(referent, ""))
strbuf_addf(err, "verifying symref target: '%s': "
"reference is missing but expected %s",
ref_update_original_update_refname(update),
update->old_target);
else
strbuf_addf(err, "verifying symref target: '%s': "
"is at %s but expected %s",
ref_update_original_update_refname(update),
referent, update->old_target);
return -1;
}

20
refs.h
View file

@ -606,7 +606,7 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
int copy_existing_ref(const char *oldref, const char *newref,
const char *logmsg);
int refs_create_symref(struct ref_store *refs, const char *refname,
int refs_update_symref(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg);
int create_symref(const char *refname, const char *target, const char *logmsg);
@ -648,6 +648,16 @@ 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
* updated to point to. If the reference is a regular reference,
* it will be converted to a symbolic reference. Cannot be set
* together with `new_oid`. A copy of this value is made in the
* transaction.
*
* old_target -- the reference that the reference must be pointing to.
* Canont be set together with `old_oid`. A copy of this value is
* made in the transaction.
*
* 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 +723,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 +736,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);

View file

@ -131,18 +131,6 @@ static int debug_pack_refs(struct ref_store *ref_store, struct pack_refs_opts *o
return res;
}
static int debug_create_symref(struct ref_store *ref_store,
const char *ref_name, const char *target,
const char *logmsg)
{
struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
int res = drefs->refs->be->create_symref(drefs->refs, ref_name, target,
logmsg);
trace_printf_key(&trace_refs, "create_symref: %s -> %s \"%s\": %d\n", ref_name,
target, logmsg, res);
return res;
}
static int debug_rename_ref(struct ref_store *ref_store, const char *oldref,
const char *newref, const char *logmsg)
{
@ -441,7 +429,6 @@ struct ref_storage_be refs_be_debug = {
.initial_transaction_commit = debug_initial_transaction_commit,
.pack_refs = debug_pack_refs,
.create_symref = debug_create_symref,
.rename_ref = debug_rename_ref,
.copy_ref = debug_copy_ref,

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);
@ -1903,66 +1903,23 @@ static int create_ref_symlink(struct ref_lock *lock, const char *target)
return ret;
}
static void update_symref_reflog(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, struct strbuf *err)
{
struct strbuf err = STRBUF_INIT;
struct object_id new_oid;
if (logmsg &&
refs_resolve_ref_unsafe(&refs->base, target,
RESOLVE_REF_READING, &new_oid, NULL) &&
files_log_ref_write(refs, refname, &lock->old_oid,
&new_oid, logmsg, 0, &err)) {
error("%s", err.buf);
strbuf_release(&err);
}
}
static int create_symref_locked(struct files_ref_store *refs,
struct ref_lock *lock, const char *refname,
const char *target, const char *logmsg)
{
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",
if (!fdopen_lock_file(&lock->lk, "w")) {
strbuf_addf(err, "unable to fdopen %s: %s",
get_lock_file_path(&lock->lk), strerror(errno));
update_symref_reflog(refs, lock, refname, target, logmsg);
/* 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;
}
static int files_create_symref(struct ref_store *ref_store,
const char *refname, const char *target,
const char *logmsg)
{
struct files_ref_store *refs =
files_downcast(ref_store, REF_STORE_WRITE, "create_symref");
struct strbuf err = STRBUF_INIT;
struct ref_lock *lock;
int ret;
lock = lock_ref_oid_basic(refs, refname, &err);
if (!lock) {
error("%s", err.buf);
strbuf_release(&err);
return -1;
}
ret = create_symref_locked(refs, lock, refname, target, logmsg);
unlock_ref(lock);
return ret;
if (fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target) < 0) {
strbuf_addf(err, "unable to write to %s: %s",
get_lock_file_path(&lock->lk), strerror(errno));
return -1;
}
return 0;
}
static int files_reflog_exists(struct ref_store *ref_store,
@ -2309,7 +2266,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
@ -2371,8 +2328,9 @@ 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->msg);
update->new_target ? NULL : &update->new_oid,
update->old_target ? NULL : &update->old_oid,
update->new_target, update->old_target, update->msg);
new_update->parent_update = update;
@ -2400,17 +2358,6 @@ static int split_symref_update(struct ref_update *update,
return 0;
}
/*
* Return the refname under which update was originally requested.
*/
static const char *original_update_refname(struct ref_update *update)
{
while (update->parent_update)
update = update->parent_update;
return update->refname;
}
/*
* 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
@ -2427,16 +2374,16 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,
if (is_null_oid(&update->old_oid))
strbuf_addf(err, "cannot lock ref '%s': "
"reference already exists",
original_update_refname(update));
ref_update_original_update_refname(update));
else if (is_null_oid(oid))
strbuf_addf(err, "cannot lock ref '%s': "
"reference is missing but expected %s",
original_update_refname(update),
ref_update_original_update_refname(update),
oid_to_hex(&update->old_oid));
else
strbuf_addf(err, "cannot lock ref '%s': "
"is at %s but expected %s",
original_update_refname(update),
ref_update_original_update_refname(update),
oid_to_hex(oid),
oid_to_hex(&update->old_oid));
@ -2471,7 +2418,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_has_null_new_value(update))
update->flags |= REF_DELETING;
if (head_ref) {
@ -2490,7 +2437,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
reason = strbuf_detach(err, NULL);
strbuf_addf(err, "cannot lock ref '%s': %s",
original_update_refname(update), reason);
ref_update_original_update_refname(update), reason);
free(reason);
goto out;
}
@ -2510,11 +2457,18 @@ static int lock_ref_for_update(struct files_ref_store *refs,
if (update->flags & REF_HAVE_OLD) {
strbuf_addf(err, "cannot lock ref '%s': "
"error reading reference",
original_update_refname(update));
ref_update_original_update_refname(update));
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
} else if (check_old_oid(update, &lock->old_oid, err)) {
}
if (update->old_target) {
if (ref_update_check_old_target(referent.buf, update, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
} else if (check_old_oid(update, &lock->old_oid, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
@ -2535,7 +2489,17 @@ static int lock_ref_for_update(struct files_ref_store *refs,
} else {
struct ref_update *parent_update;
if (check_old_oid(update, &lock->old_oid, err)) {
/*
* Even if the ref is a regular ref, if `old_target` is set, we
* check the referent value. Ideally `old_target` should only
* be set for symrefs, but we're strict about its usage.
*/
if (update->old_target) {
if (ref_update_check_old_target(referent.buf, update, 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 +2517,28 @@ 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, err)) {
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 +2746,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);
}
}
@ -2818,6 +2801,43 @@ static int files_transaction_prepare(struct ref_store *ref_store,
return ret;
}
static int parse_and_write_reflog(struct files_ref_store *refs,
struct ref_update *update,
struct ref_lock *lock,
struct strbuf *err)
{
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)) {
/*
* TODO: currently we skip creating reflogs for dangling
* symref updates. It would be nice to capture this as
* zero oid updates however.
*/
return 0;
}
}
if (files_log_ref_write(refs, lock->ref_name, &lock->old_oid,
&update->new_oid, update->msg, update->flags, err)) {
char *old_msg = strbuf_detach(err, NULL);
strbuf_addf(err, "cannot update the ref '%s': %s",
lock->ref_name, old_msg);
free(old_msg);
unlock_ref(lock);
update->backend_data = NULL;
return -1;
}
return 0;
}
static int files_transaction_finish(struct ref_store *ref_store,
struct ref_transaction *transaction,
struct strbuf *err)
@ -2848,23 +2868,20 @@ static int files_transaction_finish(struct ref_store *ref_store,
if (update->flags & REF_NEEDS_COMMIT ||
update->flags & REF_LOG_ONLY) {
if (files_log_ref_write(refs,
lock->ref_name,
&lock->old_oid,
&update->new_oid,
update->msg, update->flags,
err)) {
char *old_msg = strbuf_detach(err, NULL);
strbuf_addf(err, "cannot update the ref '%s': %s",
lock->ref_name, old_msg);
free(old_msg);
unlock_ref(lock);
update->backend_data = NULL;
if (parse_and_write_reflog(refs, update, lock, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
}
}
/*
* We try creating a symlink, if that succeeds we continue to the
* next update. 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 +3065,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)) {
@ -3291,7 +3308,6 @@ struct ref_storage_be refs_be_files = {
.initial_transaction_commit = files_initial_transaction_commit,
.pack_refs = files_pack_refs,
.create_symref = files_create_symref,
.rename_ref = files_rename_ref,
.copy_ref = files_copy_ref,

View file

@ -1714,7 +1714,6 @@ struct ref_storage_be refs_be_packed = {
.initial_transaction_commit = packed_initial_transaction_commit,
.pack_refs = packed_pack_refs,
.create_symref = NULL,
.rename_ref = NULL,
.copy_ref = NULL,

View file

@ -124,6 +124,19 @@ 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.
* Cannot be set together with `new_oid`.
*/
const char *new_target;
/*
* If set, check that the reference previously pointed to this
* value. Cannot be set together with `old_oid`.
*/
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 +186,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);
/*
@ -552,10 +566,6 @@ typedef int ref_transaction_commit_fn(struct ref_store *refs,
typedef int pack_refs_fn(struct ref_store *ref_store,
struct pack_refs_opts *opts);
typedef int create_symref_fn(struct ref_store *ref_store,
const char *ref_target,
const char *refs_heads_master,
const char *logmsg);
typedef int rename_ref_fn(struct ref_store *ref_store,
const char *oldref, const char *newref,
const char *logmsg);
@ -676,7 +686,6 @@ struct ref_storage_be {
ref_transaction_commit_fn *initial_transaction_commit;
pack_refs_fn *pack_refs;
create_symref_fn *create_symref;
rename_ref_fn *rename_ref;
copy_ref_fn *copy_ref;
@ -735,4 +744,25 @@ 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);
/*
* Return the refname under which update was originally requested.
*/
const char *ref_update_original_update_refname(struct ref_update *update);
/*
* 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_has_null_new_value(struct ref_update *update);
/*
* Check whether the old_target values stored in update are consistent
* with the referent, which is the symbolic reference's current value.
* If everything is OK, return 0; otherwise, write an error message to
* err and return -1.
*/
int ref_update_check_old_target(const char *referent, struct ref_update *update,
struct strbuf *err);
#endif /* REFS_REFS_INTERNAL_H */

View file

@ -581,16 +581,6 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,
return ret;
}
/*
* Return the refname under which update was originally requested.
*/
static const char *original_update_refname(struct ref_update *update)
{
while (update->parent_update)
update = update->parent_update;
return update->refname;
}
struct reftable_transaction_update {
struct ref_update *update;
struct object_id current_oid;
@ -829,7 +819,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);
}
@ -856,7 +846,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_has_null_new_value(u)) {
ret = queue_transaction_update(refs, tx_data, u,
&current_oid, err);
if (ret)
@ -869,7 +859,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
/* The reference does not exist, but we expected it to. */
strbuf_addf(err, _("cannot lock ref '%s': "
"unable to resolve reference '%s'"),
original_update_refname(u), u->refname);
ref_update_original_update_refname(u), u->refname);
ret = -1;
goto done;
}
@ -907,8 +897,10 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* intertwined with the locking in files-backend.c.
*/
new_update = ref_transaction_add_update(
transaction, referent.buf, new_flags,
&u->new_oid, &u->old_oid, u->msg);
transaction, referent.buf, new_flags,
&u->new_oid, &u->old_oid, u->new_target,
u->old_target, u->msg);
new_update->parent_update = u;
/*
@ -938,20 +930,25 @@ 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->old_target) {
if (ref_update_check_old_target(referent.buf, u, err)) {
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"),
original_update_refname(u));
"reference already exists"),
ref_update_original_update_refname(u));
else if (is_null_oid(&current_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
"reference is missing but expected %s"),
original_update_refname(u),
"reference is missing but expected %s"),
ref_update_original_update_refname(u),
oid_to_hex(&u->old_oid));
else
strbuf_addf(err, _("cannot lock ref '%s': "
"is at %s but expected %s"),
original_update_refname(u),
"is at %s but expected %s"),
ref_update_original_update_refname(u),
oid_to_hex(&current_oid),
oid_to_hex(&u->old_oid));
ret = -1;
@ -1043,7 +1040,9 @@ 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_has_null_new_value(u)) {
struct reftable_log_record log = {0};
struct reftable_iterator it = {0};
@ -1084,24 +1083,52 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
(u->flags & REF_FORCE_CREATE_REFLOG ||
should_write_log(&arg->refs->base, u->refname))) {
struct reftable_log_record *log;
int create_reflog = 1;
ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
log = &logs[logs_nr++];
memset(log, 0, sizeof(*log));
if (u->new_target) {
if (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,
RESOLVE_REF_READING, &u->new_oid, NULL)) {
/*
* TODO: currently we skip creating reflogs for dangling
* symref updates. It would be nice to capture this as
* zero oid updates however.
*/
create_reflog = 0;
}
}
fill_reftable_log_record(log);
log->update_index = ts;
log->refname = xstrdup(u->refname);
memcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);
memcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);
log->value.update.message =
xstrndup(u->msg, arg->refs->write_options.block_size / 2);
if (create_reflog) {
ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
log = &logs[logs_nr++];
memset(log, 0, sizeof(*log));
fill_reftable_log_record(log);
log->update_index = ts;
log->refname = xstrdup(u->refname);
memcpy(log->value.update.new_hash,
u->new_oid.hash, GIT_MAX_RAWSZ);
memcpy(log->value.update.old_hash,
tx_update->current_oid.hash, GIT_MAX_RAWSZ);
log->value.update.message =
xstrndup(u->msg, arg->refs->write_options.block_size / 2);
}
}
if (u->flags & REF_LOG_ONLY)
continue;
if (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {
if (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_has_null_new_value(u)) {
struct reftable_ref_record ref = {
.refname = (char *)u->refname,
.update_index = ts,
@ -1232,91 +1259,6 @@ struct write_create_symref_arg {
const char *logmsg;
};
static int write_create_symref_table(struct reftable_writer *writer, void *cb_data)
{
struct write_create_symref_arg *create = cb_data;
uint64_t ts = reftable_stack_next_update_index(create->stack);
struct reftable_ref_record ref = {
.refname = (char *)create->refname,
.value_type = REFTABLE_REF_SYMREF,
.value.symref = (char *)create->target,
.update_index = ts,
};
struct reftable_log_record log = {0};
struct object_id new_oid;
struct object_id old_oid;
int ret;
reftable_writer_set_limits(writer, ts, ts);
ret = reftable_writer_add_ref(writer, &ref);
if (ret)
return ret;
/*
* Note that it is important to try and resolve the reference before we
* write the log entry. This is because `should_write_log()` will munge
* `core.logAllRefUpdates`, which is undesirable when we create a new
* repository because it would be written into the config. As HEAD will
* not resolve for new repositories this ordering will ensure that this
* never happens.
*/
if (!create->logmsg ||
!refs_resolve_ref_unsafe(&create->refs->base, create->target,
RESOLVE_REF_READING, &new_oid, NULL) ||
!should_write_log(&create->refs->base, create->refname))
return 0;
fill_reftable_log_record(&log);
log.refname = xstrdup(create->refname);
log.update_index = ts;
log.value.update.message = xstrndup(create->logmsg,
create->refs->write_options.block_size / 2);
memcpy(log.value.update.new_hash, new_oid.hash, GIT_MAX_RAWSZ);
if (refs_resolve_ref_unsafe(&create->refs->base, create->refname,
RESOLVE_REF_READING, &old_oid, NULL))
memcpy(log.value.update.old_hash, old_oid.hash, GIT_MAX_RAWSZ);
ret = reftable_writer_add_log(writer, &log);
reftable_log_record_release(&log);
return ret;
}
static int reftable_be_create_symref(struct ref_store *ref_store,
const char *refname,
const char *target,
const char *logmsg)
{
struct reftable_ref_store *refs =
reftable_be_downcast(ref_store, REF_STORE_WRITE, "create_symref");
struct reftable_stack *stack = stack_for(refs, refname, &refname);
struct write_create_symref_arg arg = {
.refs = refs,
.stack = stack,
.refname = refname,
.target = target,
.logmsg = logmsg,
};
int ret;
ret = refs->err;
if (ret < 0)
goto done;
ret = reftable_stack_reload(stack);
if (ret)
goto done;
ret = reftable_stack_add(stack, &write_create_symref_table, &arg);
done:
assert(ret != REFTABLE_API_ERROR);
if (ret)
error("unable to write symref for %s: %s", refname,
reftable_error_str(ret));
return ret;
}
struct write_copy_arg {
struct reftable_ref_store *refs;
struct reftable_stack *stack;
@ -2224,7 +2166,6 @@ struct ref_storage_be refs_be_reftable = {
.initial_transaction_commit = reftable_be_initial_transaction_commit,
.pack_refs = reftable_be_pack_refs,
.create_symref = reftable_be_create_symref,
.rename_ref = reftable_be_rename_ref,
.copy_ref = reftable_be_copy_ref,

View file

@ -616,7 +616,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);
@ -1248,7 +1248,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;
}
@ -3764,8 +3764,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

@ -118,7 +118,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)
const char *target = notnull(*argv++, "target");
const char *logmsg = *argv++;
return refs_create_symref(refs, refname, target, logmsg);
return refs_update_symref(refs, refname, target, logmsg);
}
static struct flag_definition transaction_flags[] = {

View file

@ -286,7 +286,7 @@ test_expect_success 'ref transaction: creating symbolic ref fails with F/D confl
git init repo &&
test_commit -C repo A &&
cat >expect <<-EOF &&
error: unable to write symref for refs/heads: file/directory conflict
error: ${SQ}refs/heads/main${SQ} exists; cannot create ${SQ}refs/heads${SQ}
EOF
test_must_fail git -C repo symbolic-ref refs/heads refs/heads/foo 2>err &&
test_cmp expect err

View file

@ -134,4 +134,27 @@ test_expect_success 'interleaving hook calls succeed' '
test_cmp expect target-repo.git/actual
'
test_expect_success 'hook captures git-symbolic-ref updates' '
test_when_finished "rm actual" &&
test_hook reference-transaction <<-\EOF &&
echo "$*" >>actual
while read -r line
do
printf "%s\n" "$line"
done >>actual
EOF
git symbolic-ref refs/heads/symref refs/heads/main &&
cat >expect <<-EOF &&
prepared
$ZERO_OID ref:refs/heads/main refs/heads/symref
committed
$ZERO_OID ref:refs/heads/main refs/heads/symref
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);