@@ -148,7 +148,69 @@ 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+ //
166+ // Re implementation: `intersperse()` would be ideal here,
167+ // but it's only in the nightly API.
168+ if fixups. len ( ) > 1 {
169+ for i in 0 ..( fixups. len ( ) - 1 ) {
170+ fixups. insert (
171+ i* 2 + 1 ,
172+ // FIXME I'm assuming that $(...) is not portable and will need to be changed
173+ "exec git branchless hook-skip-upstream-applied-commit $(git rev-parse HEAD)" . to_string ( )
174+ )
175+ }
176+ }
177+
178+ // If the destination commit (ie `original_commit_oid`) does
179+ // not come first topologically among the commits being
180+ // rebased, then the final squashed commit will end up with
181+ // the wrong metadata. (It will end up with the metadata
182+ // from the commit that *does* come first, ie `pick_oid`.)
183+ // We have to add some additional steps to make sure the
184+ // smartlog and commit metadata are left as the user
185+ // expects.
186+ if pick_oid != original_commit_oid {
187+ // See above comment related to 0ca8681
188+ picks. insert (
189+ 1 ,
190+ "exec git branchless hook-skip-upstream-applied-commit $(git rev-parse HEAD)" . to_string ( )
191+ ) ;
192+
193+ cleanups = vec ! [
194+ // Hide the final squashed commit
195+ "exec git branchless hook-skip-upstream-applied-commit $(git rev-parse HEAD)" . to_string( ) ,
196+
197+ // Create a new final commit by applying the
198+ // metadata from the destination commit to the (now
199+ // hidden) new commit.
200+ format!( "exec git commit --amend --no-edit --reuse-message {original_commit_oid}" ) ,
201+
202+ // register the new commit as the rewritten version
203+ // of original_commit_oid
204+ format!( "exec git branchless hook-skip-upstream-applied-commit {original_commit_oid} $(git rev-parse HEAD)" )
205+ ] ;
206+ }
207+
208+ picks
209+ . iter ( )
210+ . chain ( fixups. iter ( ) )
211+ . chain ( cleanups. iter ( ) )
212+ . join ( "\n " )
213+ }
152214 } ,
153215 RebaseCommand :: Merge {
154216 commit_oid,
@@ -1169,9 +1231,18 @@ impl<'a> RebasePlanBuilder<'a> {
11691231 } ;
11701232 let first_parent_oid = * parent_oids. first ( ) . unwrap ( ) ;
11711233 first_dest_oid. get_or_insert ( first_parent_oid) ;
1172- acc. push ( RebaseCommand :: Reset {
1173- target : OidOrLabel :: Oid ( first_parent_oid) ,
1174- } ) ;
1234+ // FIXME this may be necessary in some fixup cases, but feels drastic
1235+ // I *think* it makes sense, though: if we're building from roots down (roots out?)
1236+ // then parents will be (should be) dealt with first anyway ... no maybe it's OK?
1237+ if !state
1238+ . constraints
1239+ . commits_to_move ( )
1240+ . contains ( & first_parent_oid)
1241+ {
1242+ acc. push ( RebaseCommand :: Reset {
1243+ target : OidOrLabel :: Oid ( first_parent_oid) ,
1244+ } ) ;
1245+ }
11751246
11761247 let upstream_patch_ids = if * detect_duplicate_commits_via_patch_id {
11771248 let ( effects, _progress) =
0 commit comments