Remover confirmação específica

287

Eu estava trabalhando com um amigo em um projeto, e ele editou vários arquivos que não deveriam ter sido editados. De alguma forma, fundei o trabalho dele no meu, quando o puxei ou quando tentei apenas escolher os arquivos específicos que queria. Estou procurando e reproduzindo há um longo tempo, tentando descobrir como remover os commits que contêm as edições desses arquivos, parece haver um lance entre reverter e refazer, e não há exemplos simples e os Os documentos assumem que eu sei mais do que sei.

Então, aqui está uma versão simplificada da pergunta:

Dado o cenário a seguir, como faço para remover o commit 2?

$ mkdir git_revert_test && cd git_revert_test

$ git init
Initialized empty Git repository in /Users/josh/deleteme/git_revert_test/.git/

$ echo "line 1" > myfile

$ git add -A

$ git commit -m "commit 1"
[master (root-commit) 8230fa3] commit 1
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 myfile

$ echo "line 2" >> myfile

$ git commit -am "commit 2"
[master 342f9bb] commit 2
 1 files changed, 1 insertions(+), 0 deletions(-)

$ echo "line 3" >> myfile

$ git commit -am "commit 3"
[master 1bcb872] commit 3
 1 files changed, 1 insertions(+), 0 deletions(-)

O resultado esperado é

$ cat myfile
line 1
line 3

Aqui está um exemplo de como eu tenho tentado reverter

$ git revert 342f9bb
Automatic revert failed.  After resolving the conflicts,
mark the corrected paths with 'git add <paths>' or 'git rm <paths>'
and commit the result.
Joshua Cheek
fonte
4
Se alguém encontrar isso enquanto procura pelo mesmo problema, eis o que acabei fazendo: Copie e cole. Seriamente. Passou mais de 6 horas tentando obter as soluções sugeridas para o trabalho, sem sucesso. No final, fiquei sem tempo, peguei o original e apenas copiei / colei cerca de 20 arquivos. Levou menos de 5 minutos e as coisas estão bem desde então (mesmo quando esses arquivos estão sendo mesclados com alterações em outros ramos que ocorreram antes desse fiasco). Eu sugiro que você tome essa abordagem também. Não é apenas o mais simples, também suspeito que é a única coisa que funciona.
Joshua Cheek
Eu enfrentei um problema semelhante, mas talvez mais complexo: tinha um ramo com centenas de confirmações que eu queria esmagar. Infelizmente, as confirmações de outro ramo foram mescladas novamente no ramo em um ponto intermediário; portanto, era necessário um "desmembramento" antes que eu pudesse esmagar. Eu segui um caminho semelhante ao sugerido por tk abaixo (seleção de cereja + usando a notação de intervalo), mas produziu conflitos em alguns outros arquivos. No final, copiar e colar + algumas edições manuais foram o caminho mais fácil e mais previsível. Definitivamente vale a pena considerar se você passar muito tempo com isso.
Federico

Respostas:

73

O algoritmo que o Git usa ao calcular as diferenças a serem revertidas exige que

  1. as linhas que estão sendo revertidas não são modificadas por nenhuma consolidação posterior.
  2. que não haja nenhum outro commit "adjacente" mais adiante na história.

A definição de "adjacente" é baseada no número padrão de linhas de uma diferença de contexto, que é 3. Portanto, se 'myfile' fosse construído assim:

$ cat >myfile <<EOF
line 1
junk
junk
junk
junk
line 2
junk
junk
junk
junk
line 3
EOF
$ git add myfile
$ git commit -m "initial check-in"
 1 files changed, 11 insertions(+), 0 deletions(-)
 create mode 100644 myfile

$ perl -p -i -e 's/line 2/this is the second line/;' myfile
$ git commit -am "changed line 2 to second line"
[master d6cbb19] changed line 2
 1 files changed, 1 insertions(+), 1 deletions(-)

