Bloque de Conocimiento de Sistemas de Versionado de Código

Tabla de contenidos

Introducción

El problema que todo sistema de versionado quiere resolver es: Problem of File Sharing (ver Figura 1)., es decir que los sistemas de control de versiones permiten a un grupo de desarrolladores trabajar y modificar concurrentemente archivos organizados en proyectos sin interferirse unos a los otros. Esto significa que dos o más personas pueden modificar un mismo archivo sin que se pierdan los trabajos realizados por todas. Además, todas las versiones antiguas de los archivos son guardadas, es decir que se riene un archivo historico de las modificaciones que se van realizando sobre cada uno de los archivos. Pudiendo así recuperar en cualquier momento versiones anteriores a la actual.

Figura 1 - El problema a resolver: Problem of File Sharing

Clasificaciones

Lo que diferencia a todos los sistemas de versionado existentes es la estrategia que utilizan para permitir compartir y editar archivos en forma concurrente. Es por eso que se los puede clasificar de distintas formas, una de ellas es según donde se ubica los archivos compartidos, teniendo entonces la siguiente clasificación:

  • Centralizados: estilo cliente-servidor en donde existe un repositorio centralizado compartido donde se realizan todas las funciones de control de versiones. El funcionamiento básico de estos sistemas es que cada usuario que requiera trabajar con la información compartida deba hacer una copia local del repositorio, hacer las modificaciones necesarias y actualizar las mismas en el repositorio. Son ejemplos de sistemas de este tipo el CVS y el SVN.
  • Descentralizados:son del tipo P2P donde en vez de tener un repositorio centralizado donde los clientes deben sincronizar sus copias locales, el mismo está distribuido en cada copia de trabajo de los nodos de la red P2P, la sincronización se realiza mediante el intercambio de los "parches" (cambios realizados) por los distintos nodos de la red. De esta forma cada copia de trabajo es un back-up de la información compartida y contiene el histórico de los cambios realizados. Es más flexible pero dificulta la sincronización. Son ejemplos de este tipo de sistemas el GIT.

Dentro de los sistemas centralizados pueden clasificarse según como resuelven el Problem of File Sharing:

  • File locking o lock-modify-unlock:esta estrategia se basa en bloquear el archivo cada vez que se lo quiera modificar, quedando sólo permitido el acceso para su lectura mientras se encuentre bloqueado para escritura (ver ejemplo en Figura 2). Esta estrategia tiene sus ventajas y sus desventajas, entre las primeras tenemos que evita la necesidad de la resolución de conflictos por haberse modificado un archivo concurrentemente, pero tiene la desventaja de no poder realizar modificaciones mientras este bloqueado por algún otro usuario, si este tiempo es grande puede generar otros problemas como por ejemplo hacer los cambios locales y nunca subirlos al repositorio, mantener una versión local muy diferente de la final. Esta estrategia sirve cuando si el grupo de usuarios es chico o cuando por el tipo de archivo no es posible el merge de los cambios realizados, como sucede con los archivos binarios.
Figura 2 - Ejemplo de la estrategia de file locking
  • Version merging o copy-modify-merge: esta estrategia permite realizar modificaciones del mismo archivo concurrentemente. Estos sistemas deben proveer algún mecanismo que permite mezclar en el repositorio las modificaciones realizadas por los distintos usuarios sin perder ninguna (ver ejemplo en Figura 3 y Figura 4). El CVS y el SVN utilizan esta estrategia.
Figura 3 - Ejemplo de la estrategia Version Merging
Figura 4 - Ejemplo de la estrategia Version Merging (cont.)

