Desarrollo de Aplicaciones

GIT: Solución a errores típicos

Te muestro algunos errores de GIT y te cuento cómo he llegado a solucionarlos mediante algunos snippets

Errores populares GIT

Los retos que nos presenta GIT

Git, además de ser una gran herramienta que permite trabajar en sincronía, controlar versiones y despliegues, nos presenta constantemente retos a medida que lo vamos incorporando en nuestra rutina y ciertos comandos como commit, pull, fetch, push los lanzamos casi como un automatismo.

Lo cierto es que trabajar con git es mucho más que ir concatenando mejoras en una cadena de desarrollo. Las ramas, los errores humanos y la ignorancia sobre la big picture de git nos hace encontrarnos con muchas problemáticas que nos pueden llevar de cabeza si no sabemos qué hacer.

He aquí una pequeña muestra de errores y cómo he llegado a solucionarlos:

1. Solucionar un detached head

Quizá hayas encontrado alguna vez este mensaje:

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

  HEAD is now at 2546ab73 checkout now allows jp

Cuando esto sucede, nuestros cambios no serán guardados en ninguna rama, no podemos hacer push, ni commits, ni pulls. Lo que ha sucedido es que el puntero de git ya no está apuntando a la última versión, sino que ha dejado de apuntar al repositorio remoto por completo mientras solucionamos el embrollo en local. En estos casos, si lo único que queremos es volver a estar en sincronía con el repositorio remoto, simplemente tenemos que volver a la rama en la que estábamos trabajando.

# Por ejemplo, si la rama en la que estábamos es "master"
git checkout master

Si queremos hacer cambios en local y persistirlos, es simple:
# miramos el último commit
git log -n 1

# tenemos que copiar el hash identificador
# cambiamos a la rama de la que venimos, en mi caso, es master
git checkout master

# creamos una rama local temporal en la que mantendremos los cambios
git branch tmp <commit-hash>

# volvemos a la rama origen
git checkout master
# unimos los cambios que hemos guardado
git merge tmp

2. ¿Dónde estoy?

Relacionado con el punto anterior, es importante entender cómo funciona la sincronía entre repositorios. A menudo queremos ver los cambios que hemos hecho y no sabemos qué comando utilizar.

Si hemos hecho cambios en local y aún no hemos hecho commit:

git diff
# muestra todos los cambios que hay entre el último commit y nuestros archivos
# muchas veces se utiliza con un nombre de archivo, para limitar las líneas y entender mejor los cambios que hemos realizado.
git diff ruta/a/nuestro/archivo/modificado

Git diff es mucho más potente que para comparar cambios locales, ya que también permite ver versiones almacenadas en diversas ramas o commits. Sin embargo, este uso es muy distinto y generalmente no comporta malentendidos.

Si hemos hecho commit de nuestros cambios, lo normal es que estemos seguros de qué es lo que hemos hecho. Sin embargo, quizá haya pasado un tiempo y tengamos que recapitular. En estos casos, para ver los cambios que ha hecho el último commit, podemos hacerlo de esta manera:

git show
# muestra los cambios realizados en el último commit, sea este local o remoto
# igual que git diff, nos permite poner una ruta a un archivo para ver si ese archivo en concreto ha sido modificado.
# también nos acepta si le ponemos el hash de un commit en concreto para ver los cambios de aquel

Si lo que queremos es entender qué es lo que ha pasado, el comando git log es nuestro amigo. Muestra las ids de todos los commits y su comentario. Si queremos una vista más detallada, podemos probar con:

git log --graph --decorate --pretty=oneline --abbrev-commit 
Este comando nos dará un output similar a este
* 2546ab73 (master) checkout now allows jp
*   5f4f35a6 Merge branch 'FeatureFix' into master
|\
| * c394bc03 (origin/FeatureFix, FeatureFix) final fix for 277
| * 0b401d6e small fix for Controllers
* | bdd0c919 Test
* | e15152aa OBLIGED TO FILL ADDRESS2 IN CN IN JP
|/
* efee567a Added default when querystring returns null
* 9c77302e Added querystring awareness
* ff606371 Retrieves now subscriptions dynamically
* 7cbd072a Syntax error
* 32b72c2e Small bugfix on Controller
* bbe9e2aa Modificar ciertos inputs en personal details

