1
1
#define USE_THE_REPOSITORY_VARIABLE
2
2
3
3
#include "builtin.h"
4
+ #include "cache-tree.h"
4
5
#include "commit-reach.h"
5
6
#include "commit.h"
6
7
#include "config.h"
10
11
#include "hex.h"
11
12
#include "oidmap.h"
12
13
#include "parse-options.h"
14
+ #include "path.h"
15
+ #include "read-cache.h"
13
16
#include "refs.h"
14
17
#include "replay.h"
15
18
#include "reset.h"
16
19
#include "revision.h"
20
+ #include "run-command.h"
17
21
#include "sequencer.h"
18
22
#include "strvec.h"
19
23
#include "tree.h"
@@ -368,6 +372,225 @@ static int cmd_history_reword(int argc,
368
372
return ret ;
369
373
}
370
374
375
+ static int split_commit (struct repository * repo ,
376
+ struct commit * original_commit ,
377
+ struct pathspec * pathspec ,
378
+ const char * commit_message ,
379
+ struct object_id * out )
380
+ {
381
+ struct interactive_options interactive_opts = INTERACTIVE_OPTIONS_INIT ;
382
+ struct strbuf index_file = STRBUF_INIT , split_message = STRBUF_INIT ;
383
+ struct child_process read_tree_cmd = CHILD_PROCESS_INIT ;
384
+ struct index_state index = INDEX_STATE_INIT (repo );
385
+ struct object_id original_commit_tree_oid , parent_tree_oid ;
386
+ const char * original_message , * original_body , * ptr ;
387
+ char original_commit_oid [GIT_MAX_HEXSZ + 1 ];
388
+ char * original_author = NULL ;
389
+ struct commit_list * parents = NULL ;
390
+ struct commit * first_commit ;
391
+ struct tree * split_tree ;
392
+ size_t len ;
393
+ int ret ;
394
+
395
+ if (original_commit -> parents )
396
+ parent_tree_oid = * get_commit_tree_oid (original_commit -> parents -> item );
397
+ else
398
+ oidcpy (& parent_tree_oid , repo -> hash_algo -> empty_tree );
399
+ original_commit_tree_oid = * get_commit_tree_oid (original_commit );
400
+
401
+ /*
402
+ * Construct the first commit. This is done by taking the original
403
+ * commit parent's tree and selectively patching changes from the diff
404
+ * between that parent and its child.
405
+ */
406
+ repo_git_path_replace (repo , & index_file , "%s" , "history-split.index" );
407
+
408
+ read_tree_cmd .git_cmd = 1 ;
409
+ strvec_pushf (& read_tree_cmd .env , "GIT_INDEX_FILE=%s" , index_file .buf );
410
+ strvec_push (& read_tree_cmd .args , "read-tree" );
411
+ strvec_push (& read_tree_cmd .args , oid_to_hex (& parent_tree_oid ));
412
+ ret = run_command (& read_tree_cmd );
413
+ if (ret < 0 )
414
+ goto out ;
415
+
416
+ ret = read_index_from (& index , index_file .buf , repo -> gitdir );
417
+ if (ret < 0 ) {
418
+ ret = error (_ ("failed reading temporary index" ));
419
+ goto out ;
420
+ }
421
+
422
+ oid_to_hex_r (original_commit_oid , & original_commit -> object .oid );
423
+ ret = run_add_p_index (repo , & index , index_file .buf , & interactive_opts ,
424
+ original_commit_oid , pathspec );
425
+ if (ret < 0 )
426
+ goto out ;
427
+
428
+ split_tree = write_in_core_index_as_tree (repo , & index );
429
+ if (!split_tree ) {
430
+ ret = error (_ ("failed split tree" ));
431
+ goto out ;
432
+ }
433
+
434
+ unlink (index_file .buf );
435
+
436
+ /*
437
+ * We disallow the cases where either the split-out commit or the
438
+ * original commit would become empty. Consequently, if we see that the
439
+ * new tree ID matches either of those trees we abort.
440
+ */
441
+ if (oideq (& split_tree -> object .oid , & parent_tree_oid )) {
442
+ ret = error (_ ("split commit is empty" ));
443
+ goto out ;
444
+ } else if (oideq (& split_tree -> object .oid , & original_commit_tree_oid )) {
445
+ ret = error (_ ("split commit tree matches original commit" ));
446
+ goto out ;
447
+ }
448
+
449
+ /* We retain authorship of the original commit. */
450
+ original_message = repo_logmsg_reencode (repo , original_commit , NULL , NULL );
451
+ ptr = find_commit_header (original_message , "author" , & len );
452
+ if (ptr )
453
+ original_author = xmemdupz (ptr , len );
454
+
455
+ ret = fill_commit_message (repo , & parent_tree_oid , & split_tree -> object .oid ,
456
+ "" , commit_message , "split-out" , & split_message );
457
+ if (ret < 0 )
458
+ goto out ;
459
+
460
+ ret = commit_tree (split_message .buf , split_message .len , & split_tree -> object .oid ,
461
+ original_commit -> parents , & out [0 ], original_author , NULL );
462
+ if (ret < 0 ) {
463
+ ret = error (_ ("failed writing split-out commit" ));
464
+ goto out ;
465
+ }
466
+
467
+ /*
468
+ * The second commit is much simpler to construct, as we can simply use
469
+ * the original commit details, except that we adjust its parent to be
470
+ * the newly split-out commit.
471
+ */
472
+ find_commit_subject (original_message , & original_body );
473
+ first_commit = lookup_commit_reference (repo , & out [0 ]);
474
+ commit_list_append (first_commit , & parents );
475
+
476
+ ret = commit_tree (original_body , strlen (original_body ), & original_commit_tree_oid ,
477
+ parents , & out [1 ], original_author , NULL );
478
+ if (ret < 0 ) {
479
+ ret = error (_ ("failed writing second commit" ));
480
+ goto out ;
481
+ }
482
+
483
+ ret = 0 ;
484
+
485
+ out :
486
+ if (index_file .len )
487
+ unlink (index_file .buf );
488
+ strbuf_release (& split_message );
489
+ strbuf_release (& index_file );
490
+ free_commit_list (parents );
491
+ free (original_author );
492
+ release_index (& index );
493
+ return ret ;
494
+ }
495
+
496
+ static int cmd_history_split (int argc ,
497
+ const char * * argv ,
498
+ const char * prefix ,
499
+ struct repository * repo )
500
+ {
501
+ const char * const usage [] = {
502
+ N_ ("git history split [<options>] <commit>" ),
503
+ NULL ,
504
+ };
505
+ const char * commit_message = NULL ;
506
+ struct option options [] = {
507
+ OPT_STRING ('m' , "message" , & commit_message , N_ ("message" ), N_ ("commit message" )),
508
+ OPT_END (),
509
+ };
510
+ struct oidmap rewritten_commits = OIDMAP_INIT ;
511
+ struct commit * original_commit , * parent , * head ;
512
+ struct strvec commits = STRVEC_INIT ;
513
+ struct commit_list * list = NULL ;
514
+ struct object_id split_commits [2 ];
515
+ struct pathspec pathspec = { 0 };
516
+ int ret ;
517
+
518
+ argc = parse_options (argc , argv , prefix , options , usage , 0 );
519
+ if (argc < 1 ) {
520
+ ret = error (_ ("command expects a revision" ));
521
+ goto out ;
522
+ }
523
+ repo_config (repo , git_default_config , NULL );
524
+
525
+ original_commit = lookup_commit_reference_by_name (argv [0 ]);
526
+ if (!original_commit ) {
527
+ ret = error (_ ("commit to be split cannot be found: %s" ), argv [0 ]);
528
+ goto out ;
529
+ }
530
+
531
+ if (original_commit -> parents && original_commit -> parents -> next ) {
532
+ ret = error (_ ("commit to be split must not be a merge commit" ));
533
+ goto out ;
534
+ }
535
+
536
+ parent = original_commit -> parents ? original_commit -> parents -> item : NULL ;
537
+ if (parent && repo_parse_commit (repo , parent )) {
538
+ ret = error (_ ("unable to parse commit %s" ),
539
+ oid_to_hex (& parent -> object .oid ));
540
+ goto out ;
541
+ }
542
+
543
+ head = lookup_commit_reference_by_name ("HEAD" );
544
+ if (!head ) {
545
+ ret = error (_ ("could not resolve HEAD to a commit" ));
546
+ goto out ;
547
+ }
548
+
549
+ commit_list_append (original_commit , & list );
550
+ if (!repo_is_descendant_of (repo , original_commit , list )) {
551
+ ret = error (_ ("split commit must be reachable from current HEAD commit" ));
552
+ goto out ;
553
+ }
554
+
555
+ parse_pathspec (& pathspec , 0 ,
556
+ PATHSPEC_PREFER_FULL | PATHSPEC_SYMLINK_LEADING_PATH | PATHSPEC_PREFIX_ORIGIN ,
557
+ prefix , argv + 1 );
558
+
559
+ /*
560
+ * Collect the list of commits that we'll have to reapply now already.
561
+ * This ensures that we'll abort early on in case the range of commits
562
+ * contains merges, which we do not yet handle.
563
+ */
564
+ ret = collect_commits (repo , parent , head , & commits );
565
+ if (ret < 0 )
566
+ goto out ;
567
+
568
+ /*
569
+ * Then we split up the commit and replace the original commit with the
570
+ * new new ones.
571
+ */
572
+ ret = split_commit (repo , original_commit , & pathspec ,
573
+ commit_message , split_commits );
574
+ if (ret < 0 )
575
+ goto out ;
576
+
577
+ replace_commits (& commits , & original_commit -> object .oid ,
578
+ split_commits , ARRAY_SIZE (split_commits ));
579
+
580
+ ret = apply_commits (repo , & commits , parent , head , "split" );
581
+ if (ret < 0 )
582
+ goto out ;
583
+
584
+ ret = 0 ;
585
+
586
+ out :
587
+ oidmap_clear (& rewritten_commits , 0 );
588
+ clear_pathspec (& pathspec );
589
+ strvec_clear (& commits );
590
+ free_commit_list (list );
591
+ return ret ;
592
+ }
593
+
371
594
int cmd_history (int argc ,
372
595
const char * * argv ,
373
596
const char * prefix ,
@@ -376,11 +599,13 @@ int cmd_history(int argc,
376
599
const char * const usage [] = {
377
600
N_ ("git history [<options>]" ),
378
601
N_ ("git history reword [<options>] <commit>" ),
602
+ N_ ("git history split [<options>] <commit> [--] [<pathspec>...]" ),
379
603
NULL ,
380
604
};
381
605
parse_opt_subcommand_fn * fn = NULL ;
382
606
struct option options [] = {
383
607
OPT_SUBCOMMAND ("reword" , & fn , cmd_history_reword ),
608
+ OPT_SUBCOMMAND ("split" , & fn , cmd_history_split ),
384
609
OPT_END (),
385
610
};
386
611
0 commit comments