Ir para conteúdo

Branches Git divergentes: como identificar e resolver (pull, rebase, force e reset)

Código em um monitor, representando o trabalho com Git e branches

Você deu um git push, o terminal cuspiu um bloco de texto vermelho com rejected, non-fast-forward e failed to push some refs, e agora você está olhando para a tela pensando se quebrou o repositório.

Calma. Na maioria das vezes isso significa uma coisa só: sua branch local e a branch remota divergiram. Cada uma tem commits que a outra não tem. O Git não sabe qual lado deve vencer, então ele para e pede que você decida.

O problema é que a mensagem de erro não explica isso de forma amigável, e não existe um único comando certo. O que você deve rodar depende de uma pergunta simples: qual lado tem o trabalho que vale a pena manter?

Neste guia você vai aprender a:

  • entender o que “divergir” significa, com desenhos;
  • reconhecer a divergência por três caminhos diferentes (status, push e pull);
  • escolher o comando certo para cada cenário, com os riscos de cada um;
  • evitar que isso aconteça toda semana no seu fluxo de trabalho.

Se você ainda está pegando o jeito do básico, vale passar antes pelo verbete o que é Git e voltar aqui.

O que significa “divergir”

Pense nas suas branches como linhas de commits. Entre a sua branch local main e a remota origin/main, existem quatro situações possíveis.

1. Em dia. As duas apontam para o mesmo commit. Não há nada a fazer.

a - b - c - d
            ^ LOCAL
            ^ REMOTO

2. Local atrasada. Alguém (ou você, em outra máquina) enviou commits que você ainda não baixou. A solução costuma ser um simples git pull.

a - b - c - d - e
            ^ LOCAL
                ^ REMOTO

3. Remoto atrasado. Você tem commits novos que ainda não subiram. A solução costuma ser um simples git push.

a - b - c - d - e
            ^ REMOTO
                ^ LOCAL

4. Divergentes. Os dois lados avançaram a partir de um ponto em comum, cada um para um canto. É aqui que o Git trava.

a - b - c - d - e   (LOCAL)
        \
         f          (REMOTO)

No desenho, d é o último commit que os dois compartilham. Depois dele, o local tem o e e o remoto tem o f. Nenhum dos dois é “ancestral” do outro, então o Git não consegue simplesmente avançar um ponteiro (o famoso fast-forward).

SituaçãoO que fazer
Em diaNada
Local atrasadagit pull
Remoto atrasadogit push
DivergentesDecidir qual lado manter (o resto deste guia)

Como cair nessa situação

Os cenários mais comuns de quem trabalha sozinho ou em equipe pequena:

  • Você commitou direto na main e, enquanto isso, um colega também enviou algo para a main.
  • Você fez um git commit --amend ou um rebase em commits que já tinham sido enviados.
  • Você trabalha em duas máquinas (notebook e desktop) e esqueceu de dar pull antes de começar.
  • Uma ferramenta automática (bot de dependências, CI, deploy) fez um commit no remoto sem você saber.

Em projetos de site, o último caso é frequente. Um deploy automático que altera um arquivo de versão ou um package-lock.json já basta para fazer o remoto andar sozinho.

Como reconhecer que as branches divergiram

Existem três caminhos. O mais confiável é o primeiro.

Caminho 1: git fetch + git status

O fetch atualiza o que o seu Git sabe sobre o remoto, sem mexer nos seus arquivos. Depois o status compara os dois lados.

git fetch
git status

Se estiverem divergentes, você verá algo assim:

On branch main
Your branch and 'origin/main' have diverged,
and have 1 and 2 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

Traduzindo: sua branch tem 1 commit que o remoto não tem, e o remoto tem 2 commits que você não tem.

Caminho 2: o erro no git push

$ git push
 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to 'github.com:usuario/projeto'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.

Cuidado: esse erro nem sempre significa divergência. Ele também aparece quando sua branch só está atrasada. Na dúvida, rode git fetch e git status para confirmar.