Bastante gráfico y fácil de comprender.

3. Cherrypicking

Ese archivo modificado por un compañero en su rama, la cual partió de una versión anterior de master y que no puede mergearse sin una marabunta de conflictos ahora mismo te vendría bien. ¿Cómo recuperarlo y traértelo sin tener que hacer merges?

# necesitamos saber el id del último commit donde estén los cambios
git checkout rama_ajena
git log -p
# con estos comandos habremos cambiado a su rama y habremos visto los últimos cambios listados. Tan solo es cuestión de buscar (con tecla '/') en el log donde están los cambios que necesitamos y apuntarnos el ID de ese commit
# luego podemos importarnos este commit:
git checkout nuestra_rama
git cherry-pick <id-commit>
# si el commit conlleva más archivos o y se han dado problemas, podemos solucionarlos como si se tratara de un merge
# en mi caso, utilizo la default mergetool de git para resolver estos problemas:
git mergetool
# si hay demasiados problemas y queremos volver atrás, podemos hacerlo con
git cherry-pick --abort
# si hemos solucionado correctamente los conflictos, acabamos el cherry-pick con
git cherry-pick --continue
# estos dos comandos son los mismos que podemos utilizar con git merge para cancelar o continuar adelante.

4. Mergear automáticamente

Muchas veces nos encontramos con merge conflicts y realmente sabemos que nuestra versión de un archivo no está actualizada y que queremos la del repositorio remoto. O viceversa. Hay una manera más rápida de hacer esto que no repasar manualmente cada cambio del archivo:

# simplemente enviamos el parámetro --strategy-option, o -X
git merge --strategy-option theirs rama_remota
El opuesto, si queremos persistir nuestros cambios, sería:
git merge --strategy ours

5. He hecho algo mal, ¿cómo lo revierto?

Revertir un commit

# primero veamos el id de nuetros últimos dos commits
git log --oneline -n2
# cambiemos al commit anterior al que queremos eliminar
git checkout <commit-hash> 
# también podemos probar, si es el último commit, con
git checkout HEAD^1
# ahora estamos en estado detached, podemos revisar si está funcionando nuestro código en esta versión
# cuando acabemos la revisión, podemos revertir el commit problemático (tenemos su commit hash justo antes)
git revert <commit-hash-del-commit-problematico>
# git revert no elimina el commit, sino que genera uno nuevo en el que los cambios se quitan. 
# Así que si ahora nos sincronizamos a la última versión, tendremos los cambios revertidos:
git checkout <nombre-de-rama>

Revertir archivos dentro de un commit

# Si los archivos están modificados y queremos la versión anterior:
git checkout -- <nombre_de_archivo>
# Si los archivos están aún por commitear si bien están añadidos al repositorio:
git reset HEAD <nombre_de_archivo>
# Si los archivos están ya committeados, pero no los queremos:
git reset HEAD^ <nombre_de_archivo>
# en windows:
git reset HEAD~1 <nombre_de_archivo>

Revertir un push

# necesitamos conocer la ID del último commit:
git log --oneline -n1
# con esta id podemos revertir el commit:
git revert <hash-del-commit>
# esto genera un nuevo commit que revierte los cambios
git push
# finalmente, subimos este nuevo commit

Revertir un commit anterior

# podemos utilizar el método anterior pero con otra id de commit para revertirlo:
git revert <hash-del-commit>
# esto genera un nuevo commit que revierte los cambios
git push
# finalmente, subimos este nuevo commit

Conclusiones

Espero que estos snippets puedan ser de ayuda para solventar problemas comunes. Es importante tener un mayor conocimiento sobre cómo funciona git internamente, pero muchas veces nos encontramos con problemas que no esperábamos y es difícil tener el tiempo para aprender todo lo que implican las causas del conflicto y necesitamos una solución.

Comparte este artículo

Artículos Relacionados