Como você remove uma revisão específica no histórico do git?

213

Suponha que seu histórico do git fique assim:

1 2 3 4 5

1–5 são revisões separadas. Você precisa remover 3 enquanto mantém 1, 2, 4 e 5. Como isso pode ser feito?

Existe um método eficiente quando existem centenas de revisões após a exclusão?

1800 INFORMAÇÃO
fonte
Esta questão está mal definida. Ela não diz explicitamente que o autor quer 1-2- (3 + 4) -5 ou 1-2-4-5
RandyTek
14
Bem, oito anos depois, não sei dizer exatamente qual problema estava tentando resolver. Mas no git sempre há muitas maneiras de fazer algo, e há muitas respostas que várias pessoas gostaram, então acho que a ambiguidade não está causando muita dificuldade para muitas pessoas
1800 Information
1
git rebase --onto 2 3 HEADpraticamente significa rebase para 2, com confirmações entre 3 e HEAD (HEAD é opcional, ou 5 neste caso)
neaumusic

Respostas:

76

Para combinar as revisões 3 e 4 em uma única revisão, você pode usar o git rebase. Se você deseja remover as alterações na revisão 3, use o comando de edição no modo de rebase interativo. Se você deseja combinar as alterações em uma única revisão, use squash.

Eu usei com sucesso essa técnica de squash, mas nunca precisei remover uma revisão antes. A documentação do git-rebase em "Dividir confirmações" deve fornecer uma idéia suficiente para você descobrir. (Ou alguém pode saber).

Na documentação do git :

Inicie-o com o commit mais antigo que você deseja manter como está:

git rebase -i <after-this-commit>

Um editor será acionado com todas as confirmações em sua ramificação atual (ignorando as confirmações de mesclagem), que vêm após a confirmação fornecida. Você pode reordenar os commits nesta lista para o conteúdo do seu coração e pode removê-los. A lista é mais ou menos assim:

pick deadbee O on-line desse commit
pick fa1afe1 O on-line do próximo commit
...

As descrições on-line são puramente para seu prazer; O git-rebase não olha para eles, mas para os nomes de confirmação ("deadbee" e "fa1afe1" neste exemplo), portanto, não exclua ou edite os nomes.

Substituindo o comando "pick" pelo comando "edit", você pode dizer ao git-rebase para parar após aplicar esse commit, para que você possa editar os arquivos e / ou a mensagem de commit, alterar o commit e continuar com o rebasing.

Se você deseja dobrar duas ou mais confirmações em uma, substitua o comando "pick" por "squash" para a segunda e subsequente confirmação. Se os commits tiverem autores diferentes, ele atribuirá o commit compactado ao autor do primeiro commit.

garethm
fonte
42
-1 A pergunta está bem definida, mas esta resposta não é tão clara. O autor não diz qual é a solução exata.
Aleksandr Levchuk 03/04/19
1
Esta é uma pista errada. A seção SPLITTING COMMITS não é a correta. Você quer ler muito mais alto no manual - veja a resposta de @Rares Vernica.
Aleksandr Levchuk 03/04
1
@AleksandrLevchuk A questão não está bem definida: não está claro pela maneira como a questão é declarada se o conjunto de alterações em 3 deve ser mantido ou descartado. Concordo que, se as alterações forem descartadas, as outras respostas oferecerão uma abordagem mais fácil. Se as alterações forem mantidas, no entanto, isso seria uma operação puramente cosmética. Ambas as abordagens reescrevem a história de maneiras perigosas se outras pessoas basearem o trabalho no topo da história defeituosa; se for esse o caso, a limpeza cosmética não deve ser feita e a exclusão da alteração seria melhor via git revert.
Theodore Murdock
124

Por esse comentário (e eu verifiquei que isso é verdade), a resposta do rado é muito próxima, mas deixa o git em um estado principal desanexado. Em vez disso, remova HEADe use-o para remover <commit-id>da ramificação em que está:

git rebase --onto <commit-id>^ <commit-id>
kareem
fonte
1
Se você pudesse me dizer como remover esse ID de confirmação do histórico com base apenas no ID de confirmação, você seria meu herói.
kayleeFrye_onDeck
Você pode incluir explicações sobre o que o comando mágico faz na resposta? Ou seja, o que cada parâmetro indica?
Alexandroid
isso é incrível, mas e se eu quiser remover o primeiro commit da história? (que é por isso que eu vim aqui: P)
Sterling Camden
2
Por alguma razão, nada acontece quando eu executo isso. No entanto, mudar ^para ~1fazê-lo funcionar.
Antimony
Muito tarde, mas vou acrescentar que, se você tiver conflitos de mesclagem, aborte a rebase e execute-a novamente --strategy-option theirsno final.
LastStar007
122

Aqui está uma maneira de remover de maneira não interativa um específico <commit-id>, sabendo apenas o que <commit-id>você deseja remover:

git rebase --onto <commit-id>^ <commit-id> HEAD
rado
fonte
2
Trabalhou para mim também. Btw, o que o operador ^ faz? Significa o próximo commit após o especificado?
hopia
3
@hopia significa o (primeiro) pai do commit especificado. Veja "revisões da ajuda do git"
Emil Styrke 28/03
12
Veja a recomendação de @ kareem em sua resposta para omitir HEAD, a fim de evitar uma cabeça desapegada.
precisa
1
Muito mais fácil do que ter que descobrir quantas confirmações retornam à pesquisa.
Dana Woodman
1
isso é incrível, mas e se eu quiser remover o primeiro commit da história? (que é por isso que eu vim aqui: P)
Sterling Camden
76

