3127

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?

Mateen Ulhaq
  • 24,552
  • 19
  • 101
  • 135
Sam Liao
  • 43,637
  • 15
  • 53
  • 61
  • 47
    See an alternative answer here: http://stackoverflow.com/a/18150592/520567 Your accepted answer is really an exact answer to your question but if you have your new commit ready before you decided to use edit, then this answer would be more straightforward. It can also work with multiple commits you want to merge/squash together with an older one. – akostadinov Aug 09 '13 at 15:45
  • 6
    Also you can just see [*Splitting a commit* in *Git Tools - Rewriting History*](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Splitting-a-Commit) for more information. – hakre Oct 06 '13 at 10:15
  • Possible duplicate of [How to modify existing, unpushed commits?](https://stackoverflow.com/questions/179123/how-to-modify-existing-unpushed-commits) – tkruse Jan 21 '18 at 01:22
  • 1
    I find the most easy way is to simply add a new commit and squash this with the one you want to change. I'm a hybrid termial-IDE(JetBrains)-git user. I usually do git-commands like status, add, commit, push on the terminal but squash with the JetBrains-git-integration. This way its super handy, super quick. In this case just add, commit, squash with respective commit (with the IDE) and push --force. Done. – Schmalitz Nov 25 '22 at 07:55

19 Answers19

4264

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

git rebase --interactive bbc643cd~

Please note the tilde ~ at the end of the command, because you need to reapply commits on top of the previous commit of bbc643cd (i.e. bbc643cd~).

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.

Wenfang Du
  • 8,804
  • 9
  • 59
  • 90
ZelluX
  • 69,107
  • 19
  • 71
  • 104
  • 158
    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". http://bit.ly/d50w1M – Diego Pino Mar 15 '10 at 19:18
  • 235
    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). – Chris Johnsen Nov 29 '10 at 03:35
  • 1
    Maybe I have an old version of Git, but mine (Git 1.5.6.5, on Debian 5, cough cough) required the `--interactive` option before the revision spec. I have edited the answer accordingly. – tripleee Mar 27 '13 at 11:21
  • 136
    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. – user123444555621 Sep 18 '13 at 08:42
  • 8
    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 '14 at 18:50
  • 2
    What are you rebasing to the *parent* (the `bbc643cd^`) of the commit you want to change, instead of the commit you actually want to change? – jpmc26 Mar 09 '15 at 21:45
  • 34
    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 02 '17 at 09:04
  • 1
    I suggest adding the `--preserve-merges` option when rebasing, this will preserve the merge commits. – Emmanuel Bourg Jun 28 '17 at 10:49
  • 4
    add `--root` and omit the `^` on the rebase command if editing the first commit, eg `git rebase -i --root 'bbc643cd'`. Via https://stackoverflow.com/a/14630424/1139122 – tobymackenzie Aug 09 '17 at 04:48
  • er, I guess just omit the hash entirely with `--root` – tobymackenzie Aug 09 '17 at 05:37
  • @EricChen Could you not answer the question with a very easy to understand answer step by step. I think it would get a lot of attention. – basickarl Oct 11 '17 at 09:19
  • This didn't let me change the author of an old commit, which I need to do. – Ariel Gabizon Jan 01 '18 at 13:35
  • `Git rebase --amend` now has a `reword` keyword that only updates the commit messages, simpler and safer – ms4720 Mar 20 '18 at 03:46
  • If I use `git rebase -i --root `, there is no possible to use `git rebase --continue` because it's not return back to the previous head commit – Francis Rodrigues Aug 26 '18 at 21:54
  • @MarcusJ I had to change the git editor to VS Code because I was not able to edit in the terminal one: you can find instructions on how to do that here: https://stackoverflow.com/questions/30024353/how-to-use-visual-studio-code-as-default-editor-for-git – MrAn3 Oct 22 '18 at 21:50
  • do you actually need the single-quotes in the first command? I mean, can't you write `bbc643cd^` instead of `'bbc643cd^'` there? – einpoklum Oct 27 '18 at 17:59
  • This also rebased other branches that were ahead into the branch I was working on. Why? – OskarD90 Nov 28 '18 at 11:37
  • I don't understand why it is so upped? This is merely answer how to modify only a comment of the commit but not the content. – user4674453 Feb 16 '19 at 09:46
  • 2
    @DanDascalescu how exactly can this "break" the repo? Reading your edit description, it sounds like you ran into a bug in an IDE or perhaps didn't pay close attention to what was happening? Adding such a vague warning to the answer isn't very helpful imho. – user247702 Jul 03 '19 at 16:33
  • @Stijn: I don't recall what happened 5 years ago, and WebStorm might have had its share of the fault. As to how repos might be broken, I've merely linked to another highly upvoted answer that mentions that. – Dan Dascalescu Jul 03 '19 at 17:36
  • If I do that git wants to rebase 46 commits, even though the commit in question is only 5 commits back. How can I be more specific about the exact commit? – panzi Sep 06 '19 at 12:06
  • This made me to resolve lots of conflicts... so I did `git rebase --abort` and didn't hesitate to commit another change instead of modifying the old one. – revo May 21 '20 at 21:59
  • On windows single quotes ( ' ) around the commit id don't seem to work, so that first command will have to be `git rebase -i "bbc643cd^"` instead of `git rebase -i 'bbc643cd^'` – ambidexterous Jul 23 '20 at 22:23
  • If you're unfamiliar to Vim, don't forget to press `i` to switch to the insert mode before changing `pick` to `edit`. `Esc` for exiting the insert mode. – Artem M Nov 23 '20 at 19:34
  • 1
    To edit the first commit, `--root` must be provided. – Tim Visée Feb 07 '21 at 23:52
  • Consider to use `--force-with-lease` for push instead of just `--force`. It's safer. It does not overwrite all other stuff like from other contributors. – Domske Dec 01 '21 at 17:56
  • A video to show how the interactive rebase works: https://www.youtube.com/watch?v=tukOm3Afd8s&ab_channel=Ihatetomatoes – RilDev Mar 11 '22 at 14:44
  • way to complicated. im just trying to push code – Philip Rego Dec 09 '22 at 18:59
  • Why not `$ git rebase --interactive 'bbc643cd^'`? – guest Jun 12 '23 at 20:54
  • @guest See https://stackoverflow.com/q/2221658/7881859 – Wenfang Du Jun 13 '23 at 02:54
730

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 HEAD@{0}: reset: moving to @~3
2c52489 HEAD@{1}: commit: more changes
4a5246d HEAD@{2}: commit: make important changes
e8571e4 HEAD@{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.

Community
  • 1
  • 1
Zaz
  • 46,476
  • 14
  • 84
  • 101
  • Should this be used on commits that have already been push to a remote branch? – Daniel Kobe Sep 29 '16 at 22:58
  • 8
    `git reset @~` exactly what I wanted to do after choosing commit with `git rebase ...`. You're my hero) – 18augst Jul 03 '17 at 09:01
  • 3
    For people wanting to edit the first commit: `git rebase -i --root`. – basickarl Oct 12 '17 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 03 '19 at 08: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 03 '19 at 08:38
  • Git, like many other interactive command-line programs, will use whichever editor you've specified in your `EDITOR` environment variable. For example, if you add `export EDITOR=emacs` to your `~/.profile` (assuming `bash` or similar shell) then it'll use Emacs. – sengi Nov 10 '19 at 18:23
  • 1
    This is awesome! In my JetBrains IDE I can right click on the commit I want to edit and choose "iteractively rebase from here" and get a nice gui window that lets me choose which of the commit(s) to pause on for editing. – Toby 1 Kenobi Jul 22 '20 at 01:54
133

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

thrau
  • 2,915
  • 3
  • 25
  • 32
  • 31
    You can also use `git commit --fixup=@~` 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 '15 at 20:57
  • 3
    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 ` amends all staged changes to the given commit – thrau Jul 07 '20 at 11:36
  • 1
    Thanks @thrau! But it is missing a closing `"`. – Roald Apr 10 '21 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"` – Jens Roland Mar 04 '22 at 10:42
  • 1
    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 '22 at 10:07
63

Based on Documentation

Amending the message of older or multiple commit messages

git rebase -i HEAD~3 

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:

pick e499d89 Delete CNAME
pick 0c39034 Better README
pick f7fde4a Change the commit message but push the same commit.

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:

pick e499d89 Delete CNAME
reword 0c39034 Better README
pick f7fde4a Change the commit message but push the same commit.

Save and close the commit list file, this will pop up a new editor for you to change your commit message, change the commit message and save.

Finally, force-push the amended commits.

git push --force
sikander
  • 2,286
  • 16
  • 23
justMe
  • 2,200
  • 16
  • 20
  • I get the following error: error: There was a problem with the editor 'vi'. Please supply the message using either -m or -F option. – Erick Maynard Sep 27 '18 at 17:18
  • 2
    The "reword" option is a good tool, however "git push --force" is dangerous. If the commits for which we want to change the commit message were not submitted yet, then --force is not necessary. The --force option rewrites the history at the remote rep, and requires more permissions. If you want to modify a commit that is only located on your computer, you do not need --force; if the commit was already pushed you shall not change it unless strictly necessary. – sissi_luaty Sep 03 '20 at 17:09
61

Run:

$ git rebase --interactive commit_hash^

each ^ indicates how many commits back you want to edit, if it's only one (the commit hash that you specified), then you just add one ^.

Using Vim you change the words pick to reword for the commits you want to change, save and quit(:wq). Then git will prompt you with each commit that you marked as reword so you can change the commit message.

Each commit message you have to save and quit(:wq) to go to the next commit message

If you want to exit without applying the changes, press :q!

EDIT: to navigate in vim you use j to go up, k to go down, h to go left, and l to go right( all this in NORMAL mode, press ESC to go to NORMAL mode ). To edit a text, press i so that you enter the INSERT mode, where you insert text. Press ESC to go back to NORMAL mode :)

UPDATE: Here's a great link from github listing How to undo (almost) anything with git

betoharres
  • 1,736
  • 2
  • 19
  • 25
  • 4
    Worked perfectly for me. Worth mentioning `git push --force`? – u01jmg3 Jul 12 '16 at 20:55
  • What `git push --force` does is overwrite the remotes commits with your local commits. That's not the case of this topic :) – betoharres Jul 14 '16 at 17:12
  • @BetuUuUu of course if your commits are pushed to remote and you have modified commit message locally, you would want to force push to remote, isn't it? – Sudip Bhandari Jul 05 '17 at 09:03
  • @SudipBhandari That's the feeling I get. I didn't force, and now I have an extra branch, mirroring all the commits back to the one whose message I changed, which is super-ugly. – ruffin Feb 05 '18 at 16:35
  • Interactive rebase might seem tricky at the beginning. I wrote a post (with pictures) which presents it in detail, step by step: https://blog.tratif.com/2018/04/19/the-power-of-git-interactive-rebase/ – Tomasz Kaczmarzyk Apr 20 '18 at 08:13
  • @Tomasz Kaczmarzyk amazing article thank you! Just a quick question, what kind of results can be expected to see on team members locals (or when they do a git pull master) if you do a push —force? – greenhouse Jun 02 '19 at 07:00
  • 2
    @greenhouse if you modify and force-push, then other team members most probably will encounter merging conflicts. So you should be generally super-cautious about it. But if you modify something which nobody else fetched yet, it should be fine (the will not notice it). So I would consider --force as last resort and always consult the state of the repo with other members. – Tomasz Kaczmarzyk Jun 21 '19 at 15:52
41

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):

git config --global alias.amend-to '!f() { SHA=`git rev-parse "$1"`; git commit --fixup "$SHA" && GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^"; }; f'

Or, a version that can also handle unstaged files (by stashing and then un-stashing them):

git config --global alias.amend-to '!f() { SHA=`git rev-parse "$1"`; git stash -k && git commit --fixup "$SHA" && GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^" && git stash pop; }; f'

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

git amend-to <REV> # e.g.
git amend-to HEAD~1
git amend-to aaaa1111

The name amend-to seems appropriate IMHO. Compare the flow with --amend:

git add . && git commit --amend --no-edit
# vs
git add . && git amend-to <REV>

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 variable SHA
  • git commit --fixup "$SHA" - fixup-commit for SHA. See git-commit docs
  • GIT_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 with git commit --fixup, see git-rebase docs for more info
    • GIT_SEQUENCE_EDITOR=true is what makes the whole thing non-interactive. This hack I learned from this blog post.
Dethariel
  • 3,394
  • 4
  • 33
  • 47
  • 1
    One can also make `amend-to` handle unstaged files: `git config --global alias.amend-to '!f() { SHA=`git rev-parse "$1"`; git stash -k && git commit --fixup "$SHA" && GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^" && git stash pop; }; f'` – Dethariel Mar 06 '18 at 22:16
  • 3
    One concern with this method is that it could apply unrelated fixups. – Ciro Santilli OurBigBook.com Dec 03 '18 at 16:05
  • Isn't the point of the question to change the commit message? Because this answer doesn't address that, or at least not directly. – wytten Dec 05 '18 at 18:10
  • @wytten the question doesn't ask about changing the commit message, it is about modifying the commit the is not HEAD. So an answer to your question would be "no, it's not the point of the question" – Dethariel Dec 05 '18 at 19:50
  • I'm confused, so how do we change the commit message? – ggb667 Jun 01 '19 at 18:14
  • Just FYI, I don't think the "$SHA1" argument to the rebase command works if it's the first commit. – Big McLargeHuge Sep 21 '20 at 23:32
  • "version that can also handle unstaged" - gold finding, Thank you! Game changer :D – Gelldur Sep 05 '22 at 07:05
26

If for some reason you don't like interactive editors, you can use git rebase --onto.

Say you want to modify Commit1. First, branch from before Commit1:

git checkout -b amending [commit before Commit1]

Second, grab Commit1 with cherry-pick:

git cherry-pick Commit1

Now, amend your changes, creating Commit1':

git add ...
git commit --amend -m "new message for Commit1"

And finally, after having stashed any other changes, transplant the rest of your commits up to master on top of your new commit:

git rebase --onto amending Commit1 master

Read: "rebase, onto the branch amending, all commits between Commit1 (non-inclusive) and master (inclusive)". That is, Commit2 and Commit3, cutting the old Commit1 out entirely. You could just cherry-pick them, but this way is easier.

Remember to clean up your branches!

git branch -d amending
FeepingCreature
  • 3,648
  • 2
  • 26
  • 25
  • 4
    you can use `git checkout -b amending Commit1~1` to get the prior commit – Arin Taylor Jul 12 '17 at 21:13
  • Are the first two steps equivalent to `git checkout -b amending Commit1`? – Haoshu Jan 24 '20 at 02:08
  • This is great answer for people scared with interactive rebase. My only gripe is that it's unnecessary to start from earlier commit and cherry-pick the actual commit you want to amend. You can just branch off the given commit and amend it as shown, skipping the cherrypick step. In fact, cherrypicking will just fastforward your branch one commit ahead, just as if you would branch directly off this commit. – Red Sep 17 '21 at 11:19
19

git stash + rebase automation

For when I need to modify an old commit a lot of times for Gerrit reviews, I've been doing:

git-amend-old() (
  # Stash, apply to past commit, and rebase the current branch on to of the result.
  current_branch="$(git rev-parse --abbrev-ref HEAD)"
  apply_to="$1"
  git stash
  git checkout "$apply_to"
  git stash apply
  git add -u
  git commit --amend --no-edit
  new_sha="$(git log --format="%H" -n 1)"
  git checkout "$current_branch"
  git rebase --onto "$new_sha" "$apply_to"
)

GitHub upstream.

Usage:

  • modify source file, no need to git add if already in repo
  • git-amend-old $old_sha

I like this over --autosquash as it does not squash other unrelated fixups.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
13

The best option is to use "Interactive rebase command".

The git rebase command is incredibly powerful. It allows you to edit commit messages, combine commits, reorder them ...etc.

Every time you rebase a commit a new SHA will be created for each commit regardless of the content will be changed or not! You should be careful when to use this command cause it may have drastic implications especially if you work in collaboration with other developers. They may start working with your commit while you're rebasing some. After you force to push the commits they will be out of sync and you may find out later in a messy situation. So be careful!

It's recommended to create a backup branch before rebasing so whenever you find things out of control you can return back to the previous state.

Now how to use this command?

git rebase -i <base> 

-i stand for "interactive". Note that you can perform a rebase in non-interactive mode. ex:

#interactivly rebase the n commits from the current position, n is a given number(2,3 ...etc)
git rebase -i HEAD~n 

HEAD indicates your current location(can be also branch name or commit SHA). The ~n means "n beforeé, so HEAD~n will be the list of "n" commits before the one you are currently on.

git rebase has different command like:

  • p or pick to keep commit as it is.
  • r or reword: to keep the commit's content but alter the commit message.
  • s or squash: to combine this commit's changes into the previous commit(the commit above it in the list).
  • ... etc.

    Note: It's better to get Git working with your code editor to make things simpler. Like for example if you use visual code you can add like this git config --global core.editor "code --wait". Or you can search in Google how to associate you preferred your code editor with GIT.

Example of git rebase

I wanted to change the last 2 commits I did so I process like this:

  1. Display the current commits:
    #This to show all the commits on one line
    $git log --oneline
    4f3d0c8 (HEAD -> documentation) docs: Add project description and included files"
    4d95e08 docs: Add created date and project title"
    eaf7978 (origin/master , origin/HEAD, master) Inital commit
    46a5819 Create README.md
    
  2. Now I use git rebase to change the 2 last commits messages: $git rebase -i HEAD~2 It opens the code editor and show this:

    pick 4d95e08 docs: Add created date and project title
    pick 4f3d0c8 docs: Add project description and included files
    
    # Rebase eaf7978..4f3d0c8 onto eaf7978 (2 commands)
    #
    # Commands:
    # p, pick <commit> = use commit
    # r, reword <commit> = use commit, but edit the commit message
    ...
    

    Since I want to change the commit message for this 2 commits. So I will type r or reword in place of pick. Then Save the file and close the tab. Note that rebase is executed in a multi-step process so the next step is to update the messages. Note also that the commits are displayed in reverse chronological order so the last commit is displayed in that one and the first commit in the first line and so forth.

  3. Update the messages: Update the first message:

    docs: Add created date and project title to the documentation "README.md"
    
    # Please enter the commit message for your changes. Lines starting
    # with '#' will be ignored, and an empty message aborts the commit.
    ...
    

    save and close Edit the second message

    docs: Add project description and included files to the documentation "README.md"
    
    # Please enter the commit message for your changes. Lines starting
    # with '#' will be ignored, and an empty message aborts the commit.
    ...
    

    save and close.

  4. You will get a message like this by the end of the rebase: Successfully rebased and updated refs/heads/documentation which means that you succeed. You can display the changes:

    5dff827 (HEAD -> documentation) docs: Add project description and included files to the documentation "README.md"
    4585c68 docs: Add created date and project title to the documentation "README.md"
    eaf7978 (origin/master, origin/HEAD, master) Inital commit
    46a5819 Create README.md
    

    I wish that may help the new users :).

DINA TAKLIT
  • 7,074
  • 10
  • 69
  • 74
12

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:

  1. git commit-edit <commit-hash>
    

    This will drop you at the commit you want to edit.

  2. 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)

  3. Redo the commit with --amend, eg:

    git commit --amend
    
  4. Complete the rebase:

    git rebase --continue
    

For the above to work, put the below script into an executable file called git-commit-edit somewhere in your $PATH:

#!/bin/bash

set -euo pipefail

script_name=${0##*/}

warn () { printf '%s: %s\n' "$script_name" "$*" >&2; }
die () { warn "$@"; exit 1; }

[[ $# -ge 2 ]] && die "Expected single commit to edit. Defaults to HEAD~"

# Default to editing the parent of the most recent commit
# The most recent commit can be edited with `git commit --amend`
commit=$(git rev-parse --short "${1:-HEAD~}")
message=$(git log -1 --format='%h %s' "$commit")

if [[ $OSTYPE =~ ^darwin ]]; then
  sed_inplace=(sed -Ei "")
else
  sed_inplace=(sed -Ei)
fi

export GIT_SEQUENCE_EDITOR="${sed_inplace[*]} "' "s/^pick ('"$commit"' .*)/edit \\1/"'
git rebase --quiet --interactive --autostash --autosquash "$commit"~
git reset --quiet @~ "$(git rev-parse --show-toplevel)"  # Reset the cache of the toplevel directory to the previous commit
git commit --quiet --amend --no-edit --allow-empty  #  Commit an empty commit so that that cache diffs are un-reversed

echo
echo "Editing commit: $message" >&2
echo
Tom Hale
  • 40,825
  • 36
  • 187
  • 242
9

Changing the Last Commit:

git commit --amend
// or
git commit --amend -m "an updated commit message"

Don’t amend public commits Amended commits are actually entirely new commits and the previous commit will no longer be on your current branch.

For example, if you want to change the last three commit messages, or any of the commit messages in that group, you supply as an argument to git rebase -i the parent of the last commit you want to edit, which is HEAD~2^ or HEAD~3. It may be easier to remember the ~3 because you’re trying to edit the last three commits, but keep in mind that you’re actually designating four commits ago, the parent of the last commit you want to edit:

$ git rebase -i HEAD~3

know more

8

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 on feature-branch

some-commit---0---1---2---(feature-branch)HEAD

Checkout to this commit and create a quick-branch. You can also clone your feature branch as a recovery point (before starting).

?(git checkout -b feature-branch-backup)
git checkout 0
git checkout -b quick-branch

You will now have something like this:

0(quick-branch)HEAD---1---2---(feature-branch)

Stage changes, stash everything else.

git add ./example.txt
git stash

Commit changes and checkout back to feature-branch

git commit --amend
git checkout feature-branch

You will now have something like this:

some-commit---0---1---2---(feature-branch)HEAD
           \
             ---0'(quick-branch)

Rebase feature-branch onto quick-branch (resolve any conflicts along the way). Apply stash and remove quick-branch.

git rebase quick-branch
git stash pop
git branch -D quick-branch

And you end up with:

some-commit---0'---1'---2'---HEAD(feature-branch)

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.

Olga
  • 1,648
  • 1
  • 22
  • 31
8

To get a non-interactive command, put a script with this content in your PATH:

#!/bin/sh
#
# git-fixup
# Use staged changes to modify a specified commit
set -e
cmt=$(git rev-parse $1)
git commit --fixup="$cmt"
GIT_EDITOR=true git rebase -i --autosquash "$cmt~1"

Use it by staging your changes (with git add) and then run git fixup <commit-to-modify>. Of course, it will still be interactive if you get conflicts.

Pelle Nilsson
  • 970
  • 8
  • 10
  • 1
    This works well. I added some extra functionality to do piecemeal fixups of a dirty tree for perfecting a commit set. `dirtydiff=$(git diff); if [ "${dirtydiff}" != "" ]; then echo "Stashing dirty tree" >&2; git stash; fi; – Simon Feltman Mar 04 '18 at 22:08
6

I solved this,

1) by creating new commit with changes i want..

r8gs4r commit 0

2) i know which commit i need to merge with it. which is commit 3.

