Remover commit específico

Eu estava trabalhando com um amigo em um projeto e ele editou um monte de arquivos que não deveriam ter sido editados. De alguma forma, eu mesclei seu trabalho com o meu, ou quando eu o puxei, ou quando tentei apenas escolher os arquivos específicos que eu queria. Eu tenho procurado e jogado por um longo tempo, tentando descobrir como remover os commits que contêm as edições para esses arquivos, parece ser um toss up entre revert e rebase, e não há exemplos simples, e o Acho que eu sei mais do que eu.

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

Dado o seguinte cenário, como eu removo 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 ' or 'git rm ' and commit the result. 

    O algoritmo que o Git usa ao calcular diffs a ser revertido requer que

    1. as linhas que estão sendo revertidas não são modificadas por nenhum commit posterior.
    2. que não haja nenhum outro commit “adjacente” mais adiante no histórico.

    A definição de “adjacente” é baseada no número padrão de linhas de um contexto diff, que é 3. Então, se ‘myfile’ foi construído assim:

     $ cat >myfile <  

    Então tudo funciona como esperado.

    A segunda resposta foi muito interessante. Existe 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 - estratégia resolve

    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 conheço a definição de qualquer estratégia.

    Existem quatro maneiras de fazer isso:

    • Maneira limpa, revertendo, mas mantenha em log o reverso:

       git revert --strategy resolve  
    • Maneira dura, remova completamente apenas o último commit:

       git reset --soft "HEAD^" 

    Nota: Evite git reset --hard pois ele também irá descartar todas as alterações nos arquivos desde o último commit. Se --soft não funcionar, tente --mixed ou --keep .

    • Rebase (mostre o log dos últimos 5 commits e delete as linhas que você não quer, reordene ou diminua vários commits em um, ou faça qualquer outra coisa que você queira, esta é uma ferramenta muito versátil):

       git rebase -i HEAD~5 

    E se um erro é feito:

     git rebase --abort 
    • Recondicionamento rápido: remova apenas um commit específico 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 de histórico (por exemplo, porque o git não permite editar o que você quer), você pode usar este aplicativo de código aberto muito rápido : reposurgeon .

    Nota: claro, todas essas mudanças são feitas localmente, você deve git push depois para aplicar as mudanças ao controle remoto. E no caso do seu repo não querer remover o commit (“nenhum avanço rápido permitido”, que acontece quando você quer remover um commit que você já fez), você pode usar o git push -f para forçar as mudanças.

    Nota 2: se estiver trabalhando em uma ramificação e você precisar forçar o envio, você deve absolutamente evitar o git push --force porque isso pode sobrescrever outras ramificações (se você tiver feito alterações nelas, mesmo se sua verificação atual estiver em outra ramificação). Prefiro sempre especificar o ramo remoto quando você força o push : git push --force origin your_branch .

    Maneira muito fácil

    rebase git -i HEAD ~ x

    (x = não de commits)

    após a execução do arquivo notepad será aberto ‘colocar’ drop além do seu commit insira a descrição da imagem aqui

    e é isso que você está feito … apenas sincronize o painel do git e as mudanças serão enviadas para o remoto. Se o commit que você derrubou já estava no controle remoto, você terá que forçar a atualização. Como –force é considerado prejudicial , use git push --force-with-lease .

    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 mudança errônea foi escolhida por qualquer outra pessoa e (2) se o erro for limitado a uma ramificação privada não enviada.

    O 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 saírem do seu repository não terão problemas quando atualizarem. Ele não está funcionando de maneira automatizada no seu exemplo, então você precisa editar ‘myfile’ (para remover a linha 2), fazer git add myfile e git commit para lidar com o conflito. Você terminará com quatro commits em seu histórico, com commit 4 revertendo commit 2.

    Se ninguém se importa que seu histórico seja alterado, você pode reescrevê-lo e remover o commit 2 (escolha 2). A maneira mais fácil de fazer isso é usar o git rebase -i 8230fa3 . Isto irá deixá-lo em um editor e você pode optar por não include o commit errado ao remover o commit (e manter o “pick” ao lado das outras mensagens de commit. Leia as conseqüências de fazer isso .

    Você pode remover commits indesejados com git rebase . Digamos que você incluiu alguns commits do branch de tópico de um colega de trabalho em seu ramo de tópico, mas depois decida que não deseja esses commits.

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

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

    git-rebase-todo

    1. Remova os commits que você não quer excluindo suas linhas
    2. Salvar e sair

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

     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 empurrando sua ramificação de tópico para um controle remoto, talvez seja necessário forçar o envio desde que o histórico de confirmação foi alterado. Se outros estiverem trabalhando no mesmo ramo, dê-lhes um aviso.

    De outras respostas aqui, eu fiquei meio confuso com como o git rebase -i poderia ser usado para remover um commit, então eu espero que seja OK anotar o meu caso de teste aqui (muito similar ao OP).

    Aqui está um script bash que você pode colar para criar um repository de teste na pasta /tmp :

     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.txt com estes conteúdos:

     aaaa bbbb cccc dddd eeee 

    Neste ponto, HEAD está no 5º commit, HEAD ~ 1 seria o 4º – e HEAD ~ 4 seria o 1º commit (então o HEAD ~ 5 não existiria). Digamos que queremos remover o terceiro commit – podemos emitir este comando no diretório myrepo_git :

     git rebase -i HEAD~4 

    ( Observe que git rebase -i HEAD~5 resulta com “fatal: necessária uma única revisão; upstream inválido HEAD ~ 5”. ) Um editor de texto (veja a captura de canvas na resposta do @Dennis ) será aberto com estes conteúdos:

     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 # ... 

    Então, recebemos todos os commits desde (mas não incluindo ) nosso HEAD ~ 4 solicitado. Exclua a linha pick 448c212 3rd git commit e salve o arquivo; você receberá esta resposta da 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.txt em um editor de texto; você verá que foi modificado:

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

    Basicamente, git vê que quando HEAD chegou ao 2º commit, havia conteúdo de aaaa + bbbb ; e então tem um patch de cccc + dddd adicionado que não sabe como append ao conteúdo existente.

    Então aqui o git não pode decidir por você – é você quem tem que tomar uma decisão: removendo o terceiro commit, você mantém as mudanças introduzidas por ele (aqui, a linha cccc ) – ou não. Se você não fizer isso, simplesmente remova as linhas extras – incluindo o cccc – na folder/file.txt usando um editor de texto, para que fique assim:

     aaaa bbbb dddd 

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

     $ 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 – então, para marcar que resolvemos o conflito, devemos git add a folder/file.txt antes de fazer o git rebase --continue :

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

    Aqui um editor de texto abre novamente, mostrando a linha 4th git commit – aqui temos a chance de mudar a mensagem de commit (que neste caso pode ser significativamente alterada para a 4th (and removed 3rd) commit ou similar). Vamos dizer que você não quer – então saia 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 o gitk . Ou outras ferramentas) do conteúdo de 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 3º 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, este foi o tipo de leitura que eu esperava ter encontrado, para começar a grokking como git rebase funciona em termos de exclusão de commits / revisões; então espero que possa ajudar os outros também …

    Por favor, siga abaixo squesnce de ações, Uma vez que estamos usando –force você precisa ter direitos de administrador sobre o repository git para fazer isso.

    Etapa 1: Encontre o commit antes do commit que você deseja remover o git log

    Etapa 2: Check out que confirma o git checkout

    Etapa 3: criar uma nova ramificação usando sua verificação de confirmação atual git checkout -b

    Passo 4: Agora você precisa adicionar o commit após o commit removido git cherry-pick

    Passo 5: Agora repita o Passo 4 para todos os outros commits que você deseja manter.

    Etapa 6: Depois que todos os commits forem adicionados à sua nova filial e forem confirmados. Verifique se tudo está no estado correto e funcionando conforme o esperado. Verifique se tudo foi confirmado: git status

    Passo 7: Mude para o seu branch quebrado git checkout

    Passo 8: Agora execute um hard reset no branch quebrado para o commit antes daquele que você deseja remover git reset --hard

    Etapa 9: mesclar sua ramificação fixa neste branch git merge

    Etapa 10: envie as alterações mescladas de volta à origem. AVISO: Isto replaceá o repository remoto! git push --force origin

    Você pode fazer o processo sem criar uma nova ramificação, substituindo as Etapas 2 e 3 pela Etapa 8 e, em seguida, não executando as Etapas 7 e 9.

    Então parece que o commit errado foi incorporado em um commit de merge em algum momento. Seu commit da mesclagem foi puxado ainda? Se sim, então você vai querer usar o git revert ; você terá que cerrar os dentes e trabalhar nos conflitos. Se não, então você poderia rebase ou reverter, mas você pode fazê-lo antes da consolidação da mesclagem e, em seguida, refazer a mesclagem.

    Não há muita ajuda que possamos dar para o primeiro caso, realmente. Depois de tentar a reversão e descobrir que a automática falhou, você deve examinar os conflitos e corrigi-los apropriadamente. Esse é exatamente o mesmo processo que corrigir conflitos de mesclagem; você pode usar o git status para 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ê usa git commit por si mesmo (no -m ), a mensagem que aparece no seu editor deve ser a mensagem do template criada pelo git revert ; Você pode adicionar uma nota sobre como você corrigiu os conflitos e, em seguida, salvar e sair para confirmar.

    Para o segundo caso, corrigindo o problema antes de sua mesclagem, há dois subcasos, dependendo de você ter feito mais trabalho desde a mesclagem. Se não tiver, você pode simplesmente git reset --hard HEAD^ para cancelar a mesclagem, fazer a reversão e refazer a mesclagem. Mas eu estou supondo que você tem. Então, você acabará fazendo algo assim:

    • criar um ramo temporário pouco antes da fusão, e confira
    • faça o revert (ou use git rebase -i para remover o commit incorreto)
    • refazer a mesclagem
    • rebase seu trabalho subsequente de volta em: git rebase --onto
    • remova o ramo temporário

    Então você fez algum trabalho e o empurrou, vamos chamá-los de commits A e B. Seu colega de trabalho também fez algum trabalho, compromete C e D. Você mesclou o trabalho de seus colegas no seu (merge commit E), depois continuou trabalhando, comprometeu isso, também (commit F), e descobriu que seu colega de trabalho mudou algumas coisas que ele não deveria ter.

    Então, seu histórico de commits se parece com isto:

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

    Você realmente quer se livrar de C, D e D ‘. Como você diz que você mesclou o trabalho de seus colegas com o seu, esses commits já estão “lá fora”, então remover os commits usando por exemplo git rebase é um não-não. Acredite, eu tentei.

    Agora vejo duas saídas:

    • Se você ainda não enviou E e F para seu colega de trabalho ou para qualquer outra pessoa (normalmente seu servidor de “origem”), ainda é possível removê-lo 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 em sua área de trabalho local novamente. Neste ponto, eu os movia para um ramo ou os transformava em um patch e o salvava para mais tarde. Agora, reverta o trabalho de seu colega de trabalho, seja automaticamente com um git revert ou manualmente. Quando você tiver feito isso, repita seu trabalho além 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á empurrou o trabalho que fez após o commit do seu colega de trabalho, você ainda pode tentar obter um “patch reverso” manualmente ou usando o git revert , mas já que seu trabalho está “no caminho”, por assim dizer, você provavelmente obter mais conflitos de mesclagem e mais confusos. Parece que foi isso que você acabou …