Las ramas son baratas, y esa es su virtud. También es su problema: cuando crear una cuesta cero, se crean muchas, y al cabo de unos meses el repositorio de Ana tiene ramas de funcionalidades ya integradas, ramas de experimentos que nadie recuerda, ramas con nombres como prueba o arreglo2 y alguna que lleva seis semanas sin un solo commit.

Un repositorio con cuarenta ramas de las que solo tres están vivas no es un problema técnico —Git aguanta miles sin despeinarse—, pero sí un problema humano: nadie sabe cuál es cuál, nadie se atreve a borrar nada por miedo a perder trabajo y el autocompletado deja de ser útil.

Esta lección es la caja de herramientas para mantener eso bajo control: listar ramas con la información que necesitas, saber cuáles se pueden borrar sin perder nada, renombrar, borrar con seguridad y ponerles nombres que digan algo. Es la lección menos espectacular del módulo y probablemente la que más veces vas a aplicar.

Contenido

  1. Listar ramas: git branch y sus variantes
  2. --merged y --no-merged: qué se puede borrar sin perder nada
  3. Listados a medida con --sort y --format
  4. Renombrar ramas con -m
  5. Borrar ramas: -d frente a -D
  6. Cuando el borrado seguro falla
  7. Convenciones de nombres
  8. Qué nombres admite Git: git check-ref-format
  9. Higiene: detectar ramas obsoletas
  10. Cierre del módulo

  1. Listar ramas: git branch y sus variantes

El comando base ya lo conoces:

git branch
  correccion/mensaje-lista-vacia
  documentacion/actualizar-notas
  experimento/almacenamiento-indexeddb
  funcionalidad/contador-tareas
  funcionalidad/exportar-csv
  funcionalidad/filtro-pendientes
  funcionalidad/orden-alfabetico
  limpieza/borrar-notas
* main
  prueba

Diez ramas. El asterisco marca la actual. Ordenadas alfabéticamente, sin más información. Es un punto de partida pobre: no sabes cuáles están integradas, ni cuáles llevan meses paradas.

-v: qué hay en la punta de cada una

git branch -v
  correccion/mensaje-lista-vacia        6d3f8b2 Mostrar un mensaje cuando el listado está vacío
  documentacion/actualizar-notas        3f7a9c1 Actualizar las notas internas con el nuevo flujo
  experimento/almacenamiento-indexeddb  5e8b1d4 Guardar tareas en IndexedDB
  funcionalidad/contador-tareas         9d1e4b7 Marcar tareas como completadas al hacer clic
  funcionalidad/exportar-csv            e9a2c5f Ahora sí
  funcionalidad/filtro-pendientes       b2e6d3f Corregir el foco del campo tras añadir una tarea
  funcionalidad/orden-alfabetico        a1e5c93 Mostrar las tareas ordenadas alfabéticamente
  limpieza/borrar-notas                 7f3c9a2 Borrar las notas internas, ya obsoletas
* main                                  c2a8f1e Fusionar el mensaje de lista vacía
  prueba                                8b6d3c2 Añadir estilos base del listado

Ya se puede empezar a razonar. Fíjate en prueba: apunta a 8b6d3c2, un commit antiquísimo del principio del proyecto. Es una rama muerta que se creó y nunca se usó.

Hay una variante doble:

git branch -vv

Añade, entre corchetes, la rama de seguimiento de cada una: con qué rama del repositorio remoto está emparejada y cuántos commits lleva por delante o por detrás. Como en este módulo todo ocurre en un único repositorio local, no hay nada que mostrar todavía; -vv cobrará todo su sentido en el módulo 4, cuando el trabajo empiece a viajar entre máquinas.

Otras opciones de listado

# Solo ramas que contienen un commit concreto
git branch --contains a1e5c93

# Solo ramas que NO lo contienen
git branch --no-contains a1e5c93

# Filtrar por patrón (admite comodines)
git branch --list 'funcionalidad/*'

# Incluir también las ramas remotas (módulo 4)
git branch -a

# Solo las remotas
git branch -r

--contains es especialmente útil para responder a "¿en qué ramas está este arreglo?":

git branch --contains 6d3f8b2
  correccion/mensaje-lista-vacia
* main

El arreglo del mensaje de lista vacía está en su rama original y en main. En ninguna otra.

  1. --merged y --no-merged: qué se puede borrar sin perder nada

Estas dos opciones son el corazón de la higiene de ramas.

git branch --merged
  correccion/mensaje-lista-vacia
  experimento/almacenamiento-indexeddb
  funcionalidad/contador-tareas
  funcionalidad/filtro-pendientes
  funcionalidad/orden-alfabetico
  limpieza/borrar-notas
* main
  prueba

El significado exacto, y conviene ser preciso porque genera confusión: --merged lista las ramas cuya punta es alcanzable desde la rama actual. Dicho de otro modo: todos sus commits están ya en el historial de main.

Traducido a la práctica: estas ramas se pueden borrar sin perder ni un solo commit.

