Meu guia para usar o comando Git push com segurança
Entenda o uso e o impacto desse comando popular do Git em seu projeto, aprenda novas alternativas mais seguras e domine as habilidades para restaurar um branch quebrado.
A maioria sabe que usar o comando push --force
do Git é fortemente desencorajado e considerado destrutivo.
Porém, para mim, parecia muito estranho colocar toda a minha confiança no Git com meus projetos e ao mesmo tempo evitar completamente o uso de um de seus comandos populares.
Isso me levou a pesquisar por que as pessoas consideram esse comando tão prejudicial.
Por que isso existe em primeiro lugar? E o que acontece nos bastidores?
Neste artigo, compartilho minhas descobertas para que você também possa entender o uso e o impacto desse comando em seu projeto, aprender novas alternativas mais seguras e dominar as habilidades para restaurar um galho quebrado. Você pode se surpreender com a forma como a força
está realmente com você.
O comando git push
Para entender como o Git funciona, precisamos dar um passo atrás e examinar como o Git armazena seus dados. Para o Git tudo gira em torno de commits. Um commit é um objeto que inclui várias chaves, como um ID exclusivo, um ponteiro para o instantâneo do conteúdo preparado e ponteiros para os commits que vieram diretamente antes desse commit.
Um branch, aliás, nada mais é do que um ponteiro para um único commit.
O que o git push
faz é basicamente:
1. Copia todos os commits existentes na filial local.
2. Integra os históricos encaminhando a ramificação remota para referenciar o novo commit, também chamado de Fast forward ref.
Referência de avanço rápido
O avanço rápido é simplesmente encaminhar a referência de commit atual do branch. O Git procura automaticamente um caminho linear da referência atual até a referência de commit de destino quando você envia suas alterações.
Se um commit ancestral existir no remoto e não no local (alguém atualizou o remoto e as coisas ainda não foram atualizadas localmente), o Git não encontrará um caminho linear entre os commits e o git push
falhará .
(Noaa Barki, CC BY-SA 4.0)
Quando usar o --force
Você pode usar git rebase
, git squash
e git commit --amend
para alterar o histórico de commits e reescrever commits enviados anteriormente. Mas estejam avisados, meus amigos, que esses comandos poderosos não alteram apenas os commits – eles substituem todos os commits, criando outros inteiramente novos.
Um simples git push
falha e você deve ignorar a regra de "avanço rápido".
Digite --force
.
Esta opção substitui a restrição de "avanço rápido" e combina nossa filial local com a filial remota. A sinalização --force
permite que você ordene que o Git faça isso de qualquer maneira.
Quando você altera o histórico ou deseja enviar alterações inconsistentes com a ramificação remota, você pode usar push --force
.
(Noaa Barki, CC BY-SA 4.0)
Cenário simples
Imagine que Lilly e Bob sejam desenvolvedores trabalhando no mesmo ramo de recursos. Lilly completou suas tarefas e promoveu suas alterações. Depois de um tempo, Bob também terminou seu trabalho, mas antes de fazer as alterações, ele percebeu algumas alterações adicionais. Ele executou um rebase
para manter a árvore limpa e depois usou push --force
para transferir as alterações para o controle remoto. Infelizmente, não sendo atualizado para a filial remota, Bob apagou acidentalmente todos os registros das alterações de Lilly.
(Noaa Barki, CC BY-SA 4.0)
Bob cometeu um erro comum ao usar a opção --force
. Bob esqueceu de atualizar (git pull
) seu branch local rastreado. Com um branch que o usuário ainda não atualizou, o uso de --force
fez com que o Git enviasse as alterações de Bob sem levar em conta o estado do branch rastreado remotamente, de modo que os commits eram perdidos. Claro, você não perdeu tudo e a equipe pode tomar medidas para se recuperar, mas se não for pego, esse erro pode causar muitos problemas.
Alternativa: push --force-with-lease
A opção --force
tem um parente não tão famoso chamado --force-with-lease
, que permite push --force
suas alterações com a garantia de que você não substituirá as alterações de outra pessoa. Por padrão, --force-with-lease
se recusa a atualizar o branch, a menos que o branch de rastreamento remoto e o branch remoto apontem para a mesma referência de commit. Muito bom, certo? Fica melhor.
Você pode especificar --force-with-lease
exatamente com qual commit, branch ou ref comparar. A opção --force-with-lease
oferece a flexibilidade de substituir novos commits em sua filial remota enquanto protege seu antigo histórico de commits. É a mesma força, mas com colete salva-vidas.
Guia: Como lidar com `--force` destrutivo
Você é, sem dúvida, um desenvolvedor responsável, mas aposto que pelo menos uma vez aconteceu com você ou um de seus colegas de equipe acidentalmente executou git push --force
em um branch importante que ninguém deveria mexer com. Num piscar de olhos, o trabalho mais recente de todos se perde.
Não há necessidade de entrar em pânico! Se você tiver muita sorte, alguém trabalhando no mesmo código extraiu uma versão recente do branch pouco antes de você quebrá-lo. Nesse caso, tudo o que você precisa fazer é pedir a eles --force push
as alterações recentes.
Mas mesmo que você não tenha essa sorte, ainda terá a sorte de encontrar este artigo.
1. Você foi a última pessoa a empurrar antes do erro?
Primeiro, NÃO feche seu terminal.
Segundo, vá até seus colegas de equipe e confesse seus pecados.
Por fim, certifique-se de que ninguém mexa no repositório nos próximos minutos, pois você tem muito trabalho a fazer.
Volte para sua estação. Na saída do comando git push --force
em seu terminal, procure a linha semelhante a esta:
+ d02c26f…f00f00ba [branchName] -> [branchName] (forced update)
O primeiro grupo de símbolos (que se parece com um prefixo SHA de commit) é a chave para corrigir isso.
Suponha que seu último commit válido para o branch antes de você infligir danos tenha sido d02c26f
. Sua única opção é combater fogo com fogo e push --force
este commit de volta para o branch em cima do branch ruim:
$ git push — force origin deadbeef:[branchName]
Parabéns! Você salvou o dia!
2. Usei acidentalmente `--force push` em meu repositório e quero voltar para a versão anterior. O que eu faço?
Imagine trabalhar em um branch de recursos. Você fez algumas alterações, criou alguns commits, concluiu sua parte do recurso e enviou suas alterações para o repositório principal. Em seguida, você comprimiu os commits em um só, usando git rebase -i
e fez push novamente usando push --force
. Mas algo ruim aconteceu e você deseja restaurar seu branch ao que era antes do rebase -i
. A melhor coisa sobre o Git é que ele é o melhor em nunca perder dados, então a versão do repositório antes do rebase
ainda está disponível.
Nesse caso, você pode usar o comando git reflog
, que gera um histórico detalhado do repositório.
Para cada “atualização” feita em seu repositório local, o Git cria uma entrada de log de referência. O comando git reflog
gera esses reflogs
, armazenados em seu repositório Git local. A saída de git reflog
contém todas as ações que alteraram as dicas de ramificações e outras referências no repositório local, incluindo a troca de ramificações e rebases
. A ponta do ramo (chamada HEAD) é uma referência simbólica ao ramo atualmente ativo. É apenas uma referência simbólica, pois um branch é um ponteiro para um commit.
Aqui está um reflog
simples que mostra o cenário que descrevi acima:
1b46bfc65e (HEAD -> test-branch) HEAD @ {0} : rebase -i (finish): returning to refs/heads/test-branch
b46bfc65e (HEAD -> test-branch) HEAD @ {1}: rebase -i (squash): a
dd7906a87 HEAD @ {2} : rebase -i (squash): # This is a combination of 2 commits.
a3030290a HEADC {3}: rebase -i (start): checkout refs/heads/master
Oc2d866ab HEAD@{4}: commit: c
6cab968c7 HEAD@ {5} : commit: b
a3030290a HEAD @ {6}: commit: a
c9c495792 (origin/master, origin/HEAD, master) HEAD@ {7}: checkout: moving from master to test-branch
c9c495792 (origin/master, origin/HEAD, master) HEAD@ {8} : pull: Fast-forward
A notação HEAD@{number}
é a posição de HEAD no número
das alterações anteriores. Portanto, HEAD@{0}
é o HEAD onde HEAD está agora e HEAD@{4}
é HEAD quatro etapas atrás. Você pode ver no reflog
acima que HEAD@{4}
é onde você precisa ir para restaurar o branch para onde estava antes do rebase, e 0c2d866ab
é o ID do commit desse commit.
Então, para restaurar o branch de teste para o estado desejado, você redefine o branch:
$ git reset — hard HEAD@{4}
Então você pode forçar o push novamente para restaurar o repositório para onde estava antes.
Recuperação geral
Sempre que você quiser restaurar sua branch para a versão anterior depois de push --force
, siga este modelo de solução de recuperação geral:
1. Obtenha o commit anterior usando o terminal.
2. Crie uma ramificação ou redefina para o commit anterior.
3. Use push --force
.
Se você criou um novo branch, não esqueça de resetar o branch, para que ele seja sincronizado com o remoto executando o seguinte comando:
$ git reset --hard origin/[new-branch-name]
3. Restaure o branch excluído push --force com git fsck
Suponha que você possua um repositório.
Você teve um desenvolvedor que escreveu o projeto para você.
O desenvolvedor decidiu excluir todas as ramificações e push --force
um commit com a mensagem "O projeto estava aqui".
O desenvolvedor saiu do país sem como contatá-los ou encontrá-los. Você não tem código e nunca clonou o repositório.
A primeira coisa a fazer é encontrar um commit anterior.
Infelizmente, neste caso, usar git log
não ajudará porque o único commit para o qual a ramificação aponta é "O projeto estava aqui" sem nenhum commit relacionado. Nesse caso, você deve encontrar commits excluídos aos quais nenhum commit filho, branch, tag ou outra referência tenha vinculado. Felizmente, o banco de dados Git armazena esses commits órfãos, e você pode encontrá-los usando o poderoso comando git fsck
.
git fsck --lost-found
Eles chamam esses commits de "commits pendentes". De acordo com a documentação, o simples git gc
remove commits pendentes há duas semanas. Nesse caso, tudo o que você tem são commits pendentes e tudo o que resta fazer é encontrar o commit anterior antes dos danos e seguir as etapas gerais de recuperação acima.
Ramos protegidos e revisões de código
Como diz o velho ditado:
“A diferença entre uma pessoa inteligente e uma pessoa inteligente é que uma pessoa inteligente sabe como se livrar de problemas nos quais uma pessoa inteligente não teria se metido.”
Se você deseja evitar completamente o push --force
, tanto o GitHub quanto o GitLab oferecem um recurso muito interessante chamado Protected Branches, que permite marcar qualquer branch como protegido para que ninguém possa push -- force
para isso.
Você também pode definir preferências de administrador para restringir permissões.
Como alternativa, você pode instituir ganchos do Git para exigir revisões ou aprovação do código antes que alguém possa enviar o código para uma ramificação importante.
Resumo
Esperamos que agora você entenda quando precisa adicionar a opção --force
e os riscos envolvidos ao usá-la. Lembre-se de que --force
está à sua disposição. É apenas um desvio e, como todo desvio, você deve usá-lo com cuidado.
Que a --force
esteja com você.
Este artigo foi adaptado do artigo do autor no Medium e republicado com permissão.