193

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
  • 54,094
  • 23
  • 70
  • 76
  • 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
  • 3
    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
  • 3
    11 years later but just wanted to say this is a really clear and well-worded question. :muscle: – Alan Jan 06 '23 at 11:07
  • See also a sort of inverse corollary to this: [How to cherry-pick multiple commits](https://stackoverflow.com/q/1670970/4561887) – Gabriel Staples Jul 17 '23 at 23:36

4 Answers4

247

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,262,500
  • 529
  • 4,410
  • 5,250
  • 2
    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
  • 17
    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
  • Correct me if I'm wrong: this desired range of commits should be able to be shown beforehand, using: `git rev-list OLDER_COMMIT^..NEWER_COMMIT` – Life5ign Jul 06 '22 at 20:31
  • @Life5ign I believe so, yes. – VonC Jul 06 '22 at 20:33
  • Interesting. The "commit range" syntax for `git revert` is the same as that for `git cherry-pick`. See here: [How to cherry-pick multiple commits](https://stackoverflow.com/q/1670970/4561887) – Gabriel Staples Jul 17 '23 at 23:35
  • 1
    @GabrielStaples True: the `` in [`git revert`](https://git-scm.com/docs/git-cherry-pick#Documentation/git-cherry-pick.txt-ltcommitgt82308203), et [`git cherry-pick`](https://git-scm.com/docs/git-cherry-pick#Documentation/git-cherry-pick.txt-ltcommitgt82308203) use [`git revisions`](https://git-scm.com/docs/gitrevisions#_specifying_revisions). – VonC Jul 18 '23 at 06:34
34

How to revert a range of commits with one single revert commit

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
git commit -m "revert the commit range B to D, inclusive"

The -n (or --no-commit) argument tells git to revert the changes done by the commits starting from B's parent commit (excluded) to the D commit (included), but not to create any commit with the reverted changes. The revert only modifies the working tree (your active file system) and the index (your staged file section).

Don't forget to commit the changes after running git revert -n:

git commit -m "revert the commit range B to D, inclusive"

You can also revert multiple unrelated commits in a single commit, using the 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"

References:

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

  2. Thanks Honza Haering for the correction.

  3. From man git revert:

    -n, --no-commit

    Usually the command automatically creates some commits with commit log messages stating which commits were reverted. This flag applies the changes necessary to revert the named commits to your working tree and the index, but does not make the commits. In addition, when this option is used, your index does not have to match the HEAD commit. The revert is done against the beginning state of your index.

    This is useful when reverting more than one commits' effect to your index in a row.

Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
Ramast
  • 7,157
  • 3
  • 32
  • 32
  • 7
    `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
  • 2
    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
  • @PatKilg, I don't see how. You can still re-apply the commits one-at-a-time in separate commits if you want, after doing this one, combined revert commit. – Gabriel Staples Jul 17 '23 at 23:50
  • Excellent answer, and exactly what I needed! I need to add in some history (maybe 10 commits) from old versions of our software, with*out* rewriting history, and with*out* changing our `main` branch when I'm done, and this answer is exactly what I need! To perform this, I'm going to wipe the working tree, and copy version 1 of our software into the working tree, then commit it. Then I'll copy in v2, and commit it, then v3, and commit it, etc. etc. until all our history is added. When done, I want a *single* revert commit to undo all of those versions. When done, the `main` branch will be... – Gabriel Staples Jul 18 '23 at 00:00
  • ...exactly how it started, and our software version history will be now available within the repo so we can always `git diff` against it when needed, to see what had changed in the past! – Gabriel Staples Jul 18 '23 at 00:00
13

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
  • 717
  • 8
  • 18
Orlando
  • 9,374
  • 3
  • 56
  • 53
  • 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
  • 1
    @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
-2

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

Graham Borland
  • 60,055
  • 21
  • 138
  • 179
  • 8
    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
  • 7
    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