1580

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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Bill
  • 44,502
  • 24
  • 122
  • 213
  • 5
    If you just want to reset the remote, you can clobber it with anything! But let us use the fourth commit ago: `git push -f HEAD~4:master` (assuming the remote branch is master). Yes, you can push any commit like that. – u0b34a0f6ae Sep 23 '09 at 01:01
  • 37
    If people have pulled you have to make a commit that reverts changes using `git revert`. – Jakub Narębski Sep 23 '09 at 08:21
  • 3
    Use git show HEAD~4 to ensure you are pushing to right one to the remote – Mâtt Frëëman Jun 11 '16 at 03:08
  • 2
    Possible duplicate of [How to undo last commit(s) in Git?](http://stackoverflow.com/questions/927358/how-to-undo-last-commits-in-git) – Jim Fell Feb 14 '17 at 18:53
  • 8
    "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. – avandeursen Apr 22 '17 at 11:26
  • 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 https://stackoverflow.com/a/38317763/1056563 – WestCoastProjects Mar 29 '22 at 00:00

18 Answers18

2017

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
rogerdpack
  • 62,887
  • 36
  • 269
  • 388
Jakub Narębski
  • 309,089
  • 65
  • 217
  • 230
  • 54
    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 – oma Mar 31 '11 at 14:56
  • 21
    Those solutions are not equivalent. The first one doesn't delete newly created files. – m33lky Jan 26 '12 at 05:57
  • 3
    FWIW, if you need to delete those new files, `git clean` is your friend. – Ben Hamill Jul 03 '12 at 15:41
  • 5
    hi, can you explain all the flags and switches do here? git checkout -f A -- . --> what does '--' and '.' mean? – chriz Jul 19 '12 at 20:36
  • 1
    @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. – Jakub Narębski Jul 20 '12 at 17:57
  • 2
    @JakubNarębski What does "might" mean? I too find this command very mysterious... – Jerry Jan 23 '13 at 02:19
  • 12
    @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. – Jakub Narębski Jan 23 '13 at 02:34
  • 152
    In addition to great answer. This shorthand works for me `git revert --no-commit D C B` – welldan97 Aug 09 '13 at 12:34
  • 11
    @welldan97: Thanks for a comment. When writing this answer `git revert` didn't accept multiple commits; it is quite new addition. – Jakub Narębski Aug 09 '13 at 18:39
  • 4
    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. – Robin Winslow Sep 20 '13 at 09:24
  • 2
    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. – Flatline Oct 04 '13 at 12:54
  • 3
    @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. – Jakub Narębski Oct 04 '13 at 19:14
  • 1
    This is the most effective and responsible way to do rollbacks on git. – ffffranklin Sep 23 '15 at 20:11
  • 1
    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](https://www.atlassian.com/git/tutorials/rewriting-history) – Peeter Kokk May 16 '16 at 14:58
  • 1
    `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. – Mars Oct 30 '16 at 17:18
  • 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. – 100r Jan 26 '17 at 10:17
  • 1
    The third solution was what worked for my complicated case involving several commits and merges and added and deleted files. – entpnerd Jun 07 '17 at 14:59
  • 2
    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. – edencorbin Sep 03 '17 at 12:40
  • `$ git revert --no-commit D` `fatal: bad revision 'D'` – Toolkit Sep 08 '17 at 09:51
  • 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 – 尤川豪 Nov 23 '17 at 08:19
  • git revert fails on merge commits, however checking out the contents of the earlier hash and committing those is brilliant, thanks! – T to the J Jan 16 '19 at 21:59
  • 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. – jww Jun 09 '19 at 05:46
  • `git revert` also does not accept merge commits. I used the `git checkout -f A -- .` and it worked great. – Daniel R Carletti Mar 23 '20 at 18:32
  • @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. – Jakub Narębski Mar 24 '20 at 18:08
  • 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. – Kyle Sep 25 '20 at 17:41
  • @JiZHANG The `--` would be needed in the case that [your file name begins with one or more hyphens](https://www.baeldung.com/linux/double-dash-in-shell-commands). Jakub was being thorough. – Nathaniel Jones Jul 21 '21 at 18:50
  • `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 – rick Nov 26 '21 at 02:32
  • 1
    > Mathematics tells us that (BCD)-1 = D-1 C-1 B-1. This is from which rule? – foxiris Oct 05 '22 at 04:26
  • You could also just revert each individually and then do a `rebase -i HEAD~3` to squash the 2 commits together (so you can even keep the generated revert commit messages). – mrbrdo Nov 20 '22 at 20:10
  • I pursued second option `$ git checkout -f A -- .` followed by `$ git commit -a` since I had merged commits. Finally I had to forcefully push it to git to see the changes. I used `git push -f origin master`. – kkgarg Dec 02 '22 at 18:24
  • 1
    @foxiris This rule applies for linear transformations a.k.a matrices. Pretty sure he was making a joke with this one. – domdrag Jun 08 '23 at 17:00
  • @foxiris : group theory tells us that $(BC)^{-1} = C^{-1} B^{-1}$; check how both sides act on $(BC)$: $(BC)^{-1} (BC) = 1$ by definition of $x^{-1}$, and $C^{-1}B^{-1}BC = C^{-1} 1 C = C^{-1}C = 1$, so both are equal. – Jakub Narębski Jun 11 '23 at 00:09
680

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

rogerdpack
  • 62,887
  • 36
  • 269
  • 388
deepdive
  • 9,720
  • 3
  • 30
  • 38
280

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
rogerdpack
  • 62,887
  • 36
  • 269
  • 388
Victor
  • 2,809
  • 1
  • 13
  • 2
  • 150
    `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). – kubi Jan 15 '13 at 18:49
  • 8
    @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`. –  Apr 25 '14 at 18:50
  • 3
    @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)? – Chris S Oct 01 '15 at 18:33
  • @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. – Resigned June 2023 Nov 15 '16 at 20:52
  • 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 Oct 12 '20 at 14:04
