Uso de Conventional Commits

13 de noviembre de 2022

Coffee by Louis Hansel
Photo by ThisisEngineering RAEng on Unsplash

Al utilizar herramientas de control de versiones, como Git, los mensajes de los commits son siempre útiles para describir los tipos de cambios, los cambios y una breve descripción del porqué estos cambios fueron necesarios.

Estos mensajes deben tener algún tipo de significado, no solo para el autor de los cambios pero también para el resto del equipo, ya que una vez ingresados los cambios al árbol, estos serán la única fuente de información sobre porqué los mismos fueron ingresados en primer plano.

#.Introduciendo Conventional Commits

Conventional Commits es una convención y/o especificación de cómo debemos escribir estos mensajes de commits. Esta convención provee una simple y sencilla serie de reglas para mantener un historial de los cambios de forma uniforme.

Igualmente, los Conventional Commits proveen una serie de herramientas para ayudar a los desarrolladores a escribir estos mensajes de una forma más fácil y consistente, y al mismo tiempo están alineados con Semantic Versioning.

#.Reglas y especificaciones básicas de los Conventional Commits

  1. Deben proveer un tipo de los cambios, los cuales podrían ser: feat, fix, chore, entre otros. Este tipo es un sustantivo (noun).
  2. Pueden proveer (opcional) un scope (alcance) de estos cambios, para indicar el alcance de los mismos a nivel del sistema. Por ejemplo, si tu sistema se divide en distintos módulos y tu alcance solo está destinado a un módulo en particular este sería tu scope.
  3. Deben proveer una descripción inmediatamente luego del tipo y el scope. Esta se escribe en infinitivo (imperative) y debe ser corta.
  4. Se puede proveer un cuerpo de los cambios, el cual debe ser una descripción más detallada de los cambios. Este cuerpo debe ser escrito en infinitivo (imperative) igualmente.
  5. Pueden proveer un pie de página (footer), el cual puede ser utilizado para proveer información adicional, como por ejemplo, referencias a issues o pull requests.

Puedes leer la especificación completa en este enlace, tomando en cuenta que la misma se encuentra en inglés y que utiliza RFC 2119 para indicar la importancia de cada regla (“MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, etc.).

#.Ejemplos de Conventional Commits

feat: add new login validator
fix: fix validation for username
chore: fix typo in README.md
# Estos cambios solo están planeados para ser utilizados en el módulo de login
feat(login): add new login validator
# Estos cambios solo están planeados para ser utilizados en el módulo de login
fix(login): fix validation for username
test(login): update snapshots on login page
# Estos cambios están planeados para ser utilizados en el módulo de login.
# Adicionalmente se utiliza el body con más descripción y un footer donde se
# señala quién aprobó este cambio y el número de tiquete asociado a él.
fix(login): prevent null password at login

Introduce a new validation to prevent null passwords at login.

Remove the old validation rule for the password.

Create a new validation process and helper for the password.

Reviewed-by: John Doe
Refs: #123123

#.Utilizando Commitizen

Commitizen es una herramienta que nos permite utilizar los Conventional Commits de una forma más fácil y consistente. El problema con los Conventional Commits es que no hay forma sencilla de escribirlos manualmente, por lo que debemos tener alguna plantilla con nosotros para poder escribirlos.

Commitizen resuelve este problema, al proveernos una plantilla (generada a través de un pequeño formulario) para escribir los mensajes de los commits de una forma que se ajuste a los Conventional Commits.

Con Commitizen, igualmente, podemos utilizar herramientas como commitlint para validar que los mensajes de los commits cumplan con las reglas de los Conventional Commits y otras herramientas para generar el historial de los cambios de forma automática (como un CHANGELOG).

Si estamos trabajando en un proyecto en JavaScript / TypeScript, podemos utilizar herramientas existentes para integrar Commitizen en nuestro proyecto.

#.Instalación

Debemos agregar dos librerías a nuestro proyecto:

yarn add commitizen cz-customizable -D

Para hacerlo más fácil, vamos a agregar un script en nuestro package.json para poder ejecutar Commitizen de forma más fácil:

{
  "scripts": {
    "commit": "cz"
  }
}

#.Configuración

Como vamos a utilizar cz-customizable para configurar Commitizen, debemos crear un archivo de configuración llamado .czrc y referenciar a la configuración de la librería:

{
  "path": "node_modules/cz-customizable"
}

Además, cz-customizable requiere que creemos un archivo de configuración para poder utilizarlo. Este archivo debe llamarse .cz-config.js y debe estar en la raíz de nuestro proyecto. Ya el proyecto nos provee un ejemplo de configuración, por lo que podemos copiarlo y pegarlo en nuestro proyecto.