Sistemas de versionado de uso común

  • CVS (Concurrent Versions System): Es una aplicación que implementa un sistema de control de versiones. Mantiene el regitro de todo el trabajo y los cambios en los archivos que forman parte de un proyecto y permite que distintos desarrolladores colaboren. Sus desarrolladores difunden el sistema bajo licencia GPL.
  • SVN (Subversion): Es un software de sistema de control de versiones diseñado específicamente para reemplazar al popular CVS, el cual posee varias deficiencias. Es software libre bajo una licencia de tipo Apache/BSD y se le conoce también como svn por ser ese el nombre de la herramienta de línea de comandos. Una característica importante de Subversion es que, a diferencia de CVS, los archivos versionados no tienen cada uno un número de revisión independiente. En cambio, todo el repositorio tiene un único número de versión que identifica un estado común de todos los archivos del repositorio en cierto momento.
  • SourceSafe: SourceSafe? fue creado por la compañía americana One Tree Software a principios de los años noventa. Su primera versión fue la 3.1 coincidiendo con el mismo número de versión del sistema operativo Windows. Microsoft adquirió los derechos sobre SourceSafe? que era un programa de 16 bits y lo liberó en 1995 como un programa de 32 bits, dándole la versión 4.0 SourceSafe? es un sistema basado en un equipo anfitrión a diferencia de la mayoría de los programas de control de versiones que son basados en Cliente-Servidor? donde el repositorio de control de cambios reside en el equipo servidor y los clientes toman de allí la última versión para modificarla y posteriormente ingresarla con las modificaciones realizadas.
  • Git: Es un software de sistema de control de versiones diseñado por Linus Torvalds, pensando en la eficiencia y confiabilidad de mantenimiento de versiones de aplicaciones con una enorme cantidad de archivos de código fuente. El diseño de Git se basó en BitKeeper? y en Monotone. En principio, Git se pensó como un motor de bajo nivel que otros pudieran usar para escribir front end como Cogito o StGIT. Sin embargo, Git se ha convertido desde entonces un sistema de control de versiones con funcionalidad plena. Hay algunos proyectos de mucha relevancia que ya usan Git, en particular, el grupo de programación del núcleo del sistema operativo Linux.
  • BZR (Bazaar, formerly Bazaar-NG): Es un sistema de control de versiones patrocinado por Canonical Ltd., diseñado para facilitar el acceso a las personas a contribuir a proyectos de software libres y open source. El equipo de desarrollo se enfocó en la simplicidad de uso, presición y flexibilidad. Tanto el uso de branches como el mergeo de código está pensado para ser lo más simple posible, y para lograr que el ususario se pueda arreglar con unos pococ comandos. BZR puede ser usado como un repositorio para desarrollo local, o como repositorio remoto para la colaboración de diferentes equipos de desarorollo. BZR está escrito en Python, que está incluído en la mayoría de las distribuciones de Linux, Mac OS y MS Windows. Es libre, y es parte del proyecto GNU.

En http://en.wikipedia.org/wiki/List_of_revision_control_software (external link) se encuentra una lista más completa de los sistemas de versionado existentes.

