En el caso especial del desarrollo de software, los datos que mantiene bajo control de versiones están a menudo relacionados con, o quizás dependan de, los datos de terceros. Generalmente, las necesidades de su proyecto dictarán que debe permanecer lo más actualizado posible con los datos proporcionados por esa entidad externa sin sacrificar la estabilidad de su propio proyecto. Este escenario ocurre constantemente—siempre que la información generada por un grupo de personas tiene un efecto directo en aquello generado por otro grupo.
Por ejemplo, desarrolladores de software podrían estar trabajando en una aplicación que hace uso de una librería externa. Subversion tiene justamente este tipo de relación con la Apache Portable Runtime library (vea “La librería Apache Portable Runtime”). El código fuente de Subversion depende de la librería APR para sus necesidades de portabilidad. En fases iniciales del desarrollo de Subversion, el proyecto seguía de cerca los cambios de la API de la APR, siempre manteniéndose en la “última versión” recién salida del horno. Ahora que tanto APR como Subversion han madurado, Subversion sólo intenta sincronizarse con la API de APR en momentos bien verificados, versiones estables públicas.
Ahora, si su proyecto depende de la información de otros, hay varios modos que podría intentar para sincronizar esa información con la suya. La más dolorosa, sería ordenar verbalmente, o por escrito, instrucciones a todos los contribuyentes de su proyecto, indicándoles que se aseguren de tener las versiones específicas de esa información externa que su proyecto necesita. Si la información externa se mantiene en un repositorio Subversion, podría usar las definiciones externas de Subversion para “fijar” de forma efectiva versiones específicas de esa información en alguno de los directorios de su copia local (vea “Repositorios externos”).
Pero a veces necesita mantener modificaciones propias a los datos de terceros en su propio sistema de control de versiones. Volviendo al ejemplo de desarrollo de software, los programadores podrían necesitar hacer modificaciones a esa librería externa para sus propios propósitos. Estas modificaciones podrían incluir nueva funcionalidad o correcciones de fallos, mantenidos internamente sólo hasta que se conviertan en parte de un lanzamiento oficial de esa librería externa. O los cambios quizás nunca sean enviados a los autores de la librería, y existen solamente con el propósito de satisfacer las necesidades de sus desarrolladores como modificaciones personalizadas.
Ahora se encuentra en una situación interesante. Su proyecto podría almacenar sus modificaciones propias a los datos de terceros de algún modo disjunto, como usando ficheros parche o completas versiones alternativas de ficheros y directorios. Pero estos métodos se convierten rápidamente en problemas de mantenimiento, requiriendo de algún mecanismo que aplique sus cambios personales a los datos de terceros, sin olvidar la necesidad de regenerar esos cambios con cada versión sucesiva de los datos de terceros a los que sigue la pista.
La solución a este problema es usar ramas de proveedor. Una rama de proveedor es un árbol de directorios en su propio sistema de control de versiones que contiene información proporcionada por una entidad externa, o proveedor. Cada versión de los datos del proveedor que decide absorber en su proyecto es llamada hito de proveedor.
Las ramas de proveedor proporcionan dos beneficios clave. Primero, al almacenar el hito de proveedor actualmente soportado en su propio sistema de control de versiones, los miembros de su proyecto nunca tendrán que preguntarse si poseen la versión correcta de los datos del proveedor. Simplemente obtienen la versión correcta como parte de su actualización habitual de sus copias locales de trabajo. Segundo, ya que los datos viven en su propio repositorio Subversion, puede almacenar cambios personalizados directamente en el mismo lugar—ya no tiene necesidad de crear un método automático (o peor aún, manual) para proporcionar sus modificaciones.
Gestionar ramas de vendedor generalmente funciona de
esta manera. Primero crea un directorio en la raíz de
su jerarquía (como por ejemplo /vendor
)
para almacenar las ramas de proveedor. Entonces importa
el código de terceros en un subdirectorio de ese directorio
principal. Luego copia ese subdirectorio en su rama
principal de desarrollo (por ejemplo,
/trunk
) en un lugar apropiado.
Siempre realiza cambios locales en la rama de desarrollo
principal. Con cada nueva versión del código que está
siguiendo, lo incluye en la rama de proveedor y fusiona
los cambios en /trunk
, resolviendo
cualquier conflicto entre sus cambios locales y los
cambios oficiales.
Quizás un ejemplo ayude a aclarar este algoritmo.
Usaremos un escenario donde su equipo de desarrollo
está creando un programa calculador que enlaza contra
una librería externa de aritmética de números complejos,
libcomplex. Comenzaremos con la creación inicial de
la rama de proveedor, y la importación del primer hito
de proveedor. Llamaremos al directorio de nuestra rama
de proveedor libcomplex
, y nuestros
hitos de código irán en un subdirectorio de nuestra
rama de proveedor llamado current
.
Y dado que svn import crea todos los
directorios padre intermedios que necesita, en realidad
podemos realizar ambos pasos con un único comando.
$ svn import /path/to/libcomplex-1.0 \ http://svn.example.com/repos/vendor/libcomplex/current \ -m 'importing initial 1.0 vendor drop' …
Tenemos ahora la versión actual del código fuente de
libcomplex en /vendor/libcomplex/current
.
Ahora, etiquetamos esa versión (vea “Etiquetas”) y luego la copiamos en
la rama principal de desarrollo. Nuestra copia creará
un nuevo directorio llamado libcomplex
en nuestro directorio existente de proyecto
calc
. Es en esta versión copiada
de los datos del proveedor que realizaremos nuestras
personalizaciones.
$ svn copy http://svn.example.com/repos/vendor/libcomplex/current \ http://svn.example.com/repos/vendor/libcomplex/1.0 \ -m 'tagging libcomplex-1.0' … $ svn copy http://svn.example.com/repos/vendor/libcomplex/1.0 \ http://svn.example.com/repos/calc/libcomplex \ -m 'bringing libcomplex-1.0 into the main branch' …
Ahora obtenemos una copia local de la rama principal de nuestro proyecto—que ahora incluye una copia del primer hito de proveedor—y comenzamos a personalizar el código de libcomplex. Antes de darnos cuenta, nuestra versión modificada de libcomplex está completamente integrada en nuestro programa calculador. [41]
Unas pocas semanas después, los desarrolladores de libcomplex lanzan una nueva versión de su librería—la versión 1.1—la cual contiene algunas características y funcionalidades que realmente deseamos. Nos gustaría actualizarnos a esta nueva versión, pero sin perder las personalizaciones que realizamos sobre la versión anterior. Lo que esencialmente nos gustaría hacer, es reemplazar nuestra versión de línea base de libcomplex 1.0 con una copia de libcomplex 1.1, y entonces reaplicar las modificaciones propias que anteriormente hicimos sobre la librería antes de la nueva versión. Pero en realidad nos acercaremos al problema desde otra dirección, aplicando los cambios realizados sobre libcomplex entre las versiones 1.0 y 1.1 sobre nuestra propia copia modificada.
Para realizar esta actualización, obtenemos una copia
local de nuestra rama de proveedor, y reemplazamos el
código del directorio current
con
el nuevo código fuente de libcomplex 1.1. Literalmente
copiamos nuevos ficheros sobre los existentes, quizás
expandiendo el fichero comprimido del distribución de
libcomplex 1.1 sobre nuestros ficheros y directorios.
El objetivo aquí es que el directorio
current
solamente contenta el
código de libcomplex 1.1, y asegurarnos que todo ese
código fuente está bajo control de versiones. Oh, y
queremos realizar esto con la menor perturbación posible
sobre el historial del control de versiones.
Tras reemplazar el código de la versión 1.0 con el de
la 1.1, svn status mostrará los
ficheros con modificaciones locales, junto con quizás
algunos ficheros no versionados o ausentes. Si realizamos
lo que debíamos realizar, los ficheros no versionados
son aquellos ficheros nuevos introducidos en la versión
1.1 de libcomplex—ejecutamos svn
add sobre ellos para ponerlos bajo control
de versiones. Los ficheros ausentes son ficheros que
existían en la 1.0 pero no en la 1.1, y sobre éstos
usamos svn remove. Finalmente, una
vez que nuestra copia local de current
contiene únicamente el código de libcomplex 1.1, enviamos
al repositorio los cambios realizados para que tenga
este aspecto.
Nuestra rama current
ahora contiene
el hito de proveedor. Etiquetamos la nueva versión
(del mismo modo que etiquetamos el anterior hito de
proveedor como la versión 1.0), y entonces fusionamos
las diferencias entre las etiquetas de la versión
anterior y la actual en nuestra rama principal de
desarrollo.
$ cd working-copies/calc $ svn merge http://svn.example.com/repos/vendor/libcomplex/1.0 \ http://svn.example.com/repos/vendor/libcomplex/current \ libcomplex … # resolve all the conflicts between their changes and our changes $ svn commit -m 'merging libcomplex-1.1 into the main branch' …
En el caso de uso trivial, la nueva versión de nuestra herramienta de terceros sería, desde un punto de vista de ficheros y directorios, justo igual que nuestra versión anterior. Ninguno de los ficheros de código fuente de libcomplex habría sido borrado, renombrado o movido a una ubicación diferente—la nueva versión solamente tendría modificaciones textuales contra la anterior. En un mundo perfecto, nuestras modificaciones serían aplicadas limpiamente sobre la nueva versión de la librería, absolutamente sin complicaciones o conflictos.
Pero las cosas no son siempre tan simples, y de hecho es bastante habitual que ficheros de código fuente sean desplazados entre versiones de un software. Esto complica el proceso de asegurarnos que nuestras modificaciones son todavía válidas para la nueva versión del código, y puede degenerar rápidamente en la situación donde tenemos que recrear manualmente nuestras personalizaciones para la nueva versión. Una vez Subversion conoce la historia de un fichero de código fuente determinado—incluyendo todas sus ubicaciones previas—el proceso de fusionar la nueva versión de la librería es bastante simple. Pero somos responsables de indicar a Subversion cómo ha cambiado la estructura de ficheros de un hito de proveedor a otro.
Los hitos de proveedor que conllevan algo más que algunos borrados, adiciones o movimientos de ficheros complican el proceso de actualizarse a cada versión sucesiva de los datos de terceros. Por lo que Subversion proporciona el script svn_load_dirs.pl para asistirle en este proceso. Este script automatiza los procesos de importado anteriormente mencionados en el proceso de gestión de la rama del proveedor para minimizar el número posible de errores. Todavía será responsable de usar los comandos de fusionado para fusionar las nuevas versiones de los datos de terceros en su rama de desarrollo principal, pero svn_load_dirs.pl puede ayudarle a llegar a esta fase más rápido y con facilidad.
En resumen, svn_load_dirs.pl es una mejora a svn import que tiene ciertas características importantes:
Puede ser ejecutado en cualquier momento para transformar un directorio existente del repositorio para que sea una réplica de un directorio externo, realizando todas las operaciones de adición y borrado necesarias, y también opcionalmente haciendo algunas operaciones de renombrado.
Se encarga de una serie de operaciones complicadas entre las cuales Subversion requiere como paso intermedio enviar cambios al repositorio— como antes de renombrar un fichero o directorio dos veces.
Opcionalmente etiquetará el nuevo directorio importado.
Opcionalmente añadirá propiedades arbitrarias a ficheros y directorios que coincidan con una expresión regular.
svn_load_dirs.pl recibe tres parámetros obligatorios. El primer parámetro es la URL al directorio base de Subversion en el que se realizará el trabajo. Este parámetro es seguido por la URL—relativa respecto al primer argumento—en la cual se importará el hito de proveedor actual. Finalmente, el tercer parámetro es el directorio local que desea importar. Usando nuestro ejemplo anterior, una ejecución típica de svn_load_dirs.pl podría ser:
$ svn_load_dirs.pl http://svn.example.com/repos/vendor/libcomplex \ current \ /path/to/libcomplex-1.1 …
Puede indicar que desearía que
svn_load_dirs.pl etiquete el nuevo hito
de proveedor pasando la línea de comando -t
e indicando un nombre de etiqueta. Esta etiqueta es otra
URL relativa al primer argumento pasado al programa.
$ svn_load_dirs.pl -t libcomplex-1.1 \ http://svn.example.com/repos/vendor/libcomplex \ current \ /path/to/libcomplex-1.1 …
Cuando ejecuta svn_load_dirs.pl,
examina el contenido de su hito de proveedor
“actual”, y lo compara con el hito de proveedor
propuesto. En el caso trivial, no habrá ficheros que
existan en una versión pero no en la otra, así que el script
realizará la nueva importación sin incidentes. No obstante,
si hay discrepancias en la estructura de ficheros entre las
versiones, svn_load_dirs.pl le preguntará
cómo desea resolver esas diferencias. Por ejemplo, tendrá
la oportunidad de decirle al script que sabe que el fichero
math.c
en la versión 1.0 de libcomplex
fue renombrado como arithmetic.c
en
libcomplex 1.1. Cualquier discrepancia no explicada como
renombrado será tratada como una adición o borrado
normal.
El script también acepta un fichero de configuración
separado para ajustar propiedades sobre ficheros y
directorios que coincidan con una expresión regular
y que vayan a ser añadidos al
repositorio. Este fichero de configuración se le indica
a svn_load_dirs.pl usando la opción
de línea de comando -p
. Cada línea del
fichero de configuración es un conjunto de dos o cuatro
valores separados por espacios en blanco: una expresión
regular estilo Perl que debe coincidir con una ruta,
una palabra de control (ya sea break
o cont
), y opcionalmente, un nombre de
propiedad y su valor.
\.png$ break svn:mime-type image/png \.jpe?g$ break svn:mime-type image/jpeg \.m3u$ cont svn:mime-type audio/x-mpegurl \.m3u$ break svn:eol-style LF .* break svn:eol-style native
Para cada ruta añadida, se aplicarán en orden los
cambios de propiedades configurados para las rutas que
coincidan con las expresiones regulares, a no ser que la
especificación de control sea break
(lo cual significa que no deben aplicarse más cambios
de propiedades a esa ruta). Si la especificación de
control es cont
—una abreviación
de continue
—entonces se seguirá
buscando coincidencias con el patrón de la siguiente línea
del fichero de configuración.
Cualquier carácter de espaciado en la expresión regular,
nombre de la propiedad, o valor de la propiedad deberá ser
rodeado por caracteres de comillas simples o dobles. Puede
escapar los caracteres de comillas que no son usados
para envolver caracteres de espaciado precediéndolos
con un carácter de contrabarra (\
).
Las contrabarras sólo escapan las comillas cuando se
procesa el fichero de configuración, así que no proteja
ningún otro carácter excepto lo necesario para la expresión
regular.