TL;DR
git reset --hard <H_SHA1>
# or alternatively
git reset --hard my_branch^~^~2^~
Why reset instead of revert
When undoing a set of commits which happen to be also merge commits, the best way to go is to reset your history rather than reverting. If you revert your commits, the changes brought by other branches will still be marked by Git as merged in your current branch, preventing you from including them in the future. If you need to void a set of commits coming from other branches, the best approach is to reset your branch to a previous commit (in your case H). This will bring back your branch to that previous point, removing all subsequent changes, and allowing you to merge those other branches once again.
Meaning of ^ and ~
The caret sign (^) allows you to reference a parent commit from a merge commit.
^ to identify which parent you want; for example, d921970^2 means “the second parent of d921970.” This syntax is useful only for merge commits, which have more than one parent — the first parent of a merge commit is from the branch you were on when you merged (frequently master), while the second parent of a merge commit is from the branch that was merged (say, topic).
The tilde sign (~) allows to reference a parent commit in a linear history.
refers to the first parent, so HEAD~ and HEAD^ are equivalent. The difference becomes apparent when you specify a number. HEAD~2 means “the first parent of the first parent,” or “the grandparent” — it traverses the first parents the number of times you specify
See the section Ancestry References of the official documentation for more details.
Breaking down indirect referencing
- my_branch^ references the first parent (B) of the HEAD commit of your branch (merge commit A).
- my_branch^~ references the previous commit (C) of the parent commit (B) that concurred to the merge.
- my_branch^~^ references the first parent commit (D) of your second to last merge.
- my_branch^~^~2 references the "grandparent" commit (F) of commit D.
- my_branch^~^~2^ references commit G.
- my_branch^~^~2^~ references commit H.
Expanding from the comments: Revert a Merge in Details
To answer your first question: yes, you could reset your branch by supplying a commit ID instead of using an intricate string of caret and tilde signs. That was the just an another indirect way of achieving your desired output. So, git reset --hard <H_SHA1> does also work of course.
Regarding git revert, the reason why I've pushed for a reset approach instead of a revert solution is because git revert introduces the opposite changes of the given commits. This means that if a commit adds a single line of code, the revert of that commit removes that very line (introduces the opposite changes). So, for each supplied commit, there will be a new commit with the opposite changes. Revert is suitable, if you're fine with having a commit history the first introduces some changes, and then removes those same changes. Also, keep in mind that a reverted history can look confusing for someone who reads the project history for the very first time.
As I've specified in the section Why reset instead of revert, reverting a merge commit is rarely a good idea. When performing a merge between two branches, Git first finds the merge-base between the head commits of the two branches, then detects the changes between the merge-base and the head of the other branch, and finally applies the detected changes on top of the target branch. If you revert a merge commit, you're not canceling the changes introduced by the merge, you're instead introducing even more changes on top of the merge commit, that are the opposite of what has been brought in by the merge. This means that, when you'll try to merge the two branches again after the revert, you won't be able to bring those changes back into your branch, because those modifications already present in the history of the current branch. You don't see them, because they've been canceled by the subsequent revert commits.
Revert is a viable approach for linear histories. Of course, you can use it on merge commits as well, as long as you're fine with not being able to merge the reverted changes into the current branch ever again.
In your case, if you're absolutely sure to proceed with a revert, you could write:
# revert each commit between H (excluded) up to the head (included)
git revert <H_SHA1>..HEAD
# alternatively, you could refer to H indirectly
git revert HEAD^~^~2^~..HEAD