| 1 | #!/bin/sh |
| 2 | |
| 3 | test_description='migration of ref storage backends' |
| 4 | |
| 5 | GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main |
| 6 | export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME |
| 7 | |
| 8 | . ./test-lib.sh |
| 9 | |
| 10 | print_all_reflog_entries () { |
| 11 | repo=$1 && |
| 12 | test-tool -C "$repo" ref-store main for-each-reflog >reflogs && |
| 13 | while read reflog |
| 14 | do |
| 15 | echo "REFLOG: $reflog" && |
| 16 | test-tool -C "$repo" ref-store main for-each-reflog-ent "$reflog" || |
| 17 | return 1 |
| 18 | done <reflogs |
| 19 | } |
| 20 | |
| 21 | # Migrate the provided repository from one format to the other and |
| 22 | # verify that the references and logs are migrated over correctly. |
| 23 | # Usage: test_migration <repo> <format> [<skip_reflog_verify> [<options...>]] |
| 24 | # <repo> is the relative path to the repo to be migrated. |
| 25 | # <format> is the ref format to be migrated to. |
| 26 | # <skip_reflog_verify> (default: false) whether to skip reflog verification. |
| 27 | # <options...> are other options be passed directly to 'git refs migrate'. |
| 28 | test_migration () { |
| 29 | repo=$1 && |
| 30 | format=$2 && |
| 31 | shift 2 && |
| 32 | skip_reflog_verify=false && |
| 33 | if test $# -ge 1 |
| 34 | then |
| 35 | skip_reflog_verify=$1 |
| 36 | shift |
| 37 | fi && |
| 38 | git -C "$repo" for-each-ref --include-root-refs \ |
| 39 | --format='%(refname) %(objectname) %(symref)' >expect && |
| 40 | if ! $skip_reflog_verify |
| 41 | then |
| 42 | print_all_reflog_entries "$repo" >expect_logs |
| 43 | fi && |
| 44 | |
| 45 | git -C "$repo" refs migrate --ref-format="$format" "$@" && |
| 46 | |
| 47 | git -C "$repo" for-each-ref --include-root-refs \ |
| 48 | --format='%(refname) %(objectname) %(symref)' >actual && |
| 49 | test_cmp expect actual && |
| 50 | if ! $skip_reflog_verify |
| 51 | then |
| 52 | print_all_reflog_entries "$repo" >actual_logs && |
| 53 | test_cmp expect_logs actual_logs |
| 54 | fi && |
| 55 | |
| 56 | git -C "$repo" rev-parse --show-ref-format >actual && |
| 57 | echo "$format" >expect && |
| 58 | test_cmp expect actual |
| 59 | } |
| 60 | |
| 61 | test_expect_success 'setup' ' |
| 62 | rm -rf .git |
| 63 | ' |
| 64 | |
| 65 | test_expect_success "superfluous arguments" ' |
| 66 | test_when_finished "rm -rf repo" && |
| 67 | git init repo && |
| 68 | test_must_fail git -C repo refs migrate foo 2>err && |
| 69 | cat >expect <<-EOF && |
| 70 | usage: too many arguments |
| 71 | EOF |
| 72 | test_cmp expect err |
| 73 | ' |
| 74 | |
| 75 | test_expect_success "missing ref storage format" ' |
| 76 | test_when_finished "rm -rf repo" && |
| 77 | git init repo && |
| 78 | test_must_fail git -C repo refs migrate 2>err && |
| 79 | cat >expect <<-EOF && |
| 80 | usage: missing --ref-format=<format> |
| 81 | EOF |
| 82 | test_cmp expect err |
| 83 | ' |
| 84 | |
| 85 | test_expect_success "unknown ref storage format" ' |
| 86 | test_when_finished "rm -rf repo" && |
| 87 | git init repo && |
| 88 | test_must_fail git -C repo refs migrate \ |
| 89 | --ref-format=unknown 2>err && |
| 90 | cat >expect <<-EOF && |
| 91 | error: unknown ref storage format ${SQ}unknown${SQ} |
| 92 | EOF |
| 93 | test_cmp expect err |
| 94 | ' |
| 95 | |
| 96 | ref_formats="files reftable" |
| 97 | for from_format in $ref_formats |
| 98 | do |
| 99 | for to_format in $ref_formats |
| 100 | do |
| 101 | if test "$from_format" = "$to_format" |
| 102 | then |
| 103 | continue |
| 104 | fi |
| 105 | |
| 106 | test_expect_success "$from_format: migration to same format fails" ' |
| 107 | test_when_finished "rm -rf repo" && |
| 108 | git init --ref-format=$from_format repo && |
| 109 | test_must_fail git -C repo refs migrate \ |
| 110 | --ref-format=$from_format 2>err && |
| 111 | cat >expect <<-EOF && |
| 112 | error: repository already uses ${SQ}$from_format${SQ} format |
| 113 | EOF |
| 114 | test_cmp expect err |
| 115 | ' |
| 116 | |
| 117 | test_expect_success "$from_format -> $to_format: migration with worktree fails" ' |
| 118 | test_when_finished "rm -rf repo" && |
| 119 | git init --ref-format=$from_format repo && |
| 120 | git -C repo worktree add wt && |
| 121 | test_must_fail git -C repo refs migrate \ |
| 122 | --ref-format=$to_format 2>err && |
| 123 | cat >expect <<-EOF && |
| 124 | error: migrating repositories with worktrees is not supported yet |
| 125 | EOF |
| 126 | test_cmp expect err |
| 127 | ' |
| 128 | |
| 129 | test_expect_success "$from_format -> $to_format: unborn HEAD" ' |
| 130 | test_when_finished "rm -rf repo" && |
| 131 | git init --ref-format=$from_format repo && |
| 132 | test_migration repo "$to_format" |
| 133 | ' |
| 134 | |
| 135 | test_expect_success "$from_format -> $to_format: single ref" ' |
| 136 | test_when_finished "rm -rf repo" && |
| 137 | git init --ref-format=$from_format repo && |
| 138 | test_commit -C repo initial && |
| 139 | test_migration repo "$to_format" |
| 140 | ' |
| 141 | |
| 142 | test_expect_success "$from_format -> $to_format: bare repository" ' |
| 143 | test_when_finished "rm -rf repo repo.git" && |
| 144 | git init --ref-format=$from_format repo && |
| 145 | test_commit -C repo initial && |
| 146 | git clone --ref-format=$from_format --mirror repo repo.git && |
| 147 | test_migration repo.git "$to_format" |
| 148 | ' |
| 149 | |
| 150 | test_expect_success "$from_format -> $to_format: dangling symref" ' |
| 151 | test_when_finished "rm -rf repo" && |
| 152 | git init --ref-format=$from_format repo && |
| 153 | test_commit -C repo initial && |
| 154 | git -C repo symbolic-ref BROKEN_HEAD refs/heads/nonexistent && |
| 155 | test_migration repo "$to_format" && |
| 156 | echo refs/heads/nonexistent >expect && |
| 157 | git -C repo symbolic-ref BROKEN_HEAD >actual && |
| 158 | test_cmp expect actual |
| 159 | ' |
| 160 | |
| 161 | test_expect_success "$from_format -> $to_format: broken ref" ' |
| 162 | test_when_finished "rm -rf repo" && |
| 163 | git init --ref-format=$from_format repo && |
| 164 | test_commit -C repo initial && |
| 165 | test-tool -C repo ref-store main update-ref "" refs/heads/broken \ |
| 166 | "$(test_oid 001)" "$ZERO_OID" REF_SKIP_CREATE_REFLOG,REF_SKIP_OID_VERIFICATION && |
| 167 | test_migration repo "$to_format" true && |
| 168 | test_oid 001 >expect && |
| 169 | git -C repo rev-parse refs/heads/broken >actual && |
| 170 | test_cmp expect actual |
| 171 | ' |
| 172 | |
| 173 | test_expect_success "$from_format -> $to_format: pseudo-refs" ' |
| 174 | test_when_finished "rm -rf repo" && |
| 175 | git init --ref-format=$from_format repo && |
| 176 | test_commit -C repo initial && |
| 177 | git -C repo update-ref FOO_HEAD HEAD && |
| 178 | test_migration repo "$to_format" |
| 179 | ' |
| 180 | |
| 181 | test_expect_success "$from_format -> $to_format: special refs are left alone" ' |
| 182 | test_when_finished "rm -rf repo" && |
| 183 | git init --ref-format=$from_format repo && |
| 184 | test_commit -C repo initial && |
| 185 | git -C repo rev-parse HEAD >repo/.git/MERGE_HEAD && |
| 186 | git -C repo rev-parse MERGE_HEAD && |
| 187 | test_migration repo "$to_format" && |
| 188 | test_path_is_file repo/.git/MERGE_HEAD |
| 189 | ' |
| 190 | |
| 191 | test_expect_success "$from_format -> $to_format: a bunch of refs" ' |
| 192 | test_when_finished "rm -rf repo" && |
| 193 | git init --ref-format=$from_format repo && |
| 194 | |
| 195 | test_commit -C repo initial && |
| 196 | cat >input <<-EOF && |
| 197 | create FOO_HEAD HEAD |
| 198 | create refs/heads/branch-1 HEAD |
| 199 | create refs/heads/branch-2 HEAD |
| 200 | create refs/heads/branch-3 HEAD |
| 201 | create refs/heads/branch-4 HEAD |
| 202 | create refs/tags/tag-1 HEAD |
| 203 | create refs/tags/tag-2 HEAD |
| 204 | EOF |
| 205 | git -C repo update-ref --stdin <input && |
| 206 | test_migration repo "$to_format" |
| 207 | ' |
| 208 | |
| 209 | test_expect_success "$from_format -> $to_format: dry-run migration does not modify repository" ' |
| 210 | test_when_finished "rm -rf repo" && |
| 211 | git init --ref-format=$from_format repo && |
| 212 | test_commit -C repo initial && |
| 213 | git -C repo refs migrate --dry-run \ |
| 214 | --ref-format=$to_format >output && |
| 215 | test_grep "Finished dry-run migration of refs" output && |
| 216 | test_path_is_dir repo/.git/ref_migration.* && |
| 217 | echo $from_format >expect && |
| 218 | git -C repo rev-parse --show-ref-format >actual && |
| 219 | test_cmp expect actual |
| 220 | ' |
| 221 | |
| 222 | test_expect_success "$from_format -> $to_format: reflogs of symrefs with target deleted" ' |
| 223 | test_when_finished "rm -rf repo" && |
| 224 | git init --ref-format=$from_format repo && |
| 225 | test_commit -C repo initial && |
| 226 | git -C repo branch branch-1 HEAD && |
| 227 | git -C repo symbolic-ref refs/heads/symref refs/heads/branch-1 && |
| 228 | cat >input <<-EOF && |
| 229 | delete refs/heads/branch-1 |
| 230 | EOF |
| 231 | git -C repo update-ref --stdin <input && |
| 232 | test_migration repo "$to_format" |
| 233 | ' |
| 234 | |
| 235 | test_expect_success "$from_format -> $to_format: reflogs order is retained" ' |
| 236 | test_when_finished "rm -rf repo" && |
| 237 | git init --ref-format=$from_format repo && |
| 238 | test_commit --date "100005000 +0700" --no-tag -C repo initial && |
| 239 | test_commit --date "100003000 +0700" --no-tag -C repo second && |
| 240 | test_migration repo "$to_format" |
| 241 | ' |
| 242 | |
| 243 | test_expect_success "$from_format -> $to_format: stash is retained" ' |
| 244 | test_when_finished "rm -rf repo" && |
| 245 | git init --ref-format=$from_format repo && |
| 246 | ( |
| 247 | cd repo && |
| 248 | test_commit initial A && |
| 249 | echo foo >A && |
| 250 | git stash push && |
| 251 | echo bar >A && |
| 252 | git stash push && |
| 253 | git stash list >expect.reflog && |
| 254 | test_migration . "$to_format" && |
| 255 | git stash list >actual.reflog && |
| 256 | test_cmp expect.reflog actual.reflog |
| 257 | ) |
| 258 | ' |
| 259 | |
| 260 | test_expect_success "$from_format -> $to_format: skip reflog with --skip-reflog" ' |
| 261 | test_when_finished "rm -rf repo" && |
| 262 | git init --ref-format=$from_format repo && |
| 263 | test_commit -C repo initial && |
| 264 | # we see that the repository contains reflogs. |
| 265 | git -C repo reflog --all >reflogs && |
| 266 | test_line_count = 2 reflogs && |
| 267 | test_migration repo "$to_format" true --no-reflog && |
| 268 | # there should be no reflogs post migration. |
| 269 | git -C repo reflog --all >reflogs && |
| 270 | test_must_be_empty reflogs |
| 271 | ' |
| 272 | done |
| 273 | done |
| 274 | |
| 275 | test_expect_success 'multiple reftable blocks with multiple entries' ' |
| 276 | test_when_finished "rm -rf repo" && |
| 277 | git init --ref-format=files repo && |
| 278 | test_commit -C repo first && |
| 279 | test_seq -f "create refs/heads/ref-%d HEAD" 5000 | |
| 280 | git -C repo update-ref --stdin && |
| 281 | test_commit -C repo second && |
| 282 | test_seq -f "update refs/heads/ref-%d HEAD" 3000 | |
| 283 | git -C repo update-ref --stdin && |
| 284 | test_migration repo reftable true |
| 285 | ' |
| 286 | |
| 287 | test_expect_success 'migrating from files format deletes backend files' ' |
| 288 | test_when_finished "rm -rf repo" && |
| 289 | git init --ref-format=files repo && |
| 290 | test_commit -C repo first && |
| 291 | git -C repo pack-refs --all && |
| 292 | test_commit -C repo second && |
| 293 | git -C repo update-ref ORIG_HEAD HEAD && |
| 294 | git -C repo rev-parse HEAD >repo/.git/FETCH_HEAD && |
| 295 | |
| 296 | test_path_is_file repo/.git/HEAD && |
| 297 | test_path_is_file repo/.git/ORIG_HEAD && |
| 298 | test_path_is_file repo/.git/refs/heads/main && |
| 299 | test_path_is_file repo/.git/packed-refs && |
| 300 | |
| 301 | test_migration repo reftable && |
| 302 | |
| 303 | echo "ref: refs/heads/.invalid" >expect && |
| 304 | test_cmp expect repo/.git/HEAD && |
| 305 | echo "this repository uses the reftable format" >expect && |
| 306 | test_cmp expect repo/.git/refs/heads && |
| 307 | test_path_is_file repo/.git/FETCH_HEAD && |
| 308 | test_path_is_missing repo/.git/ORIG_HEAD && |
| 309 | test_path_is_missing repo/.git/refs/heads/main && |
| 310 | test_path_is_missing repo/.git/logs && |
| 311 | test_path_is_missing repo/.git/packed-refs |
| 312 | ' |
| 313 | |
| 314 | test_expect_success 'migrating from reftable format deletes backend files' ' |
| 315 | test_when_finished "rm -rf repo" && |
| 316 | git init --ref-format=reftable repo && |
| 317 | test_commit -C repo first && |
| 318 | |
| 319 | test_path_is_dir repo/.git/reftable && |
| 320 | test_migration repo files && |
| 321 | |
| 322 | test_path_is_missing repo/.git/reftable && |
| 323 | echo "ref: refs/heads/main" >expect && |
| 324 | test_cmp expect repo/.git/HEAD && |
| 325 | test_path_is_file repo/.git/packed-refs |
| 326 | ' |
| 327 | |
| 328 | test_done |