Squash meu último X confirma usando o Git

3589

Como posso esmagar meu último X confirmado em um commit usando o Git?

markdorison
fonte
12
Pergunta semelhante: stackoverflow.com/questions/7275508/…
koppor:
2
@matt TortoiseGit é a sua ferramenta. Ele fornece uma única função "Combinar com uma confirmação" que chamará todas as etapas automaticamente em segundo plano. Infelizmente, disponível apenas para Windows. Veja minha resposta abaixo.
Matthias M
1
Para esmagar até O primeiro cometer ver isso - stackoverflow.com/questions/1657017/...
goelakash
1
post squash é preciso fazer o push forçado stackoverflow.com/questions/10298291/…
vikramvi

Respostas:

2091

Use git rebase -i <after-this-commit>e substitua "pick" na segunda confirmação e subseqüentes por "squash" ou "fixup", conforme descrito no manual .

Neste exemplo, <after-this-commit>é o hash SHA1 ou o local relativo do HEAD da ramificação atual a partir da qual as confirmações são analisadas para o comando rebase. Por exemplo, se o usuário desejar visualizar 5 confirmações do HEAD atual no passado, o comando será git rebase -i HEAD~5.

Anomie
fonte
260
Isso, eu acho, responde a esta pergunta um pouco melhor stackoverflow.com/a/5201642/295797
Roy Truelove
92
O que se entende por <after-this-commit>?
2540625
43
<after-this-commit>é commit X + 1, ou seja, pai do commit mais antigo que você deseja esmagar.
joozek
339
Achei essa resposta muito concisa para entender sem ambiguidade. Um exemplo teria ajudado.
Ian Ollmann
54
A diferença entre essa rebase -iabordagem e reset --softé, rebase -ipermite que eu retenha o autor do commit, enquanto reset --softme permite confirmar novamente. Às vezes, preciso esmagar os commits de solicitações pull, mantendo as informações do autor. Às vezes, preciso redefinir o software por conta própria. Upvotes para ambas as grandes respostas de qualquer maneira.
Zionyx 15/09/2015
3832

Você pode fazer isso facilmente, sem git rebaseou git merge --squash. Neste exemplo, esmagaremos os últimos 3 commits.

Se você deseja escrever a nova mensagem de confirmação do zero, basta:

git reset --soft HEAD~3 &&
git commit

Se você deseja começar a editar a nova mensagem de confirmação com uma concatenação das mensagens de confirmação existentes (ou seja, semelhante ao que uma git rebase -ilista de instruções de pick / squash / squash /… / squash o iniciaria), será necessário extrair essas mensagens e passar eles git commit:

git reset --soft HEAD~3 && 
git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"

Ambos os métodos esmagam os três últimos commits em um único novo commit da mesma maneira. A reinicialização suave apenas aponta novamente HEAD para o último commit que você não deseja esmagar. Nem o índice nem a árvore de trabalho são tocados pela reinicialização suave, deixando o índice no estado desejado para seu novo commit (ou seja, ele já possui todas as alterações dos commits que você está prestes a "jogar fora").

Chris Johnsen
fonte
163
Ha! Eu gosto deste método É aquele que se fecha ao espírito do problema. É uma pena que exija tanto vodu. Algo assim deve ser adicionado a um dos comandos básicos. Possivelmente git rebase --squash-recent, ou até git commit --amend-many.
Adrian Ratnapala
13
@ABB: Se o seu ramo tiver um conjunto "upstream", você poderá usá-lo branch@{upstream}(ou apenas @{upstream}para o ramo atual; em ambos os casos, a última parte poderá ser abreviada para @{u}; veja gitrevisions ). Isso pode diferir do seu "último envio por push" (por exemplo, se alguém enviou algo que foi construído sobre o envio mais recente e você o buscou), mas parece que pode estar próximo do que você deseja.
31714 Chris Chrissen
104
Isso meio que me exigia, push -fmas, caso contrário, era adorável, obrigado.
2rs2ts
39
@ 2rs2ts git push -f parece perigoso. Tome cuidado para esmagar apenas as confirmações locais. Nunca toque em confirmações por push!
Matthias M
20
Eu também preciso usar git push --forcemais tarde para que ele leva a cometer
Zach Saucier
740

Você pode usar git merge --squashisso, que é um pouco mais elegante que git rebase -i. Suponha que você esteja no mestre e queira compactar os últimos 12 commits em um.

AVISO: Primeiro, certifique-se de comprometer o seu trabalho - verifique se git statusestá limpo (pois git reset --harddescartará alterações em etapas e em etapas)

Então:

# Reset the current branch to the commit just before the last 12:
git reset --hard HEAD~12

