737

I'm not clear on how git revert works. For example, I want to revert to a commit six commits behind the head, reverting all the changes in the intermediary commits in between.

Say its SHA hash is 56e05fced214c44a37759efa2dfc25a65d8ae98d. Then why can't I just do something like:

git revert 56e05fced214c44a37759efa2dfc25a65d8ae98d
Penny Liu
  • 15,447
  • 5
  • 79
  • 98
JP Silvashy
  • 46,977
  • 48
  • 149
  • 227
  • 1
    Even though this question is actually older than the one it's now marked as a duplicate of, that one has a better answer. http://meta.stackexchange.com/questions/147643/should-i-vote-to-close-a-duplicate-question-even-though-its-much-newer-and-ha/147651#147651 – Ganesh Sittampalam Jun 28 '14 at 19:39
  • 19
    This question and the top answer here may confuse git users. Just to help understand the terminology, you don't *revert to* a commit. You can either *reset to* a commit (which is like going back in time using time machine) or *revert* a commit (which is like pulling out a commit as if it never existed - however it does preserve the revert info in history, allowing you to revert a revert if you wanted to) Note also that you shouldn't use the m flag and type a commit message if you get conflicts in the process. The auto message git provides is more informative when looking back in history. – alexrogers Mar 12 '15 at 12:30
  • 1
    @alexrogins what does pulling out a commit as if it never existed mean? Not sure what 'revert a revert' refers to either - appreciate the comment though, good info, just looking for more detail on your perspective. – Joe Jan 23 '18 at 17:15
  • 2
    @Joe as in if you add a line of code then commit that line, if you were to revert it you would be undoing that line of code (wherever it was first written in history, doesn't have to be the last commit). That then makes a revert commit. If you revert that revert commit then you're essentially undoing the undo (i.e. redoing the original line again) – alexrogers Jan 24 '18 at 21:59

9 Answers9

1337

If you want to commit on top of the current HEAD with the exact state at a different commit, undoing all the intermediate commits, then you can use reset to create the correct state of the index to make the commit.

# Reset the index and working tree to the desired tree
# Ensure you have no uncommitted changes that you want to keep
git reset --hard 56e05fced

# Move the branch pointer back to the previous HEAD
git reset --soft "HEAD@{1}"

git commit -m "Revert to 56e05fced"
metal
  • 6,202
  • 1
  • 34
  • 49
CB Bailey
  • 755,051
  • 104
  • 632
  • 656
  • 90
    Wouldn't it be equivalent (and one command shorter) to do: `git reset --hard 56e05fced` as the first command, and then skip the final `git reset --hard`? – Mark Longair Mar 02 '12 at 08:20
  • 27
    When I did this I ended up with a bunch of `Untracked Files` in the working tree. However looking at the history I could see that those files did have a corresponding delete commit in that "Revert to SHA" commit. So after `git reset --hard` at the end, you can do `git clean -f -d` to clean up any untracked files that lingered about. Also, thank you so much this helped me solve a crisis! – nzifnab Apr 27 '12 at 19:33
  • 5
    do I have to do the `git reset --soft HEAD@{1}` unconditionally? I mean always with a value of 1? – deprecated Sep 17 '13 at 09:23
  • 7
    @vemv Yes, unless you want to throw away commits on the tip of the branch. `git reset 56e05fced` adds another entry to the reflog (run `git reflog`), so `git reset --soft HEAD@{1}` simply moves the pointer back to the `HEAD` prior to calling `git reset 56e05fced`. Using a higher number (e.g. `git reset --soft HEAD@{2}`) would append the new commit on a *previous* commit. That is, increasing the number would essentially throw away `N-1` commits where `N` is the number you replace `1` with. – 0b10011 Sep 18 '13 at 18:37
  • git reset --soft HEAD@{1} seemed to be an invalid command. My branches have now diverged. This screwed me up badly. – Tom Jun 02 '14 at 20:27
  • 3
    @Tom **this solutions works**. You must have done something wrong, or your reflog might not have any entries in it (if it's a very new clone, for example). Regardless, you'll find more intuitive solutions for "reverting" a branch in [this answer](http://stackoverflow.com/a/4114122/456814). –  Jun 29 '14 at 00:29
  • Oh god, git-revert give me the wrong path!! This work!!! I just want EXACTLY (any code, any binary) should same as my previosu commit. – Yeung Oct 30 '14 at 09:05
  • as @nzifnab said, you need to do `git clean -f -d` to clean up any untracked files, otherwise the process is not fully completed. please update your answer – Mau Feb 24 '15 at 04:23
  • @Cupcake please could you change the commit message to say reset to just to avoid confusion with the functionality of git revert which I have described in a comment on the question – alexrogers Mar 12 '15 at 12:39
  • 5
    @Tom `HEAD@{1}` should be quoted as `'HEAD{@1}'` otherwise, it won't work for me (possibly every zsh users) – jilen Apr 01 '15 at 01:56
  • I found this better http://stackoverflow.com/questions/4114095/revert-git-repo-to-a-previous-commit , checkout the commit hash , and then create a new branch from there and work on that branch; so that nothing is missed – Alex Punnen Dec 21 '15 at 08:10
  • Have updated so that `clean` should no longer be needed. – CB Bailey May 17 '16 at 16:52
  • He asked about git **revert**. Your answer says to use git **reset**. "If git revert is a “safe” way to undo changes, you can think of git reset as the dangerous method." [Attalasian Git Tutorial](https://www.atlassian.com/git/tutorials/undoing-changes/git-reset) – ttulinsky Oct 19 '18 at 20:38
  • careful with the `git reset --hard`. You should rather use `git checkout `. This way you disconnect from the branch. – eftshift0 Mar 19 '20 at 23:06
181

What git-revert does is create a commit which undoes changes made in a given commit, creating a commit which is reverse (well, reciprocal) of a given commit. Therefore

git revert <SHA-1>

should and does work.

If you want to rewind back to a specified commit, and you can do this because this part of history was not yet published, you need to use git-reset, not git-revert:

git reset --hard <SHA-1>

(Note that --hard would make you lose any non-committed changes in the working directory).

Additional Notes

By the way, perhaps it is not obvious, but everywhere where documentation says <commit> or <commit-ish> (or <object>), you can put an SHA-1 identifier (full or shortened) of commit.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jakub Narębski
  • 309,089
  • 65
  • 217
  • 230
  • 9
    **In the case that you're history has already been pushed to a remote before you did the hard reset**, you would need to force push the newly reset branch with `git push -f`, but **Be Warned** that this could possibly unintentionally delete other users' commits, and if not delete new commits, then it will force other users to resynchronize their work with the reset branch, **so make sure this is OK with your collaborators first.** –  Jun 28 '14 at 17:25
  • 4
    This seems to be the best answer. It also tells clearly the difference between git revert and git reset. – kta May 14 '18 at 03:14
90

The best way to rollback to a specific commit is:

git reset --hard <commit-id>

Then:

git push <reponame> -f
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
darshit khatri
  • 941
  • 6
  • 2
  • 43
    Novices should be aware that `push -f` can destroy history. However, sometimes this is what you want :) – Jared Beck Feb 26 '13 at 00:30
  • 1
    Sometimes you are really glad that the history is deleted...was looking for this -f option, thks ! – Antoine Mar 26 '13 at 04:54
  • Thanks, to be literal, I had to type in -> git push origin master -f where can't just be origin at least for me – SWoo Aug 15 '13 at 03:10
  • As people have mentioned above, If we want our repo head pointing to a specific commit without maintaining history then use above steps other wise we can use git revert. – minhas23 May 26 '14 at 07:48
  • Is there anyway that we know who had reset (i.e rollback commit) and forced push on a specific branch? – datnt Mar 27 '15 at 02:21
90

It reverts the said commit, that is, adds the commit opposite to it. If you want to checkout an earlier revision, you do:

git checkout 56e05fced214c44a37759efa2dfc25a65d8ae98d
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Michael Krelin - hacker
  • 138,757
  • 24
  • 193
  • 173
  • 1
    then I can just merge this with the head? What if I anticipate having TONS of conflicts, can I just force this commit to be the head "as-is" and just overwrite any conflicts? – JP Silvashy Dec 12 '09 at 23:43
  • 1
    I'm not sure what head you're talking about. You can just *move* your head back to this commit. (for instance by deleting and creating branch). If you want to do a "merge" commit into the head, which is effectively the reversal of the intermediate commits, you can use merge with "ours" strategy. Pick your option and read manpages. The power is waiting for you to use it ;-) – Michael Krelin - hacker Dec 12 '09 at 23:48
  • That makes sense, the reason I ask is that git now tells me that I'm not on any branch. – JP Silvashy Dec 12 '09 at 23:51
  • 9
    because you aren't. if you type `git branch` you will clearly see it. You can do for instance `git checkout -b mybranch 56e05` to get it with branch. – Michael Krelin - hacker Dec 13 '09 at 00:06
78

Updated:

If there were no merge commits in between, this answer provides a is simpler method: https://stackoverflow.com/a/21718540/541862

But if there was one or more merge commits, that answer won't work, so stick to this one (that works in all cases).

Original answer:

# Create a backup of master branch
git branch backup_master

# Point master to '56e05fce' and
# make working directory the same with '56e05fce'
git reset --hard 56e05fce

# Point master back to 'backup_master' and
# leave working directory the same with '56e05fce'.
git reset --soft backup_master

# Now working directory is the same '56e05fce' and
# master points to the original revision. Then we create a commit.
git commit -a -m "Revert to 56e05fce"

# Delete unused branch
git branch -d backup_master

The two commands git reset --hard and git reset --soft are magic here. The first one changes the working directory, but it also changes head (the current branch) too. We fix the head by the second one.

Pedro A
  • 3,989
  • 3
  • 32
  • 56
Jacob Dam
  • 2,278
  • 18
  • 18
  • 3
    The -a in your commit isn't necessary. – splicer Jun 27 '13 at 06:21
  • 5
    Perfect. This should become one single command in the git cli, IMO. – Gabriel Queiroz Silva Dec 18 '14 at 12:24
  • 3
    very nice! this is much better than `git revert 56e05fce..HEAD` because it's just one commit – knocte Jun 30 '15 at 14:03
  • 2
    mmm, I take that back, this is simpler: http://stackoverflow.com/questions/4114095/revert-to-a-previous-git-commit#answer-21718540 – knocte Jun 30 '15 at 14:09
  • 5
    @knocte actually no, the link you gave is not simpler, even though it has thousands of upvotes. The reason is that it simply doesn't work if there is one or more merge commits in the range, which often happens. This one should be indeed the top answer. – Pedro A Dec 02 '21 at 03:32
  • Thank you so so much. git revert with merges was killing me when I just want the content of a previous commit – Mitchell Brooks Mar 04 '22 at 20:27
  • Note: In the presence of git submodules, it is neccessary to `git submodule update --init --recursive` after `git reset --hard 56e05fce`. – dpc.pw Jul 01 '22 at 02:00
  • I made it into a git alias: https://dpc.pw/git-alias-to-make-a-one-commit-to-revert-to-previous-commit – dpc.pw Jul 01 '22 at 03:02
  • Worked! Except now my CLI says the branch is (dev | REVERTING). @jacob-dam, what is the matter now? – 208_man Aug 19 '22 at 22:50
  • I don't really get why the two reset commands. Surely you want to reset and force push ( I know the force push is super destructive) but it's not clear to me what steps 3-5 actually do and why is different to hard reset and force push? – ortonomy Feb 15 '23 at 12:17
78

If your changes have already been pushed to a public, shared remote, and you want to revert all commits between HEAD and <sha-id>, then you can pass a commit range to git revert,

git revert 56e05f..HEAD

and it will revert all commits between 56e05f and HEAD (excluding the start point of the range, 56e05f).

Flueras Bogdan
  • 9,187
  • 8
  • 32
  • 30
  • 2
    Note that if you're reverting a few hundred commits, this could take a while because you have to commit each revert individually. – splicer Jun 27 '13 at 06:14
  • 9
    @splicer you don't have to revert each commit individually, you can either pass the `--no-edit` option to avoid having to make individual commit messages, or you can use `--no-commit` to commit the reversions all at once. –  Jun 28 '14 at 17:40
  • @Cupcake you are right **HEAD..56e05f** doesn't work for me but **56e05f..HEAD** did the trick – Inder Kumar Rathore Aug 19 '14 at 19:13
  • 3
    This is by far my preferred way of rolling back, no matter if you pushed it or not. I added this to my global `~/.gitconfig` under the aliases section: `rollback = "!git revert --no-commit $1..HEAD #"` - so now I can just intuitively do `$ git rollback a1s2d3` – DannyB Feb 08 '17 at 20:22
  • 3
    This seems super close to what I want, but I had about 30 commits to revert, but about half way through it fails on a merge commit with `error: Commit 6b3d9b3e05a9cd9fc1dbbebdd170bf083de02519 is a merge but no -m option was given. fatal: revert failed` - any suggestions? I tried adding -m but wasn't quite sure how that would work with this – Brad Parks Jan 28 '20 at 13:03
5

This is more understandable:

git checkout 56e05fced -- .
git add .
git commit -m 'Revert to 56e05fced'

And to prove that it worked:

git diff 56e05fced
Tuyen Tran
  • 107
  • 1
  • 1
  • 6
    This isn't correct in general, I'm afraid. The checkout will only (I think) update paths that exist, so if a file has been deleted since `56e05fced`, it won't be staged by doing `git checkout 56e05fced -- .` – Mark Longair Mar 02 '12 at 08:11
  • 1
    **This solution won't delete new files that have been added since** `56e05fced `, like a `git reset --hard` or a `git revert` would. **You really want to use those commands if you actually want to restore the state of** `56e05fced`, not `git checkout`. –  Jun 28 '14 at 17:22
  • Note: This will put you in a detached head state. Not advisable! – alexrogers Mar 12 '15 at 12:41
  • 1
    This worked for me, but what does "--" mean in this context? –  Apr 16 '15 at 16:13
  • In Bash like commands, double-dashes means the end of command options, so any value added after double-dashes wont be interpreted as an option. In this context the dot (.) wont be interpreted as an option, instead it will act as the of checkout command. – snaphuman Jun 06 '22 at 14:11
3

Should be as simple as:

git reset --hard 56e05f

That'll get you back to that specific point in time.

longda
  • 10,153
  • 7
  • 46
  • 66
  • 4
    ...and is also very dangerous as will wipe all the history since including other peoples work. **Beware of this one!** – alexrogers Mar 12 '15 at 12:42
-2

This might work:

git checkout 56e05f
echo ref: refs/heads/master > .git/HEAD
git commit
Jake
  • 2,106
  • 1
  • 24
  • 23
  • 3
    This basically does the same thing as `git reset --hard 56e05f`, except this is less safe and more hacky. You might as well use [Charle's solution](http://stackoverflow.com/a/1895095/456814) or [Jakub's solution](http://stackoverflow.com/a/1896051/456814). –  Jun 28 '14 at 17:14