Dos casos merecen comentario:

  • prueba aparece aquí porque su punta es 8b6d3c2, un commit del principio del proyecto que obviamente está en main. No aportaba nada, y por eso consta como fusionada.
  • experimento/almacenamiento-indexeddb también aparece, aunque su código nunca llegó al proyecto: la cerramos en la lección 03-04 con git merge -s ours, que crea el commit de fusión sin traer el contenido. Ese era precisamente el objetivo de aquella estrategia: que la rama dejara de figurar como pendiente.

Y ahora la otra cara:

git branch --no-merged
  documentacion/actualizar-notas
  funcionalidad/exportar-csv

Estas ramas tienen commits que main no tiene. Borrarlas dejaría esos commits sin ninguna referencia que los alcance. Por qué está cada una:

  • documentacion/actualizar-notas: resolvimos su conflicto modify/delete a favor del borrado del fichero, así que su commit nunca llegó a main. Correcto que aparezca aquí.
  • funcionalidad/exportar-csv: la integramos con squash. Su contenido está en main, pero sus seis commits originales no. Git dice la verdad: desde el punto de vista del grafo, no está fusionada.

Ese último caso es la trampa más habitual: una rama integrada con squash siempre aparece en --no-merged. Si tu equipo usa squash sistemáticamente, --merged deja de ser un criterio fiable para borrar y hay que llevar la cuenta de otra forma.

Comparar contra otra rama

Por defecto, las dos opciones comparan contra HEAD. Se puede indicar otra referencia:

# Ramas fusionadas en main, aunque yo no esté en main
git branch --merged main

# Ramas cuyo trabajo ya está en la versión 1.2
git branch --merged v1.2.0

# Ramas sin integrar en main
git branch --no-merged main

Es la forma correcta de usarlas en scripts, porque no depende de dónde estés situado.

El patrón de limpieza

Este es el idiom que verás en todas partes y que conviene entender pieza a pieza:

git branch --merged main | grep -v '^\*' | grep -v ' main$' | xargs -r git branch -d
  • git branch --merged main: las candidatas.
  • grep -v '^\*': quita la línea de la rama actual (la del asterisco).
  • grep -v ' main$': quita main de la lista, no vaya a ser que intente borrarse a sí misma.
  • xargs -r git branch -d: borra cada una con el borrado seguro (-r evita ejecutar nada si la lista viene vacía).

Antes de ejecutarlo, revisa siempre la lista quitando el último tramo:

git branch --merged main | grep -v '^\*' | grep -v ' main$'

Un consejo importante: usa -d y nunca -D en un comando automático como este. -d se niega si algo no cuadra; -D borra sin preguntar.

  1. Listados a medida con --sort y --format

El orden alfabético no es el más útil. Lo que casi siempre quieres saber es qué ramas están vivas, y eso es una cuestión de fechas.

git branch --sort=-committerdate
* main
  correccion/mensaje-lista-vacia
  funcionalidad/orden-alfabetico
  documentacion/actualizar-notas
  limpieza/borrar-notas
  funcionalidad/exportar-csv
  funcionalidad/filtro-pendientes
  funcionalidad/contador-tareas
  experimento/almacenamiento-indexeddb
  prueba

El guion delante de committerdate significa orden descendente: lo más reciente arriba. Las de abajo son las candidatas a desaparecer.

Campos de ordenación habituales:

Campo Ordena por
committerdate Fecha del último commit de la rama
authordate Fecha de autoría del último commit
refname Nombre (el orden por defecto)
-committerdate Fecha, de más reciente a más antigua

Y con --format se construye un listado a medida, usando los mismos marcadores de git for-each-ref:

git branch --sort=-committerdate \
  --format='%(HEAD) %(color:yellow)%(refname:short)%(color:reset) | %(committerdate:relative) | %(authorname) | %(contents:subject)'
* main | hace 2 horas | Ana Ferrer | Fusionar el mensaje de lista vacía
  correccion/mensaje-lista-vacia | hace 3 horas | Bruno Salas | Mostrar un mensaje cuando el listado está vacío
  funcionalidad/orden-alfabetico | hace 5 horas | Ana Ferrer | Mostrar las tareas ordenadas alfabéticamente
  documentacion/actualizar-notas | hace 2 días | Bruno Salas | Actualizar las notas internas con el nuevo flujo
  limpieza/borrar-notas | hace 2 días | Ana Ferrer | Borrar las notas internas, ya obsoletas
  funcionalidad/exportar-csv | hace 6 días | Bruno Salas | Ahora sí
  funcionalidad/filtro-pendientes | hace 3 semanas | Bruno Salas | Corregir el foco del campo tras añadir una tarea
  funcionalidad/contador-tareas | hace 3 semanas | Ana Ferrer | Marcar tareas como completadas al hacer clic
  experimento/almacenamiento-indexeddb | hace 2 meses | Ana Ferrer | Guardar tareas en IndexedDB
  prueba | hace 4 meses | Ana Ferrer | Añadir estilos base del listado

Esta sí es una vista útil. De un vistazo se ve qué está vivo, quién lo lleva y qué es lo último que se hizo.

Los marcadores más prácticos:

