Como esmagar todos os commits do git em um?

480

Como você compacta todo o seu repositório até o primeiro commit?

Posso mudar para o primeiro commit, mas isso me deixaria com 2 commits. Existe uma maneira de referenciar o commit antes do primeiro?

Verhogen
fonte
8
"o commit antes do primeiro"?
InnaM 01/11/2009
31
@innaM - É o commit primordial que gerou o git . (O humor das esperanças passa bem o suficiente pela interweb).
ripper234
11
Para aqueles que vierem mais tarde a esta pergunta, use a resposta mais moderna .
Droogans
1
Relacionado, mas não duplicado ( --rootna verdade, não é a melhor solução para esmagar todos os commits, se houver muitos deles para esmagar): Combine os dois primeiros commits de um repositório Git? .
2
Imo: este é o melhor do @MrTux: stackoverflow.com/questions/30236694/… .
J0hnG4lt

Respostas:

129

Talvez a maneira mais fácil seja apenas criar um novo repositório com o estado atual da cópia de trabalho. Se você deseja manter todas as mensagens de confirmação, você pode primeiro fazer git log > original.loge editar isso para sua mensagem de confirmação inicial no novo repositório:

rm -rf .git
git init
git add .
git commit

ou

git log > original.log
# edit original.log as desired
rm -rf .git
git init
git add .
git commit -F original.log
Pat Notz
fonte
62
mas você está perdendo ramos com este método
Olivier Refalo
150
O Git evoluiu desde que essa resposta foi dada. Não, não existe uma maneira mais simples e melhor: git rebase -i --root. Veja: stackoverflow.com/a/9254257/109618
David J.
5
Isso pode funcionar para alguns casos, mas essencialmente não é a resposta para a pergunta. Com esta receita, você perde toda a sua configuração e todos os outros ramos também.
Iwein
3
Esta é uma solução horrível que é desnecessariamente destrutiva. Por favor, não use.
Daniel Kamil Kozar
5
também quebrará submódulos. -1
Krum
694

A partir do git 1.6.2 , você pode usar git rebase --root -i.

Para cada confirmação, exceto a primeira, altere pickpara squash.

Jordan Lewis
fonte
49
Adicione um exemplo de comando completo e funcional que responda à pergunta original.
Jake13
29
Eu gostaria de ler isso antes de explodir todo o meu repositório, como a resposta aceita diz: /
Mike Chamberlain
38
Esta resposta é ok , mas se você está rebasing interativamente mais do que, digamos, 20 commits, o rebase interativo provavelmente será muito lento e pesado. Você provavelmente terá dificuldade em tentar esmagar centenas ou milhares de confirmações. Eu aceitaria uma redefinição suave ou mista da confirmação raiz e, em seguida, a confirmação novamente nesse caso.
20
@Pred Não use squashpara todas as confirmações. O primeiro precisa ser pick.
Geert
14
Se você tiver muitos commits, é difícil alterar 'pick' para 'squash' manualmente. Use :% s / pick / squash / g na linha de comando do VIM para fazer isso mais rapidamente.
eilas
314

Atualizar

Eu fiz um pseudônimo git squash-all.
Exemplo de utilização : git squash-all "a brand new start".

[alias]
  squash-all = "!f(){ git reset $(git commit-tree HEAD^{tree} -m \"${1:-A new start}\");};f"

Embargo : lembre-se de fornecer um comentário, caso contrário, a mensagem de confirmação padrão "Um novo começo" seria usada.

Ou você pode criar o alias com o seguinte comando:

git config --global alias.squash-all '!f(){ git reset $(git commit-tree HEAD^{tree} -m "${1:-A new start}");};f'

One Liner

git reset $(git commit-tree HEAD^{tree} -m "A new start")

Nota : aqui "A new start " é apenas um exemplo, fique à vontade para usar seu próprio idioma.

TL; DR

Não há necessidade de esmagar, use git commit-tree para criar um commit órfão e siga em frente.

