Skip to content

Commit 246cebe

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. Helped-by: Patrick Steinhardt <[email protected]> Signed-off-by: Karthik Nayak <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 297c09e commit 246cebe

File tree

3 files changed

+115
-52
lines changed

3 files changed

+115
-52
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: 64 additions & 28 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
@@ -2673,6 +2674,7 @@ struct migration_data {
26732674
struct ref_store *old_refs;
26742675
struct ref_transaction *transaction;
26752676
struct strbuf *errbuf;
2677+
struct strbuf sb;
26762678
};
26772679

26782680
static int migrate_one_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
@@ -2705,6 +2707,52 @@ static int migrate_one_ref(const char *refname, const char *referent UNUSED, con
27052707
return ret;
27062708
}
27072709

2710+
struct reflog_migration_data {
2711+
unsigned int index;
2712+
const char *refname;
2713+
struct ref_store *old_refs;
2714+
struct ref_transaction *transaction;
2715+
struct strbuf *errbuf;
2716+
struct strbuf *sb;
2717+
};
2718+
2719+
static int migrate_one_reflog_entry(struct object_id *old_oid,
2720+
struct object_id *new_oid,
2721+
const char *committer,
2722+
timestamp_t timestamp, int tz,
2723+
const char *msg, void *cb_data)
2724+
{
2725+
struct reflog_migration_data *data = cb_data;
2726+
const char *date;
2727+
int ret;
2728+
2729+
date = show_date(timestamp, tz, DATE_MODE(NORMAL));
2730+
strbuf_reset(data->sb);
2731+
/* committer contains name and email */
2732+
strbuf_addstr(data->sb, fmt_ident("", committer, WANT_BLANK_IDENT, date, 0));
2733+
2734+
ret = ref_transaction_update_reflog(data->transaction, data->refname,
2735+
new_oid, old_oid, data->sb->buf,
2736+
REF_HAVE_NEW | REF_HAVE_OLD, msg,
2737+
data->index++, data->errbuf);
2738+
return ret;
2739+
}
2740+
2741+
static int migrate_one_reflog(const char *refname, void *cb_data)
2742+
{
2743+
struct migration_data *migration_data = cb_data;
2744+
struct reflog_migration_data data = {
2745+
.refname = refname,
2746+
.old_refs = migration_data->old_refs,
2747+
.transaction = migration_data->transaction,
2748+
.errbuf = migration_data->errbuf,
2749+
.sb = &migration_data->sb,
2750+
};
2751+
2752+
return refs_for_each_reflog_ent(migration_data->old_refs, refname,
2753+
migrate_one_reflog_entry, &data);
2754+
}
2755+
27082756
static int move_files(const char *from_path, const char *to_path, struct strbuf *errbuf)
27092757
{
27102758
struct strbuf from_buf = STRBUF_INIT, to_buf = STRBUF_INIT;
@@ -2771,13 +2819,6 @@ static int move_files(const char *from_path, const char *to_path, struct strbuf
27712819
return ret;
27722820
}
27732821

2774-
static int count_reflogs(const char *reflog UNUSED, void *payload)
2775-
{
2776-
size_t *reflog_count = payload;
2777-
(*reflog_count)++;
2778-
return 0;
2779-
}
2780-
27812822
static int has_worktrees(void)
27822823
{
27832824
struct worktree **worktrees = get_worktrees();
@@ -2802,8 +2843,9 @@ int repo_migrate_ref_storage_format(struct repository *repo,
28022843
struct ref_store *old_refs = NULL, *new_refs = NULL;
28032844
struct ref_transaction *transaction = NULL;
28042845
struct strbuf new_gitdir = STRBUF_INIT;
2805-
struct migration_data data;
2806-
size_t reflog_count = 0;
2846+
struct migration_data data = {
2847+
.sb = STRBUF_INIT,
2848+
};
28072849
int did_migrate_refs = 0;
28082850
int ret;
28092851

@@ -2815,21 +2857,6 @@ int repo_migrate_ref_storage_format(struct repository *repo,
28152857

28162858
old_refs = get_main_ref_store(repo);
28172859

2818-
/*
2819-
* We do not have any interfaces that would allow us to write many
2820-
* reflog entries. Once we have them we can remove this restriction.
2821-
*/
2822-
if (refs_for_each_reflog(old_refs, count_reflogs, &reflog_count) < 0) {
2823-
strbuf_addstr(errbuf, "cannot count reflogs");
2824-
ret = -1;
2825-
goto done;
2826-
}
2827-
if (reflog_count) {
2828-
strbuf_addstr(errbuf, "migrating reflogs is not supported yet");
2829-
ret = -1;
2830-
goto done;
2831-
}
2832-
28332860
/*
28342861
* Worktrees complicate the migration because every worktree has a
28352862
* separate ref storage. While it should be feasible to implement, this
@@ -2855,17 +2882,21 @@ int repo_migrate_ref_storage_format(struct repository *repo,
28552882
* This operation is safe as we do not yet modify the main
28562883
* repository.
28572884
*
2858-
* 3. If we're in dry-run mode then we are done and can hand over the
2885+
* 3. Enumerate all reflogs and write them into the new ref storage.
2886+
* This operation is safe as we do not yet modify the main
2887+
* repository.
2888+
*
2889+
* 4. If we're in dry-run mode then we are done and can hand over the
28592890
* directory to the caller for inspection. If not, we now start
28602891
* with the destructive part.
28612892
*
2862-
* 4. Delete the old ref storage from disk. As we have a copy of refs
2893+
* 5. Delete the old ref storage from disk. As we have a copy of refs
28632894
* in the new ref storage it's okay(ish) if we now get interrupted
28642895
* as there is an equivalent copy of all refs available.
28652896
*
2866-
* 5. Move the new ref storage files into place.
2897+
* 6. Move the new ref storage files into place.
28672898
*
2868-
* 6. Change the repository format to the new ref format.
2899+
* 7. Change the repository format to the new ref format.
28692900
*/
28702901
strbuf_addf(&new_gitdir, "%s/%s", old_refs->gitdir, "ref_migration.XXXXXX");
28712902
if (!mkdtemp(new_gitdir.buf)) {
@@ -2907,6 +2938,10 @@ int repo_migrate_ref_storage_format(struct repository *repo,
29072938
if (ret < 0)
29082939
goto done;
29092940

2941+
ret = refs_for_each_reflog(old_refs, migrate_one_reflog, &data);
2942+
if (ret < 0)
2943+
goto done;
2944+
29102945
ret = ref_transaction_commit(transaction, errbuf);
29112946
if (ret < 0)
29122947
goto done;
@@ -2982,6 +3017,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,
29823017
}
29833018
ref_transaction_free(transaction);
29843019
strbuf_release(&new_gitdir);
3020+
strbuf_release(&data.sb);
29853021
return ret;
29863022
}
29873023

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)