4 pontos por GN⁺ 2025-01-18 | 1 comentários | Compartilhar no WhatsApp
  • Como no Git é difícil pesquisar a solução sem saber o nome do tipo de erro, este material organiza os comandos por cenários comuns de recuperação para consultar e usar rapidamente
  • Se você bagunçou muito o repositório ou quer desfazer erros de exclusão ou merge, pode encontrar o histórico de trabalho com git reflog e voltar ao estado anterior com git reset HEAD@{index}
  • Pequenos ajustes no último commit ou mudanças na mensagem podem ser resolvidos rapidamente com git commit --amend, mas isso não deve ser usado em commits já enviados para uma branch pública/compartilhada
  • Se você fez commit na branch errada, pode mover com o fluxo reset, stash, checkout, stash pop ou com cherry-pick, mas o método seguro muda conforme o commit já foi enviado ou não
  • reset --hard e git clean -d --force, usados para forçar a recuperação para o estado do repositório remoto, podem apagar até arquivos não rastreados, então é preciso conferir antes de executar

Por que é difícil recuperar erros no Git

  • É fácil cometer erros no Git, e muitas vezes para pesquisar como corrigir um problema você já precisa saber o nome do conceito necessário
  • Este material organiza situações ruins frequentes no Git em inglês simples, com exemplos de comandos
  • Não é uma referência completa, e podem existir outras formas de fazer a mesma tarefa

Voltando o repositório para um estado anterior

  • Se você bagunçou muito o repositório ou quer recuperar algo apagado, verifique o histórico de trabalho com git reflog
git reflog


# Você pode ver a lista de operações do Git em todas as branches


# Cada item tem um índice no formato HEAD@{index}
git reset HEAD@{index}
  • reflog pode ser usado nas seguintes situações
    • Recuperar conteúdo apagado por engano
    • Remover uma tentativa que bagunçou o repositório
    • Recuperar após um merge ruim
    • Voltar para um ponto em que tudo realmente funcionava

Como corrigir o último commit

  • Se precisar adicionar uma pequena correção logo após o último commit, use git commit --amend --no-edit
# Após as mudanças
git add . # ou adicione arquivos individualmente
git commit --amend --no-edit
  • Esse comando inclui as novas mudanças no último commit
  • Também daria para criar um novo commit e depois juntar com rebase -i, mas amend é muito mais rápido
  • Não use amend em commits já enviados para uma branch pública/compartilhada
  • Se quiser mudar apenas a mensagem do último commit, use o comando abaixo
git commit --amend


# Siga as instruções para alterar a mensagem do commit

Quando você fez commit na branch errada

  • Se você fez commit por engano na master, mas ele deveria estar em uma nova branch, crie a nova branch no estado atual e remova o último commit da master
git branch some-new-branch-name
git reset HEAD~ --hard
git checkout some-new-branch-name
  • Esse método não é adequado para commits já enviados para uma branch pública/compartilhada
  • Se você tentou outras coisas antes, talvez precise usar HEAD@{number-of-commits-back} em vez de HEAD~
  • Se o commit foi parar completamente na branch errada, você pode mover usando o fluxo reset --soft, stash, checkout, stash pop
git reset HEAD~ --soft
git stash
git checkout name-of-the-correct-branch
git stash pop
git add . # ou adicione arquivos individualmente
git commit -m "your message here"
  • Também existe uma forma de resolver a mesma situação com cherry-pick
git checkout name-of-the-correct-branch
git cherry-pick master
git checkout master
git reset HEAD~ --hard

Desfazendo mudanças e commits antigos

  • Se você alterou um arquivo mas git diff está vazio, ele pode já estar em staging, então você precisa da flag --staged
git diff --staged
  • Para desfazer uma mudança de alguns commits atrás, encontre o hash com git log e execute git revert
git log
git revert [saved hash]
  • git revert cria um novo commit que desfaz aquele commit
  • Quando quiser desfazer apenas um arquivo específico, e não o commit inteiro, use o fluxo de colocar a versão antiga do arquivo no índice e depois fazer commit
git log
git checkout [saved hash] -- path/to/file
git commit -m "Wow, you don't have to copy-paste to undo"

Forçando a recuperação para o estado do repositório remoto

  • Em vez de apagar o repositório e clonar de novo, você pode usar comandos do Git para alinhar com o estado do repositório remoto
git fetch origin
git checkout master
git reset --hard origin/master
git clean -d --force
  • git reset --hard origin/master ajusta a branch para o estado da master remota
  • git clean -d --force apaga arquivos e diretórios não rastreados
  • É preciso repetir checkout, reset e clean para cada branch quebrada
  • Essas operações são destrutivas e irreversíveis, então é preciso cuidado antes de executar