# HEAD@{1} is where the branch was just before the previous command.
# This command sets the state of the index to be as it would just
# after a merge from that commit:
git merge --squash HEAD@{1}

# Commit those squashed changes.  The commit message will be helpfully
# prepopulated with the commit messages of all the squashed commits:
git commit

A documentação paragit merge descreve a --squashopção com mais detalhes.


Atualização: a única vantagem real desse método sobre a mais simples git reset --soft HEAD~12 && git commitsugerida por Chris Johnsen em sua resposta é que você recebe a mensagem de confirmação pré-preenchida com todas as mensagens de confirmação que você está esmagando.

Mark Longair
fonte
16
Você diz que isso é mais "elegante" do que git rebase -i, mas não explica por que. Provisivamente, coloquei isso em prática porque me parece que, de fato, o oposto é verdadeiro e isso é um truque; você não está executando mais comandos do que o necessário apenas para forçar git mergea executar uma das coisas git rebaseespecificamente projetadas?
Mark Amery
77
@ Mark Amery: Existem várias razões pelas quais eu disse que isso é mais elegante. Por exemplo, não envolve gerar desnecessariamente um editor e, em seguida, procurar e substituir uma sequência no arquivo "tarefas". O uso git merge --squashtambém é mais fácil de usar em um script. Essencialmente, o raciocínio era que você não precisa da "interatividade" git rebase -ipara isso.
Mark Longair
12
Outra vantagem é que git merge --squashé menos provável que produza conflitos de mesclagem em face de movimentos / exclusões / renomeações em comparação com a reestruturação, especialmente se você estiver mesclando de uma ramificação local. (disclaimer: com base em apenas uma experiência, corrija-me se isso não é verdade no caso geral!)
Cheezmeister
2
Sempre sou muito relutante em redefinições rígidas - eu usaria uma tag temporal em vez de HEAD@{1}apenas estar do lado seguro, por exemplo, quando seu fluxo de trabalho é interrompido por uma hora por uma queda de energia, etc.
Tobias Kienzler
8
@ BT: Destruiu o seu commit? :( Não sei ao certo o que você quer dizer com isso. Qualquer coisa que você cometeu poderá voltar facilmente do reflog do git. Se você teve um trabalho não confirmado, mas os arquivos foram preparados, você ainda deve conseguir seu conteúdo foi devolvido, embora isso dê mais trabalho . Se o seu trabalho nem foi encenado, no entanto, receio que pouco possa ser feito; é por isso que a resposta diz de antemão: "Primeiro verifique se o status do git está limpo (desde que o git reset --hard jogue fora as mudanças organizadas e não organizadas) ".
Mark Longair
218

Eu recomendo evitar git resetsempre que possível - especialmente para iniciantes no Git. A menos que você realmente precise automatizar um processo com base em uma série de confirmações, existe uma maneira menos exótica ...

  1. Coloque os commits a serem esmagados em uma ramificação de trabalho (se ainda não estiverem) - use o gitk para isso
  2. Confira o ramo de destino (por exemplo, 'mestre')
  3. git merge --squash (working branch name)
  4. git commit

A mensagem de confirmação será pré-preenchida com base na squash.

nobar
fonte
4
Este é o método mais seguro: sem redefinir soft / hard (!!) ou reflog usado!
TeChn4K
14
Seria ótimo se você expandisse em (1).
Adam
2
@ Adam: Basicamente, isso significa usar a interface GUI gitkpara rotular a linha de código que você está esmagando e também rotular a base sobre a qual apertar. No caso normal, esses dois rótulos já existem, portanto, a etapa (1) pode ser ignorada.
Nobar
3
Observe que esse método não marca o ramo de trabalho como sendo totalmente mesclado, portanto, removê-lo requer forçar a exclusão. :(
Kyrstellaine
2
Para (1), encontrei git branch your-feature && git reset --hard HEAD~Na maneira mais conveniente. No entanto, envolve git reset novamente, o que esta resposta tentou evitar.
EIS
134

Baseado na resposta de Chris Johnsen ,

Adicione um alias global de "squash" do bash: (ou Git Bash no Windows)

git config --global alias.squash '!f(){ git reset --soft HEAD~${1} && git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"; };f'

... ou usando o prompt de comando do Windows:

git config --global alias.squash "!f(){ git reset --soft HEAD~${1} && git commit --edit -m\"$(git log --format=%B --reverse HEAD..HEAD@{1})\"; };f"


Seu ~/.gitconfigagora deve conter este alias:

[alias]
    squash = "!f(){ git reset --soft HEAD~${1} && git commit --edit -m\"$(git log --format=%B --reverse HEAD..HEAD@{1})\"; };f"


Uso:

git squash N

... Que esmaga automaticamente os últimos Ncommits, inclusive.

Nota: A mensagem de confirmação resultante é uma combinação de todas as confirmações esmagadas, em ordem. Se você não estiver satisfeito com isso, poderá sempre git commit --amendmodificá-lo manualmente. (Ou edite o alias para corresponder ao seu gosto.)

EthanB
fonte
6
Interessante, mas prefiro digitar a mensagem de confirmação esmagada, como um resumo descritivo dos meus múltiplos envios, do que inseri-lo automaticamente para mim. Por isso, prefiro especificar git squash -m "New summary."e ter Ndeterminado automaticamente como o número de confirmações não enviadas.
Acumenus
1
@ABB, isso soa como uma pergunta separada. (Eu não acho que é exatamente o que o OP estava pedindo, eu nunca senti uma necessidade para ela no meu git de squash workflow.)
EthanB
3
Isso é muito fofo. Pessoalmente, eu gostaria de uma versão que use a mensagem de confirmação do primeiro dos confirmados juntos. Seria bom para coisas como ajustes de espaço em branco.
funroll
@funroll Concordou. Apenas largar a última mensagem de confirmação é uma necessidade super comum para mim. Devemos ser capazes de conceber que ...
Steve argila
2
O @ABB pode ser usado git commit --amendpara alterar ainda mais a mensagem, mas esse alias permite que você tenha um bom começo sobre o que deve estar na mensagem de confirmação.
dashesy
131

Graças a este post útil, descobri que você pode usar este comando para esmagar os últimos 3 commits:

git rebase -i HEAD~3

Isso é útil, pois funciona mesmo quando você está em uma filial local sem informações de rastreamento / repositório remoto.

O comando abrirá o editor de rebase interativo, que permite reordenar, esmagar, reformular, etc., normalmente.


Usando o editor de rebase interativo:

O editor de rebase interativo mostra os três últimos commits. Essa restrição foi determinada HEAD~3ao executar o comando git rebase -i HEAD~3.

A confirmação mais recente,, HEADé exibida primeiro na linha 1. As linhas que começam com a #são comentários / documentação.

A documentação exibida é bastante clara. Em qualquer linha, você pode alterar o comando de pickpara um comando de sua escolha.

Prefiro usar o comando, fixuppois isso "esmaga" as alterações do commit no commit na linha acima e descarta a mensagem do commit.

Como o commit na linha 1 é HEAD, na maioria dos casos você deixaria isso como pick. Você não pode usar squashou fixupporque não há outro commit para compactar o commit.

Você também pode alterar a ordem dos commits. Isso permite esmagar ou consertar confirmações que não são adjacentes cronologicamente.

editor de rebase interativo


Um exemplo prático do dia a dia

Eu comprometi recentemente um novo recurso. Desde então, cometi duas correções de bugs. Mas agora eu descobri um bug (ou talvez apenas um erro de ortografia) no novo recurso que cometi. Que irritante! Não quero um novo commit poluindo meu histórico de commit!

A primeira coisa que faço é corrigir o erro e fazer um novo commit com o comentário squash this into my new feature!.

Em seguida, executo git logou gitkobtenho o commit SHA do novo recurso (neste caso 1ff9460).

Em seguida, trago o editor de rebase interativo com git rebase -i 1ff9460~. O ~SHA após o commit diz ao editor para incluir esse commit no editor.

Em seguida, movo a confirmação que contém o fix ( fe7f1e0) para embaixo da confirmação do recurso e mudo pickpara fixup.

Ao fechar o editor, a correção será compactada no commit do recurso e meu histórico de commit ficará bonito e limpo!

Isso funciona bem quando todos os commits são locais, mas se você tentar alterar qualquer commit já enviado para o controle remoto, poderá realmente causar problemas para outros desenvolvedores que fizeram check-out no mesmo ramo!

insira a descrição da imagem aqui

br3nt
fonte
5
você tem que escolher o primeiro e esmagar o resto? Você deve editar sua resposta para explicar como usar o editor de rebase interativo em mais detalhes
Kolob Canyon
2
Sim, deixe pickna linha 1. Se você escolher squashou fixuppara o commit na linha 1, o git mostrará uma mensagem dizendo "erro: não é possível 'consertar' sem um commit anterior". Então, você terá a opção de corrigi-lo: "Você pode corrigir isso com 'git rebase --edit-todo' e, em seguida, execute 'git rebase --continue'." ou você pode simplesmente abortar e começar de novo: "Ou você pode abortar o rebase com 'git rebase --abort'.".
Br3nt # 10/18
56

Se você usa o TortoiseGit, pode executar a função Combine to one commit:

  1. Abra o menu de contexto do TortoiseGit
  2. Selecione Show Log
  3. Marque as confirmações relevantes na visualização de log
  4. Selecione Combine to one commitno menu de contexto

Combinar confirmações

Essa função executa automaticamente todas as etapas necessárias de git único. Infelizmente disponível apenas para Windows.

Matthias M
fonte
Tanto quanto sei, isso não funcionará para confirmações de mesclagem.
Thorkil Holm-Jacobsen
1
Embora não seja comentado por outros, isso funciona mesmo para confirmações que não estão no HEAD. Por exemplo, minha necessidade era esmagar alguns commits WIP que fiz com uma descrição mais sensata antes de forçar. Trabalhou lindamente. Obviamente, ainda espero aprender como fazê-lo por comandos.
Charles Roberto Canato
55

Para fazer isso, você pode usar o seguinte comando git.

 git rebase -i HEAD~n

n (= 4 aqui) é o número da última confirmação. Então você tem as seguintes opções,

pick 01d1124 Message....
pick 6340aaa Message....
pick ebfd367 Message....
pick 30e0ccb Message....

Atualize como abaixo de pickum commit e squashos outros para os mais recentes,

p 01d1124 Message....
s 6340aaa Message....
s ebfd367 Message....
s 30e0ccb Message....

Para detalhes, clique no link

Jakir Hosen Khan
fonte
48

Com base neste artigo , achei esse método mais fácil para o meu caso de usuário.

Meu ramo 'dev' estava à frente de 'origin / dev' em 96 confirmações (portanto, essas confirmações ainda não foram enviadas para o controle remoto).

Eu queria esmagar esses commits em um antes de fazer a alteração. Eu prefiro redefinir a ramificação para o estado 'origin / dev' (isso deixará todas as alterações dos commits do 96 sem estágio) e depois confirmar as alterações de uma só vez:

git reset origin/dev
git add --all
git commit -m 'my commit message'
Trudolf
fonte
1
Apenas o que eu precisava. Squash down confirma do meu ramo de recursos e, em seguida, coloco essa confirmação no meu mestre.
David Victor
1
Isso não esmaga as confirmações anteriores!
IgorGanapolsky
você poderia elaborar isso um pouco mais @igorGanapolsky?
trudolf
3
@trudolf Isso não é realmente esmagar (escolher indivíduos se compromete a esmagar). É mais uma confirmação de todas as suas alterações de uma só vez.
IgorGanapolsky
15
sim, portanto esmaga todos os seus commits em um. Parabéns!
trudolf
45

No ramo em que você gostaria de combinar as confirmações, execute:

git rebase -i HEAD~(n number of commits back to review)

exemplo:

git rebase -i HEAD~1

Isso abrirá o editor de texto e você deverá alternar a 'seleção' na frente de cada confirmação com 'squash' se desejar que esses commits sejam mesclados. Da documentação:

p, escolha = use confirmação

s, squash = use commit, mas mescla no commit anterior

Por exemplo, se você deseja mesclar todos os commits em um, o 'pick' é o primeiro commit que você fez e todos os futuros (colocados abaixo do primeiro) devem ser configurados como 'squash'. Se estiver usando o vim, use : x no modo de inserção para salvar e sair do editor.

Em seguida, para continuar a rebase:

git rebase --continue

Para saber mais sobre essa e outras maneiras de reescrever seu histórico de consolidação, consulte este post útil

aabiro
fonte
2
Por favor, explique também o que o --continuee vim :xfaz.
not2qubit
A rebase ocorrerá em blocos à medida que for confirmada em sua ramificação, após git adda configuração correta nos arquivos ser usada git rebase --continuepara passar para a próxima confirmação e começar a mesclar. :xé um comando que salvará as alterações do arquivo ao usar o vim, veja isto
aabiro 30/11
32

A resposta das anomias é boa, mas me senti insegura quanto a isso, então decidi adicionar algumas capturas de tela.

Etapa 0: log do git

Veja onde você está git log. Mais importante, encontre o hash de confirmação do primeiro commit que você não deseja esmagar. Portanto, apenas o:

insira a descrição da imagem aqui

Etapa 1: git rebase

Execute git rebase -i [your hash], no meu caso:

$ git rebase -i 2d23ea524936e612fae1ac63c95b705db44d937d

Etapa 2: escolha / esmague o que quiser

No meu caso, quero compactar tudo o que foi confirmado pela primeira vez. A ordem é do primeiro ao último, de maneira exatamente oposta git log. No meu caso, eu quero:

insira a descrição da imagem aqui

Etapa 3: ajustar as mensagens

Se você selecionou apenas uma confirmação e esmagou o restante, poderá ajustar uma mensagem de confirmação:

insira a descrição da imagem aqui

É isso aí. Depois de salvar este ( :wq), está pronto. Dê uma olhada nisso com git log.

Martin Thoma
fonte
2
seria bom ver o resultado final, por exemplo,git log
Timothy LJ Stewart
2
Não, obrigado. É exatamente assim que eu perdi meus commits.
Axalix 11/03/19
@Axalix Você removeu todas as suas linhas? É assim que você perde seus commits.
A3y3
31

Procedimento 1

1) Identifique o hash curto de confirmação

# git log --pretty=oneline --abbrev-commit
abcd1234 Update to Fix for issue B
cdababcd Fix issue B
deab3412 Fix issue A
....

Aqui mesmo git log --oneline também pode ser usado para obter hash curto.

2) Se você quiser esmagar (mesclar) os dois últimos commit

