Cuando aprendes a programar, tarde o temprano también aprenderás sobre los sistemas de control de versiones. Hay muchas herramientas en el mercado, pero una de ellas es el estándar de facto utilizado por casi toda la industria. Es tan popular que hay empresas que utilizan su nombre en su marca, estamos hablando de Git por supuesto.

Si bien Git es una herramienta poderosa, su poder está bien escondido. Hay algunos conceptos esenciales que se deben comprender para llegar a ser realmente competente con Git. La buena noticia es que una vez que las aprendas, casi nunca tendrás problemas de los que no puedas escapar.

El flujo normal de trabajo

En un flujo normal de trabajo con Git, usarás un repositorio local (local repository), y un repositorio remoto (remote repository), y una o más ramas de trabajo (branch). Los repositorios almacenan toda la información sobre el proyecto, incluyendo su historial completo de cambios y sus branches. Un branch es básicamente una colección de cambios que van desde un proyecto vacío hasta el estado actual.

Después de clonar un repositorio, trabajarás en su copia local e introducir nuevos cambios. Mientras no envíes los cambios locales a repositorio remoto, todo el trabajo estará disponible solo en la máquina local.

Cambios locales

Todo está bien cuando tú y el resto de tu equipo están trabajando en archivos totalmente diferentes. Pase lo que pase, no entrarán en conflicto sus cambios. Sin embargo, hay ocasiones en las que tú y tus compañeros introducen cambios simultáneamente en el mismo archivo. Ahí es donde suelen empezar los problemas.

¿Alguna vez has ejecutado git pull solo para ver el temido error: Your local changes to the following files would be overwritten by merge:? Tarde o temprano, todos nos topamos con este problema.

Lo que es más confuso aquí es que nosotros no deseamos fusionar nada, solo es traer los cambios del repositorio remoto. En realidad, el comando git pull es un poco más complicado de lo que se piensa.

¿Cómo trabaja exactamente git pull?

git pull no es una operación simple. Consiste en traer datos del repositorio remoto y luego mezclar los cambios con el repositorio local. Estas dos operaciones pueden ser realizadas manualmente si lo deseas con:

git fetch
git merge origin/$CURRENT_BRANCH

La sentencia origin/$CURRENT_BRANCH significa que:

  • Git mezclará los cambios del repositorio remoto llamado origin(
    el que clonaste)
  • Serán añadidos a $CURRENT_BRANCH
  • No serán visibles si no estás parado en el branch

Dado que Git solo realiza funciones cuando no hay cambios no confirmados (uncommitted), cada vez que ejecuta git pull con cambios uncommitted podría meterse en problemas. Afortunadamente, ¡hay formas de salir de este problema de una sola pieza!

photo-1499938971550-7ad287075e0d

Diferentes acercamientos

Cuando tienes cambios locales uncommitted y aun así deseas obtener una nueva versión desde el repositorio remoto, el caso de uso generalmente cae en uno de los siguientes escenarios.

  • No importan los cambios locales y desea sobrescribirlos,
  • Te importan los cambios y te gustaría mantenerlos después de traer los cambios remotos,
  • Deseas descargar los cambios remotos pero aún no aplicarlos.

Cada uno de estos enfoques requiere una solución diferente.

No quieres mantener los cambios locales

En este caso, deseas eliminar todos los cambios locales uncommitted. Quizás modificaste un archivo para probar, pero ya no necesitas la modificación. Lo único que importa es estar al día con los cambios.

Esto significa que agrega un paso más entre buscar los cambios remotos y mezclarlos en local. Este paso restablecerá el branch a su último estado committed, lo que permitirá que git merge funcione.

git fetch
git reset --hard HEAD
git merge origin/$CURRENT_BRANCH

Si no quieres escribir el nombre del branch todo el tiempo puedes correr este comando. Git tiene un atajo que apunta al upstream branch @{u}. upstream branch es el branch en repositorio remoto al que se le hace git pull o git push.

Así es como se vería el comando anterior usando el atajo.

git fetch
git reset --hard HEAD
git merge '@{u}'

Estamos citando el atajo en el ejemplo para evitar que el shell lo interprete.

Te importan los cambios locales