Escopo e contribuições

  • Este material não é uma referência completa de Git, mas uma coletânea prática de procedimentos de recuperação encontrados por tentativa e erro
  • Se quiser ajudar a adicionar traduções, você pode enviar um PR no GitHub

1 comentários

 
GN⁺ 2025-01-18
Opiniões do Hacker News
  • Há algumas coisas que eu mudaria: sempre usaria git switch em vez de git checkout, e evitaria reset --hard ao máximo
    Por exemplo, no caso de “fiz por engano no master um commit que deveria estar em um novo branch”, dá para resolver com git branch some-new-branch-name, git switch -d HEAD~, git switch -C master, git switch some-new-branch-name, nessa ordem
    No caso de “fiz commit no branch errado”, eu também faria git switch para o branch correto e depois git cherry-pick master, git switch -d master~, git switch -C master
    Para “desistir de tudo e voltar ao estado remoto”, eu montaria algo como git fetch origin, git restore -WS ., git clean -d --force, git switch -d origin/master, git switch -C master

    • O modelo interno do Git é bonito, com blobs, árvores de commits e ponteiros de commit, mas a distância para a interface de linha de comando é grande demais
      Mesmo entendendo bem o modelo do Git, essas receitas não são intuitivas, e é preciso conhecer até os comportamentos peculiares de cada comando
      Por exemplo, git switch -c some-new-branch-name já existe, mas um comando como git move-branch master HEAD~, que apenas movesse um branch para outro commit, pareceria mais intuitivo
    • Não é que eu queira me opor à direção de usar git switch e git restore em vez de git checkout, mas esses comandos foram introduzidos no Git v2.23, e esse lançamento foi há cerca de 5 anos
      As páginas de ajuda ainda trazem o aviso THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
      Claro que esse aviso existe praticamente desde que os comandos surgiram, e, depois de 5 anos, parece que já está na hora de deixar de chamá-los de experimentais
      Ainda assim, git checkout é mais compatível com versões antigas e pode ser visto como refletindo o modo de usar Git da época em que este site foi criado
      https://github.com/git/git/blob/757161efcca150a9a96b312d9e78...
      https://github.com/git/git/releases/tag/v2.23.0
      https://github.com/git/git/commit/4e43b7ff1ea4b6f16b93a432b6...
    • Ensinar git add . como padrão para colocar alterações na área de staging não é uma boa
      Adicionar arquivos específicos explicitamente deixa menos margem para um “ah, ferrou” depois
    • Fico curioso sobre a justificativa para recomendar esse jeito
      Quero saber por que switch é melhor que checkout e por que não se deve usar reset --hard
    • Não entendo o que significa “evite reset --hard
      Tenho curiosidade sobre por que ele de fato não seria suficiente; eu uso com bastante frequência junto com alias git-restore-file='git restore --source=HEAD --', e parece funcionar bem
  • Ao aprender Git, deveríamos começar a recomendar clientes GUI como caminho padrão
    Isso resolveria um terço desses problemas, e outro terço nem chegaria a surgir
    Se depois a pessoa concluir que a linha de comando é mais rápida, pode usá-la então; primeiro, as pessoas precisam ver visualmente como interagem com a árvore
    Pessoalmente, gosto do fork.dev, mas hoje os clientes são, em geral, parecidos

    • Concordo que uma GUI muitas vezes oferece uma experiência de usuário melhor para Git
      Com o Magit, ações como “abortar cherry-pick” são descobríveis dentro da interface e usam o mesmo atalho de outras operações de “abortar X”
      Se eu tivesse que usar apenas a linha de comando do Git, não teria ideia de por onde começar
      Já apaguei, durante um rebase interativo, um commit que não deveria ter apagado; como a cada início de rebase é criado um snapshot acessível pelo reflog, é seguro desfazer um rebase incorreto
      A UI de reflog do Magit é igual à UI de log, então não fiquei perdido mesmo vendo aquilo pela primeira vez; com a CLI do Git, é bem possível que eu não entendesse o que estava acontecendo
    • Comecei a recomendar jj como uma alternativa compatível com Git
      Gosto do fato de que, ao executar jj sem argumentos, ele mostra a parte relevante da árvore de commits e a posição atual
      Dá para ver de relance o commit em que você está trabalhando, o branch associado e o histórico, outros branches ativos no repositório, a mensagem de cada commit e se houve alterações, o que é ótimo para se reorientar
    • Para iniciantes em Git, sempre recomendo encontrar uma UI que funcione para eles e usá-la em vez da linha de comando
      Mas quase ninguém escuta, acaba sofrendo na CLI, estragando branches, ficando para trás em relação ao main e criando medo de rebase
      Muitas coisas seriam resolvidas na UI com arrastar e soltar ou algo parecido, e isso parece uma espécie de autoflagelação
    • Se é preciso ver visualmente como interagir com a árvore, há um tutorial interativo que ajudou colegas meus: https://learngitbranching.js.org/
    • Pela minha experiência antiga com front-ends GUI de controle de versão, tudo bem enquanto tudo funciona, mas, quando algo ficava embolado e precisava ser organizado, era difícil alguém ajudar
      Mesmo que a interface fosse horrível, era mais fácil se comunicar usando a mesma interface que o resto da equipe usava, e os materiais online de solução de problemas em geral assumiam o “jeito comum”, então também eram mais úteis
  • Parece bem estranho pensar que, em abril de 2025, o Git fará 20 anos
    Eu estava lá na época e lembro de ficar em dúvida sobre o que aprender entre git, darcs e bazaar para substituir SVN ou CVS
    Não sei se também cheguei a experimentar o Mercurial
    Fico me perguntando se o “efeito GitHub” praticamente matou a necessidade de novos entrantes na área de sistemas de controle de versão
    Talvez, em algum momento, o yak shaving tenha acabado o suficiente

    • O efeito GitHub é real, como todos os efeitos de rede, mas isso não quer dizer que melhorias sejam impossíveis
      Migrei completamente para jj e, como há compatibilidade com Git, não preciso esperar que outras pessoas mudem junto
      Estou ficando cada vez mais frustrado com vários aspectos do GitHub e espero que um dia todo mundo também supere isso, mas nesse caso o caminho adiante não parece tão simples
    • SVN sempre funcionou bem
      SVN é intuitivo e funciona bem o bastante em 99% dos casos, então não é preciso “ensinar” as pessoas separadamente
      Gostaria que todos nós parássemos de fingir ser hackers 1337 e admitíssemos que Git é uma ferramenta exagerada para a maioria
    • Qualquer melhoria provavelmente terá de ser, no mínimo, compatível com Git, como é o caso do jj
  • Acho que tenho um modelo mental bastante bom do que o Git faz, mas, quando os comandos ficam um pouco mais complexos, não consigo lembrar os argumentos
    Os comandos não são fáceis de descobrir nem de memorizar
    Não sei se a UI de texto é ruim ou se descrever em texto a manipulação de árvores é que é difícil por natureza

  • Fico feliz em ver resultados para hg e Mercurial nesta thread
    Ainda não consigo acreditar que o Mercurial tenha perdido a guerra dos sistemas de controle de versão distribuídos, e considero uma ferramenta melhor

    • Mesmo pelos padrões de hoje, o Mercurial é sem dúvida uma ferramenta melhor
      É utilizável, estável, comprovado em produção e ativamente suportado
      É algo próximo de “seja a mudança que você quer ver”, então basta usar
      Falar que ele perdeu é bastante contraproducente, e a inibição implícita causada por esse tipo de frase mantém baixa a adoção do Mercurial
      Hospedagem é possível ao menos no Sourcehut e no heptapod.host
      Também opero uma instância privada do Heptapod, que é um fork do Gitlab com suporte direto a Mercurial, e simplesmente funciona bem
    • Interessante, mas fico me perguntando se há um link do GitHub
    • Mercurial é uma ferramenta mais lenta
    • Se você precisa de hospedagem para Mercurial, pode usar esta: https://hg.reactionary.software/
  • Git foi uma daquelas tecnologias que nunca entrou direito na minha cabeça, porque em muitos aspectos não segue a intuição
    Se você não o usa há muito tempo, é bem provável que precise procurar no Google ou ver a página man para quase qualquer operação

    • Comecei com SVN e depois migrei para Git; precisei aprender as diferenças, mas senti que em muitos aspectos ficou mais fácil
      Especialmente depois de passar pelo terror de tentar manter uma base de código grande no SVN com muitos branches e depois fazer merge de volta, dá para ver que o Git é absurdamente melhor nessa parte
      Para quem começa direto pelo Git, a curva de aprendizado pode ser muito maior, mas às vezes ainda acho difícil entender onde as pessoas travam
    • Ontem tentei apagar um arquivo do histórico
      Ao usar a forma embutida, filter-branch, aparece um aviso, depois de uma longa demora, para apertar Control+C e baixar um script Python de terceiros chamado filter-repo
  • Não me orgulho disso, mas minha operação Git de “deu ruim” mais frequente é apagar o repositório local, clonar de novo e reaplicar as alterações
    Em 95% dos casos funciona muito bem; no restante, peço ajuda para o pessoal de DevOps

    • Uso Git há quase 15 anos e já criei duas vezes programas/produtos que usam Git internamente para produzir certos resultados, mas ainda hoje às vezes faço algo como cp -R .git ../git-backup antes de realizar alguma operação mais arriscada no Git
      Porque, se eu estragar tudo demais, posso simplesmente substituir todo o diretório .git pela cópia antiga
      É muito mais rápido do que descobrir a forma correta de reverter uma operação específica ou um conjunto de operações
    • Você não está sozinho: https://xkcd.com/1597/
  • Uma coisa que eu gostaria que as pessoas realmente internalizassem sobre o Git é que ele é um armazenamento de dados somente de acréscimo
    Se quiser colocar trabalho no repositório, faça commit; uma vez que entrou, ele não pode ser removido por comandos do Git
    O reflog também funciona com base nesse princípio
    Faça o que fizer, você pode voltar ao estado anterior de um branch e restaurar o trabalho a partir do armazenamento de dados
    Mas, se você não fizer commit, o Git não pode ajudar; então simplesmente faça commit
    Faça commit sempre e, se o atrito for grande demais ou você vive esquecendo, conserte as ferramentas
    Se você passa o dia inteiro escrevendo código, em geral deveria fazer commit pelo menos uma vez por hora

  • É um detalhe bobo, mas toda vez que vejo git reflog, leio como re-flog
    Talvez seja por isso que eu tenha dificuldade para memorizar esse comando, e aparentemente não consigo pensar nele inconscientemente como “reference log”
    Em vez disso, parece que estou mandando o Git vender algo barato de novo

  • Não sou usuário de Git, mas quando vejo algo assim, fico cada vez mais com a impressão de que o git commit em si não tem significado, e que o que importa é apenas o momento em que o commit é enviado por push ou recebe merge
    É parecido com salvar um arquivo de texto
    Você não escreve uma mensagem curta toda vez que salva um arquivo, então é só seguir em frente

    • Se a mudança veio depois de uma jornada difícil de debugging e há chance de outras pessoas a consultarem de novo mais tarde, aí escrevo uma mensagem
      Não necessariamente curta; quanto menor a mudança, às vezes mais longa fica a explicação
      Já escrevi 2 ou 3 parágrafos para uma mudança de 2 ou 3 linhas com grande impacto
    • Parece muito claro que commits locais e commits remotos têm objetivos diferentes
      Commits locais são uma forma de criar checkpoints em pontos em que tudo compila e funciona
      Assim, dá para colocar mudanças em stash ou voltar para um estado que funcionava quando algo dá errado
      Antes de fazer push, você pode dar reset e dividir em commits adequados, mantendo o histórico remoto limpo e sem virar um monte de wip
      Ou pode fazer squash merge do PR e obter o melhor dos dois mundos
    • Um commit é muito importante porque é a ação que faz o Git criar internamente uma cópia dos arquivos
      Só que não há necessidade de inserir mensagens sem sentido como more fixes
      Normalmente crio um commit inicial como (WIP) nome da funcionalidade, repito commits com amend até ficar satisfeito e, no fim, removo o (WIP) da mensagem
    • A comparação é com salvar arquivos de texto, mas muita gente também escreve um breve texto sobre o dia toda vez que vai dormir
      Vejo o log de revisões do Git como algo parecido com esse diário
      Ele é mais organizado do que um documento TODO separado e incentiva a explicar, de forma legível, o que já foi feito
      Ao decidir até onde reverter, pode ser útil saber o que você estava pensando naquele commit; e, se a abordagem estava errada, isso também ajuda na análise posterior
      Quando um trabalho que ainda não recebeu merge nem push está organizado em mudanças separadas com objetivos definidos, fica muito mais fácil preservar só as partes boas se algumas delas forem boas e outras ruins
    • Até antes do push, tudo é local, então você pode fazer como quiser
      Ainda assim, tecnicamente commits não são sem significado
      Sem commits, não há nada para enviar por push nem para fazer merge
      E, de forma não técnica, registrar em texto claro por que algo está sendo alterado é muito útil
      Se você precisa escrever esse tipo de explicação com frequência demais, deve reduzir o escopo da unidade de “mudança” ou ser mais seletivo sobre o que mexe ao mesmo tempo
      Se não usa Git, fico curioso para saber com o que faz controle de versão e gestão de mudanças