refs: fix identity for migrated reflogs

When migrating reflog entries between different storage formats we must reconstruct the identity of reflog entries. This is done by passing the committer passed to the `migrate_one_reflog_entry()` callback function to `fmt_ident()`. This results in an invalid identity though: `fmt_ident()` expects the caller to provide both name and mail of the author, but we pass the full identity as mail. This leads to an identity like: pks <Patrick Steinhardt ps@pks.im> Fix the bug by splitting the identity line first. This allows us to extract both the name and mail so that we can pass them to `fmt_ident()` separately. This commit does not yet add any tests as there is another bug in the reflog migration that will be fixed in a subsequent commit. Once that bug is fixed we'll make the reflog verification in t1450 stricter, and that will catch both this bug here and the other bug. Note that we also add two new `name` and `mail` string buffers to the callback structures and splice them through to the callbacks. This is done so that we can avoid allocating a new buffer every time we compute the committer information. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Aug 6, 2025 at 07:54 UTC ec922e0d300f6541802c6460aefd63b837b2afd7
1 file changed +18 -4
refs.c
+18 -4
@@ -2954,7 +2954,7 @@ struct migration_data {
2954 struct ref_store *old_refs;
2955 struct ref_transaction *transaction;
2956 struct strbuf *errbuf;
2957 - struct strbuf sb;
2957 + struct strbuf sb, name, mail;
2958 };
2959
2960 static int migrate_one_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
@@ -2993,7 +2993,7 @@ struct reflog_migration_data {
2993 struct ref_store *old_refs;
2994 struct ref_transaction *transaction;
2995 struct strbuf *errbuf;
2996 - struct strbuf *sb;
2996 + struct strbuf *sb, *name, *mail;
2997 };
2998
2999 static int migrate_one_reflog_entry(struct object_id *old_oid,
@@ -3003,13 +3003,21 @@ static int migrate_one_reflog_entry(struct object_id *old_oid,
3003 const char *msg, void *cb_data)
3004 {
3005 struct reflog_migration_data *data = cb_data;
3006 + struct ident_split ident;
3007 const char *date;
3008 int ret;
3009
3010 + if (split_ident_line(&ident, committer, strlen(committer)) < 0)
3011 + return -1;
3012 +
3013 + strbuf_reset(data->name);
3014 + strbuf_add(data->name, ident.name_begin, ident.name_end - ident.name_begin);
3015 + strbuf_reset(data->mail);
3016 + strbuf_add(data->mail, ident.mail_begin, ident.mail_end - ident.mail_begin);
3017 +
3018 date = show_date(timestamp, tz, DATE_MODE(NORMAL));
3019 strbuf_reset(data->sb);
3011 - /* committer contains name and email */
3012 - strbuf_addstr(data->sb, fmt_ident("", committer, WANT_BLANK_IDENT, date, 0));
3020 + strbuf_addstr(data->sb, fmt_ident(data->name->buf, data->mail->buf, WANT_BLANK_IDENT, date, 0));
3021
3022 ret = ref_transaction_update_reflog(data->transaction, data->refname,
3023 new_oid, old_oid, data->sb->buf,
@@ -3026,6 +3034,8 @@ static int migrate_one_reflog(const char *refname, void *cb_data)
3034 .transaction = migration_data->transaction,
3035 .errbuf = migration_data->errbuf,
3036 .sb = &migration_data->sb,
3037 + .name = &migration_data->name,
3038 + .mail = &migration_data->mail,
3039 };
3040
3041 return refs_for_each_reflog_ent(migration_data->old_refs, refname,
@@ -3124,6 +3134,8 @@ int repo_migrate_ref_storage_format(struct repository *repo,
3134 struct strbuf new_gitdir = STRBUF_INIT;
3135 struct migration_data data = {
3136 .sb = STRBUF_INIT,
3137 + .name = STRBUF_INIT,
3138 + .mail = STRBUF_INIT,
3139 };
3140 int did_migrate_refs = 0;
3141 int ret;
@@ -3299,6 +3311,8 @@ done:
3311 ref_transaction_free(transaction);
3312 strbuf_release(&new_gitdir);
3313 strbuf_release(&data.sb);
3314 + strbuf_release(&data.name);
3315 + strbuf_release(&data.mail);
3316 return ret;
3317 }
3318