roselletabuena/reescritura-segura-de-histórico-git

Reescreva o histórico do git sem perder trabalho ou atrapalhar outras pessoas - amend, reset, squash, reordenar, remover ou editar commits, alterar uma mensagem de commit, remover um commit de uma branch ou fazer force push.

O que é reescritura-segura-de-histórico-git?

Esta skill automatiza a reescrita segura do histórico do Git, garantindo que operações como `commit --amend`, `reset`, `rebase`, `squash` e `force push` sejam realizadas sem perda de trabalho e sem conflitos com outros desenvolvedores. Antes de qualquer ação, ela avalia se a reescrita é apropriada: commits já puxados por outros devem ser revertidos, não apagados. A skill cria automaticamente um ponto de recuperação (backup branch) antes de qualquer modificação, evitando o uso de editores interativos que travam em shells não interativos, e publica as alterações com `--force-with-lease` em vez de `--force` puro. Suportada em qualquer ambiente que execute comandos Git (CLI, CI/CD, ferramentas de IA como Claude Code, Cursor, Codex), ela é indicada sempre que o usuário pedir para 'desfazer o último commit', 'remover um commit', 'limpar commits antes do PR', 'fazer squash', 'amend' ou 'force push'. A skill também fornece guardrails importantes: não reescreve commits assinados sem re-assinar, não reescreve commits de outros sem permissão, e orienta sobre branches empilhadas e segredos comprometidos.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/roselletabuena/skills/tree/HEAD/plugins/git-workflows/skills/git-safe-history-rewrite

Installed? Explore more Programação e Desenvolvimento skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Reescrevendo o histórico com segurança

Toda reescrita - commit --amend, reset, rebase, filter-repo - produz novos commits e move o ponteiro da branch para longe dos antigos. Isso é inofensivo enquanto ninguém mais tiver os commits antigos, e destrutivo no momento em que alguém os tem: o clone dessa pessoa ainda tem o histórico antigo, e na próxima vez que ela fizer push ou pull, ele volta como duplicatas ou conflitos. Portanto, a primeira pergunta nunca é "como", é "quem mais tem esses commits?"

1. Decida: reescrever ou reverter

