Como posso mesclar duas confirmações em uma, se já iniciei o rebase?


1159

Estou tentando mesclar 2 commits em 1, então segui “squashing commits with rebase” do git ready .

Eu corri

git rebase --interactive HEAD~2

No editor resultante, mudo pickpara squashe, em seguida, salve-saia, mas a rebase falha com o erro

Não é possível 'squash' sem uma confirmação anterior

Agora que minha árvore de trabalho atingiu esse estado, estou tendo problemas para me recuperar.

O comando git rebase --interactive HEAD~2falha com:

O rebase interativo já foi iniciado

e git rebase --continuefalha com

Não é possível 'squash' sem uma confirmação anterior


22
Eu bati isso também. Meu erro foi causado pelo fato de o git rebase -i listar os commits na ordem oposta do log do git; o último commit está no fundo!
Lsurprenant


Respostas:


1734

Sumário

A mensagem de erro

Não é possível 'squash' sem uma confirmação anterior

significa que você provavelmente tentou "esmagar para baixo". O Git sempre coloca uma confirmação mais recente em uma confirmação mais antiga ou "para cima", conforme exibido na lista de tarefas de rebase interativa, ou seja, em uma confirmação na linha anterior. Alterar o comando na primeira linha da sua lista de tarefas squashsempre produzirá esse erro, pois não há nada para o primeiro commit ser compactado.

O conserto

Primeiro volte para onde você começou

$ git rebase --abort

Diga que sua história é

$ git log --pretty=oneline
a931ac7c808e2471b22b5bd20f0cad046b1c5d0d c
b76d157d507e819d7511132bdb5a80dd421d854f b
df239176e1a2ffac927d8b496ea00d5488481db5 a

Ou seja, a foi o primeiro commit, depois b e finalmente c. Depois de confirmar c, decidimos esmagar bec juntos:

(Nota: A execução git logdireciona sua saída para um pager, lesspor padrão na maioria das plataformas. Para sair do pager e retornar ao prompt de comando, pressione a qtecla.)

A execução git rebase --interactive HEAD~2fornece um editor com

pick b76d157 b
pick a931ac7 c

# Rebase df23917..a931ac7 onto df23917
#
# 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
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

(Observe que esta lista de tarefas está na ordem inversa em comparação com a saída de git log.)

Alterar b's pickpara squashresultará no erro que você viu, mas, em vez disso, você esmaga c em b (confirmação mais recente na mais antiga ou “esmaga para cima”) alterando a lista de tarefas para

pick   b76d157 b
squash a931ac7 c

e salvar seu editor, você receberá outro editor cujo conteúdo é

# This is a combination of 2 commits.
# The first commit's message is:

b

# This is the 2nd commit message:

c

Quando você salva e sai, o conteúdo do arquivo editado se torna uma mensagem de confirmação da nova confirmação combinada:

$ git log --pretty=oneline
18fd73d3ce748f2a58d1b566c03dd9dafe0b6b4f b and c
df239176e1a2ffac927d8b496ea00d5488481db5 a

Nota sobre como reescrever o histórico

O rebase interativo reescreve o histórico. Tentar enviar para um controle remoto que contém o histórico antigo falhará porque não é um avanço rápido.

Se a ramificação que você reformulou for uma ramificação de tópico ou recurso na qual você está trabalhando sozinho , não é grande coisa. Enviar para outro repositório exigirá a --forceopção ou, alternativamente, você poderá, dependendo das permissões do repositório remoto, primeiro excluir a ramificação antiga e depois enviar a versão rebaseada. Exemplos desses comandos que potencialmente destruirão o trabalho estão fora do escopo desta resposta.

Reescrever o histórico já publicado em uma ramificação na qual você está trabalhando com outras pessoas sem uma boa razão, como deixar uma senha ou outros detalhes sensíveis forçar o trabalho de seus colaboradores e é antissocial e incomodará outros desenvolvedores. A seção “Recuperando de uma rebase upstream” na git rebasedocumentação explica, com ênfase adicional.

Reprocessar (ou qualquer outra forma de reescrita) uma ramificação na qual outros se basearam é uma má idéia: qualquer pessoa a jusante é forçada a corrigir manualmente seu histórico. Esta seção explica como fazer a correção do ponto de vista do downstream. A solução real, no entanto, seria evitar rebasear o upstream em primeiro lugar.