Explicar

  1. crie uma única confirmação via git commit-tree

    O que git commit-tree HEAD^{tree} -m "A new start"faz é:

    Cria um novo objeto de confirmação com base no objeto em árvore fornecido e emite o novo ID do objeto de confirmação no stdout. A mensagem de log é lida a partir da entrada padrão, a menos que sejam fornecidas as opções -m ou -F.

    A expressão HEAD^{tree}significa o objeto de árvore correspondente a HEAD, ou seja, a ponta do seu ramo atual. veja Árvore-objetos e commit-Objects .

  2. redefinir a ramificação atual para o novo commit

    Em seguida, git resetbasta redefinir a ramificação atual para o objeto de confirmação recém-criado.

Dessa forma, nada no espaço de trabalho é tocado, nem há necessidade de rebase / squash, o que o torna muito rápido. E o tempo necessário é irrelevante para o tamanho do repositório ou a profundidade do histórico.

Variação: novo repositório a partir de um modelo de projeto

Isso é útil para criar o "commit inicial" em um novo projeto usando outro repositório como modelo / arquétipo / semente / esqueleto. Por exemplo:

cd my-new-project
git init
git fetch --depth=1 -n https://github.com/toolbear/panda.git
git reset --hard $(git commit-tree FETCH_HEAD^{tree} -m "initial commit")

Isso evita adicionar o repositório de modelo como um controle remoto ( originou não) e recolhe o histórico do repositório de modelo no seu commit inicial.

ryenus
fonte
6
A sintaxe revisão git (HEAD ^ {tree}) sintaxe é explicado aqui no caso de alguém estava me perguntando: jk.gs/gitrevisions.html
Colin Bowern
1
Isso redefine o repositório local e remoto, ou apenas um deles?
Aleclarson
4
@aleclarson, isso redefine apenas a ramificação atual no repositório local, use git push -fpara propagação.
Ryenus # 28/14
2
Encontrei esta resposta enquanto procurava uma maneira de iniciar um novo projeto a partir de um repositório de modelos de projeto que não envolvesse git clone. Se você adicionar --harda git resete alternar HEADcom FETCH_HEADno git commit-treevocê pode criar cometer um inicial após buscar o repo modelo. Editei a resposta com uma seção no final demonstrando isso.
toolbear
4
Você pode se livrar desse "aviso", mas apenas usando #${1?Please enter a message}
Elliot Cameron
172

Se tudo o que você deseja fazer é compactar todos os seus commit até o commit raiz, então enquanto

git rebase --interactive --root

pode funcionar, é impraticável para um grande número de confirmações (por exemplo, centenas de confirmações), porque a operação de rebase provavelmente será executada muito lentamente para gerar a lista de confirmação do editor de rebase interativo, bem como executar a rebase.

Aqui estão duas soluções mais rápidas e eficientes quando você está esmagando um grande número de confirmações:

Solução alternativa nº 1: filiais órfãs

Você pode simplesmente criar um novo ramo órfão na ponta (ou seja, o commit mais recente) do seu ramo atual. Esse ramo órfão forma o commit raiz inicial de uma árvore de histórico de commit totalmente nova e separada, que é efetivamente equivalente a esmagar todos os seus commit:

git checkout --orphan new-master master
git commit -m "Enter commit message for your new initial commit"

# Overwrite the old master branch reference with the new one
git branch -M new-master master

Documentação:

Solução alternativa nº 2: redefinição suave

Outra solução eficiente é simplesmente usar uma redefinição mista ou suave para o commit raiz <root>:

git branch beforeReset

git reset --soft <root>
git commit --amend

# Verify that the new amended root is no different
# from the previous branch state
git diff beforeReset

Documentação:


fonte
22
Solução alternativa # 1: galhos órfãos - rochas!
Thomas
8
Solução alternativa # 1 FTW. Apenas para adicionar, se você deseja enviar suas alterações para o controle remoto, faça git push origin master --force.
Eddy Verbruggen
1
não se esqueçagit push --force
NecipAllef
Não faça uma ramificação órfã no código (por exemplo, não faça a solução alternativa nº 1 acima) se estiver prestes a enviar para uma solicitação de recebimento aberta do Github !!! O Github fechará o seu PR porque a cabeça atual não é descendente da cabeça armazenada sha .
Andrew Mackie
A solução alternativa nº 1 também evita os conflitos de mesclagem que podem ocorrer quando o squash é confirmado
FernAndr
52
echo "message" | git commit-tree HEAD^{tree}

