Si tengo un compromiso en el pasado que apunta a uno de los padres, pero quiero cambiar el padre al que apunta, ¿cómo lo haría?
Utilizando git rebase
. Es el comando genérico "tomar commit (s) y colocarlo en un comando padre (base) diferente en Git.
Algunas cosas que debes saber, sin embargo:
Dado que los SHA de compromiso involucran a sus padres, cuando cambia el padre de un commit dado, su SHA cambiará, al igual que los SHA de todos los commits que vienen después (más reciente que él) en la línea de desarrollo.
Si está trabajando con otras personas y ya ha llevado el compromiso público en cuestión a donde lo han llevado, modificar el compromiso es probablemente una mala idea ™. Esto se debe al n. ° 1 y, por lo tanto, a la confusión resultante que los repositorios de otros usuarios encontrarán al tratar de descubrir qué sucedió debido a que sus SHA ya no coinciden con los de los "mismos" compromisos. (Consulte la sección "RECUPERACIÓN DE LA REEMBOLSO DE UPSTREAM" de la página de manual vinculada para obtener más detalles).
Dicho esto, si actualmente se encuentra en una rama con algunas confirmaciones que desea mover a un nuevo padre, se vería así:
git rebase --onto <new-parent> <old-parent>
Eso moverá todo después <old-parent>
de la rama actual para sentarse en su <new-parent>
lugar.
--onto
se usa! La documentación siempre ha sido completamente oscura para mí, ¡ incluso después de leer esta respuesta!git rebase --onto <new-parent> <old-parent>
Me contó todo sobre cómo usarrebase --onto
que cualquier otra pregunta y documento que he leído hasta ahora no pudo.rebase this commit on that one
ogit rebase --on <new-parent> <commit>
. Usar el viejo padre aquí no tiene sentido para mí.git rebase <new-parent>
.Si resulta que necesita evitar volver a modificar las confirmaciones posteriores (por ejemplo, porque una reescritura del historial sería insostenible), puede usar el reemplazo de git (disponible en Git 1.6.5 y posterior).
Una vez establecido el reemplazo anterior, cualquier solicitud del objeto B en realidad devolverá el objeto C. El contenido de C es exactamente el mismo que el contenido de B, excepto el primer padre (los mismos padres (excepto el primero), el mismo árbol, mismo mensaje de confirmación).
Los reemplazos están activos de manera predeterminada, pero se pueden desactivar utilizando la
--no-replace-objects
opción git (antes del nombre del comando) o configurando laGIT_NO_REPLACE_OBJECTS
variable de entorno. Los reemplazos se pueden compartir presionandorefs/replace/*
(además de lo normalrefs/heads/*
).Si no te gusta el commit-munging (hecho con sed arriba), entonces puedes crear tu commit de reemplazo usando comandos de nivel superior:
La gran diferencia es que esta secuencia no propaga los padres adicionales si B es una confirmación de fusión.
fuente
refs/replace/
jerarquía de referencia. Si solo necesita hacerlo una vez, puede hacerlogit push yourremote 'refs/replace/*'
en el repositorio de origen ygit fetch yourremote 'refs/replace/*:refs/replace/*'
en los repositorios de destino. Si necesita hacerlo varias veces, podría agregar esas especificaciones a unaremote.yourremote.push
variable de configuración (en el repositorio de origen) y unaremote.yourremote.fetch
variable de configuración (en los repositorios de destino).git replace --grafts
. No estoy seguro de cuándo se agregó esto, pero su propósito es crear un reemplazo que sea igual que commit B pero con los padres especificados.Tenga en cuenta que cambiar un commit en Git requiere que todos los commits que le siguen también tengan que cambiarse. Esto se desaconseja si ha publicado esta parte de la historia, y alguien podría haber construido su trabajo sobre la historia que era antes del cambio.
Una solución alternativa a la
git rebase
mencionada en la respuesta de Amber es usar un mecanismo de injertos (consulte la definición de injertos Git en el Glosario de Git y la documentación del.git/info/grafts
archivo en la documentación de Diseño del repositorio de Git ) para cambiar el elemento primario de una confirmación, verifique que funcionó correctamente con algún visor de historial (gitk
,git log --graph
, etc.) y luego usargit filter-branch
(como se describe en la sección "Ejemplos" de su página de manual) para hacerlo permanente (y luego eliminar el injerto, y opcionalmente eliminar las referencias originales respaldadas porgit filter-branch
, o repositorio reclinado):NOTA !!! ¡Esta solución es diferente de la solución de rebase en que
git rebase
cambiaría los cambios de rebase / trasplante , mientras que la solución basada en injertos simplemente reparent commits como es , sin tener en cuenta las diferencias entre el padre antiguo y el padre nuevo!fuente
git replace
ha reemplazado a git grafts (suponiendo que tenga git 1.6.5 o posterior).git-replace
+git-filter-branch
? En mi prueba,filter-branch
parecía respetar el reemplazo, rebatir todo el árbol.git filter-branch
reescribe el historial, respetando los injertos y los reemplazos (pero en el historial reescrito los reemplazos serán discutibles); empujar resultará en un cambio no hacia adelante sin fasf.git replace
crea reemplazos transferibles en el lugar; push será un avance rápido, pero necesitaría presionarrefs/replace
para transferir reemplazos para tener un historial corregido. HTHPara aclarar las respuestas anteriores y descaradamente enchufar mi propio script:
Depende de si desea "rebase" o "reparent". Un rebase , como lo sugiere Amber , se mueve por los diffs . Un reparent , como lo sugirieron Jakub y Chris , se mueve alrededor de las instantáneas de todo el árbol. Si desea realizar reparent, sugiero usar en
git reparent
lugar de hacer el trabajo manualmente.Comparación
Suponga que tiene la imagen a la izquierda y desea que se vea como la imagen a la derecha:
Tanto el rebase como el reparenting producirán la misma imagen, pero la definición de
C'
difiere. Congit rebase --onto A B
,C'
no contendrá ningún cambio introducido porB
. Congit reparent -p A
,C'
será idéntico aC
(excepto queB
no estará en el historial).fuente
reparent
guión; funciona de maravilla; Muy recomendable . También recomendaría crear un nuevobranch
etiqueta, y paracheckout
esa rama antes de llamar (ya que la etiqueta de la rama se moverá).Definitivamente, la respuesta de @ Jakub me ayudó hace unas horas cuando intentaba exactamente lo mismo que el OP.
Sin embargo,
git replace --graft
ahora es la solución más fácil con respecto a los injertos. Además, un problema importante con esa solución fue que la rama de filtro me hizo perder cada rama que no se fusionó con la rama de HEAD. Entonces,git filter-repo
hizo el trabajo perfectamente y sin problemas.Para más información: consulte la sección "Re-injerto de historia" en los documentos
fuente