Pergunta

Eu perguntei antes sobre como de squash os dois primeiros commits em um git repositório.

Enquanto as soluções são bastante interessante e não realmente como mente-deformação como algumas outras coisas em git, eles ainda estão um pouco do saco proverbial de dor se você precisará repetir o procedimento várias vezes ao longo do desenvolvimento do seu projeto .

Então, eu prefiro ir através da dor apenas uma vez e, em seguida, ser capaz de usar sempre o rebase interativo padrão.

O que eu quero fazer, então, é ter uma inicial vazia cometer o que existe unicamente para o propósito de ser o primeiro. Nenhum código, sem nada. Apenas ocupando espaço para que ele possa ser a base para rebase.

A minha pergunta é, então, ter um repositório existente, como faço para ir sobre como inserir uma nova, esvaziar cometer antes do primeiro, e mudando todos os outros para a frente?

Foi útil?

Solução

Mid-2017 resposta

Criando um novo completamente vazio cometer sem efeitos colaterais provavelmente o melhor é feito usando encanamento do Git diretamente. Fazê-lo dessa maneira evita quaisquer efeitos secundários: não tocar na cópia de trabalho ou o índice, sem ramos temporários para limpar, etc. Assim:

  1. Para criar um commit, precisamos de uma árvore de diretórios para ele, por isso criamos um vazio em primeiro lugar:

    tree=`git hash-object -wt tree --stdin < /dev/null`
    
  2. Agora nós podemos envolver um commit em torno dele:

    commit=`git commit-tree -m 'root commit' $tree`
    
  3. E agora podemos rebase em que:

    git rebase --onto $commit --root master
    

E é isso. Você pode reorganizar essa coisa toda em um one-liner, se você sabe que seu shell bem o suficiente.

(NB .: na prática eu agora usar filter-branch. Will edição que mais tarde.)


resposta Histórico (referenciado por outras respostas)

Aqui está uma implementação mais limpo da mesma solução, em que ele funciona sem a necessidade de criar um repositório extra, futz redor com controles remotos, e uma cabeça destacada correto:

# first you need a new empty branch; let's call it `newroot`
git checkout --orphan newroot
git rm -rf .

# then you apply the same steps
git commit --allow-empty -m 'root commit'
git rebase --onto newroot --root master
git branch -d newroot

Voila, você acabou no master com a sua história reescrito para incluir uma raiz vazio cometer.


NB .: em versões antigas do Git que não possuem o interruptor --orphan para checkout, você precisa do encanamento para criar uma ramificação vazia:

git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d

Outras dicas

Merge do comentário de Aristóteles Pagaltzis e respostas de Uwe Kleine-König e Richard Bronosky.

git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d
# touch .gitignore && git add .gitignore # if necessary
git commit --allow-empty -m 'initial'
git rebase --onto newroot --root master
git branch -d newroot

(apenas para colocar tudo em um só lugar)

Eu gosto de resposta de Aristóteles. Mas descobriu que, para um grande repositório (> 5000 commits) filter-branch funciona melhor do que rebase por várias razões 1) é mais rápido 2) não requer intervenção humana quando há um conflito de mesclagem. 3) ele pode reescrever as tags - preservá-los. Nota que as obras filter-branch porque não há nenhuma dúvida sobre o conteúdo de cada commit - é exatamente o mesmo que antes deste 'rebase'

.

Meus passos são:

# first you need a new empty branch; let's call it `newroot`
git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d

# then you apply the same steps
git commit --allow-empty -m 'root commit'

# then use filter-branch to rebase everything on newroot
git filter-branch --parent-filter 'sed "s/^\$/-p <sha of newroot>/"' --tag-name-filter cat master

Note que o 'gato --tag-name-filter' Opções significa que as tags será reescrito para apontar para os commits criados recentemente.

Eu usei pedaços de Aristóteles e resposta de Kent com sucesso:

# first you need a new empty branch; let's call it `newroot`
git checkout --orphan newroot
git rm -rf .
git commit --allow-empty -m 'root commit'
git filter-branch --parent-filter \
'sed "s/^\$/-p <sha of newroot>/"' --tag-name-filter cat -- --all
# clean up
git checkout master
git branch -D newroot
# make sure your branches are OK first before this...
git for-each-ref --format="%(refname)" refs/original/ | \
xargs -n 1 git update-ref -d

Isso também irá reescrever todos os ramos (e não apenas master), além de tags.

git rebase --root --onto $emptyrootcommit

deve fazer o truque facilmente

I ficou animado e escreveu uma versão 'idempotent' deste script bom ... ele sempre irá inserir cometer o mesmo vazio, e se você executá-lo duas vezes, isso não muda seus cometem hashes cada vez. Então, aqui está minha opinião sobre git-inserção vazia-root :

