tag remove practices commits best git git-pull

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 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 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.