so, git rebase -i HEAD~4 # 4 represents recent 4 commit (here commit 3 is in 4th place)

3) in interactive rebase recent commit will located at bottom. it will looks alike,

pick q6ade6 commit 3
pick vr43de commit 2
pick ac123d commit 1
pick r8gs4r commit 0

4) here we need to rearrange commit if you want to merge with specific one. it should be like,

parent
|_child

pick q6ade6 commit 3
f r8gs4r commit 0
pick vr43de commit 2
pick ac123d commit 1

after rearrange you need to replace p pick with f (fixup will merge without commit message) or s (squash merge with commit message can change in run time)

and then save your tree.

now merge done with existing commit.

Note: Its not preferable method unless you're maintain on your own. if you have big team size its not a acceptable method to rewrite git tree will end up in conflicts which you know other wont. if you want to maintain you tree clean with less commits can try this and if its small team otherwise its not preferable.....

Mohideen bin Mohammed
  • 18,813
  • 10
  • 112
  • 118
6

I do the following, including changing the date and time of local commits:

git rebase -i HEAD~6

~6 is the amount of commit history to display.

  • Change from pick to edit the commits to be edited.
  • Then I save and exit (In ubuntu: Ctrl+O to save and Ctrl+X to exit)
    1. Then I run: git commit --amend --date="2022-09-02T19:10:04" -m "NEW_MSG"
    1. If the edit is opened, just save and exit.
    1. Then to confirm and go to the next commit or finish if it is the last one, I execute: git rebase --continue

