#include "date.h"
#include "commit.h"
#include "wildmatch.h"
+#include "ident.h"
/*
* List of all available backends
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,
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;
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();
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;
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
* 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)) {
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;
}
ref_transaction_free(transaction);
strbuf_release(&new_gitdir);
+ strbuf_release(&data.sb);
return ret;
}
. ./test-lib.sh
+# Migrate the provided repository from one format to the other and
+# verify that the references and logs are migrated over correctly.
+# Usage: test_migration <repo> <format> <skip_reflog_verify>
+# <repo> is the relative path to the repo to be migrated.
+# <format> is the ref format to be migrated to.
+# <skip_reflog_verify> (true or false) whether to skip reflog verification.
test_migration () {
- git -C "$1" for-each-ref --include-root-refs \
+ repo=$1 &&
+ format=$2 &&
+ skip_reflog_verify=${3:-false} &&
+ git -C "$repo" for-each-ref --include-root-refs \
--format='%(refname) %(objectname) %(symref)' >expect &&
- git -C "$1" refs migrate --ref-format="$2" &&
- git -C "$1" for-each-ref --include-root-refs \
+ if ! $skip_reflog_verify
+ then
+ git -C "$repo" reflog --all >expect_logs &&
+ git -C "$repo" reflog list >expect_log_list
+ fi &&
+
+ git -C "$repo" refs migrate --ref-format="$2" &&
+
+ git -C "$repo" for-each-ref --include-root-refs \
--format='%(refname) %(objectname) %(symref)' >actual &&
test_cmp expect actual &&
+ if ! $skip_reflog_verify
+ then
+ git -C "$repo" reflog --all >actual_logs &&
+ git -C "$repo" reflog list >actual_log_list &&
+ test_cmp expect_logs actual_logs &&
+ test_cmp expect_log_list actual_log_list
+ fi &&
- git -C "$1" rev-parse --show-ref-format >actual &&
- echo "$2" >expect &&
+ git -C "$repo" rev-parse --show-ref-format >actual &&
+ echo "$format" >expect &&
test_cmp expect actual
}
test_expect_success 'setup' '
- rm -rf .git &&
- # The migration does not yet support reflogs.
- git config --global core.logAllRefUpdates false
+ rm -rf .git
'
test_expect_success "superfluous arguments" '
test_cmp expect err
'
- test_expect_success "$from_format -> $to_format: migration with reflog fails" '
- test_when_finished "rm -rf repo" &&
- git init --ref-format=$from_format repo &&
- test_config -C repo core.logAllRefUpdates true &&
- test_commit -C repo logged &&
- test_must_fail git -C repo refs migrate \
- --ref-format=$to_format 2>err &&
- cat >expect <<-EOF &&
- error: migrating reflogs is not supported yet
- EOF
- test_cmp expect err
- '
-
test_expect_success "$from_format -> $to_format: migration with worktree fails" '
test_when_finished "rm -rf repo" &&
git init --ref-format=$from_format repo &&
test_commit -C repo initial &&
test-tool -C repo ref-store main update-ref "" refs/heads/broken \
"$(test_oid 001)" "$ZERO_OID" REF_SKIP_CREATE_REFLOG,REF_SKIP_OID_VERIFICATION &&
- test_migration repo "$to_format" &&
+ test_migration repo "$to_format" true &&
test_oid 001 >expect &&
git -C repo rev-parse refs/heads/broken >actual &&
test_cmp expect actual
git -C repo rev-parse --show-ref-format >actual &&
test_cmp expect actual
'
+
+ test_expect_success "$from_format -> $to_format: reflogs of symrefs with target deleted" '
+ test_when_finished "rm -rf repo" &&
+ git init --ref-format=$from_format repo &&
+ test_commit -C repo initial &&
+ git -C repo branch branch-1 HEAD &&
+ git -C repo symbolic-ref refs/heads/symref refs/heads/branch-1 &&
+ cat >input <<-EOF &&
+ delete refs/heads/branch-1
+ EOF
+ git -C repo update-ref --stdin <input &&
+ test_migration repo "$to_format"
+ '
+
+ test_expect_success "$from_format -> $to_format: reflogs order is retained" '
+ test_when_finished "rm -rf repo" &&
+ git init --ref-format=$from_format repo &&
+ test_commit --date "100005000 +0700" --no-tag -C repo initial &&
+ test_commit --date "100003000 +0700" --no-tag -C repo second &&
+ test_migration repo "$to_format"
+ '
done
done