108

I have committed a change and forgot to add a file to the change set. After other commits, I realized the file is now missing from a HEAD^4 commit.

How do I rewrite a previous commit to include the missing file?

Jacek Laskowski
  • 72,696
  • 27
  • 242
  • 420
kolrie
  • 12,562
  • 14
  • 64
  • 98

5 Answers5

289

I realize people can google and come here to find a simpler answer: What if it was just the last commit? (OP's question is for fixing the 4th commit back in history)

In the case you commit and realize you forgot to add some file immediately, just do:

# edited file-that-i-remember.txt
git add file-that-i-remember.txt
git commit

# realize you forgot a file
git add file-that-i-forgot.txt
git commit --amend --no-edit

Where --no-edit will keep the same commit message.

Easy peasy!

DrBeco
  • 11,237
  • 9
  • 59
  • 76
  • 6
    Worth mentioning, if the commits aren't pushed to the remote. – Ram Patra Feb 01 '17 at 17:31
  • 2
    Yes, it is worth mentioning here in the comments: It is for using before **push**. Thanks for pointing that. – DrBeco Feb 21 '17 at 12:31
  • 2
    One notice is that the commits before and after `--amend` have [different hashes](http://stackoverflow.com/questions/3926768/amend-a-commit-that-wasnt-the-previous-commit) – sonlexqt Apr 13 '17 at 04:07
  • Great answer. This should actually be the accepted best answer – David Alvarez Jul 09 '17 at 05:24
  • 7
    Thanks, but it can't be: OP asked for `HEAD^4`. Its ok the way it is, just as an addendum for reference. ;) – DrBeco Jul 09 '17 at 06:23
  • 1
    The number of times I've googled this just to find this answer... :) – OJ7 Sep 22 '17 at 18:26
  • 1
    I thought this was the answer to my problem, but it does not work if you already pushed. – stackers Mar 25 '20 at 21:29
  • What if the that last commit was already pushed? I can't find a question/answer for that... So, created this one: https://stackoverflow.com/questions/74449281/how-to-change-past-commit-to-include-a-missed-file-having-made-a-push – nephewtom Nov 15 '22 at 16:47
62

Use git rebase --interactive HEAD~4 and set edit option for the commit you'd like to amend.

Remember that you should not modify commits pushed to the remote repository this way. It's better to add a new commit with missing file in that case.


To make this more clear, first stash any current changes with git stash. Then, git rebase --interactive HEAD~4. You get the following in a text editor (note that you'll get 5 commits, in descending order):

pick 123e123 fifth last commit message
pick 321e122 fourth last commit message
pick 1d23e3f third last commit message
pick 987a987 second last commit message
pick 8a8a8a8 last commit message

Modify the change entry's prefix from pick to edit. That'd be edit 321e122 ... for the OP.

git rebase goes through the entries, in order. As there's only one we're changing, you'll only have one entry to change. Now, add your files with git add, and git commit --amend to amend the current commit with those added files.

Finally, git rebase --continue moves onto the next file. As there's only one, the rebase is complete

Kieran101
  • 565
  • 5
  • 18
Rafał Rawicki
  • 22,324
  • 5
  • 59
  • 79
  • Thanks. Is that the case even if I am the only user of the remote repo? Wouldn't it allow me to do `git push -f` if I am sure the upstream didn't change? – kolrie Jan 16 '13 at 22:48
  • 1
    If you are the only user of the remote repo it's ok to do the forced push. – Rafał Rawicki Jan 16 '13 at 22:49
  • 7
    I think these instructions are not detailed enough. On trying it the first time I got told "Cannot rebase: Your index contains uncommitted changes." I had already `add`-ed the missing files, so I did a commit with "xxx" as the message. Then I did the rebase command, and changed the "xxx" commit from "pick" to "edit". Then I did "git rebase --continue". Now when I look at the history I have "xxx" as the latest commit, and the earlier commit I wanted to add them to is unchanged! I wonder where my mistake was? – Darren Cook Nov 27 '13 at 03:02
  • @DarrenCook You should use 'squash', not 'edit'. The instructions are detailed enough. – Rafał Rawicki Nov 27 '13 at 07:47
  • 2
    Squashing the last commit will not put the file in HEAD~4. – Justin Jul 07 '16 at 03:40
  • This is useful, although I needed some experimenting to get it done. – biocyberman Sep 28 '16 at 21:13
  • 2
    git add editedFiles; git commit -m "Blah"; git rebase -i HEAD~5; // as now new commit added so we needed to rebase with 5 instead of 4. now move the "Blah" commit to second line and change it from "Pick" to "s" (squash) which will squash the commit with HEAD~5 as commands are executed from top to bottom – zstring Mar 06 '17 at 05:37
  • Downvoted because not only do I agree with @DarrenCook, but for the dismissive attitude. Mvp's answer is better. – ian Jun 10 '19 at 08:33
  • [Dominik's answer](https://stackoverflow.com/a/47662843/1295595) has the necessary detail to explain this answer. Without that detail, I wouldn't be confident enough to go messing around in my repository's history. – craq Sep 22 '19 at 23:31
14

If you have NOT pushed these 4 commits, you can do it as follows:

Create patch files for all these commits:

git format-patch -4

Rewind back by 4 commits:

git reset --hard HEAD~4

Add missing file:

git add missing-file

Commit it with --amend:

git commit --amend

Apply all saved patches back:

git am *.patch

If you have pushed, you should NOT use this method. Instead, just admit your blunder and create one more commit on top of HEAD which fixes this issue.

mvp
  • 111,019
  • 13
  • 122
  • 148
  • If you want to do this step-by-step it is easier to cherry-pick commits after the modified one, than to export them as a patch. – Rafał Rawicki Jan 16 '13 at 22:46
  • 1
    This is matter of taste. I like `git format-patch`/`git am` much better. Most importantly it gives you more confidence if you screw up something - commit saved as patch in physical file is your best safety net. – mvp Jan 16 '13 at 22:49
  • The real confidence lies in fact, that when operating on a git repository you never remove a thing. Old commits are available until you run `git gc` :) – Rafał Rawicki Jan 16 '13 at 22:51
  • 1
    This is trivial and obvious for you and me. But, for user who is just getting started and probably does not understand anything about git - this fact is not obvious at all. – mvp Jan 16 '13 at 22:53
  • 2
    These instructions seemed long-winded, but were quite simple and easy to follow. Thanks. (I'd just add a final step: `rm *.patch` ) – Darren Cook Nov 27 '13 at 11:44
  • Unnecessarily complicated. Why not just `git rebase -i HEAD~4` instead of all this? – Michał Miszczyszyn Feb 01 '17 at 21:07
  • @MichałMiszczyszyn: `git rebase -i` is not exactly safe, you can easily get to state as in http://stackoverflow.com/questions/7800874. My method is much more safe – mvp Feb 02 '17 at 00:29
  • @mvp `git reset --hard HEAD~4` is not exactly safe if `format-patch` fails for some reason, smh. `rebase` is much safer. – Michał Miszczyszyn Feb 02 '17 at 21:28
  • @MichałMiszczyszyn: I've never seen `git format-patch` to fail. Even if it did, it does no harm. Failed `git rebase` is harmful. – mvp Feb 02 '17 at 22:42
  • I didn't say `git format-patch` is harmful, but certainly `git reset --hard HEAD~4` is. Failed `git rebase` isn't harmful at all. – Michał Miszczyszyn Feb 04 '17 at 20:38
  • @michal: to each their own. I respectfully disagree – mvp Feb 05 '17 at 01:53
9

Although the accepted answer is correct, it lacks detailed instructions on how to perform editing a commit during a rebase process.

  • First, start a rebase process:

    git rebase --interactive HEAD~4
    
  • A list of commits will be presented, choose a commit you want to edit by changing the word pick to edit and save the file.

  • Make necessary modifications in your code (remember to invoke git add for new files)

  • After all modification are done, issue git commit --amend - this will amend a commit marked as edit

  • Invoke git rebase --continue that will finish the process (if there are more commits marked as edit, the above steps need to be repeated)

Important notes:

  • DO NOT remove lines marked as pick that you don't want to edit - leave them as is. Deleting these lines will result in deleting related commits

  • GIT forces you to stash before rebasing if your working directory is not clean; you can however git stash pop / git stash apply during rebase, in order to amend these changes (i.e. changes stashed before starting the rebase process) to a commit marked as edit

  • if something went wrong and you want to revert changes made during the rebase process before it finished (i.e. you want to revert to the point before starting the rebase), use git rebase --abort - also read: How to abort an interactive rebase if --abort doesn't work?

  • As said in the accepted answer:

    Remember that you should not modify commits pushed to the remote repository this way. It's better to add a new commit with missing file in that case.

    The answer why, is in the Git Book (paragraph entitled "The Perils of Rebasing"):

    Do not rebase commits that exist outside your repository.

    If you follow that guideline, you’ll be fine. If you don’t, people will hate you, and you’ll be scorned by friends and family.

    When you rebase stuff, you’re abandoning existing commits and creating new ones that are similar but different. If you push commits somewhere and others pull them down and base work on them, and then you rewrite those commits with git rebase and push them up again, your collaborators will have to re-merge their work and things will get messy when you try to pull their work back into yours.

    [...]

melomania
  • 3
  • 4
dominik
  • 2,404
  • 2
  • 29
  • 33
0

Here's a non-interactive rebase approach.

This requires an extra commit (so does not "rewrite a previous commit" as asked by OP), but I find it easier to remember.

Initial state:

* e834111 (HEAD -> master) do something that depends on file x
* 6dde62a do stuff, forget to add file x
...

Step 1 - checkout a new temporary branch at the commit that should have included the file (git checkout -b temp 6dde):

* e834111 (master) do something that depends on file x
* 6dde62a (HEAD -> temp) do stuff, forget to add file x
...

Step 2 - add the missing file and commit:

* 50d1412 (HEAD -> temp) add file x
| * e834111 (master) do something that depends on file x
|/  
* 6dde62a do stuff, forget to add file x
...

Step 3 - checkout the original branch and rebase onto temp:

* dd6f2dd (HEAD -> master) do something that depends on file x
* 50d1412 (temp) add file x
* 6dde62a do stuff, forget to add file x
...

Step 4 - remove the temporary branch (git branch -d temp)

Note: This can also be done without creating a temporary branch, using e.g. git checkout 6dde followed by a commit and rebase onto the detached head.

djvg
  • 11,722
  • 5
  • 72
  • 103