If there are more commits to edit, it is repeated from point 1

Finally I validate the changes and if everything is ok, I do the push

JxDarkAngel
  • 911
  • 11
  • 4
2

For me it was for removing some credentials from a repo. I tried rebasing and ran into a ton of seemingly unrelated conflicts along the way when trying to rebase --continue. Don't bother attempting to rebase yourself, use the tool called BFG (brew install bfg) on mac.

Pellet
  • 2,254
  • 1
  • 28
  • 20
1

If you haven't already pushed the commits then you can go back to a previous commit using git reset HEAD^[1,2,3,4...]

For example

git commit <file1> -m "Updated files 1 and 2"
git commit <file3> -m "Updated file 3"

Oops, forgot to add file2 to the first commit...

git reset HEAD^1 // because I only need to go back 1 commit

git add <file2>

This will add file2 to the first commit.

rharvey
  • 1,987
  • 1
  • 28
  • 23
1

Well, this solution might sound very silly, but can save you in certain conditions.

A friend of mine just ran into accidentally committing very some huge files (four auto-generated files ranging between 3GB to 5GB each) and then made some additional code commits on top of that before realizing the problem that git push wasn't working any longer!

The files had been listed in .gitignore but after renaming the container folder, they got exposed and committed! And now there were a few more commits of the code on top of that, but push was running forever (trying to upload GB of data!) and finally would fail due to Github's file size limits.

