@@ -6,7 +6,7 @@ use std::sync::Arc;
66
77use chashmap:: CHashMap ;
88use eyre:: Context ;
9- use itertools:: Itertools ;
9+ use itertools:: { intersperse , Itertools } ;
1010use rayon:: { prelude:: * , ThreadPool } ;
1111use tracing:: { instrument, warn} ;
1212
@@ -148,7 +148,63 @@ impl ToString for RebaseCommand {
148148 } => match commits_to_apply_oids. as_slice ( ) {
149149 [ ] => String :: new ( ) ,
150150 [ commit_oid] => format ! ( "pick {commit_oid}" ) ,
151- [ ..] => unimplemented ! ( "On-disk fixups" ) ,
151+ [ pick_oid, fixup_oids @ ..] => {
152+ let mut picks = vec ! [ format!( "pick {pick_oid}" ) ] ;
153+ let mut fixups = fixup_oids
154+ . iter ( )
155+ . map ( |oid| format ! ( "fixup {oid}" ) )
156+ . collect :: < Vec < String > > ( ) ;
157+ let mut cleanups = vec ! [ ] ;
158+
159+ // Since 0ca8681, the intermediate commits created as each
160+ // fixup is applied are left behind in the smartlog. This
161+ // forcibly removes all but the last of them. I don't
162+ // understand why this happens during `git branchless`
163+ // initiated rebases, but not during "normal" fixup rebases,
164+ // but this makes these artifacts go away.
165+ if fixups. len ( ) > 1 {
166+ fixups = intersperse (
167+ fixups,
168+ "exec git branchless hook-skip-upstream-applied-commit $(git rev-parse HEAD)" . to_string ( )
169+ ) . collect ( )
170+ }
171+
172+ // If the destination commit (ie `original_commit_oid`) does
173+ // not come first topologically among the commits being
174+ // rebased, then the final squashed commit will end up with
175+ // the wrong metadata. (It will end up with the metadata
176+ // from the commit that *does* come first, ie `pick_oid`.)
177+ // We have to add some additional steps to make sure the
178+ // smartlog and commit metadata are left as the user
179+ // expects.
180+ if pick_oid != original_commit_oid {
181+ // See above comment related to 0ca8681
182+ picks. insert (
183+ 1 ,
184+ "exec git branchless hook-skip-upstream-applied-commit $(git rev-parse HEAD)" . to_string ( )
185+ ) ;
186+
187+ cleanups = vec ! [
188+ // Hide the final squashed commit
189+ "exec git branchless hook-skip-upstream-applied-commit $(git rev-parse HEAD)" . to_string( ) ,
190+
191+ // Create a new final commit by applying the
192+ // metadata from the destination commit to the just
193+ // now hidden commit.
194+ format!( "exec git commit --amend --no-edit --reuse-message {original_commit_oid}" ) ,
195+
196+ // Finally, register the new final commit as the
197+ // rewritten version of original_commit_oid
198+ format!( "exec git branchless hook-skip-upstream-applied-commit {original_commit_oid} $(git rev-parse HEAD)" )
199+ ] ;
200+ }
201+
202+ picks
203+ . iter ( )
204+ . chain ( fixups. iter ( ) )
205+ . chain ( cleanups. iter ( ) )
206+ . join ( "\n " )
207+ }
152208 } ,
153209 RebaseCommand :: Merge {
154210 commit_oid,
@@ -1170,9 +1226,16 @@ impl<'a> RebasePlanBuilder<'a> {
11701226
11711227 let first_parent_oid = * parent_oids. first ( ) . unwrap ( ) ;
11721228 first_dest_oid. get_or_insert ( first_parent_oid) ;
1173- acc. push ( RebaseCommand :: Reset {
1174- target : OidOrLabel :: Oid ( first_parent_oid) ,
1175- } ) ;
1229+ // FIXME This feels heavy handed, but seems to be necessary for some fixup cases.
1230+ if !state
1231+ . constraints
1232+ . commits_to_move ( )
1233+ . contains ( & first_parent_oid)
1234+ {
1235+ acc. push ( RebaseCommand :: Reset {
1236+ target : OidOrLabel :: Oid ( first_parent_oid) ,
1237+ } ) ;
1238+ }
11761239
11771240 let upstream_patch_ids = if * detect_duplicate_commits_via_patch_id {
11781241 let ( effects, _progress) =
0 commit comments