@@ -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
@@ -133,12 +133,68 @@ impl RebaseCommand {
133133 RebaseCommand :: CreateLabel { label_name } => format ! ( "label {label_name}" ) ,
134134 RebaseCommand :: Reset { target } => format ! ( "reset {target}" ) ,
135135 RebaseCommand :: Pick {
136- original_commit_oid : _ ,
136+ original_commit_oid,
137137 commits_to_apply_oids,
138138 } => match commits_to_apply_oids. as_slice ( ) {
139139 [ ] => String :: new ( ) ,
140140 [ commit_oid] => format ! ( "pick {commit_oid}" ) ,
141- [ ..] => unimplemented ! ( "On-disk fixups are not yet supported" ) ,
141+ [ pick_oid, fixup_oids @ ..] => {
142+ let mut picks = vec ! [ format!( "pick {pick_oid}" ) ] ;
143+ let mut fixups = fixup_oids
144+ . iter ( )
145+ . map ( |oid| format ! ( "fixup {oid}" ) )
146+ . collect :: < Vec < String > > ( ) ;
147+ let mut cleanups = vec ! [ ] ;
148+
149+ // Since 0ca8681, the intermediate commits created as each
150+ // fixup is applied are left behind in the smartlog. This
151+ // forcibly removes all but the last of them. I don't
152+ // understand why this happens during `git branchless`
153+ // initiated rebases, but not during "normal" fixup rebases,
154+ // but this makes these artifacts go away.
155+ if fixups. len ( ) > 1 {
156+ fixups = intersperse (
157+ fixups,
158+ "exec git branchless hook-skip-upstream-applied-commit $(git rev-parse HEAD)" . to_string ( )
159+ ) . collect ( )
160+ }
161+
162+ // If the destination commit (ie `original_commit_oid`) does
163+ // not come first topologically among the commits being
164+ // rebased, then the final squashed commit will end up with
165+ // the wrong metadata. (It will end up with the metadata
166+ // from the commit that *does* come first, ie `pick_oid`.)
167+ // We have to add some additional steps to make sure the
168+ // smartlog and commit metadata are left as the user
169+ // expects.
170+ if pick_oid != original_commit_oid {
171+ // See above comment related to 0ca8681
172+ picks. insert (
173+ 1 ,
174+ "exec git branchless hook-skip-upstream-applied-commit $(git rev-parse HEAD)" . to_string ( )
175+ ) ;
176+
177+ cleanups = vec ! [
178+ // Hide the final squashed commit
179+ "exec git branchless hook-skip-upstream-applied-commit $(git rev-parse HEAD)" . to_string( ) ,
180+
181+ // Create a new final commit by applying the
182+ // metadata from the destination commit to the just
183+ // now hidden commit.
184+ format!( "exec git commit --amend --no-edit --reuse-message {original_commit_oid}" ) ,
185+
186+ // Finally, register the new final commit as the
187+ // rewritten version of original_commit_oid
188+ format!( "exec git branchless hook-skip-upstream-applied-commit {original_commit_oid} $(git rev-parse HEAD)" )
189+ ] ;
190+ }
191+
192+ picks
193+ . iter ( )
194+ . chain ( fixups. iter ( ) )
195+ . chain ( cleanups. iter ( ) )
196+ . join ( "\n " )
197+ }
142198 } ,
143199 RebaseCommand :: Merge {
144200 commit_oid,
@@ -1172,9 +1228,16 @@ impl<'a> RebasePlanBuilder<'a> {
11721228
11731229 let first_parent_oid = * parent_oids. first ( ) . unwrap ( ) ;
11741230 first_dest_oid. get_or_insert ( first_parent_oid) ;
1175- acc. push ( RebaseCommand :: Reset {
1176- target : OidOrLabel :: Oid ( first_parent_oid) ,
1177- } ) ;
1231+ // FIXME This feels heavy handed, but seems to be necessary for some fixup cases.
1232+ if !state
1233+ . constraints
1234+ . commits_to_move ( )
1235+ . contains ( & first_parent_oid)
1236+ {
1237+ acc. push ( RebaseCommand :: Reset {
1238+ target : OidOrLabel :: Oid ( first_parent_oid) ,
1239+ } ) ;
1240+ }
11781241
11791242 let upstream_patch_ids = if * detect_duplicate_commits_via_patch_id {
11801243 let ( effects, _progress) =
0 commit comments