Se eu usar rebase para compactar uma confirmação, uma nova confirmação "combinada" será criada contendo os dois conjuntos de alterações, mas o hash será diferente. os commits originais também são preservados pelo git?
fabsenet

@fabsenet Sim e não. As confirmações originais ainda estão acessíveis, mas provavelmente não podem mais ser acessadas por nenhum árbitro (dependendo dos detalhes do seu histórico). As confirmações não referenciadas acabam sendo eliminadas pelo processo de coleta de lixo.
Greg Bacon

Eu só estava brincando ... eu fiz git log hashoftheoldcommite deu certo, mas eu estava curioso para ver um git log --graphcom todos esses commits inacessíveis incluído
fabsenet

essa squash é uma boa ferramenta para organizar confirmações antes do envio, mas se eu enviar uma confirmação, não posso comprimir? git diz: HEAD desanexado, atualizado e atualizado com êxito.
Sérgio

Não estou conseguindo o editor na segunda instância, o git bash, parece congelado em algum processo. O que fazer?

411

Se houver várias confirmações, você pode usar git rebase -ipara esmagar duas confirmações em uma.

Se houver apenas duas confirmações que você deseja mesclar e elas forem as "duas mais recentes", os seguintes comandos poderão ser usados ​​para combinar as duas confirmações em uma:

git reset --soft "HEAD^"
git commit --amend

6
Qual é a desvantagem em comparação à rebase? Acho um muito mais simples de usar.
perfil

17
Você não pode entrar em ordem arbitrária - apenas os dois últimos confirmados .
dr0i

50
@ dr0i Você pode mesclar quantas confirmações quiser, desde que sejam as últimas confirmações X e não em algum lugar no meio. Basta executar git reset --soft HEAD~10, onde 10 é o número de confirmações que você deseja mesclar.
Fregante

2
Isso pode ser usado se você não tiver um conjunto de origem remoto e tiver apenas duas confirmações.
Atedja

8
Você também pode redefinir para um commit específico, se não desejar contar quantos existem HEADusando git reset --soft 47b5c5...onde 47b5c5...está o ID SHA1 do commit.
Dguay

112

Rebase: você não precisará disso:

Uma maneira mais simples para o cenário mais frequente.

Na maioria dos casos:

Na verdade, se tudo o que você deseja é simplesmente combinar vários commits recentes em um, mas não precisa drop, reworde outro trabalho de rebase.

você pode simplesmente fazer:

git reset --soft "HEAD~n"
  • Supondo que ~né o número de commits para suavemente un-commit (ou seja ~1, ~2...)

Em seguida, use o seguinte comando para modificar a mensagem de confirmação.

git commit --amend

que é praticamente o mesmo que um longo intervalo de squashe um pick.

E funciona para n confirmações, mas não apenas duas confirmações, conforme a resposta acima solicitada.


3
Isso é bom se você quiser fazer uma limpeza adicional além de compactar as confirmações, como remover 1 confirmação no meio ou alterar uma linha de código.
styfle

1
Supondo que ~né o número de commits para suavemente un-commit (ou seja ~1, ~2...)
Ray

1
E se eu quiser mesclar não as núltimas confirmações, mas nconfirmar no meio? Posso fazer isso facilmente?
Chumakoff 29/08/19

1
Então git rebase -ié o que você precisa para fazer o squashtrabalho. @chumakoff
pambda

3
Então, para unir nos commits mais recentes em um, primeiro uso git reset --soft @~m, ondem = n - 1
Łukasz Rajchel 7/07

55

Primeiro você deve verificar quantas confirmações possui:

git log

Existem dois status:

Uma é que existem apenas dois commits:

Por exemplo:

commit A
commit B

(Nesse caso, você não pode usar o git rebase para fazer), você precisa fazer o seguinte.

$ git reset --soft HEAD^1

$ git commit --amend

Outra é que existem mais de dois commits; você deseja mesclar commit C e D.

Por exemplo:

commit A
commit B
commit C
commit D

(sob essa condição, você pode usar o git rebase)

git rebase -i B

E do que usar "squash" para fazer. O resto é muito fácil. Se você ainda não sabe, leia http://zerodie.github.io/blog/2012/01/19/git-rebase-i/


