Notxor tiene un blog

Defenestrando la vida

Control de versiones

2022-05-08

A los programadores, a estas alturas, quizá les parezca superfluo hablar sobre lo que es el control de versiones. Sin embargo, es una pregunta que me han hecho, personas ajenas a la programación y a las que no les he podido dar una respuesta breve o inteligible de primeras. Aunque la pregunta iba dirigida a fossil, quedó claro que no conocían ningún tipo de herramienta similar. Por ello les prometí hablar sobre el tema en un artículo de manera mucho más reposada y extendida y de forma más o menos genérica, sin entrar en demasiados detalles sobre ninguna herramienta concreta. Por tanto, si ya conoces o trabajas con alguna herramienta de control de versiones, no me vendría mal que le echaras un ojo al artículo y me corrigieras si cometo algún error. Si estás empezando, o quieres empezar, lo mismo este artículo te sirve para hacerte una idea general sobre ese tipo de herramientas. Además, comentaré la herramienta genérica que trae Emacs para trabajar con multitud de ese tipo de sistemas.

¿Qué es el control de versiones y para qué sirve?

Un sistema de control de versiones es un programa que es capaz de almacenar el estado de los archivos de texto de un proyecto y mucha información relevante sobre ellos y los cambios que se producen en el tiempo. Guardando esa información, nos permite hacer una fotografía fija del estado del proyecto completo y también movernos por el historial de estados hacia atrás y hacia adelante, ver cómo ha evolucionado, quién ha hecho los cambios, cuándo se hicieron, etc.

Un repositorio es el conjunto de archivos que forman parte de un proyecto que está sujeto a un sistema de control de versiones. Este es un término que utilizaré con frecuencia y que defino aquí, aunque más adelante hablaré sobre otros conceptos que debemos tener claros.

En resumidas cuentas, un sistema de control de versiones nos proporciona las siguientes funcionalidades principales:

Reversibilidad
Podemos regresar a cualquier estado anterior si en algún momento te das cuenta que alguna modificación ha sido una mala idea o introduce un error no esperado en el proyecto.
Concurrencia
Podemos trabajar en equipo, modificando el proyecto y su colección de archivos estando seguros que los conflictos entre los cambios que introduzcan distintas personas serán detectados y se podrán resolver.
Historia
Un repositorio puede consultarse, generalmente, desde una línea de tiempo, por versiones... incluso cuando eres el único usuario de un proyecto versionado puedes obtener información muy importante para recordar cómo ha evolucionado.
Copia de seguridad
Puesto que suele haber un repositorio remoto que se guarda en otra máquina, cualquier desastre con el almacenamiento del contenido de un repositorio particular deja de tener importancia. Rehacer todo el trabajo es tan fácil como descargar una copia del repositorio remoto y seguir trabajando. Un disco duro estropeado o un ordenador que deja de funcionar no implican una pérdida masiva de tu trabajo.

Si has llegado hasta aquí y piensas que estas herramientas te interesan para tu trabajo diario debo hacer alguna puntualización. Como he dicho antes, el control de versiones funciona correctamente cuando utilizamos ficheros de texto plano, como es el caso del código fuente. No se llevan bien con archivos binarios. Por tanto, si en tu trabajo diario utilizas «documentos» con formato binario, aunque los puedes gestionar con un sistema de este tipo, lo que hará el control de versiones es guardar una copia completa del fichero binario, dejando de ser tan efectivo en el seguimiento de cambios puntuales y haciéndose ineficiente en el almacenaje de la información.

En mi trabajo diario utilizo de manera consistente muchos repositorios, no sólo de código cuando programo, también cuando escribo un documento con LaTeX, o preparo una presentación, que suelo hacer con html o incluso con archivos de texto plano .org. Por tanto, sácate de la cabeza que el control de versiones es algo sólo para programadores.

Un poco de historia

El primer sistema de control de versiones que utilicé fue cvs. Ya existían algunos antes de él, pero fue el primero que aprendía a manejar. Es un sistema centralizado, con un repositorio central que permitía el trabajo en equipo a través de una red. Fue superado rápidamente por svn, el siguiente sistema que aprendí a manejar. Este sistema estaba diseñado a imagen y semejanza de cvs pero superando algunos de sus problemas, como el soporte commits de conjuntos de archivos, versiones de directorios, enlaces simbólicos, renombrado de archivos, copia, borrado, etc.

