Desfazendo uma rebase do git

3180

Como posso desfazer facilmente uma rebase do git?

Minhas idéias atuais são apenas abordagens manuais:

  1. git checkout no pai de confirmação para os dois ramos
  2. Crie uma ramificação temporária a partir daí
  3. git cherry-pick tudo confirma manualmente
  4. substituir a ramificação na qual refiz a nova ramificação criada manualmente

Na minha situação atual, isso funcionaria porque eu posso identificar facilmente commits de ambos os ramos (um era o meu material, o outro era o material do meu colega).

No entanto, minha abordagem me parece subótima e propensa a erros (digamos que eu acabei de fazer uma nova redefinição com duas de minhas próprias ramificações).

Esclarecimento : Estou falando de uma re-avaliação durante a qual um monte de confirmações foram repetidas. Não é só um.

webmat
fonte
6
Observe também que, durante uma rebase, você pode excluir confirmações ou esmagá-las; essas alterações não são revertíveis sem um ponteiro para os nós originais ou peneirar o reflog, para que o cherrypicking não funcione.
ANeves

Respostas:

4337

A maneira mais fácil seria encontrar o commit principal do ramo, como era imediatamente antes do rebase começar no reflog ...

git reflog

e redefinir a ramificação atual (com as advertências usuais sobre ter certeza absoluta antes de redefinir com a --hardopção).

Suponha que o commit antigo estivesse HEAD@{5}no log ref:

git reset --hard HEAD@{5}

No Windows, pode ser necessário citar a referência:

git reset --hard "HEAD@{5}"

Você pode verificar o histórico do candidato antigo, basta fazer um git log HEAD@{5}( Windows git log "HEAD@{5}" :).

Se você não desabilitou os reflogs de ramificação, poderá fazer isso simplesmente git reflog branchname@{1}como uma rebase desanexa a cabeça da ramificação antes de recolocá-la na cabeça final. Gostaria de verificar isso novamente, embora não tenha verificado isso recentemente.

Por padrão, todos os reflogs são ativados para repositórios não bare:

[core]
    logAllRefUpdates = true
