t / t3430-rebase-merges.shon commit Merge branch 'nd/switch-and-restore' (3ff6af7)
   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
 161SQ="'"
 162test_expect_success 'failed `merge <branch>` does not crash' '
 163        test_when_finished "test_might_fail git rebase --abort" &&
 164        git checkout conflicting-G &&
 165
 166        echo "merge G" >script-from-scratch &&
 167        test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
 168        test_tick &&
 169        test_must_fail git rebase -ir HEAD &&
 170        ! grep "^merge G$" .git/rebase-merge/git-rebase-todo &&
 171        grep "^Merge branch ${SQ}G${SQ}$" .git/rebase-merge/message
 172'
 173
 174test_expect_success 'fast-forward merge -c still rewords' '
 175        git checkout -b fast-forward-merge-c H &&
 176        (
 177                set_fake_editor &&
 178                FAKE_COMMIT_MESSAGE=edited \
 179                        GIT_SEQUENCE_EDITOR="echo merge -c H G >" \
 180                        git rebase -ir @^
 181        ) &&
 182        echo edited >expected &&
 183        git log --pretty=format:%B -1 >actual &&
 184        test_cmp expected actual
 185'
 186
 187test_expect_success 'with a branch tip that was cherry-picked already' '
 188        git checkout -b already-upstream master &&
 189        base="$(git rev-parse --verify HEAD)" &&
 190
 191        test_commit A1 &&
 192        test_commit A2 &&
 193        git reset --hard $base &&
 194        test_commit B1 &&
 195        test_tick &&
 196        git merge -m "Merge branch A" A2 &&
 197
 198        git checkout -b upstream-with-a2 $base &&
 199        test_tick &&
 200        git cherry-pick A2 &&
 201
 202        git checkout already-upstream &&
 203        test_tick &&
 204        git rebase -i -r upstream-with-a2 &&
 205        test_cmp_graph upstream-with-a2.. <<-\EOF
 206        *   Merge branch A
 207        |\
 208        | * A1
 209        * | B1
 210        |/
 211        o A2
 212        EOF
 213'
 214
 215test_expect_success 'do not rebase cousins unless asked for' '
 216        git checkout -b cousins master &&
 217        before="$(git rev-parse --verify HEAD)" &&
 218        test_tick &&
 219        git rebase -r HEAD^ &&
 220        test_cmp_rev HEAD $before &&
 221        test_tick &&
 222        git rebase --rebase-merges=rebase-cousins HEAD^ &&
 223        test_cmp_graph HEAD^.. <<-\EOF
 224        *   Merge the topic branch '\''onebranch'\''
 225        |\
 226        | * D
 227        | * G
 228        |/
 229        o H
 230        EOF
 231'
 232
 233test_expect_success 'refs/rewritten/* is worktree-local' '
 234        git worktree add wt &&
 235        cat >wt/script-from-scratch <<-\EOF &&
 236        label xyz
 237        exec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :
 238        exec git rev-parse --verify refs/rewritten/xyz >b
 239        EOF
 240
 241        test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
 242        git -C wt rebase -i HEAD &&
 243        test_must_be_empty wt/a &&
 244        test_cmp_rev HEAD "$(cat wt/b)"
 245'
 246
 247test_expect_success '--abort cleans up refs/rewritten' '
 248        git checkout -b abort-cleans-refs-rewritten H &&
 249        GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
 250        git rev-parse --verify refs/rewritten/onto &&
 251        git rebase --abort &&
 252        test_must_fail git rev-parse --verify refs/rewritten/onto
 253'
 254
 255test_expect_success '--quit cleans up refs/rewritten' '
 256        git checkout -b quit-cleans-refs-rewritten H &&
 257        GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
 258        git rev-parse --verify refs/rewritten/onto &&
 259        git rebase --quit &&
 260        test_must_fail git rev-parse --verify refs/rewritten/onto
 261'
 262
 263test_expect_success 'post-rewrite hook and fixups work for merges' '
 264        git checkout -b post-rewrite H &&
 265        test_commit same1 &&
 266        git reset --hard HEAD^ &&
 267        test_commit same2 &&
 268        git merge -m "to fix up" same1 &&
 269        echo same old same old >same2.t &&
 270        test_tick &&
 271        git commit --fixup HEAD same2.t &&
 272        fixup="$(git rev-parse HEAD)" &&
 273
 274        mkdir -p .git/hooks &&
 275        test_when_finished "rm .git/hooks/post-rewrite" &&
 276        echo "cat >actual" | write_script .git/hooks/post-rewrite &&
 277
 278        test_tick &&
 279        git rebase -i --autosquash -r HEAD^^^ &&
 280        printf "%s %s\n%s %s\n%s %s\n%s %s\n" >expect $(git rev-parse \
 281                $fixup^^2 HEAD^2 \
 282                $fixup^^ HEAD^ \
 283                $fixup^ HEAD \
 284                $fixup HEAD) &&
 285        test_cmp expect actual
 286'
 287
 288test_expect_success 'refuse to merge ancestors of HEAD' '
 289        echo "merge HEAD^" >script-from-scratch &&
 290        test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
 291        before="$(git rev-parse HEAD)" &&
 292        git rebase -i HEAD &&
 293        test_cmp_rev HEAD $before
 294'
 295
 296test_expect_success 'root commits' '
 297        git checkout --orphan unrelated &&
 298        (GIT_AUTHOR_NAME="Parsnip" GIT_AUTHOR_EMAIL="root@example.com" \
 299         test_commit second-root) &&
 300        test_commit third-root &&
 301        cat >script-from-scratch <<-\EOF &&
 302        pick third-root
 303        label first-branch
 304        reset [new root]
 305        pick second-root
 306        merge first-branch # Merge the 3rd root
 307        EOF
 308        test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
 309        test_tick &&
 310        git rebase -i --force-rebase --root -r &&
 311        test "Parsnip" = "$(git show -s --format=%an HEAD^)" &&
 312        test $(git rev-parse second-root^0) != $(git rev-parse HEAD^) &&
 313        test $(git rev-parse second-root:second-root.t) = \
 314                $(git rev-parse HEAD^:second-root.t) &&
 315        test_cmp_graph HEAD <<-\EOF &&
 316        *   Merge the 3rd root
 317        |\
 318        | * third-root
 319        * second-root
 320        EOF
 321
 322        : fast forward if possible &&
 323        before="$(git rev-parse --verify HEAD)" &&
 324        test_might_fail git config --unset sequence.editor &&
 325        test_tick &&
 326        git rebase -i --root -r &&
 327        test_cmp_rev HEAD $before
 328'
 329
 330test_expect_success 'a "merge" into a root commit is a fast-forward' '
 331        head=$(git rev-parse HEAD) &&
 332        cat >script-from-scratch <<-EOF &&
 333        reset [new root]
 334        merge $head
 335        EOF
 336        test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
 337        test_tick &&
 338        git rebase -i -r HEAD^ &&
 339        test_cmp_rev HEAD $head
 340'
 341
 342test_expect_success 'A root commit can be a cousin, treat it that way' '
 343        git checkout --orphan khnum &&
 344        test_commit yama &&
 345        git checkout -b asherah master &&
 346        test_commit shamkat &&
 347        git merge --allow-unrelated-histories khnum &&
 348        test_tick &&
 349        git rebase -f -r HEAD^ &&
 350        ! test_cmp_rev HEAD^2 khnum &&
 351        test_cmp_graph HEAD^.. <<-\EOF &&
 352        *   Merge branch '\''khnum'\'' into asherah
 353        |\
 354        | * yama
 355        o shamkat
 356        EOF
 357        test_tick &&
 358        git rebase --rebase-merges=rebase-cousins HEAD^ &&
 359        test_cmp_graph HEAD^.. <<-\EOF
 360        *   Merge branch '\''khnum'\'' into asherah
 361        |\
 362        | * yama
 363        |/
 364        o shamkat
 365        EOF
 366'
 367
 368test_expect_success 'labels that are object IDs are rewritten' '
 369        git checkout -b third B &&
 370        test_commit I &&
 371        third=$(git rev-parse HEAD) &&
 372        git checkout -b labels master &&
 373        git merge --no-commit third &&
 374        test_tick &&
 375        git commit -m "Merge commit '\''$third'\'' into labels" &&
 376        echo noop >script-from-scratch &&
 377        test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
 378        test_tick &&
 379        git rebase -i -r A &&
 380        grep "^label $third-" .git/ORIGINAL-TODO &&
 381        ! grep "^label $third$" .git/ORIGINAL-TODO
 382'
 383
 384test_expect_success 'octopus merges' '
 385        git checkout -b three &&
 386        test_commit before-octopus &&
 387        test_commit three &&
 388        git checkout -b two HEAD^ &&
 389        test_commit two &&
 390        git checkout -b one HEAD^ &&
 391        test_commit one &&
 392        test_tick &&
 393        (GIT_AUTHOR_NAME="Hank" GIT_AUTHOR_EMAIL="hank@sea.world" \
 394         git merge -m "Tüntenfüsch" two three) &&
 395
 396        : fast forward if possible &&
 397        before="$(git rev-parse --verify HEAD)" &&
 398        test_tick &&
 399        git rebase -i -r HEAD^^ &&
 400        test_cmp_rev HEAD $before &&
 401
 402        test_tick &&
 403        git rebase -i --force-rebase -r HEAD^^ &&
 404        test "Hank" = "$(git show -s --format=%an HEAD)" &&
 405        test "$before" != $(git rev-parse HEAD) &&
 406        test_cmp_graph HEAD^^.. <<-\EOF
 407        *-.   Tüntenfüsch
 408        |\ \
 409        | | * three
 410        | * | two
 411        | |/
 412        * | one
 413        |/
 414        o before-octopus
 415        EOF
 416'
 417
 418test_expect_success 'with --autosquash and --exec' '
 419        git checkout -b with-exec H &&
 420        echo Booh >B.t &&
 421        test_tick &&
 422        git commit --fixup B B.t &&
 423        write_script show.sh <<-\EOF &&
 424        subject="$(git show -s --format=%s HEAD)"
 425        content="$(git diff HEAD^! | tail -n 1)"
 426        echo "$subject: $content"
 427        EOF
 428        test_tick &&
 429        git rebase -ir --autosquash --exec ./show.sh A >actual &&
 430        grep "B: +Booh" actual &&
 431        grep "E: +Booh" actual &&
 432        grep "G: +G" actual
 433'
 434
 435test_expect_success '--continue after resolving conflicts after a merge' '
 436        git checkout -b already-has-g E &&
 437        git cherry-pick E..G &&
 438        test_commit H2 &&
 439
 440        git checkout -b conflicts-in-merge H &&
 441        test_commit H2 H2.t conflicts H2-conflict &&
 442        test_must_fail git rebase -r already-has-g &&
 443        grep conflicts H2.t &&
 444        echo resolved >H2.t &&
 445        git add -u &&
 446        git rebase --continue &&
 447        test_must_fail git rev-parse --verify HEAD^2 &&
 448        test_path_is_missing .git/MERGE_HEAD
 449'
 450
 451test_expect_success '--rebase-merges with strategies' '
 452        git checkout -b with-a-strategy F &&
 453        test_tick &&
 454        git merge -m "Merge conflicting-G" conflicting-G &&
 455
 456        : first, test with a merge strategy option &&
 457        git rebase -ir -Xtheirs G &&
 458        echo conflicting-G >expect &&
 459        test_cmp expect G.t &&
 460
 461        : now, try with a merge strategy other than recursive &&
 462        git reset --hard @{1} &&
 463        write_script git-merge-override <<-\EOF &&
 464        echo overridden$1 >>G.t
 465        git add G.t
 466        EOF
 467        PATH="$PWD:$PATH" git rebase -ir -s override -Xxopt G &&
 468        test_write_lines G overridden--xopt >expect &&
 469        test_cmp expect G.t
 470'
 471
 472test_done