git fetch --prune
git status --short --branch                  # ahead/behind, e estado sujo
git branch -r --contains <commit>            # quais branches remotas já o contêm
git log --oneline @{u}..HEAD                 # commits que existem apenas localmente
Onde o commit resideFaça isto
Apenas local, nunca enviadoReescreva à vontade.
Enviado para uma branch na qual apenas este usuário trabalha (branch de PR dele)Reescreva, depois faça push com lease (passo 5).
Em uma branch compartilhada ou protegida: main, master, develop, release/*, ou qualquer coisa com commits de outras pessoas em cimaNão reescreva. Use git revert.
Já lançado ou implantadogit revert. Uma reescrita não pode desfazer a implantação.

Quando o usuário diz "livre-se daquele commit da main", ele geralmente quer que seu efeito suma, não seu SHA. git revert <sha> faz isso, mantém o histórico honesto e precisa apenas de um push comum. Para um commit de merge, git revert -m 1 <merge-sha>. Explique por que escolheu reverter, e se o usuário insistir em apagá-lo de uma branch compartilhada, nomeie quem será afetado e obtenha um sim explícito primeiro.

Um revert gera conflitos quando commits posteriores editaram as mesmas linhas - que é exatamente o caso em que commits foram construídos em cima do ruim. Resolva como qualquer conflito (veja git-conflict-resolution): remova a alteração ruim, mantenha o trabalho posterior, depois git revert --continue.

Uma armadilha do revert: depois de reverter um merge, mesclar a mesma branch novamente não traz nada de volta, porque o git acha que esses commits já chegaram. Para trazer o trabalho de volta, reverta o revert primeiro.

2. Crie um ponto de recuperação antes de tocar em qualquer coisa

git rev-parse HEAD                           # anote
git branch backup/<branch>-pre-rewrite       # uma ref real, não apenas o reflog

O reflog também registra onde a branch estava, mas é local a este clone, expira (90 dias para entradas alcançáveis, 30 para inalcançáveis por padrão), e git gc após reflog expire o encerra. Uma branch de backup nomeada não custa nada. Deixe-a no lugar até que o usuário tenha confirmado o resultado.

3. Escolha o comando menos destrutivo

ObjetivoComandoNotas
Corrigir a mensagem do último commit ou adicionar um arquivo esquecidogit commit --amend-m "..." ou --no-edit evita abrir um editor.
Desfazer commit mas manter as mudanças stagedgit reset --soft HEAD~1
Desfazer commit e unstaged, manter os arquivosgit reset --mixed HEAD~1 (o padrão)
Mover uma branch para trás, recusando perder edições locaisgit reset --keep <commit>Aborta se sobrescreveria mudanças não commitadas. Prefira isto a --hard.
Jogar fora também o trabalho não commitadogit reset --hardO trabalho não commitado não está no reflog e não pode ser recuperado. Só após confirmar que nada ali é desejado; git stash -u antes se não tiver certeza.
Descartar alterações em um arquivogit restore <file>
Squash ou agrupar correções em commits anterioresgit commit --fixup=<sha> e depois autosquashVeja abaixo.
Remover ou reordenar commitsrebase -i com um todo fornecidoVeja abaixo.

Um hook de pre-commit falho significa que nenhum commit foi feito. Executar git commit --amend para "tentar novamente" modifica o commit anterior, mesclando as novas alterações no errado. Após uma falha de hook, corrija o problema, re-faça o stage e faça um novo commit normal.

Rebase interativo sem um editor interativo

git rebase -i abre um editor, que trava em um shell não interativo. Não confie nele. Duas maneiras que funcionam:

# Agrupar commits de correção em seus alvos - aceite o todo que o git gera:
git commit --fixup=<target-sha>
GIT_SEQUENCE_EDITOR=true git rebase -i --autosquash <base>

# Remover ou reordenar: escreva o todo você mesmo.
GIT_SEQUENCE_EDITOR="sed -i '/^pick <short-sha> /d'" git rebase -i <base>

<base> é o commit anterior ao primeiro que você deseja alterar. Se um rebase parar em um conflito, resolva-o, git add, depois GIT_EDITOR=true git rebase --continue. Se der errado, git rebase --abort retorna ao ponto de partida.

4. Verifique se a reescrita alterou o que você pretendia

Uma edição de mensagem, reordenação ou squash deve deixar a árvore final idêntica:

git diff backup/<branch>-pre-rewrite HEAD --stat      # vazio = mesmo conteúdo
git range-diff backup/<branch>-pre-rewrite...HEAD     # comparação commit por commit

Se você pretendia remover conteúdo, o diff deve mostrar exatamente esse conteúdo e nada mais. Qualquer coisa inesperada significa restaurar a partir do backup e refazer.

5. Publique - apenas com a autorização do usuário

Enviar uma reescrita é o passo voltado para o exterior e difícil de reverter. Confirme primeiro, a menos que o usuário já tenha pedido o push.

git push --force-with-lease=<branch>:<expected-remote-sha> --force-if-includes origin <branch>
  • Nunca --force puro. Ele sobrescreve o que estiver no remoto, incluindo um push de colega que você não viu.
  • Nomeie o SHA esperado. Um --force-with-lease simples compara contra sua ref de rastreamento remoto, e um git fetch de fundo do IDE atualiza silenciosamente essa ref, então o lease não protege nada. Use o SHA que você viu no passo 1 (git rev-parse origin/<branch>).
  • --force-if-includes (git 2.30+) adicionalmente recusa se o remoto moveu para algo que sua branch local nunca integrou.
  • Nunca faça force push em main/master/develop/release/*. Se uma regra de hospedagem bloquear, essa regra está correta; não a contorne.

Após o push, informe ao usuário que qualquer um com a branch antiga em checkout deve fazer reset para a nova em vez de pull, e que os comentários de revisão de um PR aberto podem perder suas âncoras.

Guardrails

  • Commits assinados perdem suas assinaturas ao serem reescritos; re-assine com -S ou avise o usuário.
  • Não reescreva commits de outra pessoa sem perguntar, mesmo em uma branch de funcionalidade.
  • Branches empilhadas acima da reescrita agora estão desatualizadas. Use git-stacked-pr-rebase para reempilhá-las em vez de fazer rebase de cada uma manualmente.
  • Um segredo commitado não é corrigido por uma reescrita. Rode a credencial primeiro; assuma que está comprometida.
  • Relate claramente: qual era a ponta antiga, qual é a nova, onde está a ref de backup, e se algo não foi enviado. Se revert foi a escolha certa, diga que foi o que você fez.

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

Habilidades Relacionadas