Módulo 7: Tags, versiones y entrega
Unidad: GIT-09
Público: alumnado de DAW, DAM, ASIR y grados de informática.
Itinerario: tronco, cierre del bloque de corrección y entrega; antes del proyecto final.
Prerrequisitos: módulos 5 y 6; manejar ramas, remotos, push y recuperación.
Objetivo observable: crear, verificar y publicar una versión etiquetada, distinguiendo tag ligero de anotado y justificando por qué una versión publicada no se mueve.
Fuente técnica: documentación oficial de git tag, git describe, git archive y git push.
Duración orientativa: 2,5 horas.
Idea clave
Un tag es un nombre estable para un commit concreto. Nada más, y eso es justo lo que lo hace útil: v1.0.0 no cambia de significado, mientras que main avanza con cada integración.
De ahí salen las tres ideas del módulo:
- Una rama se mueve; un tag no debería moverse nunca una vez publicado.
- Los tags no viajan con un
git pushnormal: hay que publicarlos aparte. - Etiquetar es marcar, no empaquetar. La entrega —notas de versión, paquete descargable, release— se construye a partir del tag.
Parte 1 — Etiquetar
Partimos de un proyecto con tres commits:
mkdir conversor && cd conversor && git init
printf "# Conversor de unidades\n\nConvierte km a millas.\n" > README.md
git add README.md && git commit -m "docs: crea README"
printf "def km_a_millas(km):\n return km * 0.621371\n" > conversor.py
git add conversor.py && git commit -m "feat: convierte km a millas"
printf "\n## Uso\n\n python conversor.py 10\n" >> README.md
git add README.md && git commit -m "docs: documenta el uso"Antes de etiquetar: el estado importa, y Git no avisa
Comprueba siempre el estado antes de marcar una versión:
git status --short
git log --oneline -32ed7be7 docs: documenta el uso
e1b1d83 feat: convierte km a millas
76d03fd docs: crea READMESin salida en status, el árbol está limpio. Esto no es una formalidad, y conviene entender por qué con una prueba:
printf "basura\n" > temp.txt
git status --short
git tag -a v9.9.9 -m "prueba"
git log --oneline -1 v9.9.9?? temp.txt
354d3a4 feat: convierte millas a kmGit ha creado el tag sin protestar. El peligro no es que Git te lo impida: es que no dice nada. El tag apunta al último commit e ignora por completo temp.txt. Si ese archivo sin confirmar era parte de lo que acabas de probar, has etiquetado una versión que no es la que funcionaba en tu equipo.
Por eso la comprobación va antes, no después:
git tag -d v9.9.9
rm temp.txtTag ligero y tag anotado
Hay dos tipos, y la diferencia es real, no cosmética.
git tag v0.1.0-ligero
git tag -a v1.0.0 -m "Primera versión funcional del conversor"Un tag ligero es solo un nombre apuntando a un commit. Un tag anotado (-a) es un objeto propio de Git con autor, fecha y mensaje. Compruébalo:
git cat-file -t v0.1.0-ligero
git cat-file -t v1.0.0commit
tagEl primero es el commit; el segundo es un objeto tag que apunta al commit. Para marcar versiones se usa siempre el anotado: deja constancia de quién publicó la versión y cuándo, que es información que se necesita meses después.
Ver qué hay dentro
git show v1.0.0tag v1.0.0
Tagger: Alumno Git <alumno@example.test>
Date: Tue Aug 4 21:50:26 2026 +0200
Primera versión funcional del conversor
commit 2ed7be7d31fd84795c74705f0bc51909692eb34a
Author: Alumno Git <alumno@example.test>
Date: Tue Aug 4 21:50:26 2026 +0200
docs: documenta el usoPrimero los datos del tag, después el commit al que apunta. Con un tag ligero esa primera parte no existe.
Etiquetar un commit anterior
No hace falta etiquetar en el momento. Si olvidaste marcar una versión, pásale el hash:
git tag -a v0.9.0 e1b1d83 -m "Versión previa a la documentación"
git log --oneline --decorate -32ed7be7 (HEAD -> main, tag: v1.0.0, tag: v0.1.0-ligero) docs: documenta el uso
e1b1d83 (tag: v0.9.0) feat: convierte km a millas
76d03fd docs: crea README--decorate muestra qué nombres apuntan a cada commit. Un mismo commit puede tener varios tags.
Listar
git tag
git tag -nv0.1.0-ligero
v1.0.0
v0.1.0-ligero docs: documenta el uso
v1.0.0 Primera versión funcional del conversor-n muestra el mensaje. Fíjate en que el ligero no tiene mensaje propio: enseña el del commit, porque no guarda nada más.
Parte 2 — Publicar una versión
git push no publica los tags
Este es el error número uno del módulo. Con un remoto de práctica:
git init --bare ../conversor-remoto.git
git remote add origin ../conversor-remoto.git
git push -u origin main
git ls-remote --tags origingit ls-remote --tags origin no imprime nada. Los commits están publicados; los tags no. Son referencias distintas y push solo envía la rama que le pides.
Publicar uno, o todos
git push origin v1.0.0
git ls-remote --tags origin * [new tag] v1.0.0 -> v1.0.0
d9a4e6253469e09ac47a0927f13098883f4417cf refs/tags/v1.0.0
2ed7be7d31fd84795c74705f0bc51909692eb34a refs/tags/v1.0.0^{}Esas dos líneas son la mejor demostración de qué es un tag anotado. La primera es el hash del objeto tag; la segunda, con ^{}, es el commit al que apunta. Publica ahora los demás:
git push origin --tags
git ls-remote --tags origin * [new tag] v0.1.0-ligero -> v0.1.0-ligero
* [new tag] v0.9.0 -> v0.9.0
2ed7be7d31fd84795c74705f0bc51909692eb34a refs/tags/v0.1.0-ligero
7d2a050e683946e021bb5afb8c1f449fac3eaa98 refs/tags/v0.9.0
e1b1d83caf1221a805fbeba13fbc44e771238ee6 refs/tags/v0.9.0^{}
d9a4e6253469e09ac47a0927f13098883f4417cf refs/tags/v1.0.0
2ed7be7d31fd84795c74705f0bc51909692eb34a refs/tags/v1.0.0^{}El ligero aparece en una sola línea: no hay objeto intermedio que desreferenciar. Los anotados, en dos.
Publicar tag por tag es más seguro que --tags, que envía también los que solo tenías como prueba local.
Borrar un tag son dos pasos
git tag -d v0.1.0-ligero
git ls-remote --tags origin | grep ligeroDeleted tag 'v0.1.0-ligero' (was 2ed7be7)
2ed7be7d31fd84795c74705f0bc51909692eb34a refs/tags/v0.1.0-ligeroBorrarlo en local no lo borra del remoto. Hace falta el segundo paso:
git push origin --delete v0.1.0-ligero - [deleted] v0.1.0-ligeroPor qué no se mueve un tag publicado
Esto se repite como norma sin explicar de dónde sale. Vamos a verlo. Un compañero clona el proyecto y ya tiene la versión:
git clone ../conversor-remoto.git ../conversor-compa
cd ../conversor-compa && git log --oneline -1 v1.0.02ed7be7 docs: documenta el usoMientras tanto, alguien decide «corregir» la versión moviendo el tag a otro commit:
git tag -f -a v1.0.0 -m "Versión inicial (corregida)"
git push -f origin v1.0.0Updated tag 'v1.0.0' (was d9a4e62)
+ d9a4e62...d28e607 v1.0.0 -> v1.0.0 (forced update)Ahora el compañero actualiza su copia:
git fetch origin --tags
git log --oneline -1 v1.0.0 2ed7be7..354d3a4 main -> origin/main
! [rejected] v1.0.0 -> v1.0.0 (would clobber existing tag)
2ed7be7 docs: documenta el usoAhí está el problema, en la palabra clobber. Git se niega a pisar un tag que ya tenía, así que:
- En el remoto,
v1.0.0apunta a un commit. - En el equipo del compañero,
v1.0.0apunta a otro distinto. - Ninguno de los dos recibe ningún aviso al trabajar.
Dos personas dicen «tengo la v1.0.0» y tienen código diferente. Un tag solo vale como referencia si significa lo mismo para todo el mundo, y en el momento en que se publica deja de estar bajo tu control.
Solo se actualiza si cada persona fuerza explícitamente:
git fetch origin --tags --force t [tag update] v1.0.0 -> v1.0.0Pero eso exige avisar a todo el equipo, uno por uno. La salida correcta ante una versión mal publicada es publicar una versión nueva —v1.0.1— no reescribir la anterior.
Parte 3 — Nombrar las versiones
Semantic Versioning
Git acepta cualquier nombre de tag. v1.0.0 sigue la convención SemVer, que es la más extendida y la que se usará en el proyecto final:
v MAJOR . MINOR . PATCH
1 . 4 . 2| Parte | Se incrementa cuando | Ejemplo |
|---|---|---|
| MAJOR | Hay un cambio incompatible con la versión anterior | 1.4.2 → 2.0.0 |
| MINOR | Se añade funcionalidad manteniendo compatibilidad | 1.4.2 → 1.5.0 |
| PATCH | Se corrige un error sin cambiar el comportamiento esperado | 1.4.2 → 1.4.3 |
Al subir un nivel, los inferiores vuelven a cero. La v inicial no es parte de SemVer, pero es la costumbre en los tags de Git.
Es una convención, no una regla que Git aplique: nada te impide etiquetar entrega-final-buena. La ventaja de seguirla es que cualquiera entiende, solo mirando el número, si actualizar es arriesgado.
git describe: dónde estoy respecto a la última versión
git describev1.0.0-1-g354d3a4Se lee: 1 commit después de v1.0.0, en el commit 354d3a4 (la g indica git). Si estuvieras justo en el tag, imprimiría solo v1.0.0.
Es lo que se usa para poner un número de versión automático en un paquete o en una compilación, y de paso responde a la pregunta «¿esto que tengo delante es exactamente la versión publicada o lleva cambios encima?».
Parte 4 — Entregar
Situarse en una versión
Para ver el proyecto tal y como estaba en una versión:
git switch --detach v0.9.0
git status -sb
cat conversor.pyHEAD is now at e1b1d83 feat: convierte km a millas
## HEAD (no branch)
def km_a_millas(km):
return km * 0.621371## HEAD (no branch) significa HEAD separado: estás en un commit, no en una rama. Es correcto para mirar, no para trabajar — los commits que hicieras aquí no pertenecerían a ninguna rama. Vuelve con:
git switch -El guion significa «la rama anterior». Si necesitas trabajar a partir de esa versión —por ejemplo, para un parche—, crea una rama: git switch -c hotfix/v0.9.1 v0.9.0.
Notas de versión desde el historial
Los tags convierten el historial en un changelog. Los commits entre dos versiones son:
git log --oneline v0.9.0..v1.0.0
git log --pretty=format:"- %s" v0.9.0..v1.0.0354d3a4 feat: convierte millas a km
2ed7be7 docs: documenta el uso
- feat: convierte millas a km
- docs: documenta el usoEl segundo comando ya es una lista pegable en un CHANGELOG.md. Aquí se cobra el trabajo de escribir buenos mensajes de commit desde el módulo 3: si los mensajes son cambios y arreglos, no hay notas de versión que sacar.
Empaquetar con git archive
Para entregar la versión a alguien que no usa Git:
git archive --format=zip --output=../conversor-v0.9.0.zip v0.9.0
unzip -l ../conversor-v0.9.0.zip 48 08-04-2026 21:50 README.md
46 08-04-2026 21:50 conversor.pyEl zip contiene el proyecto tal como estaba en v0.9.0, sin el directorio .git y sin los archivos posteriores. Es la forma limpia de entregar una versión concreta.
Releases en GitHub
Al publicar un tag en GitHub, la forja lo detecta y permite crear una release: una página con el nombre de la versión, las notas y archivos adjuntos descargables. GitHub genera además automáticamente el zip y el tar.gz del código.
Como en el módulo 5, conviene tener claro el reparto: el tag lo pone Git; la release la pone GitHub. Si borras la release, el tag sigue existiendo; y un repositorio con tags que nunca se publicaron no tendrá ninguna release.
Errores frecuentes
«He hecho
git pushy el tag no aparece en el remoto».pushenvía la rama, no los tags. Publícalo congit push origin v1.0.0, y verifica congit ls-remote --tags origin.«He etiquetado con archivos sin confirmar».
Git no protesta: el tag apunta al último commit e ignora lo que no esté confirmado. Puedes acabar entregando algo distinto de lo que probaste.git statusantes.«Me equivoqué en la versión, muevo el tag».
Quien ya la tenga no recibirá el cambio: sufetchresponderáwould clobber existing tagy se quedará con la versión antigua bajo el mismo nombre. Publicav1.0.1.«He borrado el tag y sigue en el remoto».
Son dos operaciones:git tag -den local ygit push origin --deleteen el remoto.«Uso tags ligeros porque son más rápidos de escribir».
No guardan autor, fecha ni mensaje. Para marcar una versión, siempre-a.«Estoy en HEAD separado y he hecho commits».
No pertenecen a ninguna rama. Antes de cambiar de sitio,git switch -c <rama>para conservarlos; si ya te has movido,git reflog(módulo 6).
Ejercicio y criterios
Sobre un repositorio agenda-taller con un remoto de práctica y al menos cuatro commits con mensajes descriptivos:
- Verifica que el árbol está limpio y publica un tag anotado
v0.1.0. Demuestra congit cat-file -tque es un objetotagy no un commit. - Comprueba que tras
git push origin mainel tag no está en el remoto, y publícalo después. - Añade dos commits más y publica
v0.2.0. Genera las notas de versión congit log --pretty=format:"- %s" v0.1.0..v0.2.0. - Etiqueta retroactivamente un commit anterior como
v0.0.1y publícalo. - Ejecuta
git describedesde un commit posterior av0.2.0y explica cada parte de la salida. - Clona el repositorio en otra carpeta. Desde el original, mueve
v0.2.0a otro commit y fuerza su publicación. Hazfetchen el clon y captura el mensaje de rechazo. Explica qué versión tiene cada copia y cómo debería haberse resuelto. - Empaqueta
v0.1.0congit archivey comprueba que el contenido corresponde a esa versión y no a la actual.
Criterios de revisión:
- Todos los tags de versión son anotados, con mensaje.
- Se evidencia con salidas reales que
pushde la rama no publica tags, y quels-remotelos muestra después. - Las notas de versión salen del historial, no escritas a mano.
- El punto 6 incluye el mensaje
would clobber existing tagy una explicación de por qué la solución correcta era publicarv0.2.1. - El zip del punto 7 no contiene los archivos añadidos después de
v0.1.0. - El estado final del repositorio es limpio.
Resumen
Al finalizar, el alumnado debe saber:
- Explicar que un tag es un nombre estable para un commit, frente a una rama que se mueve.
- Comprobar el estado antes de etiquetar, sabiendo que Git no avisa si está sucio.
- Distinguir tag ligero de anotado, y usar siempre
-apara versiones. - Publicar y borrar tags como operaciones independientes de la rama.
- Justificar, con la evidencia del rechazo
would clobber, por qué una versión publicada no se mueve. - Nombrar versiones con Semantic Versioning y leer la salida de
git describe. - Situarse en una versión con HEAD separado sin perder trabajo.
- Generar notas de versión desde el historial y empaquetar una entrega con
git archive. - Distinguir el tag de Git de la release de GitHub.
Siguiente módulo: Módulo 8 · Proyecto final colaborativo y evaluación.
Estado editorial: listo.
Registro de revisión técnica
Revisado el 4 de agosto de 2026 con Git 2.47.1. Todas las secuencias se han reproducido desde una carpeta vacía, con identidad de práctica y configuración aislada. Se ha comprobado que:
git cat-file -tdevuelvecommitpara un tag ligero ytagpara uno anotado, ygit showde un anotado antepone las líneastag,Taggery el mensaje al commit.- Tras
git push -u origin main,git ls-remote --tags originno devuelve nada; los tags requieren su propiopush. - En
ls-remote, un tag anotado ocupa dos líneas (el objeto y su desreferencia^{}) y uno ligero, una. Es la evidencia más directa de la diferencia entre ambos. git tag -dno afecta al remoto; hace faltagit push origin --delete <tag>.- Demostración del tag movido: con un segundo clon que ya tenía
v1.0.0, ungit tag -f+git push -fen el original hace que elgit fetch origin --tagsdel clon responda! [rejected] v1.0.0 -> v1.0.0 (would clobber existing tag)y conserve el commit antiguo. Solo--forceen el fetch lo actualiza. Queda así justificada con evidencia la norma de no mover versiones publicadas. - Etiquetar con el árbol sucio no produce ningún aviso: el tag apunta al último commit e ignora los archivos sin confirmar. Se ha corregido en consecuencia la formulación del error frecuente, que antes daba a entender que Git lo impedía.
git describedevuelvev1.0.0-1-g354d3a4un commit después del tag.git switch --detach v0.9.0dejagit status -sben## HEAD (no branch)y el contenido del archivo en el estado de esa versión;git switch -devuelve a la rama previa.git archive --format=zip v0.9.0genera un paquete con los dos archivos de esa versión, sin.gitni los cambios posteriores.
Los hashes de las salidas son los de esta ejecución y serán distintos en cada repositorio.
Pendiente al llevar el módulo a GitHub: crear una release real desde un tag publicado en el repositorio del curso y añadir la captura o la descripción del resultado, para cerrar la sección «Releases en GitHub» con evidencia propia.