Resumen
De forma predeterminada, git pull
crea confirmaciones de fusión que agregan ruido y complejidad al historial del código. Además, pull
hace que sea fácil no pensar en cómo sus cambios podrían verse afectados por los cambios entrantes.
El git pull
comando es seguro siempre que solo realice fusiones de avance rápido. Si git pull
está configurado para hacer fusiones de avance rápido y cuando no es posible una fusión de avance rápido, Git saldrá con un error. Esto le dará la oportunidad de estudiar los commits entrantes, pensar en cómo podrían afectar sus commits locales y decidir el mejor curso de acción (fusionar, rebase, reset, etc.).
Con Git 2.0 y más reciente, puede ejecutar:
git config --global pull.ff only
para alterar el comportamiento predeterminado a solo avance rápido. 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 git up
alias como este:
git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'
y usando en 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 ascendentes (no solo la rama en la que está trabajando actualmente), y
- limpia las
origin/*
ramas viejas que ya no existen aguas arriba.
Problemas con git pull
git pull
no está mal si se usa correctamente. Varios cambios recientes en Git han hecho que sea más fácil de usar git pull
correctamente, pero desafortunadamente el comportamiento predeterminado de un plano git pull
tiene varios problemas:
- introduce no linealidades innecesarias en la historia
- hace que sea fácil reintroducir accidentalmente confirmaciones que fueron intencionalmente refundadas
- modifica tu directorio de trabajo de maneras impredecibles
- pausar lo que estás haciendo para revisar el trabajo de otra persona es molesto con
git pull
- hace que sea difícil rebase 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
Por defecto, el git pull
comando es equivalente a ejecutar git fetch
seguido de git merge @{u}
. Si hay confirmaciones no aceleradas en el repositorio local, la parte de git pull
fusión crea una confirmación de fusión.
No hay nada inherentemente malo en los compromisos de fusión, pero pueden ser peligrosos y deben tratarse con respeto:
- Los compromisos de fusión son inherentemente difíciles de examinar. Para comprender lo que está haciendo una fusión, debe comprender las diferencias con todos los padres. Una diferencia convencional no transmite bien esta información multidimensional. En contraste, una serie de confirmaciones 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 fusión pueden alterar algunos esquemas de integración continua (por ejemplo, autocompilar solo la ruta del primer padre bajo la convención asumida de que los segundos padres señalan trabajos incompletos en progreso).
Por supuesto, hay un momento y un lugar para las fusiones, pero comprender cuándo las fusiones deben y no deben usarse puede mejorar la utilidad de su repositorio.
Tenga en cuenta que el propósito de Git es hacer que sea fácil 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 rebase
comando y por qué se creó). Las confirmaciones de fusión creadas por git pull
no transmiten semántica útil a los demás, solo dicen que alguien más empujó al repositorio antes de que haya terminado con sus cambios. ¿Por qué esos compromisos de fusión si no son significativos para los demás y podrían ser peligrosos?
Es posible configurar git pull
una nueva versión en lugar de fusionar, pero esto también tiene problemas (discutido más adelante). En su lugar, git pull
debe configurarse para hacer solo fusiones de avance rápido.
Reintroducción de Comisiones Rebajadas
Supongamos que alguien rebasa una rama y la fuerza la empuja. Esto generalmente no debería suceder, pero a veces es necesario (p. Ej., Eliminar un archivo de registro de 50GiB que se introdujo y se introdujo accidentalmente). La fusión realizada por git pull
fusionará la nueva versión de la rama ascendente en la versión anterior que todavía existe en su repositorio local. Si presiona el resultado, las horquillas y las antorchas comenzarán a aparecer.
Algunos pueden argumentar que el verdadero problema son las actualizaciones forzadas. Sí, generalmente es aconsejable evitar los empujes forzados siempre que sea posible, pero a veces son inevitables. Los desarrolladores deben estar preparados para lidiar con las actualizaciones de fuerza, porque a veces sucederán. Esto significa no fusionarse ciegamente en los viejos commits a través de un ordinario git pull
.
Modificaciones de directorio de trabajo sorpresa
No hay forma de predecir cómo se verá el directorio o índice de trabajo hasta que git pull
se haga. Puede haber conflictos de fusión que tiene que resolver antes de que pueda hacer cualquier 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 de un 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 fusionar o cambiar de base, lo que le permite formar un plan antes de tomar medidas.
Dificultad para revisar los compromisos de otras personas
Suponga que está haciendo algunos cambios y alguien más quiere que revise algunas confirmaciones que acaba de enviar. git pull
La operación de fusión (o rebase) modifica el directorio y el índice de trabajo, lo que significa que el directorio y el índice de trabajo deben estar limpios.
Podrías usar git stash
y luego git pull
, pero ¿qué haces cuando termines de revisar? Para volver a donde estabas, debes 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 índice de trabajo, por lo que es seguro ejecutarlo en cualquier momento, incluso si tiene cambios organizados o no organizados. Puede pausar lo que está haciendo y revisar el compromiso de otra persona sin preocuparse por ocultar o terminar el compromiso en el que está trabajando. git pull
no te da esa flexibilidad.
Rebasándose en una rama remota
Un patrón de uso común de Git es hacer una git pull
para incorporar los últimos cambios seguidos de una git rebase @{u}
para eliminar la confirmación de fusión que se git pull
introdujo. Es bastante común que Git tiene algunas opciones de configuración para reducir estos dos pasos a un solo paso a contar git pull
para realizar un rebase en lugar de una fusión (ver branch.<branch>.rebase
, branch.autosetuprebase
y pull.rebase
opciones).
Desafortunadamente, si tiene una confirmación de fusión no acelerada que desea conservar (p. Ej., Una confirmación que fusiona una rama de función empujada master
), ni un rebase-pull ( git pull
con branch.<branch>.rebase
set to true
) ni un merge-pull (el git pull
comportamiento predeterminado ) seguido de un rebase funcionará. Esto se debe a que git rebase
elimina las fusiones (linealiza el DAG) sin la --preserve-merges
opción. La operación de rebase-pull no se puede configurar para preservar las fusiones, y una fusión-extracción seguida de un git rebase -p @{u}
no eliminará la fusión causada por la fusión-extracción. Actualización: Git v1.8.5 agregado git pull --rebase=preserve
y git config pull.rebase preserve
. Esto se git pull
debe hacer git rebase --preserve-merges
después de recuperar las confirmaciones anteriores. (¡Gracias a funkaster por el aviso !)
Limpieza de ramas eliminadas
git pull
no elimina las ramas de seguimiento remoto correspondientes a las ramas que se eliminaron del repositorio remoto. Por ejemplo, si alguien elimina una rama foo
del repositorio remoto, aún verá origin/foo
.
Esto lleva a los usuarios a resucitar accidentalmente ramas muertas porque creen que todavía están activas.
Una mejor alternativa: usar en git up
lugar degit pull
En lugar de git pull
, recomiendo crear y usar el siguiente git up
alias:
git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'
Este alias descarga todas las últimas confirmaciones de todas las ramas ascendentes (podando las ramas muertas) e intenta adelantar la rama local a la última confirmación en la rama ascendente. Si tuvo é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 eliminadas), lo que le brinda la oportunidad de revisar las confirmaciones anteriores antes de tomar medidas.
Esto todavía modifica su directorio de trabajo de maneras impredecibles, pero solo si no tiene ningún cambio local. A diferencia git pull
, git up
nunca lo llevará a un aviso esperando que solucione un conflicto de fusión.
Otra opción: git pull --ff-only --all -p
La siguiente es una alternativa al git up
alias anterior :
git config --global alias.up 'pull --ff-only --all -p'
Esta versión de git up
tiene el mismo comportamiento que el git up
alias anterior , excepto:
- el mensaje de error es un poco más críptico si su rama local no está configurada con una rama ascendente
- se basa en una característica no documentada (el
-p
argumento, que se pasa a fetch
) 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 hacer solo fusiones de avance rápido de forma predeterminada:
git config --global pull.ff only
Esto hace git pull
que actúe como git pull --ff-only
, pero todavía no obtiene todos los commits ascendentes ni limpia las origin/*
ramas viejas , por lo que todavía prefiero git up
.