remove - ¿En qué casos podría ser perjudicial ''git pull''?
git tag commits (4)
Tengo un colega que dice que git pull
es dañino y se enoja cuando alguien lo usa.
El comando git pull
parece ser la forma canónica de actualizar su repositorio local. ¿ git pull
crea problemas? ¿Qué problemas crea? ¿Hay una mejor manera de actualizar un repositorio git?
Resumen
De forma predeterminada, git pull
crea combinaciones de mezcla que agregan ruido y complejidad al historial del código. Además, pull
hace que sea fácil no pensar en cómo los cambios entrantes pueden afectar sus cambios.
El comando git pull
es seguro siempre que solo realice fusiones rápidas. Si git pull
está configurado para hacer solo combinaciones de avance rápido y no es posible una combinación de avance rápido, entonces Git saldrá con un error. Esto le dará la oportunidad de estudiar los compromisos entrantes, pensar cómo podrían afectar sus compromisos locales y decidir el mejor curso de acción (fusionar, reajustar, reiniciar, etc.).
Con Git 2.0 y más nuevo, puedes ejecutar:
git config --global pull.ff only
para alterar el comportamiento por defecto solo para adelantar. Con las versiones de Git entre 1.6.6 y 1.9.x, tendrás que acostumbrarte a escribir:
git pull --ff-only
Sin embargo, con todas las versiones de Git, recomiendo configurar un alias de git up
como este:
git config --global alias.up ''!git remote update -p; git merge --ff-only @{u}''
y usando git up
lugar de git pull
. Prefiero este alias sobre git pull --ff-only
porque:
- funciona con todas las versiones (no antiguas) de Git,
- recupera todas las ramas en sentido ascendente (no solo la rama en la que estás trabajando actualmente), y
- limpia las ramas de
origin/*
antiguas que ya no existen en sentido ascendente.
Problemas con git pull
git pull
no es malo si se usa correctamente. Varios cambios recientes en Git han facilitado el uso git pull
, pero desafortunadamente el comportamiento predeterminado de un simple git pull
tiene varios problemas:
- Introduce no linealidades innecesarias en la historia.
- hace que sea fácil reintroducir accidentalmente comillas que fueron rebasadas intencionalmente en sentido ascendente
- Modifica tu directorio de trabajo de manera impredecible.
- pausar lo que está haciendo para revisar el trabajo de otra persona es molesto con
git pull
- hace que sea difícil volver a colocar correctamente en la rama remota
- no limpia las ramas que se eliminaron en el repositorio remoto
Estos problemas se describen con mayor detalle a continuación.
Historia no lineal
De forma predeterminada, el comando git pull
es equivalente a ejecutar git fetch
seguido de git merge @{u}
. Si hay confirmaciones no presionadas en el repositorio local, la parte de combinación de git pull
crea una confirmación de combinación.
No hay nada intrínsecamente malo en las combinaciones de mezcla, pero pueden ser peligrosas y deben tratarse con respeto:
- Los compromisos de fusión son intrínsecamente difíciles de examinar. Para comprender lo que hace una fusión, debe comprender las diferencias entre todos los padres. Un diff convencional no transmite bien esta información multidimensional. En contraste, una serie de compromisos normales es fácil de revisar.
- La resolución de conflictos de fusión es complicada, y los errores a menudo pasan desapercibidos durante mucho tiempo porque los compromisos de fusión son difíciles de revisar.
- Las fusiones pueden reemplazar silenciosamente los efectos de las confirmaciones regulares. El código ya no es la suma de confirmaciones incrementales, lo que lleva a malentendidos sobre lo que realmente cambió.
- Las confirmaciones de mezcla pueden interrumpir algunos esquemas de integración continua (por ejemplo, construir automáticamente solo la ruta del primer padre según la convención asumida de que los padres secundarios señalan trabajos incompletos en progreso).
Por supuesto, hay un momento y un lugar para las fusiones, pero comprender cuándo se deben o no las fusiones puede mejorar la utilidad de su repositorio.
Tenga en cuenta que el propósito de Git es facilitar el compartir y consumir la evolución de una base de código, no registrar con precisión el historial exactamente como se desarrolló. (Si no está de acuerdo, considere el comando de rebase
y el motivo por el que se creó). Las confirmaciones de fusión creadas por git pull
no transmiten semánticas útiles a los demás, solo dicen que alguien más ingresó al repositorio antes de que terminara con sus cambios. . ¿Por qué tienen esos compromisos de fusión si no son significativos para otros y podrían ser peligrosos?
Es posible configurar git pull
para rebase en lugar de fusionar, pero esto también tiene problemas (se explica más adelante). En su lugar, git pull
debe configurarse para hacer solo fusiones de avance rápido.
Reintroducción de los Comités Rebajados
Supongamos que alguien rebasa una rama y la fuerza la empuja. Esto generalmente no debería suceder, pero a veces es necesario (por ejemplo, para eliminar un archivo de registro de 50GiB que se cometió y empujó accidentalmente). La fusión realizada por git pull
combinará la nueva versión de la rama ascendente en la versión anterior que aún existe en su repositorio local. Si presionas el resultado, las horquillas y las antorchas de lanzamiento comenzarán a aparecer en tu dirección.
Algunos pueden argumentar que el problema real es forzar las actualizaciones. Sí, en general es aconsejable evitar los ataques de fuerza siempre que sea posible, pero a veces son inevitables. Los desarrolladores deben estar preparados para lidiar con las actualizaciones de la fuerza, porque a veces sucederán. Esto significa no fusionarse ciegamente en los compromisos antiguos a través de un git pull
ordinario.
Modificaciones de directorio de trabajo sorpresa
No hay forma de predecir cómo se verá el directorio o el índice de trabajo hasta git pull
se haga git pull
. Puede haber conflictos de combinación que deba resolver antes de poder hacer otra cosa, podría introducir un archivo de registro de 50GiB en su directorio de trabajo porque alguien lo presionó accidentalmente, podría cambiar el nombre del directorio en el que está trabajando, etc.
git remote update -p
(o git fetch --all -p
) le permite ver los compromisos de otras personas antes de que decida fusionarse o cambiar de base, lo que le permite formar un plan antes de tomar medidas.
Dificultad para revisar los compromisos de otras personas
Supongamos que está realizando algunos cambios y alguien más quiere que revise algunos compromisos que acaba de enviar. git pull
operación de fusión (o rebase) de git pull
modifica el directorio e índice de trabajo, lo que significa que su directorio e índice de trabajo deben estar limpios.
Puedes usar git stash
y luego git pull
, pero ¿qué haces cuando terminas de revisar? Para volver al lugar donde estaba, debe deshacer la fusión creada por git pull
y aplicar el alijo.
git remote update -p
(o git fetch --all -p
) no modifica el directorio o el índice de trabajo, por lo que es seguro ejecutarlo en cualquier momento, incluso si ha realizado cambios en etapas o no. Puede pausar lo que está haciendo y revisar el compromiso de otra persona sin preocuparse por guardar o terminar el compromiso en el que está trabajando. git pull
no te da esa flexibilidad.
Rebasando en una rama remota
Un patrón de uso común de Git es hacer un git pull
para introducir los últimos cambios seguidos de un git rebase @{u}
para eliminar la confirmación de fusión que git pull
introdujo. Es bastante común que Git tenga algunas opciones de configuración para reducir estos dos pasos a un solo paso al decirle a git pull
que realice una rebase en lugar de una combinación (vea la branch.<branch>.rebase
, branch.autosetuprebase
, y pull.rebase
opciones ).
Desafortunadamente, si tiene una confirmación de fusión no presionada que desea conservar (por ejemplo, una confirmación que combina una rama de característica empujada en el master
), ni una extracción de la base ( git pull
with branch.<branch>.rebase
establecido en true
) ni una Merge-Pull (el comportamiento predeterminado de git pull
) seguido de una rebase funcionará. Esto se debe a que git rebase
elimina las fusiones (linealiza el DAG) sin la opción --preserve-merges
. La operación rebase-pull no se puede configurar para preservar las combinaciones, y un merge-pull seguido de un git rebase -p @{u}
no eliminará la fusión causada por la fusión-pull. Actualización: Git v1.8.5 agregó git pull --rebase=preserve
y git config pull.rebase preserve
. Esto causa que git pull
haga git rebase --preserve-merges
después de buscar los git rebase --preserve-merges
ascendente. (Gracias a funkaster por el heads-up!)
Limpieza de ramas eliminadas
git pull
no elimina las ramas de seguimiento remoto correspondientes a las sucursales que se eliminaron del repositorio remoto. Por ejemplo, si alguien elimina la rama foo
del repositorio remoto, aún verá origin/foo
.
Esto lleva a los usuarios a resucitar accidentalmente las ramas muertas porque creen que todavía están activas.
Una alternativa mejor: usa git up
lugar de git pull
En lugar de git pull
, recomiendo crear y usar el siguiente alias de git up
:
git config --global alias.up ''!git remote update -p; git merge --ff-only @{u}''
Este alias descarga todas las confirmaciones más recientes de todas las sucursales en sentido ascendente (podando las ramas muertas) e intenta avanzar rápidamente a la sucursal local hasta la confirmación más reciente en la rama hacia arriba. Si tiene éxito, entonces no hubo compromisos locales, por lo que no hubo riesgo de conflicto de fusión. El avance rápido fallará si hay confirmaciones locales (no presionadas), lo que le brinda la oportunidad de revisar las confirmaciones anteriores antes de tomar medidas.
Esto todavía modifica su directorio de trabajo de manera impredecible, pero solo si no tiene ningún cambio local. A diferencia de git pull
, git up
nunca lo enviará a un mensaje que espera que solucione un conflicto de combinación.
Otra opción: git pull --ff-only --all -p
La siguiente es una alternativa al alias git up
anterior:
git config --global alias.up ''pull --ff-only --all -p''
Esta versión de git up
tiene el mismo comportamiento que el alias anterior de git up
, excepto:
- el mensaje de error es un poco más críptico si su sucursal local no está configurada con una rama ascendente
- se basa en una característica no documentada (el argumento
-p
, que se pasa afetch
) que puede cambiar en futuras versiones de Git
Si está ejecutando Git 2.0 o más reciente
Con Git 2.0 y versiones más recientes, puede configurar git pull
para que solo realice fusiones rápidas de forma predeterminada:
git config --global pull.ff only
Esto hace que git pull
actúe como git pull --ff-only
, pero aún así no recupera todas las confirmaciones en sentido ascendente ni limpia las ramas de origin/*
antiguas origin/*
lo que todavía prefiero git up
.
La respuesta aceptada reclama
La operación rebase-pull no se puede configurar para preservar las fusiones
pero a partir de Git 1.8.5 , que fecha posterior a esa respuesta, puede hacerlo
git pull --rebase=preserve
o
git config --global pull.rebase preserve
o
git config branch.<name>.rebase preserve
Los docs dicen
Cuando se
preserve,
también pase--preserve-merges
con ''git rebase'' para que las confirmaciones de fusión comprometidas localmente no se aplanen al ejecutar ''git pull''.
Esta discusión anterior tiene información más detallada y diagramas: git pull --rebase --preserve-merges . También explica por qué git pull --rebase=preserve
no es lo mismo que git pull --rebase --preserve-merges
, que no hace lo correcto.
Esta otra discusión previa explica lo que realmente hace la variante preservar-fusionar de rebase, y cómo es mucho más compleja que una rebase regular: ¿Qué hace exactamente la "rebase de git --preserve-merges" (y por qué)
Mi respuesta, sacada de la discusión que arose en HackerNews:
Me siento tentado a solo responder la pregunta utilizando la Ley de Titulares de Betteridge: ¿Por qué se considera dañino a git pull
? No lo es
- Las no linealidades no son intrínsecamente malas. Si representan la historia actual están bien.
- La reintroducción accidental de confirmaciones rebasadas en sentido ascendente es el resultado de reescribir erróneamente el historial en sentido ascendente. No puede volver a escribir el historial cuando el historial se replica a lo largo de varios repositorios.
- La modificación del directorio de trabajo es un resultado esperado; de utilidad discutible, es decir, frente al comportamiento de hg / monotone / darcs / other_dvcs_predating_git, pero de nuevo no es intrínsecamente malo.
- Hacer una pausa para revisar el trabajo de otros es necesario para una fusión, y nuevamente es un comportamiento esperado en git pull. Si no desea fusionar, debe usar git fetch. Nuevamente, esta es una idiosincrasia de git en comparación con los DVD populares anteriores, pero es un comportamiento esperado y no intrínsecamente malo.
- Hacer que sea difícil rebasar contra una rama remota es bueno. No reescriba la historia a menos que sea absolutamente necesario. No puedo, por mi vida, entender esta búsqueda de una historia lineal (falsa).
- No limpiar las ramas es bueno. Cada repo sabe lo que quiere sostener. Git no tiene noción de relaciones maestro-esclavo.
No se considera dañino si está utilizando Git correctamente. Veo cómo le afecta negativamente en su caso de uso, pero puede evitar problemas simplemente no modificando el historial compartido.