CB Bailey
fonte
115
O reflit do Git é incrível, lembre-se de que você pode obter uma saída melhor formatada com git log -g(dica do progit.org/book de Scott Chacon ).
Karmi
60
@Zach: git rebase --abort( -inão faz sentido --abort) é para abandonar uma rebase que não foi concluída - seja porque houve conflitos ou porque foi interativa ou ambas; não se trata de desfazer uma rebase bem-sucedida, que é a questão. Você usaria rebase --abortou reset --harddependendo da situação em que estava. Não precisa fazer as duas coisas.
CB Bailey
311
Apenas no caso, faça um backup primeiro: git tag BACKUP. Você pode retornar a ele se algo der errado:git reset --hard BACKUP
kolypto
6
Se você já fez um monte de commits HEAD @ {#} você está procurando será antecedido com commit:ao contrário rebase:. Parece óbvio, mas isso me confundiu um pouco.
Warpling 26/08/13
4
Juntar-se à parte após uma nova recuperação acidental: D. Um git reset --hard ORIG_HEADtruque também não seria feito imediatamente após a recuperação acidental?
quaylar
1488

Na verdade, o rebase salva seu ponto de partida para ORIG_HEADque, geralmente, seja tão simples quanto:

git reset --hard ORIG_HEAD

No entanto, o reset, rebasee mergetodos salvam o HEADponteiro original para ORIG_HEADque, se você executou algum desses comandos desde o rebase que está tentando desfazer, precisará usar o reflog.

Pat Notz
fonte
34
Caso ORIG_HEADnão seja mais útil, você também pode usar a branchName@{n}sintaxe, onde né a enésima posição anterior do ponteiro de ramificação. Portanto, por exemplo, se você redefinir a featureAramificação para sua masterramificação, mas não gostar do resultado da git reset --hard featureA@{1}redefinição , você pode simplesmente redefinir a ramificação exatamente para onde estava antes da redefinição. Você pode ler mais sobre a sintaxe branch @ {n} nos documentos oficiais do Git para revisões .
15
Este é o mais fácil. Continue com um git rebase --abortpensamento.
Seph
1
@DaBlick isso funcionou bem para mim depois de uma recuperação completamente bem-sucedida, sem conflitos git 2.17.0.
precisa saber é o seguinte
4
E deixe-me complementá-lo: git reset --hard ORIG_HEADpode usar repetidamente para reverter repetidamente. Digamos que, se A --- rebase para --- --- B rebase para --- C, agora eu estou em C, eu posso voltar a uma usando duas vezesgit reset --hard ORIG_HEAD
CalvinChe
5
@ Seph Você pode explicar por que sugere sugerir o acompanhamento git rebase --abort?
UpTheCreek 28/01/19
386

A resposta de Charles funciona, mas você pode fazer o seguinte:

git rebase --abort

para limpar após o reset.

Caso contrário, você poderá receber a mensagem “ Interactive rebase already started”.

Allan
fonte
4
Este removeu a parte "| REBASE" no meu prompt. +1
Wouter Thielen
62
Essa não era a questão. A pergunta pergunta como desfazer uma rebase finalizada.
Arunav Sanyal 04/04
2
Deve ser um comentário sobre a resposta de Charles, porque não responde à pergunta em que está ligado
Murmel
Hm, eu não precisava fazer isso.
Viktor Seč
1
O único que funcionou para mim!
Alexandre Picard
90

Redefinir a ramificação para o objeto de confirmação pendente de sua dica antiga é obviamente a melhor solução, pois restaura o estado anterior sem fazer nenhum esforço. Mas se você perdeu esses commits (por exemplo, porque você coletou lixo no repositório enquanto isso, ou este é um novo clone), sempre é possível refazer a ramificação novamente. A chave para isso é o --ontointerruptor.

Digamos que você tenha um ramo de tópico chamado imaginativamente topic, que você tenha ramificado masterquando a dica masterera o 0deadbeefcommit. Em algum momento enquanto estava no topicgalho, você o fez git rebase master. Agora você quer desfazer isso. Aqui está como:

git rebase --onto 0deadbeef master topic

Isso levará todos os commits topicque não estiverem masterativados e os reproduzirá em cima de0deadbeef .

Com --onto, você pode reorganizar sua história em praticamente qualquer forma .

Diverta-se. :-)

Aristóteles Pagaltzis
fonte
3
Penso que esta é a melhor opção devido à sua flexibilidade. Ramifiquei b1 do master e, em seguida, refiz o b1 para um novo ramo b2, depois quis reverter o b1 para que ele se baseasse no master novamente. Eu simplesmente amo git - obrigado!
precisa saber é o seguinte
2
Esta é a melhor opção aqui! Ele manteve todas as alterações que tenho no meu ramo atual e removeu todas as indesejadas!
Alicia Tang
Eu diria isso com uma combinação de --ontoe -ivocê pode reorganizar sua história em praticamente qualquer forma. Use gitk (ou gitx no mac) para ver as formas que você cria :-).
Rjmunro 27/03
69

Na verdade, eu coloquei uma tag de backup na ramificação antes de executar qualquer operação não trivial (a maioria das rebotes é trivial, mas eu faria isso se parecer complexa).

Então, restaurar é tão fácil quanto git reset --hard BACKUP.

Alex Gontmakher
fonte
2
Eu também faço isso. Também é útil git diff BACKUP..HEAD para garantir que você alterou o que você pretendia.
Paul Osso
5
Eu costumava fazer isso também, mas desde que me senti mais confortável com o reflog, não sinto mais que é necessário. O reflog está basicamente fazendo isso em seu nome toda vez que você altera HEAD.
Pete Hodgson
4
Bem, eu prefiro nomes significativos, porque procurar o item certo no reflog às vezes não é nada divertido.
Alex Gontmakher
12
Na verdade, você nem precisa fazer uma ramificação de backup; basta usar a branchName@{n}sintaxe. Aqui nestá a enésima posição anterior do ponteiro da ramificação. Assim, por exemplo, se você redefinir a featureAramificação para sua masterramificação, mas não gostar do resultado da git reset --hard featureA@{1}redefinição , você pode simplesmente redefinir a ramificação exatamente para onde estava antes de fazer a redefinição. Você pode ler mais sobre a branch@{n}sintaxe nos documentos oficiais do Git para revisões .
2
Na verdade, você nem precisa usar a sintaxe acima, de acordo com a resposta de Pat Notz , o original HEADdo ramo é temporariamente armazenado ORIG_HEAD. Mas a técnica de usar um rótulo de ramificação de backup também funciona, são apenas mais etapas.
68

