| #!/bin/sh | |
| test_description='migration of ref storage backends' | |
| GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main | |
| export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME | |
| . ./test-lib.sh | |
| print_all_reflog_entries () { | |
| repo=$1 && | |
| test-tool -C "$repo" ref-store main for-each-reflog >reflogs && | |
| while read reflog | |
| do | |
| echo "REFLOG: $reflog" && | |
| test-tool -C "$repo" ref-store main for-each-reflog-ent "$reflog" || | |
| return 1 | |
| done <reflogs | |
| } | |
| # 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> [<options...>]] | |
| # <repo> is the relative path to the repo to be migrated. | |
| # <format> is the ref format to be migrated to. | |
| # <skip_reflog_verify> (default: false) whether to skip reflog verification. | |
| # <options...> are other options be passed directly to 'git refs migrate'. | |
| test_migration () { | |
| repo=$1 && | |
| format=$2 && | |
| shift 2 && | |
| skip_reflog_verify=false && | |
| if test $# -ge 1 | |
| then | |
| skip_reflog_verify=$1 | |
| shift | |
| fi && | |
| git -C "$repo" for-each-ref --include-root-refs \ | |
| --format='%(refname) %(objectname) %(symref)' >expect && | |
| if ! $skip_reflog_verify | |
| then | |
| print_all_reflog_entries "$repo" >expect_logs | |
| fi && | |
| git -C "$repo" refs migrate --ref-storage-format="$format" "$@" && | |
| git -C "$repo" for-each-ref --include-root-refs \ | |
| --format='%(refname) %(objectname) %(symref)' >actual && | |
| test_cmp expect actual && | |
| if ! $skip_reflog_verify | |
| then | |
| print_all_reflog_entries "$repo" >actual_logs && | |
| test_cmp expect_logs actual_logs | |
| fi && | |
| git -C "$repo" rev-parse --show-ref-storage-format >actual && | |
| echo "$format" >expect && | |
| test_cmp expect actual | |
| } | |
| test_expect_success 'setup' ' | |
| rm -rf .git | |
| ' | |
| test_expect_success "superfluous arguments" ' | |
| test_when_finished "rm -rf repo" && | |
| git init repo && | |
| test_must_fail git -C repo refs migrate foo 2>err && | |
| cat >expect <<-EOF && | |
| usage: too many arguments | |
| EOF | |
| test_cmp expect err | |
| ' | |
| test_expect_success "missing ref storage format" ' | |
| test_when_finished "rm -rf repo" && | |
| git init repo && | |
| test_must_fail git -C repo refs migrate 2>err && | |
| cat >expect <<-EOF && | |
| usage: missing --ref-storage-format=<format> | |
| EOF | |
| test_cmp expect err | |
| ' | |
| test_expect_success "unknown ref storage format" ' | |
| test_when_finished "rm -rf repo" && | |
| git init repo && | |
| test_must_fail git -C repo refs migrate \ | |
| --ref-storage-format=unknown 2>err && | |
| cat >expect <<-EOF && | |
| error: unknown ref storage format ${SQ}unknown${SQ} | |
| EOF | |
| test_cmp expect err | |
| ' | |
| ref_formats="files reftable" | |
| for from_format in $ref_formats | |
| do | |
| for to_format in $ref_formats | |
| do | |
| if test "$from_format" = "$to_format" | |
| then | |
| continue | |
| fi | |
| test_expect_success "$from_format: migration to same format fails" ' | |
| test_when_finished "rm -rf repo" && | |
| git init --ref-storage-format=$from_format repo && | |
| test_must_fail git -C repo refs migrate \ | |
| --ref-storage-format=$from_format 2>err && | |
| cat >expect <<-EOF && | |
| error: repository already uses ${SQ}$from_format${SQ} format | |
| 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-storage-format=$from_format repo && | |
| git -C repo worktree add wt && | |
| test_must_fail git -C repo refs migrate \ | |
| --ref-storage-format=$to_format 2>err && | |
| cat >expect <<-EOF && | |
| error: migrating repositories with worktrees is not supported yet | |
| EOF | |
| test_cmp expect err | |
| ' | |
| test_expect_success "$from_format -> $to_format: unborn HEAD" ' | |
| test_when_finished "rm -rf repo" && | |
| git init --ref-storage-format=$from_format repo && | |
| test_migration repo "$to_format" | |
| ' | |
| test_expect_success "$from_format -> $to_format: single ref" ' | |
| test_when_finished "rm -rf repo" && | |
| git init --ref-storage-format=$from_format repo && | |
| test_commit -C repo initial && | |
| test_migration repo "$to_format" | |
| ' | |
| test_expect_success "$from_format -> $to_format: bare repository" ' | |
| test_when_finished "rm -rf repo repo.git" && | |
| git init --ref-storage-format=$from_format repo && | |
| test_commit -C repo initial && | |
| git clone --ref-storage-format=$from_format --mirror repo repo.git && | |
| test_migration repo.git "$to_format" | |
| ' | |
| test_expect_success "$from_format -> $to_format: dangling symref" ' | |
| test_when_finished "rm -rf repo" && | |
| git init --ref-storage-format=$from_format repo && | |
| test_commit -C repo initial && | |
| git -C repo symbolic-ref BROKEN_HEAD refs/heads/nonexistent && | |
| test_migration repo "$to_format" && | |
| echo refs/heads/nonexistent >expect && | |
| git -C repo symbolic-ref BROKEN_HEAD >actual && | |
| test_cmp expect actual | |
| ' | |
| test_expect_success "$from_format -> $to_format: broken ref" ' | |
| test_when_finished "rm -rf repo" && | |
| git init --ref-storage-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" true && | |
| test_oid 001 >expect && | |
| git -C repo rev-parse refs/heads/broken >actual && | |
| test_cmp expect actual | |
| ' | |
| test_expect_success "$from_format -> $to_format: pseudo-refs" ' | |
| test_when_finished "rm -rf repo" && | |
| git init --ref-storage-format=$from_format repo && | |
| test_commit -C repo initial && | |
| git -C repo update-ref FOO_HEAD HEAD && | |
| test_migration repo "$to_format" | |
| ' | |
| test_expect_success "$from_format -> $to_format: special refs are left alone" ' | |
| test_when_finished "rm -rf repo" && | |
| git init --ref-storage-format=$from_format repo && | |
| test_commit -C repo initial && | |
| git -C repo rev-parse HEAD >repo/.git/MERGE_HEAD && | |
| git -C repo rev-parse MERGE_HEAD && | |
| test_migration repo "$to_format" && | |
| test_path_is_file repo/.git/MERGE_HEAD | |
| ' | |
| test_expect_success "$from_format -> $to_format: a bunch of refs" ' | |
| test_when_finished "rm -rf repo" && | |
| git init --ref-storage-format=$from_format repo && | |
| test_commit -C repo initial && | |
| cat >input <<-EOF && | |
| create FOO_HEAD HEAD | |
| create refs/heads/branch-1 HEAD | |
| create refs/heads/branch-2 HEAD | |
| create refs/heads/branch-3 HEAD | |
| create refs/heads/branch-4 HEAD | |
| create refs/tags/tag-1 HEAD | |
| create refs/tags/tag-2 HEAD | |
| EOF | |
| git -C repo update-ref --stdin <input && | |
| test_migration repo "$to_format" | |
| ' | |
| test_expect_success "$from_format -> $to_format: dry-run migration does not modify repository" ' | |
| test_when_finished "rm -rf repo" && | |
| git init --ref-storage-format=$from_format repo && | |
| test_commit -C repo initial && | |
| git -C repo refs migrate --dry-run \ | |
| --ref-storage-format=$to_format >output && | |
| test_grep "Finished dry-run migration of refs" output && | |
| test_path_is_dir repo/.git/ref_migration.* && | |
| echo $from_format >expect && | |
| git -C repo rev-parse --show-ref-storage-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-storage-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-storage-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" | |
| ' | |
| test_expect_success "$from_format -> $to_format: stash is retained" ' | |
| test_when_finished "rm -rf repo" && | |
| git init --ref-storage-format=$from_format repo && | |
| ( | |
| cd repo && | |
| test_commit initial A && | |
| echo foo >A && | |
| git stash push && | |
| echo bar >A && | |
| git stash push && | |
| git stash list >expect.reflog && | |
| test_migration . "$to_format" && | |
| git stash list >actual.reflog && | |
| test_cmp expect.reflog actual.reflog | |
| ) | |
| ' | |
| test_expect_success "$from_format -> $to_format: skip reflog with --skip-reflog" ' | |
| test_when_finished "rm -rf repo" && | |
| git init --ref-storage-format=$from_format repo && | |
| test_commit -C repo initial && | |
| # we see that the repository contains reflogs. | |
| git -C repo reflog --all >reflogs && | |
| test_line_count = 2 reflogs && | |
| test_migration repo "$to_format" true --no-reflog && | |
| # there should be no reflogs post migration. | |
| git -C repo reflog --all >reflogs && | |
| test_must_be_empty reflogs | |
| ' | |
| done | |
| done | |
| test_expect_success 'multiple reftable blocks with multiple entries' ' | |
| test_when_finished "rm -rf repo" && | |
| git init --ref-storage-format=files repo && | |
| test_commit -C repo first && | |
| test_seq -f "create refs/heads/ref-%d HEAD" 5000 | | |
| git -C repo update-ref --stdin && | |
| test_commit -C repo second && | |
| test_seq -f "update refs/heads/ref-%d HEAD" 3000 | | |
| git -C repo update-ref --stdin && | |
| test_migration repo reftable true | |
| ' | |
| test_expect_success 'migrating from files format deletes backend files' ' | |
| test_when_finished "rm -rf repo" && | |
| git init --ref-storage-format=files repo && | |
| test_commit -C repo first && | |
| git -C repo pack-refs --all && | |
| test_commit -C repo second && | |
| git -C repo update-ref ORIG_HEAD HEAD && | |
| git -C repo rev-parse HEAD >repo/.git/FETCH_HEAD && | |
| test_path_is_file repo/.git/HEAD && | |
| test_path_is_file repo/.git/ORIG_HEAD && | |
| test_path_is_file repo/.git/refs/heads/main && | |
| test_path_is_file repo/.git/packed-refs && | |
| test_migration repo reftable && | |
| echo "ref: refs/heads/.invalid" >expect && | |
| test_cmp expect repo/.git/HEAD && | |
| echo "this repository uses the reftable format" >expect && | |
| test_cmp expect repo/.git/refs/heads && | |
| test_path_is_file repo/.git/FETCH_HEAD && | |
| test_path_is_missing repo/.git/ORIG_HEAD && | |
| test_path_is_missing repo/.git/refs/heads/main && | |
| test_path_is_missing repo/.git/logs && | |
| test_path_is_missing repo/.git/packed-refs | |
| ' | |
| test_expect_success 'migrating from reftable format deletes backend files' ' | |
| test_when_finished "rm -rf repo" && | |
| git init --ref-storage-format=reftable repo && | |
| test_commit -C repo first && | |
| test_path_is_dir repo/.git/reftable && | |
| test_migration repo files && | |
| test_path_is_missing repo/.git/reftable && | |
| echo "ref: refs/heads/main" >expect && | |
| test_cmp expect repo/.git/HEAD && | |
| test_path_is_file repo/.git/packed-refs | |
| ' | |
| test_done |