$ perl -p -i -e 's/line 3/this is the third line/;' myfile
$ git commit -am "changed line 3 to third line"
[master dd054fe] changed line 3
 1 files changed, 1 insertions(+), 1 deletions(-)

$ git revert d6cbb19
Finished one revert.
[master 2db5c47] Revert "changed line 2"
 1 files changed, 1 insertions(+), 1 deletions(-)

Então tudo funciona como esperado.

A segunda resposta foi muito interessante. Há um recurso que ainda não foi lançado oficialmente (embora esteja disponível no Git v1.7.2-rc2) chamado Revert Strategy. Você pode invocar o git assim:

git revert --strategy resolve <commit>

e deve fazer um trabalho melhor para descobrir o que você quis dizer. Não sei qual é a lista de estratégias disponíveis, nem a definição de qualquer estratégia.

Hal Eisen
fonte
1
perl -pé útil para escrever programas muito curtos (uma linha) que fazem um loop e passam sua entrada para a saída, semelhante ao sed. perl -ié usado para editar arquivos no local . perl -eé como enviar o código a ser avaliado .
Hal Eisen
281

Existem quatro maneiras de fazer isso:

  • Maneira limpa, revertendo, mas mantenha em log a reversão:

    git revert --strategy resolve <commit>
    
  • De maneira severa, remova completamente apenas o último commit:

    git reset --soft "HEAD^"
    

Nota: Evite git reset --hard, pois também descartará todas as alterações nos arquivos desde a última confirmação. Se --softnão funcionar, tente --mixedou --keep.

  • Rebase (mostre o log das últimas 5 confirmações e exclua as linhas que você não deseja, ou reordene ou esmague várias confirmações em uma ou faça o que quiser, esta é uma ferramenta muito versátil):

    git rebase -i HEAD~5
    

E se um erro for cometido:

git rebase --abort
  • Rebase rápido: remova apenas uma confirmação específica usando seu ID:

    git rebase --onto commit-id^ commit-id
    
  • Alternativas: você também pode tentar:

    git cherry-pick commit-id
    
  • Ainda outra alternativa:

    git revert --no-commit
    
  • Como último recurso, se você precisar de total liberdade de edição do histórico (por exemplo, porque o git não permite editar o que você deseja), você pode usar este aplicativo de código aberto muito rápido : reposicionador .

Nota: é claro, todas essas alterações são feitas localmente, git pushdepois você deve aplicar as alterações no controle remoto. E caso seu repositório não queira remover a confirmação ("não é permitido o avanço rápido", o que acontece quando você deseja remover uma confirmação que você já enviou), você pode usar git push -fpara forçar as alterações.

Nota2: se você estiver trabalhando em um ramo e precisar forçar o envio, evite absolutamente, git push --forcepois isso pode sobrescrever outros ramos (se você tiver feito alterações neles, mesmo que seu checkout atual esteja em outro ramo). Preferem especificar sempre o ramo remoto quando você forçar impulso : git push --force origin your_branch.

laborioso
fonte
Baseado nas sugestões do @gaborous: faça um "git rebase -i HEAD ~ 2". Agora você tem várias opções. No vim, você pode ver algumas linhas comentadas: uma delas diz que você pode excluir uma linha (que deve ser a confirmação da qual você deseja se livrar) e essa confirmação será removida juntamente com o log no seu histórico.
Ilker Cat
git revert --strategy resolve <commit> .Este comando funcionou para mim. Obrigado :)
Swathin
2
git rebase -i HEAD~5trabalhou para mim. Em seguida, removi as confirmações que não precisava e consegui resolver o problema em menos de 30 segundos. Obrigado.
Lv10
117

Aqui está uma solução fácil:

git rebase -i HEAD~x

(Nota: xé o número de confirmações)

após a execução, o arquivo do bloco de notas será aberto. Entre dropalém do seu commit.
Se você não conhece o Vim, basta clicar em cada paleta de palavras que deseja editar e pressionar a tecla "I" (para o modo de inserção). Quando terminar de digitar, pressione a tecla "esc" para sair do modo de inserção.