Cuando tus cambios uncommitted son importantes, hay dos opciones. Tú puedes hacer un commit y luego realizar git pull, o puedes guardarlos temporalmente con git stash.

git stash significa guardar los cambios por un momento para traerlos nuevamente más tarde. Para ser más preciso, git stash crea un commit que no es visible en el branch actual, pero que son accesibles para Git.

Para recuperar los cambios guardados en el último stash, se debe usar el comando git stash pop. Después de aplicar con éxito los cambios este comando también elimina el stash commit, ya que no es necesario mantenerlo.

El flujo de trabajo podría verse así:

git fetch
git stash
git merge '@{u}'
git stash pop

Por defecto, los cambios en stash se convertiran en staged. Si deseas eliminarlos usa el comando git restore --staged (si usas una versión mayor de Git 2.25.0)

Solo quieres cambiar los cambios remotos

El último escenario es un poco diferente a los anteriores. Digamos que estás en medio de una refactorización muy complicada, donde perder los cambios ni guardarlos es una opción. Sin embargo, aún deseas tener los cambios remotos disponibles para ejecutar git diff contra ellos.

Como probablemente hayas descubierto, ¡descargar los cambios remotos no requiere git pull en absoluto! git fetch es suficiente. Una cosa a tener en cuenta es que, de forma predeterminada, git fetch solo traerá cambios del branch actual. Para obtener los cambios de todos los branches debes usar git fetch --all. Y si desea limpiar algunas de las ramas que ya no existen en el repositorio remoto, git fetch --prune hará la limpieza

Photo by Lenin Estrada / Unsplash

Algo de automatización

¿Has oído hablar del archivo Git Config? Es un archivo donde Git guarda todas las configuraciones creadas por el usuario. Se encuentra en el directorio de inicio: ya sea ~/.gitconfig o ~/.config/git/config, podemos agregar algunos alias personalizados que se entenderán como comandos de Git.

Por ejemplo, para tener un alias equivalente a git diff --cached (que muestra la diferencia entre la rama actual y los archivos en etapas), agregaremos lo siguiente:

[alias]
  dc = diff --cached

Después de esto puede ejecutar git dc siempre que desee revisar los cambios. De esta manera, podemos configurar algunos alias relacionados con los casos de uso anteriores.

[alias]
  pull_force = !"git fetch --all; git reset --hard HEAD; git merge @{u}"
  pf = pull_force
  pull_stash = !"git fetch --all; git stash; git merge @{u}; git stash pop"

De esta manera, git pull_force puede sobre escribir los cambios locales, mientras git pull_stash permite mantenerlos.

El otro Git Pull Force

Es posible que las mentes curiosas ya hayan descubierto que existe el comando git pull --force. Sin embargo, esta bestia es muy diferente a lo que se presenta en este artículo.

Puede sonar como algo que nos ayudaría a sobrescribir los cambios locales. En cambio, nos permite buscar los cambios de un branch remoto a un branch local diferente. git pull --force solo modifica el comportamiento. Por lo tanto, es equivalente a git fetch --force.

Al igual que git push, git fetch nos permite especificar en qué branch local y remoto deseamos operar. git fetch origin/feature-1:my-feature significa que los cambios en el branch feature-1en el repositorio remoto serán visibles en el branch local my-feature. Cuando un commit modifica el histórico existente, no es permitido por git sin el uso explícito del parámetro --force.

git push --force permite sobrescribir los branches remotos, git fetch --force (or git pull --force) permite sobreescribir los branches locales. Siempre se usa con los branches origen y destino mencionados como parámetros. Una alternativa para sobreescribir cambios locales usando git pull --force podría ser git pull force "@{u}:HEAD.

Conclusión

El mundo de git es gigantesco. Esté artículo cubrió solo una de las facetas del mantenimiento del repositorio: incorporar cambios remotos en un branch local. Incluso este escenario cotidiano requería que analicemos un poco más en profundidad los mecanismos internos de esta herramienta de control de versiones.

Aprender casos de uso reales te ayuda a comprender mejor cómo funciona Git internamente. Esto, a su vez, te hará sentir empoderado cada vez que te metas en problemas. Todos hacemos eso de vez en cuando.

Traducido del artículo de Piotr Gaczkowski - Git Pull Force – How to Overwrite Local Changes With Git