1
+#!/bin/sh
2
+
3
+test_description='tests for git-history split subcommand'
4
+
5
+. ./test-lib.sh
6
+. "$TEST_DIRECTORY/lib-log-graph.sh"
7
+
8
+# The fake editor takes multiple arguments, each of which represents a commit
9
+# message. Subsequent invocations of the editor will then yield those messages
10
+# in order.
11
+#
12
+set_fake_editor () {
13
+ printf "%s\n" "$@" >fake-input &&
14
+ write_script fake-editor.sh <<-\EOF &&
15
+ head -n1 fake-input >"$1"
16
+ sed 1d fake-input >fake-input.trimmed &&
17
+ mv fake-input.trimmed fake-input
18
+ EOF
19
+ test_set_editor "$(pwd)"/fake-editor.sh
20
+}
21
+
22
+expect_graph () {
23
+ cat >expect &&
24
+ lib_test_cmp_graph --graph --format=%s "$@"
25
+}
26
+
27
+expect_log () {
28
+ git log --format="%s" >actual &&
29
+ cat >expect &&
30
+ test_cmp expect actual
31
+}
32
+
33
+expect_tree_entries () {
34
+ git ls-tree --name-only "$1" >actual &&
35
+ cat >expect &&
36
+ test_cmp expect actual
37
+}
38
+
39
+test_expect_success 'refuses to work with merge commits' '
40
+ test_when_finished "rm -rf repo" &&
41
+ git init repo &&
42
+ (
43
+ cd repo &&
44
+ test_commit base &&
45
+ git branch branch &&
46
+ test_commit ours &&
47
+ git switch branch &&
48
+ test_commit theirs &&
49
+ git switch - &&
50
+ git merge theirs &&
51
+ test_must_fail git history split HEAD 2>err &&
52
+ test_grep "cannot split up merge commit" err &&
53
+ test_must_fail git history split HEAD~ 2>err &&
54
+ test_grep "replaying merge commits is not supported yet" err
55
+ )
56
+'
57
+
58
+test_expect_success 'errors on missing commit argument' '
59
+ test_when_finished "rm -rf repo" &&
60
+ git init repo &&
61
+ (
62
+ cd repo &&
63
+ test_commit initial &&
64
+ test_must_fail git history split 2>err &&
65
+ test_grep "command expects a committish" err
66
+ )
67
+'
68
+
69
+test_expect_success 'errors on unknown revision' '
70
+ test_when_finished "rm -rf repo" &&
71
+ git init repo &&
72
+ (
73
+ cd repo &&
74
+ test_commit initial &&
75
+ test_must_fail git history split does-not-exist 2>err &&
76
+ test_grep "commit cannot be found" err
77
+ )
78
+'
79
+
80
+test_expect_success '--dry-run does not modify any refs' '
81
+ test_when_finished "rm -rf repo" &&
82
+ git init repo &&
83
+ (
84
+ cd repo &&
85
+ test_commit base &&
86
+ touch bar foo &&
87
+ git add . &&
88
+ git commit -m split-me &&
89
+
90
+ git refs list --include-root-refs >before &&
91
+
92
+ set_fake_editor "first" "second" &&
93
+ git history split --dry-run HEAD <<-EOF &&
94
+ y
95
+ n
96
+ EOF
97
+
98
+ git refs list --include-root-refs >after &&
99
+ test_cmp before after
100
+ )
101
+'
102
+
103
+test_expect_success 'can split up tip commit' '
104
+ test_when_finished "rm -rf repo" &&
105
+ git init repo &&
106
+ (
107
+ cd repo &&
108
+ test_commit initial &&
109
+ touch bar foo &&
110
+ git add . &&
111
+ git commit -m split-me &&
112
+
113
+ git symbolic-ref HEAD >expect &&
114
+ set_fake_editor "first" "second" &&
115
+ git history split HEAD <<-EOF &&
116
+ y
117
+ n
118
+ EOF
119
+ git symbolic-ref HEAD >actual &&
120
+ test_cmp expect actual &&
121
+
122
+ expect_log <<-EOF &&
123
+ second
124
+ first
125
+ initial
126
+ EOF
127
+
128
+ expect_tree_entries HEAD~ <<-EOF &&
129
+ bar
130
+ initial.t
131
+ EOF
132
+
133
+ expect_tree_entries HEAD <<-EOF &&
134
+ bar
135
+ foo
136
+ initial.t
137
+ EOF
138
+
139
+ git reflog >reflog &&
140
+ test_grep "split: updating HEAD" reflog
141
+ )
142
+'
143
+
144
+test_expect_success 'can split up root commit' '
145
+ test_when_finished "rm -rf repo" &&
146
+ git init repo &&
147
+ (
148
+ cd repo &&
149
+ touch bar foo &&
150
+ git add . &&
151
+ git commit -m root &&
152
+ test_commit tip &&
153
+
154
+ set_fake_editor "first" "second" &&
155
+ git history split HEAD~ <<-EOF &&
156
+ y
157
+ n
158
+ EOF
159
+
160
+ expect_log <<-EOF &&
161
+ tip
162
+ second
163
+ first
164
+ EOF
165
+
166
+ expect_tree_entries HEAD~2 <<-EOF &&
167
+ bar
168
+ EOF
169
+
170
+ expect_tree_entries HEAD~ <<-EOF &&
171
+ bar
172
+ foo
173
+ EOF
174
+
175
+ expect_tree_entries HEAD <<-EOF
176
+ bar
177
+ foo
178
+ tip.t
179
+ EOF
180
+ )
181
+'
182
+
183
+test_expect_success 'can split up in-between commit' '
184
+ test_when_finished "rm -rf repo" &&
185
+ git init repo &&
186
+ (
187
+ cd repo &&
188
+ test_commit initial &&
189
+ touch bar foo &&
190
+ git add . &&
191
+ git commit -m split-me &&
192
+ test_commit tip &&
193
+
194
+ set_fake_editor "first" "second" &&
195
+ git history split HEAD~ <<-EOF &&
196
+ y
197
+ n
198
+ EOF
199
+
200
+ expect_log <<-EOF &&
201
+ tip
202
+ second
203
+ first
204
+ initial
205
+ EOF
206
+
207
+ expect_tree_entries HEAD~2 <<-EOF &&
208
+ bar
209
+ initial.t
210
+ EOF
211
+
212
+ expect_tree_entries HEAD~ <<-EOF &&
213
+ bar
214
+ foo
215
+ initial.t
216
+ EOF
217
+
218
+ expect_tree_entries HEAD <<-EOF
219
+ bar
220
+ foo
221
+ initial.t
222
+ tip.t
223
+ EOF
224
+ )
225
+'
226
+
227
+test_expect_success 'can split HEAD only' '
228
+ test_when_finished "rm -rf repo" &&
229
+ git init repo &&
230
+ (
231
+ cd repo &&
232
+ test_commit base &&
233
+ touch a b &&
234
+ git add . &&
235
+ git commit -m split-me &&
236
+ git branch unrelated &&
237
+
238
+ set_fake_editor "ours-a" "ours-b" &&
239
+ git history split --update-refs=head HEAD <<-EOF &&
240
+ y
241
+ n
242
+ EOF
243
+ expect_graph --branches <<-EOF
244
+ * ours-b
245
+ * ours-a
246
+ | * split-me
247
+ |/
248
+ * base
249
+ EOF
250
+ )
251
+'
252
+
253
+test_expect_success 'can split detached HEAD' '
254
+ test_when_finished "rm -rf repo" &&
255
+ git init repo &&
256
+ (
257
+ cd repo &&
258
+ test_commit initial &&
259
+ touch bar foo &&
260
+ git add . &&
261
+ git commit -m split-me &&
262
+ git checkout --detach HEAD &&
263
+
264
+ set_fake_editor "first" "second" &&
265
+ git history split --update-refs=head HEAD <<-EOF &&
266
+ y
267
+ n
268
+ EOF
269
+
270
+ # HEAD should be detached and updated.
271
+ test_must_fail git symbolic-ref HEAD &&
272
+
273
+ expect_log <<-EOF
274
+ second
275
+ first
276
+ initial
277
+ EOF
278
+ )
279
+'
280
+
281
+test_expect_success 'can split commit in unrelated branch' '
282
+ test_when_finished "rm -rf repo" &&
283
+ git init repo &&
284
+ (
285
+ cd repo &&
286
+ test_commit base &&
287
+ git branch ours &&
288
+ git switch --create theirs &&
289
+ touch theirs-a theirs-b &&
290
+ git add . &&
291
+ git commit -m theirs &&
292
+ git switch ours &&
293
+ test_commit ours &&
294
+
295
+ # With --update-refs=head it is not possible to split up a
296
+ # commit that is unrelated to HEAD.
297
+ test_must_fail git history split --update-refs=head theirs 2>err &&
298
+ test_grep "rewritten commit must be an ancestor of HEAD" err &&
299
+
300
+ set_fake_editor "theirs-rewritten-a" "theirs-rewritten-b" &&
301
+ git history split theirs <<-EOF &&
302
+ y
303
+ n
304
+ EOF
305
+ expect_graph --branches <<-EOF &&
306
+ * ours
307
+ | * theirs-rewritten-b
308
+ | * theirs-rewritten-a
309
+ |/
310
+ * base
311
+ EOF
312
+
313
+ expect_tree_entries theirs~ <<-EOF &&
314
+ base.t
315
+ theirs-a
316
+ EOF
317
+
318
+ expect_tree_entries theirs <<-EOF
319
+ base.t
320
+ theirs-a
321
+ theirs-b
322
+ EOF
323
+ )
324
+'
325
+
326
+test_expect_success 'updates multiple descendant branches' '
327
+ test_when_finished "rm -rf repo" &&
328
+ git init repo --initial-branch=main &&
329
+ (
330
+ cd repo &&
331
+ test_commit base &&
332
+ touch file-a file-b &&
333
+ git add . &&
334
+ git commit -m split-me &&
335
+ git branch branch &&
336
+ test_commit on-main &&
337
+ git switch branch &&
338
+ test_commit on-branch &&
339
+ git switch main &&
340
+
341
+ set_fake_editor "split-a" "split-b" &&
342
+ git history split HEAD~ <<-EOF &&
343
+ y
344
+ n
345
+ EOF
346
+
347
+ # Both branches should now descend from the split commits.
348
+ expect_graph --branches <<-EOF
349
+ * on-branch
350
+ | * on-main
351
+ |/
352
+ * split-b
353
+ * split-a
354
+ * base
355
+ EOF
356
+ )
357
+'
358
+
359
+test_expect_success 'can pick multiple hunks' '
360
+ test_when_finished "rm -rf repo" &&
361
+ git init repo &&
362
+ (
363
+ cd repo &&
364
+ touch bar baz foo qux &&
365
+ git add . &&
366
+ git commit -m split-me &&
367
+
368
+ set_fake_editor "first" "second" &&
369
+ git history split HEAD <<-EOF &&
370
+ y
371
+ n
372
+ y
373
+ n
374
+ EOF
375
+
376
+ expect_tree_entries HEAD~ <<-EOF &&
377
+ bar
378
+ foo
379
+ EOF
380
+
381
+ expect_tree_entries HEAD <<-EOF
382
+ bar
383
+ baz
384
+ foo
385
+ qux
386
+ EOF
387
+ )
388
+'
389
+
390
+test_expect_success 'can use only last hunk' '
391
+ test_when_finished "rm -rf repo" &&
392
+ git init repo &&
393
+ (
394
+ cd repo &&
395
+ touch bar foo &&
396
+ git add . &&
397
+ git commit -m split-me &&
398
+
399
+ set_fake_editor "first" "second" &&
400
+ git history split HEAD <<-EOF &&
401
+ n
402
+ y
403
+ EOF
404
+
405
+ expect_log <<-EOF &&
406
+ second
407
+ first
408
+ EOF
409
+
410
+ expect_tree_entries HEAD~ <<-EOF &&
411
+ foo
412
+ EOF
413
+
414
+ expect_tree_entries HEAD <<-EOF
415
+ bar
416
+ foo
417
+ EOF
418
+ )
419
+'
420
+
421
+test_expect_success 'can split commit with file deletions' '
422
+ test_when_finished "rm -rf repo" &&
423
+ git init repo &&
424
+ (
425
+ cd repo &&
426
+ echo a >a &&
427
+ echo b >b &&
428
+ echo c >c &&
429
+ git add . &&
430
+ git commit -m base &&
431
+ git rm a b &&
432
+ git commit -m delete-both &&
433
+
434
+ set_fake_editor "delete-a" "delete-b" &&
435
+ git history split HEAD <<-EOF &&
436
+ y
437
+ n
438
+ EOF
439
+
440
+ expect_log <<-EOF &&
441
+ delete-b
442
+ delete-a
443
+ base
444
+ EOF
445
+
446
+ expect_tree_entries HEAD~ <<-EOF &&
447
+ b
448
+ c
449
+ EOF
450
+
451
+ expect_tree_entries HEAD <<-EOF
452
+ c
453
+ EOF
454
+ )
455
+'
456
+
457
+test_expect_success 'preserves original authorship' '
458
+ test_when_finished "rm -rf repo" &&
459
+ git init repo &&
460
+ (
461
+ cd repo &&
462
+ test_commit initial &&
463
+ touch bar foo &&
464
+ git add . &&
465
+ GIT_AUTHOR_NAME="Other Author" \
466
+ GIT_AUTHOR_EMAIL="other@example.com" \
467
+ git commit -m split-me &&
468
+
469
+ set_fake_editor "first" "second" &&
470
+ git history split HEAD <<-EOF &&
471
+ y
472
+ n
473
+ EOF
474
+
475
+ git log -1 --format="%an <%ae>" HEAD~ >actual &&
476
+ echo "Other Author <other@example.com>" >expect &&
477
+ test_cmp expect actual &&
478
+
479
+ git log -1 --format="%an <%ae>" HEAD >actual &&
480
+ test_cmp expect actual
481
+ )
482
+'
483
+
484
+test_expect_success 'aborts with empty commit message' '
485
+ test_when_finished "rm -rf repo" &&
486
+ git init repo &&
487
+ (
488
+ cd repo &&
489
+ touch bar foo &&
490
+ git add . &&
491
+ git commit -m split-me &&
492
+
493
+ set_fake_editor "" &&
494
+ test_must_fail git history split HEAD <<-EOF 2>err &&
495
+ y
496
+ n
497
+ EOF
498
+ test_grep "Aborting commit due to empty commit message." err
499
+ )
500
+'
501
+
502
+test_expect_success 'commit message editor sees split-out changes' '
503
+ test_when_finished "rm -rf repo" &&
504
+ git init repo &&
505
+ (
506
+ cd repo &&
507
+ touch bar foo &&
508
+ git add . &&
509
+ git commit -m split-me &&
510
+
511
+ write_script fake-editor.sh <<-\EOF &&
512
+ cat "$1" >>MESSAGES &&
513
+ echo "some commit message" >"$1"
514
+ EOF
515
+ test_set_editor "$(pwd)"/fake-editor.sh &&
516
+
517
+ git history split HEAD <<-EOF &&
518
+ y
519
+ n
520
+ EOF
521
+
522
+ # Note that we expect to see the messages twice, once for each
523
+ # of the commits. The committed files are different though.
524
+ cat >expect <<-EOF &&
525
+ split-me
526
+
527
+ # Please enter the commit message for the split-out changes. Lines starting
528
+ # with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit.
529
+ # Changes to be committed:
530
+ # new file: bar
531
+ #
532
+ split-me
533
+
534
+ # Please enter the commit message for the split-out changes. Lines starting
535
+ # with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit.
536
+ # Changes to be committed:
537
+ # new file: foo
538
+ #
539
+ EOF
540
+ test_cmp expect MESSAGES &&
541
+
542
+ expect_log <<-EOF
543
+ some commit message
544
+ some commit message
545
+ EOF
546
+ )
547
+'
548
+
549
+test_expect_success 'can use pathspec to limit what gets split' '
550
+ test_when_finished "rm -rf repo" &&
551
+ git init repo &&
552
+ (
553
+ cd repo &&
554
+ touch bar foo &&
555
+ git add . &&
556
+ git commit -m split-me &&
557
+
558
+ set_fake_editor "first" "second" &&
559
+ git history split HEAD -- foo <<-EOF &&
560
+ y
561
+ EOF
562
+
563
+ expect_tree_entries HEAD~ <<-EOF &&
564
+ foo
565
+ EOF
566
+
567
+ expect_tree_entries HEAD <<-EOF
568
+ bar
569
+ foo
570
+ EOF
571
+ )
572
+'
573
+
574
+test_expect_success 'pathspec matching no files produces empty split error' '
575
+ test_when_finished "rm -rf repo" &&
576
+ git init repo &&
577
+ (
578
+ cd repo &&
579
+ test_commit initial &&
580
+ touch bar foo &&
581
+ git add . &&
582
+ git commit -m split-me &&
583
+
584
+ set_fake_editor "first" "second" &&
585
+ test_must_fail git history split HEAD -- nonexistent 2>err &&
586
+ test_grep "split commit is empty" err
587
+ )
588
+'
589
+
590
+test_expect_success 'split with multiple pathspecs' '
591
+ test_when_finished "rm -rf repo" &&
592
+ git init repo &&
593
+ (
594
+ cd repo &&
595
+ test_commit initial &&
596
+ touch a b c d &&
597
+ git add . &&
598
+ git commit -m split-me &&
599
+
600
+ # Only a and c should be offered for splitting.
601
+ set_fake_editor "split-ac" "remainder" &&
602
+ git history split HEAD -- a c <<-EOF &&
603
+ y
604
+ y
605
+ EOF
606
+
607
+ expect_tree_entries HEAD~ <<-EOF &&
608
+ a
609
+ c
610
+ initial.t
611
+ EOF
612
+
613
+ expect_tree_entries HEAD <<-EOF
614
+ a
615
+ b
616
+ c
617
+ d
618
+ initial.t
619
+ EOF
620
+ )
621
+'
622
+
623
+test_expect_success 'split with file mode change' '
624
+ test_when_finished "rm -rf repo" &&
625
+ git init repo &&
626
+ (
627
+ cd repo &&
628
+ echo content >script &&
629
+ git add . &&
630
+ git commit -m base &&
631
+ test_chmod +x script &&
632
+ echo change >script &&
633
+ git commit -a -m "mode and content change" &&
634
+
635
+ set_fake_editor "mode-change" "content-change" &&
636
+ git history split HEAD <<-EOF &&
637
+ y
638
+ n
639
+ EOF
640
+
641
+ expect_log <<-EOF
642
+ content-change
643
+ mode-change
644
+ base
645
+ EOF
646
+ )
647
+'
648
+
649
+test_expect_success 'refuses to create empty split-out commit' '
650
+ test_when_finished "rm -rf repo" &&
651
+ git init repo &&
652
+ (
653
+ cd repo &&
654
+ test_commit base &&
655
+ touch bar foo &&
656
+ git add . &&
657
+ git commit -m split-me &&
658
+
659
+ test_must_fail git history split HEAD 2>err <<-EOF &&
660
+ n
661
+ n
662
+ EOF
663
+ test_grep "split commit is empty" err
664
+ )
665
+'
666
+
667
+test_expect_success 'hooks are not executed for rewritten commits' '
668
+ test_when_finished "rm -rf repo" &&
669
+ git init repo &&
670
+ (
671
+ cd repo &&
672
+ touch bar foo &&
673
+ git add . &&
674
+ git commit -m split-me &&
675
+ old_head=$(git rev-parse HEAD) &&
676
+
677
+ ORIG_PATH="$(pwd)" &&
678
+ export ORIG_PATH &&
679
+ for hook in prepare-commit-msg pre-commit post-commit post-rewrite commit-msg
680
+ do
681
+ write_script .git/hooks/$hook <<-\EOF || exit 1
682
+ touch "$ORIG_PATH"/hooks.log
683
+ EOF
684
+ done &&
685
+
686
+ set_fake_editor "first" "second" &&
687
+ git history split HEAD <<-EOF &&
688
+ y
689
+ n
690
+ EOF
691
+
692
+ expect_log <<-EOF &&
693
+ second
694
+ first
695
+ EOF
696
+
697
+ test_path_is_missing hooks.log
698
+ )
699
+'
700
+
701
+test_expect_success 'refuses to create empty original commit' '
702
+ test_when_finished "rm -rf repo" &&
703
+ git init repo &&
704
+ (
705
+ cd repo &&
706
+ touch bar foo &&
707
+ git add . &&
708
+ git commit -m split-me &&
709
+
710
+ test_must_fail git history split HEAD 2>err <<-EOF &&
711
+ y
712
+ y
713
+ EOF
714
+ test_grep "split commit tree matches original commit" err
715
+ )
716
+'
717
+
718
+test_expect_success 'retains changes in the worktree and index' '
719
+ test_when_finished "rm -rf repo" &&
720
+ git init repo &&
721
+ (
722
+ cd repo &&
723
+ echo a >a &&
724
+ echo b >b &&
725
+ git add . &&
726
+ git commit -m "initial commit" &&
727
+ echo a-modified >a &&
728
+ echo b-modified >b &&
729
+ git add b &&
730
+ set_fake_editor "a-only" "remainder" &&
731
+ git history split HEAD <<-EOF &&
732
+ y
733
+ n
734
+ EOF
735
+
736
+ expect_tree_entries HEAD~ <<-EOF &&
737
+ a
738
+ EOF
739
+ expect_tree_entries HEAD <<-EOF &&
740
+ a
741
+ b
742
+ EOF
743
+
744
+ cat >expect <<-\EOF &&
745
+ M a
746
+ M b
747
+ ?? actual
748
+ ?? expect
749
+ ?? fake-editor.sh
750
+ ?? fake-input
751
+ EOF
752
+ git status --porcelain >actual &&
753
+ test_cmp expect actual
754
+ )
755
+'
756
+
757
+test_done