| 1 | #!/bin/sh |
| 2 | |
| 3 | test_description='git filter-branch' |
| 4 | GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main |
| 5 | export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME |
| 6 | |
| 7 | . ./test-lib.sh |
| 8 | . "$TEST_DIRECTORY/lib-gpg.sh" |
| 9 | |
| 10 | test_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 | |
| 40 | H=$(git rev-parse H) |
| 41 | |
| 42 | test_expect_success 'rewrite identically' ' |
| 43 | git filter-branch branch |
| 44 | ' |
| 45 | test_expect_success 'result is really identical' ' |
| 46 | test $H = $(git rev-parse HEAD) |
| 47 | ' |
| 48 | |
| 49 | test_expect_success 'rewrite bare repository identically' ' |
| 50 | (git config core.bare true && cd .git && |
| 51 | git filter-branch branch > filter-output 2>&1 && |
| 52 | ! grep fatal filter-output) |
| 53 | ' |
| 54 | git config core.bare false |
| 55 | test_expect_success 'result is really identical' ' |
| 56 | test $H = $(git rev-parse HEAD) |
| 57 | ' |
| 58 | |
| 59 | TRASHDIR=$(pwd) |
| 60 | test_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 | |
| 71 | test_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 | |
| 85 | test_expect_success 'Fail if commit filter fails' ' |
| 86 | test_must_fail git filter-branch -f --commit-filter "exit 1" HEAD |
| 87 | ' |
| 88 | |
| 89 | test_expect_success 'rewrite, renaming a specific file' ' |
| 90 | git filter-branch -f --tree-filter "mv D.t doh || :" HEAD |
| 91 | ' |
| 92 | |
| 93 | test_expect_success 'test that the file was renamed' ' |
| 94 | test D = "$(git show HEAD:doh --)" && |
| 95 | test_path_is_missing D.t && |
| 96 | test_path_is_file doh && |
| 97 | test D = "$(cat doh)" |
| 98 | ' |
| 99 | |
| 100 | test_expect_success 'rewrite, renaming a specific directory' ' |
| 101 | git filter-branch -f --tree-filter "mv dir diroh || :" HEAD |
| 102 | ' |
| 103 | |
| 104 | test_expect_success 'test that the directory was renamed' ' |
| 105 | test dir/D = "$(git show HEAD:diroh/D.t --)" && |
| 106 | test_path_is_missing dir && |
| 107 | test_path_is_dir diroh && |
| 108 | test_path_is_missing diroh/dir && |
| 109 | test_path_is_file diroh/D.t && |
| 110 | test dir/D = "$(cat diroh/D.t)" |
| 111 | ' |
| 112 | |
| 113 | V=$(git rev-parse HEAD) |
| 114 | |
| 115 | test_expect_success 'populate --state-branch' ' |
| 116 | git filter-branch --state-branch state -f --tree-filter "touch file || :" HEAD |
| 117 | ' |
| 118 | |
| 119 | W=$(git rev-parse HEAD) |
| 120 | |
| 121 | test_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 | |
| 128 | git tag oldD HEAD~4 |
| 129 | test_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 | |
| 134 | test_expect_success 'common ancestor is still common (unchanged)' ' |
| 135 | test "$(git merge-base modD D)" = "$(git rev-parse B)" |
| 136 | ' |
| 137 | |
| 138 | test_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 | |
| 159 | test_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 | |
| 167 | test_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 | |
| 180 | test_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 | |
| 190 | test_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 | |
| 197 | test_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 | |
| 212 | test_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 | |
| 231 | test_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 | |
| 236 | test_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 | |
| 246 | test_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 | |
| 260 | test_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 | |
| 285 | test_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 | |
| 293 | faux_gpg_tag='object XXXXXX |
| 294 | type commit |
| 295 | tag S |
| 296 | tagger T A Gger <tagger@example.com> 1206026339 -0500 |
| 297 | |
| 298 | This is a faux gpg signed tag. |
| 299 | -----BEGIN PGP SIGNATURE----- |
| 300 | Version: FauxGPG v0.0.0 (FAUX/Linux) |
| 301 | |
| 302 | gdsfoewhxu/6l06f1kxyxhKdZkrcbaiOMtkJUA9ITAc1mlamh0ooasxkH1XwMbYQ |
| 303 | acmwXaWET20H0GeAGP+7vow= |
| 304 | =agpO |
| 305 | -----END PGP SIGNATURE----- |
| 306 | ' |
| 307 | test_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 | |
| 317 | test_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 | |
| 330 | test_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 | ' |
| 337 | test_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 | |
| 359 | test_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 | |
| 367 | test_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 | |
| 378 | test_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 | |
| 386 | test_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 | |
| 395 | test_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 | test_must_fail git reflog exists refs/heads/prune-entire |
| 400 | ' |
| 401 | |
| 402 | test_expect_success '--remap-to-ancestor with filename filters' ' |
| 403 | git checkout main && |
| 404 | git reset --hard A && |
| 405 | test_commit add-foo foo 1 && |
| 406 | git branch moved-foo && |
| 407 | test_commit add-bar bar a && |
| 408 | git branch invariant && |
| 409 | orig_invariant=$(git rev-parse invariant) && |
| 410 | git branch moved-bar && |
| 411 | test_commit change-foo foo 2 && |
| 412 | git filter-branch -f --remap-to-ancestor \ |
| 413 | moved-foo moved-bar A..main \ |
| 414 | -- -- foo && |
| 415 | test $(git rev-parse moved-foo) = $(git rev-parse moved-bar) && |
| 416 | test $(git rev-parse moved-foo) = $(git rev-parse main^) && |
| 417 | test $orig_invariant = $(git rev-parse invariant) |
| 418 | ' |
| 419 | |
| 420 | test_expect_success 'automatic remapping to ancestor with filename filters' ' |
| 421 | git checkout main && |
| 422 | git reset --hard A && |
| 423 | test_commit add-foo2 foo 1 && |
| 424 | git branch moved-foo2 && |
| 425 | test_commit add-bar2 bar a && |
| 426 | git branch invariant2 && |
| 427 | orig_invariant=$(git rev-parse invariant2) && |
| 428 | git branch moved-bar2 && |
| 429 | test_commit change-foo2 foo 2 && |
| 430 | git filter-branch -f \ |
| 431 | moved-foo2 moved-bar2 A..main \ |
| 432 | -- -- foo && |
| 433 | test $(git rev-parse moved-foo2) = $(git rev-parse moved-bar2) && |
| 434 | test $(git rev-parse moved-foo2) = $(git rev-parse main^) && |
| 435 | test $orig_invariant = $(git rev-parse invariant2) |
| 436 | ' |
| 437 | |
| 438 | test_expect_success 'setup submodule' ' |
| 439 | rm -fr ?* .git && |
| 440 | git init && |
| 441 | test_commit file && |
| 442 | mkdir submod && |
| 443 | submodurl="$PWD/submod" && |
| 444 | ( cd submod && |
| 445 | git init && |
| 446 | test_commit file-in-submod ) && |
| 447 | git submodule add "$submodurl" && |
| 448 | git commit -m "added submodule" && |
| 449 | test_commit add-file && |
| 450 | ( cd submod && test_commit add-in-submodule ) && |
| 451 | git add submod && |
| 452 | git commit -m "changed submodule" && |
| 453 | git branch original HEAD |
| 454 | ' |
| 455 | |
| 456 | orig_head=$(git show-ref --hash --head HEAD) |
| 457 | |
| 458 | test_expect_success 'rewrite submodule with another content' ' |
| 459 | git filter-branch --tree-filter "test -d submod && { |
| 460 | rm -rf submod && |
| 461 | git rm -rf --quiet submod && |
| 462 | mkdir submod && |
| 463 | : > submod/file |
| 464 | } || :" HEAD && |
| 465 | test $orig_head != $(git show-ref --hash --head HEAD) |
| 466 | ' |
| 467 | |
| 468 | test_expect_success 'replace submodule revision' ' |
| 469 | invalid=$(test_oid numeric) && |
| 470 | git reset --hard original && |
| 471 | git filter-branch -f --tree-filter \ |
| 472 | "if git ls-files --error-unmatch -- submod > /dev/null 2>&1 |
| 473 | then git update-index --cacheinfo 160000 $invalid submod |
| 474 | fi" HEAD && |
| 475 | test $orig_head != $(git show-ref --hash --head HEAD) |
| 476 | ' |
| 477 | |
| 478 | test_expect_success 'filter commit message without trailing newline' ' |
| 479 | git reset --hard original && |
| 480 | commit=$(printf "no newline" | git commit-tree HEAD^{tree}) && |
| 481 | git update-ref refs/heads/no-newline $commit && |
| 482 | git filter-branch -f refs/heads/no-newline && |
| 483 | echo $commit >expect && |
| 484 | git rev-parse refs/heads/no-newline >actual && |
| 485 | test_cmp expect actual |
| 486 | ' |
| 487 | |
| 488 | test_expect_success 'tree-filter deals with object name vs pathname ambiguity' ' |
| 489 | test_when_finished "git reset --hard original" && |
| 490 | ambiguous=$(git rev-list -1 HEAD) && |
| 491 | git filter-branch --tree-filter "mv file.t $ambiguous" HEAD^.. && |
| 492 | git show HEAD:$ambiguous |
| 493 | ' |
| 494 | |
| 495 | test_expect_success 'rewrite repository including refs that point at non-commit object' ' |
| 496 | test_when_finished "git reset --hard original" && |
| 497 | tree=$(git rev-parse HEAD^{tree}) && |
| 498 | test_when_finished "git replace -d $tree" && |
| 499 | echo A >new && |
| 500 | git add new && |
| 501 | new_tree=$(git write-tree) && |
| 502 | git replace $tree $new_tree && |
| 503 | git tag -a -m "tag to a tree" treetag $new_tree && |
| 504 | git reset --hard HEAD && |
| 505 | git filter-branch -f -- --all >filter-output 2>&1 && |
| 506 | ! grep fatal filter-output |
| 507 | ' |
| 508 | |
| 509 | test_expect_success 'filter-branch handles ref deletion' ' |
| 510 | git switch --orphan empty-commit && |
| 511 | git commit --allow-empty -m "empty commit" && |
| 512 | git tag empty && |
| 513 | git branch to-delete && |
| 514 | git filter-branch -f --prune-empty to-delete >out 2>&1 && |
| 515 | grep "to-delete.*was deleted" out && |
| 516 | test_must_fail git rev-parse --verify to-delete |
| 517 | ' |
| 518 | |
| 519 | test_expect_success 'filter-branch handles ref rewrite' ' |
| 520 | git checkout empty && |
| 521 | test_commit to-drop && |
| 522 | git branch rewrite && |
| 523 | git filter-branch -f \ |
| 524 | --index-filter "git rm --ignore-unmatch --cached to-drop.t" \ |
| 525 | rewrite >out 2>&1 && |
| 526 | grep "rewrite.*was rewritten" out && |
| 527 | ! grep -i warning out && |
| 528 | git diff-tree empty rewrite |
| 529 | ' |
| 530 | |
| 531 | test_expect_success 'filter-branch handles ancestor rewrite' ' |
| 532 | test_commit to-exclude && |
| 533 | git branch ancestor && |
| 534 | git filter-branch -f ancestor -- :^to-exclude.t >out 2>&1 && |
| 535 | grep "ancestor.*was rewritten" out && |
| 536 | ! grep -i warning out && |
| 537 | git diff-tree HEAD^ ancestor |
| 538 | ' |
| 539 | |
| 540 | test_done |