Expandindo o que escrevi em um comentário
A regra geral é que você não deve reescrever (alterar) o histórico que você publicou, porque alguém pode ter baseado seu trabalho nele. Se você reescrever (alterar) o histórico, terá problemas ao mesclar as alterações e atualizar para elas.
Portanto, a solução é criar um novo commit que reverta as alterações das quais você deseja se livrar. Você pode fazer isso usando o comando git revert .
Você tem a seguinte situação:
A <- B <- C <- D <- mestre <- CABEÇA
(as setas aqui se referem à direção do ponteiro: a referência "pai" no caso de confirmações, a confirmação superior no caso de cabeçalho de ramificação (ref de ramificação) e o nome da ramificação no caso de referência HEAD).
O que você precisa criar é o seguinte:
A <- B <- C <- D <- [(BCD) ^ - 1] <- mestre <- CABEÇA
onde "[(BCD) ^ - 1]" significa o commit que reverte as alterações nos commits B, C, D. A matemática nos diz que (BCD) ^ - 1 = D ^ -1 C ^ -1 B ^ -1, então você pode obter a situação necessária usando os seguintes comandos:
$ git revert --no-commit D
$ git revert --no-commit C
$ git revert --no-commit B
$ git commit -m "the commit message"
A solução alternativa seria verificar o conteúdo da confirmação A e confirmar este estado:
$ git checkout -f A -- .
$ git commit -a
Então você teria a seguinte situação:
A <- B <- C <- D <- A '<- mestre <- CABEÇA
A confirmação A 'tem o mesmo conteúdo que a confirmação A, mas é uma confirmação diferente (mensagem de confirmação, pais, data de confirmação).
A solução de Jeff Ferland, modificada por Charles Bailey, baseia-se na mesma idéia, mas usa git reset :
$ git reset --hard A
$ git reset --soft @{1} # (or ORIG_HEAD), which is D
$ git commit
git push -f HEAD~4:master
(assumindo que o ramo remoto seja mestre). Sim, você pode enviar qualquer commit assim.