Isso criará um commit órfão com a árvore do HEAD e produzirá seu nome (SHA-1) no stdout. Em seguida, basta redefinir sua filial lá.

git reset SHA-1
kusma
fonte
27
git reset $(git commit-tree HEAD^{tree} -m "commit message")tornaria mais fácil.
Ryenus
4
^ ISTO! - deve ser uma resposta. Não tenho certeza se era a intenção do autor, mas era minha (precisava de um repo intocado com um único commit, e isso faz o trabalho).
chesterbr
@ryenus, sua solução fez exatamente o que eu estava procurando. Se você adicionar seu comentário como resposta, eu o aceito.
tldr
2
O motivo pelo qual eu não sugeri a variante subshell, é que ela não funcionará no cmd.exe no Windows.
Kusma
Nos prompts do Windows, pode ser necessário citar o último parâmetro: echo "message" | git commit-tree "HEAD^{tree}"
Bernard
41

Eis como acabei fazendo isso, caso funcione para outra pessoa:

Lembre-se de que sempre existe o risco de fazer coisas assim, e nunca é uma má idéia criar uma ramificação salva antes de iniciar.

Comece registrando

git log --oneline

Role para confirmar primeiro, copie SHA

git reset --soft <#sha#>

Substitua <#sha#>com o SHA copiado do log

git status

Verifique se tudo está verde, caso contrário, execute git add -A

git commit --amend

Alterar todas as alterações atuais no primeiro commit atual

Agora force o push deste ramo e ele substituirá o que está lá.

Logan
fonte
1
Excelente opção! Realmente simples.
usar o seguinte comando
1
Isso é mais do que fantástico! Obrigado!
275166 Matt Comarlinicki
1
Observe que isso realmente parece deixar a história por aí. Você ficou órfão, mas ainda está lá.
Brad
Resposta muito útil ... mas você deve estar ciente de que, após o comando de correção, você se encontrará no editor vim com sua sintaxe especial. ESC, ENTER,: x é seu amigo.
Erich Kuester
Excelente opção!
Danivicario 19/07/19
36

Eu li algo sobre o uso de enxertos, mas nunca investiguei muito.

De qualquer forma, você pode esmagar os últimos 2 commits manualmente com algo como isto:

git reset HEAD~1
git add -A
git commit --amend
R. Martinho Fernandes
fonte
4
Esta é realmente a resposta que eu estava procurando, gostaria que fosse a aceita!
Jay
Esta é uma resposta incrível
Master Yoda
36

A maneira mais fácil é usar o comando 'encanamento' update-ref para excluir a ramificação atual.

Você não pode usar git branch -D , pois possui uma válvula de segurança para impedir a exclusão da ramificação atual.

Isso coloca você de volta ao estado 'commit inicial', onde você pode começar com um commit inicial novo.

git update-ref -d refs/heads/master
git commit -m "New initial commit"
CB Bailey
fonte
15

Em uma linha de 6 palavras

git checkout --orphan new_root_branch  &&  git commit
kyb
fonte
@AlexanderMills, você deve ler git help checkoutsobre--orphan
KYB
você pode vincular os documentos para que todos os que leem não precisem procurar manualmente?
Alexander Mills
1
fácil. aqui estágit help checkout --orphan
kyb 10/05/19
8

criar um backup

git branch backup

redefinir para confirmação especificada

git reset --soft <#root>

depois adicione todos os arquivos ao teste

git add .

confirmar sem atualizar a mensagem

git commit --amend --no-edit

empurrar novo ramo com squashed compromete-se a repo