# git rebase -i deab3412 

3) Isso abre um nanoeditor para mesclar. E parece que abaixo

....
pick cdababcd Fix issue B
pick abcd1234 Update to Fix for issue B
....

4) Renomeie a palavra pickà squashqual está presente antes abcd1234. Depois de renomear, deve ser como abaixo.

....
pick cdababcd Fix issue B
squash abcd1234 Update to Fix for issue B
....

5) Agora salve e feche o nanoeditor. Pressione ctrl + oe pressione Enterpara salvar. E então pressionectrl + x para sair do editor.

6) Então nano editor é aberto novamente para atualizar os comentários, se necessário, atualize-o.

7) Agora é esmagado com sucesso, você pode verificá-lo verificando os logs.

# git log --pretty=oneline --abbrev-commit
1122abcd Fix issue B
deab3412 Fix issue A
....

8) Agora pressione para repo. Nota para adicionar +sinal antes do nome da filial. Isso significa empurrão forçado.

# git push origin +master

Nota: Isso se baseia no uso do git no ubuntushell. Se você estiver usando sistemas operacionais diferentes ( Windowsou Mac), os comandos acima serão os mesmos, exceto o editor. Você pode obter um editor diferente.

Procedimento 2

  1. Primeiro adicione os arquivos necessários para confirmação
