Establecer el puntero padre de git en un padre diferente

Si tengo un compromiso en el pasado que apunta a uno de los padres, pero quiero cambiar el padre al que apunta, ¿cómo podría hacer eso?

Usando git rebase . Es el genérico "tomar commit (s) y colocarlo / plop / them en un command diferente (base)" en Git.

Algunas cosas que debe saber, sin embargo:

  1. Como comprometer SHA involucran a sus padres, cuando cambias el padre de un compromiso determinado, su SHA cambiará, al igual que los SHA de todos los commits que vengan después (más recientes que él) en la línea de desarrollo.

  2. Si está trabajando con otras personas, y ya ha empujado el compromiso en cuestión al público hasta donde lo han extraído, modificar el compromiso es probablemente una Idea Mala ™. Esto se debe al n. ° 1 y, por lo tanto, a la confusión resultante que encontrarán los repositorys de otros usuarios al tratar de descubrir qué sucedió debido a que sus SHA ya no coinciden con los suyos para los "mismos" commits. (Consulte la sección "RECUPERACIÓN DE UPSTREAM REBASE" de la página man vinculada para más detalles).

Dicho esto, si actualmente se encuentra en una sucursal con algunas confirmaciones que desea transferir a un nuevo padre, se vería más o less así:

 git rebase --onto <new-parent> <old-parent> 

Eso moverá todo después de <old-parent> en la twig actual para situarse en la parte superior de <new-parent> .

Si resulta que debe evitar volver a establecer las correlaciones posteriores (p. Ej., Porque una reescritura del historial sería insostenible), puede usar el reemploop de git (disponible en Git 1.6.5 y 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. 

Con el reemploop anterior establecido, cualquier request para el object B realmente devolverá el object C. El contenido de C es exactamente el mismo que el contenido de B, excepto para el primer padre (los mismos padres (excepto el primero), el mismo tree, el mismo post de confirmación).

Los reemploops están activos por defecto, pero se pueden desactivar utilizando la --no-replace-objects para git (antes del nombre del command) o configurando la variable de entorno GIT_NO_REPLACE_OBJECTS . Los reemploops se pueden compartir presionando refs/replace/* (además de los refs/heads/* normales).

Si no te gusta commit-munging (hecho con sed arriba), entonces puedes crear tu commit de reemploop usando commands de nivel superior:

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

La gran diferencia es que esta secuencia no propaga los padres adicionales si B es una combinación de fusión.

Tenga en count que cambiar una confirmación en Git requiere que se modifiquen todas las confirmaciones que siguen. Esto se desaconseja si ha publicado esta parte de la historia, y alguien podría haber construido su trabajo en la historia que era antes del cambio.

La solución alternativa a git rebase mencionada en la respuesta de Amber es usar mecanismo de injertos (ver definición de injertos Git en Git Glossary y documentation del file .git/info/grafts en la documentation de Git Repository Layout ) para cambiar el padre de una confirmación, verificar que sí lo hizo Lo correcto con algún visor de historial ( gitk , git log --graph , etc.) y luego use git filter-branch (como se describe en la sección "Ejemplos" de su página de manual) para hacerlo permanente (y luego eliminar injerto, y opcionalmente eliminar las references originales respaldadas por git filter-branch o repository reclone):

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

NOTA !!! Esta solución es diferente de la solución de rebase en que la database de git rebase cambios de base / trasplante, mientras que la solución basada en injertos simplemente reparará las confirmaciones tal cual , sin tomar en count las diferencias entre el padre antiguo y el nuevo padre.

Para aclarar las respuestas anteriores y sin pudor, conecte mi propio guión:

Depende de si desea "rebase" o "reparent". Una rebase , según lo sugerido por Amber , se mueve alnetworkingedor de diffs . Un reparador , como lo sugirieron Jakub y Chris , mueve instantáneas de todo el tree. Si desea reparen, sugiero usar git reparent lugar de hacer el trabajo manualmente.

Comparación

Supongamos que tiene la image de la izquierda y quiere que se vea como en la image de la derecha:

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

Tanto el rebasamiento como el reparenting producirán la misma image, pero la definición de C' . Con git rebase --onto AB , C' no contendrá ningún cambio introducido por B Con git reparent -p A , C' será idéntico a C (excepto que B no estará en el historial).