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
Answer from Jakub Narębski on Stack OverflowExpanding 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
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
Revert changes based on multiple commit selection
git - How to revert multiple commits as part of a single commit - Stack Overflow
Revert a squashed merge
Best practices in rolling back PR's in production?
Videos
You can do:
git revert abcd123
git revert --no-commit wxyz789
git commit --amend
... and then write an appropriate commit message describing the combined effect of reverting both commits.
In case of complicated reverts, which changes each other, the revert --no-commit might be problematic.
My simple solution was to do real revert, and the squash:
git revert <all commits>
git rebase -i
And then mark all the reverts as squash, except the first one, to create a single commit.