I usually submit a list of commits for review. If I have the following commits:
HEAD
Commit3
Commit2
Commit1
...I know that I can modify head commit with git commit --amend
. But how can I modify Commit1
, given that it is not the HEAD
commit?
Came to this approach (and it is probably exactly the same as using interactive rebase) but for me it's kind of straightforward.
Note: I present this approach for the sake of illustration of what you can do rather than an everyday alternative. Since it has many steps (and possibly some caveats.)
Say you want to change commit
0
and you are currently onfeature-branch
Checkout to this commit and create a
quick-branch
. You can also clone your feature branch as a recovery point (before starting).You will now have something like this:
Stage changes, stash everything else.
Commit changes and checkout back to
feature-branch
You will now have something like this:
Rebase
feature-branch
ontoquick-branch
(resolve any conflicts along the way). Apply stash and removequick-branch
.And you end up with:
Git will not duplicate (although I can't really say to what extent) the 0 commit when rebasing.
Note: all commit hashes are changed starting from the commit we originally intended to change.
Automated interactive rebase edit followed by commit revert ready for a do-over
I found myself fixing a past commit frequently enough that I wrote a script for it.
Here's the workflow:
This will drop you at the commit you want to edit.
Fix and stage the commit as you wish it had been in the first place.
(You may want to use
git stash save
to keep any files you're not committing)Redo the commit with
--amend
, eg:Complete the rebase:
For the above to work, put the below script into an executable file called
git-commit-edit
somewhere in your$PATH
:Based on Documentation
Amending the message of older or multiple commit messages
The above displays a list of the last 3 commits on the current branch, change 3 to something else if you want more. The list will look similar to the following:
Replace pick with reword before each commit message you want to change. Let say you change the second commit in the list, your file will look like the following:
Save and close the commit list file, this will pop up a new editer for you to change your commit message, change the commit message and save.
Finaly Force-push the amended commits.
You can use git rebase, for example, if you want to modify back to commit
bbc643cd
, runIn the default editor, modify
pick
toedit
in the line whose commit you want to modify. Make your changes and then commit them with the same message you had before:to modify the commit, and after that
to return back to the previous head commit.
WARNING: Note that this will change the SHA-1 of that commit as well as all children -- in other words, this rewrites the history from that point forward. You can break repos doing this if you push using the command
git push --force
Interactive rebase with
--autosquash
is something I frequently use when I need to fixup previous commits deeper in the history. It essentially speeds up the process that ZelluX's answer illustrates, and is especially handy when you have more than one commit you need to edit.From the documentation:
Assume you have a history that looks like this:
and you have changes that you want to amend to Commit2 then commit your changes using
alternatively you can use the commit-sha instead of the commit message, so
"fixup! e8adec4
or even just a prefix of the commit message.Then initiate an interactive rebase on the commit before
your editor will open with the commits already correctly ordered
all you need to do is save and exit
Completely non-interactive command(1)
I just thought I'd share an alias that I'm using for this. It's based on non-interactive interactive rebase. To add it to your git, run this command (explanation given below):
The biggest advantage of this command is the fact that it's no-vim.
(1)given that there are no conflicts during rebase, of course
Usage
The name
amend-to
seems appropriate IMHO. Compare the flow with--amend
:Explanation
git config --global alias.<NAME> '!<COMMAND>'
- creates a global git alias named<NAME>
that will execute non-git command<COMMAND>
f() { <BODY> }; f
- an "anonymous" bash function.SHA=`git rev-parse "$1"`;
- converts the argument to git revision, and assigns the result to variableSHA
git commit --fixup "$SHA"
- fixup-commit forSHA
. Seegit-commit
docsGIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^"
git rebase --interactive "$SHA^"
part has been covered by other answers.--autosquash
is what's used in conjunction withgit commit --fixup
, seegit-rebase
docs for more infoGIT_SEQUENCE_EDITOR=true
is what makes the whole thing non-interactive. This hack I learned from this blog post.