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)
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:
then creating our feature or topic branch:
We begin working and make a commit that has a small error in it:
Let's say that this is commit
a123456
(we'll find the hash ID viagit log
later, or we'll useHEAD~10
as you did in your example; I just want something concrete to put here).We make more commits:
then discover the mistake. We hold off on fixing the mistake just yet, making the commit we need to make:
and then we fix the mistake,
git add
, and usegit commit --fixup
:The
--fixup
option directsgit commit
to use a specially-formatted commit message.Now that we have the fixup commit, we run:
(since
topic
was based onmain
; use whatever you need here). The form with all thepick
commands pops up, but when we look closely at it, we see that one of the lines doesn't saypick
, it saysfixup
: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. Usingsquash
instead offixup
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, usegit commit --squash
. In both casesgit commit
arranges for the futuregit 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.)