insira a descrição da imagem aqui

e pronto, basta sincronizar o painel do git e as alterações serão enviadas para o remoto.

Se o commit que você soltar já estava no controle remoto, você precisará forçar o push. Como --force é considerado prejudicial , use git push --force-with-lease.

JD-V
fonte
Existe uma boa maneira de descobrir o que 'x' deve ser se você souber apenas o hash do commit em questão?
jlewkovich
1
Para pessoas com mentalidade cpp: x (número de confirmações) é inclusivo. por exemplo, HEAD ~ 4 inclui os últimos 4 commits.
Herpes Free Engineer
sugestão perfeita. só quero adicionar, isso também descarta as alterações do commit descartado.
Celerno # 7/19
tentou reverter 3 commits: git rebase -i HEAD-3 obteve um erro fatal: Precisava de uma única revisão inválida upstream 'HEAD-3'
Ustin
1
@Ustin it's ~ 3 not -3
JD-V
37

Sua escolha é entre

  1. mantendo o erro e introduzindo uma correção e
  2. removendo o erro e alterando o histórico.

Você deve escolher (1) se a alteração incorreta foi detectada por mais alguém e (2) se o erro é limitado a uma ramificação não pressionada privada.

Git revert é uma ferramenta automatizada para fazer (1), cria um novo commit desfazendo algum commit anterior. Você verá o erro e a remoção no histórico do projeto, mas as pessoas que puxam do seu repositório não terão problemas quando atualizarem. Não está funcionando de maneira automatizada no seu exemplo, então você precisa editar 'myfile' (para remover a linha 2), fazer git add myfilee git commitlidar com o conflito. Você terminará com quatro confirmações no seu histórico, com a confirmação 4 revertendo a confirmação 2.

