git control de versiones

¿Quieres patrocinar?

¿Quieres aparecer aquí? Si quieres patrocinar este blog, ponte en contacto conmigo a través de este formulario

Aprende Git de manera sencilla: Comandos avanzados

En este noveno artículo sobre la guía para aprender Git de manera sencilla y desde cero, vamos a ver comandos avanzados de Git. Estos comandos son probablemente menos conocidos y por lo tanto menos utilizados, pero no por ello menos útiles.

git merge

Se utiliza para combinar dos ramas, normalmente de un historial bifurcado. El comando git merge permite integrar tu rama de desarrollo de una feature en otra rama (normalmente tu rama principal de desarrollo o tu rama master).

La fusión de una rama sobre otra se realiza sobre la rama actual. Es decir, la rama actual se fusiona con la segunda rama y la fusión queda en la rama actual. La segunda rama no se ve afectada y se mantiene.

git merge [branch_name]

En algunas ocasiones, Git encuentra datos que han sido modificados en ambos historiales, y no puede combinarlos automáticamente. En este caso, se crea un conflicto y Git solicitará la intervención del usuario para poder continuar.

Una vez solucionado el conflicto, no hace falta realizar un nuevo commit, bastará con indicar a Git que continue con el merge:

git merge --continue

Si por el contrario, no somos capaces de resolver el conflicto o necesitamos la ayuda de alguien para ver con qué nos quedamos y qué borramos y no dispones de esa persona en ese momento, puedes abortar el proceso:

git merge --abort

Si todo va bien y el merge es satisfactorio, se mezclarán e intercalarán los commits de las dos ramas por fecha de creación del commit. Es decir, si hemos creado una rama para una nueva feature que partía de master, y master ha recibido más commits mientras terminábamos el desarrollo, cuando se fusiones las dos ramas, los commits de cada una de ellas aparecerán en orden de creación, pudiendo aparecer intercalados en el historial.

git rebase

Parecido a merge, Rebase es una utilidad de Git que se utiliza para integrar cambios de una rama a otra. Merge es una fusion de ramas y Rebase se considera una reorganización de las mismas. Dispone de 2 modos principales: «manual» e «interactivo».

La razón principal para hacer un rebase e lugar de un merge es mantener un historial del proyecto lineal. Es decir, si mientras has trabajado en una nueva feature, alguien de tu equipo ha realizado otra feature y ya la ha integrado en master, cuando vayas a integrar tu parte, se van a intercalar los commits tuyos con los de tu compañero. Rebase lo que haría es poner tus commits a continuación de los de tu compañero, reorganizando el historial.

De este modo, la fusión de ramas es más «limpia» y «clara». Si en un futuro tuviérais que investigar un bug o volver a un punto anterior, será mucho más fácil.

Ojo con rebase, ya que genera commits nuevos (con hash nuevos), por lo tanto nunca se recomienda hacer rebase si las dos ramas ya se han enviado a remoto.

El Rebase de una rama sobre otra se realiza sobre la rama actual. Es decir, la rama actual se reorganiza con la segunda rama y queda en la rama actual. La segunda rama no se ve afectada y se mantiene. Adicionalmente, podemos indicar que el Rebase sea interactivo con el flag --i.

git rebase [branch_name]
git rebase [branch_name] --i

En modo interactivo, en lugar de mover de forma ciega todos los commits a la nueva base, la reorganización te da la oportunidad (se abre un editor) de alterar los commits individuales en el proceso. Eso te permite limpiar el historial eliminando, dividiendo y alterando una serie existente de commits. Es como git commit --amend a la máxima potencia.

git stash

El comando git stash almacena temporalmente (o guarda en un stash (una pila)) los cambios que hayas efectuado en el código (y añadidos al index sin commitear) en el que estás trabajando para que puedas trabajar en otra cosa y, más tarde, regresar y retomar el trabajo donde lo habías dejado.

Guardar los cambios en stashes resulta práctico si tienes que cambiar rápidamente de contexto y ponerte con otra tarea, pero estás en medio de un cambio en el código y no lo tienes todo listo para confirmar los cambios.

También puede resultar útil cuando has comenzado a trabajar en master sin darte cuenta y no quieres deshacer los cambios o tener que copiar los archivos a otro directorio, resetear todo, crear la rama, y pegar de nuevo tus archivos. Podrías crear una nueva rama con estos cambios directamente.

# Almacena tus cambios en un stash
git stash

