---
title: Git interactive rebase, squash, amend e outras formas de reescrever a história
teaser: 'Aprenda as ferramentas do git que vão te permitir manter um histórico limpo,
  como usá-las e por que elas devem ser usadas com cuidado.

  '
tags: git,tools
author: Tute Costa
published_on: 2014-11-03
---

> "Por favor, faça um rebase em cima do main e nós iremos mesclar seu pull request".
>
> "Você pode agrupar seus commits para termos um histórico git limpo e reversível?".
>
> "Você pode reescrever a mensagem do seu commit para descrever melhor o
> problema que ele resolve e como o resolve?".

Questões como essas são frequentemente feitas em pull requests. Vamos ver por que elas
existem, como executá-las e seus possíveis problemas.

## Altere a última mensagem de commit com git amend

Uma das reescritas de histórico mais simples que podemos fazer com o git é alterar a
mensagem do commit mais recente. Digamos que logo após fazer um commit você encontre um erro de digitação
na descrição ou encontre uma maneira melhor de descrever o conjunto de alterações.

Para modificar a mensagem do commit, execute o comando amend:

```bash
git commit --amend
```

Ele irá [abrir um editor](https://thoughtbot.com/blog/visual-ize-the-future)
com a última mensagem de commit, para que você possa modificá-la. Depois de salvar, um novo commit
será criado com as mesmas alterações e a nova mensagem, substituindo o commit
com a mensagem anterior.

## Modificar as últimas alterações de arquivos do commit com git amend

O git amend também pode ser útil para incluir arquivos que você esqueceu de rastrear ou
modificações nos arquivos que você acabou de fazer. Para fazer isso, você pode adicionar as alterações
e depois executar o amend:

```bash
git add README.md config/routes.rb
git rm notes.txt
git commit --amend
```

Além de editar a mensagem do commit, o novo commit conterá as alterações
especificadas com os comandos `git add` e `git rm`.

Você também pode editar a autoria. Por exemplo:

```bash
git commit --amend --author="Tute Costa and Dan Croak <tute+dan@thoughtbot.com>"
```

## Modifique os arquivos do commit mais recente sem editar a mensagem do commit

Para adicionar ou remover arquivos/alterações do último
commit sem editar sua mensagem, adicione a flag `--no-edit`:

```bash
git commit -m "Update README with latest deploy changes"
git add README.md config/routes.rb
git rm notes.txt
git commit --amend --no-edit
```

O novo commit terá os arquivos alterados e manterá a mensagem anterior.

**Conquista Desbloqueada!** Agora você pode alterar o último commit do
seu repositório para incluir novas alterações nos arquivos e/ou melhorar a
mensagem do commit. Mas não comece a alterar tudo antes de entender a
última seção deste post intitulado [PERIGO].

## Modificar outras mensagens de commit

Gostaria de falar sobre isso agora, mas precisamos entender uma ferramenta mais geral
antes. Preste atenção!

Tudo será mais fácil assim que lermos sobre...

## Rebase interativo

[`git rebase`](http://git-scm.com/docs/git-rebase) reaplica commits, um por
vez, em ordem, de sua branch atual para outra. Ele aceita várias opções
e parâmetros, então essa é uma explicação básica, suficiente para preencher o
espaço entre os comentários do StackOverflow ou GitHub e as páginas de manual do git.

Uma opção interessante que ele aceita é `--interactive` (`-i`, abreviando), que irá
abrir um editor com uma lista dos commits que estão prestes a serem alterados. Esta
lista aceita comandos, permitindo que o usuário edite a lista antes de iniciar a
ação de rebase.

Vamos ver um exemplo.

<a name="rebase-example"></a>

## Modificar as últimas mensagens de commit

Vamos supor que eu queira reescrever qualquer um dos últimos 4 commits deste blog. Eu executo
`git rebase -i HEAD~4`, e aqui está o que eu vejo:

```git-rebase-todo
pick 07c5abd Introduce OpenPGP and teach basic usage
pick de9b1eb Fix PostChecker::Post#urls
pick 3e7ee36 Hey kids, stop all the highlighting
pick fa20af3 git interactive rebase, squash, amend

# Rebase 8db7e8b..fa20af3 onto 8db7e8b
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
```

Nós vemos os últimos quatro commits, do mais antigo ao mais recente. Vê o comentário abaixo da
lista de commits? Bom trabalho explicando, git!

`pick` (`p`, abreviando) é a ação padrão. Neste caso, ele reaplicaria o commit
como está, sem alterações em seu conteúdo ou mensagem. Salvar (e executar) este arquivo não
fará alterações no repositório.

Se eu digo `reword` (`r` de forma abreviada) em um commit que quero editar:

```git-rebase-todo
pick 07c5abd Introduce OpenPGP and teach basic usage
pick de9b1eb Fix PostChecker::Post#urls
r 3e7ee36 Hey kids, stop all the highlighting
pick fa20af3 git interactive rebase, squash, amend
```

Quando salvo e saio do editor, o git seguirá os comandos descritos, colocando-me
novamente no editor, como se eu tivesse feito ammend no commit `3e7ee36`. Eu edito aquela
mensagem de commit, salvo e saio do editor, e aqui está a saída:

```bash
robots.thoughtbot.com tc-git-rebase % git rebase -i HEAD~4
[detached HEAD dd62a66] Stop all the highlighting
 Author: Caleb Hearth
 Date: Fri Oct 31 10:52:26 2014 -0500
 2 files changed, 39 insertions(+), 42 deletions(-)
Successfully rebased and updated refs/heads/tc-git-rebase.
```

Agora Caleb diz em sua mensagem de commit "Stop all the highlighting", independente
de você ser uma criança ou não.

**Conquista Desbloqueada!** Você agora pode alterar a mensagem de qualquer commit que desejar. Você pode fazê-lo, apenas certifique-se de entender a seção [PERIGO].

### Agrupar commits juntos

Outros dois comandos que o rebase interativo nos oferece são:

- `squash` (`s` para abreviar), que combina o commit com o anterior (o
  da linha acima)
- `fixup` (`f` para abreviar), que funciona como "squash", mas descarta a
  mensagem deste commit

Vamos continuar trabalhando no [exemplo de rebase que trabalhamos
anteriormente](#rebase-example). Tínhamos quatro commits, o meu para este
post de blog e três outros do Caleb, relacionados ao seu post anterior sobre PGP:

```git-rebase-todo
pick 07c5abd Introduce OpenPGP and teach basic usage
pick de9b1eb Fix PostChecker::Post#urls
pick 3e7ee36 Hey kids, stop all the highlighting
pick fa20af3 git interactive rebase, squash, amend
```

Digamos que eu queira agrupar os commits do Caleb, porque eles pertencem ao
mesmo conjunto lógico de alterações, e assim podemos usar `git revert` facilmente
se decidirmos que preferimos não ter essas alterações neste repositório.

Vamos querer manter a mensagem do primeiro commit e agrupar os dois commits
subsequentes no anterior. Altero `pick` para `squash` onde apropriado:

```git-rebase-todo
pick 07c5abd Introduce OpenPGP and teach basic usage
s de9b1eb Fix PostChecker::Post#urls
s 3e7ee36 Hey kids, stop all the highlighting
pick fa20af3 git interactive rebase, squash, amend
```

Salvo, e sou levado ao editor para decidir a mensagem do commit dos três
commits agrupados (veja como eles são concatenados um após o outro):

```git-rebase-todo
# This is a combination of 3 commits.
# The first commit's message is:
Introduce OpenPGP and teach basic usage

Besides demystifying a relatively complex tool, protocol, and etiquette,
this post is intended to help with problems such as the one outlined in
this tweet:

> Emailed sensitive info to someone with PGP. They replied, with my
> original email, all in clear text. They didn't realize it.

# This is the 2nd commit message:

Fix PostChecker::Post#urls

# This is the 3rd commit message:

Hey kids, stop all the highlighting

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Author:    Caleb Hearth
# Date:      Tue Sep 2 09:39:07 2014 -0500
#
# rebase in progress; onto 71d4789
# You are currently editing a commit while rebasing branch 'tc-git-rebase' on '71d4789'.
```

Decido remover a mensagem do terceiro commit e adicionar uma nota mais relevante à
mensagem do segundo commit. Salvo no editor, e os quatro commits se transformaram
em dois: o de Caleb e o meu em seguida. Ótimo!

Poderíamos ter usado o comando `fixup`, se tivéssemos percebido anteriormente que queríamos as mudanças, mas não a mensagem do commit, do terceiro commit. Nesse caso, os
comandos seriam:

```git-rebase-todo
pick 07c5abd Introduce OpenPGP and teach basic usage
s de9b1eb Fix PostChecker::Post#urls
f 3e7ee36 Hey kids, stop all the highlighting
pick fa20af3 git interactive rebase, squash, amend
```

Ao salvar, o editor já teria incluído a mensagem do terceiro commit comentada para nós:

```git-rebase-todo
# This is a combination of 3 commits.
# The first commit's message is:
Introduce OpenPGP and teach basic usage

Besides demystifying a relatively complex tool, protocol, and etiquette,
this post is intended to help with problems such as the one outlined in
this tweet:

> Emailed sensitive info to someone with PGP. They replied, with my
> original email, all in clear text. They didn't realize it.

- https://twitter.com/csoghoian/status/505366816685060096

* Use examples that reasonably approximates `gpg2` output.
* Reject backslash from <abbr title="Uniform Resource Locator">URL</abbr>s
  before checking them

  Markdown allows backslashes in <abbr title="Uniform Resource
  Locator">URL</abbr>s to escape characters, which are passed directly to the
  <abbr title="Uniform Resource Locator">URL</abbr>, so `http://some\_url.com`
  becomes `http://some_url.com`.

  This mitigates issues with markdown syntax highlighting thinking that
  emphasized text has started, such as `_this text_`.

# This is the 2nd commit message:

Fix PostChecker::Post#urls

# The 3rd commit message will be skipped:

#     Hey kids, stop all the highlighting

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Author:    Caleb Hearth
# Date:      Tue Sep 2 09:39:07 2014 -0500
#
# rebase in progress; onto 71d4789
# You are currently editing a commit while rebasing branch 'tc-git-rebase' on
'71d4789'.
```

Salve e obtenha a saída:

```
[detached HEAD 809241b] Introduce OpenPGP and teach basic usage
 Author: Caleb Hearth
 Date: Tue Sep 2 09:39:07 2014 -0500
 2 files changed, 1429 insertions(+), 1 deletion(-)
 create mode 100644 source/posts/2014/10-31-pgp-and-you.md
Successfully rebased and updated refs/heads/tc-git-rebase.
```

O resultado é o mesmo: 2 commits em vez de 4, cada um com um post de blog único e diferente.

**Conquista Desbloqueada!** Agora você pode agrupar commits. Como
sempre, esteja ciente da seção [PERIGO].

## Rebase em cima da main

Nós fazemos um fork de uma biblioteca open source, começamos a trabalhar em uma feature branch, e novas mudanças são feitas no repositório da biblioteca (upstream). Nosso histórico se parece com:

```
       A---B---C feature
     /
D---E---F---G upstream/main
```

A pessoa mantenedora da biblioteca nos pede para "fazer rebase em cima da main", então corrigimos quaisquer conflitos de merge que possam surgir entre ambas as branches e mantemos nosso conjunto de mudanças junto.

Ela gostaria de ver um histórico como:

```
               A'--B'--C' feature
             /
D---E---F---G upstream/main
```

Queremos reaplicar nossos commits, um a um, em ordem, em cima da `main`
do upstream. Parece exatamente o que o comando `rebase` faz!

Vamos ver quais comandos nos levariam ao cenário desejado:

```bash
# Aponta nosso `upstream` remoto para o fork original
git remote add upstream https://github.com/thoughtbot/factory_bot.git

# Busca os commits mais recentes de `upstream` (o fork original)
git fetch upstream

# Faz o checkout da nossa feature branch
git checkout feature

# Reaplica-a em cima do main do upstream
git rebase upstream/main

# Corrige conflitos, depois `git rebase --continue`, repete até concluir
# Envia para o nosso fork
git push --force origin feature
```

O GitHub tem um botão [Sync Fork] na interface web para manter o repositório
local sincronizado com um repositório upstream. É um recurso útil para
contribuidores de código aberto.

**Conquista Desbloqueada!** Sua feature branch será aplicada em cima das últimas
mudanças do fork original.

E assim chegamos a...

## PERIGO: Você está reescrevendo a história do git

Viu o `--force` no último comando `git push`? Isso significa que estamos
sobrescrevendo o histórico do repositório. Isso é sempre seguro de fazer em
commits que não compartilhamos com outros membros da equipe, ou em branches que
são nossas (veja minhas iniciais no exemplo deste post de blog).

Mas se você forçar o push de edições que já foram compartilhadas com a equipe
(commits que existem fora do meu repositório, como as mudanças que fiz nos
commits do PGP que já foram compartilhadas), então a branch de todos fica
dessincronizada. É importante mantê-los atualizados sobre as mudanças.

Uma alternativa mais suave ao force push é usar [git push force with lease]
quando se trabalha com outros em uma branch. Isso permite fazer um force push
sem o risco de sobrescrever o trabalho deles inadvertidamente.

Reescrever o histórico significa abandonar commits existentes e criar novos,
que podem ser muito semelhantes, mas são diferentes. Se outros basearem trabalho
em seus commits anteriores e você reescrever e forçar o push de seus commits,
os membros da sua equipe terão que refazer o merge do trabalho deles (se
notarem a perda potencial).

Na thoughtbot, prefixamos nossas branches com nossas iniciais, sinalizando que
esses commits podem ser reescritos e outros são recomendados a [fazer rebase
do seu repositório local] frequentemente. Quando esses commits chegam ao main,
nunca os reescrevemos novamente.

Então reescreva o histórico do git e comunique-se com seus colegas de equipe
sobre como sua equipe quer manter o histórico do git limpo e reversível.

**Conquista Desbloqueada!** Agora você sabe como reescrever o histórico do git,
exercendo uma boa cidadania.

[PERIGO]: #perigo-voce-esta-reescrevendo-a-historia-do-git
[Sync Fork]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork#syncing-a-fork-branch-from-the-web-ui
[fazer rebase do seu repositório local]: https://github.com/thoughtbot/guides/tree/main/git#write-a-feature
[git push force with lease]: https://thoughtbot.com/blog/git-push-force-with-lease
