Combinar os dois primeiros commits de um repositório Git?

197

Suponha que você tenha um histórico contendo os três commits A, B e C :

A-B-C

Gostaria de combinar os dois commits A e B com um commit AB :

AB-C

eu tentei

git rebase -i A

que abre meu editor com o seguinte conteúdo:

pick e97a17b B
pick asd314f C

Eu mudo isso para

squash e97a17b B
pick asd314f C

Então o Git 1.6.0.4 diz:

Cannot 'squash' without a previous commit

Existe um caminho ou isso é simplesmente impossível?

cristão
fonte

Respostas:

168

Use a git rebase -i --root partir da versão 1.7.12 do Git .

No arquivo de rebase interativo, altere a segunda linha do commit B para squash e deixe as outras linhas na seleção :

pick f4202da A
squash bea708e B
pick a8c6abc C

Isso combinará os dois commits A e B em um commit AB .

Encontrado nesta resposta .

kostmo
fonte
126

Você tentou:

git rebase -i A

É possível começar assim se você continuar com, em editvez de squash:

edit e97a17b B
pick asd314f C

então corra

git reset --soft HEAD^
git commit --amend
git rebase --continue

Feito.

David Lichteblau
fonte
4
Se você estiver fazendo isso para corrigir silenciosamente uma lista do github, terá que adicionar -m "initial" ao commit. ;-)
Bruno Bronosky
1
git rebase --abortpara começar de novo e fazê-lo da maneira certa (não esmagando o primeiro commit no editor) #
302 oma
66

Afoi o commit inicial, mas agora você quer Bser o commit inicial. Os commit do git são árvores inteiras, e não diffs, mesmo que sejam normalmente descritos e visualizados em termos do diff que eles introduzem.

Esta receita funciona mesmo se houver vários commits entre A e B e B e C.

# Go back to the last commit that we want
# to form the initial commit (detach HEAD)
git checkout <sha1_for_B>

# reset the branch pointer to the initial commit,
# but leaving the index and working tree intact.
git reset --soft <sha1_for_A>

# amend the initial tree using the tree from 'B'
git commit --amend

# temporarily tag this new initial commit
# (or you could remember the new commit sha1 manually)
git tag tmp

# go back to the original branch (assume master for this example)
git checkout master

# Replay all the commits after B onto the new initial commit
git rebase --onto tmp <sha1_for_B>

# remove the temporary tag
git tag -d tmp
CB Bailey
fonte
1
isso provoca um rebase interativo enorme quando eu faço ogit rebase --onto tmp <sha1_for_B>
Alex
Considerando que eu tive um novo repositório com apenas dois commits (que eu queria reunir em um), isso funcionou perfeitamente para mim. Obrigado @CB Bailey
RominRonin
10

No caso de rebase interativa, você deve fazer isso antes de A para que a lista seja:

pick A
pick B
pick C

tornar-se:

pick A
squash B
pick C

Se A é o commit inicial, você precisa ter um commit inicial diferente antes de A. Git pensa em diferenças, ele funcionará na diferença entre (A e B) e (B e C). Portanto, a abóbora não está funcionando no seu exemplo.

Loki
fonte
9

No caso de você ter centenas ou milhares de confirmações, usando a resposta de kostmo de

git rebase -i --root

pode ser impraticável e lento, apenas devido ao grande número de confirmações que o script rebase precisa processar duas vezes , uma vez para gerar a lista interativa do editor de rebase (onde você seleciona qual ação executar para cada confirmação) e uma vez para realmente executar o re-aplicação de confirmações.

Aqui está uma solução alternativa que evitará o custo de tempo para gerar a lista do editor de rebase interativo ao não usar um rebase interativo em primeiro lugar. Dessa forma, é semelhante à solução de Charles Bailey . Você simplesmente cria um ramo órfão a partir do segundo commit e, em seguida, refaz todos os commit descendentes sobre ele:

git checkout --orphan orphan <second-commit-sha>
git commit -m "Enter a commit message for the new root commit"
git rebase --onto orphan <second-commit-sha> master

Documentação

Comunidade
fonte
1

Em uma questão relacionada, eu consegui criar uma abordagem diferente para a necessidade de esmagar o primeiro commit, que é, assim, torná-lo o segundo.

Se você estiver interessado: git: como inserir um commit como o primeiro, alterando todos os outros?

kch
fonte
Seria melhor se a resposta fosse repetida aqui também? Não tenho certeza.
0

Comando git para esquadrão: git rebase -i HEAD ~ [número de confirmações]

Digamos que você tenha um histórico abaixo do git commit:


pick 5152061 feat: Adicionado suporte para salvar a imagem. (A)
escolha 39c5a04 Correção: correções de erros. (B)
escolha a correção 839c6b3: conflito resolvido. (C)

Agora você deseja compactar A e B para AB, execute as etapas abaixo:


pick 5152061 feat: Adicionado suporte para salvar a imagem. (A)
s 39c5a04 Correção: correções de erros. (B)
escolha a correção 839c6b3: conflito resolvido. (C)

Nota: para o squash commit, podemos usar squash ou s. O resultado final será:
escolha 5152061 feat: Adicionado suporte para salvar a imagem. (AB)
escolha a correção 839c6b3: conflito resolvido. (C)

Sumit
fonte
-1

Você precisa executar um pouco de mágica na linha de comando.

git checkout -b a A
git checkout B <files>
git commit --amend
git checkout master
git rebase a

Isso deve deixar você com um ramo que possui AB e C como confirmados.

Bombe
fonte
Como os antigos e novos commits iniciais não têm ancestral comum, você pode obter alguns conflitos desnecessários, pois o git tenta aplicar toda a história do master em um, mesmo que eles tenham uma árvore em comum. Usando a opção --onto para git rebase, você pode informar ao git o local correto para começar a aplicar.
CB Bailey