Conventional Commits: Guía Completa para Mejores Mensajes de Commit en Git
Introducción
Escribir mensajes de commit significativos en Git es una de esas habilidades que separa a los desarrolladores junior de los profesionales experimentados. Sin embargo, la mayoría de nosotros hemos escrito commits como "fix bug", "update stuff", o el infame "WIP" en algún momento. Yo mismo lo he hecho.
Conventional Commits es una especificación que proporciona una convención sencilla para crear mensajes de commit explícitos y estandarizados. Siguiendo una estructura simple, puedes comunicar la intención de tus cambios de forma clara, automatizar el incremento de versiones y generar changelogs automáticamente.
En esta publicación, explicaré qué es Conventional Commits, cómo funciona, por qué deberías adoptarlo y cómo lo uso en mi flujo de trabajo de desarrollo diario.
¿Qué es Conventional Commits?
Conventional Commits es una especificación para escribir mensajes de commit estandarizados que siguen un formato específico. La convención está diseñada para ser legible tanto por humanos como por máquinas, permitiendo que las herramientas de automatización entiendan la naturaleza de los cambios en tu código.
La especificación fue creada por Benjamin E. Coe en 2017, inspirada en la necesidad de una documentación más clara en torno a los formatos de commit que ya estaban siendo utilizados por proyectos como Angular. El objetivo era eliminar la tediosa tarea de incrementar versiones y gestionar changelogs, al tiempo que se animaba a los desarrolladores a escribir mensajes de commit más reflexivos.
La Estructura del Mensaje de Commit
Un mensaje de commit convencional sigue esta estructura:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]Analicemos cada componente:
- type: Describe la categoría del cambio (obligatorio)
- scope: Proporciona contexto adicional sobre qué parte del código se ve afectada (opcional)
- description: Un breve resumen del cambio (obligatorio)
- body: Una explicación más detallada del cambio (opcional)
- footer: Metadatos adicionales como cambios de ruptura o referencias a issues (opcional)
Nota
La descripción debe seguir estas convenciones recomendadas:
- Usa el modo imperativo: "add feature" no "added feature" o "adds feature"
- Piensa en ello como completar la frase: "This commit will..."
- Empieza con minúscula (no "Add feature" sino "add feature")
- Sin punto al final
Tipos de Commit
La especificación define dos tipos obligatorios que se correlacionan directamente con Semantic Versioning (SemVer):
- feat: Introduce una nueva funcionalidad (se correlaciona con MINOR en SemVer)
- fix: Corrige un bug (se correlaciona con PATCH en SemVer)
Más allá de estos, la convenci ón de Angular recomienda tipos adicionales que muchos proyectos adoptan:
| Tipo | Descripción |
|---|---|
build | Cambios en el sistema de compilación o dependencias externas |
chore | Tareas de mantenimiento que no modifican archivos src o test |
ci | Cambios en archivos de configuración y scripts de CI |
docs | Cambios únicamente en documentación |
style | Cambios de estilo de código (formateo, espacios en blanco, etc.) |
refactor | Cambios de código que no corrigen bugs ni añaden funcionalidades |
perf | Mejoras de rendimiento (un tipo especial de refactor) |
test | Añadir o corregir tests |
ops | Operaciones de infraestructura, despliegue, backup, recuperación |
revert | Revierte un commit anterior |
Consejo
No tienes que usar todos estos tipos.
Empieza con feat, fix y docs, luego añade más según las necesidades de tu equipo.
La clave es la consistencia, no la completitud.
Ejemplos Prácticos
Veamos ejemplos reales de mensajes de commit convencionales, de simples a complejos. Considero que ver ejemplos reales es la forma más rápida de interiorizar el formato.
Commits Simples
Una corrección de bug básica:
fix: prevent racing condition in user authenticationUna nueva funcionalidad:
feat: add dark mode toggle to settings pageUna actualización de documentación:
docs: correct spelling in READMECommits con Scope
Los scopes (alcances) proporcionan contexto sobre qué parte del código se ve afectada. Encuentro los scopes particularmente útiles en monorepos o proyectos con módulos distintos:
feat(api): add endpoint for user preferences
fix(auth): handle expired tokens gracefully
docs(readme): update installation instructionsPrecaución
No uses identificadores de issues como scopes.
Usa fix(auth): resolve token expiration con Fixes #123 en el footer, no fix(#123): resolve token expiration.
Breaking Changes
Los cambios de ruptura son significativos porque indican que los consumidores de tu código necesitan hacer cambios. Se correlacionan con incrementos de versión MAJOR en SemVer.
Puedes indicar breaking changes de dos formas:
Usando la notación !:
feat!: remove deprecated authentication endpointsfeat(api)!: change response format for user endpointsUsando un footer BREAKING CHANGE:
feat: allow provided config object to extend other configs
BREAKING CHANGE: `extends` key in config file is now used for
extending other config files instead of extending presets.También puedes combinar ambos enfoques para máxima claridad:
refactor(runtime)!: drop support for Node 14
BREAKING CHANGE: Node 14 has reached end-of-life status.
Minimum supported version is now Node 18.Precaución
Los cambios de ruptura deberían ser poco frecuentes y estar bien documentados. Al introducir uno, asegúrate de que el cuerpo del commit explique por qué fue necesario el cambio y cómo deben migrar los usuarios.
Commits Multilínea con Cuerpo y Footer
Para cambios complejos, usa el cuerpo para explicar el "qué" y el "por qué". Tomarse tu tiempo para escribir un buen cuerpo de commit compensa cuando hay que depurar problemas meses después.
El cuerpo debería:
- Explicar la motivación del cambio
- Contrastar el nuevo comportamiento con el anterior
- Usar el modo imperativo, igual que la descripción
El footer se usa para:
- Referencias a issues:
Fixes #123,Closes #456,Refs JIRA-789 - Descripciones de cambios de ruptura (cuando se necesita más detalle)
- Coautores y revisores
fix(parser): handle edge case in markdown table parsing
The previous implementation failed when table cells contained
pipe characters. This fix properly escapes pipes within cell
content while maintaining backwards compatibility.
Fixes #1234
Reviewed-by: Jane DoeCommits Especiales
Algunos commits siguen los patrones predeterminados de Git en lugar del formato convencional:
Commit inicial:
chore: initCommits de merge siguen el formato predeterminado de Git:
Merge branch 'feature/user-auth' into mainCommits de revert siguen el formato predeterminado de Git:
Revert "feat(auth): add OAuth support"
This reverts commit abc1234.Más Ejemplos
Aquí hay ejemplos adicionales cubriendo diferentes tipos de commit:
perf: reduce memory footprint by using streaming parserrefactor: extract validation logic into separate modulestyle: apply consistent formatting to configuration filestest(api): add integration tests for user endpointsops: configure automatic database backups¿Por Qué Usar Conventional Commits?
Adoptar Conventional Commits aporta varios beneficios tangibles a tu flujo de trabajo de desarrollo. Aquí te cuento por qué lo he convertido en una práctica estándar en todos mis proyectos.
Generación Automática de Changelog
Herramientas como conventional-changelog y semantic-release pueden analizar tu historial de commits y generar automáticamente un changelog. Esto elimina el esfuerzo manual de mantener un archivo CHANGELOG.md y garantiza la precisión.
No uso herramientas automatizadas para todos los proyectos, pero incluso sin ellas, el formato vale la pena.
Con un vistazo rápido al historial de commits, puedo determinar fácilmente la siguiente versión: fix significa patch, feat significa minor, y un cambio de ruptura (con ! o BREAKING CHANGE) significa una nueva versión major.
Sin conjeturas, sin debates, los commits te dicen exactamente qué tipo de release estás preparando.
Incremento Automático de Versión
Al analizar los mensajes de commit, las herramientas pueden determinar el incremento de versión apropiado:
- Los commits
fix:disparan un incremento de versión PATCH (1.0.0 -> 1.0.1) - Los commits
feat:disparan un incremento de versión MINOR (1.0.0 -> 1.1.0) BREAKING CHANGEo!dispara un incremento de versión MAJOR (1.0.0 -> 2.0.0)
Esto elimina el error humano de las decisiones de versionado y garantiza que tus releases sigan SemVer correctamente.
Consejo
Incluso si no automatizas el incremento de versiones, el ejercicio mental de elegir el tipo de commit correcto te ayuda a pensar sobre el impacto de tus cambios.
Mejor Comunicación en Equipo
Los mensajes de commit estandarizados facilitan a los miembros del equipo entender qué cambió y por qué. Al revisar pull requests o investigar problemas, los mensajes de commit significativos sirven como documentación que explica la evolución del código.
He visto equipos perder horas tratando de entender por qué se hizo un cambio particular, solo para encontrar un mensaje de commit que dice "fix stuff". Conventional Commits previene esto.
Navegación Mejorada del Historial de Git
Un historial de commits bien estructurado se convierte en un recurso valioso. Puedes filtrar fácilmente commits por tipo, encontrar todos los cambios de ruptura, o rastrear cuándo se introdujo una funcionalidad:
# Encontrar todos los cambios de ruptura
git log --oneline --grep="BREAKING CHANGE"
# Encontrar todas las nuevas funcionalidades
git log --oneline --grep="^feat"
# Encontrar todas las correcciones de bugs en el módulo auth
git log --oneline --grep="^fix(auth)"Integración con CI/CD
Conventional Commits se integra perfectamente con pipelines de CI. Puedes configurar tu pipeline para:
- Validar mensajes de commit antes de hacer merge
- Publicar releases automáticamente basándote en los tipos de commit
- Generar notas de release para releases de GitHub/GitLab
- Disparar diferentes workflows basándote en los tipos de commit
Herramientas y Automatización
Varias herramientas ayudan a aplicar y aprovechar Conventional Commits en tus proyectos. Te guiaré a través de las que he encontrado más útiles.
Commitlint
commitlint valida que tus mensajes de commit sigan el formato convencional. Normalmente se usa con Git hooks para prevenir commits que no cumplan el formato.
Instala y configura commitlint:
npm install --save-dev @commitlint/cli @commitlint/config-conventionalCrea un commitlint.config.js:
module.exports = {
extends: ['@commitlint/config-conventional']
};Husky
Husky facilita la configuración de Git hooks. Combinado con commitlint, valida los commits antes de que se creen:
npm install --save-dev husky
npx husky init
echo "npx --no -- commitlint --edit \$1" > .husky/commit-msgNota
Husky 9+ usa una configuración más simple que las versiones anteriores. Si estás actualizando desde una versión anterior, consulta la guía de migración.
Semantic Release
semantic-release automatiza todo el flujo de trabajo de releases:
- Determina el siguiente número de versión
- Genera notas de release
- Publica el paquete
- Crea tags de Git y releases de GitHub
Esta herramienta es particularmente potente para autores de librerías que quieren releases totalmente automatizados. Lee tus commits convencionales y, por defecto, aplica semántica estilo Angular para decidir si un cambio es patch, minor o major. La uso en varios de mis proyectos de código abierto, y ha eliminado por completo los pasos manuales en mi proceso de releases.
Cómo Uso Conventional Commits
A lo largo de los años, he refinado mi enfoque para usar Conventional Commits en mis proyectos. Esto es lo que me funciona.
Mi Flujo de Trabajo Diario
No uso herramientas interactivas para cada commit. En cambio, he interiorizado el formato y escribo los commits directamente. Para commits complejos, uso mi editor para componer mensajes multilínea con el formato adecuado.
Mi flujo de trabajo típico:
- Hacer cambios enfocados y atómicos
- Añadir los cambios con
git add -ppara un control preciso - Escribir un mensaje de commit convencional
- Hacer push cuando la funcionalidad o corrección está completa
Tiendo a centrarme en implementar funcionalidades o correcciones con alcances reducidos y pequeños pasos. Al escribir commits convencionales, me mantengo enfocado en cambios pequeños e incrementales de forma natural. Esto hace muy fácil para otros revisar mi trabajo, ya que cada commit tiene un propósito claro y único.
Consejo
Usar git add -p (modo patch) te ayuda a crear commits atómicos añadiendo solo los fragmentos relacionados con un único cambio.
Esto lleva naturalmente a mejores mensajes de commit porque cada commit tiene un propósito claro.
Scopes que Uso Comúnmente
Defino los scopes basándome en la estructura del proyecto. Para una aplicación web típica, mis scopes podrían incluir:
api- Cambios en la API del backendui- Cambios en componentes del frontendauth- Cambios relacionados con autenticacióndeps- Actualizaciones de dependenciasconfig- Cambios de configuración
También uso el tipo de commit como scope cuando tiene sentido.
Por ejemplo, para las modificaciones de archivos de build (como cambios en pom.xml o package.json) uso el scope build, y los cambios en workflows de GitHub uso ci:
chore(build): update maven-compiler-plugin to 3.12.1
chore(ci): add code coverage step to PR workflowPara proyectos de Kubernetes como Eclipse JKube, uso scopes como:
kubernetes- Funcionalidad específica de Kubernetesopenshift- Funcionalidad específica de OpenShiftmaven- Cambios en el plugin de Mavengradle- Cambios en el plugin de Gradle
Aplicación en Proyectos Open Source
En proyectos de código abierto, puedes usar GitHub Actions para validar que los títulos de PR sigan el formato convencional. Esto es a menudo más práctico que validar cada commit ya que los contribuidores pueden hacer squash y rebase de su trabajo.
Aquí hay un enfoque simple usando una regex básica que funciona para cualquier proyecto sin dependencias externas:
name: PR Title Check
on:
pull_request:
types: [opened, edited, synchronize]
jobs:
check-title:
runs-on: ubuntu-latest
steps:
- name: Validate PR title follows Conventional Commits
run: |
TITLE="${{ github.event.pull_request.title }}"
PATTERN='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?(!)?: .+$'
if ! echo "$TITLE" | grep -qE "$PATTERN"; then
echo "::error::PR title does not follow Conventional Commits format"
echo "Expected: <type>[optional scope]: <description>"
echo "Got: $TITLE"
exit 1
fi
echo "PR title is valid: $TITLE"Este workflow funciona para cualquier proyecto sin requerir Node.js o acciones externas.
Si estás usando commitlint localmente, puedes añadir un commitlint.config.js a tu proyecto y usar npx -y commitlint en su lugar para una validación más completa.
Manejo de Squash Merges
Al usar squash merges, el título del PR se convierte en el mensaje de commit. Por eso validar los títulos de PR es crucial: asegura que tu rama main mantenga un historial limpio y convencional incluso cuando los contribuidores no sigan la convención en sus commits individuales.
Errores Comunes y Cómo Evitarlos
Aquí hay trampas que he encontrado (y a veces cometido yo mismo) y cómo evitarlas.
Usar el Tipo Incorrecto
Error: Usar fix para cualquier cosa que cambie código.
Solución: Reserva fix para correcciones de bugs reales.
Usa refactor para reestructuración de código, perf para optimizaciones, y chore para tareas de mantenimiento.
Descripciones Demasiado Genéricas
Error: Escribir descripciones como "update code" o "fix issue".
Solución: Sé específico sobre los cambios. "Fix null pointer in user validation" es mucho más útil que "fix bug".
Scopes Inconsistentes
Error: Usar diferentes scopes para el mismo módulo (por ejemplo, auth, authentication, login).
Solución: Documenta tus scopes en tu guía de contribución y mantén la consistencia. Usa un plugin de commitlint para aplicar los scopes permitidos.
Mezclar Funcionalidades en Un Solo Commit
Error: Combinar una funcionalidad con un refactor en el mismo commit.
Solución: Mantén los commits atómicos. Si necesitas refactorizar para implementar una funcionalidad, haz commits separados:
refactor(auth): extract token validation to separate function
feat(auth): add support for refresh tokensConsejo
Si te encuentras escribiendo "and" en la descripción de tu commit, probablemente estás tratando de hacer demasiado en un solo commit.
Primeros Pasos
¿Listo para adoptar Conventional Commits? Aquí hay un enfoque pragmático que recomiendo a los equipos nuevos en la convención.
Empieza Sencillo
No intentes configurar todas las herramientas de una vez. Empieza simplemente siguiendo el formato manualmente. Una vez que estés cómodo con la convención, añade herramientas gradualmente.
Documenta Tus Convenciones
Crea una sección en tu CONTRIBUTING.md que explique:
- Qué tipos de commit usa tu proyecto
- Qué scopes están disponibles
- Ejemplos de buenos mensajes de commit
Configura Validación Básica
Una vez que tu equipo esté cómodo con el formato, añade commitlint con Husky para detectar errores temprano. Esto previene que commits no conformes entren en tu historial.
Automatiza Incrementalmente
Después de que la validación funcione, considera añadir:
- Generación automática de changelog
- Incremento automático de versiones
- Releases automatizados
Cada paso se construye sobre el anterior, así que tómate tu tiempo.
Conclusión
Conventional Commits transforma cómo los equipos se comunican a través de su historial de Git. Siguiendo un formato simple y estandarizado, ganas comunicación más clara, versionado automatizado y mejor integración de herramientas.
La especificación es intencionalmente ligera: puedes empezar a usarla hoy sin ninguna herramienta. A medida que tu equipo se sienta cómodo con la convención, añade automatización para desbloquear todo su potencial.
Ya sea que estés trabajando en un proyecto personal o manteniendo software empresarial, los mensajes de commit significativos hacen tu código más mantenible y tus releases más predecibles. Prueba Conventional Commits. Tu yo futuro (y tus compañeros de equipo) te lo agradecerán.
Referencias
- Especificación de Conventional Commits v1.0.0
- Semantic Versioning (SemVer)
- Guía de Mensajes de Commit de Angular
- commitlint - Lint de mensajes de commit
- semantic-release - Gestión automatizada de versiones