#!/bin/sh -ev
# idempotence achieved!
tmp_branch=__tmp_empty_root
git symbolic-ref HEAD refs/heads/$tmp_branch
git rm --cached -r . || true
git clean -f -d
touch -d '1970-01-01 UTC' .
GIT_COMMITTER_DATE='1970-01-01T00:00:00 +0000' git commit \
  --date='1970-01-01T00:00:00 +0000' --allow-empty -m 'initial'
git rebase --committer-date-is-author-date --onto $tmp_branch --root master
git branch -d $tmp_branch

Será que vale a complexidade extra? talvez não, mas eu vou estar usando este.

Este também deve permitir para executar esta operação em várias cópias clonadas do repo, e acabar com os mesmos resultados, então eles ainda são compatíveis ... testes ... sim, não, trabalho, mas necessidade também para apagar e adicionar seus controles remotos de novo, por exemplo:

git remote rm origin
git remote add --track master user@host:path/to/repo

Bem, aqui está o que eu vim com:

# Just setting variables on top for clarity.
# Set this to the path to your original repository.
ORIGINAL_REPO=/path/to/original/repository

# Create a new repository…
mkdir fun
cd fun
git init
# …and add an initial empty commit to it
git commit --allow-empty -m "The first evil."

# Add the original repository as a remote
git remote add previous $ORIGINAL_REPO
git fetch previous

# Get the hash for the first commit in the original repository
FIRST=`git log previous/master --pretty=format:%H  --reverse | head -1`
# Cherry-pick it
git cherry-pick $FIRST
# Then rebase the remainder of the original branch on top of the newly 
# cherry-picked, previously first commit, which is happily the second 
# on this branch, right after the empty one.
git rebase --onto master master previous/master

# rebase --onto leaves your head detached, I don't really know why)
# So now you overwrite your master branch with the newly rebased tree.
# You're now kinda done.
git branch -f master
git checkout master
# But do clean up: remove the remote, you don't need it anymore
git remote rm previous

Eu acho que o uso de git replace e git filter-branch é uma solução melhor do que usar um git rebase:

  • melhor preformance
  • fácil e menos arriscado (você pode verificar o seu resultado em cada etapa e desfazer o que você fez ...)
  • trabalhar bem com vários ramos com resultados garantidos

A idéia por trás dele é:

  • Criar um novo vazio cometer longe no passado
  • Substitua a raiz comprometer por um cometer exactamente semelhante, exceto que a nova raiz commit é adicionado como um pai
  • Verifique se tudo está como o esperado e executar git filter-branch
  • Mais uma vez, verifique se tudo está OK e limpar os arquivos git não mais necessários

Aqui está um script para os 2 primeiros passos:

#!/bin/bash
root_commit_sha=$(git rev-list --max-parents=0 HEAD)
git checkout --force --orphan new-root
find . -path ./.git -prune -o -exec rm -rf {} \; 2> /dev/null
git add -A
GIT_COMMITTER_DATE="2000-01-01T12:00:00" git commit --date==2000-01-01T12:00:00 --allow-empty -m "empty root commit"
new_root_commit_sha=$(git rev-parse HEAD)

echo "The commit '$new_root_commit_sha' will be added before existing root commit '$root_commit_sha'..."

parent="parent $new_root_commit_sha"
replacement_commit=$(
 git cat-file commit $root_commit_sha | sed "s/author/$parent\nauthor/" |
 git hash-object -t commit -w --stdin
) || return 3
git replace "$root_commit_sha" "$replacement_commit"

Você pode executar esse script sem risco (mesmo que fazer um backup antes de fazer a ação que você nunca fez antes é uma boa ideia;)), e se o resultado não for o esperado, basta apagar os arquivos criados no .git/refs/replace pasta e tente novamente;)

Depois de ter verificado que o estado do repositório é o que você espera, execute o seguinte comando para atualizar a história da todos os ramos :

git filter-branch -- --all

Agora, você deve ver 2 histórias, o antigo eo novo (ver ajuda on filter-branch para mais informações). Você poderia comparar a 2 e verifique novamente se tudo está OK. Se você estiver satisfeito, exclua os arquivos não mais necessários:

rm -rf ./.git/refs/original
rm -rf ./.git/refs/replace

Você poderia retornar ao seu ramo master e excluir o ramo temporária:

git checkout master
git branch -D new-root

Agora, tudo deve ser feito;)

Aqui está uma simples one-liner que pode ser usado para adicionar um vazio cometer no início de um repositório, se você esqueceu de criar um vazio commit imediatamente após "git init":

git rebase --root --onto $(git commit-tree -m 'Initial commit (empty)' 4b825dc642cb6eb9a060e54bf8d69288fbee4904)