Terminología general

  • Repositorio: El repositorio es el lugar en el que se almacenan los datos de un proyecto a compartir (archivos, estructura de directorios, etc.), y sus distintas versiones que se van generando a medida que se le realizan modificaciones, es decir el histórico de revisiones. A veces se le denomina depósito o depot.
  • Rama (Branch): Nos permite trabajar con varias versiones del proyecto al mismo tiempo. Es una ramificación de la documentación compartida, es decir que se tiene dos o más copias (una por branch agregada + la original) de los archivos del repositorio las cuales pueden ser modificadas independientemente una de la otra.
  • Head: : Es la rama principal de trabajo, es la única línea que no es un branch. A veces se suele llamar trunk a la línea principal y head a la última versión del repositorio.
  • Módulo: Es el conjunto de directorios y/o archivos dentro del repositorio que pertenecen a un proyecto común.
  • Rotular (Taguear): Establecer una marca, ya sea en el head o en un branch, a los archivos del repositorio de manera tal de indicar una versión global de los mismos. Se utiliza por ejemplo cuando se tiene una version estable de lo desarrollado hasta el momento. Dicha marca nos permitira luego, obtener todos los fuentes del proyecto en el estado en que se encontraban al momento de realizarla.
  • Revisión (Version): Una revisión es una versión determinada de un archivo, es decir es una foto del mismo en un determinado momento.
  • Línea Base (Baseline): Una revisión aprobada de un documento o archivo fuente, a partir del cual se pueden realizar cambios subsiguientes.
  • Desplegar (Check-out): Un check-out baja una copia del proyecto, que esta en el repositorio, a nuestra computadora, es decir crea una copia de trabajo. Se puede bajar una versión especifica (por defecto se baja la última).
  • Enviar (Commit): Hacemos un commit cuando enviamos las modificaciones realizadas en la copia de trabajo al repositorio. Se suele usar el término commitear a la acción de hacer un commit.
  • Conflicto: Un conflicto ocurre cuando se realizan modificaciones al mismo documento en distintas copias de trabajo y cuando se realiza el commit de ellas el sistema no es capaz de compaginar esos cambios. La resolución de los conflictos debe ser realizada por una persona, quien debe combinar las modificaciones hechas o elegir cuál es la correcta.
  • Resolver Conflicto: El acto de la intervención del usuario para atender un conflicto entre diferentes cambios al mismo documento.
  • Cambio (Change): Un cambio representa una modificación específica en un documento. La granularidad de la modificación considerada un cambio varía entre diferentes sistemas de control de versiones.
  • Lista de cambios (Changelist): En muchos sistemas de control de versiones con commits multi-cambio atómicos, una lista de cambios identifica el conjunto de cambios hechos en un único commit. Esto también puede representar una vista secuencial del código fuente, permitiendo que el fuente sea examinado a partir de cualquier identificador de lista de cambios particular.
  • Exportación (Export): Una exportación es similar a un check-out, salvo porque crea un árbol de directorios limpio sin los metadatos de control de versiones presentes en la copia de trabajo. Se utiliza a menudo de forma previa a la publicación de los contenidos.
  • Importación (Import): Una importación es la acción de copia de un árbol de directorios local (que no es en ese momento una copia de trabajo) al repositorio por primera vez.
  • Integración o fusión (Merge): Es una integración ó fusión une dos conjuntos de cambios realizados sobre un mismo archivo de manera tal de obtener una versión unificada del mismo. Puede ser necesario en los siguientes casos:
    • Esto puede suceder cuando un usuario, trabajando en ese archivo, actualiza su copia local con los cambios realizados, y añadidos al repositorio, por otros usuarios. Análogamente, este mismo proceso puede ocurrir en el repositorio cuando un usuario intenta un check-in de sus cambios.
    • Puede suceder después que los archivos hayan sido modificados en un branch y se necesite unificarlos con los cambios realizados en el Head.
  • Integración inversa: El proceso de mezclar ramas de diferentes equipos en el trunk principal del sistema de versiones.
  • Actualización (Sync ó Update): Una actualización integra los cambios que han sido hechos en el repositorio (por otras personas) en la copia de trabajo local.
  • Copia de trabajo: La copia de trabajo es la copia en nuestra maquina de trabajo, del proyecto que está en el repositorio. Todo el trabajo realizado sobre los archivos en un repositorio se realiza inicialmente sobre una copia de trabajo, de ahí su nombre.
  • Congelar: Congelar significa permitir los últimos cambios (commits) para solucionar las fallas a resolver en una entrega (release) y suspender cualquier otro cambio antes de una entrega, con el fin de obtener una versión consistente. Si no se congela el repositorio, un desarrollador podría comenzar a resolver un error cuya resolución no esta prevista, y cuya solución de lugar a efectos colaterales imprevistos.

Conceptos generales de los sistemas de versionado

Ciclo de trabajo básico

1. Crear repositorio

2. Bajar por primera vez el modulo: Para crear una copia de trabajo local del módulo deseado debemos hacer un 'checkout'. Esto creará una jerarquía de directorios donde se almacenará el módulo. Este paso sólo hay que hacerlo una vez por cada módulo.

3. Realizar cambios en nuestra copia de trabajo.

4. Actualizar cambios: Actualizar la copia local del módulo con los cambios que hayan realizado otros usuarios y que están subidos al repositorio (Update). Las buenas prácticas indican que se debe actualizar diariamente, así se mantiene lo mas sincronizado posible la copia de trabajo local y el repositorio.

5. Publicar nuestras modificaciones: Para subir al repositorio los cambios que hayamos hecho en el modulo, debemos hacer un 'commit'.

6. Taggear:Hacer un tag en el repositorio cando se llega a un release del producto

Branches

Una de las funcionalidades que proveen los sistemas de versionado es la posibilidad de crear una nueva línea de desarrollo, conocida como branches. Esto permite poder realizar modificaciones en los archivos y que las mismas no sean visibles en las otras líneas, sean branches o la principal. Pudiendo unificar las versiones de las distintas ramas mediante la realización de un merge de las mismas.

Necesidad del uso de branches