The problem with interactive rebase or anything similar was that they would deal with poking around these huge files and would take forever to do anything. Nevertheless, after spending almost an hour in the CLI, we weren't sure if the files (and deltas) are actually removed from the history or simply not included in the current commits. The push wasn't working either and my friend was really stuck.

So, the solution I came up with was:

  1. Rename current git folder to ~/Project-old.
  2. Clone the git folder again from github (to ~/Project).
  3. Checkout to the same branch.
  4. Manually cp -r the files from ~/Project-old folder to ~/Project.
  5. Make sure the massive files, that are not needed to be checked in are mved, and included in .gitignore properly.
  6. Also make sure you don't overwrite .git folder in the recently-cloned ~/Project by the old one. That's where the logs of the problematic history lives!
  7. Now review the changes. It should be the union of all the recent commits, excluding the problematic files.
  8. Finally commit the changes, and it's good to be push'ed.

The biggest problem with this solution is, it deals with manual copying some files, and also it merges all the recent commits into one (obviously with a new commit-hash.) B

The big benefits are that, it is very clear in every step, it works great for huge files (as well as sensitive ones), and it doesn't leave any trace in history behind!

Aidin
  • 25,146
  • 8
  • 76
  • 67
0

I had same problem and this this:

  1. First duplicated the branch as x,
  2. Then hard rest to where you want to go back to
  3. Then amend new changes
  4. After that cherry pick all other changes from the original branch
  5. Checkout the original branch
  6. Reset the original branch before the amended reversion
  7. rebase into x branch

My rider git log:

git checkout -b x 77164a510f1c17ed650b87c2ebf0f7762ac6b2a2 --
git reset --hard 0d038b5e3e3e2adef4bd6aab7653f922c3fdc63f
git add --ignore-errors -A -f -- src/Mores.Warehouse.Core.Api.ClientSdk/Mores.Warehouse.Core.Api.ClientSdk.csproj
git commit -F C:\Users\Hassan\AppData\Local\Temp\git-commit-msg-.txt --amend --
git cherry-pick 77164a510f1c17ed650b87c2ebf0f7762ac6b2a2
git checkout feature/MOR-2947 --
git reset --hard 0d038b5e3e3e2adef4bd6aab7653f922c3fdc63f
git rebase x
Hassan Faghihi
  • 1,888
  • 1
  • 37
  • 55