108
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.

Jeff Ferland
  • 17,832
  • 7
  • 46
  • 76
  • 11
    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. – CB Bailey Sep 23 '09 at 05:38
  • 2
    --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 Sep 25 '09 at 14:42
  • 5
    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. – Jeff Ferland Nov 11 '10 at 01:56
  • 1
    `git checkout A` then `git commit` above did not work for me, but this answer did. – SimplGy Aug 29 '13 at 03:38
  • 2
    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? – Nate Anderson Jan 10 '16 at 18:03
  • 1
    `--mixed` doesn't work for the case of a revert of a commit that removed a file, but `--soft` works well... – rogerdpack Oct 12 '20 at 16:48
  • 1
    `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. – Simon Elms Feb 21 '22 at 00:18
  • 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`. – Matt Welke Mar 15 '22 at 17:22
  • **Warning**: `reset` will **not** *revert* the commits, it will *discard* them (after they fall out of the reflog and a `git gc`). This includes a possible need to force push the result. Revert as in *stack the inverse commit on top*, is annihilating the *changes* but keeps both *commits*. – Holger Böhnke Feb 20 '23 at 17:30
103

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'
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
konyak
  • 10,818
  • 4
  • 59
  • 65
  • 12
    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 – Bogdan Feb 22 '16 at 16:42
  • 3
    Agree with @Bogdan, the revert range is like that: SHA_TO_REVERT_TO..HEAD – Vadym Tyemirov May 10 '16 at 16:44
  • 16
    The range is wrong. It should be `B^..HEAD`, otherwise B is excluded. – tessus Apr 28 '18 at 03:50
  • 6
    Agree with @tessus, so the right thing to do would be: `git revert --no-commit B^..HEAD` or `git revert --no-commit A..HEAD` – Yoho Sep 29 '18 at 00:09