git add <files>
  1. Em seguida, confirme usando a --fixupopção e OLDCOMMITdeve estar na qual precisamos mesclar (squash) esse commit.
git commit --fixup=OLDCOMMIT

Agora, isso cria um novo commit no topo do HEAD fixup1 <OLDCOMMIT_MSG>.

  1. Em seguida, execute o comando abaixo para mesclar (squash) o novo commit no OLDCOMMIT.
git rebase --interactive --autosquash OLDCOMMIT^

Aqui ^significa o compromisso anterior com OLDCOMMIT. Este rebasecomando abre uma janela interativa em um editor (vim ou nano), em que não precisamos fazer nada, basta salvar e sair é suficiente. Como a opção passada para isso moverá automaticamente a confirmação mais recente para a confirmação antiga e alterará a operação parafixup (equivalente a squash). Em seguida, o rebase continua e termina.

Procedimento 3

  1. Se for necessário adicionar novas alterações à última confirmação, é --amendpossível usar os meios git-commit.
    # git log --pretty=oneline --abbrev-commit
    cdababcd Fix issue B
    deab3412 Fix issue A
    ....
    # git add <files> # New changes
    # git commit --amend
    # git log --pretty=oneline --abbrev-commit
    1d4ab2e1 Fix issue B
    deab3412 Fix issue A
    ....  