Marcador Contenido
%(HEAD) Un * si es la rama actual, un espacio si no
%(refname:short) El nombre de la rama sin refs/heads/
%(committerdate:relative) "hace 3 semanas"
%(committerdate:short) 2026-07-10
%(authorname) Autor del último commit
%(contents:subject) Primera línea del mensaje
%(objectname:short) Hash abreviado
%(color:...) / %(color:reset) Color

Como es imposible recordar esa línea, se guarda como alias:

git config --global alias.ramas "branch --sort=-committerdate --format='%(HEAD) %(color:yellow)%(refname:short)%(color:reset) | %(committerdate:relative) | %(authorname) | %(contents:subject)'"
git ramas

Los alias tienen su propia lección en el módulo 6; este es un buen candidato para tu colección.

  1. Renombrar ramas con -m

git branch -m <nombre-antiguo> <nombre-nuevo>

Bruno abrió hace unos días una rama llamada arreglo2 y ahora nadie sabe qué contiene. Tras mirarla, Ana le pone un nombre que diga algo:

git branch -m arreglo2 correccion/foco-tras-borrar

Si quieres renombrar la rama en la que estás, basta con un argumento:

git switch funcionalidad/exportar-csv
git branch -m funcionalidad/exportacion-csv

Hay una variante mayúscula, -M, que fuerza el renombrado aunque ya exista una rama con el nombre de destino, sobrescribiéndola. La misma advertencia de siempre: -M puede dejar commits huérfanos sin avisar. Usa -m salvo que sepas exactamente lo que haces.

Qué hace realmente

Nada sorprendente, a estas alturas del módulo:

ls .git/refs/heads/
cat .git/HEAD

Renombrar una rama es renombrar el fichero de 41 bytes y, si era la rama actual, actualizar .git/HEAD para que apunte al nombre nuevo. Los commits no se tocan: son inmutables y ni se enteran.

Por eso renombrar es una operación totalmente segura en local. Cuando la rama ya se ha compartido con el equipo la cosa cambia, porque los demás siguen viendo el nombre antiguo; eso pertenece al módulo 4.

  1. Borrar ramas: -d frente a -D

git branch -d <rama>     # borrado SEGURO
git branch -D <rama>     # borrado FORZADO

El seguro, sobre una rama ya integrada:

git branch -d funcionalidad/contador-tareas
Deleted branch funcionalidad/contador-tareas (was 9d1e4b7).

Fíjate en que Git te da el hash de donde estaba. No es cortesía: es el dato con el que puedes recrear la rama si te has equivocado (git branch <nombre> 9d1e4b7). Cópialo si tienes la más mínima duda.

Se pueden borrar varias de golpe:

git branch -d funcionalidad/filtro-pendientes funcionalidad/orden-alfabetico limpieza/borrar-notas
Deleted branch funcionalidad/filtro-pendientes (was b2e6d3f).
Deleted branch funcionalidad/orden-alfabetico (was a1e5c93).
Deleted branch limpieza/borrar-notas (was 7f3c9a2).

Qué se borra exactamente

El fichero de 41 bytes. Nada más.

ls .git/refs/heads/funcionalidad/

Los commits siguen íntegros en .git/objects/. Lo único que ha desaparecido es el nombre que apuntaba a ellos. Si esos commits siguen siendo alcanzables desde main (que es el caso cuando la rama estaba fusionada), no cambia absolutamente nada en el repositorio.

Este es el motivo por el que borrar una rama fusionada es una operación trivial y sin riesgo, y por el que no hay que tenerle ningún respeto.

Dos limitaciones

No puedes borrar la rama en la que estás:

git branch -d main
error: Cannot delete branch 'main' checked out at '/home/ana/proyectos/gestor-tareas'

Y, desde Git 2.5, tampoco puedes borrar una rama que esté activa en otra copia de trabajo (git worktree, lección 06-06). En los dos casos la solución es la misma: git switch a otra rama y repetir.

  1. Cuando el borrado seguro falla

git branch -d funcionalidad/exportacion-csv
error: The branch 'funcionalidad/exportacion-csv' is not fully merged.
If you are sure you want to delete it, run 'git branch -D funcionalidad/exportacion-csv'.

Qué significa exactamente ese mensaje: la punta de esa rama no es alcanzable desde la rama actual (ni desde su rama de seguimiento, si la tuviera). Es decir, hay commits en ella que no están en ningún otro sitio del grafo.

Y qué NO significa: no significa que el trabajo se haya perdido, ni que el contenido no esté en main. Recuerda el caso del squash: el contenido de funcionalidad/exportacion-csv está íntegro en main en el commit 3b9e7d1, pero sus seis commits originales no. Git razona sobre el grafo, no sobre el contenido.

Antes de forzar, comprueba. Este es el reflejo que hay que adquirir:

# ¿Qué commits se quedarían sin referencia?
git log --oneline main..funcionalidad/exportacion-csv
e9a2c5f Ahora sí
7e2a9c4 Quitar el console.log
1d6b4f8 wip 2
5a8e2b9 Arreglar el separador
9f3a2c1 wip
4a1c7e3 Primer intento de exportación
# ¿Su contenido está realmente en main?
git diff main funcionalidad/exportacion-csv
(sin salida)