module.exports = {
  types: [
    { value: 'feat', name: 'feat:     A new feature' },
    { value: 'fix', name: 'fix:      A bug fix' },
    { value: 'docs', name: 'docs:     Documentation only changes' },
    {
      value: 'style',
      name: 'style:    Changes that do not affect the meaning of the code\n            (white-space, formatting, missing semi-colons, etc)',
    },
    {
      value: 'refactor',
      name: 'refactor: A code change that neither fixes a bug nor adds a feature',
    },
    {
      value: 'perf',
      name: 'perf:     A code change that improves performance',
    },
    { value: 'test', name: 'test:     Adding missing tests' },
    {
      value: 'chore',
      name: 'chore:    Changes to the build process or auxiliary tools\n            and libraries such as documentation generation',
    },
    { value: 'revert', name: 'revert:   Revert to a commit' },
    { value: 'WIP', name: 'WIP:      Work in progress' },
  ],

  scopes: [{ name: 'accounts' }, { name: 'admin' }, { name: 'exampleScope' }, { name: 'changeMe' }],

  usePreparedCommit: false, // to re-use commit from ./.git/COMMIT_EDITMSG
  allowTicketNumber: false,
  isTicketNumberRequired: false,
  ticketNumberPrefix: 'TICKET-',
  ticketNumberRegExp: '\\d{1,5}',

  // it needs to match the value for field type. Eg.: 'fix'
  /*
  scopeOverrides: {
    fix: [
      {name: 'merge'},
      {name: 'style'},
      {name: 'e2eTest'},
      {name: 'unitTest'}
    ]
  },
  */
  // override the messages, defaults are as follows
  messages: {
    type: "Select the type of change that you're committing:",
    scope: '\nDenote the SCOPE of this change (optional):',
    // used if allowCustomScopes is true
    customScope: 'Denote the SCOPE of this change:',
    subject: 'Write a SHORT, IMPERATIVE tense description of the change:\n',
    body: 'Provide a LONGER description of the change (optional). Use "|" to break new line:\n',
    breaking: 'List any BREAKING CHANGES (optional):\n',
    footer: 'List any ISSUES CLOSED by this change (optional). E.g.: #31, #34:\n',
    confirmCommit: 'Are you sure you want to proceed with the commit above?',
  },

  allowCustomScopes: true,
  allowBreakingChanges: ['feat', 'fix'],
  // skip any questions you want
  // skipQuestions: ['scope', 'body'],

  // limit subject length
  subjectLimit: 100,
  // breaklineChar: '|', // It is supported for fields body and footer.
  // footerPrefix : 'ISSUES CLOSED:'
  // askForBreakingChangeFirst : true, // default is false
};

#.Utilizándolo

Para utilizar Commitizen, debemos ejecutar el script que creamos anteriormente:

yarn commit

Esto nos mostrará un formulario con las opciones que tenemos para escribir el mensaje del commit, que se verá como lo siguiente:

yarn commit
yarn run v1.22.19
$ cz
cz-cli@4.2.5, cz-customizable@7.0.0

All lines except first will be wrapped after 100 characters.
? Select the type of change that you're committing: feat:     A new feature
?
Denote the SCOPE of this change (optional): accounts
? Write a SHORT, IMPERATIVE tense description of the change:
 change on something
? Provide a LONGER description of the change (optional). Use "|" to break new line:
 change providing more changes
? List any BREAKING CHANGES (optional):

? List any ISSUES CLOSED by this change (optional). E.g.: #31, #34:


###--------------------------------------------------------###
feat(accounts): change on something

change providing more changes
###--------------------------------------------------------###

? Are you sure you want to proceed with the commit above? Yes
Auto packing the repository in background for optimum performance.
See "git help gc" for manual housekeeping.
warning: The last gc run reported the following. Please correct the root cause
and remove .git/gc.log
Automatic cleanup will not be performed until the file is removed.

Si verificamos el git log, vamos a notar que la herramienta nos ayudó a tener un mensaje de commit más claro y ordenado:

git log

commit f1eb95cacf7385d8f44d31c44317f1a5f283b813 (HEAD -> master)
Author: Demostenes Garcia G <...>
Date:   ...

    feat(accounts): change on something

    change providing more changes

Ya, después de aquí, podríamos implementar otras herramientas: como alguna para asegurarnos que el mensaje cumple con el estándar que decidamos, que no cuente con ciertas palabras o incluso implementar versionamiento de forma automática.