Skip to content

Commit

Permalink
Merge branch 'kn/reflog-migration'
Browse files Browse the repository at this point in the history
"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
  • Loading branch information
gitster committed Dec 23, 2024
2 parents f74eae3 + 8ddcdc1 commit 6f8ae95
Show file tree
Hide file tree
Showing 7 changed files with 337 additions and 121 deletions.
2 changes: 0 additions & 2 deletions Documentation/git-refs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ KNOWN LIMITATIONS

The ref format migration has several known limitations in its current form:

* It is not possible to migrate repositories that have reflogs.

* It is not possible to migrate repositories that have worktrees.

* There is no way to block concurrent writes to the repository during an
Expand Down
176 changes: 132 additions & 44 deletions refs.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "date.h"
#include "commit.h"
#include "wildmatch.h"
#include "ident.h"

/*
* List of all available backends
Expand Down Expand Up @@ -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]);
Expand All @@ -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;
Expand All @@ -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,
Expand All @@ -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);
Expand All @@ -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;
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -2743,6 +2797,52 @@ static int migrate_one_ref(const char *refname, const char *referent UNUSED, con
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;
Expand Down Expand Up @@ -2809,13 +2909,6 @@ static int move_files(const char *from_path, const char *to_path, struct strbuf
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();
Expand All @@ -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;

Expand All @@ -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
Expand All @@ -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)) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -3020,6 +3107,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,
}
ref_transaction_free(transaction);
strbuf_release(&new_gitdir);
strbuf_release(&data.sb);
return ret;
}

Expand Down
14 changes: 14 additions & 0 deletions refs.h
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,20 @@ int ref_transaction_update(struct ref_transaction *transaction,
unsigned int flags, const char *msg,
struct strbuf *err);

/*
* Similar to`ref_transaction_update`, but this function is only for adding
* a reflog update. Supports providing custom committer information. The index
* field can be utiltized to order updates as desired. When not used, the
* updates default to being ordered by refname.
*/
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);

/*
* Add a reference creation to transaction. new_oid is the value that
* the reference should have after the update; it must not be
Expand Down
Loading

0 comments on commit 6f8ae95

Please sign in to comment.