Como esmagar todos os commits git em um?

Como você esmaga todo o seu repository até o primeiro commit?

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

Talvez a maneira mais fácil seja apenas criar um novo repository com o estado atual da cópia de trabalho. Se você quiser manter todas as mensagens de commit você pode primeiro fazer o git log > original.log e então editar isso para a sua mensagem de commit inicial no novo repository:

 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 

Em versões recentes do git, você pode usar o git rebase --root -i .

Para cada commit, exceto o primeiro, altere pick to squash .

Atualizar

Eu fiz um alias git squash-all .
Exemplo de uso : git squash-all "a brand new start" .

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

Aviso : lembre-se de fornecer um comentário, caso contrário, a mensagem de confirmação padrão “A new start” será 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' 

Um forro

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

Nota : aqui ” A new start ” é apenas um exemplo, sinta-se à vontade para usar seu próprio idioma.

TL; DR

Não há necessidade de squash, use git commit-tree para criar um commit órfão e seguir com ele.

Explicar

  1. crie um único commit via git commit-tree

    Qual git commit-tree HEAD^{tree} -m "A new start" é:

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

    A expressão HEAD^{tree} significa o object de tree correspondente a HEAD , ou seja, a ponta de sua ramificação atual. veja Tree-Objects e Commit-Objects .

  2. redefinir o ramo atual para o novo commit

    Em seguida, git reset simplesmente redefina a ramificação atual para o object de commit recém-criado.

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

Variação: Novo Repo de um Modelo de Projeto

Isso é útil para criar o “commit inicial” em um novo projeto usando outro repository como o template / archetype / seed / skeleton. 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 repository de modelos como um controle remoto ( origin ou não) e recolhe o histórico do repository de modelos em seu commit inicial.

Se tudo o que você quer fazer é esmagar todos os seus commits até o commit root, então enquanto

 git rebase --interactive --root 

pode funcionar, é impraticável para um grande número de commits (por exemplo, centenas de commits), porque a operação de rebase provavelmente rodará muito lentamente para gerar a lista de commits do editor interativo de rebase, assim como executará o rebase em si.

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

Solução alternativa # 1: ramos órfãos

Você pode simplesmente criar uma nova ramificação órfã na ponta (ou seja, a confirmação mais recente) da sua ramificação atual. Esta ramificação órfã forma a consolidação inicial da raiz de uma tree de histórico de commit inteiramente nova e separada, que é efetivamente equivalente a esmagar todos os seus commits:

 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:

  • git-checkout (1) Página do manual .

Solução alternativa # 2: soft reset

Outra solução eficiente é simplesmente usar uma reconfiguração mista ou soft para o commit :

 git branch beforeReset git reset --soft  git commit --amend # Verify that the new amended root is no different # from the previous branch state git diff beforeReset 

Documentação:

  • git-reset (1) Página do manual .
 echo "message" | git commit-tree HEAD^{tree} 

Isto irá criar um commit órfão com a tree de HEAD e gerar seu nome (SHA-1) no stdout. Então restaure sua filial lá.

 git reset SHA-1 

Aqui está como acabei fazendo isso, apenas no caso de funcionar para outra pessoa:

Lembre-se de que sempre há risco em fazer coisas como essa, e nunca é uma má ideia criar uma ramificação de salvamento antes de iniciar.

Comece registrando

 git log --oneline 

Role até primeiro commit, copie o SHA

 git reset --soft <#sha#> 

Substitua <#sha#> w / o SHA copiado do log

 git status 

Certifique-se de que tudo está verde, caso contrário, execute git add -A

 git commit --amend 

Alterar todas as alterações atuais para o primeiro commit atual

Agora force empurrar esse ramo e ele irá sobrescrever o que está lá.

A maneira mais fácil é usar o comando ‘encanamento’ update-ref para excluir o ramo atual.

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

Isso coloca você de volta no estado ‘initial commit’, onde você pode começar com um novo commit inicial.

 git update-ref -d refs/heads/master git commit -m "New initial commit" 

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 parecido com isto:

 git reset HEAD~1 git add -A git commit --amend 

Primeiro, esmague todos os seus commits em um único commit usando git rebase --interactive . Agora você fica com dois commits para squash. Para fazer isso, leia qualquer um dos

  • Como faço para combinar os dois primeiros commits de um repository Git?
  • git: como esmagar os dois primeiros commits?

Para esmagar usando enxertos

Adicione um arquivo .git/info/grafts , coloque lá o hash de commit que você quer tornar sua raiz

git log irá agora iniciar a partir desse commit

Para torná-lo ‘real’, execute git filter-branch

“Solução alternativa # 1: ramos órfãos” ajuda-me.

“git rebase –interactive –root” preso no conflito de arquivos gitignored.

Esta resposta melhora em um par acima (por favor vote-os), assumindo que além de criar um commit (no-no-history sem pais), você também quer manter todos os commit-data daquele commit:

  • Autor (nome e email)
  • Data de autoria
  • Commiter (nome e email)
  • Data confirmada
  • Commmit log message

É claro que o commit-SHA do novo / single commit irá mudar, porque representa uma nova (não) história, tornando-se um parentless / root-commit.

Isto pode ser feito lendo o git log e definindo algumas variables ​​para git commit-tree . Supondo que você queira criar um único commit do master em um novo branch one-commit , retendo os commit-data 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} < 

Eu costumo fazer assim:

  • Certifique-se de que tudo está comprometido e anote o ID de confirmação mais recente, caso algo dê errado, ou crie uma ramificação separada como o 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 aparecerão agora prontas para serem confirmadas.

  • Execute git commit --amend -m "initial commit" para emendar seu commit no primeiro commit e alterar a mensagem de commit, ou se você quiser manter a mensagem de commit existente, você pode executar git commit --amend --no-edit

  • Executar git push -f para forçar a empurrar suas mudanças

criar um backup

 git branch backup 

redefinir para o commit especificado

 git reset --soft  

adicionar todos os arquivos ao teste

 git add . 

commit sem atualizar a mensagem

 git commit --amend --no-edit 

empurrar novo ramo com commits esmagados para repo

 git push -f 

Em uma linha de 6 palavras

 git checkout --orphan new_root_branch && git commit