Se ninguém se importa que seu histórico seja alterado, você pode reescrevê-lo e remover o commit 2 (opção 2). A maneira mais fácil de fazer isso é usar git rebase -i 8230fa3. Isso o levará a um editor e você poderá optar por não incluir a confirmação incorreta removendo a confirmação (e mantendo "pick" ao lado das outras mensagens de confirmação. Leia as conseqüências de fazer isso .

Andrew Walker
fonte
Rebase pode ser complicado, pois parece que houve uma fusão.
Cascabel
3
git rebase -i 8230fa3 e delete a linha do commit funcionaram muito bem para mim com minhas alterações somente locais. Obrigado!
Samuel
26

Approch 1

Primeiro obtenha o hash de confirmação (ex: 1406cd61) que você precisa reverter. correção simples estará abaixo do comando,

$ git revert 1406cd61

se você confirmar mais alterações relacionadas aos arquivos 1406cd61 após a confirmação 1406cd61, o comando Above simple não funcionará. Então você tem que seguir os passos abaixo, que é a escolha da cereja.

Approch 2

Siga as etapas abaixo, pois estamos usando o --force, você precisa ter direitos de administrador sobre o repositório git para fazer isso.

Etapa 1: encontre a confirmação antes da confirmação que você deseja removergit log

Etapa 2: finalizar a compra que confirmagit checkout <commit hash>

Etapa 3: crie uma nova ramificação usando seu commit de checkout atualgit checkout -b <new branch>

Etapa 4: Agora você precisa adicionar a confirmação após a confirmação removidagit cherry-pick <commit hash>

Etapa 5: Agora repita a Etapa 4 para todas as outras confirmações que deseja manter.

Etapa 6: depois que todas as confirmações foram adicionadas à sua nova filial e foram confirmadas. Verifique se tudo está no estado correto e funcionando como planejado. Verifique tudo que foi confirmado:git status

Etapa 7: mude para o seu ramo quebradogit checkout <broken branch>

Etapa 8: Agora execute uma redefinição definitiva no ramo quebrado para o commit antes do que você deseja removergit reset --hard <commit hash>

Etapa 9: mesclar sua ramificação fixa nessa ramificaçãogit merge <branch name>

Etapa 10: envie as alterações mescladas de volta à origem. AVISO: Isso substituirá o repositório remoto!git push --force origin <branch name>

Você pode executar o processo sem criar uma nova ramificação, substituindo as etapas 2 e 3 pela etapa 8 e não executando as etapas 7 e 9.

tk_
fonte
1
A primeira abordagem funcionou como um encanto. Ele inclui uma confirmação, especificando que a confirmação desejada foi revertida, o que é muito bom para fins de rastreamento.
suarsenegger 20/03
18

Você pode remover confirmações indesejadas com git rebase. Digamos que você incluiu algumas confirmações da ramificação de tópicos de um colega de trabalho em sua ramificação de tópicos, mas depois decida que não deseja essas confirmações.

git checkout -b tmp-branch my-topic-branch  # Use a temporary branch to be safe.
git rebase -i master  # Interactively rebase against master branch.

Nesse ponto, seu editor de texto abrirá a exibição de rebase interativa. Por exemplo

git-rebase-todo

  1. Remova as confirmações que você não deseja, excluindo suas linhas
  2. Salvar e sair

Se a recuperação não tiver êxito, exclua a ramificação temporária e tente outra estratégia. Caso contrário, continue com as seguintes instruções.

git checkout my-topic-branch
git reset --hard tmp-branch  # Overwrite your topic branch with the temp branch.
git branch -d tmp-branch  # Delete the temporary branch.

Se você estiver enviando a ramificação do tópico para um controle remoto, talvez seja necessário forçar o envio, pois o histórico de confirmação foi alterado. Se outros estiverem trabalhando no mesmo ramo, avise-os.

Dennis
fonte
Você poderia dar um exemplo de 'tente outra estratégia'?
Pfabri 28/05/19
@pfabri, você pode, por exemplo, escolher dois intervalos de confirmações, onde deixa de fora a confirmação incorreta. Você pode reverter o commit incorreto. Você pode evitar o uso de uma solução git, mesmo desfazendo manualmente as alterações ou iniciando de um novo ramo sem a confirmação incorreta e refazendo manualmente as boas alterações. Se o commit incorreto contiver dados confidenciais, você precisará de uma estratégia mais cuidadosa: help.github.com/en/articles/…
Dennis
8

De outras respostas aqui, eu fiquei meio confuso com como git rebase -ipoderia ser usado para remover uma confirmação, então espero que seja correto anotar meu caso de teste aqui (muito semelhante ao OP).

Aqui está um bashscript que você pode colar para criar um repositório de teste na /tmppasta:

set -x

rm -rf /tmp/myrepo*
cd /tmp

mkdir myrepo_git
cd myrepo_git
git init
git config user.name me
git config user.email me@myself.com

mkdir folder
echo aaaa >> folder/file.txt
git add folder/file.txt
git commit -m "1st git commit"

echo bbbb >> folder/file.txt
git add folder/file.txt
git commit -m "2nd git commit"

echo cccc >> folder/file.txt
git add folder/file.txt
git commit -m "3rd git commit"

echo dddd >> folder/file.txt
git add folder/file.txt
git commit -m "4th git commit"

echo eeee >> folder/file.txt
git add folder/file.txt
git commit -m "5th git commit"

Neste ponto, temos um file.txtcom este conteúdo:

aaaa
bbbb
cccc
dddd
eeee

Nesse ponto, HEAD está no 5º commit, HEAD ~ 1 seria o quarto - e HEAD ~ 4 seria o 1º commit (portanto, HEAD ~ 5 não existiria). Digamos que queremos remover o terceiro commit - podemos emitir este comando no myrepo_gitdiretório:

git rebase -i HEAD~4

( Observe que git rebase -i HEAD~5resulta em "fatal: precisava de uma única revisão; HEAD ~ 5 upstream inválido". ) Um editor de texto (veja a captura de tela na resposta do @Dennis ' ) será aberto com o seguinte conteúdo:

pick 5978582 2nd git commit
pick 448c212 3rd git commit
pick b50213c 4th git commit
pick a9c8fa1 5th git commit

# Rebase b916e7f..a9c8fa1 onto b916e7f
# ...

Portanto, obtemos todos os commits desde (mas não incluindo ) nosso pedido HEAD ~ 4. Exclua a linha pick 448c212 3rd git commite salve o arquivo; você receberá esta resposta de git rebase:

error: could not apply b50213c... 4th git commit

When you have resolved this problem run "git rebase --continue".
If you would prefer to skip this patch, instead run "git rebase --skip".
To check out the original branch and stop rebasing run "git rebase --abort".
Could not apply b50213c... 4th git commit

Neste ponto, abra myrepo_git / folder/file.txtem um editor de texto; você verá que foi modificado:

aaaa
bbbb
<<<<<<< HEAD
=======
cccc
dddd
>>>>>>> b50213c... 4th git commit

Basicamente, gitvê que quando HEAD chegou ao segundo commit, havia conteúdo de aaaa+ bbbb; e, em seguida, possui um patch adicionado cccc+ ddddque não sabe como anexar ao conteúdo existente.

Portanto, aqui gitnão é possível decidir por você - é você quem deve tomar uma decisão: removendo o terceiro commit, você mantém as alterações introduzidas por ele (aqui, a linha cccc) - ou não. Caso contrário, remova as linhas extras - incluindo a cccc- folder/file.txtusando um editor de texto, para que fique assim:

aaaa
bbbb
dddd

... e depois salve folder/file.txt. Agora você pode emitir os seguintes comandos no myrepo_gitdiretório:

$ nano folder/file.txt  # text editor - edit, save
$ git rebase --continue
folder/file.txt: needs merge
You must edit all merge conflicts and then
mark them as resolved using git add

Ah - por isso, a fim de marca que nós resolvemos o conflito, que deve git add o folder/file.txt, antes de fazer git rebase --continue:

$ git add folder/file.txt
$ git rebase --continue

Aqui um editor de texto é aberto novamente, mostrando a linha 4th git commit- aqui temos a chance de alterar a mensagem de confirmação (que neste caso pode ser significativamente alterada para 4th (and removed 3rd) commitou similar). Digamos que você não queira - então basta sair do editor de texto sem salvar; Depois de fazer isso, você receberá:

$ git rebase --continue
[detached HEAD b8275fc] 4th git commit
 1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/master.

Neste ponto, agora você tem um histórico como este (que você também pode inspecionar com digamos gitk .ou outras ferramentas) do conteúdo folder/file.txt(com, aparentemente, timestamps inalterados dos commits originais):

1st git commit  |  +aaaa
----------------------------------------------
2nd git commit  |   aaaa
                |  +bbbb
----------------------------------------------
4th git commit  |   aaaa
                |   bbbb
                |  +dddd
----------------------------------------------
5th git commit  |   aaaa
                |   bbbb
                |   dddd
                |  +eeee

E se anteriormente, decidimos manter a linha cccc(o conteúdo do terceiro commit do git que removemos), teríamos:

1st git commit  |  +aaaa
----------------------------------------------
2nd git commit  |   aaaa
                |  +bbbb
----------------------------------------------
4th git commit  |   aaaa
                |   bbbb
                |  +cccc
                |  +dddd
----------------------------------------------
5th git commit  |   aaaa
                |   bbbb
                |   cccc
                |   dddd
                |  +eeee

Bem, esse era o tipo de leitura que eu esperava ter encontrado, para começar a entender como git rebasefunciona em termos de exclusão de confirmações / revisões; espero que ajude outras pessoas também ...

sdaau
fonte
Que explicação inestimável - o caso de uso exato com o qual eu estava lutando, isso me ajudou imensamente.
Pfabri 28/05/19
2

Portanto, parece que o commit incorreto foi incorporado em um commit de mesclagem em algum momento. Seu commit de mesclagem já foi realizado? Se sim, você vai querer usar git revert; você terá que cerrar os dentes e resolver os conflitos. Se não, você pode, se possível , refazer ou reverter, mas pode fazê-lo antes da consolidação da mesclagem e refazer a mesclagem.

Não há muita ajuda que possamos oferecer para o primeiro caso, realmente. Depois de tentar reverter e descobrir que o automático falhou, você deve examinar os conflitos e corrigi-los adequadamente. Esse é exatamente o mesmo processo que a correção de conflitos de mesclagem; você pode usar git statuspara ver onde estão os conflitos, editar os arquivos não mesclados, encontrar os pedaços conflitantes, descobrir como resolvê-los, adicionar os arquivos conflitantes e finalmente confirmar. Se você usar git commitsozinho (não -m <message>), a mensagem exibida em seu editor deve ser a mensagem de modelo criada por git revert; você pode adicionar uma observação sobre como você corrigiu os conflitos, salvar e sair para confirmar.

Para o segundo caso, corrigindo o problema antes da mesclagem, existem duas subcasas, dependendo se você fez mais trabalho desde a mesclagem. Caso contrário, você pode simplesmente git reset --hard HEAD^interromper a mesclagem, reverter e refazer a mesclagem. Mas acho que sim. Então, você acabará fazendo algo assim:

  • crie uma ramificação temporária antes da mesclagem e confira
  • faça a reversão (ou use git rebase -i <something before the bad commit> <temporary branch>para remover a consolidação incorreta)
  • refazer a mesclagem
  • rebase seu trabalho subseqüente em: git rebase --onto <temporary branch> <old merge commit> <real branch>
  • remova a ramificação temporária
Cascabel
fonte
1

Então, você fez algum trabalho e pressionou, vamos chamá-los de commit A e B. Seu colega de trabalho também deu certo, confirma C e D. Você mesclou o trabalho de seus colegas no seu (mesclar commit E), depois continuou trabalhando, confirmou que, (comprometa F) e descobriu que seu colega de trabalho mudou algumas coisas que ele não deveria ter.

Portanto, seu histórico de consolidação é assim:

A -- B -- C -- D -- D' -- E -- F

Você realmente quer se livrar de C, D e D '. Como você diz que mesclou o trabalho de seus colegas com o seu, esses commits já estão "disponíveis", portanto, remover os commits usando, por exemplo, git rebase é um não-não. Acredite, eu tentei.

Agora, vejo duas maneiras de sair:

  • se você ainda não enviou E e F ao seu colega de trabalho ou a qualquer outra pessoa (normalmente seu servidor de "origem"), ainda pode removê-los do histórico por enquanto. Este é o seu trabalho que você deseja salvar. Isso pode ser feito com um

    git reset D'
    

    (substitua D 'pelo hash de confirmação real que você pode obter de um git log

    Neste ponto, as confirmações E e F desaparecem e as alterações não são confirmadas na área de trabalho local novamente. Nesse ponto, eu os movia para um galho ou os transformava em um patch e o guardava para mais tarde. Agora, reverta o trabalho do seu colega de trabalho, automaticamente com um git revertou manualmente. Quando você tiver feito isso, repita o seu trabalho em cima disso. Você pode ter conflitos de mesclagem, mas pelo menos eles estarão no código que você escreveu, em vez do código do seu colega de trabalho.

  • Se você já executou o trabalho que fez após o comprometimento do colega de trabalho, ainda pode tentar obter um "patch reverso" manualmente ou usando-o git revert, mas como o seu trabalho está "no caminho", por assim dizer, você provavelmente obterá mais conflitos de mesclagem e mais confusos. Parece que foi nisso que você acabou ...

Frans
fonte
0

git revert --strategy resolve se commit é uma mesclagem: use git revert --strategy resolve -m 1

ShaDow RiDeR
fonte