De esos sistemas centralizados, pasé a git. Quizá el cambio a otro sistema descentralizado hizo que me costara cogerle el tranquillo. Este sistema fue desarrollado inicialmente por Linus Torvalds para el desarrollo del núcleo de Linux. Como digo es un sistema descentralizado, no hay un repositorio central, cualquier repositorio puede ejercer como tal y además cambia la orientación del control de cambios. En los sistemas anteriores se maneja el concepto de cambio dentro de cada fichero. Es decir los commits versionan archivos, mientras que en git, los commits se centran en las características y los cambios pueden abarcar varios archivos por lo que podemos hacer que un commit realice un cambio funcional del proyecto. Dicho de otro modo, normalmente en un commit se suelen agrupar los cambios que realizan los cambios necesarios para generar una nueva funcionalidad, arreglar un error, etc.

Existen otros sistemas de control de versiones que tienen también su público, aunque yo no he utilizado mucho. Por mencionar sólo tres:

  • Mercurial (hg): Es un sistema descentralizado como git.
  • Bazaar (bzr): Otro sistema descentralizado, con la particularidad de que puede funcionar también como centralizado.
  • SRC: src es un sistema de control de versiones diseñado para funcionar en proyectos de sólo un archivo y sólo una persona. Esto permite tener varios archivos, cada uno con su historia independiente en el mismo directorio y viene muy bien para proyectos que trabajan con archivos pequeños, o una colección de scripts o programitas de shell, etc.

No entro mucho en detalle con éstos porque apenas los he utilizado. Alguno de ellos sólo para hacer alguna prueba de concepto, ver que no se ajustaban a lo que esperaba y ser descartado del tirón.

Uno de los problemas que tienen que superar los usuarios de estas herramientas son los conflictos que se producen cuando dos personas modifican el contenido del mismo archivo. Una solución es que al segundo que hace el cambio le avise de que ese fichero ya ha sido cambiado y lo revise a mano. La otra opción, como hacen algunos de estos sistemas, es bloquear el fichero mientras lo está trabajando un usuario. A este sistema se le denomina de candado (lock) por ese motivo, porque bloquea la edición del fichero, mientras está siendo editado por otro usuario.

Si te estás preguntando cuál deberías utilizar, lamento informarte que no tengo una respuesta única. Hace unos meses te hubiera recomendando sin dudar git, aún diciéndote que bazaar o mercurial son excelentes sistemas de control de versiones, pero que el más extendido es git. Luego me tropecé con fossil. Entonces ¿te recomiendo fossil en lugar de git? Pues tampoco... mi recomendación es que trabajes con un sistema moderno, es decir: que sea descentralizado, en lugar de centralizado, que trabaje sobre el commit funcional, en lugar de trabajar los cambios del cambios en el fichero y, sobre todo, que elijas el sistema con el que te encuentres cómodo. Al final, todos tienen sus pros y contras.

Algunos conceptos

Ya expliqué antes lo que es un repositorio, pero también he utilizado, sin explicarlo, el concepto commit, que también se puede llamar check in. Un commit consiste en generar la anotación de los cambios realizados en una copia de trabajo para que el repositorio los expanda a las demás copias de trabajo. Es decir, cuando estoy trabajando, he hecho unos cambios que veo que funcionan y quiero que se guarden. Esta acción guarda los cambios realizados junto con una etiqueta, donde se suele explicar qué cambios se han hecho, por qué, qué funcionalidad tienen, etc. Una vez guardados aparecerán en el árbol histórico.

El árbol histórico es el modo en que un repositorio guarda los distintos cambios realizados. También lo podemos llamar log del control de versiones o línea de tiempo (time line). Cada sistema tiene su propio vocabulario y hay que adaptarse a ello. El histórico muchas veces se consulta de «manera gráfica», donde vemos el tronco principal (trunk) del proyecto con otros hilos que son las ramas, que pueden o no, volver a reintegrarse en el tronco.

Una rama (branch) es una ramificación del proyecto, para hacer pruebas, para añadir funcionalidad paralela. Cuando digo ramificación no es que se separe para siempre. En programación suele ser habitual que abras una rama cuando quieres comenzar a desarrollar una nueva característica. La rama comienza en un estado de repositorio e inicia una nueva línea de trabajo. La programación de la nueva funcionalidad puede desarrollarse mientras otros siguen evolucionando la línea principal (tronco) sin interferir con el código de la rama. Cuando se comprueba que la nueva funcionalidad es útil y correcta se puede hacer que se integre de nuevo en el tronco. Si no es correcto o útil, simplemente se abandona. Incluso se puede borrar para que no ocupe espacio. Hay que recordar que los ficheros del mismo nombre en cada rama son independientes y muchas veces terminan con diferente contenido. Pero esto ya son problemas de usuarios avanzados.