A continuación se listan algunos de los escenarios donde surge la necesidad de utilizar branches:

  • Corrección de Bugs:Supongamos que hemos pasado a producción el release 1.0 del sistema que estamos desarrollando y que debemos continuar trabajando en él para llegar a un nuevo release, el 1.1, planeado para unos meses después. Ahora supongamos que durante el desarrollo del nuevo release, se detecta un bug en el productivo, que debe ser modificado rápidamente y no puede esperar a terminar el nuevo release. Si el desarrollo se continúo sobre la línea del 1.0, podemos crear un branch sobre los archivos del release 1.0, arreglar el bug en ese branch, pasar a producción el release 1.0 corregido y mientras tanto seguir avanzando con el 1.1 sobre el head. En algún momento se tendrá que mergear las modificaciones hechas en el branch con el head. Generalmente se prevén estas situaciones y lo que se hace es crear un branch cada vez que se encare un nuevo release, se encara el desarrollo del mismo en el head y se utiliza el branch para la corrección de los bugs que vallan surgiendo y cuando se finaliza el desarrollo del nuevo release se hace el merge del branch con el head, de esta forma siempre tenemos disponible la versión productiva del sistema. Otra opción es mantener la versión productiva en el head y utilizar el branch para el desarrollo del nuevo release.
  • Refactor:Supongamos que se quiere realizar un refactor de importancia sobre un sistema, como por ejemplo migrar de una biblioteca o un framework que se está utilizando a otro distinto, que requiere la modificación de gran cantidad de archivos. En estos casos se desea, como en el caso del escenario anterior, mantener la versión productiva estable, para que se le pueda realizar pequeñas modificaciones necesarias. También puede suceder que la migración se realiza para mejorar algún atributo de calidad, por ejemplo la performance, y que no se decida si la migración debe realizarse en producción, hasta que realmente se compruebe que tal mejora se produce. En este caso se creara un branch en el cual se hará el refactor (la migración a la nueva biblioteca o framework para el caso del ejemplo planteado) y el head se mantiene intacto (respecto a este refactor). De esta manera, cuando se finalizan las modificaciones en el branch, se evalúa el release obtenido y si es aceptado se hace el merge con el head.
  • Releases secuenciales:Los branches son frecuentemente utilizados cuando se desarrollan release consecutivos sin querer esperar a finalizar uno para comenzar el otro. Por ejemplo se empieza a desarrollar el release 2.1 antes de finalizar el 2.0 y cuando este finaliza, se hace un merge del branch en donde se lo estaba desarrollando al branch del 2.1.
  • Releases simultáneos:Los branches también son utilizados para desarrollar releases simultáneos, por ejemplo crear branches para múltiples plataformas.

Ciclo de trabajo con branches

Figura 5 - Ciclo básico de trabajo con branches

En la Figura 5 muestra el diagrama de un ciclo básico de trabajo con branches, en donde hay dos desarrolladores, Pablo y Walter, trabajando sobre un mismo proyecto. En un determinado momento del desarrollo se necesita crear un nuevo branch (Root_p1test ) para que Pablo realice algunas modificaciones, mientras que Walter continua trabajando en el head. Ambos realizan sus modificaciones (PD1 y WD1), aplican los tags P1 y W1 a sus respectivos branches de trabajo (el branch Root_p1test y Head) , hacen el commit y luego realiza el merge del branch donde estaba trabajando pablo al head (M1) y se hace un tag de la versión unificada y comienzan una nueva iteración .

Figura 6 - Ciclo alternativo de trabajo con branches

En la Figura 6 se muestra otro tipo de ciclo de trabajo con branches, en donde también tenemos los mismos desarrolladores, Pablo y Walter, trabajando en separados branches del mismo proyecto, el branch Root_p1test y Head, respectivamente. Se diferencia del escenario anterior en que en lugar de impactar los cambios realizados por Pablo en su branches al head, primeramente se sincronizan en el branch las modificaciones del head (merge del head en el branch) y luego se pasara la versión final obtenida en el branch al head. En este escenario las dos ramas estarán sincronizadas al comienzo de la nueva iteración, a diferencia del escenario básico.

Algunos términos relacionado con los ciclos de branch

  • Rebase: se suele llamar así a la incorporación en un branch de los cambios realizados en el Head, ya que se considera que se está ajustando el branch al head, que fue desde donde se lo creo.
  • Delivery: se llama así al merge desde un branch al head, ya que se están entregando (enviando) los cambios a la rama principal.

Buenas Practicas

A continuación se listan algunas de las buenas prácticas en el uso de los sistemas de versionados:

1. Usar clientes GUI: La utilización de clientes con interfaz gráfica minimiza el tiempo de aprendizaje del uso de los sistemas de versionado.

2. Hacer Update al comienzo de la jornada: De esta se mantiene la copia de trabajo sincronizada con el repositorio central y minimiza la generación de conflictos.