# Almacena tus cambios en un stash y añade un nombre descriptivo
git stash save "Montando el entorno de test"

# Lista todos los stash
git stash list

# Recuperar el último stash
git stash pop

# Recuperar un stash concreto
git stash pop --index 2

# Crear una rama con los cambios que tienes
git stash branch F/branch-name

# Eliminar los stashes
git stash clear

# Almacenar en un stash sólo algunos archivos
git stash -p

git cherry-pick

Resumiendo, hacer un cherry-pick es elegir un commit de una rama y aplicarla a otra. Con un ejemplo quizá se entienda un poco mejor: imagina que empiezas a desarrollar una nueva funcionalidad y a mitad del desarrollo un compañero/a va a echarte una mano, pero necesita parte de lo que ya has ido avanzando, por ejemplo crear una estructura de datos.

Este compañero/a podría usar git cherry-pick para elegir el commit en el que se creó esta estructura de datos y desarrollar una feature en paralelo a ti.

Otro ejemplo, imagina que un desarrollador ha comenzado a trabajar en una nueva funcionalidad. Durante su desarrollo, identifica un error preexistente. Entonces se crea un commit explícito para aplicar una solución al error. Desde la rama master podemos hacer un cherry-pick de este nuevo commit para corregir el error antes de que afecte a más usuarios.

La sintaxis es sencilla. Debemos obtener el hash del commit que queremos y a continuación situarnos sobre la rama donde queremos aplicarlo:

git cherry-pick [commit_hash]

El comando git cherry-pick también se puede combinar con algunas opciones de ejecución:

# Solicitar un mensaje de confirmación antes de aplicar la operación
-edit ó -e

# En lugar de hacer un nuevo commit, moverá el contenido del commit de destino al directorio de trabajo de la rama actual.
--no-commit

# Añadir una línea de firma 'signoff' al final del mensaje de confirmación de cherry-pick.
--signoff

git blame

El comando git blame se usa para examinar el contenido de un archivo línea por línea y ver cuándo se ha modificado cada línea y quién es el autor de las modificaciones. Muchos IDEs muestran esta información.

Sólo actúa sobre ficheros individuales, por lo que hay que indicar el archivo sobre el que queremos obtener información. El formato de salida de git blame se puede modificar con varias opciones de línea de comandos.

git blame [filename]

# Restringir la salida a in intervalo de líneas (ej: de la 1 a la 15)
git blame -L 1,15 [filename]

# Para mostrar el e-mail del usuario que hizo las modificaciones en lugar de su nombre de usuario
git blame -e [filename]

# Ignorar los cambios en los espacios en blanco y tabulaciones
git blame -w [filename]

# Detectar las líneas que se han copiado o movido dentro del mismo archivo
git blame -M [filename]

# Detectar las líneas que se han movido o copiado desde otros archivos
git blame -C [filename]

git bisect

El comando git bisect nos ayudará a encontrar bugs en nuestro código. A través del control de versiones podemos volver hasta un punto anterior donde sepamos que nuestro código funciona.

El proceso para dar con el commit donde se produjo el bug es: Primero inicia el proceso con el comando git bisect start, y luego utiliza git bisect bad para decirle al sistema que el commit actual está roto. A continuación, debemos indicar cuándo fue el último estado bueno conocido, usando git bisect good e indicando el commit o tag de la versión:

git bisect start
git bisect bad
git bisect good [commit_hash_or_tag_version]
git bisect reset

Git sabrá el número de commits que se han producido desde la última versión «buena» hasta el commit que hemos marcado como malo. Lo que hará es ir acotando, por ejemplo si hay 100 commits se situará sobre el commit de en medio (el 50) y te preguntará si tu código funciona o no.

Si el código funciona deberás escribir git bisect good, en caso contrario git bisect bad. Git ahora entre los 50 commits donde sabe que existe el problema, volverá a acotar al medio (el 25) y volverá a preguntar. Así hasta dar con el commit en cuestión donde se introdujo el bug.

Cuando hayas terminado, debes ejecutar git bisect reset para reiniciar el HEAD a donde estaba antes de comenzar. De esta forma darás con el punto en el historial a partir del cual dejó de funcionar tu código y podrás averiguar qué es lo que pasó.

¿Te ha resultado útil esta información?

Si este post te ha resuelto un problema, invítame a un café o a una cerveza. Con este pequeño gesto me animas a seguir escribiendo.