85

I'm so frustrated that this question can't just be answered. Every other question is in relation to how to revert correctly and preserve history. This question says "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."

git checkout <branch_name>
git reset --hard <commit Hash for A>
git push -f

I learned a lot reading Jakub's post, but some guy in the company (with access to push to our "testing" branch without a Pull-Request) pushed like five bad commits, trying to fix and fix and fix a mistake he made five commits ago. Not only that, but one or two pull requests were accepted, which were now bad. So forget it; I found the last good commit (abc1234) and just ran the basic script:

git checkout testing
git reset --hard abc1234
git push -f

I told the other five guys working in this repository that they better make note of their changes for the last few hours and wipe/rebranch from the latest testing. End of the story.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Suamere
  • 5,691
  • 2
  • 44
  • 58
  • 6
    I hadn't published the commits, so this was the answer I needed. Thanks, @Suamere. – Tom Barron Nov 30 '16 at 17:37
  • 5
    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. – frandroid Dec 01 '17 at 16:59
  • 4
    @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. – Suamere Apr 24 '18 at 02:18
  • 1
    @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. – frandroid Apr 28 '18 at 15:02
  • @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. – Suamere May 01 '18 at 15:55
  • 2
    @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. – Lee Richardson Jun 19 '19 at 13:53
  • 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 :). – BradStell May 07 '20 at 20:29
  • 1
    *This* is where I needed to be - since history needs to go bye-bye along with the changes . Clean, correct, decisive, concise. And done. – WestCoastProjects Mar 28 '22 at 23:59
  • 1
    _This_ is the answer especially as the question _doesn't say that the commits in question have been pushed to any remote_. – davidbak Jul 27 '22 at 17:28
75

First be sure that your working copy is not modified.

Then:

git diff --binary HEAD commit_sha_you_want_to_revert_to | git apply

And then just commit. Don't forget to document what the reason is for the revert.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
mateusz.fiolka
  • 3,032
  • 2
  • 23
  • 24
  • 1
    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` – GabLeRoux Jul 10 '17 at 19:16
  • 2
    This is a much more flexible solution than the accepted answer. Thanks! – Brian Kung Sep 29 '17 at 13:57
  • 4
    re: binary files use --binary option: `git diff --binary HEAD commit_sha_you_want_to_revert_to | git apply` – weinerk Jan 03 '18 at 14:25
  • 2
    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.` – Juliusz Gonera Aug 22 '18 at 00:10
  • 2
    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. ;) – Somebody Feb 17 '20 at 12:20
  • This helped me out with reverting a whole batch of commits which were already pushed to the remote - great solution! – confirmator Nov 11 '20 at 16:14
  • 1
    Using this command you don't have to worry about merge commits. Very useful – Redar Jun 01 '21 at 14:55
  • 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. – mcarans Apr 19 '22 at 22:06
  • That's the best answer in cause you have merge commits in the list of commits you want to revert. – homiak Jul 13 '22 at 13:20
29

Using Git Restore

You can also use the restore command:

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

Let's say you want HEAD to look exactly like A. Make sure you've pulled the latest master. Then cut a new branch.

git switch -c feature/flux-capacitor  # synonymous with checkout -b
git restore --source A .
git add .
git commit
git push

The restore command changes everything (.) to what it was at the --source commit. Then you commit that to your local branch and push it to the origin. You can then open a PR against it.

This has the benefit of not changing any history that others might have based work on. It also leaves a useful history for folks in the future.

Docs: git restore

