Categories
git git-rewrite-history

How do I modify a specific commit?

2874

I have the following commit history:

  1. HEAD
  2. HEAD~
  3. HEAD~2
  4. HEAD~3

git commit --amend modifies the current HEAD commit. But how do I modify HEAD~3?

3

3871

Use git rebase. For example, to modify commit bbc643cd, run:

$ git rebase --interactive 'bbc643cd^'

Please note the caret ^ at the end of the command, because you need actually to rebase back to the commit before the one you wish to modify.

In the default editor, modify pick to edit in the line mentioning bbc643cd.

Save the file and exit. git will interpret and automatically execute the commands in the file. You will find yourself in the previous situation in which you just had created commit bbc643cd.

At this point, bbc643cd is your last commit and you can easily amend it. Make your changes and then commit them with the command:

$ git commit --all --amend --no-edit

After that, return back to the previous HEAD commit using:

$ git rebase --continue

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.

27

  • 149

    Another interesting option within this flow is once you have moved to the commit you want to modify, instead of modifying files and ammed over the commit on top (the one you’re editing), you may want to split that commit into two different commits (or even more). In that case, move back to the commit to edit, and run “git reset HEAD^”. that will put the modified files of that commit into the stage. Now pick and commit any files as you wish. This flow is quite well explained in “git-rebase” man page. See section “Splitting commits”. bit.ly/d50w1M

    Mar 15, 2010 at 19:18


  • 221

    In Git 1.6.6 and newer you can use the reword action in git rebase -i instead of edit (it automatically opens the editor and continues with the rest of the rebase steps; this obviates the use of git commit --ammend and git rebase --continue when you only need to change the commit message and not the content).

    Nov 29, 2010 at 3:35


  • 130

    It’s worth noting that you may need to run git stash before git rebase and git stash pop afterwards, if you have pending changes.

    Sep 18, 2013 at 8:42

  • 7

    Is there a shortucut command to edit a specific commit in the interactive rebase without opening the editor, finding the commit, marking it edit, then dropping back to the command line?

    – sstur

    Nov 21, 2014 at 18:50

  • 27

    Note that with newer git, it would be wiser to follow prompt instructions instead of blindly using git commit --all --amend --no-edit here. All I had to do after git rebase -i ... was to git commit --amend normally then git rebase --continue.

    – Eric Chen

    Mar 2, 2017 at 9:04

643

Use the awesome interactive rebase:

git rebase -i @~9   # Show the last 9 commits in a text editor

Find the commit you want, change pick to e (edit), and save and close the file. Git will rewind to that commit, allowing you to either:

  • use git commit --amend to make changes, or
  • use git reset @~ to discard the last commit, but not the changes to the files (i.e. take you to the point you were at when you’d edited the files, but hadn’t committed yet).

The latter is useful for doing more complex stuff like splitting into multiple commits.

Then, run git rebase --continue, and Git will replay the subsequent changes on top of your modified commit. You may be asked to fix some merge conflicts.

Note: @ is shorthand for HEAD, and ~ is the commit before the specified commit.

Read more about rewriting history in the Git docs.

Don’t be afraid to rebase

ProTip™:   Don’t be afraid to experiment with “dangerous” commands that rewrite history* — Git doesn’t delete your commits for 90 days by default; you can find them in the reflog:

$ git reset @~3   # go back 3 commits
$ git reflog
c4f708b [email protected]{0}: reset: moving to @~3
2c52489 [email protected]{1}: commit: more changes
4a5246d [email protected]{2}: commit: make important changes
e8571e4 [email protected]{3}: commit: make some changes
... earlier commits ...
$ git reset 2c52489
... and you're back where you started

* Watch out for options like --hard and --force though — they can discard data.
* Also, don’t rewrite history on any branches you’re collaborating on.


On many systems, git rebase -i will open up Vim by default. Vim doesn’t work like most modern text editors, so take a look at how to rebase using Vim. If you’d rather use a different editor, change it with git config --global core.editor your-favorite-text-editor.

7

  • Should this be used on commits that have already been push to a remote branch?

    Sep 29, 2016 at 22:58

  • 6

    git reset @~ exactly what I wanted to do after choosing commit with git rebase .... You’re my hero)

    – 18augst

    Jul 3, 2017 at 9:01

  • For people wanting to edit the first commit: git rebase -i --root.

    – basickarl

    Oct 12, 2017 at 10:06


  • This is really great – thanks. Following use of git reset @~ to make a few updates (mostly a git checkout file-i-didn't-mean-to-change), is there an easy way to put add that commit back with the original message? I copied the original message from the blurb output by git status, and then ran git rebase --continue, but I wondered if there is an easier way? Perhaps if there isn’t an easier way, you could also mention this briefly in your answer?

    – Benjohn

    May 3, 2019 at 8:15

  • Ah! I guess I could have used: git reset @~ -- file-i-didn't-mean-to-change followed by git commit --amend --no-edit?

    – Benjohn

    May 3, 2019 at 8:38

114

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:

--autosquash

When the commit log message begins with “squash! …​” (or “fixup! …​”), and there is a commit whose title begins with the same …​, automatically modify the todo list of rebase -i so that the commit marked for squashing comes right after the commit to be modified

Assume you have a history that looks like this:

$ git log --graph --oneline
* b42d293 Commit3
* e8adec4 Commit2
* faaf19f Commit1

and you have changes that you want to amend to Commit2 then commit your changes using

$ git commit -m "fixup! Commit2"

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

$ git rebase e8adec4^ -i --autosquash

your editor will open with the commits already correctly ordered

pick e8adec4 Commit2
fixup 54e1a99 fixup! Commit2
pick b42d293 Commit3

all you need to do is save and exit

5

  • 30

    You can also use git commit [email protected]~ instead of git commit -m "fixup! Commit2". This is especially useful when your commit messages are longer and it would be a pain to type out the whole thing.

    – Zaz

    Oct 19, 2015 at 20:57

  • 2

    i wrote an alias for my .gitconfig to streamline this fixup = "!fn() { git commit --fixup ${1} && GIT_EDITOR=true git rebase --autosquash -i ${1}^; }; fn -> git fixup <commitId> amends all staged changes to the given commit

    – thrau

    Jul 7, 2020 at 11:36

  • 1

    Thanks @thrau! But it is missing a closing ".

    – Roald

    Apr 10, 2021 at 13:57


  • 1

    Thraus alias doesn’t seem to work with short commit hashes. This one works: fixup = "!fn() { git commit -m \"fixup! ${1}\" && GIT_EDITOR=true git rebase --autosquash -i ${1}^; }; fn"

    Mar 4 at 10:42

  • Now works with HEAD, HEAD^, HEAD~7 etc: fixup = "!fn() { _FIXUP_COMMIT=`git rev-parse ${1}` && git commit -m \"fixup! ${_FIXUP_COMMIT}\" && GIT_EDITOR=true git rebase --autosquash -i ${_FIXUP_COMMIT}^; }; fn"

    – Dori

    Jul 22 at 10:07