O que significa esmagar commits no git?

117

O que significa Squashing commits em git? Como faço para squash commits no Github?

Eu sou novo no Git e solicitei ser atribuído a um bug recém-chegado no analisador de coala. Consertei o bug e agora fui solicitado a esmagar meus commits. Como eu faço isso?

Lakshmanan Meiyappan
fonte
Ei @Lakshman, fique à vontade para aceitar a resposta com maior votação. Ele responde adequadamente à sua pergunta.
Mostafiz Rahman

Respostas:

180

Você pode pensar no Git como um banco de dados avançado de instantâneos de seu (s) diretório (s) de trabalho.

Um recurso muito bom do Git é a capacidade de reescrever o histórico de commits.
A principal razão para fazer isso é que muito desse histórico é relevante apenas para o desenvolvedor que o gerou, então ele deve ser simplificado, ou melhorado, antes de enviá-lo para um repositório compartilhado.

Eliminar um commit significa, de um ponto de vista idiomático, mover as mudanças introduzidas no dito commit em seu pai, de forma que você termine com um commit em vez de dois (ou mais).
Se você repetir esse processo várias vezes, poderá reduzir n commit a um único.

Visualmente, se você começou seu trabalho no commit marcado como Iniciar , você quer isso

Git commits squashing

Você pode notar que o novo commit tem um tom de azul ligeiramente mais escuro. Isso é intencional.

No Git, a compressão é feita com um Rebase , de uma forma especial chamada Rebase Interativo .
Simplificando quando você rebase um conjunto de commits em um branch B , você aplica todas as mudanças introduzidas por aqueles commits como eles foram feitos, começando de B ao invés de seu ancestral original.

Uma pista visual

insira a descrição da imagem aqui

Observe novamente os diferentes tons de azul.

Um rebase interativo permite que você escolha como os commits devem ser rebaseizados. Se você executar este comando:

 git rebase -i branch

Você acabaria com um arquivo que lista os commits que serão rebaseados

 pick ae3...
 pick ef6...
 pick 1e0...
 pick 341...

Eu não nomear os commits, mas esses quatro uns pretendem ser os commits a partir Iniciar a cabeça

O bom dessa lista é que ela é editável .
Você pode omitir commits, ou você pode esmagá-los .
Tudo o que você precisa fazer é mudar a primeira palavra para squash .

 pick ae3...
 squash ef6...
 squash 1e0...
 squash 341...

Se você fechar o editor e nenhum conflito de mesclagem for encontrado, você terá este histórico:

insira a descrição da imagem aqui

No seu caso, você não quer rebase em outro branch, mas em um commit anterior.
Para transformar o histórico conforme mostrado no primeiro exemplo, você deve executar algo como

git rebase -i HEAD~4

mude os "comandos" para squash para todos os commits exceto o primeiro, e então feche seu editor.


Nota sobre a alteração do histórico

No Git, os commits nunca são editados. Eles podem ser podados, tornados inacessíveis, clonados, mas não alterados.
Quando você rebase, na verdade está criando novos commits.
Os antigos não são mais acessíveis a nenhum árbitro, então não são mostrados na história, mas eles ainda estão lá!

Isso é o que você realmente obtém para um rebase:

insira a descrição da imagem aqui

Se você já os colocou em algum lugar, reescrever o histórico fará um branch!

Margaret Bloom
fonte
Legal - o que acontece com os comentários de commit originais - eles são combinados em um único comentário de commit grande ou são perdidos?
Paul R
De man git rebase: A mensagem de commit sugerida para o commit dobrado é a concatenação das mensagens de commit do primeiro commit e daquelas com o comando "squash"
Margaret Bloom
1
Esta é a melhor explicação que já vi sobre o rebase, obrigado.
Kerry Jones
Sobre a compressão na sua primeira imagem: Você pode fazer um exemplo em que o HEAD tem um diretório de trabalho diferente antes (azul claro) e depois (azul escuro) da compressão? Em todos os exemplos que tentei esmagar parecia que ele apenas excluiu os três commits entre START e HEAD.
actual_panda
@actual_panda Não tenho certeza se entendi. HEAD é uma referência a um commit. Confirma deltas de loja. O diretório de trabalho é uma propriedade do repositório como um todo, depende de qual commit você retirou. Em geral, dois refs (como HEAD e START) sempre fornecem dois endereços de trabalho diferentes quando check-out. Se você rebase o squash no mesmo branch, o efeito é "perder" os commits intermediários, mas na realidade, o git fez um novo com todos os deltas. git diffpode ajudá-lo a mostrar o que aconteceu.
Margaret Bloom
22

