Skip to content

Commit f477854

Browse files
KarthikNayakgitster
authored andcommitted
refs: add support for migrating reflogs
The `git refs migrate` command was introduced in 25a0023 (builtin/refs: new command to migrate ref storage formats, 2024-06-06) to support migrating from one reference backend to another. One limitation of the command was that it didn't support migrating repositories which contained reflogs. A previous commit, added support for adding reflog updates in ref transactions. Using the added functionality bake in reflog support for `git refs migrate`. To ensure that the order of the reflogs is maintained during the migration, we add the index for each reflog update as we iterate over the reflogs from the old reference backend. This is to ensure that the order is maintained in the new backend. Signed-off-by: Karthik Nayak <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 2caf589 commit f477854

File tree

3 files changed

+107
-49
lines changed

3 files changed

+107
-49
lines changed

Documentation/git-refs.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ KNOWN LIMITATIONS
5757

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

60-
* It is not possible to migrate repositories that have reflogs.
61-
6260
* It is not possible to migrate repositories that have worktrees.
6361

6462
* There is no way to block concurrent writes to the repository during an

refs.c

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "date.h"
3131
#include "commit.h"
3232
#include "wildmatch.h"
33+
#include "ident.h"
3334

3435
/*
3536
* List of all available backends
@@ -2687,6 +2688,7 @@ int ref_update_check_old_target(const char *referent, struct ref_update *update,
26872688
}
26882689

26892690
struct migration_data {
2691+
unsigned int index;
26902692
struct ref_store *old_refs;
26912693
struct ref_transaction *transaction;
26922694
struct strbuf *errbuf;
@@ -2722,6 +2724,53 @@ static int migrate_one_ref(const char *refname, const char *referent UNUSED, con
27222724
return ret;
27232725
}
27242726

2727+
struct reflog_migration_data {
2728+
unsigned int *index;
2729+
const char *refname;
2730+
struct ref_store *old_refs;
2731+
struct ref_transaction *transaction;
2732+
struct strbuf *errbuf;
2733+
};
2734+
2735+
static int migrate_one_reflog_entry(struct object_id *old_oid,
2736+
struct object_id *new_oid,
2737+
const char *committer,
2738+
timestamp_t timestamp, int tz,
2739+
const char *msg, void *cb_data)
2740+
{
2741+
struct reflog_migration_data *data = cb_data;
2742+
struct strbuf sb = STRBUF_INIT;
2743+
const char *date;
2744+
int ret;
2745+
2746+
date = show_date(timestamp, tz, DATE_MODE(NORMAL));
2747+
/* committer contains name and email */
2748+
strbuf_addstr(&sb, fmt_ident("", committer, WANT_BLANK_IDENT, date, 0));
2749+
2750+
ret = ref_transaction_update_reflog(data->transaction, data->refname,
2751+
new_oid, old_oid, sb.buf,
2752+
REF_HAVE_NEW | REF_HAVE_OLD, msg,
2753+
(*data->index)++, data->errbuf);
2754+
strbuf_release(&sb);
2755+
2756+
return ret;
2757+
}
2758+
2759+
static int migrate_one_reflog(const char *refname, void *cb_data)
2760+
{
2761+
struct migration_data *migration_data = cb_data;
2762+
struct reflog_migration_data data;
2763+
2764+
data.refname = refname;
2765+
data.old_refs = migration_data->old_refs;
2766+
data.transaction = migration_data->transaction;
2767+
data.errbuf = migration_data->errbuf;
2768+
data.index = &migration_data->index;
2769+
2770+
return refs_for_each_reflog_ent(migration_data->old_refs, refname,
2771+
migrate_one_reflog_entry, &data);
2772+
}
2773+
27252774
static int move_files(const char *from_path, const char *to_path, struct strbuf *errbuf)
27262775
{
27272776
struct strbuf from_buf = STRBUF_INIT, to_buf = STRBUF_INIT;
@@ -2788,13 +2837,6 @@ static int move_files(const char *from_path, const char *to_path, struct strbuf
27882837
return ret;
27892838
}
27902839

2791-
static int count_reflogs(const char *reflog UNUSED, void *payload)
2792-
{
2793-
size_t *reflog_count = payload;
2794-
(*reflog_count)++;
2795-
return 0;
2796-
}
2797-
27982840
static int has_worktrees(void)
27992841
{
28002842
struct worktree **worktrees = get_worktrees();
@@ -2820,7 +2862,6 @@ int repo_migrate_ref_storage_format(struct repository *repo,
28202862
struct ref_transaction *transaction = NULL;
28212863
struct strbuf new_gitdir = STRBUF_INIT;
28222864
struct migration_data data;
2823-
size_t reflog_count = 0;
28242865
int did_migrate_refs = 0;
28252866
int ret;
28262867

@@ -2832,21 +2873,6 @@ int repo_migrate_ref_storage_format(struct repository *repo,
28322873

28332874
old_refs = get_main_ref_store(repo);
28342875

2835-
/*
2836-
* We do not have any interfaces that would allow us to write many
2837-
* reflog entries. Once we have them we can remove this restriction.
2838-
*/
2839-
if (refs_for_each_reflog(old_refs, count_reflogs, &reflog_count) < 0) {
2840-
strbuf_addstr(errbuf, "cannot count reflogs");
2841-
ret = -1;
2842-
goto done;
2843-
}
2844-
if (reflog_count) {
2845-
strbuf_addstr(errbuf, "migrating reflogs is not supported yet");
2846-
ret = -1;
2847-
goto done;
2848-
}
2849-
28502876
/*
28512877
* Worktrees complicate the migration because every worktree has a
28522878
* separate ref storage. While it should be feasible to implement, this
@@ -2868,8 +2894,8 @@ int repo_migrate_ref_storage_format(struct repository *repo,
28682894
* 1. Set up a new temporary directory and initialize it with the new
28692895
* format. This is where all refs will be migrated into.
28702896
*
2871-
* 2. Enumerate all refs and write them into the new ref storage.
2872-
* This operation is safe as we do not yet modify the main
2897+
* 2. Enumerate all refs and reflogs and write them into the new ref
2898+
* storage. This operation is safe as we do not yet modify the main
28732899
* repository.
28742900
*
28752901
* 3. If we're in dry-run mode then we are done and can hand over the
@@ -2924,6 +2950,11 @@ int repo_migrate_ref_storage_format(struct repository *repo,
29242950
if (ret < 0)
29252951
goto done;
29262952

2953+
data.index = 1;
2954+
ret = refs_for_each_reflog(old_refs, migrate_one_reflog, &data);
2955+
if (ret < 0)
2956+
goto done;
2957+
29272958
ret = ref_transaction_commit(transaction, errbuf);
29282959
if (ret < 0)
29292960
goto done;

t/t1460-refs-migrate.sh

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,44 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
77

88
. ./test-lib.sh
99

10+
# Migrate the provided repository from one format to the other and
11+
# verify that the references and logs are migrated over correctly.
12+
# Usage: test_migration <repo> <format> <skip_reflog_verify>
13+
# <repo> is the relative path to the repo to be migrated.
14+
# <format> is the ref format to be migrated to.
15+
# <skip_reflog_verify> (true or false) whether to skip reflog verification.
1016
test_migration () {
11-
git -C "$1" for-each-ref --include-root-refs \
17+
repo=$1 &&
18+
format=$2 &&
19+
skip_reflog_verify=${3:-false} &&
20+
git -C "$repo" for-each-ref --include-root-refs \
1221
--format='%(refname) %(objectname) %(symref)' >expect &&
13-
git -C "$1" refs migrate --ref-format="$2" &&
14-
git -C "$1" for-each-ref --include-root-refs \
22+
if ! $skip_reflog_verify
23+
then
24+
git -C "$repo" reflog --all >expect_logs &&
25+
git -C "$repo" reflog list >expect_log_list
26+
fi &&
27+
28+
git -C "$repo" refs migrate --ref-format="$2" &&
29+
30+
git -C "$repo" for-each-ref --include-root-refs \
1531
--format='%(refname) %(objectname) %(symref)' >actual &&
1632
test_cmp expect actual &&
33+
if ! $skip_reflog_verify
34+
then
35+
git -C "$repo" reflog --all >actual_logs &&
36+
git -C "$repo" reflog list >actual_log_list &&
37+
test_cmp expect_logs actual_logs &&
38+
test_cmp expect_log_list actual_log_list
39+
fi &&
1740

18-
git -C "$1" rev-parse --show-ref-format >actual &&
19-
echo "$2" >expect &&
41+
git -C "$repo" rev-parse --show-ref-format >actual &&
42+
echo "$format" >expect &&
2043
test_cmp expect actual
2144
}
2245

2346
test_expect_success 'setup' '
24-
rm -rf .git &&
25-
# The migration does not yet support reflogs.
26-
git config --global core.logAllRefUpdates false
47+
rm -rf .git
2748
'
2849

2950
test_expect_success "superfluous arguments" '
@@ -78,19 +99,6 @@ do
7899
test_cmp expect err
79100
'
80101

81-
test_expect_success "$from_format -> $to_format: migration with reflog fails" '
82-
test_when_finished "rm -rf repo" &&
83-
git init --ref-format=$from_format repo &&
84-
test_config -C repo core.logAllRefUpdates true &&
85-
test_commit -C repo logged &&
86-
test_must_fail git -C repo refs migrate \
87-
--ref-format=$to_format 2>err &&
88-
cat >expect <<-EOF &&
89-
error: migrating reflogs is not supported yet
90-
EOF
91-
test_cmp expect err
92-
'
93-
94102
test_expect_success "$from_format -> $to_format: migration with worktree fails" '
95103
test_when_finished "rm -rf repo" &&
96104
git init --ref-format=$from_format repo &&
@@ -141,7 +149,7 @@ do
141149
test_commit -C repo initial &&
142150
test-tool -C repo ref-store main update-ref "" refs/heads/broken \
143151
"$(test_oid 001)" "$ZERO_OID" REF_SKIP_CREATE_REFLOG,REF_SKIP_OID_VERIFICATION &&
144-
test_migration repo "$to_format" &&
152+
test_migration repo "$to_format" true &&
145153
test_oid 001 >expect &&
146154
git -C repo rev-parse refs/heads/broken >actual &&
147155
test_cmp expect actual
@@ -195,6 +203,27 @@ do
195203
git -C repo rev-parse --show-ref-format >actual &&
196204
test_cmp expect actual
197205
'
206+
207+
test_expect_success "$from_format -> $to_format: reflogs of symrefs with target deleted" '
208+
test_when_finished "rm -rf repo" &&
209+
git init --ref-format=$from_format repo &&
210+
test_commit -C repo initial &&
211+
git -C repo branch branch-1 HEAD &&
212+
git -C repo symbolic-ref refs/heads/symref refs/heads/branch-1 &&
213+
cat >input <<-EOF &&
214+
delete refs/heads/branch-1
215+
EOF
216+
git -C repo update-ref --stdin <input &&
217+
test_migration repo "$to_format"
218+
'
219+
220+
test_expect_success "$from_format -> $to_format: reflogs order is retained" '
221+
test_when_finished "rm -rf repo" &&
222+
git init --ref-format=$from_format repo &&
223+
test_commit --date "100005000 +0700" --no-tag -C repo initial &&
224+
test_commit --date "100003000 +0700" --no-tag -C repo second &&
225+
test_migration repo "$to_format"
226+
'
198227
done
199228
done
200229

0 commit comments

Comments
 (0)