Git Ajustar Commits: Um Guia Com Exemplos

“Commit cedo, commit frequentemente” é um mantra popular no desenvolvimento de software ao usar Git. Fazendo isso, garante-se que cada mudança está bem documentada, melhora a colaboração e facilita o acompanhamento da evolução do projeto. No entanto, isso também pode levar a uma abundância excessiva de commits.

É aí que entra a importância de mesclar commits. Mesclar commits é o processo de combinar várias entradas de commit em um único commit coeso.

Por exemplo, vamos dizer que estamos trabalhando em um recurso implementando um formulário de login e criamos os seguintes quatro commits:

Depois que o recurso é concluído, para o projeto como um todo, esses commits são muito detalhados. Não precisamos saber no futuro que encontramos um bug que foi corrigido durante o desenvolvimento. Para garantir um histórico limpo no branch principal, mesclamos esses commits em um único commit:

Como Mesclar Commits no Git: Rebase Interativo

O método mais comum para mesclar commits é usar um rebase interativo. Iniciamos isso usando o comando:

git rebase -i HEAD~<number_of_commits>

Substitua <number_of_commits> pelo número de commits que queremos mesclar.

No nosso caso, temos quatro commits, então o comando é:

git rebase -i HEAD~4

Executar este comando abrirá um editor de linha de comando interativo:

A seção superior exibe os commits, enquanto a parte inferior contém comentários sobre como mesclar commits.

Observamos quatro commits. Para cada um, devemos decidir qual comando executar. nos importamos com os comandos pick (p) e squash (s). Para mesclar esses quatro commits em um único commit, podemos escolher o primeiro e mesclar os outros três.

Aplicamos os comandos modificando o texto precedente a cada commit, especificamente alterando pick para s ou squash para os segundos, terceiros e quartos commits. Para fazer essas edições, precisamos entrar no modo “INSERT” do editor de texto de linha de comando pressionando a tecla i no teclado:

Após pressionar i, o texto -- INSERT -- aparecerá na parte inferior, indicando que entramos no modo de inserção. Agora, podemos mover o cursor com as teclas de seta, deletar caracteres e digitar como faríamos em um editor de texto padrão:

Una vez satisfeitos com as mudanças, precisamos sair do modo de inserção pressionando a tecla Esc no teclado. O próximo passo é salvar nossas mudanças e sair do editor. Para fazer isso, primeiramente pressionamos a tecla : para sinalizar ao editor que pretendemos executar um comando:

No fundo do editor, agora vemos um ponto e vírgula : prompting-nos a inserir um comando. Para salvar as mudanças, usamos o comando w, que significa “escrever”. Para fechar o editor, use q, que significa “sair”. Esses comandos podem ser combinados e digitados juntos wq:

Para executar o comando, pressionamos a tecla Enter. Esta ação fechará o editor atual e abrirá um novo, permitindo-nos inserir a mensagem de commit para o commit recém mesclado. O editor exibirá uma mensagem padrão que compreende as mensagens dos quatro commits que estamos mesclando:

Recomendo modificar a mensagem para refletir com precisão as mudanças implementadas por esses commits combinados — afinal, o objetivo de mesclar é manter um histórico limpo e facilmente legível.

Para interagir com o editor e editar a mensagem, pressionamos i novamente para entrar no modo de edição e editamos a mensagem conforme desejamos.

Neste caso, substituímos a mensagem do commit por “Implementar formulário de login.” Para sair do modo de edição, pressionamos Esc. Em seguida, salvamos as mudanças pressionando :, digitando o comando wq e pressesionando Enter.

Como Ver o Histórico de Commits

Normalmente, lembrar todo o histórico de commits pode ser desafiador. Para visualizar o histórico de commits, podemos usar o comando git log. No exemplo mencionado, antes de realizar o squash, executar o comando git log mostraria:

Para navegar pela lista de commits, use as teclas de seta para cima e para baixo. Para sair, pressione q.

Podemos usar o git log para confirmar o sucesso do squash. Executá-lo após o squash exibirá um único commit com a nova mensagem:

Empurrando commit compactado

O comando acima agirá no repositório local. Para atualizar o repositório remoto, precisamos empurrar nossas mudanças. No entanto, como alteramos o histórico de commits, precisamos usar o empurrão forçado com a opção --force:

git push --force origin feature/login-form

Empurrar forçado sobrescreverá o histórico de commits no ramo remoto e pode potencialmente perturbar outros que estão trabalhando nesse ramo. É uma boa prática comunicar-se com a equipe antes de fazer isso

Uma maneira mais segura de fazer push forçado, que reduz o risco de perturbar os colaboradores, é usar a opção --force-with-lease:

git push --force-with-lease origin feature/login-form

Esta opção garante que só fazemos push forçado se o branch remoto não tiver sido atualizado desde nossa última busca ou pull.

Amalgamar commits específicos

Imagine que temos cinco commits:

Suponha que queremos manter os commits 1, 2 e 5 e amalgamar os commits 3 e 4.

Ao usar o rebase interativo, os commits marcados para amalgamação serão combinados com o commit diretamente anterior. Neste caso, isso significa que queremos amalgamar o Commit4 para que ele se funda com o Commit3.

Para fazer isso, devemos iniciar uma rebase interativa que inclua esses dois commits. Neste caso, três commits são suficientes, então usamos o comando:

git rebase -i HEAD~3

Em seguida, configuramos Commit4 para s para que ele seja fundido com o Commit3:

Após executar este comando e listar os commits, observamos que os commits 3 e 4 foram fundidos enquanto o restante permanece inalterado.

Fusão a partir de um commit específico

No comando git rebase -i HEAD~3, a parte HEAD é um atalho para o commit mais recente. A sintaxe ~3 é usada para especificar um ancestral de um commit. Por exemplo, HEAD~1 se refere ao pai do commit HEAD.

Em um rebase interativo, os commits considerados incluem todos os commits ancestrais que levam ao commit especificado no comando. Note que o commit especificado não é incluído:

Em vez de usar HEAD, podemos especificar diretamente o hash de um commit. Por exemplo, Commit2 tem um hash de dbf3cc118d6d7c08ef9c4a326b26dbb1e3fe9ddf, então o comando:

git rebase -i dbf3cc118d6d7c08ef9c4a326b26dbb1e3fe9ddf

iniciaria um rebase considerando todos os commits feitos após o Commit2. Portanto, se quisermos iniciar um rebase em um commit específico e incluí-lo, podemos usar o comando:

git rebase -i <commit-hash>~1

Resolvendo Conflitos ao Mesclar Commits

Quando mesclamos commits, combinamos várias mudanças de commit em um único commit, o que pode levar a conflitos se as mudanças se sobrepuserem ou divergirem significativamente. Aqui estão alguns cenários comuns onde podem surgir conflitos:

  1. Mudanças sobrepostas: Se dois ou mais commits que estão sendo mesclados modificaram as mesmas linhas de um arquivo ou linhas intimamente relacionadas, o Git pode não conseguir reconciliar automaticamente essas mudanças.
  2. Estado de mudanças diferentes: Se um commit adiciona um pedaço de código e outro commit modifies ou exclui esse mesmo pedaço de código, mesclar esses commits pode levar a conflitos que precisam ser resolvidos.
  3. Renomear e modificar: Se um commit renomeia um arquivo e commits subsequentes fazem mudanças no nome antigo, mesclar esses commits pode confundir o Git, causando um conflito.
  4. Mudanças em arquivos binários: Arquivos binários não se mesclam bem usando ferramentas de diff baseadas em texto. Se várias commit alteram o mesmo arquivo binário e tentamos mesclá-las, pode ocorrer um conflito porque o Git não pode reconciliar automaticamente essas mudanças.
  5. Histórico complexo: Se os commits têm um histórico complexo com várias mesclas, ramificações ou rebase entre eles, mesclá-los pode resultar em conflitos devido à natureza não linear das mudanças.

Ao mesclar, o Git tentará aplicar cada mudança uma por uma. Se encontrar conflitos durante o processo, ele pausará e nos permitirá resolverlos.

Conflitos serão marcados com marcadores de conflito <<<<<< e >>>>>>. Para lidar com os conflitos, precisamos abrir os arquivos e resolver manualmente cada um, selecionando qual parte do código queremos manter. 

Após resolver os conflitos, precisamos preparar os arquivos resolvidos usando o comando git add. Depois disso, podemos continuar o rebase usando o seguinte comando:

git rebase --continue

Para mais sobre conflitos no Git, consulte este tutorial sobre como resolver conflitos de mesclagem no Git.

Alternativas ao Achatamento com Rebase

O comando git merge --squash é um método alternativo ao git rebase -i para combinar vários commits em um único commit. Este comando é particularmente útil quando queremos mesclar mudanças de um ramo para o ramo principal enquanto compacta todos os commits individuais em um. Aqui está uma visão geral de como compactar usando git merge:

  1. Navegamos até o ramo de destino no qual queremos incorporar as mudanças.
  2. Executamos o comando git merge --squash <nome-do-ramo> substituindo <nome-do-ramo> pelo nome do ramo.
  3. Commitamos as mudanças com git commit para criar um único commit que representa todas as mudanças do ramo de recursos.

Por exemplo, digamos que queremos incorporar as mudanças do ramo feature/login-form no main como um único commit:

git checkout main git merge --squash feature-branch git commit -m "Implement login form"

Essas são as limitações dessa abordagem em comparação com git rebase -i:

  • Granularidade de controle: Menos controle sobre commits individuais. Com o rebase podemos escolher quais commits mesclar, enquanto o merge força a combinação de todas as mudanças em um único commit.
  • História intermediária: Ao usar mesclagem, o histórico individual de commits da ramificação de recursos é perdido na ramificação principal. Isso pode dificultar o rastreamento das mudanças incrementais que foram feitas durante o desenvolvimento do recurso.
  • Revisão pré-commit: Como ele estagia todas as mudanças como um único conjunto de mudanças, não podemos revisar ou testar cada commit individualmente antes de mesclar, ao contrário de uma rebase interativa, onde cada commit pode ser revisado e testado em sequência.

Conclusão

Incorporar commits frequentes e pequenos no fluxo de trabalho de desenvolvimento promove colaboração e documentação clara, mas também pode poluir o histórico do projeto. Mesclar commits equilibrando, preserva os marcos importantes enquanto elimina o ruído das mudanças iterativas menores.

Para aprender mais sobre Git, recomendo esses recursos:

Source:
https://www.datacamp.com/tutorial/git-squash-commits