En todo caso, el flujo de trabajo personal es, más o menos, la siguiente:

  1. Descargar cambios del repositorio general. Sobre todo si trabajamos en equipo, no está de más comprobar si ha habido cambios y actualizar nuestro repositorio de trabajo.
  2. Hacer nuestros cambios, anotándolos en sus correspondientes commits para tener toda la información ordenada y congruente.
  3. Al finalizar algún cambio importante o cuando dejamos el trabajo por el momento. Subir todas nuestras anotaciones (commits) al servidor general para que otros usuarios puedan descargar nuestros cambios y anotaciones y poder trabajar así coordinados.

Es fácil de decir, fácil de entender... pero el demonio está en los detalles. Luego, llegas a la herramienta y te encuentras decenas de comandos con decenas de opciones, cada una para realizar una acción diferente y la curva de aprendizaje se nos convierte no en una pendiente, sino en una pared de escalada. Hay que gestionar (crear, editar, mover, borrar) repositorios, ramas, archivos, usuarios. Además, estas herramientas están pensadas para la línea de comandos, por lo que necesitan de aclimatación para aprenderse dichos comandos y sus opciones. Para gente no técnica la línea de comandos suele ser árida y difícil. Suelen preferir herramientas más visuales que hagan de interface con la línea de comandos. Sabemos que están equivocados, la potencia está en los comandos, pero les da más seguridad tener un formulario que rellenar, que inventarse toda una orden escribiendo desde cero.

Por ese lado, estas herramientas, como magit de Emacs, que es lo mejor que he visto para trabajar con repositorios git, aparte de las propias gitk y git gui que proporciona el sistema de control de versiones, pueden resultar un engaño al aprendizaje. Convertir todas las opciones que proporciona la línea de comandos en diálogos y menús hace que no nos preocupemos de aprendernos el sistema. Saber qué menú necesitas llamar o qué opción debes activar lo aprendes conociéndolo, sabiendo cómo funciona y eso lo aprendes siempre mejor desde los comandos que desde las ventanas. Dicho esto, también es habitual, que todos los editores vengan ya con alguna herramienta para gestionar nuestros repositorios sin necesidad de salir del mismo. Emacs no es una excepción y vamos a verlo un poco por encima.

Control de versiones en Emacs

Antes de comenzar, quiero dar un aviso: Si eres usuario de git y utilizas Emacs, instala magit y tendrás la mejor herramienta externa de gestión de repositorios git que podrás encontrar. Sin embargo, no voy a hablar de magit, voy a hablar de vc (version control) de Emacs. Podéis encontrarlo en el menú del editor en tools → Verison Control. Está instalada con la instalación básica de Emacs.

La herramienta vc viene preparada para trabajar con muchas herramientas de control de versiones: Bazaar, CVS, Git, Mercurial, Monotone, RCS, SRC, SCCS/CSSC y Subversion. Si quieres, como es mi caso, que soporte fossil tienes que instalar el paquete vs-fossil. El proceso de instalación es similar a cualquier otro paquete. En mi init.el he añadido el siguiente código:

(use-package vc-fossil
:defer t)

Sin modificar nada más, pues el paquete se encarga de actualizar la lista de backends para vc. Un aspecto técnico del que no te tienes que ocupar, que siempre es de agradecer.

El problema de un gestor genérico es que algunos comandos funcionan o no, según el sistema que estemos utilizando detrás. Si algún comando u opción no las soporta el sistema, no se realizarán las acciones. O según el tipo de sistema detrás puede variar cómo se realizan las acciones.

Siempre que estás editando un archivo sujeto a control de versiones, con alguna excepción1, en la línea de estado aparece un mensaje que te da la información sobre la herramienta y versión del fichero. Veamos un ejemplo:

Captura_fichero-fossil.png

Como se puede apreciar en la imagen nos dice el tipo de repositorio en el que está incluido el archivo, en nuestro caso fossil y la versión del fichero, que en nuestro ejemplo es el hash 4869c216c3. El carácter separador también puede variar:

  • : significa que el fichero está bloqueado.
  • - significa que el archivo no está bloqueado.

Por defecto, el control de versiones se encuentra mapeado con la combinación de teclas C-x v. Curiosamente, el comando principal del control de versiones es un comando multipropósito vc-next-action, que realiza la siguiente acción, o la más apropiada si lo quieres llamar así, en el repositorio. Por ejemplo, he hecho unas pocas modificaciones en el fichero y al ejecutar ese comando nos muestra lo siguiente:

Captura_lanzar-cambios.png