Aqui --amendmescla as novas mudanças para a última confirmaçãocdababcd e gera um novo ID de confirmação1d4ab2e1

Conclusão

  • A vantagem do 1º procedimento é esmagar várias confirmações e reordenar. Mas esse procedimento será difícil se precisarmos mesclar uma correção para o commit muito antigo.
  • Portanto, o segundo procedimento ajuda a mesclar o commit com o commit muito antigo facilmente.
  • E o terceiro procedimento é útil em um caso para esmagar novas alterações para a última confirmação.
rashok
fonte
Ele só atualiza os dois últimos commits mesmo eu redefinir a um Commit ID do 6º último commit, não sei por quê
Carlos Liu
Mesmo você pode reorganizar a ordem de confirmação. Funciona bem.
rashok
29

Para compactar os últimos 10 commit em um único commit:

git reset --soft HEAD~10 && git commit -m "squashed commit"

Se você também deseja atualizar a ramificação remota com o commit compactado:

git push -f
Ayan
fonte
--force é perigoso quando várias pessoas estão trabalhando em uma ramificação compartilhada, pois são atualizadas às cegas remotamente com sua cópia local. --force-with-lease poderia ter sido melhor, pois garante que o controle remoto não seja confirmado por terceiros desde a última vez que você o buscou.
bharath
27