Aqui está o meu script bash baseado em Kent 's resposta com melhorias:

  • ele verifica o ramo original, não apenas master, quando feito;
  • Eu tentei evitar o ramo temporário, mas git checkout --orphan só funciona com um ramo, não state-cabeça individual, por isso é verificado tempo suficiente para fazer a nova raiz cometer e, em seguida, eliminado;
  • utiliza o hash da nova raiz cometer durante o filter-branch (Kent deixou um espaço reservado lá para substituição manual);
  • a operação filter-branch reescreve apenas as agências locais, não remotos demasiado
  • os metadados autor e committer é padronizado de forma que a raiz cometer é idêntico em repositórios.

#!/bin/bash

# Save the current branch so we can check it out again later
INITIAL_BRANCH=`git symbolic-ref --short HEAD`
TEMP_BRANCH='newroot'

# Create a new temporary branch at a new root, and remove everything from the tree
git checkout --orphan "$TEMP_BRANCH"
git rm -rf .

# Commit this empty state with generic metadata that will not change - this should result in the same commit hash every time
export GIT_AUTHOR_NAME='nobody'
export GIT_AUTHOR_EMAIL='nobody@example.org'
export GIT_AUTHOR_DATE='2000-01-01T00:00:00+0000'
export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"
export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
git commit --allow-empty -m 'empty root'
NEWROOT=`git rev-parse HEAD`

# Check out the commit we just made and delete the temporary branch
git checkout --detach "$NEWROOT"
git branch -D "$TEMP_BRANCH"

# Rewrite all the local branches to insert the new root commit, delete the 
# original/* branches left behind, and check out the rewritten initial branch
git filter-branch --parent-filter "sed \"s/^\$/-p $NEWROOT/\"" --tag-name-filter cat -- --branches
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
git checkout "$INITIAL_BRANCH"

Para mudar a raiz cometer:

Em primeiro lugar, criar a cometer você quer que o primeiro.

Em segundo lugar, mudar a ordem dos commits usando:

git rebase -i --root

Um editor irá aparecer com os commits até a raiz cometer, como:

escolher 1234 mensagem antiga raiz

escolher 0294 A cometem no meio

escolher 5678 comprometer você quer colocar na raiz

Você pode então colocar a cometer você quer primeiro, colocando-o na primeira linha. No exemplo:

escolher 5678 comprometer você quer colocar na raiz

escolher 1234 mensagem antiga raiz

escolher 0294 A cometem no meio

Saia do editor a cometer fim terá mudado.

PS: Para alterar os usos editor git, execute:

git configuração --global core.editor name_of_the_editor_program_you_want_to_use

A seguir resposta Aristóteles Pagaltzis e outros, mas usando os comandos mais simples

zsh% git checkout --orphan empty     
Switched to a new branch 'empty'
zsh% git rm --cached -r .
zsh% git clean -fdx
zsh% git commit --allow-empty -m 'initial empty commit'
[empty (root-commit) 64ea894] initial empty commit
zsh% git checkout master
Switched to branch 'master'
zsh% git rebase empty
First, rewinding head to replay your work on top of it...
zsh% git branch -d empty 
Deleted branch empty (was 64ea894).

Observe o seu repo não deve conter nenhuma modificação locais à espera de ser comprometida.
Nota git checkout --orphan irá trabalhar em novas versões do git, eu acho.
Nota a maior parte do tempo git status dá dicas úteis.

Iniciar um novo repositório.

Defina a sua data de volta para a data de início que você deseja.

fazer tudo da maneira que você gostaria que você tivesse feito isso, ajustando a hora do sistema para refletir quando você tinha desejou que você tinha feito isso dessa maneira. arquivos de puxar a partir do repositório existente, conforme necessário, para evitar um monte de digitação desnecessária.

Quando você chegar aos dias de hoje, trocar os repositórios e você está feito.

Se você é uma loucura (estabelecido), mas razoavelmente inteligente (provável, porque você tem que ter uma certa quantidade de inteligência para pensar em idéias malucas como este), você vai roteiro do processo.

Isso também irá torná-lo mais agradável quando você decidir que quer o passado para ter acontecido alguma outra maneira daqui a uma semana.

Eu sei que este post é antigo, mas esta página é o primeiro quando pesquisando "inserção de cometer git".

Por que tornar as coisas simples complicado?

Você tem A-B-C e você quer que A-B-Z-C.

  1. git rebase -i trunk (ou qualquer coisa antes de B)
  2. mudança escolher para editar na linha B
  3. fazer as alterações: git add ..
  4. git commit (git commit --amend que irá editar B e não criar Z)

[Você pode fazer como muitos git commit como você quer aqui para inserir mais commits. Claro, você pode ter problemas com o passo 5, mas a resolução de fusão conflito com git é uma habilidade que você deve ter. Se não, a prática!]

  1. git rebase --continue

Simples, não é?

Se você entender git rebase, adicionando um 'root' comprometer não deve ser um problema.

Divirta-se com git!

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top