Caminho 3: o erro no git pull

$ git pull
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint:   git config pull.rebase false  # merge
hint:   git config pull.rebase true   # rebase
hint:   git config pull.ff only       # fast-forward only
fatal: Need to specify how to reconcile divergent branches.

Esse é o mais claro dos três: ele diz literalmente que você tem branches divergentes. Só que ele depende da sua configuração. Se você já definiu pull.rebase, o Git não pergunta nada e faz a escolha por você:

ConfiguraçãoO que o git pull faz sozinho
pull.rebase falseInicia um merge com o remoto
pull.rebase trueFaz rebase da sua branch sobre o remoto
pull.ff onlyAborta com Not possible to fast-forward

Como visualizar a divergência antes de agir

Antes de rodar qualquer comando que reescreve histórico, olhe o que cada lado tem. Dois comandos resolvem.

Para ver um grafo compacto dos dois lados:

git log --oneline --graph --decorate main origin/main

Para listar só os commits que existem de um lado e não do outro:

# commits que só você tem (ainda não foram enviados)
git log --oneline origin/main..main

# commits que só o remoto tem (você ainda não baixou)
git log --oneline main..origin/main

Dois minutos olhando isso evitam a maioria dos desastres. Se a lista do remoto tiver commits de outra pessoa, você já sabe que --force está fora de questão.

Não existe solução única: escolha pelo cenário

Em vez de decorar comandos, responda esta pergunta: qual lado tem trabalho útil?

CenárioComando
Quero manter os dois ladosgit pull --rebase (ou --no-rebase)
O trabalho remoto é inútilgit push --force-with-lease
O meu trabalho local é inútilgit reset --hard origin/main

Vamos ver cada um com calma.

Solução 1: manter os dois lados

Opção A: git pull --rebase

O rebase pega os seus commits locais, “levanta” eles do ponto onde divergiram e os reaplica por cima do que veio do remoto. O histórico fica linear, como se você tivesse começado seu trabalho depois do dos outros.

git pull --rebase

Antes e depois:

Antes:                 Depois:
a - b - c - d - e      a - b - c - d - f - e'
        \                              ^ REMOTO   ^ LOCAL
         f

Repare que o e virou e'. Ele tem o mesmo conteúdo, mas é um commit novo, com outro identificador. Por isso o rebase é seguro apenas para commits que ainda não foram compartilhados.

Se houver conflito, o Git para no meio e pede que você resolva. O fluxo é:

# 1. abra os arquivos em conflito, resolva e salve
# 2. marque como resolvido
git add arquivo-resolvido.php
# 3. continue o rebase
git rebase --continue

# se quiser desistir e voltar ao estado anterior
git rebase --abort

Ótimo para quando você trabalha direto na main de um projeto pessoal ou em uma branch só sua.

Opção B: git pull --no-rebase (merge)

O merge cria um commit extra que une as duas linhas, preservando o histórico exatamente como aconteceu.

git pull --no-rebase

O Git abre o editor para você confirmar a mensagem do commit de merge. Salve e feche. O resultado:

a - b - c - d - e - M
        \         /
         f ------

O histórico fica mais “ramificado”, mas nenhum commit é reescrito. Muitas equipes preferem isso justamente por ser mais seguro e fiel ao que aconteceu.

RebaseMerge
Histórico linear e limpoHistórico fiel, com commit de união
Reescreve seus commits locaisNão reescreve nada

Qual escolher é decisão de fluxo de trabalho do time. Há bons argumentos dos dois lados.

Solução 2: sobrescrever o remoto

Às vezes o trabalho que está no remoto é lixo e você quer que ele fique igual ao seu local. O caso clássico, em repositório pessoal:

  1. Você deu git push.
  2. Percebeu um erro no último commit.
  3. Corrigiu e rodou git commit --amend.
  4. Agora local e remoto divergiram, porque o commit antigo foi substituído por outro.