Se você estiver em uma ramificação remota (chamada feature-branch) clonada de um Repositório de Ouro ( golden_repo_name), então aqui está a técnica para compactar seus commits em um:

  1. Finalize o repo de ouro

    git checkout golden_repo_name
    
  2. Crie um novo ramo a partir dele (repositório de ouro) da seguinte maneira

    git checkout -b dev-branch
    
  3. O Squash se mescla com a filial local que você já possui

    git merge --squash feature-branch
    
  4. Confirme suas alterações (esse será o único commit que ocorre no ramo dev)

    git commit -m "My feature complete"
    
  5. Envie a ramificação para o seu repositório local

    git push origin dev-branch
    
Sandesh Kumar
fonte
Desde que eu estava esmagando apenas ~ 100 confirmações (para sincronizar ramificações svn via git-svn), isso é muito mais rápido do que refazer a interação!
sage
1
Lendo, vejo o comentário do @ Chris, que é o que eu costumava fazer (rebase --soft ...) - pena que o stackoverflow não está mais colocando a resposta com centenas de upvotes no topo ...
sáb
1
concordo com você @sage, deixa a esperança de que pudessem fazê-lo em algum momento no futuro
Sandesh Kumar
Este é o caminho certo. A abordagem de rebase é boa, mas só deve ser usada para squash como uma solução de último recurso.
Axalix
20

O que pode ser realmente conveniente:
encontre o hash de confirmação que você deseja esmagar, digamosd43e15 .

Agora usa

git reset d43e15
git commit -am 'new commit name'
Ariel Gabizon
fonte
2
Este. Por que mais pessoas não usam isso? É muito mais rápido do que refazer e esmagar confirmações individuais.
A3y3
17

Isso é super-duper kludgy, mas de uma maneira legal, então eu vou jogá-lo no ringue:

GIT_EDITOR='f() { if [ "$(basename $1)" = "git-rebase-todo" ]; then sed -i "2,\$s/pick/squash/" $1; else vim $1; fi }; f' git rebase -i foo~5 foo

Tradução: forneça um novo "editor" para o git que, se o nome do arquivo a ser editado for git-rebase-todo (o prompt de rebase interativo), alterará tudo, exceto o primeiro "pick" para "squash", e gerará o vim - de modo que quando você for solicitado Para editar a mensagem de confirmação compactada, você obtém o vim. (E, obviamente, eu estava esmagando os últimos cinco commits no ramo foo, mas você pode mudar isso da maneira que quiser.)

Eu provavelmente faria o que Mark Longair sugeriu , no entanto.

Cascabel
fonte
7
+1: isso é divertido e instrutivo, pois não é óbvio para mim que você pode colocar algo mais complexo que o nome de um programa na variável de ambiente GIT_EDITOR.
Mark Longair
16

Se você deseja compactar cada commit em um único commit (por exemplo, ao liberar um projeto publicamente pela primeira vez), tente:

git checkout --orphan <new-branch>
git commit
William Denniss
fonte
15

2020 Solução simples sem rebase:

git reset --soft HEAD~2

git commit -m "new commit message"

git push --force

2 significa que os dois últimos commits serão esmagados. Você pode substituí-lo por qualquer número

Xys
fonte
14

Eu acho que a maneira mais fácil de fazer isso é criar uma nova ramificação fora do master e fazer uma mesclagem - sucata da ramificação de recursos.

git checkout master
git checkout -b feature_branch_squashed
git merge --squash feature_branch

Então você tem todas as alterações prontas para confirmar.

user1376350
fonte
12

Uma linha simples que sempre funciona, já que você está atualmente no ramo que deseja esmagar, master é o ramo de origem e o commit mais recente contém a mensagem e o autor do commit que você deseja usar:

git reset --soft $(git merge-base HEAD master) && git commit --reuse-message=HEAD@{1}
ColinM
fonte
4
Fiquei absolutamente lívido com a frustração por esmagar os commit e como é estupidamente complicado - basta usar a última mensagem e esmagá-los todos para um commit! Por que é tão difícil ???? Este forro faz isso por mim. Obrigado do fundo do meu coração zangado.
Locane
12

se, por exemplo, você deseja compactar as últimas 3 confirmações em uma única confirmação em uma ramificação (repositório remoto), por exemplo: https://bitbucket.org

O que eu fiz é

  1. git reset --soft Head ~ 3 &&
  2. git commit
  3. origem do push do git (branch_name) --force
Albert Ruelan
fonte
3
Basta ter cuidado, pois se você usar a força, então não há maneira de recuperar os commits anteriores desde que você removeu
Albert Ruelan
12

