Se eu tenho um commit no passado que aponta para um pai, mas quero mudar o pai para o qual ele aponta, como eu faria isso?
Usando git rebase
. É o genérico "pegar commit (s) e colocá-los em um comando pai (base)" diferente no Git.
Algumas coisas a saber, no entanto:
Como os SHAs de confirmação envolvem seus pais, quando você altera o pai de uma determinada confirmação, o SHA mudará - assim como os SHAs de todos os commits que vierem depois dele (mais recentes que ele) na linha de desenvolvimento.
Se você estiver trabalhando com outras pessoas, e você já empurrou o commit em questão para onde eles o puxaram, a modificação do commit provavelmente é uma Bad Idea ™. Isso se deve ao número 1 e, portanto, à confusão resultante que os repositórios dos outros usuários encontrarão ao tentar descobrir o que aconteceu devido aos seus SHAs não mais corresponderem aos deles para as "mesmas" confirmações. (Consulte a seção "RECUPERANDO DO UPSTREAM REBASE" da página de manual vinculada para obter detalhes.)
Dito isto, se você estiver atualmente em uma ramificação com alguns commit que deseja mover para um novo pai, seria algo parecido com isto:
git rebase --onto <new-parent> <old-parent>
Isso vai passar tudo depois <old-parent>
no ramo atual para sentar em cima de <new-parent>
vez.
--onto
é usado! A documentação sempre foi completamente obscura para mim, mesmo depois de ler esta resposta!git rebase --onto <new-parent> <old-parent>
contou-me tudo sobre como usarrebase --onto
que todas as outras perguntas e documentos que li até agora falharam.rebase this commit on that one
ougit rebase --on <new-parent> <commit>
. Usar o pai antigo aqui não faz sentido para mim.git rebase <new-parent>
.Se acontecer que você precisa evitar a reestruturação das confirmações subseqüentes (por exemplo, porque uma reescrita do histórico seria insustentável), use o git replace (disponível no Git 1.6.5 e posterior).
Com a substituição acima estabelecida, quaisquer solicitações para o objeto B realmente retornarão o objeto C. O conteúdo de C é exatamente o mesmo que o conteúdo de B, exceto o primeiro pai (os mesmos pais (exceto o primeiro), a mesma árvore, mesma mensagem de confirmação).
As substituições estão ativas por padrão, mas podem ser desativadas usando a
--no-replace-objects
opção git (antes do nome do comando) ou configurando aGIT_NO_REPLACE_OBJECTS
variável de ambiente. As substituições podem ser compartilhadas pressionandorefs/replace/*
(além do normalrefs/heads/*
).Se você não gostar do commit-munging (feito com sed acima), poderá criar seu commit de substituição usando comandos de nível superior:
A grande diferença é que essa sequência não propaga os pais adicionais se B for um commit de mesclagem.
fonte
refs/replace/
hierarquia de referências. Se você precisar fazer isso apenas uma vez, poderá fazê-logit push yourremote 'refs/replace/*'
no repositório de origem egit fetch yourremote 'refs/replace/*:refs/replace/*'
nos repositórios de destino. Se você precisar fazer isso várias vezes, poderá adicionar esses refspecs a umaremote.yourremote.push
variável de configuração (no repositório de origem) e a umaremote.yourremote.fetch
variável de configuração (nos repositórios de destino).git replace --grafts
. Não tenho certeza de quando isso foi adicionado, mas seu objetivo é criar uma substituição igual à confirmação B, mas com os pais especificados.Observe que a alteração de uma confirmação no Git requer que todas as confirmações a seguir sejam alteradas. Isso é desencorajado se você publicou esta parte da história, e alguém pode ter construído seu trabalho na história que era antes da mudança.
A solução alternativa
git rebase
mencionada na resposta de Amber é usar o mecanismo de enxertos (consulte a definição de enxertos do Git no Git Glossary e a documentação do.git/info/grafts
arquivo na documentação do Git Repository Layout ) para alterar o pai de um commit, verifique se ele corrigiu alguma coisa com algum visualizador de histórico (gitk
,git log --graph
, etc.) e, em seguida, usegit filter-branch
(como descrito na seção "Exemplos" de sua página de manual) para torná-lo permanente (e em seguida remova o enxerto e, opcionalmente, remova os refs originais com backupgit filter-branch
ou reclone o repositório):NOTA !!! Esta solução é diferente da solução rebase em que
git rebase
seria rebase / transplante mudanças , enquanto solução de enxerto baseada seria commits simplesmente Reparent como é , não levando em conta as diferenças entre pai velho e novo pai!fonte
git replace
substituiu os enxertos git (supondo que você tenha o git 1.6.5 ou posterior).git-replace
+git-filter-branch
? No meu teste,filter-branch
parecia respeitar a substituição, refazendo a árvore inteira.git filter-branch
reescreve o histórico, respeitando os enxertos e substituições (mas no reescrito o histórico será discutido); push resultará em alterações não fasf-forward.git replace
cria substituições transferíveis no local; push será rápido, mas você precisará pressionarrefs/replace
para transferir substituições para ter o histórico corrigido. HTHPara esclarecer as respostas acima e conectar descaradamente meu próprio script:
Depende se você deseja "rebase" ou "reparent". Uma rebase , como sugerido por Amber , move-se por diferenças . Um reparador , como sugerido por Jakub e Chris , se desloca por instantâneos de toda a árvore. Se você deseja reparar, sugiro usar em
git reparent
vez de fazer o trabalho manualmente.Comparação
Suponha que você tenha a imagem à esquerda e deseje que ela se pareça com a imagem à direita:
O rebasing e o reparenting produzirão a mesma imagem, mas a definição de
C'
difere. Comgit rebase --onto A B
,C'
não conterá nenhuma alteração introduzida porB
. Withgit reparent -p A
,C'
será idêntico aC
(exceto queB
não estará na história).fonte
reparent
script; funciona lindamente; altamente recomendado . Também recomendo criar um novobranch
rótulo e paracheckout
esse ramo antes de chamar (como o rótulo do ramo será movido).Definitivamente, a resposta de @ Jakub me ajudou algumas horas atrás, quando eu estava tentando exatamente a mesma coisa que o OP.
No entanto,
git replace --graft
agora é a solução mais fácil para enxertos. Além disso, um grande problema com essa solução foi que o ramo de filtro me soltou todos os ramos que não foram mesclados ao ramo da HEAD. Então,git filter-repo
fez o trabalho de forma perfeita e sem falhas.Para obter mais informações: consulte a seção "Histórico de novo enxerto" nos documentos
fonte