Aqui você precisa forçar o envio.

git push --force-with-lease (prefira este)

git push --force-with-lease

Ele só sobrescreve o remoto se ninguém tiver mexido nele desde a última vez que você o buscou. Se alguém enviou algo no meio tempo, o push é recusado e você não apaga o trabalho alheio sem querer.

Um detalhe que pouca gente sabe: se seu editor faz git fetch automático em segundo plano (o VS Code faz isso, por exemplo), o Git passa a achar que você “já viu” os commits novos e o --force-with-lease perde a proteção. Para fechar essa brecha existe a opção extra:

git push --force-with-lease --force-if-includes

Ela confere no reflog se você realmente integrou o que veio do remoto antes de forçar.

git push --force (use com muito cuidado)

git push --force

Sobrescreve o remoto sem perguntar nada. Em um repositório só seu, tudo bem. Em repositório compartilhado, pode apagar o trabalho de outras pessoas e causar um belo dia ruim para o time.

Em projetos com mais de uma pessoa, ative a proteção de branch no GitHub ou GitLab para a main. Assim o push forçado fica impossível, mesmo por engano.

Solução 3: descartar o seu lado

O inverso: o que está no remoto é o que vale, e o que você fez localmente deve ser jogado fora. Como não existe git pull --force, usamos o reset.

Um caso muito comum: você percebeu que commitou na main algo que deveria estar em outra branch.

git checkout main

# 1. guarde o seu trabalho em uma branch nova, para não perder nada
git checkout -b minha-nova-branch

# 2. volte para a main
git checkout main

# 3. atualize o que o Git sabe sobre o remoto
git fetch

# 4. confira se não há nada sem commitar (o reset vai apagar isso)
git status

# 5. force a main local a ficar igual à remota
git reset --hard origin/main

Note o passo 1: em vez de jogar o trabalho fora, você o guarda numa branch. Assim você pode decidir com calma depois o que fazer com ele. Essa estratégia também ajuda quando você não sabe ainda como resolver o conflito, porque é bem mais fácil mesclar duas branches locais do que lidar com uma remota.

Atenção ao --hard. Ele apaga alterações não commitadas de forma definitiva. Sempre rode git status antes.

Se quiser alternativas ao reset --hard, a partir de outra branch você pode usar:

git branch -f main origin/main
# ou
git fetch origin main:main --force

Tabela de comandos de socorro

SituaçãoComando
Desfazer um rebase em andamentogit rebase --abort
Desfazer um merge em andamentogit merge --abort

E se você fez um reset ou rebase errado e quer voltar? O git reflog guarda o histórico de onde sua branch esteve, mesmo para commits “perdidos”:

git reflog
git reset --hard HEAD@{2}   # volta para o estado de dois movimentos atrás

Por isso, na maior parte das vezes, “perdi meu commit” não é verdade. Ele só está fora do alcance da branch.

Como configurar o Git para parar de reclamar

Se você já sabe qual estratégia prefere, pode definir o padrão e nunca mais ver aquele aviso:

# sempre fazer rebase ao dar pull
git config --global pull.rebase true

# ou sempre fazer merge
git config --global pull.rebase false

# ou nunca criar merge automático (só aceita fast-forward)
git config --global pull.ff only

Minha opinião honesta: o pull.ff only é o mais seguro para quem está começando. Ele recusa qualquer divergência e te obriga a olhar o problema, em vez de decidir por você. Para quem já tem domínio e trabalha em branches pessoais, pull.rebase true economiza tempo.

Como evitar divergências no dia a dia

Resolver é bom, mas não ter o problema é melhor:

  • Dê git pull antes de começar a trabalhar e antes de dar push.
  • Trabalhe em branches de funcionalidade, não direto na main. Divergência em branch que só você usa é fácil de resolver.
  • Não reescreva commits já enviados (amend, rebase, reset) em branches compartilhadas.
  • Proteja a main exigindo pull request e bloqueando force push.
  • Commits pequenos e frequentes geram conflitos menores e mais fáceis de resolver.
  • Saiba o que seus bots fazem. Se um deploy automático ou o Dependabot commita no repositório, o remoto vai andar sozinho.

