Merge branch 'kn/reflog-migration'

"git refs migrate" learned to also migrate the reflog data across
backends.

* kn/reflog-migration:
  refs: mark invalid refname message for translation
  refs: add support for migrating reflogs
  refs: allow multiple reflog entries for the same refname
  refs: introduce the `ref_transaction_update_reflog` function
  refs: add `committer_info` to `ref_transaction_add_update()`
  refs: extract out refname verification in transactions
  refs/files: add count field to ref_lock
  refs: add `index` field to `struct ref_udpate`
  refs: include committer info in `ref_update` struct
This commit is contained in:
Junio C Hamano
2024-12-23 09:32:29 -08:00
7 changed files with 337 additions and 121 deletions

176
refs.c
View File

@ -31,6 +31,7 @@
#include "date.h"
#include "commit.h"
#include "wildmatch.h"
#include "ident.h"
/*
* List of all available backends
@ -1199,6 +1200,7 @@ void ref_transaction_free(struct ref_transaction *transaction)
for (i = 0; i < transaction->nr; i++) {
free(transaction->updates[i]->msg);
free(transaction->updates[i]->committer_info);
free((char *)transaction->updates[i]->new_target);
free((char *)transaction->updates[i]->old_target);
free(transaction->updates[i]);
@ -1213,6 +1215,7 @@ struct ref_update *ref_transaction_add_update(
const struct object_id *new_oid,
const struct object_id *old_oid,
const char *new_target, const char *old_target,
const char *committer_info,
const char *msg)
{
struct ref_update *update;
@ -1237,12 +1240,44 @@ struct ref_update *ref_transaction_add_update(
oidcpy(&update->new_oid, new_oid);
if ((flags & REF_HAVE_OLD) && old_oid)
oidcpy(&update->old_oid, old_oid);
if (!(flags & REF_SKIP_CREATE_REFLOG))
if (!(flags & REF_SKIP_CREATE_REFLOG)) {
update->committer_info = xstrdup_or_null(committer_info);
update->msg = normalize_reflog_message(msg);
}
return update;
}
static int transaction_refname_valid(const char *refname,
const struct object_id *new_oid,
unsigned int flags, struct strbuf *err)
{
if (flags & REF_SKIP_REFNAME_VERIFICATION)
return 1;
if (is_pseudo_ref(refname)) {
const char *refusal_msg;
if (flags & REF_LOG_ONLY)
refusal_msg = _("refusing to update reflog for pseudoref '%s'");
else
refusal_msg = _("refusing to update pseudoref '%s'");
strbuf_addf(err, refusal_msg, refname);
return 0;
} else if ((new_oid && !is_null_oid(new_oid)) ?
check_refname_format(refname, REFNAME_ALLOW_ONELEVEL) :
!refname_is_safe(refname)) {
const char *refusal_msg;
if (flags & REF_LOG_ONLY)
refusal_msg = _("refusing to update reflog with bad name '%s'");
else
refusal_msg = _("refusing to update ref with bad name '%s'");
strbuf_addf(err, refusal_msg, refname);
return 0;
}
return 1;
}
int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
@ -1260,21 +1295,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
return -1;
}
if (!(flags & REF_SKIP_REFNAME_VERIFICATION) &&
((new_oid && !is_null_oid(new_oid)) ?
check_refname_format(refname, REFNAME_ALLOW_ONELEVEL) :
!refname_is_safe(refname))) {
strbuf_addf(err, _("refusing to update ref with bad name '%s'"),
refname);
if (!transaction_refname_valid(refname, new_oid, flags, err))
return -1;
}
if (!(flags & REF_SKIP_REFNAME_VERIFICATION) &&
is_pseudo_ref(refname)) {
strbuf_addf(err, _("refusing to update pseudoref '%s'"),
refname);
return -1;
}
if (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS)
BUG("illegal flags 0x%x passed to ref_transaction_update()", flags);
@ -1291,7 +1313,38 @@ int ref_transaction_update(struct ref_transaction *transaction,
ref_transaction_add_update(transaction, refname, flags,
new_oid, old_oid, new_target,
old_target, msg);
old_target, NULL, msg);
return 0;
}
int ref_transaction_update_reflog(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
const char *committer_info, unsigned int flags,
const char *msg, unsigned int index,
struct strbuf *err)
{
struct ref_update *update;
assert(err);
flags |= REF_LOG_ONLY | REF_NO_DEREF;
if (!transaction_refname_valid(refname, new_oid, flags, err))
return -1;
update = ref_transaction_add_update(transaction, refname, flags,
new_oid, old_oid, NULL, NULL,
committer_info, msg);
/*
* While we do set the old_oid value, we unset the flag to skip
* old_oid verification which only makes sense for refs.
*/
update->flags &= ~REF_HAVE_OLD;
update->index = index;
return 0;
}
@ -2711,6 +2764,7 @@ struct migration_data {
struct ref_store *old_refs;
struct ref_transaction *transaction;
struct strbuf *errbuf;
struct strbuf sb;
};
static int migrate_one_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
@ -2743,6 +2797,52 @@ done:
return ret;
}
struct reflog_migration_data {
unsigned int index;
const char *refname;
struct ref_store *old_refs;
struct ref_transaction *transaction;
struct strbuf *errbuf;
struct strbuf *sb;
};
static int migrate_one_reflog_entry(struct object_id *old_oid,
struct object_id *new_oid,
const char *committer,
timestamp_t timestamp, int tz,
const char *msg, void *cb_data)
{
struct reflog_migration_data *data = cb_data;
const char *date;
int ret;
date = show_date(timestamp, tz, DATE_MODE(NORMAL));
strbuf_reset(data->sb);
/* committer contains name and email */
strbuf_addstr(data->sb, fmt_ident("", committer, WANT_BLANK_IDENT, date, 0));
ret = ref_transaction_update_reflog(data->transaction, data->refname,
new_oid, old_oid, data->sb->buf,
REF_HAVE_NEW | REF_HAVE_OLD, msg,
data->index++, data->errbuf);
return ret;
}
static int migrate_one_reflog(const char *refname, void *cb_data)
{
struct migration_data *migration_data = cb_data;
struct reflog_migration_data data = {
.refname = refname,
.old_refs = migration_data->old_refs,
.transaction = migration_data->transaction,
.errbuf = migration_data->errbuf,
.sb = &migration_data->sb,
};
return refs_for_each_reflog_ent(migration_data->old_refs, refname,
migrate_one_reflog_entry, &data);
}
static int move_files(const char *from_path, const char *to_path, struct strbuf *errbuf)
{
struct strbuf from_buf = STRBUF_INIT, to_buf = STRBUF_INIT;
@ -2809,13 +2909,6 @@ done:
return ret;
}
static int count_reflogs(const char *reflog UNUSED, void *payload)
{
size_t *reflog_count = payload;
(*reflog_count)++;
return 0;
}
static int has_worktrees(void)
{
struct worktree **worktrees = get_worktrees();
@ -2840,8 +2933,9 @@ int repo_migrate_ref_storage_format(struct repository *repo,
struct ref_store *old_refs = NULL, *new_refs = NULL;
struct ref_transaction *transaction = NULL;
struct strbuf new_gitdir = STRBUF_INIT;
struct migration_data data;
size_t reflog_count = 0;
struct migration_data data = {
.sb = STRBUF_INIT,
};
int did_migrate_refs = 0;
int ret;
@ -2853,21 +2947,6 @@ int repo_migrate_ref_storage_format(struct repository *repo,
old_refs = get_main_ref_store(repo);
/*
* We do not have any interfaces that would allow us to write many
* reflog entries. Once we have them we can remove this restriction.
*/
if (refs_for_each_reflog(old_refs, count_reflogs, &reflog_count) < 0) {
strbuf_addstr(errbuf, "cannot count reflogs");
ret = -1;
goto done;
}
if (reflog_count) {
strbuf_addstr(errbuf, "migrating reflogs is not supported yet");
ret = -1;
goto done;
}
/*
* Worktrees complicate the migration because every worktree has a
* separate ref storage. While it should be feasible to implement, this
@ -2893,17 +2972,21 @@ int repo_migrate_ref_storage_format(struct repository *repo,
* This operation is safe as we do not yet modify the main
* repository.
*
* 3. If we're in dry-run mode then we are done and can hand over the
* 3. Enumerate all reflogs and write them into the new ref storage.
* This operation is safe as we do not yet modify the main
* repository.
*
* 4. If we're in dry-run mode then we are done and can hand over the
* directory to the caller for inspection. If not, we now start
* with the destructive part.
*
* 4. Delete the old ref storage from disk. As we have a copy of refs
* 5. Delete the old ref storage from disk. As we have a copy of refs
* in the new ref storage it's okay(ish) if we now get interrupted
* as there is an equivalent copy of all refs available.
*
* 5. Move the new ref storage files into place.
* 6. Move the new ref storage files into place.
*
* 6. Change the repository format to the new ref format.
* 7. Change the repository format to the new ref format.
*/
strbuf_addf(&new_gitdir, "%s/%s", old_refs->gitdir, "ref_migration.XXXXXX");
if (!mkdtemp(new_gitdir.buf)) {
@ -2945,6 +3028,10 @@ int repo_migrate_ref_storage_format(struct repository *repo,
if (ret < 0)
goto done;
ret = refs_for_each_reflog(old_refs, migrate_one_reflog, &data);
if (ret < 0)
goto done;
ret = ref_transaction_commit(transaction, errbuf);
if (ret < 0)
goto done;
@ -3020,6 +3107,7 @@ done:
}
ref_transaction_free(transaction);
strbuf_release(&new_gitdir);
strbuf_release(&data.sb);
return ret;
}