Todo el que se inicia en el desarrollo de software pronto descubre que llevar un registro preciso de los cambios que se realizan en cada unidad de código es una tarea tediosa (especialmente si es un proyecto en el que participan varias personas), pero es esencial para producir y mantener un proyecto de calidad.
Afortunadamente, existen aplicaciones para facilitar el control de versiones, algunas de ellas muy conocidas como Git, Mercurial, Subversion o CVS, otras menos conocidas como Bazaar o DARCS, y otras relativamente desconocidas pero muy útiles, como Fossil, que es sobre la cual trataremos en este artículo.
Este tutorial asume que se tiene un nivel básico del uso del intérprete de comandos, o en otras palabras, que el usuario está suficientemente familiarizado con la introducción de comandos para realizar operaciones básicas como cambiar de directorio, ejecutar una aplicación, copiar o mover archivos, etc.
También se asume que el usuario tiene al menos un nivel rudimentario de inglés, ya que los comandos y la ayuda de Fossil se encuentran en dicho idioma. No obstante, en este tutorial las palabras claves se ofrecen en español, junto con su nomenclatura original en inglés (usualmente entre paréntesis y/o en itálicas).
Fossil es un sistema para la gestión de la configuración del software (SCM) desarrollado por D. Richard Hipp (creador de la popular base de datos ligera SQLite), y distribuido bajo la licencia BSD de dos cláusulas.
Fossil tiene algunas características técnicas interesantes:
Estas características combinadas con el modelo de uso de la aplicación, ofrecen a Fossil ciertas ventajas:
Para quienes se inician en el control de versiones, conviene aclarar algunos conceptos utilizados por Fossil.
Un proyecto de software se considera un “árbol de código”, donde el código principal o estable de un proyecto se considera el tronco (trunk) del proyecto.
Cada instante (snapshot) de ese árbol en el tiempo en el cual las modificaciones realizadas se confirman (check-in, commit) constituye una versión o revisión (version, revision, baseline) del producto.
Fossil permite rastrear (track) cada error (bug) que se detecte en el código, para lo cual ofrece la posibilidad de crear un reporte o boleto (ticket). También permite la creación páginas de documentación (Wiki) y de etiquetas (tags) para identificar más facilmente las versiones.
Un repositorio (repository) es una base de datos que contiene copias de todas las versiones históricas de un proyecto, y en el caso de Fossil también se almacenan el blog, las páginas de documentación que se generen sobre el proyecto, y el rastreo de errores.
El proyecto puede tener variantes de desarrollo, cada una de las cuales se considera una rama (branch) del proyecto. Un proyecto puede subdividirse en muchas ramas que incluso pueden desarrollarse en diferentes lugares del mundo por diferentes individuos o equipos de trabajo. Dichos equipos suelen trabajar en repositorios locales que sincronizan regularmente con otros repositorios remotos, recibiendo (pull) los nuevos cambios o enviando (push) sus propias modificaciones. A esto se le conoce como sistema de control de versiones descentralizado o distribuido.
Cuando más de una persona realiza simultáneamente cambios en su repositorio local, y luego intentan sincronizarlos con un repositorio remoto, las modificaciones pueden entrar en conflicto, en cuyo caso el proyecto sufre una bifurcación (fork) y cada versión que se realice sobre una bifurcación se considera una hoja (leaf).
Las bifurcaciones deben resolverse para evitar afectaciones al proyecto, para lo cual suele utilizarse una fusión (merge) o unificación del código. Esta operación también se realiza para incorporar al tronco del proyecto las mejoras de una rama, cuando es suficientemente estable.
A cada versión particular de un archivo particular se le conoce como artefacto (artifact). Cada artefacto tiene un identificador único de 40 caracteres que se toma de la máscara SHA-1 del contenido del archivo, aunque como el cambio de un solo carácter en el código de un archivo resulta en un identificador totalmente diferente, puede hacerse referencia al artefacto utilizando solamente una cantidad reducida de caracteres del identificador (al menos 4). En Fossil cada artefacto es inherentemente inmutable.
Con cada commit Fossil genera un manifiesto (manifest), que es un archivo especial que lista todos los archivos del directorio de trabajo. En Fossil el ID de artefacto del manifiesto es también el identificador del check-in.
Fossil probablemente sea el sistema de control de versiones más fácil de instalar, pues aunque se encuentra en la mayoría de los repositorios de software, siendo un único archivo, también puede descargarse del sitio oficial y simplemente ejecutarse. En este último caso conviene extraerlo en una ruta que facilite su ejecución sin tener que declarar la ruta completa al ejecutable.
Por ejemplo, el archivo podría colocarse en C:\Windows\
en el caso de MS Windows, en /usr/local/bin/
o /usr/bin/
en el caso de GNU/Linux (adicionalmente verificando que tenga permisos de ejecución), etc.
La aplicación usualmente no necesita configuración alguna, aunque sí ofrece varias opciones de configuración, que podremos consultar mediante el comando fossil help
y el argumento deseado (de no especificarse un argumento, se ofrece una información genérica de los comandos disponibles).
Fossil puede utilizarse en diferentes sistemas operativos; en este tutorial escogeremos Windows porque es una plataforma popular que sirve como punto de inicio para muchas personas que se interesan por la programación, donde a veces instalar herramientas para el control de versiones puede ser engorroso. Pero Fossil puede utilizarse igualmente en GNU/Linux, FreeBSD, MacOSX, etc.
Ilustremos el uso de Fossil mediante un ejemplo hipotético. Supongamos que acabamos de desarrollar en php una aplicación web que funciona correctamente y cuyos archivos de código fuente se encuentran en C:\UwAmp\www\miproyecto
. Esta ubicación constituirá el árbol del proyecto, por lo que el repositorio de Fossil debería crearse en la misma ruta o de lo contrario actualizarlo será más complejo o incluso podrían ocurrir errores.
Para crear un repositorio de Fossil y agregar a este todos los directorios y archivos de código del proyecto (posiblemente excluyendo algunos tipos de archivos), ejecutamos los siguientes comandos:
cd /d C:\UwAmp\www\miproyecto fossil settings --global crnl-glob '*' fossil new miproyecto.fossil fossil open miproyecto.fossil fossil settings ignore-glob '*.bak,*/datos/*.db' fossil add . fossil commit -m "Mi Proyecto, versión inicial" -tag release -tag version-0.1
Expliquemos brevemente que hace cada comando:
--global
, esta preferencia funciona para todos los repositorios).Para quienes nunca han utilizado un sistema de control de versiones puede que lo anterior parezca innecesariamente complicado, pero supongamos que nos tropezamos con la siguiente situación típica:
Decidimos seguir trabajando en el proyecto para introducirle algunas mejoras, tras lo cual al intentar utilizar la aplicación aparece un error inesperado, y a estas alturas ya no recordamos exactamente cuáles fueron las secciones de código que modificamos. Aquí es donde Fossil puede resultar de utilidad práctica, todo depende de lo que deseemos hacer.
Si deseamos regresar al código que funcionaba, solo necesitamos ejecutar el siguiente comando y Fossil restaurará los archivos originales:
fossil revert
Si al restaurar las unidades notamos que una de ellas realmente estaba mejor en la versión más reciente, podemos deshacer la restauración de dicha unidad para devolverla a la versión nueva:
fossil undo launidad.php
Por otra parte, si todas las unidades de código menos una estuviesen bien en la nueva versión, podríamos haber restaurado la versión anterior solo de la unidad problemática:
fossil revert launidad.php
Aunque para revisar los cambios es más cómodo utilizar la interfaz web de Fossil (sobre lo cual hablaremos en breve), si deseásemos conocer rápidamente qué modificaciones exactas ha sufrido el código de la unidad problemática, podríamos ejecutar este comando:
fossil diff launidad.php
Si deseamos conocer qué archivos monitoreados en el repositorio han cambiado en el directorio de trabajo, podemos ejecutar el siguiente comando:
fossil changes
Alternativamente, podríamos haber utilizado esta variante:
fossil status
Si los cambios realizados no hubiesen introducido error alguno, para guardar la última versión solo tendríamos que ejecutar algo como esto:
fossil addremove fossil commit -m "Primera mejora"
Si sólo deseamos llevar un registro del intento de modificación, pero en una rama experimental que no afecte el tronco del proyecto, podemos ejecutar esto:
fossil addremove fossil commit -m "Mejora experimental" --branch experimental
Para fusionar los cambios de la rama experimental en el tronco del proyecto, colocándole etiquetas a la versión resultante, y mostrando detalles adicionales sobre la fusión, podemos ejecutar los siguientes comandos:
fossil update trunk fossil merge experimental --verbose fossil commit -f -m "Fusionada al tronco rama experimental" -tag release -tag version-0.2
Para ver el registro histórico de versiones con los archivos que fueron parte de cada commit, podemos utilizar el siguiente comando:
fossil timeline --showfiles
Si hubiésemos perdido absolutamente todos los archivos del proyecto, pero conservásemos una copia del repositorio digamos en un medio extraíble, solo tendríamos que copiar dicho archivo en el directorio de trabajo, y entonces podríamos recuperar totalmente el proyecto ejecutando los siguientes comandos:
cd /d C:\UwAmp\www\miproyecto fossil open miproyecto.fossil
El mismo principio recién mostrado puede utilizarse para mover un proyecto de una computadora a otra, de un sistema operativo a otro, etc. Tanto Fossil como los repositorios que crea son portables, solo necesitamos disponer del ejecutable (para la plataforma en la cual deseamos trabajar, obviamente) y una copia del repositorio; no hace falta nada más.
Si dejamos de trabajar en el proyecto, podemos cerrar la conexión con el repositorio mediante el siguente comando (requiere haber realizado antes un commit a menos que se use en modo forzado, en cuyo caso se perderían todos los cambios):
fossil close
A continuación ofrecemos un listado de algunos de los comandos utilizados frecuentemente en Fossil, auque existen muchos más que como ya mencionamos al principio pueden consultarse con el comando fossil help
.
Comando | Alternativa | Descripción |
---|---|---|
add | Agrega archivos al repositorio. | |
addremove | Agrega al repositorio los archivos nuevos en el directorio de trabajo y elimina los inexistentes. | |
branch | Permite crear o listar ramas en un repositorio. | |
changes | Muestra los cambios en el directorio de trabajo. Si la salida está en blanco, significa que no se han modificado archivos. | |
checkout | co | Permite cargar archivos desde el repositorio, reemplazando los archivos locales ignorando cualquier modificación que hayan recibido. De ser necesario, copia en el directorio de trabajo los archivos desde el repositorio. |
close | Cierra la conexión con el repositorio. | |
clone | Clona localmente un repositorio remoto. | |
commit | ci | Guarda en el repositorio todos los cambios, realizando push de ser necesario. |
delete | del, rm | Elimina archivos del repositorio (pero no del directorio de trabajo) para que en adelante no se consideren parte del mismo. |
diff | Muestra las diferencias entre dos versiones. | |
extras | Lista los archivos en el directorio de trabajo que Fossil no monitorea (probablemente porque no se han agregado al repositorio o se han quitado del mismo). | |
finfo | Muestra información sobre un archivo específico. | |
info | Muestra información sobre el árbol actual (si no se especifica un argumento) o sobre un objeto en particular. | |
ls | Lista los archivos que Fossil monitorea. | |
new | Crea un nuevo repositorio. | |
open | Abre una conexión con el repositorio y realiza un checkout. | |
pull | Recibe en el repositorio local los cambios desde un repositorio remoto. | |
push | Envía los cambios del repositorio local a un repositorio remoto. | |
rename | mv | Renombra o mueve archivos en el repositorio. |
revert | Revierte los cambios realizados, devolviendo los archivos al estado en que se encontraban en el último commit. | |
rebuild | Si se actualiza la versión de Fossil, conviene ejecutar este comando en los repositorios para actualizarlos a la nueva versión de Fossil. | |
search | Busca cualquier comentario que coincida con la expresión. | |
status | Equivalente a una combinación de info y changes. | |
sync | Sincroniza un repositorio local con uno remoto, haciendo automáticamente push y pull. | |
tag | Agrega una etiqueta a una revisión. Puede utilizarse junto con commit o en un momento posterior. | |
ui | Inicia Fossil en modo de servidor web y lanza el navegador mostrando la interfaz gráfica. | |
undo | Deshace total o parcialmente el efecto de los comandos update, merge o revert. Solo se almacena un comando, que se limpia al ejecutar los comandos checkout o commit. | |
update | Actualiza el repositorio, realizando pull de ser necesario. |
Fossil tiene una modalidad de uso en que funciona como un servidor web, y ofrece una interfaz de gestión visual que es muy cómoda para la consulta de información sobre las modificaciones de un proyecto.
Para esto, solo tenemos que ejecutar el siguiente comando (si no hemos cerrado la conexión con el repositorio, podemos omitir el parámetro de su ubicación):
fossil ui C:\UwAmp\www\miproyecto\miproyecto.fossil
Esta modalidad visual permite además ponerle un nombre y una descripción al proyecto, crear usuarios de trabajo con sus respectivos privilegios y contraseñas, definir el puerto en que escucha el servidor web (por defecto es en el 8080), y muchas más opciones de administración y gestión.
Fossil permite un uso más avanzado cuando se ejecuta en sistemas operativos como GNU/Linux o Unix. Por ejemplo, para importar un proyecto que se encuentra en Git:
cd repogit git fast-export --all | fossil import --git nuevorepo.fossil
Igualmente podemos exportar nuestro repositorio a Git:
git init nuevorepo cd nuevorepo fossil export --git ../repo.fossil | git fast-import
Para que múltiples usuarios puedan acceder al repositorio desde una red, podemos emular un servicio usando el servidor web integrado:
fossil ui -port 80 elrepositorio.fossil &
Fossil también permite su uso como un script CGI para un servidor web dedicado, lo cual puede ser útil si deseamos tener varios repositorios disponibles todo el tiempo para una red.
Primeramente, podríamos crear el siguiente archivo CGI:
#! /usr/local/bin/fossil repository: /var/www/miproyecto/miproyecto.fossil
Una vez hecho esto, procedemos a asignarle al archivo cgi el permiso de ejecución:
chmod a+x /usr/local/lib/cgi-bin/miproyecto.cgi
Luego solo nos quedaría configurar el servidor web para que use el script. Por ejemplo, en caso que tengamos Apache:
ScriptAlias /repositorios/miproyecto.cgi /usr/local/lib/cgi-bin/miproyecto.cgi <Location "/repositorios/miproyecto.cgi"> Order allow,deny Allow from 192.168.1.0/24 Deny from All Satisfy All </Location>
Entonces podría accederse al repositorio mediante una URL como la siguiente:
http://192.168.1.1/repositorios/miproyecto.cgi:8080
Para clonar un repositorio remoto de Fossil en uno local, podemos utilizar un comando como el siguiente:
fossil clone http://usuario:clave@elsitioweb.tld:8080/repositorios/proyecto1.cgi proyecto1.fossil
Para sincronizar el repositorio local con el repositorio remoto, realizando a la vez push y pull, podemos ejecutar esto:
fossil sync trunk
El comando sync toma la ubicación remota del último comando exitoso que haya involucrado un repotitorio remoto, pero igualmente puede utilizarse el argumento --repository
o -R
para especificar un repositorio.
Para visualizar que cambios sufriría nuestro proyecto local de actualizarse, pero sin que ocurra ninguno, se puede hacer una prueba o “ejecución en seco” (dry-run) de la actualización:
fossil update -n -v
El comando anterior podría mostrar que algunos archivos están programados para su actualización (UPDATE), lo cual implica que serán remplazados por versiones más nuevas del repositorio, y otros para fusión (MERGE), lo cual implica que Fossil podrá fusionar el código de estos archivos con el del repositorio sin intervención humana. Esto significa que el comando update realmente podrá ejecutarse sin problemas.
Para crear dos nuevas ramas aplicándoles etiquetas y colores diferentes, podemos ejecutar los siguiente comandos:
fossil branch new v1.0 trunk -bgcolor 0x007F00 fossil branch new v2.0 trunk -bgcolor 0xFF0000
¿Que permite esto? Pues por ejemplo, podemos crear subdirectorios para cada rama y desarrollarlas independientemente; como las ramas parten del mismo tronco, podría actualizarse en el tronco una unidad común para ambas ramas y simplementa confirmar los cambios en cada una:
mkdir -p version1 version2 cd version1 fossil open ../elproyecto.fossil v1.0 fossil commit -m "[2f31e654] arreglando variable" cd ../version2 fossil open ../elproyecto.fossil v2.0 fossil commit -m "[2f31e654] arreglando variable" fossil close
Este artículo es un trabajo en progreso, disculpe cualquier error u omisión.