Caso você tenha enviado sua ramificação para o repositório remoto (geralmente a origem) e depois tenha feito uma nova recuperação bem-sucedida (sem mesclagem) (git rebase --abort fornece "Nenhuma recuperação em andamento"), é possível redefinir facilmente a ramificação usando o comando:

git reset --hard origem / {branchName}

Exemplo:

$ ~/work/projects/{ProjectName} $ git status
On branch {branchName}
Your branch is ahead of 'origin/{branchName}' by 135 commits.
  (use "git push" to publish your local commits)

nothing to commit, working directory clean

$ ~/work/projects/{ProjectName} $ git reset --hard origin/{branchName}
HEAD is now at 6df5719 "Commit message".

$ ~/work/projects/{ProjectName} $ git status
On branch {branchName}
Your branch is up-to-date with 'origin/{branchName}.

nothing to commit, working directory clean
Maksym
fonte
Essa é a resposta correta para mim. Rebase e commit antes do rebase tinham o mesmo ID de commit, e voltar ao HEAD {1} simplesmente não reverteria o rebase!
Bill Kotsias
23

Usar reflognão funcionou para mim.

O que funcionou para mim foi semelhante ao descrito aqui . Abra o arquivo em .git / logs / refs com o nome da ramificação que foi rebatizada e localize a linha que contém "rebase finihed", algo como:

5fce6b51 88552c8f Kris Leech <[email protected]> 1329744625 +0000  rebase finished: refs/heads/integrate onto 9e460878

Faça o checkout do segundo commit listado na linha.

git checkout 88552c8f

Uma vez confirmado que isso continha minhas mudanças perdidas, eu me ramifiquei e soltei um suspiro de alívio.

git log
git checkout -b lost_changes
Kris
fonte
3
Uau - a partir desse link, "há uma ressalva: perdi o histórico do ramo, mas neste caso não importava. Fiquei feliz por ter recuperado minhas alterações". ?
Ruffin
16

Para várias confirmações, lembre-se de que qualquer confirmação faz referência a todo o histórico que antecede essa confirmação. Portanto, na resposta de Charles, leia "o antigo commit" como "o mais novo do antigo commit". Se você redefinir para esse commit, todo o histórico que antecedeu esse commit reaparecerá. Isso deve fazer o que você quiser.

Greg Hewgill
fonte
11

Seguindo a solução de @Allan e @Zearin, eu gostaria de poder simplesmente fazer um comentário, mas não tenho reputação suficiente, então usei o seguinte comando:

Em vez de fazer git rebase -i --abort (observe o -i ), eu tinha que simplesmente fazer git rebase --abort( sem o -i ).

Usando ambos -ie--abort ao mesmo tempo faz com que o Git me mostre uma lista de opções / uso.

Portanto, meu status de filial anterior e atual com esta solução é:

matbhz@myPc /my/project/environment (branch-123|REBASE-i)
$ git rebase --abort

matbhz@myPc /my/project/environment (branch-123)
$
Matheus Felipe
fonte
11

Se você reprovou com êxito contra a filial remota e git rebase --abortainda não pode, pode fazer alguns truques para salvar seu trabalho e não possui impulsos forçados. Suponha que sua ramificação atual que foi rebatizada por engano seja chamada your-branche esteja rastreandoorigin/your-branch

  • git branch -m your-branch-rebased # renomear ramificação atual
  • git checkout origin/your-branch # checkout para o estado mais recente conhecido por sua origem
  • git checkout -b your-branch
  • verificar git log your-branch-rebased, comparar git log your-branche definir confirmações ausentes doyour-branch
  • git cherry-pick COMMIT_HASH para cada confirmação em your-branch-rebased
  • faça suas alterações. Por favor, esteja ciente de que duas filiais locais estão associadas remote/your-branche você deve enviar apenasyour-branch