meh
  • 2,591
  • 4
  • 20
  • 33
  • 1
    The restore method worked clean and simple for me. – Liyong Zhou Mar 23 '22 at 17:39
  • 2
    This worked great for me because I didn't want to use Reset which deletes commits. After running those commands, I of course just switched to master and merged. Thanks. – Mauro Torres Feb 26 '23 at 17:31
  • But restore changes the PR branch and that is not expected. PR has source of comments or notes etc so the approach should work in the same branch. – vgdub Mar 20 '23 at 08:17
  • @vgdub, thanks for your comment. Git itself has no concept of a PR. PR is a construct of GitHub. With a bit of creativity you can use `restore` on your desired branch, just as you would with any of the other approaches mentioned in this list. – meh Apr 25 '23 at 11:59
20

This is an expansion of one of the solutions provided in Jakub's answer.

I was faced with a situation where the commits I needed to roll back were somewhat complex, with several of the commits being merge commits, and I needed to avoid rewriting history. I was not able to use a series of git revert commands because I eventually ran into conflicts between the reversion changes being added. I ended up using the following steps.

First, check out the contents of the target commit while leaving HEAD at the tip of the branch:

git checkout -f <target-commit> -- .

(The -- makes sure <target-commit> is interpreted as a commit rather than a file; the . refers to the current directory.)

Then, determine what files were added in the commits being rolled back, and thus need to be deleted:

git diff --name-status --cached <target-commit>

Files that were added should show up with an "A" at the beginning of the line, and there should be no other differences. Now, if any files need to be removed, stage these files for removal:

git rm <filespec>[ <filespec> ...]

Finally, commit the reversion:

git commit -m 'revert to <target-commit>'

If desired, make sure that we're back to the desired state:

git diff <target-commit> <current-commit>

There should be no differences.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Warren Dew
  • 8,790
  • 3
  • 30
  • 44
9

The easy way to revert a group of commits on shared repository (that people use and you want to preserve the history) is to use git revert in conjunction with git rev-list. The latter one will provide you with a list of commits, the former will do the revert itself.

There are two ways to do that. If you want the revert multiple commits in a single commit use:

for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert --no-commit $i; done

this will revert a group of commits you need, but leave all the changes on your working tree, you should commit them all as usual afterward.

Another option is to have a single commit per reverted change:

for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert --no-edit -s $i; done

For instance, if you have a commit tree like

 o---o---o---o---o---o--->    
fff eee ddd ccc bbb aaa

to revert the changes from eee to bbb, run

for i in `git rev-list eee^..bbb`; do git revert --no-edit -s $i; done
rogerdpack
  • 62,887
  • 36
  • 269
  • 388
