723

I have changed several things over the last hour and committed them step by step, but I just realized I've forgot to add a changed file some commits ago.

The Log looks like this:

GIT TidyUpRequests u:1 d:0> git log 
commit fc6734b6351f6c36a587dba6dbd9d5efa30c09ce 
Author: David Klein <> 
Date:   Tue Apr 27 09:43:55 2010 +0200

    The Main program now tests both Webservices at once

commit 8a2c6014c2b035e37aebd310a6393a1ecb39f463 
Author: David Klein <>
Date:   Tue Apr 27 09:43:27 2010 +0200

    ISBNDBQueryHandler now uses the XPath functions from XPath.fs too

commit 06a504e277fd98d97eed4dad22dfa5933d81451f 
Author: David Klein <> 
Date:   Tue Apr 27 09:30:34 2010 +0200

    AmazonQueryHandler now uses the XPath Helper functions defined in XPath.fs

commit a0865e28be35a3011d0b6091819ec32922dd2dd8 <--- changed file should go here
Author: David Klein <> 
Date:   Tue Apr 27 09:29:53 2010 +0200

    Factored out some common XPath Operations

Any ideas?

Tiago Martins Peres
  • 14,289
  • 18
  • 86
  • 145
leen
  • 8,568
  • 3
  • 23
  • 18
  • 2
    Essentially a duplicate of [How to modify a specified commit?](https://stackoverflow.com/questions/1186535/how-to-modify-a-specified-commit) – Dan Dascalescu Jan 09 '19 at 03:18

7 Answers7

1114

Use git rebase. Specifically:

  1. Use git stash to store the changes you want to add.
  2. Use git rebase -i HEAD~10 (or however many commits back you want to see).
  3. Mark the commit in question (a0865...) for edit by changing the word pick at the start of the line into edit. Don't delete the other lines as that would delete the commits.[^vimnote]
  4. Save the rebase file, and git will drop back to the shell and wait for you to fix that commit.
  5. Pop the stash by using git stash pop.
  6. Add your file with git add <file>.
  7. Amend the commit with git commit --amend --no-edit.
  8. Do a git rebase --continue which will rewrite the rest of your commits against the new one.
  9. Repeat from step 2 onwards if you have marked more than one commit for edit.
  10. If you have previously pushed the modified commits anywhere else, then you will have to push --force again to update them on the remote. However, the usual warnings about using --force apply, and you can easily lose other people's work if you are not careful and coordinate with them beforehand.

[^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".

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • 24
    What if you have unstaged changes which you want to add to the edit? If I stash them, I couldn't `git add`. – Sam Jun 05 '13 at 01:35
  • 19
    Sam you can simply unstash the changes while on the commit in question, it will work fine. – omnikron Sep 12 '13 at 12:13
  • 21
    Note: When you mark the commit with `edit`, DO NOT DELETE the other commits listed in the file. If you do, the commits will be deleted and you'll have to [follow these steps](http://stackoverflow.com/a/2496001/574190) to get them back. – David Tuite Dec 04 '13 at 17:21
  • 7
    Regarding what @DavidTuite said, a habit of mine when doing stuff on git that I'm not sure how will turn out is creating a "branchname-ref" branch to save the current state of the timeline in case I screw things up. When I'm done, I delete it. – Raphael Apr 29 '16 at 22:13
  • 1
    In step 6, include the . (dot) in the command. Correct command: `git add .` – Tom Aug 29 '18 at 16:39
  • 4
    Note that the `--no-edit` is optional and will keep your existing commit message. If you intend to update the original commit message, leave that parameter out and you'll be dropped into another editor view to change it. Also, your working directory must be clean before you can `git rebase --continue`. So if you're only adding one portion of code from your stash, you'll want to re-stash before continuing the rebase (i.e. between steps 7 & 8). – etipaced Dec 13 '19 at 15:55
  • I followed these directions exactly with one exception: I did not have a change to stash but an untracked file to add. So no stashing, but still used the other steps. Now I can't push: ```! [rejected] master -> master (non-fast-forward) error: failed to push some refs to 'git.cpdig.com:/git/phpype.git' hint: Updates were rejected because the tip of your current branch is behind its remote counterpart. Integrate the remote changes (e.g. 'git pull ...') before pushing again. See the 'Note about fast-forwards' in 'git push --help' for details.``` – Dave Feb 06 '20 at 19:08
  • 1
    @Dave if you already pushed your commits to a remote, you have to push using `--force-with-lease` (and note that rebasing after a push is not always a good idea). The details are explained [here](https://stackoverflow.com/questions/8939977/git-push-rejected-after-feature-branch-rebase) – Qw3ry Jun 26 '20 at 08:54
  • 1
    This stepwise answer is very helpful! Just to add, step 10 will be to execute command `git push -f` for pushing it to remote repository. Remember `-f` is required in this case to force push @Dave Also, after above steps, all the date times for all the N commits will be reset. – AdityaKapreShrewsburyBoston Aug 10 '21 at 20:42
  • 1
    I think it's important to note that if you've already pushed this branch remotely, you will be editing the history of everyone that has already fetched your branch. It's important to [understand the ramifications](https://stackoverflow.com/questions/1491001/what-are-the-practical-consequences-of-rewriting-git-history). Also, if you're worried about these ramifications, it's worth learning about [--force-with-lease](https://stackoverflow.com/questions/52823692/git-push-force-with-lease-vs-force) – Daniel Kaplan Jan 10 '22 at 22:33
  • 1
    I think this is the cleanest solution to have, without fixup the commit! – vincenzopalazzo Sep 24 '22 at 16:44
  • Although it takes more commands than other solutions, but I found this to be the cleanest and understandable solution. Thanks ;) – Saad Ismail May 18 '23 at 10:03
  • Great step-by-step guide. Just note that at step 8 you may have to do `git stash` again if you haven't added all modified files to your amended commit. – J.M. Janzen Jul 20 '23 at 20:07
650

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 the OLDCOMMIT commit message and automatically prefixes fixup! so it can be put in the correct order during interactive rebase. (--squash=OLDCOMMIT does the same but prefixes squash!.)
  • 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 in vim) 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 ^ in OLDCOMMIT^ means it's a reference to the commit just before OLDCOMMIT. (OLDCOMMIT^ is the first parent of OLDCOMMIT.)

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:

Joel Purra
  • 24,294
  • 8
  • 60
  • 60
  • 36
    Much clearer than the other options, and it worked like a charm – Chris Mitchelmore Apr 17 '15 at 10:30
  • 1
    Terrific answer. When I executed the final rebase command, it opened vim to allow me to edit the commit message, which i didn't need to do. Is there a way to prevent that from happening? – Jonah Oct 21 '16 at 04:58
  • 10
    @Jonah: the editor isn't opened to edit the *commit message*, but to confirm (or edit) the *rebase steps*. It cannot be avoided; [`--autosquash` is only valid when the `--interactive` option is used](https://git-scm.com/docs/git-rebase). – Joel Purra Oct 21 '16 at 11:01
  • 8
    Using this solution I am stuck git showing up VIM. My problem is, I don't know how to use VIM. How the heck do I get out of it, how can I control this thing, this so confusing. – Neon Warge Oct 30 '16 at 13:30
  • 2
    @NeonWarge: the editor of choice is configurable using for example `git config --global core.editor "pico"`. There are [several other ways to configure git and/or change your system's default editor](https://stackoverflow.com/questions/2596805/how-do-i-make-git-use-the-editor-of-my-choice-for-commits) etcetera. – Joel Purra Oct 30 '16 at 17:12
  • I didn't know `--autosquash`, it is great! If it just could bypass the editor, it would be perfect. – brandizzi Nov 11 '16 at 11:31
  • 3
    one line nice alias [here](https://stackoverflow.com/a/48999882/5767241) – idanp Apr 27 '18 at 13:06
  • @idanp: cool! Added short info about `GIT_SEQUENCE_EDITOR` and some links to this answer =) – Joel Purra Apr 27 '18 at 16:49
  • 1
    Incredibly useful. I found this, from a google engineer, which automates this process using a git alias: https://blog.filippo.io/git-fixup-amending-an-older-commit/ . Thank you! – Dom Dec 18 '19 at 11:03
  • @NeonWarge You can set your default terminal editor by adding `export EDITOR=nano` to your `~/.bashrc` -- this will affect all programs, not just git (: – xjcl Nov 12 '20 at 13:56
  • 1
    USE '~' INSTEAD OF '^' – Bexultan Myrzatay Dec 09 '20 at 16:59
  • 1
    My hero of the day! – phaberest Apr 23 '21 at 07:19
  • What if you want to append changes to the first commit in the project git history? commit^ should fail as there is no parent commit. – Shafi May 21 '21 at 12:13
  • 1
    @MASh: you can use `--root` for the very first commit in the repository: `git rebase --interactive --autosquash --root` – Joel Purra May 21 '21 at 15:24
  • @Shafi Wouldn't you just use `git commit --amend` to amend the most recent commit? @JoelPurra I just tried this on my last two commits, using the SHA from that second-to-last commit as `OLDCOMMIT`, and now that commit is completely gone. :thinking: The changed files are all in the most recent commit; they seemed to have [Highlandered](http://reappropriate.co/wp-content/uploads/2015/02/there-can-be-only-one.jpg) when I was saving (and I did follow the instruction _just save and quit the editor (:wq in vim) to continue with the rebase_). Is there a case where that's expected? – ruffin Oct 26 '21 at 18:29
  • @ruffin: I suggest that you ask a new StackOverflow question and provide full details of each step you took. – Joel Purra Oct 27 '21 at 13:37
  • Fair. ;^) I'll try again as it presents itself and ask if it Highlanders again. – ruffin Oct 27 '21 at 14:31
  • `error: cannot rebase: You have unstaged changes. error: Please commit or stash them.` Be sure to stash all not staged changes, before rebase – PythoNic Aug 11 '22 at 16:51
  • in the `zsh` in the macOS, I have to use exact parent commit id instead of `8e50480d^` – S.M.Mousavi Jun 01 '23 at 20:25
68

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

knittl
  • 246,190
  • 53
  • 318
  • 364
  • 3
    +1. Nice use of the new fixup directive (1.7+): http://stackoverflow.com/questions/2302736/trimming-git-checkins/2302947#2302947 – VonC Apr 27 '10 at 08:15
  • @knittl I was trying out your method to add another file to an old commit of mine (not pushed) but when rebasing I get `You asked me to rebase without telling me which branch you want to rebase against, and 'branch.master.merge'` and if I then use `git rebase -i --autosquash` I just get a `noop` subject line, rebasing the commit onto itself. Any idea what I do wrong? – oschrenk Mar 01 '12 at 16:24
  • 9
    @oschrenk: You need to provide a commit to which you want to rebase, e.g. `git rebase -i --autosquash HEAD~10` – knittl Mar 01 '12 at 16:42
  • 1
    Good answer but I also needed to add a commit against which to rebase. Would be great if you could update it. – Paul Odeon Mar 08 '13 at 09:44
  • @PaulOdeon: I don't understand your question. What are you trying to do, and where are you having problems? – knittl Mar 08 '13 at 16:23
  • @knittl: I believe Paul was referring to the issue oschrenk was having; The last step, `git rebase -i --autosquash` is missing the last argument. You addressed this in your [next comment](http://stackoverflow.com/questions/2719579/howto-add-a-changed-file-to-an-older-not-last-commit-in-git/2719659#comment12058260_2719659). – Aaron Blenkush Sep 27 '13 at 21:58
  • To automate prepending `fixup!` or `squash!` for `--autosquash`, use `--fixup=` or `--squash=`. – Joel Purra Dec 31 '14 at 12:05
  • I think the step to create a new commit be clear if ```master~4``` be ```HEAD~4``` or any n-number and I would add an explanation about is the n-previous commit you want to change. – Jaime M. Jun 10 '16 at 12:42
13

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.

Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
2

Here's a function that implements @Greg's answer:

function gitamendoldcommit() {
    printf "\n\nPress any key if you have no changes other than those you want to add to $1."
    printf "(You can commit your unwanted changes and undo later.):\n\n"
    read foo
    printf "\n\nChange 'pick' to 'edit' in front of $1 (top one), save, close the editor & press any key..."
    printf "Resolve any possible conflicts, if any. then: git add .; git rebase --continue\n\n"
    git rebase -i $1^

    git stash pop
    git add .
    git commit --amend --no-edit
    git rebase --continue
}

Usage: (while having only the intended changes in the staging) gitamendoldcommit $OLDCOMMIT

aljabadi
  • 463
  • 4
  • 16
0

Aditionally, if you use git rebase -i and want to go to the first commit of your current branch you can use git rebase -i --root. Now you could easily modify your first commit.

JulianSoto
  • 182
  • 1
  • 4
  • 15
0

In 2023 there are tools that do this: specifically git absorb. It is a great tool with the potential to change a lot of dusty git workflows.

Sebastian
  • 2,876
  • 2
  • 24
  • 28