De conventional commits a un changelog
6 min de lectura actualizado el
Los conventional commits le dan a un changelog tres cosas gratis: el tipo de cada cambio, la parte del sistema que tocó, y si rompe algo. No le dan nada más. La redacción, la agrupación y la selección, que son el changelog, quedan completamente abiertas, y una canalización que finge lo contrario publica un git log formateado.
feat(exports): add CSV column selection
fix(auth): reject expired refresh tokens
chore(deps): bump node-pg to 8.11
Tres commits en el formato Conventional Commits. A partir de estos, una máquina puede decirte que uno es una función, otro una corrección, otro mantenimiento, y qué parte del sistema tocó cada uno. Eso es genuinamente útil, y es toda la promesa de la convención: un historial de commits que puede leer algo que no sea una persona. El error es pensar que eso te da un changelog. Te da la materia prima.
¿Qué especifica la convención?
Un tipo, un ámbito opcional, y una descripción: type(scope): description. Los tipos
convencionalmente son feat, fix, chore, docs, refactor, test, perf, build, ci. Dos
cosas marcan un cambio que rompe algo: un ! antes de los dos puntos, o un footer
BREAKING CHANGE:. Las herramientas se guían por feat y fix para saltos de versión menor y de
parche, y por el marcador de breaking para uno mayor.
| El commit te da | El changelog necesita | Quién llena la brecha |
|---|---|---|
feat / fix / chore | Added / Fixed / interno | Un mapeo, automático |
(scope) | Una agrupación que el lector reconoce | Una persona, una vez por ámbito |
! o BREAKING CHANGE: | Quién rompe, para cuándo, y qué hacer | Una persona, cada vez |
| La descripción, escrita para una revisora | El resultado, escrito para una clienta | Una persona, cada entrada |
| Un commit | Un cambio, que puede ser varios commits | Reglas de squash, o una persona |
El marcador se lo dice a la herramienta; no se lo dice al llamante, que es el tema de cómo deprecar una API y qué es un cambio que rompe algo. Es una especificación pequeña y vale la pena seguirla aunque nunca generes nada a partir de ella, porque fuerza una decisión por commit: ¿es este un cambio que ven los usuarios, o no?
¿Dónde se detienen los conventional commits?
Se detienen en la frase. Todo lo que captura la convención son metadatos sobre un cambio; el cambio en sí sigue descrito en el vocabulario de una revisora.
Los mensajes de commit están escritos para revisoras. fix(auth): reject expired refresh tokens es correcto y no le dice nada a una clienta. La lectora de un changelog quiere “se te
cerrará la sesión cuando de verdad haya expirado, en vez de ver 401 intermitentes”.
Los ámbitos son internos. exports, auth, ingest son nombres de módulos. Son estables, lo
que los hace buenos para agrupar, y no significan nada para quien está fuera de la base de código.
Un cambio suele ser varios commits. Una función mergeada en once commits produce once entradas, diez de las cuales son ruido, y aplastarlas para ocultarlo pierde el historial de revisión.
chore es un cajón de sastre, no una categoría. Actualizaciones de dependencias, cambios de
CI y renombrados terminan todos ahí, y algunos importan a los usuarios mientras la mayoría no.
Así que: la convención te da tipo, ámbito y estado de breaking gratis, y deja la redacción, la agrupación y la selección completamente abiertas. Esas tres son el changelog. Quién es realmente dueña de una entrada de changelog cubre quién debería encargarse de esa redacción, agrupación y selección, ya que la convención en sí no tiene opinión al respecto.
¿Cómo se genera un changelog a partir de conventional commits?
En dos capas, y la segunda tiene que ser obligatoria.
Capa uno, automática. Al mergear, deriva una entrada borrador del commit: tipo mapeado a un
tipo de changelog (feat a Added, fix a Fixed, un marcador de breaking a Changed más un flag),
ámbito guardado como metadato en vez de texto, enlace al PR. Colócala en la sección Unreleased que
pide Keep a Changelog.
Capa dos, humana, y requerida. Antes de que salga un lanzamiento, cada entrada borrador o recibe una reescritura de una línea en el vocabulario del usuario, o se marca interna y se descarta de la vista pública. Este es el paso que la gente intenta saltarse, y saltárselo es lo que produce changelogs que se leen como un diff.
El detalle importante de diseño es que la capa dos no es opcional en la canalización. Si un lanzamiento se puede cortar con borradores sin editar, lo será, la semana en que todos están ocupados. Qué pasos pertenecen a la máquina y cuáles a la persona es todo el tema de automatización de changelog.
Cortar el lanzamiento es también el momento en que un tag de git, un lanzamiento y esta entrada de changelog o encajan o empiezan a desincronizarse; tags de git, lanzamientos y tu changelog cubre cómo mantener los tres sincronizados.
Tres trampas
Los squash merges se comen los footers. Si tu plataforma aplasta con el título del PR como
mensaje, el footer BREAKING CHANGE: de un commit dentro de esa rama desaparece, y tu herramienta
deja de ver el cambio que rompe algo en silencio. Comprueba qué guarda realmente tu plantilla de
squash.
Los commits de revert producen entradas fantasma. Un fix que se revierte al día siguiente
genera una entrada por algo que nunca se lanzó, a menos que la derivación reconcilie los reverts. La
mayoría de las herramientas no lo hacen.
El salto de versión y el changelog se desincronizan. Si la versión se calcula a partir de commits y el changelog se escribe a mano después, se desvían en unos dos lanzamientos. Calcula ambos en la misma pasada o acepta que uno de los dos estará mal.
Si quieres la parte mecánica sin una canalización
Nuestro generador de changelog hace el paso de derivación en el navegador: pega commits, obtén entradas agrupadas y tipadas. Es deliberadamente determinista y enteramente del lado del cliente, así que los commits que pegas nunca salen de tu máquina, lo cual importa cuando los mensajes son de un repositorio privado. Hace la mitad de recolección con honestidad y no intenta la capa dos, porque la capa dos es un juicio y una herramienta que la finge produce exactamente el changelog contra el que argumenta este artículo.
Para la versión de canalización, herramientas de changelog cubre lo que existe.
El resumen
Los conventional commits responden “qué tipo de cambio es este” de forma fiable y barata. No responden “qué deberíamos decirle a la gente”, y ninguna cantidad de herramientas sobre el mensaje de commit lo hará, porque la información nunca estuvo en el mensaje de commit. Presupuesta la reescritura.
FAQ
¿Los conventional commits generan un changelog automáticamente? Generan un borrador automáticamente: entradas tipadas, con ámbito, enlazadas. La redacción para una clienta, la agrupación y la decisión de qué dejar fuera siguen necesitando a una persona, y una canalización que se salta ese paso publica mensajes de commit.
¿Qué tipos de conventional commit aparecen en un changelog?
feat y fix siempre, como Added y Fixed. perf normalmente, como Changed. chore, docs,
refactor, test, build y ci son internos por defecto y solo aparecen si una persona promueve
uno.
¿Cómo marcan los conventional commits un cambio que rompe algo?
Un ! después del tipo o ámbito (feat(api)!: ...), o un footer BREAKING CHANGE: en el cuerpo
del commit. Ambos se pierden si un squash merge solo conserva el título del PR.
¿Necesitas conventional commits para automatizar un changelog? No. Las etiquetas de PR, las plantillas de PR y los enlaces a issues llevan los mismos metadatos para equipos que mergean por pull request. Los conventional commits son la opción más barata cuando la unidad de cambio es el commit.
Las afirmaciones técnicas de este artículo no se han revisado de forma independiente. Si algo está mal, avísanos y lo corregiremos.