“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:
git commit --amend
Ele irá abrir um editor 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:
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:
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
:
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
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.
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:
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:
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:
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. Tínhamos quatro commits, o meu para este post de blog e três outros do Caleb, relacionados ao seu post anterior sobre PGP:
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:
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):
# 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:
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:
# 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:
# 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.