1
+#!/bin/sh
2
+
3
+test_description='tests for git-history squash subcommand'
4
+
5
+. ./test-lib.sh
6
+
7
+stage_file () {
8
+ printf "%s\n" "$1" >file &&
9
+ git add file
10
+}
11
+
12
+commit_with_message () {
13
+ printf "%b" "$1" >msg &&
14
+ git commit --allow-empty -qF msg
15
+}
16
+
17
+check_commit_count () {
18
+ git rev-list --count "$1" >actual &&
19
+ echo "$2" >expect &&
20
+ test_cmp expect actual
21
+}
22
+
23
+check_log_subjects () {
24
+ git log --format="%s" "$1" >actual &&
25
+ cat >expect &&
26
+ test_cmp expect actual
27
+}
28
+
29
+check_log_messages () {
30
+ git log --format="%B" "$1" >actual &&
31
+ cat >expect &&
32
+ test_cmp expect actual
33
+}
34
+
35
+test_expect_success 'setup linear history touching two files' '
36
+ test_commit base file a &&
37
+ git tag start &&
38
+ GIT_AUTHOR_NAME=Squasher GIT_AUTHOR_EMAIL=squash@example.com \
39
+ test_commit --no-tag one other x &&
40
+ test_commit --no-tag two file c &&
41
+ test_commit three file d
42
+'
43
+
44
+test_expect_success 'errors on missing range argument' '
45
+ test_must_fail git history squash 2>err &&
46
+ test_grep "expects a revision range" err
47
+'
48
+
49
+test_expect_success 'errors on an empty range' '
50
+ test_must_fail git history squash HEAD..HEAD 2>err &&
51
+ test_grep "the revision range is empty" err
52
+'
53
+
54
+test_expect_success 'errors on a single revision that is not a range' '
55
+ test_must_fail git history squash HEAD 2>err &&
56
+ test_grep "not a .*range" err &&
57
+ test_must_fail git history squash HEAD~1 2>err &&
58
+ test_grep "not a .*range" err
59
+'
60
+
61
+test_expect_success 'errors on a range holding a single commit' '
62
+ git reset --hard three &&
63
+ head_before=$(git rev-parse HEAD) &&
64
+
65
+ test_must_fail git history squash "HEAD^!" 2>err &&
66
+ test_grep "single commit; nothing to squash" err &&
67
+ test_cmp_rev "$head_before" HEAD
68
+'
69
+
70
+test_expect_success 'accepts multiple revision arguments with an exclusion' '
71
+ git reset --hard three &&
72
+ git branch -f keep HEAD~2 &&
73
+ tip_tree=$(git rev-parse HEAD^{tree}) &&
74
+
75
+ git history squash --no-edit start..HEAD ^keep &&
76
+
77
+ git reflog -1 --format=%gs >actual &&
78
+ echo "squash: updating start..HEAD ^keep" >expect &&
79
+ test_cmp expect actual &&
80
+
81
+ check_log_subjects start..HEAD <<-\EOF &&
82
+ two
83
+ one
84
+ EOF
85
+ test_cmp_rev keep HEAD~1 &&
86
+ test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
87
+
88
+ git branch -D keep
89
+'
90
+
91
+test_expect_success 'refuses a range with more than one tip' '
92
+ git reset --hard start &&
93
+ main=$(git symbolic-ref --short HEAD) &&
94
+ test_commit --no-tag main-one file b &&
95
+ test_commit --no-tag main-two file c &&
96
+ git checkout -b other-tip start &&
97
+ test_commit --no-tag other-tip other d &&
98
+ git checkout "$main" &&
99
+ main_before=$(git rev-parse HEAD) &&
100
+ other_before=$(git rev-parse other-tip) &&
101
+
102
+ test_must_fail git history squash ^start HEAD other-tip 2>err &&
103
+ test_grep "more than one tip" err &&
104
+ test_cmp_rev "$main_before" HEAD &&
105
+ test_cmp_rev "$other_before" other-tip &&
106
+
107
+ git branch -D other-tip
108
+'
109
+
110
+test_expect_success 'refuses a range that reaches a root commit' '
111
+ git reset --hard three &&
112
+ root=$(printf "unrelated root\n" |
113
+ git commit-tree "$(git rev-parse HEAD^{tree})") &&
114
+ head_before=$(git rev-parse HEAD) &&
115
+
116
+ test_must_fail git history squash --ancestry-path="$root" \
117
+ ^start HEAD "$root" 2>err &&
118
+ test_grep "reaches a root commit" err &&
119
+ test_cmp_rev "$head_before" HEAD
120
+'
121
+
122
+test_expect_success 'squashes a branch the current branch is not on' '
123
+ git reset --hard three &&
124
+ main=$(git symbolic-ref --short HEAD) &&
125
+ head_before=$(git rev-parse HEAD) &&
126
+ git checkout -b off-history start &&
127
+ test_commit --no-tag off-one off a &&
128
+ test_commit --no-tag off-two off b &&
129
+ git checkout "$main" &&
130
+
131
+ git history squash --no-edit start..off-history &&
132
+
133
+ check_commit_count start..off-history 1 &&
134
+ test_cmp_rev "$head_before" HEAD &&
135
+
136
+ git branch -D off-history
137
+'
138
+
139
+test_expect_success 'squashes a range preserving its tree and oldest authorship' '
140
+ git reset --hard three &&
141
+ head_before=$(git rev-parse HEAD) &&
142
+ tip_tree=$(git rev-parse HEAD^{tree}) &&
143
+
144
+ git history squash --dry-run start.. >out &&
145
+ predicted=$(awk "/^update refs\/heads\// {print \$3}" out) &&
146
+ test_cmp_rev "$head_before" HEAD &&
147
+
148
+ git history squash start.. &&
149
+
150
+ test "$predicted" = "$(git rev-parse HEAD)" &&
151
+ check_commit_count start..HEAD 1 &&
152
+ test_cmp_rev start HEAD^ &&
153
+ test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
154
+ check_log_subjects -1 <<-\EOF &&
155
+ one
156
+ EOF
157
+ git log -1 --format="%an <%ae>" >actual &&
158
+ echo "Squasher <squash@example.com>" >expect &&
159
+ test_cmp expect actual &&
160
+ git reflog >reflog &&
161
+ test_grep "squash: updating" reflog
162
+'
163
+
164
+test_expect_success 'sanitizes rev-list walk options, before and after --' '
165
+ git reset --hard three &&
166
+ tip_tree=$(git rev-parse HEAD^{tree}) &&
167
+
168
+ git history squash --no-edit --date-order start.. 2>err &&
169
+ test_grep "ignoring rev-list options" err &&
170
+ test_cmp_rev start HEAD^ &&
171
+ test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
172
+
173
+ git reset --hard three &&
174
+ git history squash --no-edit -- --reverse start.. 2>err &&
175
+ test_grep "ignoring rev-list options" err &&
176
+ test_cmp_rev start HEAD^ &&
177
+ test "$tip_tree" = "$(git rev-parse HEAD^{tree})"
178
+'
179
+
180
+test_expect_success 'squashes an interior range and replays descendants verbatim' '
181
+ git reset --hard three &&
182
+ final_tree=$(git rev-parse HEAD^{tree}) &&
183
+
184
+ git history squash --no-edit start..@~1 &&
185
+
186
+ check_log_subjects start..HEAD <<-\EOF &&
187
+ three
188
+ one
189
+ EOF
190
+
191
+ test_cmp_rev start HEAD~2 &&
192
+ test "$final_tree" = "$(git rev-parse HEAD^{tree})"
193
+'
194
+
195
+test_expect_success 'squashes when the base is the root commit' '
196
+ git reset --hard three &&
197
+ root=$(git rev-list --max-parents=0 HEAD) &&
198
+ tip_tree=$(git rev-parse HEAD^{tree}) &&
199
+
200
+ git history squash --no-edit "$root.." &&
201
+
202
+ check_commit_count "$root..HEAD" 1 &&
203
+ test_cmp_rev "$root" HEAD^ &&
204
+ test "$tip_tree" = "$(git rev-parse HEAD^{tree})"
205
+'
206
+
207
+
208
+test_expect_success 'folds fixups whose target is in the range' '
209
+ git reset --hard start &&
210
+ test_commit --no-tag target file b &&
211
+ git commit --allow-empty -m "fixup! target" &&
212
+ git commit --allow-empty -m "fixup! target" &&
213
+ test_commit --no-tag later file c &&
214
+
215
+ git history squash --no-edit start.. &&
216
+
217
+ check_commit_count start..HEAD 1 &&
218
+ check_log_subjects -1 <<-\EOF
219
+ target
220
+ EOF
221
+'
222
+
223
+test_expect_success 'refuses a below-range fixup! after an in-range commit' '
224
+ git reset --hard start &&
225
+ test_commit --no-tag inside file b &&
226
+ test_commit --no-tag "fixup! outside" file c &&
227
+ head_before=$(git rev-parse HEAD) &&
228
+
229
+ test_must_fail git history squash start.. 2>err &&
230
+ test_grep "target is not in the range" err &&
231
+ test_cmp_rev "$head_before" HEAD
232
+'
233
+
234
+test_expect_success 'combines a run of fixups for one commit below the range' '
235
+ git reset --hard start &&
236
+ stage_file b && git commit -m "fixup! base" &&
237
+ stage_file c && git commit -m "fixup! base" &&
238
+
239
+ git history squash --no-edit start.. &&
240
+
241
+ check_commit_count start..HEAD 1 &&
242
+ check_log_subjects -1 <<-\EOF
243
+ fixup! base
244
+ EOF
245
+'
246
+
247
+test_expect_success 'combining below-range markers offers every message' '
248
+ git reset --hard start &&
249
+ stage_file b && git commit -m "fixup! base" &&
250
+ stage_file c &&
251
+ commit_with_message "amend! base\n\namended body\n" &&
252
+
253
+ git history squash start.. &&
254
+
255
+ check_commit_count start..HEAD 1 &&
256
+ check_log_messages -1 <<-\EOF
257
+ fixup! base
258
+
259
+ amend! base
260
+
261
+ amended body
262
+
263
+ EOF
264
+'
265
+
266
+test_expect_success 'refuses fixups for two different commits below the range' '
267
+ git reset --hard start &&
268
+ stage_file b && git commit -m "fixup! aaa" &&
269
+ stage_file c && git commit -m "fixup! bbb" &&
270
+ head_before=$(git rev-parse HEAD) &&
271
+
272
+ test_must_fail git history squash start.. 2>err &&
273
+ test_grep "target is not in the range" err &&
274
+ test_cmp_rev "$head_before" HEAD
275
+'
276
+
277
+test_expect_success 'does not discard squash! or amend! message bodies' '
278
+ git reset --hard start &&
279
+ test_commit --no-tag marker-oldest file b &&
280
+ git commit --allow-empty -m "squash! marker-oldest" &&
281
+ commit_with_message "amend! marker-oldest\n\nearlier message\n" &&
282
+ commit_with_message \
283
+ "amend! marker-oldest\n\namended subject\n\namended body\n" &&
284
+ test_commit --no-tag marker-later file c &&
285
+ commit_with_message "amend! marker-later\n\nwrong message\n" &&
286
+
287
+ git history squash start.. &&
288
+
289
+ check_commit_count start..HEAD 1 &&
290
+ check_log_messages -1 <<-\EOF
291
+ marker-oldest
292
+
293
+ earlier message
294
+
295
+ amended subject
296
+
297
+ amended body
298
+
299
+ wrong message
300
+
301
+ EOF
302
+'
303
+
304
+test_expect_success '--no-edit keeps the selected message without an editor' '
305
+ git reset --hard start &&
306
+ test_commit --no-tag no-edit-target file b &&
307
+ git commit --allow-empty -m "squash! no-edit-target" &&
308
+ commit_with_message "amend! no-edit-target\n\namended subject\n\namended body\n" &&
309
+
310
+ write_script editor <<-\EOF &&
311
+ exit 1
312
+ EOF
313
+ test_set_editor "$(pwd)/editor" &&
314
+ git history squash --no-edit start.. &&
315
+
316
+ check_log_messages -1 <<-\EOF &&
317
+ amended subject
318
+
319
+ amended body
320
+
321
+ EOF
322
+ test_set_editor :
323
+'
324
+
325
+test_expect_success 'edits every message and aborts on an empty result' '
326
+ git reset --hard start &&
327
+ stage_file b &&
328
+ git commit -m "re-one subject" -m "re-one body line" &&
329
+ test_commit --no-tag re-two file c &&
330
+ test_commit re-three file d &&
331
+ head_before=$(git rev-parse HEAD) &&
332
+
333
+ write_script empty-editor <<-\EOF &&
334
+ >"$1"
335
+ EOF
336
+ test_set_editor "$(pwd)/empty-editor" &&
337
+ test_must_fail git history squash start.. 2>err &&
338
+ test_grep "Aborting commit due to empty commit message" err &&
339
+ test_cmp_rev "$head_before" HEAD &&
340
+
341
+ write_script editor <<-\EOF &&
342
+ cat "$1" >edited &&
343
+ echo combined >"$1"
344
+ EOF
345
+ test_set_editor "$(pwd)/editor" &&
346
+ git history squash start.. &&
347
+
348
+ cat >expect <<-EOF &&
349
+ # This is a combination of 3 commits.
350
+ # This is the 1st commit message:
351
+
352
+ re-one subject
353
+
354
+ re-one body line
355
+
356
+ # This is the commit message #2:
357
+
358
+ re-two
359
+
360
+ # This is the commit message #3:
361
+
362
+ re-three
363
+
364
+ # Please enter the commit message for the squash changes. Lines starting
365
+ # with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit.
366
+ # Changes to be committed:
367
+ # modified: file
368
+ #
369
+ EOF
370
+ test_cmp expect edited &&
371
+ check_log_subjects -1 <<-\EOF
372
+ combined
373
+ EOF
374
+'
375
+
376
+test_expect_success 'handles fixup!, squash! and amend! messages like rebase' '
377
+ git reset --hard start &&
378
+ test_commit --no-tag mark-base file b &&
379
+ stage_file c &&
380
+ commit_with_message "fixup! mark-base\n\nfixup body\n" &&
381
+ stage_file d &&
382
+ commit_with_message "squash! mark-base\n\nsquash remark\n" &&
383
+ stage_file e &&
384
+ commit_with_message "amend! mark-base\n\namended message\n" &&
385
+
386
+ write_script editor <<-\EOF &&
387
+ cat "$1" >edited
388
+ EOF
389
+ test_set_editor "$(pwd)/editor" &&
390
+ git history squash start.. &&
391
+
392
+ cat >expect <<-EOF &&
393
+ # This is a combination of 4 commits.
394
+ # This is the 1st commit message:
395
+
396
+ mark-base
397
+
398
+ # The commit message #2 will be skipped:
399
+
400
+ # fixup! mark-base
401
+ #
402
+ # fixup body
403
+
404
+ # This is the commit message #3:
405
+
406
+ # squash! mark-base
407
+
408
+ squash remark
409
+
410
+ # This is the commit message #4:
411
+
412
+ # amend! mark-base
413
+
414
+ amended message
415
+
416
+ # Please enter the commit message for the squash changes. Lines starting
417
+ # with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit.
418
+ # Changes to be committed:
419
+ # modified: file
420
+ #
421
+ EOF
422
+ test_cmp expect edited &&
423
+ check_log_messages -1 <<-\EOF
424
+ mark-base
425
+
426
+ squash remark
427
+
428
+ amended message
429
+
430
+ EOF
431
+'
432
+
433
+test_expect_success 'groups fixups under their targets in the editor' '
434
+ git reset --hard start &&
435
+ test_commit --no-tag alpha file a1 &&
436
+ test_commit --no-tag beta file b1 &&
437
+ stage_file a2 &&
438
+ commit_with_message "fixup! alpha\n" &&
439
+ stage_file b2 &&
440
+ commit_with_message "fixup! beta\n" &&
441
+
442
+ write_script editor <<-\EOF &&
443
+ cat "$1" >edited
444
+ EOF
445
+ test_set_editor "$(pwd)/editor" &&
446
+ git history squash start.. &&
447
+
448
+ cat >expect <<-EOF &&
449
+ # This is a combination of 4 commits.
450
+ # This is the 1st commit message:
451
+
452
+ alpha
453
+
454
+ # The commit message #2 will be skipped:
455
+
456
+ # fixup! alpha
457
+
458
+ # This is the commit message #3:
459
+
460
+ beta
461
+
462
+ # The commit message #4 will be skipped:
463
+
464
+ # fixup! beta
465
+
466
+ # Please enter the commit message for the squash changes. Lines starting
467
+ # with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit.
468
+ # Changes to be committed:
469
+ # modified: file
470
+ #
471
+ EOF
472
+ test_cmp expect edited
473
+'
474
+
475
+test_expect_success 'lets amend! replace its target message in the editor' '
476
+ git reset --hard start &&
477
+ test_commit --no-tag mark-base file b &&
478
+ stage_file c &&
479
+ commit_with_message "amend! mark-base\n\namended message\n" &&
480
+ stage_file d &&
481
+ commit_with_message "squash! mark-base\n\nsquash remark\n" &&
482
+
483
+ write_script editor <<-\EOF &&
484
+ cat "$1" >edited
485
+ EOF
486
+ test_set_editor "$(pwd)/editor" &&
487
+ git history squash start.. &&
488
+
489
+ cat >expect <<-EOF &&
490
+ # This is a combination of 3 commits.
491
+ # The 1st commit message will be skipped:
492
+
493
+ # mark-base
494
+
495
+ # This is the commit message #2:
496
+
497
+ # amend! mark-base
498
+
499
+ amended message
500
+
501
+ # This is the commit message #3:
502
+
503
+ # squash! mark-base
504
+
505
+ squash remark
506
+
507
+ # Please enter the commit message for the squash changes. Lines starting
508
+ # with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit.
509
+ # Changes to be committed:
510
+ # modified: file
511
+ #
512
+ EOF
513
+ test_cmp expect edited &&
514
+ check_log_messages -1 <<-\EOF
515
+ amended message
516
+
517
+ squash remark
518
+
519
+ EOF
520
+'
521
+
522
+test_expect_success 'handles branches pointing at the squashed range' '
523
+ git reset --hard three &&
524
+ git branch -f other HEAD &&
525
+ git branch -f mid HEAD~1 &&
526
+ other_before=$(git rev-parse other) &&
527
+ mid_before=$(git rev-parse mid) &&
528
+ head_before=$(git rev-parse HEAD) &&
529
+
530
+ test_must_fail git history squash start.. 2>err &&
531
+ test_grep "error: .* points into the squashed range" err &&
532
+ test_grep "hint: .*--update-refs=head" err &&
533
+ test_cmp_rev "$head_before" HEAD &&
534
+
535
+ test_must_fail git -c advice.historyUpdateRefs=false \
536
+ history squash start.. 2>err &&
537
+ test_grep "points into the squashed range" err &&
538
+ test_grep ! "hint:" err &&
539
+ test_cmp_rev "$head_before" HEAD &&
540
+
541
+ git history squash --no-edit --update-refs=head start.. &&
542
+
543
+ check_commit_count start..HEAD 1 &&
544
+ test_cmp_rev "$other_before" other &&
545
+ test_cmp_rev "$mid_before" mid &&
546
+
547
+ git branch -D other mid
548
+'
549
+
550
+test_expect_success 'leaves tags and remote-tracking refs unchanged' '
551
+ git reset --hard three &&
552
+ git tag -f mark HEAD~1 &&
553
+ git update-ref refs/remotes/origin/mark HEAD~1 &&
554
+ mark_before=$(git rev-parse mark) &&
555
+
556
+ git history squash --no-edit start.. &&
557
+
558
+ test_cmp_rev "$mark_before" mark &&
559
+ test_cmp_rev "$mark_before" refs/remotes/origin/mark &&
560
+
561
+ git tag -d mark &&
562
+ git update-ref -d refs/remotes/origin/mark
563
+'
564
+
565
+test_expect_success 'handles a branch pointing at an internal merge' '
566
+ git reset --hard start &&
567
+ main=$(git symbolic-ref --short HEAD) &&
568
+ test_commit --no-tag before-side file b &&
569
+ git checkout -b inner-side &&
570
+ test_commit --no-tag on-inner-side inner x &&
571
+ git checkout "$main" &&
572
+ test_commit --no-tag after-side file c &&
573
+ git merge --no-ff -m merge inner-side &&
574
+ git branch -D inner-side &&
575
+ git branch at-merge HEAD &&
576
+ test_commit --no-tag after-merge file d &&
577
+ head_before=$(git rev-parse HEAD) &&
578
+ tip_tree=$(git rev-parse HEAD^{tree}) &&
579
+
580
+ test_must_fail git history squash start.. 2>err &&
581
+ test_grep "at-merge" err &&
582
+ test_grep "points into the squashed range" err &&
583
+ test_cmp_rev "$head_before" HEAD &&
584
+ git branch -D at-merge &&
585
+
586
+ git history squash --no-edit start.. &&
587
+
588
+ check_commit_count start..HEAD 1 &&
589
+ check_log_subjects -1 <<-\EOF &&
590
+ before-side
591
+ EOF
592
+ test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
593
+ test_path_is_file inner
594
+'
595
+
596
+test_expect_success 'folds a merge of a branch that forked at the base' '
597
+ git reset --hard start &&
598
+ main=$(git symbolic-ref --short HEAD) &&
599
+ git checkout -b base-fork-side &&
600
+ test_commit --no-tag base-fork-side side x &&
601
+ git checkout "$main" &&
602
+ test_commit --no-tag base-fork-main file b &&
603
+ git merge --no-ff -m "merge base-fork-side" base-fork-side &&
604
+ git branch -D base-fork-side &&
605
+ test_commit --no-tag base-fork-tail file c &&
606
+ tip_tree=$(git rev-parse HEAD^{tree}) &&
607
+
608
+ git history squash --no-edit start.. &&
609
+
610
+ check_commit_count start..HEAD 1 &&
611
+ test_cmp_rev start HEAD^ &&
612
+ test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
613
+ test_path_is_file side
614
+'
615
+
616
+test_expect_success 'refuses a merge whose other parent is outside the range' '
617
+ git reset --hard start &&
618
+ main=$(git symbolic-ref --short HEAD) &&
619
+ git checkout -b outside-parent &&
620
+ test_commit --no-tag outside-parent outside x &&
621
+ git checkout "$main" &&
622
+ test_commit --no-tag outside-main file b &&
623
+ base=$(git rev-parse HEAD) &&
624
+ test_commit --no-tag outside-mid file c &&
625
+ git merge --no-ff -m "merge outside-parent" outside-parent &&
626
+ git branch -D outside-parent &&
627
+ merged=$(git rev-parse HEAD) &&
628
+
629
+ test_must_fail git history squash "$base.." 2>err &&
630
+ test_grep "more than one base" err &&
631
+ test_cmp_rev "$merged" HEAD
632
+'
633
+
634
+test_expect_success 'folds a range whose tip is a merge commit' '
635
+ git reset --hard start &&
636
+ main=$(git symbolic-ref --short HEAD) &&
637
+ test_commit --no-tag tipmerge-base file b &&
638
+ git checkout -b tipmerge-side &&
639
+ test_commit --no-tag tipmerge-side side x &&
640
+ git checkout "$main" &&
641
+ test_commit --no-tag tipmerge-main file c &&
642
+ git merge --no-ff -m "merge tipmerge-side" tipmerge-side &&
643
+ git branch -D tipmerge-side &&
644
+ tip_tree=$(git rev-parse HEAD^{tree}) &&
645
+
646
+ git history squash --no-edit start.. &&
647
+
648
+ check_commit_count start..HEAD 1 &&
649
+ test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
650
+ test_path_is_file side
651
+'
652
+
653
+test_expect_success 'folds a range whose base is a merge commit' '
654
+ git reset --hard start &&
655
+ main=$(git symbolic-ref --short HEAD) &&
656
+ git checkout -b basemerge-side &&
657
+ test_commit --no-tag basemerge-side side x &&
658
+ git checkout "$main" &&
659
+ test_commit --no-tag basemerge-main file b &&
660
+ git merge --no-ff -m "merge basemerge-side" basemerge-side &&
661
+ git branch -D basemerge-side &&
662
+ base=$(git rev-parse HEAD) &&
663
+ test_commit --no-tag basemerge-one file c &&
664
+ test_commit --no-tag basemerge-two file d &&
665
+ tip_tree=$(git rev-parse HEAD^{tree}) &&
666
+
667
+ git history squash --no-edit "$base.." &&
668
+
669
+ check_commit_count "$base..HEAD" 1 &&
670
+ test_cmp_rev "$base" HEAD^ &&
671
+ test "$tip_tree" = "$(git rev-parse HEAD^{tree})"
672
+'
673
+
674
+test_expect_success 'folds a range with a nested merge' '
675
+ git reset --hard start &&
676
+ main=$(git symbolic-ref --short HEAD) &&
677
+ git checkout -b nested-outer &&
678
+ test_commit --no-tag nested-outer outer x &&
679
+ git checkout -b nested-inner &&
680
+ test_commit --no-tag nested-inner inner y &&
681
+ git checkout nested-outer &&
682
+ git merge --no-ff -m "merge inner" nested-inner &&
683
+ git checkout "$main" &&
684
+ test_commit --no-tag nested-main file b1 &&
685
+ git merge --no-ff -m "merge outer" nested-outer &&
686
+ git branch -D nested-outer nested-inner &&
687
+ tip_tree=$(git rev-parse HEAD^{tree}) &&
688
+
689
+ git history squash --no-edit start.. &&
690
+
691
+ check_commit_count start..HEAD 1 &&
692
+ test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
693
+ test_path_is_file outer &&
694
+ test_path_is_file inner
695
+'
696
+
697
+test_expect_success 'folds a range with an octopus merge' '
698
+ git reset --hard start &&
699
+ main=$(git symbolic-ref --short HEAD) &&
700
+ test_commit --no-tag octo-base file a1 &&
701
+ git checkout -b octo-1 &&
702
+ test_commit --no-tag octo-1 o1 x &&
703
+ git checkout "$main" &&
704
+ git checkout -b octo-2 &&
705
+ test_commit --no-tag octo-2 o2 y &&
706
+ git checkout "$main" &&
707
+ git merge --no-ff -m octopus octo-1 octo-2 &&
708
+ git branch -D octo-1 octo-2 &&
709
+ tip_tree=$(git rev-parse HEAD^{tree}) &&
710
+
711
+ git history squash --no-edit start.. &&
712
+
713
+ check_commit_count start..HEAD 1 &&
714
+ test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
715
+ test_path_is_file o1 &&
716
+ test_path_is_file o2
717
+'
718
+
719
+test_expect_success 'refuses an octopus merge with an arm forked before the base' '
720
+ git reset --hard start &&
721
+ main=$(git symbolic-ref --short HEAD) &&
722
+ git checkout -b octo-pre &&
723
+ test_commit octo-pre-side pside x &&
724
+ git checkout "$main" &&
725
+ test_commit octo-pre-main file b1 &&
726
+ octo_base=$(git rev-parse HEAD) &&
727
+ git checkout -b octo-within &&
728
+ test_commit --no-tag octo-within wside y &&
729
+ git checkout "$main" &&
730
+ git merge --no-ff -m octopus octo-pre octo-within &&
731
+ merged=$(git rev-parse HEAD) &&
732
+ git branch -D octo-pre octo-within &&
733
+
734
+ test_must_fail git history squash "$octo_base.." 2>err &&
735
+ test_grep "more than one base" err &&
736
+ test_cmp_rev "$merged" HEAD
737
+'
738
+
739
+test_expect_success 'refuses when a descendant above the range is a merge' '
740
+ git reset --hard start &&
741
+ main=$(git symbolic-ref --short HEAD) &&
742
+ test_commit --no-tag desc-one file b &&
743
+ test_commit --no-tag desc-two file c &&
744
+ git tag desc-tip &&
745
+ git checkout -b desc-above &&
746
+ test_commit --no-tag desc-above above x &&
747
+ git checkout "$main" &&
748
+ test_commit --no-tag desc-main file d &&
749
+ git merge --no-ff -m "merge desc-above" desc-above &&
750
+ git branch -D desc-above &&
751
+ head_before=$(git rev-parse HEAD) &&
752
+
753
+ test_must_fail git history squash --no-edit start..desc-tip 2>err &&
754
+ test_grep "merge commits is not supported" err &&
755
+ test_cmp_rev "$head_before" HEAD
756
+'
757
+
758
+test_done