E quando isso envolve o deploy do site?

Se você mantém um site com deploy via Git (Astro, Next.js, WordPress com tema versionado), divergir na branch errada pode ter consequência real: um --force na main que dispara um deploy com código quebrado, ou um reset que apaga uma correção que já estava em produção.

Algumas boas práticas que uso nos projetos:

  • Nunca fazer force push na branch que dispara deploy em produção.
  • Testar mudanças em um ambiente de staging antes de levar para a branch principal.
  • Versionar o tema customizado do WordPress, como mostrei no guia de como criar um tema WordPress do zero, para que qualquer alteração tenha histórico e possa ser revertida.

Perguntas frequentes

O que significa “divergent branches” no Git?

Significa que a branch local e a remota têm commits diferentes a partir de um ponto em comum. Cada uma avançou por um caminho, e o Git não consegue juntar as duas sozinho sem você escolher entre merge, rebase ou sobrescrever um dos lados.

O que significa o erro “non-fast-forward”?

É o erro do git push quando o remoto tem commits que você não tem. O Git recusa o envio para não apagar esse trabalho. Pode ser divergência de verdade ou apenas sua branch atrasada, por isso vale rodar git fetch e git status.

Devo usar merge ou rebase para resolver?

Depende do seu fluxo. Rebase deixa o histórico linear, mas reescreve seus commits locais. Merge preserva tudo como aconteceu, ao custo de um commit extra. Em branch compartilhada, o merge é mais seguro. Em branch pessoal, o rebase costuma ser mais limpo.

É seguro usar git push --force?

Só em repositório onde você é a única pessoa que envia código. Em repositórios compartilhados, prefira --force-with-lease e ative a proteção de branch na main.

Perdi commits depois de um reset. Dá para recuperar?

Quase sempre sim. Rode git reflog, ache o estado anterior e volte com git reset --hard HEAD@{n} ou crie uma branch a partir dele. Já alterações que nunca foram commitadas e foram apagadas pelo reset --hard não voltam.

Qual a diferença entre git fetch e git pull?

O fetch só baixa as informações do remoto, sem alterar seus arquivos. O pull é um fetch seguido de uma integração (merge ou rebase). Quando há dúvida, comece pelo fetch: ele é inofensivo.

Como configurar o Git para sempre fazer rebase no pull?

Com git config --global pull.rebase true. Se preferir merge, use false. Para aceitar apenas fast-forward e ser avisado de qualquer divergência, use git config --global pull.ff only.

Resumo rápido

  1. Rode git fetch e git status para confirmar a divergência.
  2. Use git log --oneline --graph --decorate main origin/main para ver quem tem o quê.
  3. Quer manter os dois lados? git pull --rebase ou git pull --no-rebase.
  4. O remoto é inútil? git push --force-with-lease.
  5. O local é inútil? Salve numa branch e rode git reset --hard origin/main.
  6. Errou? git reflog te salva.

Conclusão

Branches divergentes assustam porque a mensagem de erro é técnica, mas o raciocínio é simples: dois lados avançaram e você precisa dizer ao Git qual vale. Com o status você confirma, com o log você enxerga, e com a pergunta “qual lado tem o trabalho útil?” você escolhe o comando certo.

Se o seu projeto vive de deploy por Git e você já perdeu horas (ou um fim de semana) com conflito, branch quebrada ou produção fora do ar por causa de um push errado, isso é sintoma de fluxo mal estruturado, não de azar. Posso ajudar a organizar isso: desenvolvimento freelancer para estruturar repositório, branches e deploy, e manutenção de sites WordPress para manter tema e plugins versionados e atualizados com segurança.

Referências

Leia também

Artigos Relacionados