Sin diferencias: el contenido es idéntico. Se integró con squash y no se pierde nada. Ahora sí, con conocimiento de causa:

git branch -D funcionalidad/exportacion-csv
Deleted branch funcionalidad/exportacion-csv (was e9a2c5f).

-D es recuperable (durante un tiempo)

Un borrado con -D no es el fin del mundo, y conviene saberlo para no vivir con miedo. Los commits siguen en la base de datos de objetos, y Git mantiene un registro llamado reflog con todos los movimientos de referencias. Mientras el hash sea recuperable —de ahí que git branch -D lo imprima al borrar— basta con recrear la rama:

git branch funcionalidad/exportacion-csv e9a2c5f

Los commits sin ninguna referencia acaban siendo eliminados por el recolector de basura (git gc), pero no de inmediato: el plazo por defecto es de 30 días para objetos inalcanzables y 90 días para las entradas del reflog. Hay margen de sobra.

Todo el procedimiento de rescate —encontrar el hash con git reflog y git fsck --lost-found cuando ya no lo tienes apuntado— se ve en la lección 09-04. Aquí quédate con la idea tranquilizadora: -D casi nunca es irreversible.

-d -D
Comprueba si está fusionada No
Falla si hay commits sin integrar No
Imprime el hash al borrar
Recuperable después Sí (nada se pierde) Sí, vía reflog, durante semanas
Cuándo usarlo Siempre por defecto Cuando has comprobado y sabes lo que haces

  1. Convenciones de nombres

Git no impone ninguna estructura en los nombres de rama más allá de unos pocos caracteres prohibidos. Lo que hacen los equipos es adoptar prefijos con barra que agrupan las ramas por propósito, exactamente como hemos venido haciendo en el módulo.

Prefijo Para qué Ejemplo
funcionalidad/ (o feature/) Funcionalidad nueva funcionalidad/exportar-csv
correccion/ (o fix/, bugfix/) Corrección de un fallo correccion/mensaje-lista-vacia
hotfix/ Corrección urgente sobre la versión en producción hotfix/error-al-guardar
experimento/ (o spike/) Prueba que puede acabar descartada experimento/almacenamiento-indexeddb
documentacion/ (o docs/) Solo documentación documentacion/guia-instalacion
refactor/ Reorganización sin cambio funcional refactor/separar-modulo-tareas
limpieza/ (o chore/) Mantenimiento, dependencias, configuración limpieza/borrar-notas
release/ Preparación de una versión release/1.3.0

Muchos equipos añaden el identificador de la tarea del gestor de incidencias, que permite ir de la rama a la especificación y viceversa:

funcionalidad/GT-142-exportar-csv
correccion/GT-158-mensaje-lista-vacia

Y algunos incluyen el nombre de quien la lleva, útil en equipos grandes:

ana/funcionalidad/orden-alfabetico
bruno/correccion/foco-del-campo

La ventaja práctica de la barra

No es solo estética. La barra permite filtrar:

git branch --list 'funcionalidad/*'
git branch --list 'correccion/*'

Y hace que el autocompletado sea utilizable: escribes git switch fun<TAB> y la shell te ofrece solo ese grupo.

También conviene saber por qué la barra tiene un límite: como los nombres se convierten en rutas dentro de .git/refs/heads/, no puede existir a la vez una rama llamada funcionalidad y otra llamada funcionalidad/orden. La primera sería un fichero y la segunda necesitaría que funcionalidad fuese un directorio. Git lo rechaza:

git branch funcionalidad
fatal: cannot lock ref 'refs/heads/funcionalidad': 'refs/heads/funcionalidad/orden-alfabetico' exists; cannot create 'refs/heads/funcionalidad'

Es un error críptico con una explicación muy simple, y ahora ya la conoces.

Reglas de estilo que ayudan

  • Minúsculas y guiones: funcionalidad/exportar-csv, no Funcionalidad/ExportarCSV. Evita sorpresas entre sistemas de ficheros sensibles y no sensibles a mayúsculas (recuerda que Ana está en Ubuntu, Bruno en macOS y Carla en Windows).
  • Sin tildes ni ñ. correccion/, no corrección/. Git los admite, pero acaban en URL, en nombres de fichero y en salidas de herramientas de terceros donde dan problemas.
  • Descriptivo pero corto: correccion/foco-tras-anadir, no correccion/arreglar-el-problema-del-foco-que-reporto-el-cliente-el-martes.
  • Nada de prueba, temporal, nueva, arreglo2. Dentro de dos semanas no sabrás qué eran, y por eso nadie se atreverá a borrarlas.

Los flujos de trabajo con nombre

