I'm in the process of editing my source files to produce a new commit atop my dev
branch. My changes are saved on disk but not yet staged.
Then I notice a small mistake introduced by a former commit, say HEAD~10
. Once fixed, the result is only one (or a few) additional git diff hunk(s) on my disk.
I don't want these hunks to be recorded within the new commit, because they belong to HEAD~10
. So I need to rebase. Here is my workflow then, but I'm not satisfied with it:
$ git stash # save my current work the small fix
$ git rebase --interactive HEAD~11
# set first commit appearing in todo list to `edit` action
$ git stash pop # re-introduce my changes here (**may conflict**)
$ git add --patch fixed_files # or similar, to only stage relevant fix hunk(s)
$ git commit --amend # fix old commit
$ git stash # save my current work again
$ git rebase --continue # get back to my current commit (will likely *not* conflict)
$ git stash pop # back here with HEAD~10 fixed
What I'm not happy with is that the process is complicated, and that the first git stash pop
line may introduce meaningless conflicts, even though I'm confident that no conflicts will occur during execution of the git rebase --continue
line.
Is there a better way? Suppose I only have a few staged hunks in HEAD
, can I easily introduce them earlier in my branch with some magical:
git amend-old-commit-then-rebase HEAD~10
yet keep my unstaged changes? (of course I'd be warned in case the inherent rebase does conflict)
CodePudding user response:
Interactive rebase already has a built in way to deal with this. Let's demonstrate it. We start by checking out the tip commit of some existing branch name:
git switch main
then creating our feature or topic branch:
git switch -c topic
We begin working and make a commit that has a small error in it:
... edit ...
git add frotz.c
git commit
Let's say that this is commit a123456
(we'll find the hash ID via git log
later, or we'll use HEAD~10
as you did in your example; I just want something concrete to put here).
We make more commits:
... edit and commit repeatedly ...
then discover the mistake. We hold off on fixing the mistake just yet, making the commit we need to make:
git commit
and then we fix the mistake, git add
, and use git commit --fixup
:
... edit frotz.c to fix the mistake ...
git add frotz.c
git commit --fixup a123456 # or git commit --fixup HEAD~10
The --fixup
option directs git commit
to use a specially-formatted commit message.
Now that we have the fixup commit, we run:
git rebase -i --autosquash main
(since topic
was based on main
; use whatever you need here). The form with all the pick
commands pops up, but when we look closely at it, we see that one of the lines doesn't say pick
, it says fixup
:
pick a123456 commit subject line
fixup b789abc fixup! commit subject line
pick 9876543 another commit subject
... and so on ...
Writing out this set of commands (or not bothering since we don't need to change them around) and exiting the editor fires up the actual rebase, which ... absorbs the fixup commit into the bad commit, so that it's now all just one commit.
The fixup
command basically tells Git: squash this commit into the previous commit, while dropping this commit's log message entirely. Using squash
instead of fixup
tells Git: squash this commit into the previous one, but stop and give me a chance to write a new log message, showing me the two existing log messages. These are effectively the same except for the chance to edit the commit message.
Since you didn't mention wanting to fix the commit message, we used --fixup
here. To automatically get a "squash" command, use git commit --squash
. In both cases git commit
arranges for the future git rebase --autosquash
to see a message that tells rebase rearrange the order of the pick commands, and change the second one to squash or fixup as appropriate.
As always, you can also re-order and/or modify the instructions manually. (I usually do this rather than using git commit --fixup
anyway, as I often want to do a lot of commit message rewording, and think about which commits to combine and what order to put them in.)