Como observado antes, o git-rebase (1) é seu amigo. Supondo que os commits estejam em sua masterfilial, você faria:

git rebase --onto master~3 master~2 master

Antes:

1---2---3---4---5  master

Depois de:

1---2---4'---5' master

Do git-rebase (1):

Um intervalo de confirmações também pode ser removido com rebase. Se tivermos a seguinte situação:

E---F---G---H---I---J  topicA

então o comando

git rebase --onto topicA~5 topicA~3 topicA

resultaria na remoção dos commits F e G:

E---H'---I'---J'  topicA

Isso é útil se F e G foram defeituosos de alguma forma ou não deveriam fazer parte do tópico A. Observe que o argumento para --onto e o parâmetro podem ser qualquer commit-ish válido.

rvernica
fonte
3
não deveria ser assim --onto master~3 master~1?
Matthias
3
Se você simplesmente deseja excluir o último commit, é --onto master ~ 1 master #
MikeHoss
Eu gostaria de carregar esse sol. mas eu recebo o MERGE CONFLICTerro. Usei o cenário mencionado em stackoverflow.com/questions/2938301/remove-specific-commit e não consigo remover o segundo commit nesse exemplo.
precisa saber é
22

Se tudo o que você deseja fazer é remover as alterações feitas na revisão 3, convém usar o git revert.

O Git revert simplesmente cria uma nova revisão com alterações que desfaz todas as alterações na revisão que você está revertendo.

O que isso significa é que você retém informações sobre a confirmação indesejada e a confirmação que remove essas alterações.

Isso provavelmente é muito mais amigável se for possível que alguém tenha saído do seu repositório nesse meio tempo, já que a reversão é basicamente apenas uma confirmação padrão.

SpoonMeiser
fonte
4
Infelizmente, não é uma boa solução para mim, porque alguém acidentalmente comprometeu 100 MB de lixo no repositório, ampliando o tamanho e tornando a interface da Web lenta.
Stephen Smith
18

Até agora, todas as respostas não abordam a preocupação final:

Existe um método eficiente quando existem centenas de revisões após a exclusão?

As etapas a seguir, mas para referência, vamos assumir o seguinte histórico:

[master] -> [hundreds-of-commits-including-merges] -> [C] -> [R] -> [B]

C : confirmar apenas após a confirmação a ser removida (limpa)

R : O commit a ser removido

B : confirmar imediatamente antes da confirmação a ser removida (base)

Devido à restrição de "centenas de revisões", estou assumindo as seguintes pré-condições:

  1. existe algum comprometimento embaraçoso que você deseja que nunca exista
  2. há ZERO confirmações subsequentes que realmente dependem dessa confirmação embaraçosa (zero conflito ao reverter)
  3. você não se importa em ser listado como o 'Committer' das centenas de commits intermediários ('Author' será preservado)
  4. você nunca compartilhou o repositório
    • ou você realmente tem influência suficiente sobre todas as pessoas que já clonaram a história com esse comprometimento para convencê-las a usar sua nova história
    • e você não se importa em reescrever o histórico

Este é um conjunto bastante restritivo de restrições, mas há uma resposta interessante que realmente funciona nesse caso de canto.

Aqui estão os passos:

  1. git branch base B
  2. git branch remove-me R
  3. git branch save
  4. git rebase --preserve-merges --onto base remove-me

Se realmente não houver conflitos, isso deve prosseguir sem mais interrupções. Se houver conflitos, você poderá resolvê-los e / rebase --continueou decidir apenas viver com o constrangimento erebase --abort .

Agora você deve estar em masterque não possui mais commit R nele. osave ramificação aponta para onde você estava antes, caso deseje se reconciliar.

Você decide como deseja organizar a transferência de todos para o seu novo histórico. Você precisará estar familiarizado com stash, reset --harde cherry-pick. E você pode excluir os base, remove-mee saveramos

jdsumsion
fonte
como posso gostar disso 150000 vezes?
Gena Moroz #
Trabalhei para remover 3 commits em sequência do meio da história de uma ramificação em que alguém cometeu um monte de arquivos de 150mb.
Marcos Marcos
3

Eu também aterrei em uma situação semelhante. Use o rebase interativo usando o comando abaixo e, ao selecionar, solte o terceiro commit.

git rebase -i remote/branch
Sankalp
fonte
2

Então, aqui está o cenário que eu enfrentei e como o resolvi.

[branch-a]

[Hundreds of commits] -> [R] -> [I]

aqui Restá o commit que eu precisava ser removido e Ié um único commit que vem depoisR

Fiz um commit reverso e os esmaguei juntos

git revert [commit id of R]
git rebase -i HEAD~3

Durante o squash de rebase interativo, os 2 últimos commits.

Gautam
fonte
0

As respostas do rado e do kareem não fazem nada para mim (apenas a mensagem "O ramo atual está atualizado." Aparece). Possivelmente isso acontece porque o símbolo '^' não funciona no console do Windows. No entanto, de acordo com este comentário, substituir '^' por '~ 1' resolve o problema.

git rebase --onto <commit-id>^ <commit-id>
fdermishin
fonte