@@ -435,7 +435,8 @@ mod in_memory {
435435 use crate :: core:: rewrite:: move_branches;
436436 use crate :: core:: rewrite:: plan:: { OidOrLabel , RebaseCommand , RebasePlan } ;
437437 use crate :: git:: {
438- CherryPickFastOptions , CreateCommitFastError , GitRunInfo , MaybeZeroOid , NonZeroOid , Repo ,
438+ AmendFastOptions , CherryPickFastOptions , CreateCommitFastError , GitRunInfo , MaybeZeroOid ,
439+ NonZeroOid , Repo ,
439440 } ;
440441 use crate :: util:: EyreExitOr ;
441442
@@ -546,14 +547,25 @@ mod in_memory {
546547 } => {
547548 current_oid = match labels. get ( label_name) {
548549 Some ( oid) => * oid,
549- None => eyre:: bail!( "BUG: no associated OID for label: {}" , label_name ) ,
550+ None => eyre:: bail!( "BUG: no associated OID for label: {label_name}" ) ,
550551 } ;
551552 }
552553
553554 RebaseCommand :: Reset {
554555 target : OidOrLabel :: Oid ( commit_oid) ,
555556 } => {
556- current_oid = * commit_oid;
557+ current_oid = match rewritten_oids. get ( commit_oid) {
558+ Some ( MaybeZeroOid :: NonZero ( rewritten_oid) ) => {
559+ // HEAD has been rewritten.
560+ * rewritten_oid
561+ }
562+ Some ( MaybeZeroOid :: Zero ) | None => {
563+ // Either HEAD was not rewritten, or it was but its
564+ // associated commit was skipped. Either way, just
565+ // use the current OID.
566+ * commit_oid
567+ }
568+ } ;
557569 }
558570
559571 RebaseCommand :: Pick {
@@ -563,107 +575,144 @@ mod in_memory {
563575 let current_commit = repo
564576 . find_commit_or_fail ( current_oid)
565577 . wrap_err ( "Finding current commit" ) ?;
566- let commit_to_apply_oid = match commits_to_apply_oids. as_slice ( ) {
567- [ commit_to_apply_oid] => commit_to_apply_oid,
568- [ ..] => unimplemented ! ( "Picking multiple commits" ) ,
569- } ;
570- let commit_to_apply = repo
571- . find_commit_or_fail ( * commit_to_apply_oid)
578+
579+ let original_commit = repo
580+ . find_commit_or_fail ( * original_commit_oid)
572581 . wrap_err ( "Finding commit to apply" ) ?;
573582 i += 1 ;
574583
575- let commit_description = effects
576- . get_glyphs ( )
577- . render ( commit_to_apply. friendly_describe ( effects. get_glyphs ( ) ) ?) ?;
578584 let commit_num = format ! ( "[{i}/{num_picks}]" ) ;
579585 progress. notify_progress ( i, num_picks) ;
580586
581- if commit_to_apply. get_parent_count ( ) > 1 {
582- warn ! (
583- ?commit_to_apply_oid,
584- "BUG: Merge commit should have been detected during planning phase"
585- ) ;
586- return Ok ( RebaseInMemoryResult :: MergeFailed (
587- FailedMergeInfo :: CannotRebaseMergeInMemory {
588- commit_oid : * commit_to_apply_oid,
589- } ,
590- ) ) ;
591- } ;
592-
593- progress. notify_status (
594- OperationIcon :: InProgress ,
595- format ! ( "Applying patch for commit: {commit_description}" ) ,
596- ) ;
597- let commit_tree = match repo. cherry_pick_fast (
598- & commit_to_apply,
599- & current_commit,
600- & CherryPickFastOptions {
601- reuse_parent_tree_if_possible : true ,
602- } ,
603- ) {
604- Ok ( rebased_commit) => rebased_commit,
605- Err ( CreateCommitFastError :: MergeConflict { conflicting_paths } ) => {
606- return Ok ( RebaseInMemoryResult :: MergeFailed (
607- FailedMergeInfo :: Conflict {
608- commit_oid : * commit_to_apply_oid,
609- conflicting_paths,
610- } ,
611- ) )
612- }
613- Err ( other) => eyre:: bail!( other) ,
614- } ;
615-
616- let commit_message = commit_to_apply. get_message_raw ( ) ;
587+ let commit_message = original_commit. get_message_raw ( ) ;
617588 let commit_message = commit_message. to_str ( ) . with_context ( || {
618589 eyre:: eyre!(
619590 "Could not decode commit message for commit: {:?}" ,
620- commit_to_apply_oid
591+ original_commit_oid
621592 )
622593 } ) ?;
623594
624- progress. notify_status (
625- OperationIcon :: InProgress ,
626- format ! ( "Committing to repository: {commit_description}" ) ,
627- ) ;
595+ let commit_author = original_commit. get_author ( ) ;
628596 let committer_signature = if * preserve_timestamps {
629- commit_to_apply . get_committer ( )
597+ original_commit . get_committer ( )
630598 } else {
631- commit_to_apply . get_committer ( ) . update_timestamp ( * now) ?
599+ original_commit . get_committer ( ) . update_timestamp ( * now) ?
632600 } ;
633- let rebased_commit_oid = repo
634- . create_commit (
635- None ,
636- & commit_to_apply. get_author ( ) ,
637- & committer_signature,
638- commit_message,
639- & commit_tree,
640- vec ! [ & current_commit] ,
641- )
642- . wrap_err ( "Applying rebased commit" ) ?;
601+ let mut rebased_commit_oid = None ;
602+ let mut rebased_commit = None ;
603+
604+ for commit_oid in commits_to_apply_oids. iter ( ) {
605+ let commit_to_apply = repo
606+ . find_commit_or_fail ( * commit_oid)
607+ . wrap_err ( "Finding commit to apply" ) ?;
608+ let commit_description = effects
609+ . get_glyphs ( )
610+ . render ( commit_to_apply. friendly_describe ( effects. get_glyphs ( ) ) ?) ?;
611+
612+ if commit_to_apply. get_parent_count ( ) > 1 {
613+ warn ! (
614+ ?commit_oid,
615+ "BUG: Merge commit should have been detected during planning phase"
616+ ) ;
617+ return Ok ( RebaseInMemoryResult :: MergeFailed (
618+ FailedMergeInfo :: CannotRebaseMergeInMemory {
619+ commit_oid : * commit_oid,
620+ } ,
621+ ) ) ;
622+ } ;
623+
624+ progress. notify_status (
625+ OperationIcon :: InProgress ,
626+ format ! ( "Applying patch for commit: {commit_description}" ) ,
627+ ) ;
628+
629+ // Create a commit and then repeatedly amend & re-create it
630+ // FIXME what #perf gains can be had by working directly on a tree?
631+ // Is it even possible to repeatedly amend a tree and then commit
632+ // it once at the end?
633+
634+ let maybe_tree = if rebased_commit. is_none ( ) {
635+ repo. cherry_pick_fast (
636+ & commit_to_apply,
637+ & current_commit,
638+ & CherryPickFastOptions {
639+ reuse_parent_tree_if_possible : true ,
640+ } ,
641+ )
642+ } else {
643+ repo. amend_fast (
644+ & rebased_commit. expect ( "rebased commit should not be None" ) ,
645+ & AmendFastOptions :: FromCommit {
646+ commit : commit_to_apply,
647+ } ,
648+ )
649+ } ;
650+ let commit_tree = match maybe_tree {
651+ Ok ( tree) => tree,
652+ Err ( CreateCommitFastError :: MergeConflict { conflicting_paths } ) => {
653+ return Ok ( RebaseInMemoryResult :: MergeFailed (
654+ FailedMergeInfo :: Conflict {
655+ commit_oid : * commit_oid,
656+ conflicting_paths,
657+ } ,
658+ ) )
659+ }
660+ Err ( other) => eyre:: bail!( other) ,
661+ } ;
643662
644- let rebased_commit = repo
645- . find_commit_or_fail ( rebased_commit_oid)
646- . wrap_err ( "Looking up just-rebased commit" ) ?;
663+ // this is the description of each fixup commit
664+ // FIXME should we instead be using the description of the base commit?
665+ // or use a different message altogether when squashing multiple commits?
666+ progress. notify_status (
667+ OperationIcon :: InProgress ,
668+ format ! ( "Committing to repository: {commit_description}" ) ,
669+ ) ;
670+ rebased_commit_oid = Some (
671+ repo. create_commit (
672+ None ,
673+ & commit_author,
674+ & committer_signature,
675+ commit_message,
676+ & commit_tree,
677+ vec ! [ & current_commit] ,
678+ )
679+ . wrap_err ( "Applying rebased commit" ) ?,
680+ ) ;
681+
682+ rebased_commit = repo. find_commit ( rebased_commit_oid. unwrap ( ) ) ?;
683+ }
684+
685+ let rebased_commit_oid =
686+ rebased_commit_oid. expect ( "rebased oid should not be None" ) ;
647687 let commit_description =
648688 effects
649689 . get_glyphs ( )
650690 . render ( repo. friendly_describe_commit_from_oid (
651691 effects. get_glyphs ( ) ,
652692 rebased_commit_oid,
653693 ) ?) ?;
654- if rebased_commit. is_empty ( ) {
694+
695+ if rebased_commit
696+ . expect ( "rebased commit should not be None" )
697+ . is_empty ( )
698+ {
655699 rewritten_oids. insert ( * original_commit_oid, MaybeZeroOid :: Zero ) ;
656700 maybe_set_skipped_head_new_oid ( * original_commit_oid, current_oid) ;
657701
658702 writeln ! (
659703 effects. get_output_stream( ) ,
660- "[{i}/{num_picks}] Skipped now-empty commit: {commit_description}"
704+ "{commit_num} Skipped now-empty commit: {commit_description}" ,
661705 ) ?;
662706 } else {
663707 rewritten_oids. insert (
664708 * original_commit_oid,
665709 MaybeZeroOid :: NonZero ( rebased_commit_oid) ,
666710 ) ;
711+ for commit_oid in commits_to_apply_oids {
712+ rewritten_oids
713+ . insert ( * commit_oid, MaybeZeroOid :: NonZero ( rebased_commit_oid) ) ;
714+ }
715+
667716 current_oid = rebased_commit_oid;
668717
669718 writeln ! (
0 commit comments