git push -f
David Morton
fonte
Isso preservará as mensagens de confirmação anteriores?
not2qubit
1
@ not2qubit no isso não preservará as mensagens de confirmação anteriores, em vez de confirmar # 1, confirmar # 2, confirmar # 3, você receberá todas as alterações nesses commit agrupadas em um único commit # 1. A confirmação nº 1 será a <root>confirmação para a qual você redefinirá. git commit --amend --no-editconfirmará todas as alterações na confirmação atual, <root>sem a necessidade de editar a mensagem de confirmação.
David Morton
5

Para esmagar usando enxertos

Adicione um arquivo .git/info/grafts, coloque lá o hash de confirmação que você deseja que seja seu root

git log agora começará a partir desse commit

Para torná-lo 'real' executado git filter-branch

dimus
fonte
1

Esta resposta é aprimorada em algumas das opções acima (vote-as), assumindo que, além de criar o único commit (sem pais sem histórico), você também deseja manter todos os dados de commit desse commit:

  • Autor (nome e email)
  • Data de criação
  • Commiter (nome e email)
  • Data de confirmação
  • Confirmar mensagem de log

É claro que o commit-SHA do novo / único commit será alterado, pois representa um novo (não) histórico, tornando-se um commit sem pai / root.

Isso pode ser feito lendo git loge configurando algumas variáveis ​​para git commit-tree. Supondo que você deseja criar uma única confirmação masterem uma nova ramificação one-commit, mantendo os dados de confirmação acima:

git checkout -b one-commit master ## create new branch to reset
git reset --hard \
$(eval "$(git log master -n1 --format='\
COMMIT_MESSAGE="%B" \
GIT_AUTHOR_NAME="%an" \
GIT_AUTHOR_EMAIL="%ae" \
GIT_AUTHOR_DATE="%ad" \
GIT_COMMITTER_NAME="%cn" \
GIT_COMMITTER_EMAIL="%ce" \
GIT_COMMITTER_DATE="%cd"')" 'git commit-tree master^{tree} <<COMMITMESSAGE
$COMMIT_MESSAGE
COMMITMESSAGE
')
javabrett
fonte
1

Para fazer isso, você pode redefinir seu repositório git local para a primeira hashtag de confirmação, para que todas as suas alterações após essa confirmação sejam desassistidas, e você pode confirmar com a opção --amend.

git reset your-first-commit-hashtag
git add .
git commit --amend

E edite o primeiro nome de confirmação, se necessário, e salve o arquivo.

T.EL MOUSSAOUI
fonte
1

Para mim, funcionou assim: eu tinha 4 confirmações no total e usei o rebase interativo:

git rebase -i HEAD~3

O primeiro commit permanece e eu fiz 3 commit mais recentes.

Caso você esteja preso no editor que aparece a seguir, verá algo como:

pick fda59df commit 1
pick x536897 commit 2
pick c01a668 commit 3

Você precisa primeiro confirmar e esmagar os outros. O que você deve ter é:

pick fda59df commit 1
squash x536897 commit 2
squash c01a668 commit 3

Para isso, use a tecla INSERT para alterar o modo 'inserir' e 'editar'.

Para salvar e sair do editor, use :wq. Se o cursor estiver entre essas linhas de confirmação ou em outro lugar, pressione ESC e tente novamente.

Como resultado, tive dois commits: o primeiro que permaneceu e o segundo com a mensagem "Esta é uma combinação de 3 commits".

Verifique os detalhes aqui: https://makandracards.com/makandra/527-squash-several-git-commits-into-a-single-commit

Vlad
fonte
0

Eu costumo fazer assim:

  • Verifique se tudo está confirmado e anote o último ID de confirmação, caso algo dê errado, ou crie uma ramificação separada como backup.

  • Execute git reset --soft `git rev-list --max-parents=0 --abbrev-commit HEAD`para redefinir sua cabeça para o primeiro commit, mas deixe seu índice inalterado. Todas as alterações desde o primeiro commit agora parecerão prontas para serem confirmadas.

  • Execute git commit --amend -m "initial commit"para alterar seu commit no primeiro commit e altere a mensagem de commit ou, se desejar manter a mensagem de commit existente, você poderá executargit commit --amend --no-edit

  • Corra git push -fpara forçar a pressionar suas alterações

Kent Munthe Caspersen
fonte