Sergey P. tcp azure
fonte
4

Digamos que eu refiz o master para o meu ramo de recursos e recebo 30 novos commits que quebram algo. Descobri que muitas vezes é mais fácil remover os commits ruins.

git rebase -i HEAD~31

Rebase interativo para os últimos 31 commits (não dói se você escolher muitos).

Simplesmente aceite os commits dos quais você deseja se livrar e marque-os com "d" em vez de "pick". Agora, as confirmações são excluídas efetivamente, desfazendo a rebase (se você remover apenas as confirmações que acabou de obter ao rebasear).

Hardev
fonte
3

Para iniciantes / qualquer pessoa com muito medo de fazer uma redefinição forçada, você pode retirar o commit do reflog e salvá-lo como um novo ramo.

git reflog

Encontre o commit antes de começar a rebasear. Pode ser necessário rolar mais para baixo para encontrá-lo (pressione Enter ou PageDown). Anote o número HEAD e substitua 57:

git checkout HEAD@{57}

Revise a ramificação / confirmações, se parecer bom, crie uma nova ramificação usando este HEAD:

git checkout -b new_branch_name
Andrew
fonte
2

Se você estiver em uma filial, poderá usar:

git reset --hard @{1}

Não há apenas um log de referência para HEAD (obtido por git reflog), também há reflogs para cada ramo (obtido por git reflog <branch>). Portanto, se você estiver ligado master, git reflog masterlistará todas as alterações nesse ramo. Você pode referir-se que as mudanças por master@{1}, master@{2}etc.

git rebase normalmente mudará HEAD várias vezes, mas a ramificação atual será atualizada apenas uma vez.

@{1}é simplesmente um atalho para a ramificação atual ; portanto, é igual a master@{1}se você estiver ativado master.

git reset --hard ORIG_HEADnão funcionará se você tiver usado git resetdurante uma interação rebase.

devconsole
fonte
1

O que eu costumo fazer é git reset #commit_hash

até o último commit, onde acho que rebase não teve efeito.

então git pull

Agora sua ramificação deve corresponder exatamente como mestre e confirmações rebased não devem estar nela.

Agora pode-se escolher os commits neste ramo.

Mrigendra
fonte
1

Tentei todas as sugestões com redefinição e reflog sem sucesso. A restauração do histórico local do IntelliJ resolveu o problema de arquivos perdidos

user3638751
fonte
Obrigado! Nunca usei o histórico local antes, mas essa foi a opção mais fácil para recuperar algum código acidentalmente reformulado. Recurso muito bom, mas um pouco oculto.
Magnus W
0

git reset --hard origem / {branchName}

é a solução correta para redefinir todas as alterações locais feitas por rebase.

Damodar P
fonte
1
se você redefinir para origin/branchvocê, poderá perder as alterações entre HEAD e esse ponto. Você não quer isso de um modo geral.
Bluesmonk
Isso é exatamente o que eu queria no meu caso. Tão votando.
Mandar Vaze 16/01
-4

Se você estragar alguma coisa dentro de uma rebase do git, por exemplo git rebase --abort, enquanto você tiver arquivos não confirmados, eles serão perdidos e git reflognão ajudarão. Isso aconteceu comigo e você precisará pensar fora da caixa aqui. Se você tiver sorte como eu e usar o IntelliJ Webstorm, poderá right-click->local historye poderá reverter para um estado anterior de seus arquivos / pastas, independentemente dos erros cometidos com o software de controle de versão. É sempre bom ter outro à prova de falhas em execução.

stevek
fonte
5
git rebase --abortinterrompe uma rebase ativa, não desfaz uma rebase. Além disso, usar dois VCS ao mesmo tempo é uma má ideia. É um recurso interessante no software Jetbrains, mas você não deve usar os dois. É melhor apenas aprender o Git, principalmente ao responder a perguntas no Stack Overflow sobre o Git.
Dudewad 15/05