How to add a changed file to an older (not last) commit in Git
Solution 1:
Use git rebase
. Specifically:
- Use
git stash
to store the changes you want to add. - Use
git rebase -i HEAD~10
(or however many commits back you want to see). - Mark the commit in question (
a0865...
) for edit by changing the wordpick
at the start of the line intoedit
. Don't delete the other lines as that would delete the commits.[^vimnote] - Save the rebase file, and git will drop back to the shell and wait for you to fix that commit.
- Pop the stash by using
git stash pop
- Add your file with
git add <file>
. - Amend the commit with
git commit --amend --no-edit
. - Do a
git rebase --continue
which will rewrite the rest of your commits against the new one. - Repeat from step 2 onwards if you have marked more than one commit for edit.
[^vimnote]: If you are using vim
then you will have to hit the Insert key to edit, then Esc and type in :wq
to save the file, quit the editor, and apply the changes. Alternatively, you can configure a user-friendly git commit editor with git config --global core.editor "nano"
.
Solution 2:
To "fix" an old commit with a small change, without changing the commit message of the old commit, where OLDCOMMIT
is something like 091b73a
:
git add <my fixed files>
git commit --fixup=OLDCOMMIT
git rebase --interactive --autosquash OLDCOMMIT^
You can also use git commit --squash=OLDCOMMIT
to edit the old commit message during rebase.
See documentation for git commit and git rebase. As always, when rewriting git history, you should only fixup or squash commits you have not yet published to anyone else (including random internet users and build servers).
Detailed explanation
-
git commit --fixup=OLDCOMMIT
copies theOLDCOMMIT
commit message and automatically prefixesfixup!
so it can be put in the correct order during interactive rebase. (--squash=OLDCOMMIT
does the same but prefixessquash!
.) -
git rebase --interactive
will bring up a text editor (which can be configured) to confirm (or edit) the rebase instruction sequence. There is info for rebase instruction changes in the file; just save and quit the editor (:wq
invim
) to continue with the rebase. -
--autosquash
will automatically put any--fixup=OLDCOMMIT
commits in the correct order. Note that--autosquash
is only valid when the--interactive
option is used. - The
^
inOLDCOMMIT^
means it's a reference to the commit just beforeOLDCOMMIT
. (OLDCOMMIT^
is the first parent ofOLDCOMMIT
.)
Optional automation
The above steps are good for verification and/or modifying the rebase instruction sequence, but it's also possible to skip/automate the interactive rebase text editor by:
- Setting
GIT_SEQUENCE_EDITOR
to a script. - Creating a git alias to automatically autosquash all queued fixups.
- Creating a git alias to automatically fixup a single commit.
Solution 3:
with git 1.7, there's a really easy way using git rebase
:
stage your files:
git add $files
create a new commit and re-use commit message of your "broken" commit
git commit -c master~4
prepend fixup!
in the subject line (or squash!
if you want to edit commit (message)):
fixup! Factored out some common XPath Operations
use git rebase -i --autosquash
to fixup your commit
Solution 4:
You can try a rebase --interactive
session to amend your old commit (provided you did not already push those commits to another repo).
Sometimes the thing fixed in b.2. cannot be amended to the not-quite perfect commit it fixes, because that commit is buried deeply in a patch series.
That is exactly what interactive rebase is for: use it after plenty of "a"s and "b"s, by rearranging and editing commits, and squashing multiple commits into one.Start it with the last commit you want to retain as-is:
git rebase -i <after-this-commit>
An editor will be fired up with all the commits in your current branch (ignoring merge commits), which come after the given commit.
You can reorder the commits in this list to your heart's content, and you can remove them. The list looks more or less like this:
pick deadbee The oneline of this commit
pick fa1afe1 The oneline of the next commit
...
The oneline descriptions are purely for your pleasure; git rebase will not look at them but at the commit names ("deadbee" and "fa1afe1" in this example), so do not delete or edit the names.
By replacing the command "pick" with the command "edit", you can tell git rebase to stop after applying that commit, so that you can edit the files and/or the commit message, amend the commit, and continue rebasing.