O comando rebase tem algumas opções incríveis disponíveis em seu modo --interactive(ou -i), e uma das mais amplamente usadas é a capacidade de esmagar commits. O que isso faz é pegar commits menores e combiná-los em outros maiores, o que pode ser útil se você estiver encerrando o dia de trabalho ou se quiser apenas empacotar suas alterações de forma diferente. Vamos ver como você pode fazer isso facilmente.

Uma palavra de cautela: só faça isso em commits que não foram enviados para um repositório externo. Se outros basearam o trabalho nos commits que você vai deletar, muitos conflitos podem ocorrer. Só não reescreva sua história se ela tiver sido compartilhada com outras pessoas.

Então, digamos que você acabou de fazer alguns pequenos commits e quer fazer um commit maior deles. O histórico do nosso repositório atualmente se parece com isto:

insira a descrição da imagem aqui

Os últimos 4 commits seriam muito mais felizes se estivessem juntos, então vamos fazer isso por meio de rebase interativo:

$ git rebase -i HEAD~4

pick 01d1124 Adding license
pick 6340aaa Moving license into its own file
pick ebfd367 Jekyll has become self-aware.
pick 30e0ccb Changed the tagline in the binary, too.

# Rebase 60709da..30e0ccb onto 60709da
#
# Commands:
#  p, pick = use commit
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

Então, algumas coisas aconteceram aqui. Em primeiro lugar, eu disse ao Git que queria rebase usando os últimos quatro commits de onde o HEAD está com o HEAD ~ 4. O Git agora me colocou em um editor com o texto acima e uma pequena explicação do que pode ser feito. Você tem muitas opções disponíveis para você nesta tela, mas agora vamos apenas compactar tudo em um commit. Portanto, alterar as primeiras quatro linhas do arquivo para isso resolverá o problema:

pick 01d1124 Adding license
squash 6340aaa Moving license into its own file
squash ebfd367 Jekyll has become self-aware.
squash 30e0ccb Changed the tagline in the binary, too.

Basicamente, isso diz ao Git para combinar todos os quatro commits no primeiro commit da lista. Assim que isso for feito e salvo, outro editor aparecerá com o seguinte:

# This is a combination of 4 commits.
# The first commit's message is:
Adding license

# This is the 2nd commit message:

Moving license into its own file

# This is the 3rd commit message:

Jekyll has become self-aware.

# This is the 4th commit message:

Changed the tagline in the binary, too.

    # Please enter the commit message for your changes. Lines starting
    # with '#' will be ignored, and an empty message aborts the commit.
    # Explicit paths specified without -i nor -o; assuming --only paths...
    # Not currently on any branch.
    # Changes to be committed:
    #   (use "git reset HEAD <file>..." to unstage)
    #
    #   new file:   LICENSE
    #   modified:   README.textile
    #   modified:   Rakefile
    #   modified:   bin/jekyll
    #

Já que estamos combinando tantos commits, Git permite que você modifique a mensagem do novo commit com base no resto dos commits envolvidos no processo. Edite a mensagem conforme achar adequado, salve e saia. Feito isso, seus commits foram esmagados com sucesso!

Created commit 0fc4eea: Creating license file, and making jekyll self-aware.
 4 files changed, 27 insertions(+), 30 deletions(-)
  create mode 100644 LICENSE
    Successfully rebased and updated refs/heads/master.

E se olharmos para a história novamente ... insira a descrição da imagem aqui

Portanto, esta tem sido relativamente indolor até agora. Se você encontrar conflitos durante o rebase, eles geralmente são muito fáceis de resolver e o Git o orienta o máximo possível. O básico disso é consertar o conflito em questão, git addo arquivo, e então git rebase --continueretomar o processo. É claro que fazer um git rebase --aborto trará de volta ao estado anterior, se quiser. Se por algum motivo você perdeu um commit no rebase, você pode usar o reflog para recuperá-lo.

Detalhes podem ser encontrados neste link .

0xAliHn
fonte
3
Obrigado! No caso de alguém estar tão desconfortável com o VIM quanto eu ... quando você chegar ao ponto em que você entra em ações para editar, pressione 'i' para entrar no modo de edição. Quando terminar as alterações, pressione escape e digite ": wq" e isso o moverá para frente. (Mac)
Farasi78
1 para o cuidado e contexto de quando fazer isso. Na minha experiência pessoal, é bom se você está trabalhando sozinho em um branch de recurso e tem uma tonelada de commits triviais como "readme atualizado", "fez algo que ninguém se preocupa" e você está pronto para fundir o branch, pode como bem, apenas esmague tudo isso em um commit. Ninguém vai querer voltar ao seu "fez algo que ninguém se preocupa" e isso polui o histórico de commits
Adam Hughes