Configurando o ponteiro pai git para um pai diferente

Se eu tiver um commit no passado que aponte para um dos pais, mas eu quero mudar o pai para o qual ele aponta, como eu faria isso?

   

Usando o git rebase . É o comando genérico “take commit (s) e plop ele / eles em um pai (base) diferente” no Git.

Algumas coisas para saber, no entanto:

  1. Uma vez que commits SHAs envolvem seus pais, quando você altera o pai de um dado commit, seu SHA irá mudar – assim como os SHAs de todos os commits que vierem depois dele (mais recentes que ele) na linha de desenvolvimento.

  2. Se você está trabalhando com outras pessoas e já colocou o commit em questão em público para onde eles o puxaram, modificar o commit é provavelmente uma Bad Idea ™. Isso se deve a # 1 e, portanto, à confusão resultante que os repositorys dos outros usuários encontrarão ao tentar descobrir o que aconteceu devido a seus SHAs não mais corresponderem aos deles para os “mesmos” commits. (Veja a seção “RECUPERANDO DO REINCASE DA UPSTREAM” da página de manual vinculada para detalhes.)

Dito isso, se você está atualmente em uma ramificação com alguns commits que deseja mover para um novo pai, seria algo como isto:

 git rebase --onto   

Isso moverá tudo depois de na ramificação atual para ficar em cima de .

Se acontecer de você precisar evitar rebasing dos commits subseqüentes (por exemplo, porque uma reescrita do histórico seria insustentável), então você pode usar o git replace (disponível no Git 1.6.5 e posterior).

 # …---o---A---o---o---… # # …---o---B---b---b---… # # We want to transplant B to be "on top of" A. # The tree of descendants from B (and A) can be arbitrarily complex. replace_first_parent() { old_parent=$(git rev-parse --verify "${1}^1") || return 1 new_parent=$(git rev-parse --verify "${2}^0") || return 2 new_commit=$( git cat-file commit "$1" | sed -e '1,/^$/s/^parent '"$old_parent"'$/parent '"$new_parent"'/' | git hash-object -t commit -w --stdin ) || return 3 git replace "$1" "$new_commit" } replace_first_parent BA # …---o---A---o---o---… # \ # C---b---b---… # # C is the replacement for B. 

Com a substituição acima estabelecida, quaisquer solicitações para o object B retornarão, na verdade, o object C. O conteúdo de C é exatamente o mesmo que o conteúdo de B, exceto para o primeiro pai (mesmos pais (exceto o primeiro), mesma tree, mesma mensagem de confirmação).

As substituições são ativas por padrão, mas podem ser ativadas usando a opção --no-replace-objects para git (antes do nome do comando) ou configurando a variável de ambiente GIT_NO_REPLACE_OBJECTS . As substituições podem ser compartilhadas pressionando-se refs/replace/* (além dos refs/heads/* normais).

Se você não gosta do commit-munging (feito com o sed acima), então você pode criar seu commit substituto usando comandos de nível mais alto:

 git checkout B~0 git reset --soft A git commit -CB git replace B HEAD git checkout - 

A grande diferença é que essa sequência não propaga os pais adicionais se B for um commit de mesclagem.

Note que alterar um commit no Git requer que todos os commits que seguem o mesmo tenham que ser alterados. Isto é 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.

Solução alternativa para git rebase mencionada na resposta de Amber é usar o mecanismo de enxertos (ver definição de enxertos Git no Git Glossary e documentação do arquivo .git/info/grafts na documentação do Git Repository Layout ) para alterar o pai de um commit, verifique se ele fez corrija com algum visualizador de histórico ( gitk , git log --graph , etc.) e então use git filter-branch (como descrito na seção “Examples” de sua manpage) para torná-lo permanente (e então remova o enxerto e opcionalmente remova os refs originais copiados em backup por git filter-branch , ou reclone repository):

 echo "$ commit-id $ graft-id" >> .git / info / enxertos
 git filter-branch $ graft-id..HEAD

NOTA !!! Esta solução é diferente da solução rebase em que git rebase iria rebase / mudanças de transplante, enquanto a solução baseada em enxertos seria simplesmente reparent compromete como é , não tendo em conta as diferenças entre pai antigo e novo pai!

Para esclarecer as respostas acima e descaradamente ligar meu próprio script:

Depende se você quer “rebase” ou “reparante”. Um rebase , como sugerido por Amber , se move em torno de diffs . Um reparador , como sugerido por Jakub e Chris , se move ao redor de instantâneos de toda a tree. Se você quiser reparent, sugiro usar git reparent ao invés de fazer o trabalho manualmente.

Comparação

Suponha que você tenha a foto à esquerda e queira que ela se pareça com a imagem à direita:

  C' / A---B---C A---B---C 

Ambos rebasing e reparenting produzirão a mesma imagem, mas a definição de C' difere. Com o git rebase --onto AB , C' não conterá quaisquer alterações introduzidas por B Com git reparent -p A , C' será idêntico a C (exceto que B não estará no histórico).