Estandarizar commits en Magit con commitizen

4 minutos de lectura Publicado:

Quién trabaje o contribuya a proyectos de software con otras personas sabrá que a veces el tiempo se va discutiendo chorradas. Un ejemplo típico son las convenciones del código.

Por ejemplo, en python lineas más largas de 79 carácteres (como el pep8 define) o más (ya que no estamos en los 90 y tenemos pantallas de más de 8 pulgadas)? Una lista debería estar en una sola línea o en varias?

Para la mayor parte de los casos no hay respuesta correcta, por que es cuestión de gustos. Por ello, ya hace un tiempo que decidí que paso de dedicar mi tiempo a esto y que otra persona decida por mi. En el caso de los ejemplos anteriores he decidido que sea black quien decida como hacer las cosas. Pero yendo al articulo, he decidido que quién se encargue del formato de mis commits sea commitizen.

Alredor del formato de los commits hay mucha historia. Lo idóneo es poner el tipo de cambio, el alcance de este, un título que resuma el cambio, un cuerpo con una explicación más profunda si hace falta y un ticket del gestor de tareas que uses. De esta forma es más fácil ver como evoluciona el código y la lógica que hay detrás. Tiene un añadido y es que si los commits tienen el mismo formato, se pueden generar changelogs de forma automática.

Meter toda esta información no es fácil. Para ello está commitizen. Esta herramienta da lo siguiente:

cz es un plugin de git. En vez de usar `git commit` lo que hay que hacer es usar git cz y te hace una serie de preguntas que ayudarán a generar el mensaje. Se puede ver un ejemplo en esta imagen:

Pero esta es una herramienta para la terminal, y teniendo emacs, la terminal está obsoleta. Así que veamos como usar esto en Magit. Lo idóneo sería llamar a git cz directamente, ya que aunque cz define las convenciones, estas se pueden cambiar por proyecto, por lo que generar una plantilla para el commit sin más puede no cubrir todos los casos. Estuve investigando cómo hacerlo pero tendría que haberme metido mucho más en la madriguera del conejo de lo que ya he hecho, por lo que decidí ir a por una solución de compromiso.

Por un lado, hice una plantilla para magit. Esto me servirá para la mayoría de los casos. Pero para cuando no, tengo una función que me deja hacer el commit desde emacs. No usa magit, lo cual es una pena, pero para el caso valdrá. Veamos ambas opciones.

He creado una función que usa yasnippet, un sistema de plantillas. Luego se añade esa función al hook del git-commit y de esta forma cada vez que se haga un commit, se llama a la función:

(defun daemons/commitizen-template()
  "Expand a commitizen template."
  (yas-expand-snippet "${1:Select the type of change you are committing: $$(yas-choose-value '(\"fix\" \"feat\" \"docs\" \"style\" \"refactor\" \"perf\" \"test\" \"build\" \"ci\"))}(${2:Scope. Could be anything specifying place of the commit change (users, db, poll): )}): ${3:Subject. Concise description of the changes. Imperative, lower case and no final dot}

${4:Body. Motivation for the change and contrast this with previous behavior}

${5:Footer. Information about Breaking Changes and reference issues that this commit closes}"))

(add-hook 'git-commit-setup-hook #'daemons/commitizen-template)

Para llamar a cz directamente he usado una función bonica que encontré en reddit (la fuente está en la función) que en resumen usa vterm para lanzar una orden arbitrária. Creé la función chorra que llama a esa función con cz y ya está.

(defun phalp/run-in-vterm (command)
  "Execute string COMMAND in a new vterm.

From: https://www.reddit.com/r/emacs/comments/ft84xy/run_shell_command_in_new_vterm/

Interactively, prompt for COMMAND with the current buffer's file
name supplied. When called from Dired, supply the name of the
file at point.

Like `async-shell-command`, but run in a vterm for full terminal features.

The new vterm buffer is named in the form `*foo bar.baz*`, the
command and its arguments in earmuffs.

When the command terminates, the shell remains open, but when the
shell exits, the buffer is killed."
  (interactive
   (list
    (let* ((f (cond (buffer-file-name)
                    ((eq major-mode 'dired-mode)
                     (dired-get-filename nil t))))
           (filename (concat " " (shell-quote-argument (and f (file-relative-name f))))))
      (read-shell-command "Terminal command: "
                          (cons filename 0)
                          (cons 'shell-command-history 1)
                          (list filename)))))
  (with-current-buffer (vterm (concat "*" command "*"))
    (vterm-send-string command)
    (vterm-send-return)))

(defun daemons/commitizen-gz() (interactive) (phalp/run-in-vterm "git cz commit; exit"))

Tiene pinta de que no integraré nunca magit con cz dada su dificultad, por lo que probablemente vaya creando plantillas para los commits según el proyecto y buscaré alguna forma de que use una plantilla u otra según el directorio en el que esté. Aún así molaria ver cz integrado.

Cualquier duda o comentario, me puedes contactar en los canales descritos en la página principal