1#!/bin/sh 2 3test_description='git filter-branch' 4GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main 5export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME 6 7. ./test-lib.sh 8. "$TEST_DIRECTORY/lib-gpg.sh" 9 10test_expect_success 'setup' ' 11 test_commit A && 12 GIT_COMMITTER_DATE="@0 +0000" GIT_AUTHOR_DATE="@0 +0000" && 13 test_commit --notick B && 14 git checkout -b branch B && 15 test_commit D && 16 mkdir dir && 17 test_commit dir/D && 18 test_commit E && 19 git checkout main && 20 test_commit C && 21 git checkout branch && 22 git merge C && 23 git tag F && 24 test_commit G && 25 test_commit H 26' 27# * (HEAD, branch) H 28# * G 29# * Merge commit 'C' into branch 30# |\ 31# | * (main) C 32# * | E 33# * | dir/D 34# * | D 35# |/ 36# * B 37# * A 38 39 40H=$(git rev-parse H) 41 42test_expect_success 'rewrite identically' ' 43 git filter-branch branch 44' 45test_expect_success 'result is really identical' ' 46 test $H = $(git rev-parse HEAD) 47' 48 49test_expect_success 'rewrite bare repository identically' ' 50 (git config core.bare true && cd .git && 51 git filter-branch branch > filter-output 2>&1 && 52 ! fgrep fatal filter-output) 53' 54git config core.bare false 55test_expect_success 'result is really identical' ' 56 test $H = $(git rev-parse HEAD) 57' 58 59TRASHDIR=$(pwd) 60test_expect_success 'correct GIT_DIR while using -d' ' 61 mkdir drepo && 62 ( cd drepo && 63 git init && 64 test_commit drepo && 65 git filter-branch -d "$TRASHDIR/dfoo" \ 66 --index-filter "cp \"$TRASHDIR\"/dfoo/backup-refs \"$TRASHDIR\"" \ 67 ) && 68 grep drepo "$TRASHDIR/backup-refs" 69' 70 71test_expect_success 'tree-filter works with -d' ' 72 git init drepo-tree && 73 ( 74 cd drepo-tree && 75 test_commit one && 76 git filter-branch -d "$TRASHDIR/dfoo" \ 77 --tree-filter "echo changed >one.t" && 78 echo changed >expect && 79 git cat-file blob HEAD:one.t >actual && 80 test_cmp expect actual && 81 test_cmp one.t actual 82 ) 83' 84 85test_expect_success 'Fail if commit filter fails' ' 86 test_must_fail git filter-branch -f --commit-filter "exit 1" HEAD 87' 88 89test_expect_success 'rewrite, renaming a specific file' ' 90 git filter-branch -f --tree-filter "mv D.t doh || :" HEAD 91' 92 93test_expect_success 'test that the file was renamed' ' 94 test D = "$(git show HEAD:doh --)" && 95 ! test -f D.t && 96 test -f doh && 97 test D = "$(cat doh)" 98' 99 100test_expect_success 'rewrite, renaming a specific directory' ' 101 git filter-branch -f --tree-filter "mv dir diroh || :" HEAD 102' 103 104test_expect_success 'test that the directory was renamed' ' 105 test dir/D = "$(git show HEAD:diroh/D.t --)" && 106 ! test -d dir && 107 test -d diroh && 108 ! test -d diroh/dir && 109 test -f diroh/D.t && 110 test dir/D = "$(cat diroh/D.t)" 111' 112 113V=$(git rev-parse HEAD) 114 115test_expect_success 'populate --state-branch' ' 116 git filter-branch --state-branch state -f --tree-filter "touch file || :" HEAD 117' 118 119W=$(git rev-parse HEAD) 120 121test_expect_success 'using --state-branch to skip already rewritten commits' ' 122 test_when_finished git reset --hard $V && 123 git reset --hard $V && 124 git filter-branch --state-branch state -f --tree-filter "touch file || :" HEAD && 125 test_cmp_rev $W HEAD 126' 127 128git tag oldD HEAD~4 129test_expect_success 'rewrite one branch, keeping a side branch' ' 130 git branch modD oldD && 131 git filter-branch -f --tree-filter "mv B.t boh || :" D..modD 132' 133 134test_expect_success 'common ancestor is still common (unchanged)' ' 135 test "$(git merge-base modD D)" = "$(git rev-parse B)" 136' 137 138test_expect_success 'filter subdirectory only' ' 139 mkdir subdir && 140 touch subdir/new && 141 git add subdir/new && 142 test_tick && 143 git commit -m "subdir" && 144 echo H > A.t && 145 test_tick && 146 git commit -m "not subdir" A.t && 147 echo A > subdir/new && 148 test_tick && 149 git commit -m "again subdir" subdir/new && 150 git rm A.t && 151 test_tick && 152 git commit -m "again not subdir" && 153 git branch sub && 154 git branch sub-earlier HEAD~2 && 155 git filter-branch -f --subdirectory-filter subdir \ 156 refs/heads/sub refs/heads/sub-earlier 157' 158 159test_expect_success 'subdirectory filter result looks okay' ' 160 test 2 = $(git rev-list sub | wc -l) && 161 git show sub:new && 162 test_must_fail git show sub:subdir && 163 git show sub-earlier:new && 164 test_must_fail git show sub-earlier:subdir 165' 166 167test_expect_success 'more setup' ' 168 git checkout main && 169 mkdir subdir && 170 echo A > subdir/new && 171 git add subdir/new && 172 test_tick && 173 git commit -m "subdir on main" subdir/new && 174 git rm A.t && 175 test_tick && 176 git commit -m "again subdir on main" && 177 git merge branch 178' 179 180test_expect_success 'use index-filter to move into a subdirectory' ' 181 git branch directorymoved && 182 git filter-branch -f --index-filter \ 183 "git ls-files -s | sed \"s- -&newsubdir/-\" | 184 GIT_INDEX_FILE=\$GIT_INDEX_FILE.new \ 185 git update-index --index-info && 186 mv \"\$GIT_INDEX_FILE.new\" \"\$GIT_INDEX_FILE\"" directorymoved && 187 git diff --exit-code HEAD directorymoved:newsubdir 188' 189 190test_expect_success 'stops when msg filter fails' ' 191 old=$(git rev-parse HEAD) && 192 test_must_fail git filter-branch -f --msg-filter false HEAD && 193 test $old = $(git rev-parse HEAD) && 194 rm -rf .git-rewrite 195' 196 197test_expect_success 'author information is preserved' ' 198 : > i && 199 git add i && 200 test_tick && 201 GIT_AUTHOR_NAME="B V Uips" git commit -m bvuips && 202 git branch preserved-author && 203 (sane_unset GIT_AUTHOR_NAME && 204 git filter-branch -f --msg-filter "cat; \ 205 test \$GIT_COMMIT != $(git rev-parse main) || \ 206 echo Hallo" \ 207 preserved-author) && 208 git rev-list --author="B V Uips" preserved-author >actual && 209 test_line_count = 1 actual 210' 211 212test_expect_success "remove a certain author's commits" ' 213 echo i > i && 214 test_tick && 215 git commit -m i i && 216 git branch removed-author && 217 git filter-branch -f --commit-filter "\ 218 if [ \"\$GIT_AUTHOR_NAME\" = \"B V Uips\" ];\ 219 then\ 220 skip_commit \"\$@\"; 221 else\ 222 git commit-tree \"\$@\";\ 223 fi" removed-author && 224 cnt1=$(git rev-list main | wc -l) && 225 cnt2=$(git rev-list removed-author | wc -l) && 226 test $cnt1 -eq $(($cnt2 + 1)) && 227 git rev-list --author="B V Uips" removed-author >actual && 228 test_line_count = 0 actual 229' 230 231test_expect_success 'barf on invalid name' ' 232 test_must_fail git filter-branch -f main xy-problem && 233 test_must_fail git filter-branch -f HEAD^ 234' 235 236test_expect_success '"map" works in commit filter' ' 237 git filter-branch -f --commit-filter "\ 238 parent=\$(git rev-parse \$GIT_COMMIT^) && 239 mapped=\$(map \$parent) && 240 actual=\$(echo \"\$@\" | sed \"s/^.*-p //\") && 241 test \$mapped = \$actual && 242 git commit-tree \"\$@\";" main~2..main && 243 git rev-parse --verify main 244' 245 246test_expect_success 'Name needing quotes' ' 247 248 git checkout -b rerere A && 249 mkdir foo && 250 name="れれれ" && 251 >foo/$name && 252 git add foo && 253 git commit -m "Adding a file" && 254 git filter-branch --tree-filter "rm -fr foo" && 255 test_must_fail git ls-files --error-unmatch "foo/$name" && 256 test $(git rev-parse --verify rerere) != $(git rev-parse --verify A) 257 258' 259 260test_expect_success 'Subdirectory filter with disappearing trees' ' 261 git reset --hard && 262 git checkout main && 263 264 mkdir foo && 265 touch foo/bar && 266 git add foo && 267 test_tick && 268 git commit -m "Adding foo" && 269 270 git rm -r foo && 271 test_tick && 272 git commit -m "Removing foo" && 273 274 mkdir foo && 275 touch foo/bar && 276 git add foo && 277 test_tick && 278 git commit -m "Re-adding foo" && 279 280 git filter-branch -f --subdirectory-filter foo && 281 git rev-list main >actual && 282 test_line_count = 3 actual 283' 284 285test_expect_success 'Tag name filtering retains tag message' ' 286 git tag -m atag T && 287 git cat-file tag T > expect && 288 git filter-branch -f --tag-name-filter cat && 289 git cat-file tag T > actual && 290 test_cmp expect actual 291' 292 293faux_gpg_tag='object XXXXXX 294type commit 295tag S 296tagger T A Gger <tagger@example.com> 1206026339 -0500 297 298This is a faux gpg signed tag. 299-----BEGIN PGP SIGNATURE----- 300Version: FauxGPG v0.0.0 (FAUX/Linux) 301 302gdsfoewhxu/6l06f1kxyxhKdZkrcbaiOMtkJUA9ITAc1mlamh0ooasxkH1XwMbYQ 303acmwXaWET20H0GeAGP+7vow= 304=agpO 305-----END PGP SIGNATURE----- 306' 307test_expect_success 'Tag name filtering strips gpg signature' ' 308 sha1=$(git rev-parse HEAD) && 309 sha1t=$(echo "$faux_gpg_tag" | sed -e s/XXXXXX/$sha1/ | git mktag) && 310 git update-ref "refs/tags/S" "$sha1t" && 311 echo "$faux_gpg_tag" | sed -e s/XXXXXX/$sha1/ | head -n 6 > expect && 312 git filter-branch -f --tag-name-filter cat && 313 git cat-file tag S > actual && 314 test_cmp expect actual 315' 316 317test_expect_success GPG 'Filtering retains message of gpg signed commit' ' 318 mkdir gpg && 319 touch gpg/foo && 320 git add gpg && 321 test_tick && 322 git commit -S -m "Adding gpg" && 323 324 git log -1 --format="%s" > expect && 325 git filter-branch -f --msg-filter "cat" && 326 git log -1 --format="%s" > actual && 327 test_cmp expect actual 328' 329 330test_expect_success 'Tag name filtering allows slashes in tag names' ' 331 git tag -m tag-with-slash X/1 && 332 git cat-file tag X/1 | sed -e s,X/1,X/2, > expect && 333 git filter-branch -f --tag-name-filter "echo X/2" && 334 git cat-file tag X/2 > actual && 335 test_cmp expect actual 336' 337test_expect_success 'setup --prune-empty comparisons' ' 338 git checkout --orphan main-no-a && 339 git rm -rf . && 340 unset test_tick && 341 test_tick && 342 GIT_COMMITTER_DATE="@0 +0000" GIT_AUTHOR_DATE="@0 +0000" && 343 test_commit --notick B B.t B Bx && 344 git checkout -b branch-no-a Bx && 345 test_commit D D.t D Dx && 346 mkdir dir && 347 test_commit dir/D dir/D.t dir/D dir/Dx && 348 test_commit E E.t E Ex && 349 git checkout main-no-a && 350 test_commit C C.t C Cx && 351 git checkout branch-no-a && 352 git merge Cx -m "Merge tag '\''C'\'' into branch" && 353 git tag Fx && 354 test_commit G G.t G Gx && 355 test_commit H H.t H Hx && 356 git checkout branch 357' 358 359test_expect_success 'Prune empty commits' ' 360 git rev-list HEAD > expect && 361 test_commit to_remove && 362 git filter-branch -f --index-filter "git update-index --remove to_remove.t" --prune-empty HEAD && 363 git rev-list HEAD > actual && 364 test_cmp expect actual 365' 366 367test_expect_success 'prune empty collapsed merges' ' 368 test_config merge.ff false && 369 git rev-list HEAD >expect && 370 test_commit to_remove_2 && 371 git reset --hard HEAD^ && 372 test_merge non-ff to_remove_2 && 373 git filter-branch -f --index-filter "git update-index --remove to_remove_2.t" --prune-empty HEAD && 374 git rev-list HEAD >actual && 375 test_cmp expect actual 376' 377 378test_expect_success 'prune empty works even without index/tree filters' ' 379 git rev-list HEAD >expect && 380 git commit --allow-empty -m empty && 381 git filter-branch -f --prune-empty HEAD && 382 git rev-list HEAD >actual && 383 test_cmp expect actual 384' 385 386test_expect_success '--prune-empty is able to prune root commit' ' 387 git rev-list branch-no-a >expect && 388 git branch testing H && 389 git filter-branch -f --prune-empty --index-filter "git update-index --remove A.t" testing && 390 git rev-list testing >actual && 391 git branch -D testing && 392 test_cmp expect actual 393' 394 395test_expect_success '--prune-empty is able to prune entire branch' ' 396 git branch prune-entire B && 397 git filter-branch -f --prune-empty --index-filter "git update-index --remove A.t B.t" prune-entire && 398 test_must_fail git rev-parse refs/heads/prune-entire && 399 if test_have_prereq REFFILES 400 then 401 test_must_fail git reflog exists refs/heads/prune-entire 402 fi 403' 404 405test_expect_success '--remap-to-ancestor with filename filters' ' 406 git checkout main && 407 git reset --hard A && 408 test_commit add-foo foo 1 && 409 git branch moved-foo && 410 test_commit add-bar bar a && 411 git branch invariant && 412 orig_invariant=$(git rev-parse invariant) && 413 git branch moved-bar && 414 test_commit change-foo foo 2 && 415 git filter-branch -f --remap-to-ancestor \ 416 moved-foo moved-bar A..main \ 417 -- -- foo && 418 test $(git rev-parse moved-foo) = $(git rev-parse moved-bar) && 419 test $(git rev-parse moved-foo) = $(git rev-parse main^) && 420 test $orig_invariant = $(git rev-parse invariant) 421' 422 423test_expect_success 'automatic remapping to ancestor with filename filters' ' 424 git checkout main && 425 git reset --hard A && 426 test_commit add-foo2 foo 1 && 427 git branch moved-foo2 && 428 test_commit add-bar2 bar a && 429 git branch invariant2 && 430 orig_invariant=$(git rev-parse invariant2) && 431 git branch moved-bar2 && 432 test_commit change-foo2 foo 2 && 433 git filter-branch -f \ 434 moved-foo2 moved-bar2 A..main \ 435 -- -- foo && 436 test $(git rev-parse moved-foo2) = $(git rev-parse moved-bar2) && 437 test $(git rev-parse moved-foo2) = $(git rev-parse main^) && 438 test $orig_invariant = $(git rev-parse invariant2) 439' 440 441test_expect_success 'setup submodule' ' 442 rm -fr ?* .git && 443 git init && 444 test_commit file && 445 mkdir submod && 446 submodurl="$PWD/submod" && 447 ( cd submod && 448 git init && 449 test_commit file-in-submod ) && 450 git submodule add "$submodurl" && 451 git commit -m "added submodule" && 452 test_commit add-file && 453 ( cd submod && test_commit add-in-submodule ) && 454 git add submod && 455 git commit -m "changed submodule" && 456 git branch original HEAD 457' 458 459orig_head=$(git show-ref --hash --head HEAD) 460 461test_expect_success 'rewrite submodule with another content' ' 462 git filter-branch --tree-filter "test -d submod && { 463 rm -rf submod && 464 git rm -rf --quiet submod && 465 mkdir submod && 466 : > submod/file 467 } || :" HEAD && 468 test $orig_head != $(git show-ref --hash --head HEAD) 469' 470 471test_expect_success 'replace submodule revision' ' 472 invalid=$(test_oid numeric) && 473 git reset --hard original && 474 git filter-branch -f --tree-filter \ 475 "if git ls-files --error-unmatch -- submod > /dev/null 2>&1 476 then git update-index --cacheinfo 160000 $invalid submod 477 fi" HEAD && 478 test $orig_head != $(git show-ref --hash --head HEAD) 479' 480 481test_expect_success 'filter commit message without trailing newline' ' 482 git reset --hard original && 483 commit=$(printf "no newline" | git commit-tree HEAD^{tree}) && 484 git update-ref refs/heads/no-newline $commit && 485 git filter-branch -f refs/heads/no-newline && 486 echo $commit >expect && 487 git rev-parse refs/heads/no-newline >actual && 488 test_cmp expect actual 489' 490 491test_expect_success 'tree-filter deals with object name vs pathname ambiguity' ' 492 test_when_finished "git reset --hard original" && 493 ambiguous=$(git rev-list -1 HEAD) && 494 git filter-branch --tree-filter "mv file.t $ambiguous" HEAD^.. && 495 git show HEAD:$ambiguous 496' 497 498test_expect_success 'rewrite repository including refs that point at non-commit object' ' 499 test_when_finished "git reset --hard original" && 500 tree=$(git rev-parse HEAD^{tree}) && 501 test_when_finished "git replace -d $tree" && 502 echo A >new && 503 git add new && 504 new_tree=$(git write-tree) && 505 git replace $tree $new_tree && 506 git tag -a -m "tag to a tree" treetag $new_tree && 507 git reset --hard HEAD && 508 git filter-branch -f -- --all >filter-output 2>&1 && 509 ! fgrep fatal filter-output 510' 511 512test_expect_success 'filter-branch handles ref deletion' ' 513 git switch --orphan empty-commit && 514 git commit --allow-empty -m "empty commit" && 515 git tag empty && 516 git branch to-delete && 517 git filter-branch -f --prune-empty to-delete >out 2>&1 && 518 grep "to-delete.*was deleted" out && 519 test_must_fail git rev-parse --verify to-delete 520' 521 522test_expect_success 'filter-branch handles ref rewrite' ' 523 git checkout empty && 524 test_commit to-drop && 525 git branch rewrite && 526 git filter-branch -f \ 527 --index-filter "git rm --ignore-unmatch --cached to-drop.t" \ 528 rewrite >out 2>&1 && 529 grep "rewrite.*was rewritten" out && 530 ! grep -i warning out && 531 git diff-tree empty rewrite 532' 533 534test_expect_success 'filter-branch handles ancestor rewrite' ' 535 test_commit to-exclude && 536 git branch ancestor && 537 git filter-branch -f ancestor -- :^to-exclude.t >out 2>&1 && 538 grep "ancestor.*was rewritten" out && 539 ! grep -i warning out && 540 git diff-tree HEAD^ ancestor 541' 542 543test_done 544