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