Revert a range of commits in git
Asked Answered
C

4

199

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.

Crowley answered 14/2, 2011 at 11:25 Comment(4)
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?Orangy
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'"Crowley
11 years later but just wanted to say this is a really clear and well-worded question. :muscle:Quota
See also a sort of inverse corollary to this: How to cherry-pick multiple commitsHunks
D
252

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"
Dunkin answered 14/2, 2011 at 13:31 Comment(17)
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^..DCrowley
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.Alimentary
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_COMMITImprovisator
what does the ^ mean?Bespatter
@DustinGetz first parent: see git-scm.com/docs/gitrevisions: "A suffix ^ to a revision parameter means the first parent of that commit object".Dunkin
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.Yearly
@JorgeOrpinel True. I remember studying the difference between merge squash and rebase in https://mcmap.net/q/12642/-what-is-the-difference-between-merge-squash-and-rebase in 2010.Dunkin
Seems it does not work properly in case of merge conflicts in several commits of the range.Devine
@Devine That is possible. What version of Git are you using?Dunkin
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.Devine
@Devine OK. Resolving conflicts seems necessary, but Git 2.34+ has a better merge algo by default (the ORT one: https://mcmap.net/q/13688/-when-would-you-use-the-different-git-merge-strategies), which can help.Dunkin
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_COMMITCopperplate
@Copperplate I believe so, yes.Dunkin
Interesting. The "commit range" syntax for git revert is the same as that for git cherry-pick. See here: How to cherry-pick multiple commitsHunks
@GabrielStaples True: the <commit> in git revert, et git cherry-pick use git revisions.Dunkin
Doesn't work for me. It only reverts the last commit in the given range for some reason. I'm on git 2.39.2 (Apple Git-143).Wifeless
@Wifeless OK. Can you ask a separate question to study that in detail?Dunkin
S
38

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.

Sawhorse answered 24/1, 2017 at 7:22 Comment(7)
git revert -n B..D does not revert commit B, only C and D. git revert -n B^..D reverts B as well.Ardellardella
according to git documentation it does. reference in the postSawhorse
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 in git docs for detailed info about .. notation :-)Ardellardella
This is generally a bad idea if you plan to re-apply those commitsDin
@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.Hunks
Excellent answer, and exactly what I needed! I need to add in some history (maybe 10 commits) from old versions of our software, without rewriting history, and without 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...Hunks
...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!Hunks
P
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.

Perspiration answered 25/10, 2012 at 18:15 Comment(6)
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).Saddler
@Saddler why you should leave the ^?Perspiration
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.Saddler
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 ^. thanksPerspiration
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.Duteous
@Saddler 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.Osteoarthritis
W
-2

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

Wiliness answered 14/2, 2011 at 13:48 Comment(3)
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.Verrucose
Alternatively, squash the reverting commits into one.Linda
I guess you can squash them on a separate branch, revert that commit, and cherry-pick the reverting commit onto the original branch.Diction

© 2022 - 2024 — McMap. All rights reserved.