Existen metodologías completas que definen qué ramas hay, cómo se llaman, de dónde salen y dónde se integran. Las más conocidas:

  • Git Flow: ramas develop, release/*, hotfix/* y feature/* sobre una main que solo contiene versiones publicadas.
  • GitHub Flow: una sola rama de larga duración (main) y ramas de trabajo de vida corta que se integran mediante propuestas de cambio.
  • Trunk Based Development: todo el mundo integra en el tronco a diario, con ramas de horas y no de días.

Cada una tiene sus supuestos sobre el tamaño del equipo, la frecuencia de publicación y el nivel de automatización. Las estudiaremos en el módulo 7, donde ya tendremos los remotos y las propuestas de cambio para entenderlas de verdad. Por ahora, lo importante: la convención concreta importa menos que el hecho de que todo el equipo siga la misma.

  1. Qué nombres admite Git: git check-ref-format

Las reglas están definidas formalmente y se pueden comprobar:

git check-ref-format --branch 'funcionalidad/exportar-csv'
funcionalidad/exportar-csv

Si el nombre es válido, lo imprime y devuelve 0. Si no:

git check-ref-format --branch 'funcionalidad/exportar csv'
fatal: 'funcionalidad/exportar csv' is not a valid branch name

Las reglas principales, con la razón de cada una:

Prohibido Ejemplo inválido Por qué
Espacios y caracteres de control mi rama Rompen el uso en línea de comandos
Dos puntos seguidos .. rama..vieja .. es el operador de rangos
Los caracteres ~ ^ : ? * [ \ rama^2, rama:x Son sintaxis de referencias o comodines
Empezar o acabar en /, o // /rama, rama//x No son rutas válidas
Acabar en . o en .lock rama., rama.lock .lock es el mecanismo de bloqueo de Git
El componente @{ rama@{1} Es la sintaxis del reflog
Un solo @ @ Es un alias de HEAD
Empezar un componente por . .oculta Sería un fichero oculto

Un detalle curioso y coherente con todo lo que has aprendido: la mayoría de estas reglas existen porque el nombre de la rama es a la vez una ruta de fichero y una expresión que Git tiene que poder analizar. Prohibir ^ no es capricho: si existiera una rama llamada mi^rama, git log mi^rama sería ambiguo.

Comprobación en un script:

NOMBRE="funcionalidad/nueva-cosa"
if git check-ref-format --branch "$NOMBRE" > /dev/null 2>&1; then
  git switch -c "$NOMBRE"
else
  echo "Nombre de rama no válido: $NOMBRE"
fi

Este tipo de validación es exactamente lo que se automatiza con un hook para impedir que entren nombres que no siguen la convención del equipo. Los hooks son el tema de la lección 06-01.

  1. Higiene: detectar ramas obsoletas

Reunamos todo en una rutina de mantenimiento. Basta con hacerla una vez al mes.

Paso 1: ver el panorama ordenado por antigüedad.

git branch --sort=committerdate --format='%(committerdate:short) %(refname:short) %(authorname)'
2026-03-12 prueba Ana Ferrer
2026-05-28 experimento/almacenamiento-indexeddb Ana Ferrer
2026-07-10 funcionalidad/contador-tareas Ana Ferrer
2026-07-12 funcionalidad/filtro-pendientes Bruno Salas
...

Sin el guion, el orden es ascendente: lo más viejo arriba, que es lo que quieres cuando buscas candidatos a borrar.

Paso 2: separar las integradas de las que no lo están.

echo "=== Fusionadas en main (se pueden borrar) ==="
git branch --merged main | grep -v ' main$'

echo "=== Sin fusionar (revisar antes) ==="
git branch --no-merged main

Paso 3: revisar una por una las que no están fusionadas. Para cada una:

RAMA=experimento/almacenamiento-indexeddb

# ¿Qué commits tiene que main no tenga?
git log --oneline main..$RAMA

# ¿Cuánto hace del último?
git log -1 --format='%ar por %an' $RAMA

# ¿Su contenido está en main de otra forma (squash)?
git diff main..$RAMA --stat

Con esos tres datos, la decisión es fácil: si el contenido ya está en main, -D sin miedo; si tiene trabajo real y reciente, se deja; si tiene trabajo real pero de hace meses, se habla con quien la abrió antes de tocar nada.

Paso 4: borrar las integradas.

git branch --merged main | grep -v '^\*' | grep -v ' main$' | xargs -r git branch -d

Señales de una rama obsoleta

Señal Qué suele indicar
Fusionada en main Cumplió su función; borrar
Sin commits en más de un mes Trabajo abandonado; preguntar
Nombre genérico (prueba, temp, nueva) Nadie recuerda para qué era
Su contenido ya está en main sin estar fusionada Se integró con squash; borrar con -D
Cientos de commits por detrás de main Fusionarla será un infierno; replantear

Y una reflexión de fondo

La mejor gestión de ramas es no necesitarla: si las ramas viven dos o tres días y se borran al integrarlas, el repositorio nunca acumula basura. La limpieza mensual es un parche para un problema que se evita trabajando con ramas cortas.

Es la misma conclusión a la que llegamos con los conflictos en la lección anterior, y no es casualidad: la vida corta de las ramas resuelve a la vez la divergencia, los conflictos y el desorden. Es la idea que sostiene el desarrollo basado en tronco y buena parte del módulo 7.

  1. Cierre del módulo

Recapitulemos lo que hemos construido en estas seis lecciones.

Empezamos con una promesa del módulo 2 —"una rama es un fichero con un hash dentro"— y la cumplimos hasta el byte: 41, exactamente. Vimos que HEAD es otro fichero que apunta a una rama, no a un commit, y que esa indirección es lo que permite que confirmar mueva el puntero correcto. Entendimos por qué las ramas de Git son gratis frente a las de Subversion, y qué significa que dos ramas diverjan desde un ancestro común.

Después las creamos y nos movimos entre ellas con git branch, git switch y git switch -c, entendimos por qué Git separó checkout en dos comandos, qué pasa con los cambios sin confirmar al cambiar de rama y qué es el HEAD desacoplado.

Las volvimos a juntar con git merge, distinguiendo el fast-forward —un puntero que avanza— de la fusión a tres bandas que crea un commit con dos padres, y aprendimos a forzar o prohibir cada comportamiento con --no-ff y --ff-only. Estudiamos las estrategias (ort, resolve, octopus, ours, subtree), las opciones -X, la diferencia crítica entre -s ours y -X ours, y el squash merge.

Resolvimos un conflicto de verdad: los marcadores, el estilo zdiff3 que muestra el ancestro, las tres etapas del índice, git add como forma de decir "resuelto" y git merge --abort como tecla de escape.

Y hemos terminado poniendo orden: listar, filtrar, renombrar, borrar con criterio y nombrar bien.

El problema que queda abierto

Y sin embargo, todo lo que hemos hecho en este módulo ha ocurrido dentro de un único portátil. Todas las ramas viven en el .git/ de Ana. Cuando decíamos "Bruno abrió una rama", en realidad estábamos razonando como si su trabajo ya estuviera allí.

En el mundo real no lo está. Ana trabaja en Ubuntu, Bruno en macOS, y sus repositorios son dos bases de datos de objetos completamente independientes que no se conocen entre sí. Los commits de Bruno están en su MacBook y los de Ana en su Ubuntu, y ningún git merge del mundo puede fusionar algo que no está en tu propio .git/objects/.

Faltan las respuestas a preguntas muy concretas:

  • ¿Cómo llegan los objetos de un repositorio a otro?
  • ¿Dónde vive la copia "oficial" del proyecto?
  • ¿Qué es exactamente ese origin/main que apareció fugazmente en la lección 02-06, y por qué lleva una barra en el nombre?
  • ¿Cómo se autentica Bruno para poder enviar su trabajo?
  • ¿Qué pasa si dos personas envían cambios sobre la misma rama a la vez?
  • Y sobre todo: Carla todavía no se ha incorporado. Está esperando en su Windows 11 a que le digan de dónde descargar el proyecto.

En el módulo 4: Trabajando con Repositorios Remotos cerramos ese círculo. Veremos qué es un remoto y por qué no es más que un nombre corto para una URL, cómo se registra con git remote add, cómo funciona la autenticación con SSH y con tokens, la diferencia crucial entre git fetch y git pull, cómo enviar el trabajo con git push, y qué son las ramas de seguimiento que hacen que git status te diga "tu rama está 3 commits por delante de origin/main".

Y la buena noticia es que todo lo que has aprendido en este módulo sigue siendo cierto sin ningún cambio. Fusionar el trabajo de otra persona es exactamente el mismo git merge que has usado aquí. Los conflictos se resuelven igual. Las estrategias son las mismas. Lo único que se añade es el transporte: cómo consiguen los objetos viajar de un .git/ a otro. Una vez han llegado, todo funciona como ya sabes.

Errores Comunes y Consejos

Error 1: interpretar --no-merged como "aquí hay trabajo perdido". Solo significa que la punta de esa rama no es alcanzable desde donde estás. Una rama integrada con squash siempre aparecerá ahí aunque su contenido esté íntegro en main. Comprueba con git diff main..<rama> antes de sacar conclusiones.

Error 2: usar -D por costumbre porque -d "da la lata". Esa lata es la red de seguridad. Cada vez que -d falla, te está diciendo algo que merece treinta segundos de comprobación. Reserva -D para después de haber mirado.

Error 3: no anotar el hash que imprime el borrado. Deleted branch X (was 9d1e4b7) es tu billete de vuelta. Si te das cuenta al minuto siguiente de que te has equivocado, git branch X 9d1e4b7 lo arregla. Sin el hash, hay que ir al reflog.

Error 4: crear una rama con el nombre de un directorio de ramas existente. Si tienes funcionalidad/orden-alfabetico, no puedes crear funcionalidad. El error que da Git es críptico, pero la causa es que los nombres son rutas de fichero.

Error 5: acumular ramas "por si acaso". El coste no es el espacio (son 41 bytes) sino la confusión: nadie sabe cuáles están vivas, el autocompletado se vuelve inútil y las listas dejan de leerse. Si la rama está fusionada, bórrala; los commits no se van a ninguna parte.

Consejo 1: guarda el listado bonito como alias. git ramas con fecha relativa, autor y último mensaje es la vista que de verdad usarás. La línea completa está en el apartado 3.

Consejo 2: haz la limpieza mensual, y hazla en dos pasos. Primero listar, revisar con los ojos, y después borrar. Nunca en un solo comando encadenado sin mirar.

Consejo 3: nombra las ramas pensando en quien las vea dentro de un mes. Incluye el prefijo de tipo y, si el equipo usa un gestor de incidencias, su identificador. Es la diferencia entre una lista de ramas que se entiende y una que da miedo tocar.

Consejo 4: borra la rama en cuanto la integres. Convertirlo en el último paso del ritual de fusión —fusionar, comprobar, borrar— hace que la limpieza mensual no llegue nunca a ser necesaria.

Ejercicios

Ejercicio 1: auditoría de un repositorio

En un repositorio con varias ramas (créalas si hace falta), responde con comandos:

  1. ¿Qué ramas se pueden borrar ahora mismo sin perder ningún commit?
  2. ¿Qué ramas tienen trabajo sin integrar y cuántos commits son en cada caso?
  3. ¿Cuál es la rama que lleva más tiempo sin actividad?
  4. ¿En qué ramas está presente un commit concreto?

Ejercicio 2: el caso del squash

Reproduce la situación que confunde a --merged: integra una rama con --squash y demuestra con comandos que:

  1. Su contenido está en main.
  2. Git la considera no fusionada.
  3. -d se niega a borrarla y -D no.
  4. Después de borrarla con -D, se puede recrear exactamente donde estaba.

Ejercicio 3: validar nombres de rama

Escribe un pequeño script que reciba un nombre de rama y decida si es válido según Git y según una convención de equipo que exija uno de estos prefijos: funcionalidad/, correccion/, hotfix/ o documentacion/. Pruébalo con al menos cuatro nombres, válidos e inválidos.

Soluciones

Solución 1:

# 1. Ramas borrables sin perder nada
git branch --merged main | grep -v ' main$'
  correccion/mensaje-lista-vacia
  funcionalidad/contador-tareas
  prueba

Su punta es alcanzable desde main: borrarlas no deja ningún commit sin referencia.

# 2. Ramas con trabajo sin integrar, con el recuento
for r in $(git branch --no-merged main --format='%(refname:short)'); do
  n=$(git rev-list --count main..$r)
  echo "$r: $n commits sin integrar"
done
documentacion/actualizar-notas: 1 commits sin integrar
funcionalidad/exportacion-csv: 6 commits sin integrar

git rev-list --count main..<rama> cuenta los commits del rango, con la misma semántica que aprendiste en la lección 02-06.

# 3. La rama más inactiva: orden ascendente, primera línea
git branch --sort=committerdate --format='%(committerdate:short) %(refname:short)' | head -1
2026-03-12 prueba
# 4. En qué ramas está un commit
git branch --contains 6d3f8b2
  correccion/mensaje-lista-vacia
* main

Solución 2:

mkdir /tmp/practica-gestion && cd /tmp/practica-gestion
git init -b main
echo "base" > f.txt && git add . && git commit -m "Base"

git switch -c funcionalidad/algo
echo "paso 1" >> f.txt && git commit -am "Paso 1"
echo "paso 2" >> f.txt && git commit -am "Paso 2"
echo "paso 3" >> f.txt && git commit -am "Paso 3"

git switch main
git merge --squash funcionalidad/algo
git commit -m "Añadir la funcionalidad completa"
# 1. El contenido SÍ está en main
git diff main funcionalidad/algo
(sin salida)

Cero diferencias: los ficheros son idénticos.

# 2. Pero Git no la considera fusionada
git branch --merged main
* main
git branch --no-merged main
  funcionalidad/algo
git log --oneline main..funcionalidad/algo
5f8b2e1 Paso 3
2c9d4e6 Paso 2
9f4c2a8 Paso 1

Tres commits que main no tiene, aunque su efecto sí esté ahí, condensado en un único commit distinto.

# 3. El borrado seguro falla
git branch -d funcionalidad/algo
error: The branch 'funcionalidad/algo' is not fully merged.
If you are sure you want to delete it, run 'git branch -D funcionalidad/algo'.
git branch -D funcionalidad/algo
Deleted branch funcionalidad/algo (was 5f8b2e1).
# 4. Recreación exacta con el hash que Git nos dio
git branch funcionalidad/algo 5f8b2e1
git log --oneline -1 funcionalidad/algo
5f8b2e1 Paso 3

Idéntica a como estaba. Los commits nunca se fueron: solo se borró el nombre que los señalaba.

Solución 3:

#!/bin/bash
# validar-rama.sh — comprueba nombre válido para Git y para el equipo

NOMBRE="$1"

if [ -z "$NOMBRE" ]; then
  echo "Uso: $0 <nombre-de-rama>"
  exit 2
fi

# 1. ¿Lo acepta Git?
if ! git check-ref-format --branch "$NOMBRE" > /dev/null 2>&1; then
  echo "RECHAZADO: '$NOMBRE' no es un nombre de rama válido para Git."
  exit 1
fi

# 2. ¿Cumple la convención del equipo?
case "$NOMBRE" in
  funcionalidad/*|correccion/*|hotfix/*|documentacion/*)
    ;;
  *)
    echo "RECHAZADO: '$NOMBRE' debe empezar por funcionalidad/, correccion/, hotfix/ o documentacion/."
    exit 1
    ;;
esac

# 3. Regla de estilo adicional: sin mayúsculas
if echo "$NOMBRE" | grep -q '[A-ZÑÁÉÍÓÚ]'; then
  echo "RECHAZADO: '$NOMBRE' no debe llevar mayúsculas ni caracteres acentuados."
  exit 1
fi

echo "ACEPTADO: $NOMBRE"
exit 0

Pruebas:

chmod +x validar-rama.sh

./validar-rama.sh 'funcionalidad/exportar-csv'
./validar-rama.sh 'correccion/mensaje-lista-vacia'
./validar-rama.sh 'arreglo2'
./validar-rama.sh 'funcionalidad/Exportar CSV'
./validar-rama.sh 'correccion/rama..vieja'
ACEPTADO: funcionalidad/exportar-csv
ACEPTADO: correccion/mensaje-lista-vacia
RECHAZADO: 'arreglo2' debe empezar por funcionalidad/, correccion/, hotfix/ o documentacion/.
RECHAZADO: 'funcionalidad/Exportar CSV' no es un nombre de rama válido para Git.
RECHAZADO: 'correccion/rama..vieja' no es un nombre de rama válido para Git.

Fíjate en el cuarto caso: el espacio hace que lo rechace ya git check-ref-format, sin que lleguemos a la comprobación de mayúsculas. Y el quinto lo rechaza por el .., que Git reserva para los rangos de commits.

Este script es exactamente lo que se convierte en un hook pre-commit o pre-push para que la convención se aplique sola, sin depender de la disciplina de cada persona. Lo veremos en la lección 06-01.

Conclusión

Con esta lección cerramos el módulo 3. Lo aprendido aquí:

  • git branch -v muestra la punta y el último mensaje de cada rama; -vv añadirá la información de seguimiento cuando tengamos remotos (módulo 4).
  • --merged y --no-merged responden a la pregunta clave del mantenimiento: qué ramas se pueden borrar sin perder ningún commit. Ojo con las integradas mediante squash, que siempre aparecen como no fusionadas aunque su contenido esté en main.
  • --sort=-committerdate con --format convierte un listado alfabético inútil en una vista real de qué está vivo, quién lo lleva y desde cuándo. Guárdalo como alias.
  • git branch -m renombra: es renombrar el fichero de 41 bytes y, si era la rama actual, actualizar HEAD. Totalmente seguro en local.
  • -d frente a -D: el primero comprueba que no se pierde nada y se niega si no es así; el segundo fuerza. Cuando -d falla, significa que la punta no es alcanzable desde donde estás, no necesariamente que el trabajo se pierda. Comprueba con git log main..<rama> y git diff main <rama> antes de forzar. -D es recuperable durante semanas gracias al reflog (módulo 9).
  • Borrar una rama borra 41 bytes. Los commits siguen intactos en la base de datos de objetos.
  • Las convenciones de nombres (funcionalidad/, correccion/, hotfix/…) agrupan, permiten filtrar y hacen útil el autocompletado. Las reglas formales de Git se comprueban con git check-ref-format --branch.
  • La higiene es una rutina de cuatro pasos: listar por antigüedad, separar fusionadas de no fusionadas, revisar las segundas y borrar las primeras. Y la mejor higiene es tener ramas de vida corta que no lleguen a acumularse.

Lo que viene

Ya dominas las ramas: qué son, cómo crearlas, cómo fusionarlas, cómo resolver los choques y cómo mantener el repositorio ordenado. Pero todo ello dentro de un único .git/.

En el módulo 4: Trabajando con Repositorios Remotos el proyecto sale por fin del portátil de Ana. Veremos qué es un remoto, cómo se registra, cómo se autentica el acceso, la diferencia entre git fetch y git pull, cómo enviar el trabajo con git push y qué son las ramas de seguimiento. Y Carla se incorporará por fin al equipo desde su Windows 11, clonando el proyecto tal como hizo Bruno en la lección 02-02, pero esta vez con un repositorio que ya tiene historia, ramas y una forma de trabajar.

Dominando Git: De Principiante a Avanzado

Módulo 1: Introducción a Git

Módulo 2: Operaciones Básicas de Git

Módulo 3: Ramas y Fusión

Módulo 4: Trabajando con Repositorios Remotos

Módulo 5: Operaciones Avanzadas de Git

Módulo 6: Herramientas y Técnicas de Git

Módulo 7: Estrategias de Colaboración y Flujo de Trabajo

Módulo 8: Mejores Prácticas y Consejos de Git

Módulo 9: Solución de Problemas y Depuración

Módulo 10: Git en el Mundo Real

© Copyright 2026. Todos los derechos reservados