170

How can I revert a range of commits in git? From looking at the gitrevisions documentation, I cannot see how to specify the range I need. For example:

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

I want to do the equivalent of:

git revert B-D

where the result would be:

A -> B -> C -> D -> E -> F -> HEAD

where F contains the reverse of B-D inclusive.

Alex Spurling
  • 51,267
  • 23
  • 67
  • 73
  • Towards the end of the gitrevisions(7) page, there is a section headed "SPECIFYING RANGES". How does what you want differ from what's described there? – Gareth McCaughan Feb 14 '11 at 11:30
  • 2
    The gitrevisions page suggests that 'git revert A..D' will do what I want. However when I try that I get the error "fatal: Cannot find 'A..D'" – Alex Spurling Feb 14 '11 at 11:36

4 Answers4

223

What version of Git are you using?

Reverting multiple commits in only supported in Git1.7.2+: see "Rollback to an old commit using revert multiple times." for more details.
The current git revert man page is only for the current Git version (1.7.4+).


As the OP Alex Spurling reports in the comments:

Upgrading to 1.7.4 works fine.
To answer my own question, this is the syntax I was looking for:

git revert B^..D 

B^ means "the first parent commit of B": that allows to include B in the revert.
See "git rev-parse SPECIFYING REVISIONS section" which include the <rev>^, e.g. HEAD^ syntax: see more at "What does the caret (^) character mean?")

Note that each reverted commit is committed separately.

Henrik N clarifies in the comments:

git revert OLDER_COMMIT^..NEWER_COMMIT

As shown below, you can revert without committing right away:

git revert -n OLDER_COMMIT^..NEWER_COMMIT
git commit -m "revert OLDER_COMMIT to NEWER_COMMIT"
VonC
  • 1,129,465
  • 480
  • 4,036
  • 4,755
  • 1
    Thanks, that was the answer. Upgrading to 1.7.4 works fine. To answer my own question, this is the syntax I was looking for: git revert B^..D – Alex Spurling Feb 14 '11 at 14:56
  • genius. thanks. It hadn't occurred to me I need to revert commits in reverse order for the patches to apply, duh. This command shows the way. – Tim Abell May 15 '12 at 14:20
  • 10
    I refer back to this answer often, and it always takes me a while to figure out the order. So to help my future self: `git revert OLDER_COMMIT^..NEWER_COMMIT` – Henrik N Sep 20 '12 at 16:49
  • 2
    what does the ^ mean? – Dustin Getz Oct 15 '15 at 17:43
  • 1
    @DustinGetz first parent: see http://git-scm.com/docs/gitrevisions: "A suffix `^` to a revision parameter means the first parent of that commit object". – VonC Oct 15 '15 at 18:46
  • You could also then `git rebase -i HEAD~3` (up to the previous `HEAD`) and squash most of the revert commits (in this case the last 2, -D and -C) together into the first one (-B) to have a single revert commit. – Jorge Orpinel Pérez Jan 18 '20 at 19:35
  • @JorgeOrpinel True. I remember studying the difference between merge squash and rebase in https://stackoverflow.com/a/2427520/6309 in 2010. – VonC Jan 18 '20 at 22:05
  • Seems it does not work properly in case of merge conflicts in several commits of the range. – ks1322 Jan 21 '22 at 09:43
  • @ks1322 That is possible. What version of Git are you using? – VonC Jan 21 '22 at 09:51
  • I use git version 2.20.1. Seems it stopped on first merge conflict. I had to revert commits and resolve conflicts one by one. – ks1322 Jan 21 '22 at 10:02
  • @ks1322 OK. Resolving conflicts seems necessary, but Git 2.34+ has a better merge algo by default (the ORT one: https://stackoverflow.com/a/64950077/6309), which can help. – VonC Jan 21 '22 at 10:04
26

If you want to revert commit range B to D (at least in git version 2) in a single commit, you can do

 git revert -n B^..D

This revert the changes done by commits from B's parent commit (excluded) to the D commit (included), but doesn't create any commit with the reverted changes. The revert only modifies the working tree and the index.

Don't forgot to commit the changes after

 git commit -m "revert commit range B to D"

You can also revert multiple unrelated commits in a single commit, using same method. for example to revert B and D but not C

 git revert -n B D
 git commit -m "Revert commits B and D"

Reference: https://www.kernel.org/pub/software/scm/git/docs/git-revert.html

Thanks Honza Haering for the correction

Community
  • 1
  • 1
Ramast
  • 6,157
  • 2
  • 26
  • 32
  • 5
    `git revert -n B..D` does not revert commit B, only C and D. `git revert -n B^..D` reverts B as well. – Honza Haering Feb 02 '17 at 06:57
  • according to git documentation it does. reference in the post – Ramast Feb 03 '17 at 05:28
  • 1
    If you reffering to this example (which I think is a bit confusing) in the reference: `git revert -n master~5..master~2`, it says fifth latest commit included. But `master~5` is actually 6th latest commit. See [revision selection](https://git-scm.com/book/en/v2/Git-Tools-Revision-Selection) in git docs for detailed info about `..` notation :-) – Honza Haering Feb 03 '17 at 15:09
  • This is generally a bad idea if you plan to re-apply those commits – PatKilg Oct 11 '21 at 22:49
10

Doing git revert OLDER_COMMIT^..NEWER_COMMIT didn't work for me.

I used git revert -n OLDER_COMMIT^..NEWER_COMMIT and everything is good. I'm using git version 1.7.9.6.

mc_kaiser
  • 699
  • 7
  • 17
Orlando
  • 8,954
  • 2
  • 54
  • 52
  • I have had the same issue and fix it using -n, but you should leave ^ with OLDER_COMMIT (git revert -n OLDER_COMMIT^..NEWER_COMMIT). – FeelGood Dec 24 '12 at 17:54
  • @FeelGood why you should leave the ^? – Orlando Dec 24 '12 at 21:48
  • I had history A -> B -> C and the goal was to revert B and C. When I run 'git revert -n B..C', only C was reverted. When I used 'git revert -n B^..C', git reverted both commits. Maybe I did something wrong. – FeelGood Dec 25 '12 at 12:39
  • cool, well have to test it but i think in my case worked good (unless i was reverting a 1 commit range lol) i'll modify the answer to include the ^. thanks – Orlando Dec 26 '12 at 22:34
  • 3
    The `-n` or `--no-commit` option will revert all the changes across the range in a single commit, instead of creating a revert commit for every commit in the range. End result is the same, as in, the same changes will be reverted. Just depends how you want your git history to look like. – Dennis Aug 31 '18 at 16:41
  • @FeelGood the range B..D signifies commits that are reachable from D but not from B. That's why B is not included -- because B is reachable from itself. Using B^..D means commits that are reachable from D but not from B's parent, so B gets included. – MawrCoffeePls Jun 02 '20 at 20:12
-1

Use git rebase -i to squash the relevant commits into one. Then you just have one commit to revert.

Graham Borland
  • 58,983
  • 20
  • 134
  • 177
  • 7
    If using git rebase, you can simply remove the commits. I think there is a reason not to rebase, like wanting to keep SHA1 of commit F the same. – Paŭlo Ebermann Jun 02 '11 at 15:18
  • 6
    Alternatively, squash the reverting commits into one. – aeosynth Aug 31 '12 at 17:15
  • I guess you can squash them on a separate branch, revert that commit, and cherry-pick the reverting commit onto the original branch. – herman Oct 06 '20 at 15:20