3. Hacer Update antes de hacer un commit: Siempre antes de hacer un commit se recomienda hacer un update. Dependiendo de la herramienta que se esté utilizando puede pasar que si no se procede así se pierdan modificaciones, otros clientes directamente dan error cuando detectan una versión más nueva en el repositorio que la de la copia local. Si la herramienta que se está utilizando provee la funcionalidad de observar las diferencias entre la copia local y el repositorio antes de bajar o subir cambios, conviene realizar esta tarea, de esta forma podemos ver que archivos fueron modificados por otros y por nosotros, cuales necesitan hacer un merge, etc.

4. No trabajar fuera del área de trabajo: La copia de trabajo local puede verse como un área controlada donde el sistema de versionado puede rastrear las modificaciones que se van realizando en los archivos dentro de la misma. Si se trabaja por afuera de ella se pierden los beneficios que otorga este sistema. Una mala práctica es enviar a los otros desarrolladores archivos por mail en lugar de subirlos al cvs.

5. Commitear frecuentemente: Ayuda a mantener lo más sincronizado posible los archivos de todos los desarrolladores del proyecto. Además de evitar perdida de trabajo realizado por rotura u otro problema en la PC de trabajo del desarrollador, ya que comúnmente el repositorio se encuentra en un servidor al cual se le realizan backup frecuentemente. El criterio de cuan frecuente hacer commit debe ser un balance entre tiempo y funcionalidad finalizada, es decir que si esperar a terminar una funcionalidad completa hace que estemos una semana sin subir nuestras modificaciones al repositorio, apliquemos otro criterio como puede ser hacer un commit al finalizar nuestra jornada de trabajo, siempre y cuando lo que estemos subiendo no de errores ni perjudique al resto del equipo (ver siguientes ítems).

6. Subir sólo las modificaciones que no perjudican al resto del equipo: Cuando se hace un commit se debe tener en cuenta que lo que se esté subiendo no perjudique al resto del equipo, es decir no subir archivos con errores, no subir modificaciones que inhabiliten alguna funcionalidad que otros desarrolladores necesiten, subir todos los archivos modificados. Respecto a este último punto, muchas veces es común que se suban sólo algunos archivos y otros no, por ejemplo porque se termino parcialmente el trabajo que se estaba haciendo, hay que ser cuidadoso con esto ya que es muy frecuente que nos estemos olvidando de subir archivos que son necesarios por otros ya subidos y esto perjudique a quien se actualiza su copia de trabajo, es decir a quien realiza un update o a quien quiere bajarse el proyecto por primera vez (check-out).

7. Agregar comentarios en el commit: Es recomendable agregar un comentario en el commit, el mismo debe ser escrito con conciencia y debe reflejar que cambios se están subiendo al repositorio. Los comentarios facilitan el rastreo de versiones de los archivos así como también la detección de errores encontrados en la última versión de los mismos y que antes no estaban.

8. Taggear: Hacer un tag cada vez que se llegue a la finalización de una iteración o a la finalización de un sprint, si se utiliza scrum como metodología de trabajo, así como también cuando se obtenga un release del producto. Los nombre de los tags deben identificar le versión del proyecto, es decir que si el tag se está realizando porque se llego al release 1.2 del sistema, el nombre del tag podría ser RELEASE_1.2.

9. Utilizar branches: Utilizar branches siempre que se presente algunos de los escenarios descriptos en la sección Necesidad del uso de branches. Muchas veces existe la mala costumbre de crear un nuevo repositorio cuando se quiere encarar un refactor importante del sistema, en lugar de crear un branch para hacerlo, perdiéndose las herramientas que proveen los sistemas de versionado para hacer el merge de los branches.

10. Usar nombres identificatorios para los branches: Al igual que para el caso de los Tags, los nombres de los branches deben ser representativos del motivo de creación o utilización del branch, de manera tal que puedan ser identificadas fácilmente. Por ejemplo, si se creó porque se necesitaba encarar un refactor importante, el nombre del branch debería contener una referencia al refactor, por ejemplo: BRANCH_MIGRACION_XXXX para el caso de un refactor por migración.

11. Nombrar un responsable del branch y del head: Es conveniente nombrar a un responsable de cada branch creado y del head, quien será responsable del mantenimiento del mismo, el cual incluye las siguientes tareas entre otras: taggear, mergear, etc.

Ciclo de Trabajo Recomendado

El ciclo de trabajo recomendado es el siguiente:

