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 loglater, or we'll useHEAD~10as 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
--fixupoption directsgit committo use a specially-formatted commit message.Now that we have the fixup commit, we run:
(since
topicwas based onmain; use whatever you need here). The form with all thepickcommands 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
fixupcommand basically tells Git: squash this commit into the previous commit, while dropping this commit's log message entirely. Usingsquashinstead offixuptells 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
--fixuphere. To automatically get a "squash" command, usegit commit --squash. In both casesgit commitarranges for the futuregit rebase --autosquashto 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 --fixupanyway, 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.)