How to revert multiple git commits?

829,487

Solution 1

Expanding what I wrote in a comment

The general rule is that you should not rewrite (change) history that you have published, because somebody might have based their work on it. If you rewrite (change) history, you would make problems with merging their changes and with updating for them.

So the solution is to create a new commit which reverts changes that you want to get rid of. You can do this using git revert command.

You have the following situation:

A <-- B  <-- C <-- D                                  <-- master <-- HEAD

(arrows here refers to the direction of the pointer: the "parent" reference in the case of commits, the top commit in the case of branch head (branch ref), and the name of branch in the case of HEAD reference).

What you need to create is the following:

A <-- B  <-- C <-- D <-- [(BCD)-1]                   <-- master <-- HEAD

where [(BCD)^-1] means the commit that reverts changes in commits B, C, D. Mathematics tells us that (BCD)-1 = D-1 C-1 B-1, so you can get the required situation using the following commands:

$ git revert --no-commit D
$ git revert --no-commit C
$ git revert --no-commit B
$ git commit -m "the commit message for all of them"

Works for everything except merge commits.


Alternate solution would be to checkout contents of commit A, and commit this state. Also works with merge commits. Added files will not be deleted, however. If you have any local changes git stash them first:

$ git checkout -f A -- . # checkout that revision over the top of local files
$ git commit -a

Then you would have the following situation:

A <-- B  <-- C <-- D <-- A'                       <-- master <-- HEAD

The commit A' has the same contents as commit A, but is a different commit (commit message, parents, commit date).


Alternate solution by Jeff Ferland, modified by Charles Bailey builds upon the same idea, but uses git reset. Here it is slightly modified, this way WORKS FOR EVERYTHING:

$ git reset --hard A
$ git reset --soft D # (or ORIG_HEAD or @{1} [previous location of HEAD]), all of which are D
$ git commit

Solution 2

Clean way which I found useful

git revert --no-commit HEAD~3..
git commit -m "your message regarding reverting the multiple commits"

This command reverts last 3 commits with only one commit.

Also doesn't rewrite history, so doesn't require a force push.

The .. helps create a range. Meaning HEAD~3.. is the same as HEAD~3..HEAD

Solution 3

For doing so you just have to use the revert command, specifying the range of commits you want to get reverted.

Taking into account your example, you'd have to do this (assuming you're on branch 'master'):

git revert master~3..master

or git revert B...D or git revert D C B

This will create a new commit in your local with the inverse commit of B, C and D (meaning that it will undo changes introduced by these commits):

A <- B <- C <- D <- BCD' <- HEAD

Solution 4

git reset --hard a
git reset --mixed d
git commit

That will act as a revert for all of them at once. Give a good commit message.

Solution 5

Similar to Jakub's answer, this allows you to easily select consecutive commits to revert.

# Revert all commits from and including B to HEAD, inclusively
git revert --no-commit B^..HEAD
git commit -m 'message'
Share:
829,487
Bill
Author by

Bill

Updated on March 16, 2022

