11#define USE_THE_REPOSITORY_VARIABLE
22
33#include "builtin.h"
4+ #include "cache-tree.h"
45#include "commit-reach.h"
56#include "commit.h"
67#include "config.h"
1011#include "hex.h"
1112#include "oidmap.h"
1213#include "parse-options.h"
14+ #include "path.h"
15+ #include "read-cache.h"
1316#include "refs.h"
1417#include "replay.h"
1518#include "reset.h"
1619#include "revision.h"
20+ #include "run-command.h"
1721#include "sequencer.h"
1822#include "strvec.h"
1923#include "tree.h"
@@ -368,6 +372,225 @@ static int cmd_history_reword(int argc,
368372 return ret ;
369373}
370374
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+
371594int cmd_history (int argc ,
372595 const char * * argv ,
373596 const char * prefix ,
@@ -376,11 +599,13 @@ int cmd_history(int argc,
376599 const char * const usage [] = {
377600 N_ ("git history [<options>]" ),
378601 N_ ("git history reword [<options>] <commit>" ),
602+ N_ ("git history split [<options>] <commit> [--] [<pathspec>...]" ),
379603 NULL ,
380604 };
381605 parse_opt_subcommand_fn * fn = NULL ;
382606 struct option options [] = {
383607 OPT_SUBCOMMAND ("reword" , & fn , cmd_history_reword ),
608+ OPT_SUBCOMMAND ("split" , & fn , cmd_history_split ),
384609 OPT_END (),
385610 };
386611
0 commit comments