t / t3430-rebase-merges.shon commit Merge branch 'dl/use-sq-from-test-lib' (d693345)
   1#!/bin/sh
   2#
   3# Copyright (c) 2018 Johannes E. Schindelin
   4#
   5
   6test_description='git rebase -i --rebase-merges
   7
   8This test runs git rebase "interactively", retaining the branch structure by
   9recreating merge commits.
  10
  11Initial setup:
  12
  13    -- B --                   (first)
  14   /       \
  15 A - C - D - E - H            (master)
  16   \    \       /
  17    \    F - G                (second)
  18     \
  19      Conflicting-G
  20'
  21. ./test-lib.sh
  22. "$TEST_DIRECTORY"/lib-rebase.sh
  23
  24test_cmp_graph () {
  25        cat >expect &&
  26        git log --graph --boundary --format=%s "$@" >output &&
  27        sed "s/ *$//" <output >output.trimmed &&
  28        test_cmp expect output.trimmed
  29}
  30
  31test_expect_success 'setup' '
  32        write_script replace-editor.sh <<-\EOF &&
  33        mv "$1" "$(git rev-parse --git-path ORIGINAL-TODO)"
  34        cp script-from-scratch "$1"
  35        EOF
  36
  37        test_commit A &&
  38        git checkout -b first &&
  39        test_commit B &&
  40        b=$(git rev-parse --short HEAD) &&
  41        git checkout master &&
  42        test_commit C &&
  43        c=$(git rev-parse --short HEAD) &&
  44        test_commit D &&
  45        d=$(git rev-parse --short HEAD) &&
  46        git merge --no-commit B &&
  47        test_tick &&
  48        git commit -m E &&
  49        git tag -m E E &&
  50        e=$(git rev-parse --short HEAD) &&
  51        git checkout -b second C &&
  52        test_commit F &&
  53        f=$(git rev-parse --short HEAD) &&
  54        test_commit G &&
  55        g=$(git rev-parse --short HEAD) &&
  56        git checkout master &&
  57        git merge --no-commit G &&
  58        test_tick &&
  59        git commit -m H &&
  60        h=$(git rev-parse --short HEAD) &&
  61        git tag -m H H &&
  62        git checkout A &&
  63        test_commit conflicting-G G.t
  64'
  65
  66test_expect_success 'create completely different structure' '
  67        cat >script-from-scratch <<-\EOF &&
  68        label onto
  69
  70        # onebranch
  71        pick G
  72        pick D
  73        label onebranch
  74
  75        # second
  76        reset onto
  77        pick B
  78        label second
  79
  80        reset onto
  81        merge -C H second
  82        merge onebranch # Merge the topic branch '\''onebranch'\''
  83        EOF
  84        test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
  85        test_tick &&
  86        git rebase -i -r A master &&
  87        test_cmp_graph <<-\EOF
  88        *   Merge the topic branch '\''onebranch'\''
  89        |\
  90        | * D
  91        | * G
  92        * |   H
  93        |\ \
  94        | |/
  95        |/|
  96        | * B
  97        |/
  98        * A
  99        EOF
 100'
 101
 102test_expect_success 'generate correct todo list' '
 103        cat >expect <<-EOF &&
 104        label onto
 105
 106        reset onto
 107        pick $b B
 108        label E
 109
 110        reset onto
 111        pick $c C
 112        label branch-point
 113        pick $f F
 114        pick $g G
 115        label H
 116
 117        reset branch-point # C
 118        pick $d D
 119        merge -C $e E # E
 120        merge -C $h H # H
 121
 122        EOF
 123
 124        grep -v "^#" <.git/ORIGINAL-TODO >output &&
 125        test_cmp expect output
 126'
 127
 128test_expect_success '`reset` refuses to overwrite untracked files' '
 129        git checkout -b refuse-to-reset &&
 130        test_commit dont-overwrite-untracked &&
 131        git checkout @{-1} &&
 132        : >dont-overwrite-untracked.t &&
 133        echo "reset refs/tags/dont-overwrite-untracked" >script-from-scratch &&
 134        test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
 135        test_must_fail git rebase -ir HEAD &&
 136        git rebase --abort
 137'
 138
 139test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '
 140        test_when_finished "test_might_fail git rebase --abort" &&
 141        git checkout -b conflicting-merge A &&
 142
 143        : fail because of conflicting untracked file &&
 144        >G.t &&
 145        echo "merge -C H G" >script-from-scratch &&
 146        test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
 147        test_tick &&
 148        test_must_fail git rebase -ir HEAD &&
 149        grep "^merge -C .* G$" .git/rebase-merge/done &&
 150        grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo &&
 151        test_path_is_file .git/rebase-merge/patch &&
 152
 153        : fail because of merge conflict &&
 154        rm G.t .git/rebase-merge/patch &&
 155        git reset --hard conflicting-G &&
 156        test_must_fail git rebase --continue &&
 157        ! grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo &&
 158        test_path_is_file .git/rebase-merge/patch
 159'
 160
 161test_expect_success 'failed `merge <branch>` does not crash' '
 162        test_when_finished "test_might_fail git rebase --abort" &&
 163        git checkout conflicting-G &&
 164
 165        echo "merge G" >script-from-scratch &&
 166        test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
 167        test_tick &&
 168        test_must_fail git rebase -ir HEAD &&
 169        ! grep "^merge G$" .git/rebase-merge/git-rebase-todo &&
 170        grep "^Merge branch ${SQ}G${SQ}$" .git/rebase-merge/message
 171'
 172
 173test_expect_success 'fast-forward merge -c still rewords' '
 174        git checkout -b fast-forward-merge-c H &&
 175        (
 176                set_fake_editor &&
 177                FAKE_COMMIT_MESSAGE=edited \
 178                        GIT_SEQUENCE_EDITOR="echo merge -c H G >" \
 179                        git rebase -ir @^
 180        ) &&
 181        echo edited >expected &&
 182        git log --pretty=format:%B -1 >actual &&
 183        test_cmp expected actual
 184'
 185
 186test_expect_success 'with a branch tip that was cherry-picked already' '
 187        git checkout -b already-upstream master &&
 188        base="$(git rev-parse --verify HEAD)" &&
 189
 190        test_commit A1 &&
 191        test_commit A2 &&
 192        git reset --hard $base &&
 193        test_commit B1 &&
 194        test_tick &&
 195        git merge -m "Merge branch A" A2 &&
 196
 197        git checkout -b upstream-with-a2 $base &&
 198        test_tick &&
 199        git cherry-pick A2 &&
 200
 201        git checkout already-upstream &&
 202        test_tick &&
 203        git rebase -i -r upstream-with-a2 &&
 204        test_cmp_graph upstream-with-a2.. <<-\EOF
 205        *   Merge branch A
 206        |\
 207        | * A1
 208        * | B1
 209        |/
 210        o A2
 211        EOF
 212'
 213
 214test_expect_success 'do not rebase cousins unless asked for' '
 215        git checkout -b cousins master &&
 216        before="$(git rev-parse --verify HEAD)" &&
 217        test_tick &&
 218        git rebase -r HEAD^ &&
 219        test_cmp_rev HEAD $before &&
 220        test_tick &&
 221        git rebase --rebase-merges=rebase-cousins HEAD^ &&
 222        test_cmp_graph HEAD^.. <<-\EOF
 223        *   Merge the topic branch '\''onebranch'\''
 224        |\
 225        | * D
 226        | * G
 227        |/
 228        o H
 229        EOF
 230'
 231
 232test_expect_success 'refs/rewritten/* is worktree-local' '
 233        git worktree add wt &&
 234        cat >wt/script-from-scratch <<-\EOF &&
 235        label xyz
 236        exec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :
 237        exec git rev-parse --verify refs/rewritten/xyz >b
 238        EOF
 239
 240        test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
 241        git -C wt rebase -i HEAD &&
 242        test_must_be_empty wt/a &&
 243        test_cmp_rev HEAD "$(cat wt/b)"
 244'
 245
 246test_expect_success '--abort cleans up refs/rewritten' '
 247        git checkout -b abort-cleans-refs-rewritten H &&
 248        GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
 249        git rev-parse --verify refs/rewritten/onto &&
 250        git rebase --abort &&
 251        test_must_fail git rev-parse --verify refs/rewritten/onto
 252'
 253
 254test_expect_success '--quit cleans up refs/rewritten' '
 255        git checkout -b quit-cleans-refs-rewritten H &&
 256        GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
 257        git rev-parse --verify refs/rewritten/onto &&
 258        git rebase --quit &&
 259        test_must_fail git rev-parse --verify refs/rewritten/onto
 260'
 261
 262test_expect_success 'post-rewrite hook and fixups work for merges' '
 263        git checkout -b post-rewrite H &&
 264        test_commit same1 &&
 265        git reset --hard HEAD^ &&
 266        test_commit same2 &&
 267        git merge -m "to fix up" same1 &&
 268        echo same old same old >same2.t &&
 269        test_tick &&
 270        git commit --fixup HEAD same2.t &&
 271        fixup="$(git rev-parse HEAD)" &&
 272
 273        mkdir -p .git/hooks &&
 274        test_when_finished "rm .git/hooks/post-rewrite" &&
 275        echo "cat >actual" | write_script .git/hooks/post-rewrite &&
 276
 277        test_tick &&
 278        git rebase -i --autosquash -r HEAD^^^ &&
 279        printf "%s %s\n%s %s\n%s %s\n%s %s\n" >expect $(git rev-parse \
 280                $fixup^^2 HEAD^2 \
 281                $fixup^^ HEAD^ \
 282                $fixup^ HEAD \
 283                $fixup HEAD) &&
 284        test_cmp expect actual
 285'
 286
 287test_expect_success 'refuse to merge ancestors of HEAD' '
 288        echo "merge HEAD^" >script-from-scratch &&
 289        test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
 290        before="$(git rev-parse HEAD)" &&
 291        git rebase -i HEAD &&
 292        test_cmp_rev HEAD $before
 293'
 294
 295test_expect_success 'root commits' '
 296        git checkout --orphan unrelated &&
 297        (GIT_AUTHOR_NAME="Parsnip" GIT_AUTHOR_EMAIL="root@example.com" \
 298         test_commit second-root) &&
 299        test_commit third-root &&
 300        cat >script-from-scratch <<-\EOF &&
 301        pick third-root
 302        label first-branch
 303        reset [new root]
 304        pick second-root
 305        merge first-branch # Merge the 3rd root
 306        EOF
 307        test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
 308        test_tick &&
 309        git rebase -i --force-rebase --root -r &&
 310        test "Parsnip" = "$(git show -s --format=%an HEAD^)" &&
 311        test $(git rev-parse second-root^0) != $(git rev-parse HEAD^) &&
 312        test $(git rev-parse second-root:second-root.t) = \
 313                $(git rev-parse HEAD^:second-root.t) &&
 314        test_cmp_graph HEAD <<-\EOF &&
 315        *   Merge the 3rd root
 316        |\
 317        | * third-root
 318        * second-root
 319        EOF
 320
 321        : fast forward if possible &&
 322        before="$(git rev-parse --verify HEAD)" &&
 323        test_might_fail git config --unset sequence.editor &&
 324        test_tick &&
 325        git rebase -i --root -r &&
 326        test_cmp_rev HEAD $before
 327'
 328
 329test_expect_success 'a "merge" into a root commit is a fast-forward' '
 330        head=$(git rev-parse HEAD) &&
 331        cat >script-from-scratch <<-EOF &&
 332        reset [new root]
 333        merge $head
 334        EOF
 335        test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
 336        test_tick &&
 337        git rebase -i -r HEAD^ &&
 338        test_cmp_rev HEAD $head
 339'
 340
 341test_expect_success 'A root commit can be a cousin, treat it that way' '
 342        git checkout --orphan khnum &&
 343        test_commit yama &&
 344        git checkout -b asherah master &&
 345        test_commit shamkat &&
 346        git merge --allow-unrelated-histories khnum &&
 347        test_tick &&
 348        git rebase -f -r HEAD^ &&
 349        ! test_cmp_rev HEAD^2 khnum &&
 350        test_cmp_graph HEAD^.. <<-\EOF &&
 351        *   Merge branch '\''khnum'\'' into asherah
 352        |\
 353        | * yama
 354        o shamkat
 355        EOF
 356        test_tick &&
 357        git rebase --rebase-merges=rebase-cousins HEAD^ &&
 358        test_cmp_graph HEAD^.. <<-\EOF
 359        *   Merge branch '\''khnum'\'' into asherah
 360        |\
 361        | * yama
 362        |/
 363        o shamkat
 364        EOF
 365'
 366
 367test_expect_success 'labels that are object IDs are rewritten' '
 368        git checkout -b third B &&
 369        test_commit I &&
 370        third=$(git rev-parse HEAD) &&
 371        git checkout -b labels master &&
 372        git merge --no-commit third &&
 373        test_tick &&
 374        git commit -m "Merge commit '\''$third'\'' into labels" &&
 375        echo noop >script-from-scratch &&
 376        test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
 377        test_tick &&
 378        git rebase -i -r A &&
 379        grep "^label $third-" .git/ORIGINAL-TODO &&
 380        ! grep "^label $third$" .git/ORIGINAL-TODO
 381'
 382
 383test_expect_success 'octopus merges' '
 384        git checkout -b three &&
 385        test_commit before-octopus &&
 386        test_commit three &&
 387        git checkout -b two HEAD^ &&
 388        test_commit two &&
 389        git checkout -b one HEAD^ &&
 390        test_commit one &&
 391        test_tick &&
 392        (GIT_AUTHOR_NAME="Hank" GIT_AUTHOR_EMAIL="hank@sea.world" \
 393         git merge -m "Tüntenfüsch" two three) &&
 394
 395        : fast forward if possible &&
 396        before="$(git rev-parse --verify HEAD)" &&
 397        test_tick &&
 398        git rebase -i -r HEAD^^ &&
 399        test_cmp_rev HEAD $before &&
 400
 401        test_tick &&
 402        git rebase -i --force-rebase -r HEAD^^ &&
 403        test "Hank" = "$(git show -s --format=%an HEAD)" &&
 404        test "$before" != $(git rev-parse HEAD) &&
 405        test_cmp_graph HEAD^^.. <<-\EOF
 406        *-.   Tüntenfüsch
 407        |\ \
 408        | | * three
 409        | * | two
 410        | |/
 411        * | one
 412        |/
 413        o before-octopus
 414        EOF
 415'
 416
 417test_expect_success 'with --autosquash and --exec' '
 418        git checkout -b with-exec H &&
 419        echo Booh >B.t &&
 420        test_tick &&
 421        git commit --fixup B B.t &&
 422        write_script show.sh <<-\EOF &&
 423        subject="$(git show -s --format=%s HEAD)"
 424        content="$(git diff HEAD^! | tail -n 1)"
 425        echo "$subject: $content"
 426        EOF
 427        test_tick &&
 428        git rebase -ir --autosquash --exec ./show.sh A >actual &&
 429        grep "B: +Booh" actual &&
 430        grep "E: +Booh" actual &&
 431        grep "G: +G" actual
 432'
 433
 434test_expect_success '--continue after resolving conflicts after a merge' '
 435        git checkout -b already-has-g E &&
 436        git cherry-pick E..G &&
 437        test_commit H2 &&
 438
 439        git checkout -b conflicts-in-merge H &&
 440        test_commit H2 H2.t conflicts H2-conflict &&
 441        test_must_fail git rebase -r already-has-g &&
 442        grep conflicts H2.t &&
 443        echo resolved >H2.t &&
 444        git add -u &&
 445        git rebase --continue &&
 446        test_must_fail git rev-parse --verify HEAD^2 &&
 447        test_path_is_missing .git/MERGE_HEAD
 448'
 449
 450test_expect_success '--rebase-merges with strategies' '
 451        git checkout -b with-a-strategy F &&
 452        test_tick &&
 453        git merge -m "Merge conflicting-G" conflicting-G &&
 454
 455        : first, test with a merge strategy option &&
 456        git rebase -ir -Xtheirs G &&
 457        echo conflicting-G >expect &&
 458        test_cmp expect G.t &&
 459
 460        : now, try with a merge strategy other than recursive &&
 461        git reset --hard @{1} &&
 462        write_script git-merge-override <<-\EOF &&
 463        echo overridden$1 >>G.t
 464        git add G.t
 465        EOF
 466        PATH="$PWD:$PATH" git rebase -ir -s override -Xxopt G &&
 467        test_write_lines G overridden--xopt >expect &&
 468        test_cmp expect G.t
 469'
 470
 471test_done