WAR️ AVISO: "Meu último X confirma" pode ser ambíguo.

  (MASTER)  
Fleetwood Mac            Fritz
      ║                    ║
  Add Danny  Lindsey     Stevie       
    Kirwan  Buckingham    Nicks                                              
      ║         ╚═══╦══════╝     
Add Christine       ║          
   Perfect      Buckingham
      ║           Nicks            
    LA1974══════════╝                                    
      ║                  
      ║                  
    Bill <══════ YOU ARE EDITING HERE
  Clinton        (CHECKED OUT, CURRENT WORKING DIRECTORY)              

Nesta história muito abreviada do repositório https://github.com/fleetwood-mac/band-history, você abriu uma solicitação pull para mesclar o commit de Bill Clinton no commit original ( MASTER) do Fleetwood Mac.

Você abriu uma solicitação de recebimento e no GitHub você vê isso:

Quatro confirmações:

  • Adicionar Danny Kirwan
  • Adicionar Christine Perfect
  • LA1974
  • Bill Clinton

Pensando que ninguém se importaria em ler a história completa do repositório. (Na verdade, existe um repositório, clique no link acima!) Você decide esmagar esses commits. Então você vai e corre git reset --soft HEAD~4 && git commit. Então vocêgit push --force no GitHub para limpar seu PR.

E o que acontece? Você acabou de fazer um único commit que vai de Fritz a Bill Clinton. Porque você esqueceu que ontem estava trabalhando na versão Buckingham Nicks deste projeto. E git lognão corresponde ao que você vê no GitHub.

AL MORAL DA HISTÓRIA

  1. Encontre os arquivos exatos que você deseja obter para , e git checkouteles
  2. Encontre a confirmação prévia exata que você deseja manter na história e git reset --softque
  3. Faça um git commitque entorte diretamente do de para o para
William Entriken
fonte
1
Essa é 100% a maneira mais fácil de fazer isso. Se o seu HEAD atual estiver no estado correto desejado, você poderá pular o número 1.
Stan
Esta é a única maneira que sei que permite reescrever o primeiro histórico de confirmação.
Vadorequest
9

Se você não se importa com as mensagens de confirmação das confirmações intermediárias, pode usar

git reset --mixed <commit-hash-into-which-you-want-to-squash>
git commit -a --amend
Zsolt Z.
fonte
7

Se você estiver trabalhando com o GitLab, basta clicar na opção Squash na solicitação de mesclagem, como mostrado abaixo. A mensagem de confirmação será o título da solicitação de mesclagem.

insira a descrição da imagem aqui

arush436
fonte
6
git rebase -i HEAD^^

onde o número de ^ é X

(neste caso, esmague os dois últimos commits)

Yoaz Menda
fonte
6

Além de outras excelentes respostas, gostaria de acrescentar como git rebase -isempre me confunde com a ordem de confirmação - da mais antiga para a mais recente ou vice-versa? Portanto, este é o meu fluxo de trabalho:

  1. git rebase -i HEAD~[N], em que N é o número de confirmações nas quais quero ingressar, começando pela mais recente . Então git rebase -i HEAD~5, significa "esmagar os últimos 5 commits em um novo";
  2. o editor aparece, mostrando a lista de confirmações que quero mesclar. Agora eles são exibidos na ordem inversa : a confirmação mais antiga está no topo. Marque como "squash" ou "s" todos os commit lá, exceto o primeiro / mais antigo : será usado como ponto de partida. Salve e feche o editor;
  3. o editor aparece novamente com uma mensagem padrão para o novo commit: altere-o para suas necessidades, salve e feche. Squash concluído!

Fontes e leituras adicionais: # 1 , # 2 .

Ignorante
fonte
6

Que tal uma resposta para a pergunta relacionada a um fluxo de trabalho como este?

  1. muitas confirmações locais, combinadas com várias mesclagens FROM master ,
  2. finalmente um empurrão para o controle remoto,
  3. PR e mesclar PARA dominar pelo revisor . (Sim, seria mais fácil para o desenvolvedor merge --squashapós o PR, mas a equipe achou que isso atrasaria o processo.)

Não vi um fluxo de trabalho como esse nesta página. (Talvez sejam meus olhos.) Se eu entendi rebasecorretamente, várias mesclagens exigiriam várias resoluções de conflito . Eu não quero nem pensar nisso!

Então, isso parece funcionar para nós.

  1. git pull master
  2. git checkout -b new-branch
  3. git checkout -b new-branch-temp
  4. editar e confirmar muito localmente, mesclar mestre regularmente
  5. git checkout new-branch
  6. git merge --squash new-branch-temp // coloca todas as alterações no estágio
  7. git commit 'one message to rule them all'
  8. git push
  9. O revisor faz PR e mescla com o mestre.