En este caso, detecta que hay cambios en el archivo y lo que hace es entrar en el modo commit pidiéndome que escriba un comentario a los cambios. Una vez hecho, se pulsa C-c C-c para guardarlo y si vemos un log del repositorio (C-x v L) nos aparece ya en nuestro repositorio local. También podemos observar el cambio de versión del fichero en la línea de estado:

Captura_vc-log.png

Si vemos el resultado en el ui que proporciona fossil con el comando fossil ui, el resultado es similar:

Captura_fossil-ui.png

Como vemos, nos permite gestionar un repositorio. Ya vemos que nos permite gestionar esos cambios. Vamos con una lista bastante más extensa de comandos, aunque sin ánimo de ser exhaustivo.

vc-pull
Actualiza la información del repositorio local con la información del(los) repositorio(s) remoto(o).
vc-push (C-x v P)
Actualiza la información del repositorio remoto con la información del repositorio local.
vc-register (C-x v i)
Añade el archivo actual al repositorio. Suele ser la acción que necesitamos cuando creamos un archivo nuevo y queremos que entre en el control de versiones.
vc-diff (C-x v =)
Nos permite comparar los cambios que hay en nuestro fichero con la versión guardada en el repositorio. Si queremos sacar todo el partido a Emacs también podemos utilizar el modo ediff con el comando M-x vc-ediff.
vc-annotate (C-x v g)
Nos muestra un buffer con el fichero actual con información de la versión y la fecha en que fue escrita cada línea.
vc-print-log (C-x v l)
Muestra un historial de cambios del repositorio desde que el fichero actual se añade.
vc-print-root-log (C-x v L)
Muestra el historial de cambios del repositorio desde su inicio.
vc-log-outgoing (C-x v O)
Muestra la lista de cambios que se enviarán en la siguiente operación push.
vc-revert (C-x v u)
Revierte el trabajo hecho en el(los) fichero(s) del repositorio en la última revisión. Puedes comprobar qué cambios se eliminarán utilizando el comando vc-revert-show-diff.
vc-ignore (C-x v G)
Ignora el fichero en el control de versiones. La mayoría de los sistemas de control de versiones tienen o mantienen una lista de ficheros y/o directorios que serán ignorados. Muchas veces bastaría con no añadirlos, sin embargo, cuando lanzas una lista de los archivos huérfanos, aparecerán en el listado. Al decirle que ignore un archivo, no lo tendrá en cuenta ni para listarlo entre los no gestionados por el sistema.
vc-dir (C-x v d)
Abre un buffer especializado que nos permite tener una visión general del estado del repositorio. Nos muestra una lista del árbol de directorios sujeto al control de versiones junto con el estado de cada fichero: unregistered, edited, added. Dentro de ese buffer podemos realizar algunas de las opciones que nos permite vc, por ejemplo, registrar un archivo con i, ver el diff con =, o borrar un archivo con d, etc. U otras acciones como por ejemplo, crear una nueva rama con B c, o preguntar el nombre de la rama actual con B l, o cambiar de rama B s.
vc-create-tag (C-x v s)
Crear una nueva rama.
vc-retrieve-tag (C-x v r)
Cambiar la rama de trabajo actual.
vc-merge (C-x v m)
Mezcla (merge) el contenido de otra rama con la rama de trabajo actual.

Otros comandos no listados aquí, que nos pueden ser muy útiles para el trabajo con los ficheros de un repositorio son vc-deletefile y/o vc-rename-file. No hace falta explicar mucho para qué sirven dado el nombre que tienen.

Conclusión

Esto como introducción a los sistemas de control de versiones es muy corto. Hay auténticos libros escritos para cada uno de los sistemas que he mencionado en el artículo. Lo importante es quedarse con que nos permiten controlar cómo evoluciona el trabajo, nos permiten arrepentirnos, nos dan tranquilidad porque nos aseguran una copia de nuestro trabajo.

Además hemos visto cómo gestiona Emacs cualquier repositorio sin necesidad de instalar ningún paquete, como sería magit. El paquete vc viene instalado con Emacs y no necesitas instalar ningún paquete extra.

Footnotes:

1

La excepción es cuando estás utilizando git y tienes instalado magit, que sustituye el control de versiones.

Categoría: emacs vc

Comentarios

Debido a algunos ataques mailintencionados a través de la herramienta de comentarios, he decidido no proporcionar dicha opción en el Blog. Si alguien quiere comentar algo, me puede encontrar en Mastodon y en Diaspora con el nick de Notxor.

También se ha abierto un grupo en Telegram, para usuarios de esa red.

Disculpen las molestias.