Merge branch 'ps/reflog-migrate-fixes'

"git refs migrate" to migrate the reflog entries from a refs
backend to another had a handful of bugs squashed.

* ps/reflog-migrate-fixes:
  refs: fix invalid old object IDs when migrating reflogs
  refs: stop unsetting REF_HAVE_OLD for log-only updates
  refs/files: detect race when generating reflog entry for HEAD
  refs: fix identity for migrated reflogs
  ident: fix type of string length parameter
  builtin/reflog: implement subcommand to write new entries
  refs: export `ref_transaction_update_reflog()`
  builtin/reflog: improve grouping of subcommands
  Documentation/git-reflog: convert to use synopsis type
This commit is contained in:
Junio C Hamano
2025-08-21 13:46:57 -07:00
12 changed files with 446 additions and 150 deletions

View File

@@ -68,6 +68,12 @@
*/
#define REF_DELETED_RMDIR (1 << 9)
/*
* Used to indicate that the reflog-only update has been created via
* `split_head_update()`.
*/
#define REF_LOG_VIA_SPLIT (1 << 14)
struct ref_lock {
char *ref_name;
struct lock_file lk;
@@ -2421,9 +2427,10 @@ static enum ref_transaction_error split_head_update(struct ref_update *update,
new_update = ref_transaction_add_update(
transaction, "HEAD",
update->flags | REF_LOG_ONLY | REF_NO_DEREF,
update->flags | REF_LOG_ONLY | REF_NO_DEREF | REF_LOG_VIA_SPLIT,
&update->new_oid, &update->old_oid,
NULL, NULL, update->committer_info, update->msg);
new_update->parent_update = update;
/*
* Add "HEAD". This insertion is O(N) in the transaction
@@ -2494,7 +2501,6 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update,
* done when new_update is processed.
*/
update->flags |= REF_LOG_ONLY | REF_NO_DEREF;
update->flags &= ~REF_HAVE_OLD;
return 0;
}
@@ -2509,8 +2515,9 @@ static enum ref_transaction_error check_old_oid(struct ref_update *update,
struct object_id *oid,
struct strbuf *err)
{
if (!(update->flags & REF_HAVE_OLD) ||
oideq(oid, &update->old_oid))
if (update->flags & REF_LOG_ONLY ||
!(update->flags & REF_HAVE_OLD) ||
oideq(oid, &update->old_oid))
return 0;
if (is_null_oid(&update->old_oid)) {
@@ -2601,7 +2608,36 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re
update->backend_data = lock;
if (update->type & REF_ISSYMREF) {
if (update->flags & REF_LOG_VIA_SPLIT) {
struct ref_lock *parent_lock;
if (!update->parent_update)
BUG("split update without a parent");
parent_lock = update->parent_update->backend_data;
/*
* Check that "HEAD" didn't racily change since we have looked
* it up. If it did we must refuse to write the reflog entry.
*
* Note that this does not catch all races: if "HEAD" was
* racily changed to point to one of the refs part of the
* transaction then we would miss writing the split reflog
* entry for "HEAD".
*/
if (!(update->type & REF_ISSYMREF) ||
strcmp(update->parent_update->refname, referent.buf)) {
strbuf_addstr(err, "HEAD has been racily updated");
ret = REF_TRANSACTION_ERROR_GENERIC;
goto out;
}
if (update->flags & REF_HAVE_OLD) {
oidcpy(&lock->old_oid, &update->old_oid);
} else {
oidcpy(&lock->old_oid, &parent_lock->old_oid);
}
} else if (update->type & REF_ISSYMREF) {
if (update->flags & REF_NO_DEREF) {
/*
* We won't be reading the referent as part of
@@ -2977,6 +3013,20 @@ static int parse_and_write_reflog(struct files_ref_store *refs,
struct ref_lock *lock,
struct strbuf *err)
{
struct object_id *old_oid = &lock->old_oid;
if (update->flags & REF_LOG_USE_PROVIDED_OIDS) {
if (!(update->flags & REF_HAVE_OLD) ||
!(update->flags & REF_HAVE_NEW) ||
!(update->flags & REF_LOG_ONLY)) {
strbuf_addf(err, _("trying to write reflog for '%s'"
"with incomplete values"), update->refname);
return REF_TRANSACTION_ERROR_GENERIC;
}
old_oid = &update->old_oid;
}
if (update->new_target) {
/*
* We want to get the resolved OID for the target, to ensure
@@ -2994,7 +3044,7 @@ static int parse_and_write_reflog(struct files_ref_store *refs,
}
}
if (files_log_ref_write(refs, lock->ref_name, &lock->old_oid,
if (files_log_ref_write(refs, lock->ref_name, old_oid,
&update->new_oid, update->committer_info,
update->msg, update->flags, err)) {
char *old_msg = strbuf_detach(err, NULL);
@@ -3062,7 +3112,8 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
if ((update->flags & REF_HAVE_OLD) &&
if (!(update->flags & REF_LOG_ONLY) &&
(update->flags & REF_HAVE_OLD) &&
!is_null_oid(&update->old_oid))
BUG("initial ref transaction with old_sha1 set");

View File

@@ -662,7 +662,8 @@ enum ref_transaction_error ref_update_check_old_target(const char *referent,
/*
* Check if the ref must exist, this means that the old_oid or
* old_target is non NULL.
* old_target is non NULL. Log-only updates never require the old state to
* match.
*/
int ref_update_expects_existing_old_ref(struct ref_update *update);

View File

@@ -1102,6 +1102,20 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
if (ret)
return REF_TRANSACTION_ERROR_GENERIC;
if (u->flags & REF_LOG_USE_PROVIDED_OIDS) {
if (!(u->flags & REF_HAVE_OLD) ||
!(u->flags & REF_HAVE_NEW) ||
!(u->flags & REF_LOG_ONLY)) {
strbuf_addf(err, _("trying to write reflog for '%s'"
"with incomplete values"), u->refname);
return REF_TRANSACTION_ERROR_GENERIC;
}
if (queue_transaction_update(refs, tx_data, u, &u->old_oid, err))
return REF_TRANSACTION_ERROR_GENERIC;
return 0;
}
/* Verify that the new object ID is valid. */
if ((u->flags & REF_HAVE_NEW) && !is_null_oid(&u->new_oid) &&
!(u->flags & REF_SKIP_OID_VERIFICATION) &&
@@ -1186,8 +1200,6 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
if (ret > 0) {
/* The reference does not exist, but we expected it to. */
strbuf_addf(err, _("cannot lock ref '%s': "
"unable to resolve reference '%s'"),
ref_update_original_update_refname(u), u->refname);
return REF_TRANSACTION_ERROR_NONEXISTENT_REF;
@@ -1241,13 +1253,8 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
new_update->parent_update = u;
/*
* Change the symbolic ref update to log only. Also, it
* doesn't need to check its old OID value, as that will be
* done when new_update is processed.
*/
/* Change the symbolic ref update to log only. */
u->flags |= REF_LOG_ONLY | REF_NO_DEREF;
u->flags &= ~REF_HAVE_OLD;
}
}
@@ -1271,7 +1278,8 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
ret = ref_update_check_old_target(referent->buf, u, err);
if (ret)
return ret;
} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {
} else if ((u->flags & (REF_LOG_ONLY | REF_HAVE_OLD)) == 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"),