allenjom
fonte
De muitas opiniões, eu gosto da sua abordagem. É muito conveniente e rápido
Artem Solovev
5

Acho que uma solução mais genérica não é especificar confirmações 'N', mas sim o ID da filial / confirmação que você deseja esmagar. Isso é menos propenso a erros do que contar as confirmações até uma confirmação específica - basta especificar a tag diretamente ou, se você realmente quiser contar, pode especificar HEAD ~ N.

No meu fluxo de trabalho, inicio uma ramificação, e meu primeiro commit nessa ramificação resume o objetivo (ou seja, geralmente é o que enviarei como a mensagem 'final' do recurso para o repositório público.) Então, quando terminar, tudo Eu quero fazer é git squash master voltar para a primeira mensagem e então estou pronto para enviar.

Eu uso o alias:

squash = !EDITOR="\"_() { sed -n 's/^pick //p' \"\\$1\"; sed -i .tmp '2,\\$s/^pick/f/' \"\\$1\"; }; _\"" git rebase -i

Isso irá despejar o histórico que está sendo compactado antes de fazê-lo - isso lhe dá uma chance de recuperar, pegando um ID de confirmação antigo do console, se você quiser reverter. (Os usuários do Solaris observam que ele usa a -iopção GNU sed ; os usuários de Mac e Linux devem ficar bem com isso.)

Ethan
fonte
Eu tentei o alias, mas não tenho certeza se o sed substitui está tendo algum efeito. O que eles deveriam fazer?
raine 21/03
O primeiro sed apenas despeja a história no console. O segundo sed substitui todo o 'pick' por 'f' (correção) e reescreve o arquivo do editor no local (a opção -i). Então o segundo faz todo o trabalho.
Ethan
Você está certo, contar o número N de confirmações específicas é muito suscetível a erros. Ele me ferrou várias vezes e perdeu horas tentando desfazer a recuperação.
IgorGanapolsky
Olá Ethan, gostaria de saber se esse fluxo de trabalho ocultará possíveis conflitos na mesclagem. Portanto, considere se você tem dois ramos, mestre e escravo. Se o escravo tem um conflito com o mestre e o usamos git squash masterquando fazemos check-out no escravo. o que isso vai acontecer? vamos esconder o conflito?
Sergio Bilello 24/02
@Sergio Este é um caso de reescrever o histórico, então você provavelmente terá conflitos se espremer os commit que já foram enviados e, em seguida, tentar mesclar / refazer a versão esmagada. (Alguns casos triviais pode fugir com ela.)
Ethan
5

Em questão, pode ser ambíguo o que se entende por "último".

por exemplo, git log --graphgera o seguinte (simplificado):

* commit H0
|
* merge
|\
| * commit B0
| |
| * commit B1
| | 
* | commit H1
| |
* | commit H2
|/
|

As últimas confirmações por hora são H0, mesclagem, B0. Para esmagá-los, você precisará refazer sua ramificação mesclada no commit H1.

O problema é que H0 contém H1 e H2 (e geralmente confirma mais antes da mesclagem e após a ramificação), enquanto B0 não. Então você precisa gerenciar as alterações de H0, mesclagem, H1, H2, B0 pelo menos.

É possível usar o rebase, mas de maneira diferente das respostas mencionadas nas outras:

rebase -i HEAD~2

Isso mostrará as opções de escolha (conforme mencionado em outras respostas):

pick B1
pick B0
pick H0

Coloque a abóbora em vez de escolher para H0:

pick B1
pick B0
s H0

Após salvar e sair, o rebase aplicará confirmações sucessivas após H1. Isso significa que ele solicitará que você resolva conflitos novamente (onde HEAD será H1 no início e depois acumulará confirmações à medida que forem aplicadas).

Após o término da recuperação, você pode escolher a mensagem para H0 e B0 esmagados:

* commit squashed H0 and B0
|
* commit B1
| 
* commit H1
|
* commit H2
|

PS Se você apenas redefinir o BO: (por exemplo, usar reset --mixedisso é explicado em mais detalhes aqui https://stackoverflow.com/a/18690845/2405850 ):

git reset --mixed hash_of_commit_B0
git add .
git commit -m 'some commit message'

então você esmaga as alterações B0 de H0, H1, H2 (perdendo completamente as confirmações após as ramificações e antes da mesclagem).

littlewhywhat
fonte
4

1) git reset --soft HEAD ~ n

n - Número do commit, é necessário esmagar

2) git commit -m "nova mensagem de confirmação"

3) origem do push do git branch_name --force

Poonkodi
fonte