A redefinição --soft e commit --amend é a única maneira que funciona se você já tiver uma rebase em andamento (e escolha 'editar' em vez de 'squash' para esta confirmação). +1
Jacek Lach

1
Mesclando os primeiros e apenas dois commits em um repositório, exatamente o meu caso extremo :-)
Chris Huang-Leaver

1
Por favor, adicione que git push -f origin masterpode ser necessário.
Rishabh Agrahari

33

Supondo que você estivesse em seu próprio ramo de tópico. Se você deseja mesclar os dois últimos commits em um e parecer um herói, ramifique o commit pouco antes de fazer os dois últimos commits.

git checkout -b temp_branch HEAD^2

Em seguida, o squash confirma o outro ramo neste novo ramo:

git merge branch_with_two_commits --squash

Isso trará as mudanças, mas não as comprometerá. Então, apenas os comprometa e pronto.

git commit -m "my message"

Agora você pode mesclar esse novo ramo de tópico novamente no ramo principal.


5
Essa foi, na verdade, a resposta mais útil para mim, porque não exigiu uma nova redefinição manual, mas apenas reduz todas as confirmações de uma ramificação inteira em uma confirmação. Muito agradável.
Robert

Obrigado por isso! Isto é como fazer o git fazer como eu imagino os squash commits na minha cabeça!
Marjan Venema

resposta incrível, muito mais simples do que as alternativas

Aparentemente, essa resposta não é adequada para o caso em que ae cprecisa ser mesclada e mantida bcomo está.
precisa saber é o seguinte

2
Alguma coisa mudou nas versões recentes do git? Quando tento o primeiro comando ( git checkout -b combine-last-two-commits "HEAD^2") na versão 2.17 do git, recebo um erro:fatal: 'HEAD^2' is not a commit and a branch 'combine-last-two-commits' cannot be created from it
mhucka 14/05

23

você pode cancelar o rebase com

git rebase --abort

e quando você executa o comando rebase interativo novamente, o 'squash; commit deve estar abaixo do pick commit na lista


16

Eu costumo usar o git reset --mixed para reverter uma versão base antes de vários commits que você deseja mesclar, então eu faço um novo commit, para que o seu commit seja mais recente, garanta que a sua versão seja HEAD depois de enviar para o servidor.

commit ac72a4308ba70cc42aace47509a5e
Author: <me@me.com>
Date:   Tue Jun 11 10:23:07 2013 +0500

    Added algorithms for Cosine-similarity

commit 77df2a40e53136c7a2d58fd847372
Author: <me@me.com>
Date:   Tue Jun 11 13:02:14 2013 -0700

    Set stage for similar objects

commit 249cf9392da197573a17c8426c282
Author: Ralph <ralph@me.com>
Date:   Thu Jun 13 16:44:12 2013 -0700

    Fixed a bug in space world automation

Se eu quiser mesclar a cabeça dois commits em um, primeiro eu uso:

git reset --mixed 249cf9392da197573a17c8426c282

"249cf9392da197573a17c8426c282" era a terceira versão, também é a sua versão base antes de mesclar, depois disso, faço um novo commit:

git add .
git commit -m 'some commit message'

É tudo, a esperança é outro caminho para todos.

FYI, de git reset --help:

 --mixed
     Resets the index but not the working tree (i.e., the changed files are
     preserved but not marked for commit) and reports what has not been
     updated. This is the default action.

Não li os documentos para '--mixed', mas tenho certeza que outras pessoas leram a postagem e se perguntaram a mesma coisa: Qual é a vantagem de usar --mixed? Pode melhorar sua postagem para incluir um snippet da página de manual.
Funroll

@funroll que eu não conhecia - misturado muito antes de escrever esta resposta, de acordo com minha própria experiência, a operação mista girará especificar versão que eu transmito como argumento como repositório HEAD version, e nada pode perder após essa versão, para que ainda possamos lidar com essas mudanças.
21814 VinceStyling #

14

$ git rebase --abort

Execute esse código a qualquer momento, se você quiser desfazer o git rebase

$ git rebase -i HEAD~2

Reaplicar os últimos dois commits. O comando acima abrirá um editor de código

  • [ A confirmação mais recente estará na parte inferior ]. Altere a última confirmação para squash (s). Como o squash se fundirá com o commit anterior.
  • Em seguida, pressione a tecla esc e digite: wq para salvar e fechar