Comments

  • Bill
    Bill over 2 years

    I have a git repository that looks like this:

    A <- B <- C <- D <- HEAD
    

    I want the head of the branch to point to A, i.e. I want B, C, D, and HEAD to disappear and I want head to be synonymous with A.

    It sounds like I can either try to rebase (doesn't apply, since I've pushed changes in between), or revert. But how do I revert multiple commits? Do I revert one at a time? Is the order important?

    • Anonigan
      Anonigan almost 15 years
      If people have pulled you have to make a commit that reverts changes using git revert.
    • Mâtt Frëëman
      Mâtt Frëëman about 8 years
      Use git show HEAD~4 to ensure you are pushing to right one to the remote
    • Jim Fell
      Jim Fell over 7 years
      Possible duplicate of How to undo last commit(s) in Git?
    • avandeursen
      avandeursen about 7 years
      "Is the order important?" Yes, if the commits affect the same lines in the same files. Then you should start reverting the most recent commit, and work your way back.
    • WestCoastProjects
      WestCoastProjects over 2 years
      Please note that git revert will leave the intervening work in history. Use git checkout with git reset --hard to nuke the history as well: see answer stackoverflow.com/a/38317763/1056563
  • CB Bailey
    CB Bailey almost 15 years
    If he wants HEAD to look like A then he probably want the index to match so git reset --soft D is probably more appropriate.
  • Jeff Ferland
    Jeff Ferland almost 15 years
    --soft resetting doesn't move the index, so when he commits, it would look like the commit came directly from a instead of from D. That would make the branch split. --mixed leaves the changes, but moves the index pointer, so D will become the parent commit.
  • Jeff Ferland
    Jeff Ferland over 13 years
    Yes, I think git reset --keep is exactly what I have above. It came out in version 1.7.1, released in April of 2010, so the answer wasn't around at that time.
  • oma
    oma about 13 years
    If you added files in B, C or D. the git checkout -f A -- . Will not delete these, you will have to do it manually. I applied this strategy now, thanks Jakub
  • m33lky
    m33lky over 12 years
    Those solutions are not equivalent. The first one doesn't delete newly created files.
  • Ben Hamill
    Ben Hamill almost 12 years
    FWIW, if you need to delete those new files, git clean is your friend.
  • chriz
    chriz almost 12 years
    hi, can you explain all the flags and switches do here? git checkout -f A -- . --> what does '--' and '.' mean?
  • Anonigan
    Anonigan almost 12 years
    @mastah: the -- might be needed to separate branch name from pathspec. The . means "current directory", and because paths are relative to top irectory of a project here, it means to check out the whole project, all files of a project.
  • Ajay Choudhary
    Ajay Choudhary over 11 years
    git revert --no-commit HEAD~2.. is a slightly more idiomatic way to do it. If you're on master branch, no need to specify master again. The --no-commit option lets git try to revert all the commits at once, instead of littering the history with multiple revert commit ... messages (assuming that's what you want).
  • Ji ZHANG
    Ji ZHANG over 11 years
    @JakubNarębski What does "might" mean? I too find this command very mysterious...
  • Anonigan
    Anonigan over 11 years
    @Jerry: git checkout foo might mean checkout branch foo (switch to branch) or checkout file foo (from index). -- is used to disambiguate, e.g. git checkout -- foo is always about file.
  • welldan97
    welldan97 almost 11 years
    In addition to great answer. This shorthand works for me git revert --no-commit D C B
  • Anonigan
    Anonigan almost 11 years
    @welldan97: Thanks for a comment. When writing this answer git revert didn't accept multiple commits; it is quite new addition.
  • SimplGy
    SimplGy almost 11 years
    git checkout A then git commit above did not work for me, but this answer did.
  • Robin Winslow
    Robin Winslow almost 11 years
    This answer explains all the options very well, but shouldn't we put the git reset --soft @{1} solution at the top? It's clearly the best solution to the problem.
  • Flatline
    Flatline over 10 years
    To clarify git checkout -f A -- . I think it means "check out the changes of commit A to the current directory without going into detached HEAD mode", basically, you stay in master, but bring the changes of commit A to the current place in time. At least that's how I interpret it, please, correct me if I'm wrong.
  • Anonigan
    Anonigan over 10 years
    @Flatline: git checkout -f A -- . means checkout the state of commit A to current directory, overwriting files... but it does not delete files to bring it into A state.
  • Admin
    Admin about 10 years
    @Victor I fixed your commit range. The beginning of the range is exclusive, meaning it's not included. So if you want to revert the last 3 commits, you need to start the range from the parent of the 3rd commit, i.e. master~3.
  • ffffranklin
    ffffranklin almost 9 years
    This is the most effective and responsible way to do rollbacks on git.
  • MordechayS
    MordechayS over 8 years
    @kubi is there no way of including the SHAs in a commit message, using a single commit (your method but without having to manually enter the commits that were reverted)?
  • The Red Pea
    The Red Pea over 8 years
    Why is git reset --mixed D required? Specifically why reset? Is it because, without resetting to D, that, HEAD would point at A, causing B, C, and D to be "dangling" and garbage-collected -- which is not what he wants? But then why --mixed? You already answered "--soft resetting doesn't move the index..." So by moving the index, this means index will contain D's changes, while Working Directory will contain A's changes -- this way a git status or git diff (which compares Index [D] to Working Directory [A]) will show the substance; that user is going from D back to A?
  • Bogdan
    Bogdan over 8 years
    Your solution worked fine for me, but with a slight modification. If we have this case Z -> A -> B -> C -> D -> HEAD and if I would want to return to the A state, then weirdly I would have to execute git revert --no-commit Z..HEAD
  • Vadym Tyemirov
    Vadym Tyemirov about 8 years
    Agree with @Bogdan, the revert range is like that: SHA_TO_REVERT_TO..HEAD
  • Peeter Kokk
    Peeter Kokk about 8 years
    Only do rebase locally! As we’ve discussed with git commit --amend and git reset, you should never rebase commits that have been pushed to a public repository. The rebase would replace the old commits with new ones, and it would look like that part of your project history abruptly vanished. link
  • Mars
    Mars over 7 years
    git checkout -f A -- . didn't work well for me. When I tried to push it, I couldn't without merging the remote version, which isn't what I wanted. I'm trying to undo the last few commits, so I don't want the remote version merged in.
  • Suamere
    Suamere over 7 years
    Are you sure you can git HEAD with just the tip of the branch?
  • Radon Rosborough
    Radon Rosborough over 7 years
    @ChrisS My first thought would be to not use --no-commit (so you get a separate commit for each revert), and then squash all of them together in an interactive rebase. The combined commit message will contain all of the SHAs, and you can arrange them however you like using your favorite commit message editor.
  • sovemp
    sovemp over 7 years
    This was a much better solution for me, since in mine I had merge commits.
  • Tom Barron
    Tom Barron over 7 years
    I hadn't published the commits, so this was the answer I needed. Thanks, @Suamere.
  • 100r
    100r over 7 years
    Just want to add one thing that helped us. Do not ever commit between reverts. Key part is --no-commit. If you do commits between reverts, at the end you can end up with having strange compares between different commits.
  • entpnerd
    entpnerd about 7 years
    The third solution was what worked for my complicated case involving several commits and merges and added and deleted files.
  • GabLeRoux
    GabLeRoux almost 7 years
    Won't work with binary files: error: cannot apply binary patch to 'some/image.png' without full index line error: some/image.png: patch does not apply
  • edencorbin
    edencorbin almost 7 years
    I went with git checkout -f A -- . as I had 8 commits and liked the 1 step idea. This mostly worked, however I would note that new files were not deleted, just changed files undone, I compared remote head to my cherry picked branch after running this command and pushing and saw only new files, after deleting these, and then committing/pushing, the branches were identical.
  • Toolkit
    Toolkit almost 7 years
    $ git revert --no-commit D fatal: bad revision 'D'
  • Brian Kung
    Brian Kung over 6 years
    This is a much more flexible solution than the accepted answer. Thanks!
  • John Little
    John Little over 6 years
    how can this work if there is no commit? What needs to be done in addition to the above command? what git commands are needed before/after this command?
  • x1a4
    x1a4 over 6 years
    @JohnLittle it stages the changes. git commit from there will actually do the commit.
  • 尤川豪
    尤川豪 over 6 years
    you are my hero you save my day you help me a lot this is a great answer your solution is great i want to say thank you you are so professional <3 <3 <3 <3 <3 <3 <3
  • frandroid
    frandroid over 6 years
    The better way to do this is git push --force-with-lease, which will rewrite history only if no one else has committed to the branch after or within the range of the commits to vapourize. If other people have used the branch, then its history should never be rewritten, and the commit should simply be visibly reverted.
  • Daniel says Reinstate Monica
    Daniel says Reinstate Monica over 6 years
    I've never thought of commits as matrices before. Amazing.
  • weinerk
    weinerk over 6 years
    re: binary files use --binary option: git diff --binary HEAD commit_sha_you_want_to_revert_to | git apply
  • kboom
    kboom over 6 years
    This is a viable option only if your changes aren't pushed yet.
  • Suamere
    Suamere about 6 years
    @frandroid "history should never be rewritten", only the sith deal in absolutes. The question of this thread, and the point of my answer, is exactly that for a particular scenario, all history should be wiped.
  • tessus
    tessus about 6 years
    The range is wrong. It should be B^..HEAD, otherwise B is excluded.
  • frandroid
    frandroid about 6 years
    @Suamere Sure, that's the question. But as your answer mentions about what you had to tell the other guys, there is potential for trouble. From personal experience, push -f can mess up your code-base if other people have committed after what you're trying to erase. --force-with-lease achieves the same result, except that it saves your ass if you were about to mess up your repo. Why take the chance? If --force-with-lease fails, you can see which commit gets in the way, assess properly, adjust, and try again.
  • Suamere
    Suamere about 6 years
    @frandroid Ah! You were saying that, instead of the third line git push -f to instead git push --force-with-lease Seems obvious, but that's not how your comment read. It moreso seemed like it was your one-line, nothing-else-required, exact solution. I agree that altering the last line of my answer would offer that extra protection (and extra work). I just personally wouldn't do it unless there were people who I did not control working on the code. So your point is good for anything open-source or multi-departmental for sure. Though I'm no Git Savant, and don't even know that command, lol.
  • Juliusz Gonera
    Juliusz Gonera almost 6 years
    This will work even if you want to revert a range of commits that contains merge commits. When using git revert A..Z you'd get error: commit X is a merge but no -m option was given.
  • MegaManX
    MegaManX almost 6 years
    This will not work if some commits are merge commits.
  • Yoho
    Yoho over 5 years
    Agree with @tessus, so the right thing to do would be: git revert --no-commit B^..HEAD or git revert --no-commit A..HEAD
  • cardamom
    cardamom over 5 years
    What do the two dots at the end do?
  • Toine H
    Toine H over 5 years
    @cardamom Those specify a range. HEAD~3.. is the same as HEAD~3..HEAD
  • T to the J
    T to the J over 5 years
    git revert fails on merge commits, however checking out the contents of the earlier hash and committing those is brilliant, thanks!
  • nulll
    nulll over 5 years
    Can you please explain the reason of downvoting?
  • jww
    jww about 5 years
    More broken Git shit... As soon as the first revert is performed it results in error: your index file is unmerged. This tool is a broken joke.
  • Lee Richardson
    Lee Richardson about 5 years
    @Suamere Thank you! I agree the question clearly states it wants to rewrite history. I'm in the same situation as you and I'm guessing the OP in that someone made dozens of ugly reverts and odd commits and reverts of reverts by accident (while I was on vacation) and the state needs to be reverted. In any event along with a good healthy warning this should be the accepted answer.
  • Levent Divilioglu
    Levent Divilioglu almost 5 years
    This works fine. What is the point of giving downvote for this useful answer or can anybody explain what is the best-practice?
  • M.M
    M.M over 4 years
    Is that the same as git checkout master; git reset --hard A ? Or if not could you explain a bit more about what this does?
  • Somebody
    Somebody over 4 years
    Wow cool, just what i was looking for. So it's reverse diff and then applying these changes over existing code. Very clever, thank you. ;)
  • Daniel R Carletti
    Daniel R Carletti over 4 years
    git revert also does not accept merge commits. I used the git checkout -f A -- . and it worked great.
  • Anonigan
    Anonigan over 4 years
    @DanielRCarletti : in the case of merge commit git revert needs to know which change (against which parent) you want to revert, with -m option. But in this case other solution might be better.
  • BradStell
    BradStell about 4 years
    Thanks @Suamere this is also the answer I needed, as I had not pushed to the remote yet. I had a large file in a commit I had later ignored and I had to just get rid of the first commit because I was stuck not being able to push. I literally needed the commit to just go away. This did the trick :).
  • Prasanna Mondkar
    Prasanna Mondkar almost 4 years
    exactly what i was looking for. Great explanation
  • Kyle
    Kyle almost 4 years
    Another thing to be careful of with git checkout -f A -- . is that it will discard any un-staged changes you have. Do a git stash first if you want to save those.
  • rogerdpack
    rogerdpack over 3 years
    Sure enough, if you do this without --no-commit it does them one at a time and you have to keep typing git revert --continue over and over for each one...dang I was hoping git would have a friendly commit option like "do them all and list all the hashes for me in a single commit" but appears not :|
  • rogerdpack
    rogerdpack over 3 years
    --mixed doesn't work for the case of a revert of a commit that removed a file, but --soft works well...
  • confirmator
    confirmator over 3 years
    This helped me out with reverting a whole batch of commits which were already pushed to the remote - great solution!
  • ScottyBlades
    ScottyBlades over 3 years
    is a merge but no -m option was given. fatal: revert failed
  • Redar
    Redar about 3 years
    Using this command you don't have to worry about merge commits. Very useful
  • Nathaniel Jones
    Nathaniel Jones almost 3 years
    @JiZHANG The -- would be needed in the case that your file name begins with one or more hyphens. Jakub was being thorough.
  • Basit Anwer
    Basit Anwer over 2 years
    This is better than all the --hard commands, If you're reverting a commit then it should be logged in history.
  • rick
    rick over 2 years
    git checkout -f A -- . works well for me, while running this command, make sure you're on the correct branch as i tried attempted different method and was detached from the branch i wanna run
  • Elad Nava
    Elad Nava over 2 years
    Excellent answer, thanks so much, works like a charm!
  • P D
    P D over 2 years
    Just to add: `<first-commit-sha>^..<last-commit-sha>`` is inclusive range. Besides, excellent answer. It worked for me in Git Bash.
  • Simon Tewsi
    Simon Tewsi over 2 years
    git reset --hard a then git reset --soft d worked perfectly for me. It seems a neat and simple solution, much better than reverting commits b, c, and d separately. A two-step process no matter how many commits you want to revert.
  • Matt Welke
    Matt Welke over 2 years
    This worked well for me, except I had to also do a git add before the git commit because I was left with unstaged changes after git reset --mixed d.
  • Liyong Zhou
    Liyong Zhou over 2 years
    The restore method worked clean and simple for me.
  • WestCoastProjects
    WestCoastProjects over 2 years
    This is where I needed to be - since history needs to go bye-bye along with the changes . Clean, correct, decisive, concise. And done.
  • mcarans
    mcarans about 2 years
    Nice! This is simpler than my long handed approach which was to clone the repository again, check out the specific good commit in MyRepo2, then use a file compare tool to see what files changed between MyRepo and MyRepo2, then copy the changed files from MyRepo2 to MyRepo.