Ruslan Kabalin
  • 6,580
  • 2
  • 28
  • 20
  • Just to add: `^..`` is **inclusive** range. Besides, excellent answer. It worked for me in Git Bash. – P D Feb 18 '22 at 11:34
  • this won't work most of the times: error: commit XXX is a merge but no -m option was given. fatal: revert failed. I'm starting to think the best general way to do this is `git checkout ` then `git archive` and then manually create a commit unzipping the archived version... – GACy20 Aug 22 '22 at 13:36
7

None of those worked for me, so I had three commits to revert (the last three commits), so I did:

git revert HEAD
git revert HEAD~2
git revert HEAD~4
git rebase -i HEAD~3 # pick, squash, squash

Worked like a charm :)

Dorian
  • 22,759
  • 8
  • 120
  • 116
6

In my opinion a very easy and clean way could be:

go back to A

git checkout -f A

point master's head to the current state

git symbolic-ref HEAD refs/heads/master

save

git commit
nulll
  • 1,465
  • 1
  • 17
  • 28
5

Probably less elegant than other approaches here, but I've always used get reset --hard HEAD~N to undo multiple commits, where N is the number of commits you want to go back.

Or, if unsure of the exact number of commits, just running git reset --hard HEAD^ (which goes back one commit) multiple times until you reach the desired state.

Ian
  • 296
  • 1
  • 3
  • 15
3

I found myself needing to revert a long range of commits and then re-revert them to help a team present a clear pull request without having to force-push over their target branch (which was committed directly to)

# checkout the branch that should be targeted
git checkout $branch_target

# revert the commits in $branch_target to some $count where
#   $count is the number of commits to revert
#   cut is used to slice just the commit hash field from each line of output
#   xargs runs the command once for each line of input, reversing the commits!
git log --oneline -n $count | cut -d' ' -f1 | xargs git revert

# check out the branch which should be the source of the pull request
git checkout -b $branch_for_pull

# revert the revert commits
# $count is that same number of commits being reverted (again)
git log --oneline -n $count | cut -d' ' -f1 | xargs git revert

# push branches up and go off to create PR in whatever web UI
git push --set-upstream origin $branch_for_pull  # it's new!
git checkout $branch_target
git push  # if this branch wasn't pushed, just fix the issue locally instead..

Because this reverts all of the commits from HEAD to git log -n $count in reverse order, it'll work well and cleanly with any number of commits

View from $branch_target at this state

% git log --oneline origin/$branch_target
ffff006 (origin/$branch_target, $branch_target) Revert "first commit"
ffff005 Revert "second commit"
ffff004 Revert "third commit"
ffff003 third commit
ffff002 second commit
ffff001 first commit

View from $branch_for_pull at this state

% git log --oneline origin/$branch_for_pull
ffff009 (origin/$branch_for_pull, $branch_for_pull) Revert "Revert "third commit""
ffff008 Revert "Revert "second commit""
ffff007 Revert "Revert "first commit""
ffff006 (origin/$branch_target, $branch_target) Revert "first commit"
ffff005 Revert "second commit"
ffff004 Revert "third commit"
ffff003 third commit
ffff002 second commit
ffff001 first commit

If the intention was to create N branches with changesets, but they were all committed to the same branch, you can still revert all of them back to the base commit, then only revert the reverts needed as the changesets should be ordered logically (try saying that 5x fast)

Using a syntax like HEAD~7..HEAD~5 may help with describing the ranges to precisely split the revert-revert branches

Here, it would make sense when reverting the last 7 commits (git log -n 7), but restoring 5 with in one branch (git log -n 5) and 2 then the top-most 2 in another git log HEAD~12..HEAD~10 (12 is 7 commits + 5 commits, assuming the new PR branch is based off either the branch "before" it, or the result of a FF (non-squashed) merge the branch "before" it into the original target branch)

ti7
  • 16,375
  • 6
  • 40
  • 68
  • 1
    This is better than all the --hard commands, If you're reverting a commit then it should be logged in history. – Basit Anwer Sep 29 '21 at 06:07
1

I really wanted to avoid hard resets, this is what I came up with.

A -> B -> C -> D -> HEAD

To go back to A (which is 4 steps back):

git pull                  # Get latest changes
git reset --soft HEAD~4   # Set back 4 steps
git stash                 # Stash the reset
git pull                  # Go back to head
git stash pop             # Pop the reset 
git commit -m "Revert"    # Commit the changes
Cloudkollektiv
  • 11,852
  • 3
  • 44
  • 71
1

If you

  1. have a merged commit and
  2. you are not able to revert, and
  3. you don't mind squashing the history you are to revert,

then you can

git reset --soft HEAD~(number of commits you'd like to revert)
git commit -m "The stuff you didn't like."
git log
# copy the hash of your last commit
git revert <hash of your last (squashed) commit>

Then when you want to push your changes remember to use the -f flag because you modified the history

git push <your fork> <your branch> -f
ScottyBlades
  • 12,189
  • 5
  • 77
  • 85
0

Other way we can clone the branch

and then run below command.

git reset --hard commitID
git push -f branchname
Anil Singh
  • 333
  • 3
  • 7
-9

If you want to temporarily revert the commits of a feature, then you can use the series of following commands.

Here is how it works

git log --pretty=oneline | grep 'feature_name' | cut -d ' ' -f1 | xargs -n1 git revert --no-edit
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ajit Singh
  • 2,436
  • 23
  • 27