After: wq você estará no modo rebase ativo

Nota : Você receberá outro editor se não houver mensagens de aviso / erro. Se houver um erro ou aviso de que outro editor não será exibido, você pode abortar executando $ git rebase --abort se encontrar um erro ou aviso, caso contrário, continue executando$ git rebase --continue

Você verá sua 2 mensagem de confirmação. Escolha uma ou escreva sua própria mensagem de confirmação, salve e saia [: wq]

Nota 2: pode ser necessário forçar o envio de alterações ao repositório remoto se você executar o comando rebase

$ git push -f

$ git push -f origin master


1
Nota 2: git push -f origin/masteré o que outras respostas estão faltando. +1
Rishabh Agrahari

2

Desde que eu uso git cherry-pick quase tudo, para mim é natural fazê-lo mesmo aqui.

Como fiz o branchXcheck-out e há dois commits na ponta, dos quais desejo criar um commit combinando seu conteúdo, faço o seguinte:

git checkout HEAD^ // Checkout the privious commit
git cherry-pick --no-commit branchX // Cherry pick the content of the second commit
git commit --amend // Create a new commit with their combined content

Se eu quiser atualizar branchXtambém (e acho que esse é o lado negativo deste método), também tenho que:

git checkout branchX
git reset --hard <the_new_commit>

1

Se o seu ramo principal se git logparece com o seguinte:

commit ac72a4308ba70cc42aace47509a5e
Author: <me@me.com>
Date:   Tue Jun 11 10:23:07 2013 +0500

    Added algorithms for Cosine-similarity

commit 77df2a40e53136c7a2d58fd847372
Author: <me@me.com>
Date:   Tue Jun 11 13:02:14 2013 -0700

    Set stage for similar objects

commit 249cf9392da197573a17c8426c282
Author: Ralph <ralph@me.com>
Date:   Thu Jun 13 16:44:12 2013 -0700

    Fixed a bug in space world automation

e você deseja mesclar as duas confirmações principais, siga estas etapas simples:

  1. O primeiro a fazer checkout pelo lado seguro, o segundo último commit em um ramo separado. Você pode nomear o ramo como qualquer coisa.git checkout 77df2a40e53136c7a2d58fd847372 -b merged-commits
  2. Agora, basta escolher suas alterações desde o último commit neste novo ramo como: git cherry-pick -n -x ac72a4308ba70cc42aace47509a5e . (Resolva conflitos, se houver)
  3. Portanto, agora, suas alterações no último commit estão no seu segundo último commit. Mas você ainda precisa confirmar, primeiro adicione as alterações que você escolheu e depois execute git commit --amend.

É isso aí. Você pode enviar esta versão mesclada na ramificação "fusões-confirmações", se desejar.

Além disso, você pode descartar as duas confirmações consecutivas em sua ramificação mestre agora. Atualize seu ramo principal como:

git checkout master
git reset --hard origin/master (CAUTION: This command will remove any local changes to your master branch)
git pull

0

Se você deseja combinar as duas confirmações mais recentes e usar apenas a mensagem da confirmação mais antiga, pode automatizar o processo usando expect.

Eu assumo:

  • Você está usando o vi como seu editor
  • Seus commits são de uma linha cada

Eu testei com git version 2.14.3 (Apple Git-98).


#!/usr/bin/env expect
spawn git rebase -i HEAD~2

# change the second "pick" to "squash"
# down, delete word, insert 's' (for squash), Escape, save and quit
send "jdwis \033:wq\r"

expect "# This is a"

# skip past first commit message (assumed to be one line), delete rest of file
# down 4, delete remaining lines, save and quit
send "4jdG\r:wq\r"

interact

Não está claro o que seu script faz.
buhtz

@ buu Adicionei mais alguns comentários. Deixe-me saber se você ainda acha confuso e, em caso afirmativo, qual parte.
erwaman 28/05

Ainda não está claro o que o seu scrpit faz. Também expectnão é descrito.
buhtz

@buhtz que parte não está clara? Forneci um link para uma página com mais documentação para expect.
erwaman

Uma descrição geral do script está ausente. Não está claro o que faz. Nenhuma parte é clara. A intenção do próprio script não é clara.
buhtz
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.