1. Cada vez que se termina un release hacer un tag del repositorio.

2. Crear un branch para la corrección de los bugs del release finalizado.

3. Realizar el desarrollo del nuevo release en el head.

4. Cuando se finaliza un patch del release:

4.1. taggear el branch.

4.2. hacer el merge del branch con el head.

Una de las ventajas de la utilización de un branch para la corrección de los bugs, es que permite obtener una versión corregida del release productivo sin tener que esperar a finalizar el desarrollo del nuevo.

Comparación entre herramientas (SVN y CVS)

SVN fue diseñado pensando en reemplazar CVS, por lo tanto, tiene la misma arquitectura basada en un repositorio central. Los comandos son muy parecidos. La idea es que se pueda migrar de CVS a SVN con un costo muy bajo. Las mejoras más importantes son las siguientes:

  • Los commits son atómicos y más eficientes. Si el commit sufre una interrupción, no corrompe el repositorio.
  • Para ingresar un cambio en un archivo, CVS tiene que transferir todo el archivo entre la copia local y el repositorio. SVN calcula antes la diferencia y sólo transfiere los bytes involucrados en el cambio, haciendo el commit mucho más eficiente en cuanto a tiempo y tráfico.
  • CVS estudia diferencias entre archivos basándose en líneas. SVN en cambio lo hace comparando bytes, lo que permite parches de menor tamaño. Además, permite un mejor manejo de documentos de texto que contienen párrafos, a diferencia de los archivos con código.
  • En CVS no se puede renombrar un archivo. Hay que borrarlo, y agregar la copia como un archivo nuevo con otro nombre. Lo peor es que el nuevo archivo no hereda el historial, por lo tanto, se pierde. SVN permite renombrar archivos e incluso cambiarlos de directorio manteniendo todo el historial.
  • En SVN el soporte de archivos binarios es nativo y funciona muy bien.
  • SVN trabaja en base a revisiones que involucran todo el proyecto. CVS en cambio, funciona con historiales independientes por cada archivo, o sea, no es fácil identificar una correción de bug que involucra cambios en distintos archivos, porque cada uno tiene distintos números de revisión. Este cambio introducido por SVN permite al equipo de trabajo hablar por ejemplo sobre "la revisión 742", y hace mucho más fácil las mezclas entre distintas ramas de desarrollo.

SVN y Eclipse: Hacer un merge

Se dice, y seguro que es verdad, que la forma de trabajar con SVN debe ser a base de branches y, una vez probados los diferentes cambios, pasarlo al trunk. Desde Eclipse, con el Subclipse, crear un nuevo branch es muy fácil, pero… ¿cómo se hace posteriormente el merge?

El día que tuve que hacerlo por primera vez seguí las instrucciones de aquí, la parte donde dice Merging Two Different Trees y que, traduciendo, resumiendo y ampliando, dice lo siguiente:

  • Ponte en la copia local del Eclipse que apunta al trunk (muy importante)
  • Pulsa botón derecho del ratón, selecciona Team → Merge
  • En el campo “From” se indica la URL completa del trunk
  • Selecciona la revisión en la que iniciaste el branch
  • Desmarca el check del Use “From:” URL
  • En el campo “To” se indica la URL completa del branch
  • Selecciona la revisión del branch que quieres juntar (habitualmente será el HEAD)
  • Pulsa “OK”

Finalmente, comprueba que todo es correcto, corrige posibles problemas en el buildpath y realiza un commit

Este método es exactamente lo que se debe hacer también para volver a una versión anterior (ahora que está de moda, imaginemos el fallo de Debian), salvo el paso 5 ya que estamos con la misma URL. A pesar de que algunos lo odien, SVN es a buen seguro la herramienta de control de versiones más extendida y espero que lo anterior os sirva para vuestro día a día. Fuente: http://www.planetacodigo.com/planeta/index.php?entry=entry070613-085717&paged=124 (external link)

Bibliografía

Otras fuentes de información

Bloques Relacionados

Enlaces externos

CVS

SVN

Varios

Referentes

  • Andrés Gonzalez
  • Fernando Gaddi

-----



Created by fernandog. Last Modification: Martes 04 de Enero, 2011 11:29:50 EST by martins.
El contenido de esta página esta licenciado bajo los términos del http://creativecommons.org/licenses/by-sa/2.5/legalcode.
El documento original está disponible en http://www.epidataconsulting.com/tikiwiki/tiki-index.php?page=Versionado