Control Distribuido de Revisiones con Mercurial

Bryan O’Sullivan

Copyright © 2006, 2007 Bryan O’Sullivan.
Este material puede distribuirse únicamente bajo los términos y condiciones establecidos en la versión 1.0 de la Licencia de Publicación Abierta (OPL). Refiérase por favor al apéndice D para encontrar el texto de la licencia.
Este libro fue preparado a partir de http://mercurial.intuxication.org/hg/mercurialbookesrev fc4daaad6415,fechado2009021000 : 280500,usandoMercurialhttp : ∕∕www.selenic.com∕hg∕rev MercurialDistributedSCM(version1,0,1).

Índice general

Índice general
Prefacio
 0.1 Este libro es un trabajo en progreso
 0.2 Acerca de los ejemplos en este libro
 0.3 Colofón—este libro es Libre
1 Introducción
 1.1 Acerca del control de revisiones
  1.1.1 ¿Por qué usar control de revisiones?
  1.1.2 La cantidad de nombres del control de revisiones
 1.2 Historia resumida del control de revisiones
 1.3 Tendencias en el control de revisiones
 1.4 Algunas ventajas del control distribuido de revisiones
  1.4.1 Ventajas para proyectos de código abierto
  1.4.2 Ventajas para proyectos comerciales
 1.5 ¿Por qué elegir Mercurial?
 1.6 Comparación de Mercurial con otras herramientas
  1.6.1 Subversion
  1.6.2 Git
  1.6.3 CVS
  1.6.4 Herramientas comerciales
  1.6.5 Elegir una herramienta de control de revisiones
 1.7 Migrar de otra herramienta hacia Mercurial
2 Una gira de Mercurial: lo básico
 2.1 Instalar Mercurial en su sistema
  2.1.1 Linux
  2.1.2 Solaris
  2.1.3 Mac OS X
  2.1.4 Windows
 2.2 Arrancando
  2.2.1 Ayuda integrada
 2.3 Trabajar con un repositorio
  2.3.1 Hacer una copia local de un repositorio
  2.3.2 Qué hay en un repositorio?
 2.4 Vistazo rápido al historial
  2.4.1 Conjuntos de cambios, revisiones, y comunicándose con otras personas
  2.4.2 Ver revisiones específicas
  2.4.3 Información más detallada
 2.5 Todo acerca de las opciones para comandos
 2.6 Hacer y repasar cambios
 2.7 Grabar cambios en un nuevo conjunto de cambios
  2.7.1 Definir un nombre de usuario
  2.7.2 Escribir un mensaje de consignación
  2.7.3 Escribir un buen mensaje de consignación
  2.7.4 Cancelar una consignación
  2.7.5 Admirar nuestro trabajo
 2.8 Compartir cambios
  2.8.1 Jalar cambios desde otro repositorio
  2.8.2 Actualizar el directorio de trabajo
  2.8.3 Empujar cambios a otro repositorio
  2.8.4 Compartir cambios a través de una red
3 Una gira de Mercurial: fusionar trabajo
 3.1 Fusionar líneas de trabajo
  3.1.1 Conjuntos de cambios de frentes
  3.1.2 Hacer la fusión
  3.1.3 Consignar los resultados de la fusión
 3.2 Fusionar cambios con conflictos
  3.2.1 Usar una herramienta gráfica para fusión
  3.2.2 Un ejemplo real
 3.3 Simplificar el ciclo jalar-fusionar-consignar
4 Tras bambalinas
 4.1 Registro del historial de Mercurial
  4.1.1 Seguir el historial de un único fichero
  4.1.2 Administración de ficheros monitoreados
  4.1.3 Registro de información del conjunto de cambios
  4.1.4 Relaciones entre revisiones
 4.2 Almacenamiento seguro y eficiente
  4.2.1 Almacenamiento eficiente
  4.2.2 Operación segura
  4.2.3 Recuperación rápida de datos
  4.2.4 Identificación e integridad fuerte
 4.3 Historial de revisiones, ramas y fusiones
 4.4 El directorio de trabajo
  4.4.1 Qué pasa en una consignación
  4.4.2 Creación de un nuevo frente
  4.4.3 Fusión de frentes
 4.5 Otras características de diseño interesantes
  4.5.1 Compresión ingeniosa
  4.5.2 Reordenado de lectura/escritura y atomicidad
  4.5.3 Acceso concurrente
  4.5.4 Evitar movimientos de brazo
  4.5.5 Otros contenidos del estado de directorio
5 Mercurial día a día
 5.1 Cómo indicarle a Mercurial qué ficheros seguir
  5.1.1 Nombramiento explícito e implícito de ficheros
  5.1.2 Nota al margen: Mercurial trata ficheros, no directorios
 5.2 Cómo dejar de hacer seguimiento a un fichero
  5.2.1 Al eliminar un fichero no se afecta su historial
  5.2.2 Ficheros perdidos
  5.2.3 Nota al margen: ¿Por qué decirle explícitamente a Mercurial que elimine un fichero?
  5.2.4 Atajo útil—agregar y eliminar ficheros en un solo paso
 5.3 Copiar ficheros
  5.3.1 Resultados de copiar un fichero durante una fusión
  5.3.2 ¿Por qué los cambios se reflejan en las copias?
  5.3.3 Cómo hacer que los cambios no sigan a la copia?
  5.3.4 Comportamiento de la orden “hg copy
 5.4 Renombrar ficheros
  5.4.1 Renombrar ficheros y fusionar cambios
  5.4.2 Cambios de nombre divergentes y fusión
  5.4.3 Cambios de nombre convergentes y fusión
  5.4.4 Otros casos límite relacionados con renombramientos
 5.5 Recuperarse de equivocaciones
6 Colaborar con otros
 6.1 La interfaz web de Mercurial
 6.2 Modelos de colaboración
  6.2.1 Factores a tener en cuenta
  6.2.2 Anarquía informal
  6.2.3 Un repositorio central único
  6.2.4 Trabajo con muchas ramas
  6.2.5 Ramas de características
  6.2.6 El tren de publicación
  6.2.7 El modelo del kernel Linux
  6.2.8 Solamente jalar frente a colaboración pública
  6.2.9 Cuando la colaboración encuentra la administración ramificada
 6.3 Aspectos técnicos de la colaboración
 6.4 Compartir informalmente con “hg serve
  6.4.1 Cuestiones adicionales para tener en cuenta
 6.5 Uso del protocolo Secure Shell (ssh)
  6.5.1 Cómo leer y escribir URLs de ssh
  6.5.2 Encontrar un cliente ssh para su sistema
  6.5.3 Generar un par de llaves
  6.5.4 Uso de un agente de autenticación
  6.5.5 Configurar el lado del servidor apropiadamente
  6.5.6 Compresión con ssh
 6.6 Servir sobre HTTP usando CGI
  6.6.1 Lista de chequeo de la configuración del servidor web
  6.6.2 Configuración básica de CGI
  6.6.3 Compartir varios repositorios con un guión CGI
  6.6.4 Descarga de ficheros fuente
  6.6.5 Opciones de configuración en Web
7 Nombres de ficheros y asociación de patrones
 7.1 Nombrado de ficheros simple
 7.2 Ejecución de comandos sin ningún nombre de fichero
 7.3 Reportar que está pasando
 7.4 Uso de patrones para identificar ficheros
  7.4.1 Patrones glob estilo intérprete
  7.4.2 Asociación con patrones de expresiones regulares re
 7.5 Filtrado de ficheros
 7.6 Ignorar ficheros y directorios no deseados
 7.7 Sensibilidad a mayúsculas
  7.7.1 Almacenamiento portable y seguro de repositorios
  7.7.2 Detección de conflictos de mayúsculas/minúsculas
  7.7.3 Arreglar un conflicto de mayúsculas/minúsculas
8 Administración de versiones y desarrollo ramificado
 8.1 Dar un nombre persistente a una revisión
  8.1.1 Manejo de conflictos entre etiquetas durante una fusión
  8.1.2 Etiquetas y clonado
  8.1.3 Cuando las etiquetas permanentes son demasiado
 8.2 El flujo de cambios—El gran cuadro vs. el pequeño
 8.3 Administrar ramas en repositorios estilo gran cuadro
 8.4 No repita trabajo: fusión entre ramas
 8.5 Nombrar ramas dentro de un repositorio
 8.6 Tratamiento de varias ramas nombradas en un repositorio
 8.7 Nombres de ramas y fusiones
 8.8 Normalmente es útil nombrar ramas
9 Encontrar y arreglar sus equivocaciones
 9.1 Borrar el historial local
  9.1.1 La consignación accidental
  9.1.2 Hacer rollback una transacción
  9.1.3 Erroneamente jalado
  9.1.4 Después de publicar, un roll back es futil
  9.1.5 Solamente hay un roll back
 9.2 Revertir un cambio equivocado
  9.2.1 Errores al administrar ficheros
 9.3 Tratar cambios consignados
  9.3.1 Retroceder un conjunto de cambios
  9.3.2 Retroceder el conjunto de cambios punta
  9.3.3 Retroceso de un cambio que no es la punta
  9.3.4 Más control sobre el proceso de retroceso
  9.3.5 Por qué “hg backout” hace lo que hace
 9.4 Cambios que nunca debieron ocurrir
  9.4.1 Cómo protegerse de cambios que han “escapado”
 9.5 Al encuentro de la fuente de un fallo
  9.5.1 Uso de la orden “hg bisect
  9.5.2 Limpieza después de la búsqueda
 9.6 Consejos para encontrar fallos efectivamente
  9.6.1 Dar una entrada consistente
  9.6.2 Automatizar tanto como se pueda
  9.6.3 Verificar los resultados
  9.6.4 Tener en cuenta la interferencia entre fallos
  9.6.5 Acotar la búsqueda perezosamente
10 Manejo de eventos en repositorios mediante ganchos
 10.1 Vistazo general de ganchos en Mercurial
 10.2 Ganchos y seguridad
  10.2.1 Los ganchos se ejecutan con sus privilegios de usuario
  10.2.2 Los ganchos no se propagan
  10.2.3 Es posible hacer caso omiso de los ganchos
  10.2.4 Asegurarse de que ganchos críticos sean ejecutados
 10.3 Precauciones con ganchos pretxn en un repositorio de acceso compartido
  10.3.1 Ilustración del problema
 10.4 Tutorial corto de uso de ganchos
  10.4.1 Llevar a cabo varias acciones por evento
  10.4.2 Controlar cuándo puede llevarse a cabo una actividad
 10.5 Escribir sus propios ganchos
  10.5.1 Escoger cómo debe ejecutarse su gancho
  10.5.2 Parámetros para ganchos
  10.5.3 Valores de retorno de ganchos y control de actividades
  10.5.4 Escribir un gancho externo
  10.5.5 Indicar a Mercurial que use un gancho interno
  10.5.6 Escribir un gancho interno
 10.6 Ejemplos de ganchos
  10.6.1 Escribir mensajes de consignación significativos
  10.6.2 Comprobar espacios en blanco finales
 10.7 Ganchos adicionales
  10.7.1 acl—control de acceso a partes de un repositorio
  10.7.2 bugzilla—integración con Bugzilla
  10.7.3 notify—enviar notificaciones de correo electrónico
 10.8 Información para escritores de ganchos
  10.8.1 Ejecución de ganchos internos
  10.8.2 Ejecución de ganchos externos
  10.8.3 Averiguar de dónde vienen los conjuntos de cambios
 10.9 Referencia de ganchos
  10.9.1 changegroup—luego de añadir conjuntos de cambios remotos
  10.9.2 commit—luego de la creación de un nuevo conjunto de cambios
  10.9.3 incoming—luego de que un conjunto de cambios remoto es añadido
  10.9.4 outgoing—luego de la propagación de los conjuntos de cambios
  10.9.5 prechangegroup—antes de empezar la adición de conjuntos de cambios remotos
  10.9.6 precommit—antes de iniciar la consignación de un conjunto de cambios
  10.9.7 preoutgoing—antes de empezar la propagación de conjuntos de cambios
  10.9.8 pretag—antes de etiquetar un conjunto de cambios
  10.9.9 pretxnchangegroup—antes de completar la adición de conjuntos de cambios remotos
  10.9.10 pretxncommit—antes de completar la consignación de un nuevo conjunto de cambios
  10.9.11 preupdate—antes de actualizar o fusionar el directorio de trabajo
  10.9.12 tag—luego de etiquetar un conjunto de cambios
  10.9.13 update—luego de actualizar o fusionar el directorio de trabajo
11 Personalizar los mensajes de Mercurial
 11.1 Usar estilos que vienen con Mercurial
  11.1.1 Especificar un estilo predeterminado
 11.2 Órdenes que soportan estilos y plantillas
 11.3 Cuestiones básicas de plantillas
 11.4 Palabras claves más comunes en las plantillas
 11.5 Secuencias de Control
 11.6 Uso de filtros con palabras claves
  11.6.1 Combinar filtros
 11.7 De plantillas a estilos
  11.7.1 Los ficheros de estilo más sencillos
  11.7.2 Sintaxis de ficheros de estilo
 11.8 Ejemplos de ficheros de estilos
  11.8.1 Identificar equivocaciones en ficheros de estilo
  11.8.2 Identificar de forma única un repositorio
  11.8.3 Mostrando salida parecida a Subversion
12 Administración de cambios con Colas de Mercurial
 12.1 El problema de la administración de parches
 12.2 La prehistoria de las Colas de Mercurial
  12.2.1 Trabajar parches con quilt
  12.2.2 Pasar de trabajo con parches con Quilt hacia Colas de Mercurial
 12.3 La gran ventaja de MQ
 12.4 Entender los parches
 12.5 Comenzar a usar Colas de Mercurial
  12.5.1 Crear un nuevo parche
  12.5.2 Refrescar un parche
  12.5.3 Aplicar un parche tras otro y dar seguimiento
  12.5.4 Manipular la pila de parches
  12.5.5 Introducir y sustraer muchos parches
  12.5.6 Medidas de seguridad y cómo saltarlas
  12.5.7 Trabajar con varios parches a la vez
 12.6 Más acerca de parches
  12.6.1 La cantidad de franjas
  12.6.2 Estrategias para aplicar parches
  12.6.3 Algunos detalles de la representación de parches
  12.6.4 Cuidado con los difusos
  12.6.5 Manejo de descartes
 12.7 maximizar el rendimiento de MQ
 12.8 Actualiar los parches cuando el código cambia
 12.9 Identificar parches
 12.10 Otra información útil
 12.11 Administrar parches en un repositorio
  12.11.1 Soporte de MQ para repositorios de parches
  12.11.2 Detalles a tener en cuenta
 12.12 Otras herramientas para trabajar con parches
 12.13 Buenas prácticas de trabajo con parches
 12.14 Recetas de MQ
  12.14.1 Administrar parches “triviales”
  12.14.2 Combinar parches completos
  12.14.3 Fusionar una porción de un parche dentro de otro
 12.15 Diferencias entre quilt y MQ
13 Usos avanzados de las Colas de Mercurial
 13.1 El problema de múltiples objetivos
  13.1.1 Aproximaciones tentadoras que no funcionan adecuadamente
 13.2 Aplicar parches condicionalmente mediante guardias
 13.3 Controlar los guardias de un parche
 13.4 Selecccionar los guardias a usar
 13.5 Reglas de MQ para aplicar parches
 13.6 Podar el entorno de trabajo
 13.7 Dividir el fichero series
 13.8 Mantener la serie de parches
  13.8.1 El arte de escribir parches de backport
 13.9 Consejos útiles para hacer desarrollo con MQ
  13.9.1 Organizar parches en directorios
  13.9.2 Ver el historial de un parche
14 Añadir funcionalidad con extensiones
 14.1 Mejorar el desempeño con la extensión inotify
 14.2 Soporte flexible de diff con la extensión extdiff
  14.2.1 Definición de alias de comandos
 14.3 Uso de la extensión transplant para seleccionar
 14.4 Enviar cambios vía correo electrónico con la extensión patchbomb
  14.4.1 Cambiar el comportamiento de las bombas de parches
A Referencia de Órdenes
 A.1 hg add”—Añade ficheros en la próxima consignación
 A.2 hg diff”—imprime los cambios en el historial o el directorio actual
  A.2.1 Options
 A.3 hg version”—imprime la información de versión y derechos de reproducción
  A.3.1 Consejos y trucos
B Referencia de las Colas de Mercurial
 B.1 Referencia de órdenes MQ
  B.1.1 hg qapplied”—imprimir los parches aplicados
  B.1.2 hg qcommit”—consignar cambios en la cola del repositorio
  B.1.3 hg qdelete”—eliminar un parche del fichero series
  B.1.4 hg qdiff”—imprimir la diferencia del último parche aplicado
  B.1.5 hg qfold”—fusionar (“integrar”) varios parches en uno solo
  B.1.6 hg qheader”—desplegar el encabezado/descripción de un parche
  B.1.7 hg qimport”—importar el parche de un tercero en la cola
  B.1.8 hg qinit”—preparar un repositorio para trabajar con MQ
  B.1.9 hg qnew”—crear un parche nuevo
  B.1.10 hg qnext”—imprimir el nombre del próximo parche
  B.1.11 hg qpop”—sustraer parches de la pila
  B.1.12 hg qprev”—imprimir el nombre del parche anterior
  B.1.13 hg qpush”—introducir parches a la pila
  B.1.14 hg qrefresh”—actualiza el último parche aplicado
  B.1.15 hg qrename”—renombrar un parche
  B.1.16 hg qrestore”—restaurar el estado almacenado de la cola
  B.1.17 hg qsave”—almacena el estado actual de la cola
  B.1.18 hg qseries”—imprime la serie completa de parches
  B.1.19 hg qtop”—imprime el nombre del parche actual
  B.1.20 hg qunapplied”—imprimir los parches que aún no se han aplicado
  B.1.21 hg strip”—remover una revisión y sus descendientes
 B.2 Referencia de ficheros de MQ
  B.2.1 El fichero series
  B.2.2 El fichero status
C Instalar Mercurial desde las fuentes
 C.1 En un sistema tipo Unix
 C.2 En Windows
D Licencia de Publicación Abierta
 D.1 Requerimientos en versiones modificadas y no modificadas
 D.2 Derechos de reproducción
 D.3 Alcance de la licencia
 D.4 Requerimientos sobre trabajos modificados
 D.5 Recomendaciones de buenas prácticas
 D.6 Opciones de licencia
Bibliografía
Índice alfabético

Índice de figuras

2.1 Historial gráfico del repositorio hello
3.1 Historial reciente divergente de los repositorios my-hello y my-new-hello
3.2 Contenidos del repositorio después de jalar my-hello a my-new-hello
3.3 Directorio de trabajo y repositorio durante la fusión, y consignación consecuente
3.4 Cambios con conflictos a un documento
3.5 Usando kdiff3 para fusionar versiones de un fichero
4.1 Relación entre ficheros en el directorio de trabajo y ficheros de registro en el repositorio
4.2 Relaciones entre metadatos
4.3 Instantánea de un revlog, con deltas incrementales
4.4 
4.5 El directorio de trabajo puede tener dos padres
4.6 El directorio de trabajo obtiene nuevos padres luego de una consignación
4.7 El directorio de trabajo, actualizado a un conjunto de cambios anterior
4.8 Después de una consignación hecha mientras se usaba un conjunto de cambios anterior
4.9 Fusión de dos frentes
5.1 Simular un directorio vacío con un fichero oculto
6.1 Ramas de Características
9.1 Retroceso de un cambio con la orden “hg backout
9.2 Retroceso automatizado de un cambio a algo que no es la punta con la orden “hg backout
9.3 Retroceso usando la orden “hg backout
9.4 Fusión manual de un retroceso
10.1 Un gancho simple que se ejecuta al hacer la consignación de un conjunto de cambios
10.2 Definición de un segundo gancho commit
10.3 Uso del gancho pretxncommit para controlar consignaciones
10.4 Un gancho que prohíbe mensajes de consignación demasiado cortos
10.5 Un gancho simple que revisa si hay espacios en blanco finales
10.6 Un mejor gancho para espacios en blanco finales
11.1 Template keywords in use
11.2 Filtros de plantilla en acción
12.1 Uso sencillo de las órdenes diff y patch
12.2 Líneas a añadir en ĩ/.hgrc para habilitar la extensión MQ
12.3 Cómo verificar que MQ está habilitado
12.4 Preparar un repositorio para usar MQ
12.5 Crear un nuevo parche
12.6 Refrescar un parche
12.7 Refrescar un parche muchas veces para acumular cambios
12.8 Aplicar un parche después del primero
12.9 Entender la pila de parches con “hg qseries” y “hg qapplied
12.10 Parches aplicados y no aplicados en la pila de parches de MQ
12.11 Modificar la pila de parches aplicados
12.12 Pushing all unapplied patches
12.13 Crear un parche a la fuerza
12.14 Uso de las características de etiquetamiento al trabajar con MQ
12.15 Las órdenes diffstat, filterdiff, y lsdiff

Prefacio

El control distribuido de revisiones es un territorio relativamente nuevo, y ha crecido hasta ahora gracias a a la voluntad que tiene la gente de salir y explorar territorios desconocidos.

Estoy escribiendo este libro acerca de control de revisiones distribuido porque creo que es un tema importante que merece una guía de campo. Escogí escribir acerca de Mercurial porque es la herramienta más fácil para explorar el terreno, y sin embargo escala a las demandas de retadores ambientes reales donde muchas otras herramientas de control de revisiones fallan.

0.1. Este libro es un trabajo en progreso

Estoy liberando este libro mientras lo sigo escribiendo, con la esperanza de que pueda ser útil a otros. También espero que los lectores contribuirán como consideren adecuado.

0.2. Acerca de los ejemplos en este libro

Este libro toma un enfoque inusual hacia las muestras de código. Cada ejemplo está “en directo”—cada uno es realmente el resultado de un script de shell que ejecuta los comandos de Mercurial que usted ve. Cada vez que una copia del libro es construida desde su código fuente, todos los scripts de ejemplo son ejecutados automáticamente, y sus resultados actuales son comparados contra los resultados esperados.

La ventaja de este enfoque es que los ejemplos siempre son precisos; ellos describen exactamente el comportamiento de la versión de Mercurial que es mencionada en la portada del libro. Si yo actualizo la versión de Mercurial que estoy documentando, y la salida de algún comando cambia, la construcción falla.

Hay una pequeña desventaja de este enfoque, que las fechas y horas que usted verá en los ejemplos tienden a estar “aplastadas” juntas de una forma que no sería posible si los mismos comandos fueran escritos por un humano. Donde un humano puede emitir no más de un comando cada pocos segundos, con cualquier marca de tiempo resultante correspondientemente separada, mis scripts automatizados de ejemplos ejecutan muchos comandos en un segundo.

Como un ejemplo de esto, varios commits consecutivos en un ejemplo pueden aparecer como habiendo ocurrido durante el mismo segundo. Usted puede ver esto en el ejemplo bisect en la sección 9.5, por ejemplo.

Así que cuando usted lea los ejemplos, no le dé mucha importancia a las fechas o horas que vea en las salidas de los comandos. Pero tenga confianza en que el comportamiento que está viendo es consistente y reproducible.

0.3. Colofón—este libro es Libre

Este libro está licenciado bajo la Licencia de Publicación Abierta, y es producido en su totalidad usando herramientas de Software Libre. Es compuesto con LATEX; las ilustraciones son dibujadas y generadas con http://www.inkscape.org/Inkscape.

El código fuente completo para este libro es publicado como un repositorio Mercurial, en http://hg.serpentine.com/mercurial/book.

Capítulo 1
Introducción

1.1. Acerca del control de revisiones

El control de revisiones es el proceso de administrar diferentes versiones de una pieza de información. En su forma más simple es algo que la mayoría de gente hace a mano: cada vez que usted modifica un fichero, lo graba con un nuevo nombre que contiene un número, cada uno mayor que el anterior.

Administrar manualmente muchas versiones de incluso sólo un fichero es una tarea propensa a errores, a pesar de que hace bastante tiempo hay herramientas que ayudan en este proceso. Las primeras herramientas para automatizar el control de revisiones fueron pensadas para que un usuario administrara un solo fichero. En las décadas pasadas, el alcance de las herramientas de control de revisiones ha ido aumentando considerablemente; ahora manejan muchos ficheros y facilitan el trabajo en conjunto de varias personas. Las mejores herramientas de control de revisiones de la actualidad no tienen problema con miles de personas trabajando en proyectos que consisten de cientos de miles de ficheros.

1.1.1. ¿Por qué usar control de revisiones?

Hay muchas razones por las cuales usted o su equipo desearía usar una herramienta automática de control de revisiones para un proyecto.

La mayoría de estas razones son igualmente válidas —por lo menos en teoría— así esté trabajando en un proyecto solo usted, o con mucha gente.

Algo fundamental acerca de lo práctico de un sistema de control de revisiones en estas dos escalas (“un hacker solitario” y “un equipo gigantesco”) es cómo se comparan los beneficios con los costos. Una herramienta de control de revisiones que sea difícil de entender o usar impondrá un costo alto.

Un proyecto de quinientas personas es muy propenso a colapsar solamente con su peso inmediatamente sin una herramienta y un proceso de control de versiones. En este caso, el costo de usar control de revisiones ni siquiera se tiene en cuenta, puesto que sin él, el fracaso está casi garantizado.

Por otra parte, un “arreglo rápido” de una sola persona, excluiría la necesidad de usar una herramienta de control de revisiones, porque casi seguramente, el costo de usar una estaría cerca del costo del proyecto. ¿No es así?

Mercurial soporta ambas escalas de de desarrollo de manera única. Puede aprender lo básico en pocos minutos, y dado su bajo sobrecosto, puede aplicar el control de revisiones al proyecto más pequeño con facilidad. Su simplicidad significa que no tendrá que preocuparse por conceptos obtusos o secuencias de órdenes compitiendo por espacio mental con lo que sea que realmente esté tratando de hacer. Al mismo tiempo, Mercurial tiene alto desempeño y su naturaleza peer-to-peer le permite escalar indoloramente para manejar grandes proyectos.

Ninguna herramienta de control de revisiones puede salvar un proyecto mal administrado, pero la elección de herramientas puede hacer una gran diferencia en la fluidez con la cual usted puede trabajar en un proyecto.

1.1.2. La cantidad de nombres del control de revisiones

El control de revisiones es un campo amplio, tan amplio que no hay un acrónimo o nombre único. A continuación presentamos un listado de nombres comunes y acrónimos que se podrían encontrar:

Algunas personas aducen que estos términos tienen significados diversos, pero en la práctica se sobreponen tanto que no hay una forma acordada o incluso adecuada de separarlos.

1.2. Historia resumida del control de revisiones

La herramienta de control de revisiones más antigua conocida es SCCS (Sistema de Control de Código), escrito por Marc Rochkind en Bell Labs, a comienzos de los setentas (1970s). SCCS operaba sobre ficheros individuales, y requería que cada persona que trabajara en el proyecto tuviera acceso a un espacio compartido en un solo sistema. Solamente una persona podía modificar un fichero en un momento dado; el arbitramiento del acceso a los ficheros se hacía con candados. Era común que la gente pusiera los candados a los ficheros, y que posteriormente olvidara quitarlos, impidiendo que otro pudiera modificar los ficheros en cuestión sin la intervención del administrador.

Walter Tichy desarrolló una alternativa gratuita a SCCS a comienzos de los ochentas (1980s); llamó a su programa RCS (Sistema de Control de Revisiones). Al igual que SCCS, RCS requería que los desarrolladores trabajaran en un único espacio compartido y colocaran candados a los ficheros para evitar que varias personas los modificaran simultáneamente.

Después en los ochenta, Dick Grune usó RCS como un bloque de construcción para un conjunto de guiones de línea de comando, que inicialmente llamó cmt, pero que renombró a CVS (Sistema Concurrente de Versiones). La gran innovación de CVS era que permitía a los desarrolladores trabajar simultáneamente de una forma más o menos independiente en sus propios espacios de trabajo. Los espacios de trabajo personales impedían que los desarrolladores se pisaran las mangueras todo el tiempo, situación común con SCCS y RCS. Cada desarrollador tenía una copia de todos los ficheros del proyecto y podía modificar sus copias independientemente, Tenían que fusionar sus ediciones antes de consignar los cambios al repositorio central.

Brian Berliner tomó los scripts originales de Grune y los reescribió en C, publicando en 1989 el código sobre el cual se ha desarrollado la versión moderna de CVS. CVS adquirió posteriormente la habilidad de operar sobre una conexión de red, dotándolo de una arquitectura, cliente/servidor. La arquitectura de CVS es centralizada; el historial del proyecto está únicamente en el repositorio central. Los espacios de trabajo de los clientes contienen únicamente copias recientes de las versiones de los ficheros, y pocos metadatos para indicar dónde está el servidor. CVS ha tenido un éxito enorme; Es probablemente el sistema de control de revisiones más extendido del planeta.

A comienzos de los noventa (1990s), Sun MicroSystems desarrollo un temprano sistema distribuido de control de revisiones llamado TeamWare. Un espacio de trabajo TeamWare contiene una copia completa del historial del proyecto. TeamWare no tiene la noción de repositorio central. (CVS se basaba en RCS para el almacenamiento de su historial; TeamWare usaba SCCS.)

A medida que avanzaba la decada de los noventa, se empezó a evidenciar los problemas de CVS. Almacena cambios simultáneos a muchos ficheros de forma individual, en lugar de agruparlos como una operación única y atómica lógicamente. No maneja bien su jerarquía de ficheros; es fácil desordenar un repositorio al renombrar ficheros y directorios. Peor aún, su código fuente es difícil de leer y mantener, lo que hizo que su “umbral de dolor” para arreglar sus problemas arquitecturales fuera algo prohibitivo.

En 2001, Jim Blandy y Karl Fogel, dos desarrolladores que habían trabajado en CVS, comenzaron un proyecto para reemplazarlo con una herramienta con mejor arquitectura y código más limpio. El resultado, Subversion, no se separó del modelo centralizado cliente/servidor de CVS, pero añadió consignaciones atómicas de varios ficheros, mejor manejo de espacios de nombres , y otras características que lo hacen mejor que CVS. Desde su versión inicial, ha ido creciendo en popularidad rápidamente.

Más o menos en forma simultánea Graydon Hoare comenzó a trabajar en un ambicioso sistema distribuido de control de versiones que llamó Monotone. Mientras que Monotone se enfocaba a evitar algunas fallas de diseño de CVS con una arquitectura peer-to-peer, fue mucho más allá de las herramientas anteriores (y posteriores) de control de revisiones en varios aspectos innovadores. Usa hashes criptográficos como identificadores, y tiene una noción integral de “confianza” para código de diversas fuentes.

Mercurial nació en el 2005. Algunos de sus aspectos de de diseño fueron influenciados por Monotone, pero Mercurial se enfoca en la facilidad de uso, gran rendimiento y escalabilidad para proyectos muy grandes.

1.3. Tendencias en el control de revisiones

Ha habido una tendencia inconfundible en el desarrollo y uso de las herramientas de control de revisiones en las cuatro décadas pasadas, mientras la gente se ha hecho familiar con las capacidades de sus herramientas y se ha visto restringida por sus limitaciones.

La primera generación comenzó administrando ficheros individuales en computadores por persona. A pesar de que tales herramientas representaron un avance importante frente al control de revisiones manual, su modelo de candados y la dependencia a un sólo computador los limitó a equipos de trabajo pequeños y acoplados.

La segunda generación dejó atrás esas limitaciones moviéndose a arquitecturas centradas en redes, y administrando proyectos completos a la vez. A medida que los proyectos crecían, nacieron nuevos problemas. Con la necesidad de comunicación frecuente con los servidores, escalar estas máquinas se convirtió en un problema en proyectos realmente grandes. Las redes con poca estabilidad podrían impedir que usuarios remotos se conectaran al servidor. A medida que los proyectos de código abierto comenzaron a ofrecer acceso de sólo lectura de forma anónima a cualquiera, la gente sin permiso para consignar vio que no podían usar tales herramientas para interactuar en un proyecto de forma natural, puesto que no podían guardar sus cambios.

La generación actual de herramientas de control de revisiones es peer-to-peer por naturaleza. Todos estos sistemas han eliminado la dependencia de un único servidor central, y han permitido que la gente distribuya sus datos de control de revisiones donde realmente se necesita. La colaboración a través de Internet ha cambiado las limitantes tecnológicas por la cuestión de elección y consenso. Las herramientas modernas pueden operar sin conexión indefinidamente y autónomamente, necesitando una conexión de red solamente para sincronizar los cambios con otro repositorio.

1.4. Algunas ventajas del control distribuido de revisiones

A pesar de que las herramientas para el control distribuido de revisiones lleva varios años siendo tan robustas y usables como la generación previa de sus contrapartes, algunas personas que usan las herramientas más antiguas no se han percatado de sus ventajas. Hay gran cantidad de situaciones en las cuales las herramientas distribuidas brillan frente a las centralizadas.

Para un desarrollador individual, las herramientas distribuidas casi siempre son más rápidas que las centralizadas. Por una razón sencilla: Una herramienta centralizada necesita comunicarse por red para las operaciones más usuales, debido a que los metadatos se almacenan en una sola copia en el servidor central. Una herramienta distribuida almacena todos sus metadatos localmente. Con todo lo demás de la misma forma, comunicarse por red tiene un sobrecosto en una herramienta centralizada. No subestime el valor de una herramienta de respuesta rápida: Usted empleará mucho tiempo interactuando con su programa de control de revisiones.

Las herramientas distribuidas son indiferentes a los caprichos de su infraestructura de servidores, de nuevo, debido a la replicación de metadatos en tantos lugares. Si usa un sistema centralizado y su servidor explota, ojalá los medios físicos de su copia de seguridad sean confiables, y que su última copia sea reciente y además funcione. Con una herramienta distribuida tiene tantas copias de seguridad disponibles como computadores de contribuidores.

La confiabilidad de su red afectará las herramientas distribuidas de una forma mucho menor que a las herramientas centralizadas. Usted no puede siquiera usar una herramienta centralizada sin conexión de red, excepto por algunas órdenes muy limitadas. Con herramientas distribuidas, si sus conexión cae mientras usted está trabajando, podría nisiquiera darse cuenta. Lo único que que no podrá hacer es comunicarse con repositorios en otros computadores, algo que es relativamente raro comparado con las operaciones locales. Si tiene colaboradores remotos en su equipo, puede ser importante.

1.4.1. Ventajas para proyectos de código abierto

Si descubre un proyecto de código abierto y decide que desea comenzar a trabajar en él, y ese proyecto usa una herramienta de control distribuido de revisiones, usted es de inmediato un par con la gente que se considera el “alma” del proyecto. Si ellos publican sus repositorios, usted puede copiar inmediatamente el historial del proyecto, hacer cambios y guardar su trabajo, usando las mismas herramientas de la misma forma que ellos. En contraste, con una herramienta centralizada, usted debe usar el programa en un modo “sólo lectura” a menos que alguien le otorgue permisos para consignar cambios en el repositorio central. Hasta entonces, no podrá almacenar sus cambios y sus modificaciones locales correrán el riesgo de dañarse cuando trate de actualizar su vista del repositorio.

Las bifurcaciones (forks) no son un problema

Se ha mencionado que las herramientas de control distribuido de versiones albergan un riesgo a los proyectos de código abierto, puesto que se vuelve muy sencillo hacer una “bifurcación”1 del desarrollo del proyecto. Una bifurcación sucede cuando hay diferencias de opinión o actitud entre grupos de desarrolladores que desemboca en la decisión de la imposibilidad de continuar trabajando juntos. Cada parte toma una copia más o menos completa del código fuente del proyecto y toma su propio rumbo.

En algunas ocasiones los líderes de las bifurcaciones reconcilian sus diferencias. Con un sistema centralizado de control de revisiones, el proceso técnico de reconciliarse es doloroso, y se hace de forma muy manual. Usted tiene que decidir qué historial de revisiones va a “ganar”, e injertar los cambios del otro equipo en el árbol de alguna manera. Con esto usualmente se pierde algo o todo del historial de la revisión de alguna de las partes.

Lo que las herramientas distribuidas hacen con respecto a las bifurcaciones, es que las bifurcaciones son la única forma de desarrollar un proyecto. Cada cambio que usted hace es potencialmente un punto de bifurcación. La gran fortaleza de esta aproximación es que las herramientas distribuidas de control de revisiones tiene que ser bueno al fusionar las bifurcaciones, porque las bifurcaciones son absolutamente fundamentales: pasan todo el tiempo.

Si todas las porciones de trabajo que todos hacen, todo el tiempo, se enmarcan en términos de bifurcaciones y fusiones, entonces a aquello a lo que se refiere en el mundo del código abierto a una “bifurcación” se convierte puramente en una cuestión social. Lo que hacen las herramientas distribuidas es disminuir la posibilidad de una bifurcación porque:

Algunas personas se resisten a las herramientas distribuidas porque desean mantener control completo sobre sus proyectos, y creen que las herramientas centralizadas les dan tal control. En todo caso, si este es su parecer, y usted publica sus repositorios de CVS o Subversion, hay muchas herramientas disponibles que pueden obtener el historial completo (aunque sea lentamente) y recrearlo en otro sitio que usted no controla. Siendo así un control ilusorio, puesto que está impidiendo la fluidez de colaboración en lugar de prevenir que alguien se sienta impulsado a obtener una copia y hacer una bifurcación con su historial.

1.4.2. Ventajas para proyectos comerciales

Muchos proyectos comerciales tienen grupos de trabajo distribuidos alrededor del globo. Quienes contribuyen y están lejos de un repositorio central verán una ejecución más lenta de los comandos y tal vez menos confiabilidad. Los sistemas de control de revisión comerciales intentan amortiguar estos problemas con adiciones de replicación remota que usualmente son muy costosos y complicados de administrar. Un sistema distribuido no padece estos problemas. Mejor aún, puede colocar varios servidores autorizados, por ejemplo, uno por sitio, de tal forma que no haya comunicación redundante entre repositorios sobre enlaces de conexión costosos.

Los sistemas de control de revisiones distribuidos tienden a ser poco escalables. No es inusual que costosos sistemas centralizados caigan ante la carga combinada de unas cuantas docenas de usuarios concurrentes. De nuevo, las respuestas típicas de replicación tienden a ser costosas y complejas de instalar y administrar. Dado que la carga en un servidor central—si es que tiene uno—es muchas veces menor con una herramienta distribuida (debido a que los datos están replicados en todas partes), un solo servidor económico puede tratar las necesidades de equipos mucho más grandes, y la replicación para balancear la carga se vuelve cosa de guiones.

Si tiene un empleado en el campo, se beneficiará grandemente de un sistema distribuido de control de versiones al resolver problemas en el sitio del cliente. La herramienta le permitirá generar construcciones a la medida, probar diferentes arreglos de forma independiente y buscar de forma eficiente las fuentes de fallos en el historial y regresiones en los ambientes de los clientes, todo sin necesidad de conectarse al servidor de su compañía.

1.5. ¿Por qué elegir Mercurial?

Mercurial cuenta con un conjunto único de propiedades que lo hacen una elección particularmente buena como sistema de control de revisiones, puesto que:

Si los sistemas de control de revisiones le son familiares, debería estar listo para usar Mercurial en menos de cinco minutos. Si no, sólo va a tomar unos pocos minutos más. Las órdenes de Mercurial y su conjunto de características son uniformes y consistentes generalmente, y basta con que siga unas pocas reglas generales en lugar de un montón de excepciones.

En un proyecto pequeño, usted puede comenzar a trabajar con Mercurial en pocos momentos. Crear nuevos cambios y ramas, transferir cambios (localmente o por la red); y las operaciones relacionadas con el estado y el historial son rápidas. Mercurial buscar ser ligero y no incomodar en su camino combinando poca sobrecarga cognitiva con operaciones asombrosamente rápidas.

La utilidad de Mercurial no se limita a proyectos pequeños: está siendo usado por proyectos con centenas de miles de contribuyentes, cada uno conteniendo decenas de miles de ficheros y centenas de megabytes de código fuente

Si la funcionalidad básica de Mercurial no es suficiente para usted, es muy fácil extenderlo. Mercurial se comporta muy bien con tareas de scripting y su limpieza interna junto con su implementación en Python permiten añadir características fácilmente en forma de extensiones. Hay un buen número de extensiones útiles y populares en este momento, desde ayudar a identificar fallos hasta mejorar su desempeño.

1.6. Comparación de Mercurial con otras herramientas

Antes de leer, por favor tenga en cuenta que esta sección necesariamente refleja mis propias experiencias, intereses y (tengo que decirlo) mis preferencias. He usado cada una de las herramientas de control de versiones listadas a continuación, y en muchos casos por varios años.

1.6.1. Subversion

Subversion es una herramienta de control de revisiones muy popular, desarrollada para reemplazar a CVS. Tiene una arquitectura centralizada tipo cliente/servidor.

Subversion y Mercurial tienen comandos con nombres similares para hacer las mismas operaciones, por lo que si le son familiares en una, será sencillo aprender a usar la otra. Ambas herramientas son portables en todos los sistemas operativos populares.

Antes de la versión 1.5, Subversion no tenía soporte para fusiones. En el momento de la escritura, sus capcidades para llevar cuenta de las funciones son nuevas, http://svnbook.red-bean.com/nightly/en/svn.branchmerge.advanced.htmlsvn.branchmerge.advanced.finalwordcomplicadas y poco estables2.

Mercurial tiene una ventaja considerable en desempeño sobre Subversion en cualquier operación de control de revisiones que yo haya medido. He medido sus ventajas con factores desde dos hasta seis veces comparando con almacenamiento de ficheros ra_local Subversion 1.4.3, el cual es el método de acceso más rápido. En los escenarios más realistas incluyendo almacenamiento con la red de por medio, Subversion se encuentra en desventaja aún mayor. Dado que casi todas las órdenes de Subversion deben tratar con el servidor y Subversion no tiene utilidades de replicación adecuadas, la capacidad del servidor y el ancho de banda se convierten en cuellos de botella para proyectos modestamente grandes.

Adicionalmente, Subversion tiene un sobrecosto considerable en almacenamiento para evitar transacciones por red en algunas operaciones, tales como encontrar ficheros modificados (status) y desplegar información frente a la revisión actual (diff). Como resultado, la copia de trabajo de Subversion termina siendo del mismo tamaño o más grande que un repositorio de Mercurial y el directorio de trabajo, a pesar de que el repositorio de Mercurial contiene el historial completo del proyecto.

Subversion tiene soporte amplio de otras herramientas. Mercurial por ahora está bastante atrás en este aspecto. Esta diferencia está disminuyendo, y algunas de las herramientas GUI3, eclipsan sus equivalentes de Subversion. Al igual que Mercurial, Subversion tiene un excelente manual de usuario.

Dado que Subversion no almacena el historial de revisiones en el cliente, es muy bueno para administrar proyectos que tienen muchos ficheros binarios grandes y opacos. Si consigna cincuenta revisiones de un fichero de 10MB que no es comprimible, el esapacio en el cliente de Subversion se mantendrá constante mientras que el espacio usado por cualquier Sistema Distribuido de Control de Revisiones crecerá rápidamente en proporción con el número de revisiones, debido a que las diferencias entre cada revisión es grande.

Adicionalmente, generalmente es difícil o más bien, imposible mezclar diferentes versiones de un fichero binario. La habilidad de Subversion para permitirle al usuario poner una cerradura a un fichero, de modo que tenga un permiso exclusivo para consignar cambios, puede ser una ventaja significativa en un proyecto donde los ficheros binarios sean usados ampliamente.

Mercurial puede importar el historial de revisiones de un repositorio de Subversion. También puede exportar el historial de revisiones a un repositorio de Subversion. De esta forma es sencillo “dar un vistazo” y usar Mercurial y Subversion en paralelo antes de decidirse a dar el paso. La conversión del historial es incremental, de modo que puede aplicar una conversión inicial, y después conversiones pequeñas y adicionales posteriormente para traer nuevos cambios.

1.6.2. Git

Git es una herramienta distribuida de control de revisiones desarrollada para administrar el arbol del kernel de Linux. Al igual que Mercurial los principios de su diseño fueron influenciados por Monotone.

Git tiene un conjunto de órdenes muy grande; en la versión 1.5.0 ofrece 139 órdenes individuales. Tiene cierta reputación de ser difícil de aprender. Comparado con Git, Mercurial tiene un fuerte enfoque hacia la facilidad.

En términos de rendimiento, Git es extremadamente rápido. En muchos casos, es más rápido que Mercurial, por lo menos en Linux, mientras que Mercurial se comporta mejor en otras operaciones. De todas maneras en Windows, el desempeño y el nivel general de soporte que ofrece Git, al momento de la escritura, está bastante atrás de Mercurial.

Mientras que el repositorio de Mercurial no requiere mantenimiento, el repositorio de Git requiere frecuentes “reempaquetados” de sus metadatos. Sin estos, el desempeño se degrada y el uso de espacio crece rápidamente. Un servidor que contenga repositorios de Git que no sean reempacados rigurosa y frecuentemente requerirá trabajo intenso de disco durante las copias de seguridad, y ha habido situaciones en copias de seguridad diaria que toman más de 24 horas como resultado. Un repositorio recién reempacado de Git es un poco más pequeño que un repositorio de Mercurial, pero un repositorio sin reempacar es varios órdenes de magnitud más grande.

El corazón de Git está escrito en C. Muchas órdenes de Git están implementadas como guiones de línea de comandos o de Perl y la calidad de esos guiones varía ampliamente. He encontrado muchas situaciones en las cuales los guiones no tuvieron en cuenta la presencia de errores que podrían haber sido fatales.

Mercurial puede importar el historial de revisiones de un repositorio de Git.

1.6.3. CVS

CVS es probablemente la herramienta de control de revisiones más ampliamente usada en el planeta. Debido a su edad y su poca pulcritud interna, ha sido ligeramente mantenida en muchos años.

Tiene una arquitectura centralizada cliente/servidor. No agrupa cambios relacionados en consignaciones atómicas, pemitiendo que con facilidad la gente “rompa la construcción”: una persona puede consignar exitósamente parte del cambio y estar bloqueada por la necesidad de una mezcla, forzando a otras personas a ver solamente una porción del trabajo que estaban buscando hacer. Esto afecta también la forma como usted trabaja con el historial del proyecto. Si quiere ver todas las modificaciones que alguien hizo como parte de una tarea, necesitará inspeccionar manualmente las descripciones y las marcas de tiempo de cambio de cada fichero involucrado (esto, si usted saber cuáles eran tales ficheros).

CVS tiene una noción confusa de etiquetas y ramas que yo no trataría incluso de describir. No soporta renombramiento de ficheros o directorios adecuadamente, facilitando el corromper un repositorio. Casi no tiene chequeo de consistencia interna, por lo tanto es casi imposible identificar por que o cómo se corrompió un repositorio. Yo no recomendaría un repositorio de CVS para proyecto alguno, ni existente ni nuevo.

Mercurial puede importar el historial de revisiones de CVS. De todas maneras hay ciertas precauciones que aplican; las cuales también son necesarias para cualquier herramienta importadora de historial de CVS. Debido a la falta de atomicidad de cambios y el no versionamiento de la jerarquía del sistema de ficheros, es imposible reconstruir completamente el historial de CVS con precisión; hay cierto trabajo de conjetura involucrado y los renombramientos tampoco se mostrarán. Debido a que gran parte de la administración avanzada de CVS tiene que hacerse manualmente y por lo tanto es proclive al error, es común que los importadores de CVS encuentren muchos problemas con repositorios corruptos (marcas de tiempo totalmente desubicadas y ficheros que han permanecido con candados por más de una década son dos de los problemas menos interesantes de los que puedo retomar de mi experiencia personal).

Mercurial puede importar el historial de revisiones de un repositorio CVS.

1.6.4. Herramientas comerciales

Perforce tiene una arquitectura centralizada cliente/servidor sin almacenamiento de dato alguno de caché en el lado del cliente. A diferencia de las herramientas modernas de control de revisiones, Perforce requiere que un usuario ejecute un comando para informar al servidor acerca de todo fichero que se vaya a editar.

El rendimiento de Perforce es muy bueno para equipos pequeños, pero se degrada rápidamente cuando el número de usuarios va más allá de pocas docenas. Instalaciones modestamente grandes de Perforce requiere la organización de proxies para soportar la carga que sus usuarios generan.

1.6.5. Elegir una herramienta de control de revisiones

Con la excepción de CVS, toda las herramientas que se han listado anteriormente tienen fortalezas únicas que las hacen valiosas de acuerdo al tipo de trabajo. No hay una única herramienta de control de revisiones que sea la mejor en todas las situaciones.

Por ejemplo, Subversion es una buena elección para trabajar con edición frecuente de ficheros binarios, debido a su naturaleza centralizada y soporte para poner candados a ficheros.

Personalmente encuentro las propiedades de simplicidad, desempeño, y buen soporte de fusiones de Mercurial una combinación llamativa que ha dado buenos frutos por varios años.

1.7. Migrar de otra herramienta hacia Mercurial

Mercurial viene con una extensión llamada convert, que puede importar historiales de revisiones de forma incremental desde varias herramientas de control de revisiones. Por “incremental”, quiero decir que puede migrar toda el historial de un proyecto en una primera instancia y después volver a ejecutar la migración posteriormente para obtener los nuevos cambios que han sucedido después de la migración inicial.

A continuación presentamos las herramientas de revisiones que soporta el comando convert:

Adicionalmente, convert puede exportar cambios de Mercurial hacia Subversion. Lo que hace posible probar Subversion y Mercurial en paralelo antes de lanzarse a un migración total, sin arriesgarse a perder trabajo alguno.

El comando hg convert” es sencillo de usar. Basta con apuntarlo hacia la ruta o el URL del repositorio fuente, opcionalmente darle el nombre del nombre del repositorio destino y comenzará a hacer su trabajo. Después de la conversión inicial, basta con invocar de nuevo el comando para importar cambios nuevos.

Capítulo 2
Una gira de Mercurial: lo básico

2.1. Instalar Mercurial en su sistema

Hay paquetes binarios precompilados de Mercurial disponibles para cada sistema operativo popular. Esto hace fácil empezar a usar Mercurial en su computador inmediatamente.

2.1.1. Linux

Dado que cada distribución de Linux tiene sus propias herramientas de manejo de paquetes, políticas, y ritmos de desarrollo, es difícil dar un conjunto exhaustivo de instrucciones sobre cómo instalar el paquete de Mercurial. La versión de Mercurial que usted tenga a disposición puede variar dependiendo de qué tan activa sea la persona que mantiene el paquete para su distribución.

Para mantener las cosas simples, me enfocaré en instalar Mercurial desde la línea de comandos en las distribuciones de Linux más populares. La mayoría de estas distribuciones proveen administradores de paquetes gráficos que le permitirán instalar Mercurial con un solo clic; el nombre de paquete a buscar es mercurial.

2.1.2. Solaris

SunFreeWare, en http://www.sunfreeware.com, es una buena fuente para un gran número de paquetes compilados para Solaris para las arquitecturas Intel y Sparc de 32 y 64 bits, incluyendo versiones actuales de Mercurial.

2.1.3. Mac OS X

Lee Cantey publica un instalador de Mercurial para Mac OS X en http://mercurial.berkwood.com. Este paquete funciona en tanto en Macs basados en Intel como basados en PowerPC. Antes de que pueda usarlo, usted debe instalar una versión compatible de Universal MacPython [BI]. Esto es fácil de hacer; simplemente siga las instrucciones del sitio de Lee.

También es posible instalar Mercurial usando Fink o MacPorts, dos administradores de paquetes gratuitos y populares para Mac OS X. Si usted tiene Fink, use sudo apt-get install mercurial-py25. Si usa MacPorts, sudo port install mercurial.

2.1.4. Windows

Lee Cantey publica un instalador de Mercurial para Windows en http://mercurial.berkwood.com. Este paquete no tiene dependencias externas; “simplemente funciona”.

Nota: La versión de Windows de Mercurial no convierte automáticamente los fines de línea entre estilos Windows y Unix. Si usted desea compartir trabajo con usuarios de Unix, deberá hacer un trabajo adicional de configuración. XXX Terminar esto.

2.2. Arrancando

Para empezar, usaremos el comando hg version” para revisar si Mercurial está instalado adecuadamente. La información de la versión que es impresa no es tan importante; lo que nos importa es si imprime algo en absoluto.

1  $ hg version
2  Mercurial Distributed SCM (version 1.0.1)
3  
4  Copyright (C) 2005-2008 Matt Mackall <mpm@selenic.com> and others
5  This is free software; see the source for copying conditions. There is NO
6  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

2.2.1. Ayuda integrada

Mercurial provee un sistema de ayuda integrada. Esto es invaluable para ésas ocasiones en la que usted está atorado tratando de recordar cómo ejecutar un comando. Si está completamente atorado, simplemente ejecute hg help”; esto imprimirá una breve lista de comandos, junto con una descripción de qué hace cada uno. Si usted solicita ayuda sobre un comando específico (como abajo), se imprime información más detallada.

1  $ hg help init
2  hg init [-e CMD] [--remotecmd CMD] [DEST]
3  
4  create a new repository in the given directory
5  
6      Initialize a new repository in the given directory.  If the given
7      directory does not exist, it is created.
8  
9      If no directory is given, the current directory is used.
10  
11      It is possible to specify an ssh:// URL as the destination.
12      Look at the help text for the pull command for important details
13      about ssh:// URLs.
14  
15  options:
16  
17   -e --ssh        specify ssh command to use
18      --remotecmd  specify hg command to run on the remote side
19  
20  use "hg -v help init" to show global options

Para un nivel más impresionante de detalle (que usted no va a necesitar usualmente) ejecute hg help -v”. La opción -v es la abreviación para --verbose, y le indica a Mercurial que imprima más información de lo que haría usualmente.

2.3. Trabajar con un repositorio

En Mercurial, todo sucede dentro de un repositorio. El repositorio para un proyecto contiene todos los ficheros que “pertenecen a” ése proyecto, junto con un registro histórico de los ficheros de ese proyecto.

No hay nada particularmente mágico acerca de un repositorio; es simplemente un árbol de directorios en su sistema de ficheros que Mercurial trata como especial. Usted puede renombrar o borrar un repositorio en el momento que lo desee, usando bien sea la línea de comandos o su explorador de ficheros.

2.3.1. Hacer una copia local de un repositorio

Copiar un repositorio es sólo ligeramente especial. Aunque usted podría usar un programa normal de copia de ficheros para hacer una copia del repositorio, es mejor usar el comando integrado que Mercurial ofrece. Este comando se llama hg clone1, porque crea una copia idéntica de un repositorio existente.

1  $ hg clone http://hg.serpentine.com/tutorial/hello
2  destination directory: hello
3  requesting all changes
4  adding changesets
5  adding manifests
6  adding file changes
7  added 5 changesets with 5 changes to 2 files
8  updating working directory
9  2 files updated, 0 files merged, 0 files removed, 0 files unresolved

Si nuestro clonado tiene éxito, deberíamos tener un directorio local llamado hello. Este directorio contendrá algunos ficheros.

1  $ ls -l
2  total 0
3  drwxr-xr-x 3 jerojasro jerojasro 120 Feb 10 18:23 hello
4  $ ls hello
5  Makefile  hello.c

Estos ficheros tienen el mismo contenido e historial en nuestro repositorio y en el repositorio que clonamos.

Cada repositorio Mercurial está completo, es autocontenido e independiente. Contiene su propia copia de los ficheros y el historial de un proyecto. Un repositorio clonado recuerda la ubicación de la que fue clonado, pero no se comunica con ese repositorio, ni con ningún otro, a menos que usted le indique que lo haga.

Lo que esto significa por ahora es que somos libres de experimentar con nuestro repositorio, con la tranquilidad de saber que es una “caja de arena” privada que no afectará a nadie más.

2.3.2. Qué hay en un repositorio?

Cuando miramos en detalle dentro de un repositorio, podemos ver que contiene un directorio llamado .hg. Aquí es donde Mercurial mantiene todos los metadatos del repositorio.

1  $ cd hello
2  $ ls -a
3  .  ..  .hg  Makefile  hello.c

Los contenidos del directorio .hg y sus subdirectorios son exclusivos de Mercurial. Usted es libre de hacer lo que desee con cualquier otro fichero o directorio en el repositorio.

Para introducir algo de terminología, el directorio .hg es el repositorio “real”, y todos los ficheros y directorios que coexisten con él están en el directorio de trabajo. Una forma sencilla de recordar esta distinción es que el repositorio contiene el historial de su proyecto, mientras que el directorio de trabajo contiene una instantánea de su proyecto en un punto particular del historial.

2.4. Vistazo rápido al historial

Una de las primeras cosas que se desea hacer con un repositorio nuevo, poco conocido, es conocer su historial. El comando hg log” nos permite ver el mismo.

1  $ hg log
2  changeset:   4:2278160e78d4
3  tag:         tip
4  user:        Bryan O'Sullivan <bos@serpentine.com>
5  date:        Sat Aug 16 22:16:53 2008 +0200
6  summary:     Trim comments.
7  
8  changeset:   3:0272e0d5a517
9  user:        Bryan O'Sullivan <bos@serpentine.com>
10  date:        Sat Aug 16 22:08:02 2008 +0200
11  summary:     Get make to generate the final binary from a .o file.
12  
13  changeset:   2:fef857204a0c
14  user:        Bryan O'Sullivan <bos@serpentine.com>
15  date:        Sat Aug 16 22:05:04 2008 +0200
16  summary:     Introduce a typo into hello.c.
17  
18  changeset:   1:82e55d328c8c
19  user:        mpm@selenic.com
20  date:        Fri Aug 26 01:21:28 2005 -0700
21  summary:     Create a makefile
22  
23  changeset:   0:0a04b987be5a
24  user:        mpm@selenic.com
25  date:        Fri Aug 26 01:20:50 2005 -0700
26  summary:     Create a standard "hello, world" program
27  

Por defecto este programa imprime un párrafo breve por cada cambio al proyecto que haya sido grabado. Dentro de la terminología de Mercurial, cada uno de estos eventos es llamado conjunto de cambios, porque pueden contener un registro de cambios a varios ficheros.

Los campos de la salida de hg log” son los siguientes.

El texto impreso por hg log” es sólo un sumario; omite una gran cantidad de detalles.

La figura 2.1 es una representación gráfica del historial del repositorio hello, para hacer más fácil ver en qué dirección está “fluyendo” el historial. Volveremos a esto varias veces en este capítulo y en los siguientes.


PIC

Figura 2.1: Historial gráfico del repositorio hello

2.4.1. Conjuntos de cambios, revisiones, y comunicándose con otras personas

Ya que el inglés es un lenguaje notablemente desordenado, y el área de ciencias de la computación tiene una notable historia de confusión de términos (porqué usar sólo un término cuando cuatro pueden servir?), el control de revisiones tiene una variedad de frases y palabras que tienen el mismo significado. Si usted habla acerca del historial de Mercurial con alguien, encontrará que la expresión “conjunto de cambios” es abreviada a menudo como “cambio” o (por escrito) “cset”6, y algunas veces un se hace referencia a un conjunto de cambios como una “revisión” o “rev”7.

Si bien no es relevante qué palabra use usted para referirse al concepto “conjunto de cambios”, el identificador que usted use para referise a “un conjunto de cambios particular” es muy importante. Recuerde que el campo changeset en la salida de hg log” identifica un conjunto de cambios usando tanto un número como una cadena hexadecimal.

La diferencia es importante. Si usted le envía a alguien un correo electrónico hablando acerca de la “revisión 33”, hay una probabilidad alta de que la revisión 33 de esa persona no sea la misma suya. Esto sucede porque el número de revisión depende del orden en que llegan los cambios al repositorio, y no hay ninguna garantía de que los mismos cambios llegarán en el mismo orden en diferentes repositorios. Tres cambios dados a,b,c pueden aparecer en un repositorio como 0,1,2, mientras que en otro aparecen como 1,0,2.

Mercurial usa los números de revisión simplemente como una abreviación conveniente. Si usted necesita hablar con alguien acerca de un conjunto de cambios, o llevar el registro de un conjunto de cambios por alguna otra razón (por ejemplo, en un reporte de fallo), use el identificador hexadecimal.

2.4.2. Ver revisiones específicas

Para reducir la salida de hg log” a una sola revisión, use la opción -r (o --rev). Puede usar un número de revisión o un identificador hexadecimal de conjunto de cambios, y puede pasar tantas revisiones como desee.

1  $ hg log -r 3
2  changeset:   3:0272e0d5a517
3  user:        Bryan O'Sullivan <bos@serpentine.com>
4  date:        Sat Aug 16 22:08:02 2008 +0200
5  summary:     Get make to generate the final binary from a .o file.
6  
7  $ hg log -r 0272e0d5a517
8  changeset:   3:0272e0d5a517
9  user:        Bryan O'Sullivan <bos@serpentine.com>
10  date:        Sat Aug 16 22:08:02 2008 +0200
11  summary:     Get make to generate the final binary from a .o file.
12  
13  $ hg log -r 1 -r 4
14  changeset:   1:82e55d328c8c
15  user:        mpm@selenic.com
16  date:        Fri Aug 26 01:21:28 2005 -0700
17  summary:     Create a makefile
18  
19  changeset:   4:2278160e78d4
20  tag:         tip
21  user:        Bryan O'Sullivan <bos@serpentine.com>
22  date:        Sat Aug 16 22:16:53 2008 +0200
23  summary:     Trim comments.
24  

Si desea ver el historial de varias revisiones sin tener que mencionar cada una de ellas, puede usar la notación de rango; esto le permite expresar el concepto “quiero ver todas las revisiones entre a y b, inclusive”.

1  $ hg log -r 2:4
2  changeset:   2:fef857204a0c
3  user:        Bryan O'Sullivan <bos@serpentine.com>
4  date:        Sat Aug 16 22:05:04 2008 +0200
5  summary:     Introduce a typo into hello.c.
6  
7  changeset:   3:0272e0d5a517
8  user:        Bryan O'Sullivan <bos@serpentine.com>
9  date:        Sat Aug 16 22:08:02 2008 +0200
10  summary:     Get make to generate the final binary from a .o file.
11  
12  changeset:   4:2278160e78d4
13  tag:         tip
14  user:        Bryan O'Sullivan <bos@serpentine.com>
15  date:        Sat Aug 16 22:16:53 2008 +0200
16  summary:     Trim comments.
17  

Mercurial también respeta el orden en que usted especifica las revisiones, así que hg log -r 2:4” muestra 2,3,4 mientras que hg log -r 4:2” muestra 4,3,2.

2.4.3. Información más detallada

Aunque la información presentada por hg log” es útil si usted sabe de antemano qué está buscando, puede que necesite ver una descripción completa del cambio, o una lista de los ficheros que cambiaron, si está tratando de averiguar si un conjunto de cambios dado es el que usted está buscando. La opción -v (or --verbose) del comando hg log” le da este nivel extra de detalle.

1  $ hg log -v -r 3
2  changeset:   3:0272e0d5a517
3  user:        Bryan O'Sullivan <bos@serpentine.com>
4  date:        Sat Aug 16 22:08:02 2008 +0200
5  files:       Makefile
6  description:
7  Get make to generate the final binary from a .o file.
8  
9  

Si desea ver tanto la descripción como el contenido de un cambio, añada la opción -p (o --patch). Esto muestra el contenido de un cambio como un diff unificado (si usted nunca ha visto un diff unificado antes, vea la sección 12.4 para un vistazo global).

1  $ hg log -v -p -r 2
2  changeset:   2:fef857204a0c
3  user:        Bryan O'Sullivan <bos@serpentine.com>
4  date:        Sat Aug 16 22:05:04 2008 +0200
5  files:       hello.c
6  description:
7  Introduce a typo into hello.c.
8  
9  
10  diff -r 82e55d328c8c -r fef857204a0c hello.c
11  --- a/hello.c Fri Aug 26 01:21:28 2005 -0700
12  +++ b/hello.c Sat Aug 16 22:05:04 2008 +0200
13  @@ -11,6 +11,6 @@
14  
15   int main(int argc, char ⋆⋆argv)
16   {
17  - printf("hello, world!n");
18  + printf("hello, world!");
19    return 0;
20   }
21  

2.5. Todo acerca de las opciones para comandos

Tomemos un breve descanso de la tarea de explorar los comandos de Mercurial para hablar de un patrón en la manera en que trabajan; será útil tener esto en mente a medida que avanza nuestra gira.

Mercurial tiene un enfoque directo y consistente en el manejo de las opciones que usted le puede pasar a los comandos. Se siguen las convenciones para opciones que son comunes en sistemas Linux y Unix modernos.

En los ejemplos en este libro, uso las opciones cortas en vez de las largas. Esto sólo muestra mis preferencias, así que no le dé significado especial a eso.

Muchos de los comandos que generan salida de algún tipo mostrarán más salida cuando se les pase la opción -v (o --verbose8), y menos cuando se les pase la opción -q (o --quiet9).

2.6. Hacer y repasar cambios

Ahora que tenemos una comprensión adecuada sobre cómo revisar el historial en Mercurial, hagamos algunos cambios y veamos cómo examinarlos.

Lo primero que haremos será aislar nuestro experimento en un repositorio propio. Usaremos el comando hg clone”, pero no hace falta clonar una copia del repositorio remoto. Como ya contamos con una copia local del mismo, podemos clonar esa. Esto es mucho más rápido que clonar a través de la red, y en la mayoría de los casos clonar un repositorio local usa menos espacio en disco también.

1  $ cd ..
2  $ hg clone hello my-hello
3  updating working directory
4  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
5  $ cd my-hello

A manera de recomendación, es considerado buena práctica mantener una copia “prístina” de un repositorio remoto a mano, del cual usted puede hacer clones temporales para crear cajas de arena para cada tarea en la que desee trabajar. Esto le permite trabajar en múltiples tareas en paralelo, teniendo cada una de ellas aislada de las otras hasta que estén completas y usted esté listo para integrar los cambios de vuelta. Como los clones locales son tan baratos, clonar y destruir repositorios no consume demasiados recursos, lo que facilita hacerlo en cualquier momento.

En nuestro repositorio my-hello, hay un fichero hello.c que contiene el clásico programa “hello, world”10. Usaremos el clásico y venerado comando sed para editar este fichero y hacer que imprima una segunda línea de salida. (Estoy usando el comando sed para hacer esto sólo porque es fácil escribir un ejemplo automatizado con él. Dado que usted no tiene esta restricción, probablemente no querrá usar sed; use su editor de texto preferido para hacer lo mismo).

1  $ sed -i '/printf/a∖∖tprintf("hello again!∖∖n");' hello.c

El comando hg status” de Mercurial nos dice lo que Mercurial sabe acerca de los ficheros en el repositorio.

1  $ ls
2  Makefile  hello.c
3  $ hg status
4  M hello.c

El comando hg status” no imprime nada para algunos ficheros, sólo una línea empezando con “M” para el fichero hello.c. A menos que usted lo indique explícitamente, hg status” no imprimirá nada respecto a los ficheros que no han sido modificados.

La “M” indica que Mercurial se dio cuenta de que nosotros modificamos hello.c. No tuvimos que decirle a Mercurial que íbamos a modificar ese fichero antes de hacerlo, o que lo modificamos una vez terminamos de hacerlo; él fue capaz de darse cuenta de esto por sí mismo.

Es algo útil saber que hemos modificado el fichero hello.c, pero preferiríamos saber exactamente qué cambios hicimos. Para averiguar esto, usamos el comando hg diff”.

1  $ hg diff
2  diff -r 2278160e78d4 hello.c
3  --- a/hello.c Sat Aug 16 22:16:53 2008 +0200
4  +++ b/hello.c Tue Feb 10 18:23:34 2009 +0000
5  @@ -8,5 +8,6 @@
6   int main(int argc, char ⋆⋆argv)
7   {
8    printf("hello, world!");
9  + printf("hello again!n");
10    return 0;
11   }

2.7. Grabar cambios en un nuevo conjunto de cambios

Podemos modificar, compilar y probar nuestros cambios, y usar hg status” y hg diff” para revisar los mismos, hasta que estemos satisfechos con los resultados y lleguemos a un momento en el que sea natural que querramos guardar nuestro trabajo en un nuevo conjunto de cambios.

El comando hg commit” nos permite crear un nuevo conjunto de cambios. Nos referiremos usualmente a esto como “hacer una consigna” o consignar.

2.7.1. Definir un nombre de usuario

Cuando usted trata de ejecutar hg commit11 por primera vez, no está garantizado que lo logre. Mercurial registra su nombre y dirección en cada cambio que usted consigna, para que más adelante otros puedan saber quién es el responsable de cada cambio. Mercurial trata de encontrar un nombre de usuario adecuado con el cual registrar la consignación. Se intenta con cada uno de los siguientes métodos, en el orden presentado.

  1. Si usted pasa la opción -u al comando hg commit” en la línea de comandos, seguido de un nombre de usuario, se le da a esto la máxima precedencia.
  2. A continuación se revisa si usted ha definido la variable de entorno HGUSER.
  3. Si usted crea un fichero en su directorio personal llamado .hgrc, con una entrada username, se usa luego. Para revisar cómo debe verse este fichero, refiérase a la sección 2.7.1 más abajo.
  4. Si usted ha definido la variable de entorno EMAIL, será usada a continuación.
  5. Mercurial le pedirá a su sistema buscar su nombre de usuario local, y el nombre de máquina, y construirá un nombre de usuario a partir de estos componentes. Ya que esto generalmente termina generando un nombre de usuario no muy útil, se imprimirá una advertencia si es necesario hacerlo.

Si todos estos procedimientos fallan, Mercurial fallará, e imprimirá un mensaje de error. En este caso, no le permitirá hacer la consignación hasta que usted defina un nombre de usuario.

Trate de ver la variable de entorno HGUSER y la opción -u del comando hg commit” como formas de hacer caso omiso de la selección de nombre de usuario que Mercurial hace normalmente. Para uso normal, la manera más simple y sencilla de definir un nombre de usuario para usted es crear un fichero .hgrc; los detalles se encuentran más adelante.

Crear el fichero de configuración de Mercurial

Para definir un nombre de usuario, use su editor de texto favorito para crear un fichero llamado .hgrc en su directorio personal. Mercurial usará este fichero para obtener las configuraciones personalizadas que usted haya hecho. El contenido inicial de su fichero .hgrc debería verse así.

1  # Este es un fichero de configuración de Mercurial.
2  [ui]
3  username = Primernombre Apellido <correo.electronico@dominio.net>

La línea “[ui]” define una section del fichero de configuración, así que usted puede leer la línea “username = ...” como “defina el valor del elemento username en la sección ui”. Una sección continua hasta que empieza otra nueva, o se llega al final del fichero. Mercurial ignora las líneas vacías y considera cualquier texto desde el caracter “#” hasta el final de la línea como un comentario.

Escoger un nombre de usuario

Usted puede usar el texto que desee como el valor del campo de configuración username, ya que esta información será leída por otras personas, e interpretada por Mercurial. La convención que sigue la mayoría de la gente es usar su nombre y dirección de correo, como en el ejemplo anterior.

Nota: El servidor web integrado de Mercurial ofusca las direcciones de correo, para dificultar la tarea de las herramientas de recolección de direcciones de correo que usan los spammersa. Esto reduce la probabilidad de que usted empiece a recibir más correo basura si publica un repositorio en la red.

2.7.2. Escribir un mensaje de consignación

Cuando consignamos un cambio, Mercurial nos ubica dentro de un editor de texto, para ingresar un mensaje que describa las modificaciones que hemos introducido en este conjunto de cambios. Esto es conocido como un mensaje de consignación. Será un registro de lo que hicimos y porqué lo hicimos, y será impreso por hg log” una vez hayamos hecho la consignación.

1  $ hg commit

El editor en que hg commit” nos ubica contendrá una línea vacía, seguida de varias líneas que empiezan con la cadena “HG:”.

1  línea vacía
2  HG: changed hello.c

Mercurial ignora las líneas que empiezan con “HG:”; sólo las usa para indicarnos para cuáles ficheros está registrando los cambios. Modificar o borrar estas líneas no tiene ningún efecto.

2.7.3. Escribir un buen mensaje de consignación

Ya que por defecto hg log” sólo muestra la primera línea de un mensaje de consignación, lo mejor es escribir un mensaje cuya primera línea tenga significado por sí misma. A continuación se encuentra un ejemplo de un mensaje de consignación que no sigue esta pauta, y debido a ello tiene un sumario que no es legible.

1  changeset:   73:584af0e231be
2  user:        Persona Censurada <persona.censurada@ejemplo.org>
3  date:        Tue Sep 26 21:37:07 2006 -0700
4  summary:     se incluye buildmeister/commondefs.   Añade un módulo

Con respecto al resto del contenido del mensaje de consignación, no hay reglas estrictas-y-rápidas. Mercurial no interpreta ni le da importancia a los contenidos del mensaje de consignación, aunque es posible que su proyecto tenga políticas que definan una manera particular de escribirlo.

Mi preferencia personal es usar mensajes de consignación cortos pero informativos, que me digan algo que no puedo inferir con una mirada rápida a la salida de hg log --patch”.

2.7.4. Cancelar una consignación

Si usted decide que no desea hacer la consignación mientras está editando el mensaje de la misma, simplemente cierre su editor sin guardar los cambios al fichero que está editando. Esto hará que no pase nada ni en el repositorio ni en el directorio de trabajo.

Si ejecutamos el comando hg commit” sin ningún argumento, se registran todos los cambios que hemos hecho, como lo indican hg status” y hg diff”.

2.7.5. Admirar nuestro trabajo

Una vez hemos terminado la consignación, podemos usar el comando hg tip12 para mostrar el conjunto de cambios que acabamos de crear. La salida de este comando es idéntica a la de hg log”, pero sólo muestra la revisión más reciente en el repositorio.

1  $ hg tip -vp
2  changeset:   5:fccff93807a3
3  tag:         tip
4  user:        Bryan O'Sullivan <bos@serpentine.com>
5  date:        Tue Feb 10 18:23:34 2009 +0000
6  files:       hello.c
7  description:
8  Added an extra line of output
9  
10  
11  diff -r 2278160e78d4 -r fccff93807a3 hello.c
12  --- a/hello.c Sat Aug 16 22:16:53 2008 +0200
13  +++ b/hello.c Tue Feb 10 18:23:34 2009 +0000
14  @@ -8,5 +8,6 @@
15   int main(int argc, char ⋆⋆argv)
16   {
17    printf("hello, world!");
18  + printf("hello again!n");
19    return 0;
20   }
21  

Nos referimos a la revisión más reciente en el repositorio como la revisión de punta, o simplemente la punta.

2.8. Compartir cambios

Anteriormente mencionamos que los repositorios en Mercurial están auto contenidos. Esto quiere decir que el conjunto de cambios que acabamos de crear sólo existe en nuestro repositorio my-hello. Veamos unas cuantas formas de propagar este cambio a otros repositorios.

2.8.1. Jalar cambios desde otro repositorio

Para empezar, clonemos nuestro repositorio hello original, el cual no contiene el cambio que acabamos de consignar. Llamaremos a este repositorio temporal hello-pull.

1  $ cd ..
2  $ hg clone hello hello-pull
3  updating working directory
4  2 files updated, 0 files merged, 0 files removed, 0 files unresolved

Usaremos el comando hg pull” para traer los cambios de my-hello y ponerlos en hello-pull. Sin embargo, traer cambios desconocidos y aplicarlos en un repositorio es una perspectiva que asusta al menos un poco. Mercurial cuenta con el comando hg incoming13 para decirnos qué cambios jalaría el comando hg pull” al repositorio, sin jalarlos.

1  $ cd hello-pull
2  $ hg incoming ../my-hello
3  comparing with ../my-hello
4  searching for changes
5  changeset:   5:fccff93807a3
6  tag:         tip
7  user:        Bryan O'Sullivan <bos@serpentine.com>
8  date:        Tue Feb 10 18:23:34 2009 +0000
9  summary:     Added an extra line of output
10  

(Por supuesto, alguien podría enviar más conjuntos de cambios al repositorio en el tiempo que pasa entre la ejecución de hg incoming” y la ejecución de hg pull” para jalar los cambios, así que es posible que terminemos jalando cambios que no esperábamos.)

Traer cambios al repositorio simplemente es cuestión de ejecutar el comando hg pull”, indicándole de qué repositorio debe jalarlos.

1  $ hg tip
2  changeset:   4:2278160e78d4
3  tag:         tip
4  user:        Bryan O'Sullivan <bos@serpentine.com>
5  date:        Sat Aug 16 22:16:53 2008 +0200
6  summary:     Trim comments.
7  
8  $ hg pull ../my-hello
9  pulling from ../my-hello
10  searching for changes
11  adding changesets
12  adding manifests
13  adding file changes
14  added 1 changesets with 1 changes to 1 files
15  (run 'hg update' to get a working copy)
16  $ hg tip
17  changeset:   5:fccff93807a3
18  tag:         tip
19  user:        Bryan O'Sullivan <bos@serpentine.com>
20  date:        Tue Feb 10 18:23:34 2009 +0000
21  summary:     Added an extra line of output
22  

Como puede verse por las salidas antes-y-después de hg tip”, hemos jalado exitosamente los cambios en nuestro repositorio. Aún falta un paso para que podamos ver estos cambios en nuestro directorio de trabajo.

2.8.2. Actualizar el directorio de trabajo

Hasta ahora hemos pasado por alto la relación entre un repositorio y su directorio de trabajo. El comando hg pull” que ejecutamos en la sección 2.8.1 trajo los cambios al repositorio, pero si revisamos, no hay rastro de esos cambios en el directorio de trabajo. Esto pasa porque hg pull” (por defecto) no modifica el directorio de trabajo. En vez de eso, usamos el comando hg update14 para hacerlo.

1  $ grep printf hello.c
2   printf("hello, world!");
3  $ hg update tip
4  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
5  $ grep printf hello.c
6   printf("hello, world!");
7   printf("hello again!n");

Puede parecer algo raro que hg pull” no actualice el directorio de trabajo automáticamente. De hecho, hay una buena razón para esto: usted puede usar hg update” para actualizar el directorio de trabajo al estado en que se encontraba en cualquier revisión del historial del repositorio. Si usted hubiera actualizado el directorio de trabajo a una revisión anterior—digamos, para buscar el origen de un fallo—y hubiera corrido un hg pull” que hubiera actualizado el directorio de trabajo automáticamente a la nueva revisión, puede que no estuviera particularmente contento.

Sin embargo, como jalar-y-actualizar es una secuencia de operaciones muy común, Mercurial le permite combinarlas al pasar la opción -u a hg pull”.

1  hg pull -u

Si mira de vuelta la salida de hg pull” en la sección 2.8.1 cuando lo ejecutamos sin la opción -u, verá que el comando imprimió un amable recordatorio de que tenemos que encargarnos explícitamente de actualizar el directorio de trabajo:

1  (run 'hg update' to get a working copy)

Para averiguar en qué revisión se encuentra el directorio de trabajo, use el comando hg parents”.

1  $ hg parents
2  changeset:   5:fccff93807a3
3  tag:         tip
4  user:        Bryan O'Sullivan <bos@serpentine.com>
5  date:        Tue Feb 10 18:23:34 2009 +0000
6  summary:     Added an extra line of output
7  

Si mira de nuevo la figura 2.1, verá flechas conectando cada conjunto de cambios. En cada caso, el nodo del que la flecha sale es un padre, y el nodo al que la flecha llega es su hijo. El directorio de trabajo tiene un padre exactamente de la misma manera; ése es el conjunto de cambios que contiene actualmente el directorio de trabajo.

Para actualizar el conjunto de trabajo a una revisión particular, pase un número de revisión o un ID de conjunto de cambios al comando hg update”.

1  $ hg update 2
2  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
3  $ hg parents
4  changeset:   2:fef857204a0c
5  user:        Bryan O'Sullivan <bos@serpentine.com>
6  date:        Sat Aug 16 22:05:04 2008 +0200
7  summary:     Introduce a typo into hello.c.
8  
9  $ hg update
10  2 files updated, 0 files merged, 0 files removed, 0 files unresolved

Si no indica explícitamente una revisión, hg update” actualizará hasta la revisión de punta, como se vio en la segunda llamada a hg update” en el ejemplo anterior.

2.8.3. Empujar cambios a otro repositorio

Mercurial nos permite empujar cambios a otro repositorio, desde el repositorio que estemos usando actualmente. De la misma forma que en el ejemplo de hg pull” arriba, crearemos un repositorio temporal para empujar allí nuestros cambios.

1  $ cd ..
2  $ hg clone hello hello-push
3  updating working directory
4  2 files updated, 0 files merged, 0 files removed, 0 files unresolved

El comando hg outgoing15 nos dice qué cambios serían empujados en el otro repositorio.

1  $ cd my-hello
2  $ hg outgoing ../hello-push
3  comparing with ../hello-push
4  searching for changes
5  changeset:   5:fccff93807a3
6  tag:         tip
7  user:        Bryan O'Sullivan <bos@serpentine.com>
8  date:        Tue Feb 10 18:23:34 2009 +0000
9  summary:     Added an extra line of output
10  

Y el comando hg push” se encarga de empujar dichos cambios.

1  $ hg push ../hello-push
2  pushing to ../hello-push
3  searching for changes
4  adding changesets
5  adding manifests
6  adding file changes
7  added 1 changesets with 1 changes to 1 files

Al igual que hg pull”, el comando hg push” no actualiza el directorio de trabajo del repositorio en el que estamos empujando los cambios. (A diferencia de hg pull”, hg push” no ofrece la opción -u para actualizar el directorio de trabajo del otro repositorio.)

Qué pasa si tratamos de jalar o empujar cambios y el repositorio receptor ya tiene esos cambios? Nada emocionante.

1  $ hg push ../hello-push
2  pushing to ../hello-push
3  searching for changes
4  no changes found

2.8.4. Compartir cambios a través de una red

Los comandos que hemos presentando en las pocas secciones anteriores no están limitados a trabajar con repositorios locales. Cada uno de ellos funciona exactamente de la misma manera a través de una conexión de red. Simplemente pase una URL en vez de una ruta local.

1  $ hg outgoing http://hg.serpentine.com/tutorial/hello
2  comparing with http://hg.serpentine.com/tutorial/hello
3  searching for changes
4  changeset:   5:fccff93807a3
5  tag:         tip
6  user:        Bryan O'Sullivan <bos@serpentine.com>
7  date:        Tue Feb 10 18:23:34 2009 +0000
8  summary:     Added an extra line of output
9  

En este ejemplo, podemos ver qué cambios empujaríamos al repositorio remoto, aunque, de manera entendible, el repositorio remoto está configurado para no permitir a usuarios anónimos empujar cambios a él.

1  $ hg push http://hg.serpentine.com/tutorial/hello
2  pushing to http://hg.serpentine.com/tutorial/hello
3  searching for changes
4  ssl required

Capítulo 3
Una gira de Mercurial: fusionar trabajo

Hasta ahora hemos cubierto cómo clonar un repositorio, hacer cambios, y jalar o empujar dichos cambios de un repositorio a otro. Nuestro siguiente paso es fusionar cambios de repositorios separados.

3.1. Fusionar líneas de trabajo

Fusionar es una parte fundamental de trabajar con una herramienta de control distribuido de versiones.

Como fusionar es una operación tan necesaria y común, Mercurial la facilita. Revisemos el proceso. Empezaremos clonando (otro) repositorio (ve lo seguido que aparecen?) y haciendo un cambio en él.

1  $ cd ..
2  $ hg clone hello my-new-hello
3  updating working directory
4  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
5  $ cd my-new-hello
6  $ sed -i '/printf/i∖∖tprintf("once more, hello.∖∖n");' hello.c
7  $ hg commit -m 'A new hello for a new day.'

Ahora deberíamos tener dos copias de hello.c con contenidos diferentes. El historial de los dos repositorios diverge ahora, como se ilustra en la figura 3.1.

1  $ cat hello.c
2  /⋆
3   ⋆ Placed in the public domain by Bryan O'Sullivan.  This program is
4   ⋆ not covered by patents in the United States or other countries.
5   ⋆/
6  
7  #include <stdio.h>
8  
9  int main(int argc, char ⋆⋆argv)
10  {
11   printf("once more, hello.n");
12   printf("hello, world!");
13   return 0;
14  }
15  $ cat ../my-hello/hello.c
16  /⋆
17   ⋆ Placed in the public domain by Bryan O'Sullivan.  This program is
18   ⋆ not covered by patents in the United States or other countries.
19   ⋆/
20  
21  #include <stdio.h>
22  
23  int main(int argc, char ⋆⋆argv)
24  {
25   printf("hello, world!");
26   printf("hello again!n");
27   return 0;
28  }

PIC

Figura 3.1: Historial reciente divergente de los repositorios my-hello y my-new-hello

Ya sabemos que jalar los cambios desde nuestro repositorio my-hello no tendrá efecto en el directorio de trabajo.

1  $ hg pull ../my-hello
2  pulling from ../my-hello
3  searching for changes
4  adding changesets
5  adding manifests
6  adding file changes
7  added 1 changesets with 1 changes to 1 files (+1 heads)
8  (run 'hg heads' to see heads, 'hg merge' to merge)

Sin embargo, el comando hg pull” dice algo acerca de “frentes”1.

3.1.1. Conjuntos de cambios de frentes

Un frente es un cambio que no tiene descendientes, o hijos, como también se les conoce. La revisión de punta es, por tanto, un frente, porque la revisión más reciente en un repositorio no tiene ningún hijo. Sin embargo, un repositorio puede contener más de un frente.


PIC

Figura 3.2: Contenidos del repositorio después de jalar my-hello a my-new-hello

En la figura 3.2 usted puede ver el efecto que tiene jalar los cambios de my-hello a my-new-hello. El historial que ya existía en my-new-hello se mantiene intacto, pero fue añadida una nueva revisión. Refiriéndonos a la figura 3.1, podemos ver que el ID del conjunto de cambios se mantiene igual en el nuevo repositorio, pero el número de revisión ha cambiado. (Incidentalmente, éste es un buen ejemplo de porqué no es seguro usar números de revisión cuando se habla de conjuntos de cambios). Podemos ver los frentes en un repositorio usando el comando hg heads2.

1  $ hg heads
2  changeset:   6:fccff93807a3
3  tag:         tip
4  parent:      4:2278160e78d4
5  user:        Bryan O'Sullivan <bos@serpentine.com>
6  date:        Tue Feb 10 18:23:34 2009 +0000
7  summary:     Added an extra line of output
8  
9  changeset:   5:05b9c1e50b3c
10  user:        Bryan O'Sullivan <bos@serpentine.com>
11  date:        Tue Feb 10 18:23:36 2009 +0000
12  summary:     A new hello for a new day.
13  

3.1.2. Hacer la fusión

Qué pasa si tratamos de usar el comando usual, hg update”, para actualizar el nuevo frente?

1  $ hg update
2  abort: crosses branches (use 'hg merge' or 'hg update -C')

Mercurial nos indica que el comando hg update” no hará la fusión; no actualizará el directorio de trabajo cuando considera que lo que deseamos hacer es una fusión, a menos que lo obliguemos a hacerlo. En vez de hg update”, usamos el comando hg merge” para hacer la fusión entre los dos frentes.

1  $ hg merge
2  merging hello.c
3  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
4  (branch merge, don't forget to commit)

PIC

Figura 3.3: Directorio de trabajo y repositorio durante la fusión, y consignación consecuente

Esto actualiza el directorio de trabajo, de tal forma que contenga los cambios de ambos frentes, lo que se ve reflejado tanto en la salida de hg parents” como en los contenidos de hello.c.

1  $ hg parents
2  changeset:   5:05b9c1e50b3c
3  user:        Bryan O'Sullivan <bos@serpentine.com>
4  date:        Tue Feb 10 18:23:36 2009 +0000
5  summary:     A new hello for a new day.
6  
7  changeset:   6:fccff93807a3
8  tag:         tip
9  parent:      4:2278160e78d4
10  user:        Bryan O'Sullivan <bos@serpentine.com>
11  date:        Tue Feb 10 18:23:34 2009 +0000
12  summary:     Added an extra line of output
13  
14  $ cat hello.c
15  /⋆
16   ⋆ Placed in the public domain by Bryan O'Sullivan.  This program is
17   ⋆ not covered by patents in the United States or other countries.
18   ⋆/
19  
20  #include <stdio.h>
21  
22  int main(int argc, char ⋆⋆argv)
23  {
24   printf("once more, hello.n");
25   printf("hello, world!");
26   printf("hello again!n");
27   return 0;
28  }

3.1.3. Consignar los resultados de la fusión

Siempre que hacemos una fusión, hg parents” mostrará dos padres hasta que consignemos (hg commit”) los resultados de la fusión.

1  $ hg commit -m 'Merged changes'

Ahora tenemos una nueva revisión de punta; note que tiene los dos frentes anteriores como sus padres. Estos son las mismas revisiones que mostró previamente el comando hg parents”.

1  $ hg tip
2  changeset:   7:22a572779faf
3  tag:         tip
4  parent:      5:05b9c1e50b3c
5  parent:      6:fccff93807a3
6  user:        Bryan O'Sullivan <bos@serpentine.com>
7  date:        Tue Feb 10 18:23:36 2009 +0000
8  summary:     Merged changes
9  

En la figura 3.3 usted puede apreciar una representación de lo que pasa en el directorio de trabajo durante la fusión cuando se hace la consignación. Durante la fusión, el directorio de trabajo tiene dos conjuntos de cambios como sus padres, y éstos se vuelven los padres del nuevo conjunto de cambios.

3.2. Fusionar cambios con conflictos

La mayoría de las fusiones son algo simple, pero a veces usted se encontrará fusionando cambios donde más de uno de ellos afecta las mismas secciones de los mismos ficheros. A menos que ambas modificaciones sean idénticas, el resultado es un conflicto, en donde usted debe decidir cómo reconciliar ambos cambios y producir un resultado coherente.


PIC

Figura 3.4: Cambios con conflictos a un documento

La figura 3.4 ilustra un ejemplo con dos cambios generando conflictos en un documento. Empezamos con una sola versión del fichero; luego hicimos algunos cambios; mientras tanto, alguien más hizo cambios diferentes en el mismo texto. Lo que debemos hacer para resolver el conflicto causado por ambos cambios es decidir cómo debe quedar finalmente el fichero.

Mercurial no tiene ninguna utilidad integrada para manejar conflictos. En vez de eso, ejecuta un programa externo llamado hgmerge. Es un guión de línea de comandos que es instalado junto con Mercurial; usted puede modificarlo para que se comporte como usted lo desee. Por defecto, lo que hace es tratar de encontrar una de varias herramientas para fusionar que es probable que estén instaladas en su sistema. Primero se intenta con unas herramientas para fusionar cambios automáticamente; si esto no tiene éxito (porque la fusión demanda una guía humana) o dichas herramientas no están presentes, el guión intenta con herramientas gráficas para fusionar.

También es posible hacer que Mercurial ejecute otro programa o guión en vez de hgmerge, definiendo la variable de entorno HGMERGE con el nombre del programa de su preferencia.

3.2.1. Usar una herramienta gráfica para fusión

Mi herramienta favorita para hacer fusiones es kdiff3, y la usaré para describir las características comunes de las herramientas gráficas para hacer fusiones. Puede ver una captura de pantalla de kdiff3 ejecutándose, en la figura 3.5. El tipo de fusión que la herramienta hace se conoce como fusión de tres vías, porque hay tres versiones diferentes del fichero en que estamos interesados. Debido a esto la herramienta divide la parte superior de la ventana en tres paneles.

En el panel inferior se encuentra el resultado actual de la fusión. Nuestra tarea es reemplazar todo el texto rojo, que muestra los conflictos sin resolver, con una fusión adecuada de “nuestra” versión del fichero y la de “ellos”.

Los cuatro paneles están enlazados; si avanzamos vertical o horizontalmente en cualquiera de ellos, los otros son actualizados para mostrar las secciones correspondientes del fichero que tengan asociado.


PICwidth=]kdiff3

Figura 3.5: Usando kdiff3 para fusionar versiones de un fichero

En cada conflicto del fichero podemos escoger resolverlo usando cualquier combinación del texto de la revisión base, la nuestra, o la de ellos. También podemos editar manualmente el fichero en que queda la fusión, si es necesario hacer cambios adicionales.

Hay muchas herramientas para fusionar ficheros disponibles. Se diferencian en las plataformas para las que están disponibles, y en sus fortalezas y debilidades particulares. La mayoría están afinadas para fusionar texto plano, mientras que otras están pensadas para formatos de ficheros especializados (generalmente XML).

3.2.2. Un ejemplo real

En este ejemplo, reproduciremos el historial de modificaciones al fichero de la figura 3.4 mostrada anteriormente. Empecemos creando un repositorio con la versión base de nuestro documento.

1  $ cat > letter.txt <<EOF
2  > Greetings!
3  > I am Mariam Abacha, the wife of former
4  > Nigerian dictator Sani Abacha.
5  > EOF
6  $ hg add letter.txt
7  $ hg commit -m '419 scam, first draft'

Clonaremos el repositorio y haremos un cambio al fichero.

1  $ cd ..
2  $ hg clone scam scam-cousin
3  updating working directory
4  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
5  $ cd scam-cousin
6  $ cat > letter.txt <<EOF
7  > Greetings!
8  > I am Shehu Musa Abacha, cousin to the former
9  > Nigerian dictator Sani Abacha.
10  > EOF
11  $ hg commit -m '419 scam, with cousin'

Y haremos otro clon, para simular a alguien más haciendo un cambio al mismo fichero. (Esto introduce la idea de que no es tan inusual hacer fusiones consigo mismo, cuando usted aísla tareas en repositorios separados, y de hecho encuentra conflictos al hacerlo.)

1  $ cd ..
2  $ hg clone scam scam-son
3  updating working directory
4  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
5  $ cd scam-son
6  $ cat > letter.txt <<EOF
7  > Greetings!
8  > I am Alhaji Abba Abacha, son of the former
9  > Nigerian dictator Sani Abacha.
10  > EOF
11  $ hg commit -m '419 scam, with son'

Ahora que tenemos dos versiones diferentes de nuestro fichero, crearemos un entorno adecuado para hacer la fusión.

1  $ cd ..
2  $ hg clone scam-cousin scam-merge
3  updating working directory
4  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
5  $ cd scam-merge
6  $ hg pull -u ../scam-son
7  pulling from ../scam-son
8  searching for changes
9  adding changesets
10  adding manifests
11  adding file changes
12  added 1 changesets with 1 changes to 1 files (+1 heads)
13  not updating, since new heads added
14  (run 'hg heads' to see heads, 'hg merge' to merge)

En este ejemplo, no usaré el comando normal de Mercurial para hacer la fusión (hgmerge), porque lanzaría mi linda herramienta automatizada para correr ejemplos dentro de una interfaz gráfica de usuario. En vez de eso, definiré la variable de entorno HGMERGE para indicarle a Mercurial que use el comando merge. Este comando forma parte de la instalación base de muchos sistemas Unix y similares. Si usted está ejecutando este ejemplo en su computador, no se moleste en definir HGMERGE.

1  $ export HGMERGE=merge
2  $ hg merge
3  merging letter.txt
4  merge: warning: conflicts during merge
5  merging letter.txt failed!
6  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
7  There are unresolved merges, you can redo the full merge using:
8    hg update -C 1
9    hg merge 2
10  $ cat letter.txt
11  Greetings!
12  <<<<<<< /tmp/tour-merge-conflictsNZwSt/scam-merge/letter.txt
13  I am Shehu Musa Abacha, cousin to the former
14  =======
15  I am Alhaji Abba Abacha, son of the former
16  >>>>>>> /tmp/letter.txt~other.749P-k
17  Nigerian dictator Sani Abacha.

Debido a que merge no puede resolver los conflictos que aparecen, él deja marcadores de fusión en el fichero con conflictos, indicando si provienen de nuestra versión o de la de ellos.

Mercurial puede saber —por el código de salida del comando merge— que no fue posible hacer la fusión exitosamente, así que nos indica qué comandos debemos ejecutar si queremos rehacer la fusión. Esto puede ser útil si, por ejemplo, estamos ejecutando una herramienta gráfica de fusión y salimos de ella porque nos confundimos o cometimos un error.

Si la fusión —automática o manual— falla, no hay nada que nos impida “arreglar” los ficheros afectados por nosotros mismos, y consignar los resultados de nuestra fusión:

1  $ cat > letter.txt <<EOF
2  > Greetings!
3  > I am Bryan O'Sullivan, no relation of the former
4  > Nigerian dictator Sani Abacha.
5  > EOF
6  $ hg resolve -m letter.txt
7  hg: unknown command 'resolve'
8  Mercurial Distributed SCM
9  
10  basic commands:
11  
12   add        add the specified files on the next commit
13   annotate   show changeset information per file line
14   clone      make a copy of an existing repository
15   commit     commit the specified files or all outstanding changes
16   diff       diff repository (or selected files)
17   export     dump the header and diffs for one or more changesets
18   init       create a new repository in the given directory
19   log        show revision history of entire repository or files
20   merge      merge working directory with another revision
21   parents    show the parents of the working dir or revision
22   pull       pull changes from the specified source
23   push       push changes to the specified destination
24   remove     remove the specified files on the next commit
25   serve      export the repository via HTTP
26   status     show changed files in the working directory
27   update     update working directory
28  
29  use "hg help" for the full list of commands or "hg -v" for details
30  $ hg commit -m 'Send me your money'
31  $ hg tip
32  changeset:   3:e29827f5cf51
33  tag:         tip
34  parent:      1:eac603d73208
35  parent:      2:78b0a9e9cf1b
36  user:        Bryan O'Sullivan <bos@serpentine.com>
37  date:        Tue Feb 10 18:23:37 2009 +0000
38  summary:     Send me your money
39  

3.3. Simplificar el ciclo jalar-fusionar-consignar

El proceso de fusionar cambios delineado anteriomente es directo, pero requiere la ejecución de tres comandos en sucesión.

1  hg pull
2  hg merge
3  hg commit -m 'Fusionados cambios remotos'

En la consignación final usted debe proveer un mensaje adecuado, que casi siempre es un fragmento de texto “de relleno” carente de valor particular.

Sería agradable reducir la cantidad de pasos necesarios, si fuera posible. De hecho, Mercurial es distribuido junto con una extensión llamada fetch3 que hace precisamente esto.

Mercurial cuenta con un mecanismo de extensión flexible que le permite a sus usuarios extender su funcionalidad, manteniendo el núcleo de Mercurial pequeño y fácil de manejar. Algunas extensiones añaden nuevos comandos que usted puede usar desde la línea de comandos, mientras que otros funcionan “tras bambalinas”, por ejemplo, añadiendo funcionalidad al servidor.

La extensión fetch añade un comando llamado, no sorpresivamente, hg fetch”. Esta extensión actúa como una combinación de hg pull”, hg update” y hg merge”. Empieza jalando cambios de otro repositorio al repositorio actual. Si encuentra que los cambios añaden un nuevo frente en el repositorio actual, inicia una fusión, y luego consigna el resultado de la misma con un mensaje generado automáticamente. Si no se añadieron nuevos frentes, actualiza el directorio de trabajo con el nuevo conjunto de cambios de punta.

Activar la extensión fetch es fácil. Edite su .hgrc, y vaya a (o cree) la sección [extensions]. Luego añada una línea que diga simplemente “fetch ”.

1  [extensions]
2  fetch =

(Normalmente, a la derecha del “=” debería aparecer la ubicación de la extensión, pero como el comando fetch es parte de la distribución estándar, Mercurial sabe dónde buscarla.)

Capítulo 4
Tras bambalinas

A diferencia de varios sistemas de control de revisiones, los conceptos en los que se fundamenta Mercurial son lo suficientemente simples como para entender fácilmente cómo funciona el software. Saber esto no es necesario, pero considero útil tener un “modelo mental” de qué es lo que sucede.

Comprender esto me da la confianza de que Mercurial ha sido cuidadosamente diseñado para ser tanto seguro como eficiente. Y tal vez con la misma importancia, si es fácil para mí hacerme a una idea adecuada de qué está haciendo el software cuando llevo a cabo una tarea relacionada con control de revisiones, es menos probable que me sosprenda su comportamiento.

En este capítulo, cubriremos inicialmente los conceptos centrales del diseño de Mercurial, y luego discutiremos algunos detalles interesantes de su implementación.

4.1. Registro del historial de Mercurial

4.1.1. Seguir el historial de un único fichero

Cuando Mercurial sigue las modificaciones a un fichero, guarda el historial de dicho fichero en un objeto de metadatos llamado filelog1. Cada entrada en el fichero de registro contiene suficiente información para reconstruir una revisión del fichero que se está siguiendo. Los ficheros de registro son almacenados como ficheros el el directorio .hg/store/data. Un fichero de registro contiene dos tipos de información: datos de revisiones, y un índice para ayudar a Mercurial a buscar revisiones eficientemente.

El fichero de registro de un fichero grande, o con un historial muy largo, es guardado como ficheros separados para datos (sufijo “.d”) y para el índice (sufijo “.i”). Para ficheros pequeños con un historial pequeño, los datos de revisiones y el índice son combinados en un único fichero “.i”. La correspondencia entre un fichero en el directorio de trabajo y el fichero de registro que hace seguimiento a su historial en el repositorio se ilustra en la figura 4.1.


PIC

Figura 4.1: Relación entre ficheros en el directorio de trabajo y ficheros de registro en el repositorio

4.1.2. Administración de ficheros monitoreados

Mercurial usa una estructura llamada manifiesto para centralizar la información que maneja acerca de los ficheros que monitorea. Cada entrada en el manifiesto contiene información acerca de los ficheros involucrados en un único conjunto de cambios. Una entrada registra qué ficheros están presentes en el conjunto de cambios, la revisión de cada fichero, y otros cuantos metadatos del mismo.

4.1.3. Registro de información del conjunto de cambios

La bitácora de cambios contiene información acerca de cada conjunto de cambios. Cada revisión indica quién consignó un cambio, el comentario para el conjunto de cambios, otros datos relacionados con el conjunto de cambios, y la revisión del manifiesto a usar.

4.1.4. Relaciones entre revisiones

Dentro de una bitácora de cambios, un manifiesto, o un fichero de registro, cada revisión conserva un apuntador a su padre inmediato (o sus dos padres, si es la revisión de una fusión). Como menciońe anteriormente, también hay relaciones entre revisiones a través de estas estructuras, y tienen naturaleza jerárquica.

Por cada conjunto de cambios en un repositorio, hay exactamente una revisión almacenada en la bitácora de cambios. Cada revisión de la bitácora de cambios contiene un apuntador a una única revisión del manifiesto. Una revisión del manifiesto almacena un apuntador a una única revisión de cada fichero de registro al que se le hacía seguimiento cuando fue creado el conjunto de cambios. Estas relaciones se ilustran en la figura 4.2.


PIC

Figura 4.2: Relaciones entre metadatos

Como lo muestra la figura, no hay una relación “uno a uno” entre las revisiones en el conjunto de cambios, el manifiesto, o el fichero de registro. Si el manifiesto no ha sido modificado de un conjunto de cambios a otro, las entradas en la bitácora de cambios para esos conjuntos de cambios apuntarán a la misma revisión del manifiesto. Si un fichero monitoreado por Mercurial no sufre ningún cambio de un conjunto de cambios a otro, la entrada para dicho fichero en las dos revisiones del manifiesto apuntará a la misma revisión de su fichero de registro.

4.2. Almacenamiento seguro y eficiente

La base común de las bitácoras de cambios, los manifiestos, y los ficheros de registros es provista por una única estructura llamada el revlog2.

4.2.1. Almacenamiento eficiente

El revlog provee almacenamiento eficiente de revisiones por medio del mecanismo de deltas3. En vez de almacenar una copia completa del fichero por cada revisión, almacena los cambios necesarios para transformar una revisión anterior en la nueva revisión. Para muchos tipos de fichero, estos deltas son típicamente de una fracción porcentual del tamaño de una copia completa del fichero.

Algunos sistemas de control de revisiones obsoletos sólo pueden manipular deltas de ficheros de texto plano. Ellos o bien almacenan los ficheros binarios como instantáneas completas, o codificados en alguna representación de texto plano adecuada, y ambas alternativas son enfoques que desperdician bastantes recursos. Mercurial puede manejar deltas de ficheros con contenido binario arbitrario; no necesita tratar el texto plano como un caso especial.

4.2.2. Operación segura

Mercurial sólo añade datos al final de los ficheros de revlog. Nunca modifica ninguna sección de un fichero una vez ha sido escrita. Esto es más robusto y eficiente que otros esquemas que requieren modificar o reescribir datos.

Adicionalmente, Mercurial trata cada escritura como parte de una transacción, que puede cubrir varios ficheros. Una transacción es atómica: o bien la transacción tiene éxito y entonces todos sus efectos son visibles para todos los lectores, o la operación completa es cancelada. Esta garantía de atomicidad implica que, si usted está ejecutando dos copias de Mercurial, donde una de ellas está leyendo datos y la otra los está escribiendo, el lector nunca verá un resultado escrito parcialmente que podría confundirlo.

El hecho de que Mercurial sólo hace adiciones a los ficheros hace más fácil proveer esta garantía transaccional. A medida que sea más fácil hacer operaciones como ésta, más confianza tendrá usted en que sean hechas correctamente.

4.2.3. Recuperación rápida de datos

Mercurial evita ingeniosamente un problema común a todos los sistemas de control de revisiones anteriores¿ el problema de la recuperación4 ineficiente de datos. Muchos sistemas de control de revisiones almacenan los contenidos de una revisión como una serie incremental de modificaciones a una “instantánea”. Para reconstruir una versión cualquiera, primero usted debe leer la instantánea, y luego cada una de las revisiones entre la instantánea y su versión objetivo. Entre más largo sea el historial de un fichero, más revisiones deben ser leídas, y por tanto toma más tiempo reconstruir una versión particular.


PIC

Figura 4.3: Instantánea de un revlog, con deltas incrementales

La innovación que aplica Mercurial a este problema es simple pero efectiva. Una vez la cantidad de información de deltas acumulada desde la última instantánea excede un umbral fijado de antemano, se almacena una nueva instantánea (comprimida, por supuesto), en lugar de otro delta. Esto hace posible reconstruir cualquier versión de un fichero rápidamente. Este enfoque funciona tan bien que desde entonces ha sido copiado por otros sistemas de control de revisiones.

La figura 4.3 ilustra la idea. En una entrada en el fichero índice de un revlog, Mercurial almacena el rango de entradas (deltas) del fichero de datos que se deben leer para reconstruir una revisión en particular.

Nota al margen: la influencia de la compresión de vídeo

Si le es familiar la compresión de vídeo, o ha mirado alguna vez una emisión de TV a través de cable digital o un servicio de satélite, puede que sepa que la mayor parte de los esquemas de compresión de vídeo almacenan cada cuadro del mismo como un delta contra el cuadro predecesor. Adicionalmente, estos esquemas usan técnicas de compresión “con pérdida” para aumentar la tasa de compresión, por lo que los errores visuales se acumulan a lo largo de una cantidad de deltas inter-cuadros.

Ya que existe la posibilidad de que un flujo de vídeo se “pierda” ocasionalmente debido a fallas en la señal, y para limitar la acumulación de errores introducida por la compresión con pérdidas, los codificadores de vídeo insertan periódicamente un cuadro completo (también llamado “cuadro clave”) en el flujo de vídeo; el siguiente delta es generado con respecto a dicho cuadro. Esto quiere decir que si la señal de vídeo se interrumpe, se reanudará una vez se reciba el siguiente cuadro clave. Además, la acumulación de errores de codificación se reinicia con cada cuadro clave.

4.2.4. Identificación e integridad fuerte

Además de la información de deltas e instantáneas, una entrada en un revlog contiene un hash criptográfico de los datos que representa. Esto hace difícil falsificar el contenido de una revisión, y hace fácil detectar una corrupción accidental.

Los hashes proveen más que una simple revisión de corrupción: son usados como los identificadores para las revisiones. Los hashes de identificación de conjuntos de cambios que usted ve como usuario final son de las revisiones de la bitácora de cambios. Aunque los ficheros de registro y el manifiesto también usan hashes, Mercurial sólo los usa tras bambalinas.

Mercurial verifica que los hashes sean correctos cuando recupera revisiones de ficheros y cuando jala cambios desde otro repositorio. Si se encuentra un problema de integridad, Mercurial se quejará y detendrá cualquier operación que esté haciendo.

Además del efecto que tiene en la eficiencia en la recuperación, el uso periódico de instantáneas de Mercurial lo hace más robusto frente a la corrupción parcial de datos. Si un fichero de registro se corrompe parcialmente debido a un error de hardware o del sistema, a menudo es posible reconstruir algunas o la mayoría de las revisiones a partir de las secciones no corrompidas del fichero de registro, tanto antes como después de la sección corrompida. Esto no sería posible con un sistema de almacenamiento basado únicamente en deltas.

4.3. Historial de revisiones, ramas y fusiones

Cada entrada en el revlog de Mercurial conoce la identidad de la revisión de su ancestro inmediato, al que se conoce usualmente como su padre. De hecho, una revisión contiene sitio no sólo para un padre, sino para dos. Mercurial usa un hash especial, llamado el “ID nulo”, para representar la idea de “no hay padre aquí”. Este hash es simplemente una cadena de ceros.

En la figura 4.4 usted puede ver un ejemplo de la estructura conceptual de un revlog. Los ficheros de registro, manifiestos, y bitácoras de cambios comparten la misma estructura; sólo difieren en el tipo de datos almacenados en cada delta o instantánea.

La primera revisión en un revlog (al final de la imagen) tiene como padre al ID nulo, en las dos ranuras disponibles para padres. En una revisión normal, la primera ranura para padres contiene el ID de la revisión padre, y la segunda contiene el ID nulo, señalando así que la revisión sólo tiene un padre real. Un par de revisiones que tenga el mismo ID padre son ramas. Una revisión que representa una fusión entre ramas tiene dos IDs de revisión normales en sus ranuras para padres.


PIC

Figura 4.4:

4.4. El directorio de trabajo

En el directorio de trabajo, Mercurial almacena una instantánea de los ficheros del repositorio como si fueran los de un conjunto de cambios particular.

El directorio de trabajo “sabe” qué conjunto de cambios contiene. Cuando usted actualiza el directorio de trabajo para que contenga un conjunto de cambios particular, Mercurial busca la revisión adecuada del manifiesto para averiguar qué ficheros estaba monitoreando cuando se hizo la consignación del conjunto de cambios, y qué revisión de cada fichero era la actual en ese momento. Luego de eso, recrea una copia de cada uno de esos ficheros, con los mismos contenidos que tenían cuando fue consignado el conjunto de cambios.

El estado de directorio5 contiene el conocimiento de Mercurial acerca del directorio de trabajo. Allí se detalla a qué conjunto de cambios es actualizado el directorio de trabajo, y todos los ficheros que Mercurial está monitoreando en este directorio.

Tal como la revisión de un revlog tiene espacio para dos padres, para que pueda representar tanto una revisión normal (con un solo padre) o una fusión de dos revisiones anteriores, el estado de directorio tiene espacio para dos padres. Cuando usted usa el comando hg update”, el conjunto de cambios al que usted se actualiza es almacenado en la casilla destinada al “primer padre”, y un ID nulo es almacenado en la segunda. Cuando usted hace una fusión (hg merge”) con otro conjunto de cambios, la casilla para el primer padre permanece sin cambios, y la casilla para el segundo es actualizada con el conjunto de cambios con el que usted acaba de hacer la fusión. El comando hg parents” le indica cuáles son los padres del estado de directorio.

4.4.1. Qué pasa en una consignación

El estado de directorio almacena información sobre los padres para algo más que mero registro. Mercurial usa los padres del estado de directorio como los padres de un nuevo conjunto de cambios cuando usted hace una consignación.


PIC

Figura 4.5: El directorio de trabajo puede tener dos padres

La figura 4.5 muestra el estado normal del directorio de trabajo, que tiene un único conjunto de cambios como padre. Dicho conjunto de cambios es la punta, el conjunto de cambios más reciente en el repositorio que no tiene hijos.


PIC

Figura 4.6: El directorio de trabajo obtiene nuevos padres luego de una consignación

Es útil pensar en el directorio de trabajo como en “el conjunto de cambios que estoy a punto de enviar”. Cualquier fichero que usted le diga a Mercurial que fue añadido, borrado, renombrado o copiado, se verá reflejado en ese conjunto de cambios, como también se verán las modificaciones a cualquiera de los ficheros que Mercurial ya esté monitoreando; el nuevo conjunto de cambios dentrá los padres del directorio de trabajo como propios.

Luego de una consignación, Mercurial actualizará los padres del directorio de trabajo, de tal manera que el primer padre sea el ID del nuevo conjunto de cambios, y el segundo sea el ID nulo. Esto puede verse en la figura 4.6. Mercurial no toca ninguno de los ficheros del directorio de trabajo cuando usted hace la consignación; sólo modifica el estado de directorio para anotar sus nuevos padres.

4.4.2. Creación de un nuevo frente

Es perfectamente normal actualizar el directorio de trabajo a un conjunto de cambios diferente a la punta actual. Por ejemplo, usted podría desear saber en qué estado se encontraba su proyecto el martes pasado, o podría estar buscando en todos los conjuntos de cambios para saber cuándo se introdujo un fallo. En casos como éstos, la acción natural es actualizar el directorio de trabajo al conjunto de cambios de su interés, y examinar directamente los ficheros en el directorio de trabajo para ver sus contenidos tal como estaban en el momento de hacer la consignación. El efecto que tiene esto se muestra en la figura 4.7.


PIC

Figura 4.7: El directorio de trabajo, actualizado a un conjunto de cambios anterior

Una vez se ha actualizado el directorio de trabajo a un conjunto de cambios anterior, qué pasa si se hacen cambios, y luego se hace una consignación? Mercurial se comporta en la misma forma que describí anteriormente. Los padres del directorio de trabajo se convierten en los padres del nuevo conjunto de cambios. Este nuevo conjunto de cambios no tiene hijos, así que se convierte en la nueva punta. Y el repositorio tiene ahora dos conjuntos de cambios que no tienen hijos; a éstos los llamamos frentes. Usted puede apreciar la estructura que esto crea en la figura 4.8.


PIC

Figura 4.8: Después de una consignación hecha mientras se usaba un conjunto de cambios anterior

Nota: Si usted es nuevo en Mercurial, debería tener en mente un “error” común, que es usar el comando hg pull” sin ninguna opción. Por defecto, el comando hg pullno actualiza el directorio de trabajo, así que usted termina trayendo nuevos conjuntos de cambios a su repositorio, pero el directorio de trabajo sigue usando el mismo conjunto de cambios que tenía antes de jalar. Si usted hace algunos cambios, y luego hace una consignación, estará creando un nuevo frente, porque su directorio de trabajo no es sincronizado a cualquiera que sea la nueva punta.

Pongo la palabra “error” en comillas porque todo lo que usted debe hacer para rectificar la situación es hacer una fusión (hg merge”), y luego una consignación (hg commit”). En otras palabras, esto casi nunca tiene consecuencias negativas; sólo sorprende a la gente. Discutiré otras formas de evitar este comportamiento, y porqué Mercurial se comporta de esta forma, inicialmente sorprendente, más adelante.

4.4.3. Fusión de frentes

Cuando usted ejecuta el comando hg merge”, Mercurial deja el primer padre del directorio de trabajo intacto, y escribe como segundo padre el conjunto de cambios contra el cual usted está haciendo la fusión, como se muestra en la figura 4.9.


PIC

Figura 4.9: Fusión de dos frentes

Mercurial también debe modificar el directorio de trabajo, para fusionar los ficheros que él monitorea en los dos conjuntos de cambios. Con algunas simplificaciones, el proceso es el siguiente, por cada fichero en los manifiestos de ambos conjuntos de cambios.

Hay más detalles—hacer una fusión tiene una gran cantidad de casos especiales—pero éstas son las elecciones más comunes que se ven involucradas en una fusión. Como usted puede ver, muchos de los casos son completamente automáticos, y de hecho la mayoría de las fusiones terminan automáticamente, sin requerir la interacción del usuario para resolver ningún conflicto.

Cuando considere qué pasa cuando usted hace una consignación después de una fusión, de nuevo el directorio de trabajo es “el conjunto de cambios que estoy a punto de consignar”. Una vez termina su trabajo el comando hg merge”, el directorio de trabajo tiene dos padre; éstos se convertirán en los padres del nuevo conjunto de cambios.

Mercurial le permite hacer múltiples fusiones, pero usted debe consignar los resultados de cada fusión sucesivamente. Esto es necesario porque Mercurial sólo monitorea dos padres, tanto para las revisiones como para los directorios de trabajo. Aunque técnicamente es posible fusionar varios conjuntos de trabajo en una sola operación, la posibilidad de confundir al usuario y crear un desorden terrible en la fusión se hace incontenible de inmediato.

4.5. Otras características de diseño interesantes

En las secciones anteriores, he tratado de resaltar algunos de los aspectos más importantes del diseño de Mercurial, para mostrar que se presta gran cuidado y atención a la confiabilidad y el desempeño. Sin embargo, la atención a los detalles no para ahí. Hay una cantidad de aspectos de la construcción de Mercurial que encuentro interesantes personalmente. Detallaré unos cuantos de ellos aquí, aparte de los elementos “importantes” de arriba, para que, si usted está interesado, pueda obetener una idea mejor de la cantidad de esfuerzo mental invertido en el diseño de un sistema bien diseñado.

4.5.1. Compresión ingeniosa

Cuando es adecuado, Mercurial almacenará tanto las instantáneas como los deltas en formato comprimido. Lo hace tratando siempre de comprimir una instantánea o delta, y conservando la versión comprimida sólo si es más pequeña que la versión sin compresión.

Esto implica que Mercurial hace “lo correcto” cuando almacena un fichero cuyo formato original está comprimido, como un fichero zip o una imagen JPEG. Cuando estos tipos de ficheros son comprimidos por segunda vez, el fichero resultante usualmente es más grande que la versión comprimida una sola vez, por lo que Mercurial almacenará el fichero zip o JPEG original.

Los deltas entre revisiones de un fichero comprimido usualmente son más grandes que las instantáneas del mismo fichero, y Mercurial de nuevo hace “lo correcto” en estos casos. Él encuentra que dicho delta excede el umbral respecto al cual se debería almacenar una instantánea completa del fichero, así que almacena la instantánea, ahorrando espacio de nuevo respecto al enfoque simplista de usar únicamente deltas.

Recompresión de red

Cuando almacena las revisiones en disco, Mercurial usa el algoritmo de compresión “deflación” (el mismo usado en el popular formato de fichero zip), que provee una buena velocidad con una tasa de compresión respetable. Sin embargo, cuando se transmiten datos de revisiones a través de una conexión de red, Mercurial descomprime los datos comprimidos de las revisiones.

Si la conexión es hecha a través de HTTP, Mercurial recomprime el flujo completo de datos usando un algoritmo de compresión que brinda una mejor tasa de compresión (el algoritmo Burrows-Wheeler del ampliamente usado paquete de compresión bzip2). Esta combinación de algoritmo y compresión del flujo completo de datos (en vez de una revisión a la vez) reduce sustancialmente la cantidad de bytes a transferir, brindando así un mejor desempeño de red sobre casi todo tipo de redes.

(Si la conexión se hace sobre ssh, Mercurial no recomprmime el flujo, porque ssh puede hacer esto por sí mismo.)

4.5.2. Reordenado de lectura/escritura y atomicidad

Añadir datos al final de un fichero no es todo lo que hace falta para garantizar que un lector no verá una escritura parcial. Si recuerda la figura 4.2, las revisiones en la bitácora de cambios apuntan a revisiones en el manifiesto, y las revisiones en el manifiesto apuntan a revisiones en ficheros de registro. Esta jerarquía es deliberada.

Un escritor inicia una transacción al escribir los datos del ficheros del fichero de registro y el manifiesto, y no escribe nada en la bitácora de cambios hasta que dichas escrituras hayan terminado. Un lector empieza leyendo datos de la bitácora de cambios, luego del manifiesto, y finalmente del fichero de registro.

Como el escritor siempre termina de escribir los datos en el fichero de registro y en el manifiesto antes de escribir a la bitácora de cambios, un lector nunca verá un apuntador a una versión parcialmente escrita de revisiones del manifiesto desde la bitácora de cambios, y nunca leerá un apuntador a una revisión parcialmente escrita del fichero de registro desde el manifiesto.

4.5.3. Acceso concurrente

El reordenado de lectura/escritura y la atomicidad garantizan que Mercurial nunca necesita bloquear un repositorio cuando está leyendo datos, aún si se está escribiendo al repositorio mientras se hace la lectura. Esto tiene un gran efecto en la escalabilidad; usted puede tener cualquier cantidad de procesos Mercurial leyendo datos de un repositorio de manera segura al mismo tiempo, sin importar si se está escribiendo al mismo o no.

La naturaleza carente de bloqueos de la lectura significa que si usted está compartiendo un repositorio en un sistema multiusuario, no necesita dar a los usuarios locales permisos de escritura a su repositorio para que ellos puedan clonarlo o jalar cambios; sólo necesitan permisos de lectura. (Esta no es una característica común entre los sistemas de control de revisiones, así que no la dé por hecha! Muchos de ellos requieren que los lectores sean capaces de bloquear el repositorio antes de poder leerlo, y esto requiere acceso de escritura en al menos un directorio, lo que por supuesto se convierte en una fuente de todo tipo de problemas administrativos y de seguridad bastante molestos.)

Mercurial usar bloqueos para asegurarse de que sólo un proceso pueda escribir a un repositorio al mismo tiempo (el mecanismo de bloqueo es seguro incluso sobre sistemas de ficheros notoriamente hostiles al bloqueo, como NFS). Si un repositorio está bloqueado, los escritores esperarán un buen rato para revisar si el repositorio ya ha sido desbloqueado, pero si el repositorio sique bloqueado por mucho tiempo, el proceso que intenta escribir fallará por tiempo de espera máximo. Esto significa que sus guiones automáticos diarios no se quedarán esperando para siempre, apilándose si el sistema se cayó sin que nadie se diera cuenta, por ejemplo. (Sí, el tiempo de espera máximo es configurable, de cero a infinito).

Acceso seguro al estado de directorio

Al igual que con los datos de revisión, Mercurial no requiere un bloqueo para leer el fichero de estado de directorio; sí se usa un bloqueo para escribir a él. Para evitar la posibilidad de leer una copia parcialmente escrita del fichero de estado de directorio, Mercurial escribe a un fichero con un nombre único en el mismo directorio del fichero de estado de directorio, y luego renombra atómicamente este fichero temporal a dirstate6. Así se garantiza que el fichero llamado dirstate esté completo, y no parcialmente escrito.

4.5.4. Evitar movimientos de brazo

Un aspecto crítico para el desempeño de Mercurial es evitar los movimientos del brazo de lectura del disco duro, ya que cualquier movimiento de brazo es mucho más costoso que incluso una operación de lectura relativamente grande.

Es por esto que, por ejemplo, el estado de directorio es almacenado como un solo fichero. Si hubiera un estado de directorio por cada directorio que Mercurial monitorea, el disco haría un movimiento de brazo por cada directorio. En cambio, Mercurial lee el estado de directorio completo en un solo paso.

Mercurial también usa un esquema de “copiar al escribir” cuando clona un repositorio en un mismo medio de almacenamiento local. En vez de copiar cada fichero de revlog del repositorio viejo al nuevo, se crea un “enlace duro”, que es una manera sucinta de decir “estos dos nombres apuntan al mismo fichero”. Cuando Mercurial está a punto de escribir a uno de los ficheros de revlog, revisa si la cantidad de nombres apuntando al fichero es de más de uno. Si lo es, más de un repositorio está usando el fichero, así que Mercurial hace una nueva copia del fichero, privada para este repositorio.

Algunos desarrolladores de control de revisiones han indicado que la idea de hacer una copia privada completa de un fichero no es eficiente desde el punto de vista de almacenamiento. Aunque esto es cierto, el almacenamiento es barato, y este método brinda el máximo rendimiento al tiempo que delega la mayor parte del trabajo de manejo de ficheros al sistema operativo. Un esquema alternativo seguramente reduciría el desempeño y aumentaría la complejidad del software, cada uno de los cuales es mucho más importante para la “sensación” que se tiene del software en el trabajo día a día.

4.5.5. Otros contenidos del estado de directorio

Debido a que Mercurial no lo fuerza a indicar si usted está modificando un fichero, se usa el estado de directorio para almacenar información extra para poder determinar efecientemente si usted ha modificado un fichero. Por cada fichero en el directorio de trabajo, se almacena el momento en que Mercurial modificó por última vez el fichero, y el tamaño del fichero en ese momento.

Cuando usted añade (hg add”), remueve (hg remove”), renombra (hg rename”) o copia (hg copy”) ficheros, Mercurial actualiza el estado de directorio para saber qué hacer con dichos ficheros cuando usted haga la consignación.

Cuando Mercurial está revisando el estado de los ficheros en el directorio de trabajo, revisa primero la fecha de modificación del fichero. Si no ha cambiado, el fichero no ha sido modificado. Si el tamaño del fichero ha cambiado, el fichero ha sido modificado. Sólo en el caso en que el tiempo de modificación ha cambiado, pero el tamaño no, es necesario leer el contenido del fichero para revisar si ha cambiado. Almacenar estos pocos datos reduce dramáticamente la cantidad de datos que Mercurial debe leer, lo que brinda una mejora en el rendimiento grande, comparado con otros sistemas de control de revisiones.

Capítulo 5
Mercurial día a día

5.1. Cómo indicarle a Mercurial qué ficheros seguir

Mercurial no trabaja con ficheros en su repositorio a menos que usted se lo indique explícitamente. La orden hg status” le mostrará cuáles ficheros son desconocidos para Mercurial; se emplea un “?” para mostrar tales ficheros.

Para indicarle a Mercurial que tenga en cuenta un fichero, emplee la orden hg add”. Una vez que haya adicionado el fichero, la línea referente al fichero al aplicar la orden hg status” para tal fichero cambia de “?” a “A”.

1  $ hg init add-example
2  $ cd add-example
3  $ echo a > a
4  $ hg status
5  ? a
6  $ hg add a
7  $ hg status
8  A a
9  $ hg commit -m 'Added one file'
10  $ hg status

Después de invocar hg commit”, los ficheros que haya adicionado antes de consignar no se listarán en la salida de hg status”. La razón para esto es que hg status” solamente le muestra aquellos ficheros “interesantes” — los que usted haya modificado o a aquellos sobre los que usted haya indicado a Mercurial hacer algo— de forma predeterminada. Si tiene un repositorio que contiene miles de ficheros, rara vez deseará saber cuáles de ellos están siendo seguidos por Mercurial, pero que no han cambiado. (De todas maneras, puede obtener tal información; más adelante hablaremos de ello.)

Cuando usted añade un fichero, Mercurial no hace nada con él inmediatamente. En cambio, tomará una instantánea del estado del fichero la próxima vez que usted consigne. Continuará haciendo seguimiento a los cambios que haga sobre el fichero cada vez que consigne, hasta que usted lo elimine.

5.1.1. Nombramiento explícito e implícito de ficheros

Mercurial tiene un comportamiento útil en el cual si a una orden, le pasa el nombre de un directorio, todas las órdenes lo interpretarán como “Deseo operar en cada fichero de este directorio y sus subdirectorios”.

1  $ mkdir b
2  $ echo b > b/b
3  $ echo c > b/c
4  $ mkdir b/d
5  $ echo d > b/d/d
6  $ hg add b
7  adding b/b
8  adding b/c
9  adding b/d/d
10  $ hg commit -m 'Added all files in subdirectory'

Tenga en cuenta que en este ejemplo Mercurial imprimió los nombres de los ficheros que se adicionaron, mientras que no lo hizo en el ejemplo anterior cuando adicionamos el fichero con nombre a.

En el último caso hicimos explícito el nombre del fichero que deseábamos adicionar en la línea de órdenes, y Mercurial asume en tales casos que usted sabe lo que está haciendo y no imprime información alguna.

Cuando hacemos implícitos los nombres de los ficheros dando el nombre de un directorio, Mercurial efectúa el paso extra de imprimir el nombre de cada fichero con el que va a hacer algo. Esto para aclarar lo que está sucediendo, y reducir en lo posible una sorpresa silenciosa pero fatal. Este comportamiento es común a la mayoría de órdenes en Mercurial.

5.1.2. Nota al margen: Mercurial trata ficheros, no directorios

Mercurial no da seguimiento a la información de los directorios. En lugar de eso tiene en cuenta las rutas de los ficheros. Antes de crear un fichero, primero crea todos los directorios que hagan falta para completar la ruta del mismo. Después de borrar un fichero, borra todos los directorios vacíos que estuvieran en la ruta del fichero borrado. Suena como una diferencia trivial, pero tiene una consecuencia práctica menor: no es posible representar un directorio completamente vacío en Mercurial.

Los directorios vacíos rara vez son útiles, y hay soluciones alternativas no intrusivas que usted puede emplear para obtener el efecto apropiado. Los desarrolladores de Mercurial pensaron que la complejidad necesaria para administrar directorios vacíos no valía la pena frente al beneficio limitado que esta característica podría traer.

Si necesita un directorio vacío en su repositorio, hay algunas formas de lograrlo. Una es crear un directorio, después hacer hg add” a un fichero “oculto” dentro de ese directorio. En sistemas tipo Unix, cualquier fichero cuyo nombre comience con un punto (“.”) es tratado como oculto por la mayoría de comandos y herramientas GUI. Esta aproximación se ilustra en la figura 5.1.


1  $ hg init hidden-example
2  $ cd hidden-example
3  $ mkdir empty
4  $ touch empty/.hidden
5  $ hg add empty/.hidden
6  $ hg commit -m 'Manage an empty-looking directory'
7  $ ls empty
8  $ cd ..
9  $ hg clone hidden-example tmp
10  updating working directory
11  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
12  $ ls tmp
13  empty
14  $ ls tmp/empty

Figura 5.1: Simular un directorio vacío con un fichero oculto

Otra forma de abordar la necesidad de un directorio vacío es simplemente crear uno en sus guiones de construcción antes de que lo necesiten.

5.2. Cómo dejar de hacer seguimiento a un fichero

Si decide que un fichero no pertenece a su repositorio, use la orden hg remove”; se borrará el fichero y le indicará a Mercurial que deje de hacerle seguimiento. Los ficheros eliminados se representan con “R” al usar hg status”.

1  $ hg init remove-example
2  $ cd remove-example
3  $ echo a > a
4  $ mkdir b
5  $ echo b > b/b
6  $ hg add a b
7  adding b/b
8  $ hg commit -m 'Small example for file removal'
9  $ hg remove a
10  $ hg status
11  R a
12  $ hg remove b
13  removing b/b

Después de hacer hg remove” a un fichero, Mercurial dejará de hacer seguimiento al mismo, incluso si recrea el fichero con el mismo nombre en su directorio de trabajo. Si decide recrear un fichero con el mismo nombre y desea que Mercurial le haga seguimiento, basta con hacerle hg add”. Mercurial sabrá que el fichero recientemente adicionado no está relacionado con el fichero anterior que tenía el mismo nombre.

5.2.1. Al eliminar un fichero no se afecta su historial

Es preciso tener en cuenta que eliminar un fichero tiene sólo dos efectos.

Al eliminar un fichero no se altera de ninguna manera el historial del mismo.

Si actualiza su directorio de trabajo a un conjunto de cambios en el cual el fichero que eliminó aún era tenido en cuenta, éste reaparecerá en el directorio de trabajo, con los contenidos que este tenía cuando se consignó tal conjunto de cambios. Si usted actualiza el directorio de trabajo a un conjunto de cambios posterior en el cual el fichero había sido eliminado, Mercurial lo eliminará de nuevo del directorio de trabajo.

5.2.2. Ficheros perdidos

Mercurial considera como perdido un fichero que usted borró, pero para el que no se usó hg remove”. Los ficheros perdidos se representan con “!” al visualizar hg status”. Las órdenes de Mercurial generalmente no harán nada con los ficheros perdidos.

1  $ hg init missing-example
2  $ cd missing-example
3  $ echo a > a
4  $ hg add a
5  $ hg commit -m 'File about to be missing'
6  $ rm a
7  $ hg status
8  ! a

Si su repositorio contiene un fichero que hg status” reporta como perdido, y desea que el mismo se vaya, se puede usar hg remove --after” posteriormente para indicarle a Mercurial que usted deseaba borrar tal fichero.

1  $ hg remove --after a
2  $ hg status
3  R a

Por otro lado, si borró un fichero perdido por accidente, puede usar hg revert nombre de fichero” para recuperar el fichero. Reaparecerá, sin modificaciones.

1  $ hg revert a
2  $ cat a
3  a
4  $ hg status

5.2.3. Nota al margen: ¿Por qué decirle explícitamente a Mercurial que elimine un fichero?

Es posible que se haya preguntado por qué Mercurial exige que usted le indique explícitamente que está borrando un fichero. Al principio del desarrollo de Mercurial, este permitía que usted borrara el fichero sin más; Mercurial se daría cuenta de la ausencia del fichero automáticamente después de la ejecución de hg commit”, y dejaría de hacer seguimiento al fichero. En la práctica, resultaba muy sencillo borrar un fichero accidentalmente sin darse cuenta.

5.2.4. Atajo útil—agregar y eliminar ficheros en un solo paso

Mercurial ofrece una orden combinada, hg addremove”, que agrega los ficheros que no tienen seguimiento y marca los ficheros faltantes como eliminados.

1  $ hg init addremove-example
2  $ cd addremove-example
3  $ echo a > a
4  $ echo b > b
5  $ hg addremove
6  adding a
7  adding b

La orden hg commit” se puede usar con la opción -A que aplica el mismo agregar-eliminar, seguido inmediatamente de una consignación.

1  $ echo c > c
2  $ hg commit -A -m 'Commit with addremove'
3  adding c

5.3. Copiar ficheros

Mercurial ofrece la orden hg copy” para hacer una copia nueva de un fichero. Cuando se copia un fichero con esta orden, Mercurial lleva un registro indicando que el nuevo fichero es una copia del fichero original. Los ficheros copiados se tratan de forma especial cuando usted hace una fusión con el trabajo de alguien más.

5.3.1. Resultados de copiar un fichero durante una fusión

Durante una fusión los cambios “siguen” una copia. Para ilustrar lo que esto significa, haremos un ejemplo. Comenzaremos con el mini repositorio usual que contiene un solo fichero

1  $ hg init my-copy
2  $ cd my-copy
3  $ echo line > file
4  $ hg add file
5  $ hg commit -m 'Added a file'

Debemos hacer algo de trabajo en paralelo, de forma que tengamos algo para fusionar. Aquí clonamos el repositorio.

1  $ cd ..
2  $ hg clone my-copy your-copy
3  updating working directory
4  1 files updated, 0 files merged, 0 files removed, 0 files unresolved

De vuelta en el repositorio inicial, usemos la orden hg copy” para hacer una copia del primer fichero que creamos.

1  $ cd my-copy
2  $ hg copy file new-file

Si vemos la salida de la orden hg status”, el fichero copiado luce tal como un fichero que se ha añadido normalmente.

1  $ hg status
2  A new-file

Pero si usamos la opción -C de la orden hg status”, se imprimirá otra línea: el fichero desde el cual fue copiado nuestro fichero recién añadido.

1  $ hg status -C
2  A new-file
3    file
4  $ hg commit -m 'Copied file'

Ahora, en el repositorio que clonamos, hagamos un cambio en paralelo. Adicionaremos una línea de contenido al fichero original que creamos.

1  $ cd ../your-copy
2  $ echo 'new contents' >> file
3  $ hg commit -m 'Changed file'

Hemos modificado el fichero file en este repositorio. Cuando jalemos los cambios del primer repositorio y fusionemos las dos cabezas, Mercurial propagará los cambios que hemos hecho localmente en file a su copia, new-file.

1  $ hg pull ../my-copy
2  pulling from ../my-copy
3  searching for changes
4  adding changesets
5  adding manifests
6  adding file changes
7  added 1 changesets with 1 changes to 1 files (+1 heads)
8  (run 'hg heads' to see heads, 'hg merge' to merge)
9  $ hg merge
10  merging file and new-file
11  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
12  (branch merge, don't forget to commit)
13  $ cat new-file
14  line
15  new contents

5.3.2. ¿Por qué los cambios se reflejan en las copias?

Este comportamiento de cambios en ficheros que se propagan a las copias de los ficheros parecería esotérico, pero en la mayoría de casos es absolutamente deseable. Es indispensable recordar que esta propagación solamente sucede cuando fusionamos. Por lo tanto si sobre un fichero se usa hg copy”, y se modifica el fichero original durante el curso normal de su trabajo, nada pasará.

Lo segundo a tener en cuenta es que las modificaciones solamente se propagarán en las copias únicamente si los repositorios de los cuales está jalando los cambios no saben de la copia.

Explicaremos a continuación la razón de este comportamiento de Mercurial. Digamos que yo he aplicado un arreglo de un fallo importante a un fichero fuente y consigné los cambios. Por otro lado, usted decidió hacer hg copy” sobre el fichero en su repositorio, sin saber acerca del fallo o sin ver el arreglo, y ha comenzado a trabajar sobre su copia del fichero.

Si jala y fusiona mis cambios y Mercurial no hubiera propagado los cambios en las copias, su fichero fuente tendría el fallo, a menos que usted haya recordado propagar el arreglo del fallo a mano, el mismo permanecería en su copia del fichero.

Mercurial previene esta clase de problemas, gracias a la propagación automática del cambio que arregló el fallo del fichero original. Hasta donde sé, Mercurial es el único sistema de control de revisiones que propaga los cambios en las copias de esta forma.

Cuando su historial de cambios tiene un registro de la copia y la subsecuente fusión, usualmente no es necesario propagar los cambios el fichero original a las copias del mismo, y por esta razón Mercurial propaga únicamente los cambios en las copias hasta este punto y no más allá.

5.3.3. Cómo hacer que los cambios no sigan a la copia?

Si por algún motivo usted decide que esta característica de propagación automática de cambios en las copias no es para usted, simplemente use la orden usual de su sistema para copiar ficheros (en sistemas tipo Unix, es cp), y posteriormente use hg add” sobre la nueva copia hecha a mano. Antes de hacerlo, de todas maneras, relea la sección 5.3.2, y tome una decisión asegurándose que este comportamiento no es el apropiado para su caso específico.

5.3.4. Comportamiento de la orden “hg copy

Cuando usa la orden hg copy”, Mercurial hace una copia de cada fichero fuente tal como se encuentra en el directorio actual. Esto significa que si usted hace modificaciones a un fichero, y le aplica hg copy” sin haber consignado primero los cambios, la nueva copia contendrá también las modificaciones que haya hecho hasta ese punto. (Este comportamiento me parece poco intuitivo, y por tal motivo lo menciono.)

La orden hg copy” actúa de forma parecida a la orden cp de Unix (puede usar el alias hg cp” si le es más cómodo). El último argumento es el destino, y todos los argumentos previos son las fuentes. Si solamente indica un fichero como la fuente, y el destino no existe, se crea un fichero nuevo con ese nombre.

1  $ mkdir k
2  $ hg copy a k
3  $ ls k
4  a

Si el destino es un directorio, Mercurial copia las fuentes en éste.

1  $ mkdir d
2  $ hg copy a b d
3  $ ls d
4  a  b

La copia de un directorio es recursiva, y preserva la estructura del directorio fuente.

1  $ hg copy c e
2  copying c/a/c to e/a/c

Si tanto la fuente como el destino son directorios, la estructura de la fuente se recrea en el directorio destino.

1  $ hg copy c d
2  copying c/a/c to d/c/a/c

De la misma forma que la orden hg rename”, si copia un fichero manualmente y desea que Mercurial sepa que ha copiado un fichero, basta con aplicar la opción --after a la orden hg copy”.

1  $ cp a z
2  $ hg copy --after a z

5.4. Renombrar ficheros

La necesidad de renombrar un fichero es más común que hacer una copia del mismo. La razón por la cual discutí la orden hg copy” antes de hablar acerca de cambiar el nombre de los ficheros, es que Mercurial trata el renombrar un fichero de la misma forma que una copia. Por lo tanto, saber lo que hace Mercurial cuando usted copia un fichero le indica qué esperar cuando renombra un fichero.

Cuando usa la orden hg rename”, Mercurial hace una copia de cada fichero fuente, lo borra y lo marca como fichero eliminado.

1  $ hg rename a b

La orden hg status” muestra la nueva copia del fichero como añadida y el fichero inicial de la copia, como eliminado.

1  $ hg status
2  A b
3  R a

De la misma forma en que se usa la orden hg copy”, debemos usar la opción -C de la orden hg status” para verificar que el fichero añadido realmente comienza a ser seguido por Mercurial como una copia del fichero original, ahora eliminado.

1  $ hg status -C
2  A b
3    a
4  R a

Igual que con hg remove” y hg copy”, puede indicársele a Mercurial acerca de un renombramiento inmediato con la opción --after. El comportamiento de la orden hg rename” y las opciones que acepta, son similares a la orden hg copy” en casi todo.

5.4.1. Renombrar ficheros y fusionar cambios

Dado que el renombrado de Mercurial se implementa como un copiar-y-eliminar, la misma propagación de cambios ocurre cuando usted fusiona después de renombrar como después de hacer una copia.

Si yo modifico un fichero y usted lo renombra a un nuevo fichero, y posteriormente fusionamos nuestros respectivos cambios, mi modificación al fichero bajo su nombre original se propagará en el fichero con el nuevo nombre. (Es lo que se esperaría que “simplemente funcione,” pero, no todos los sistemas de control de revisiones hacen esto.)

Aunque el hecho de que los cambios sigan la copia es una característica respecto a la cual usted puede estar de acuerdo y decir “si, puede ser útil,” debería ser claro que el seguimiento de cambios de un renombramiento es definitivamente importante. Sin esto, sería muy sencillo que los cambios se quedaran atrás cuando los ficheros se renombran.

5.4.2. Cambios de nombre divergentes y fusión

El caso de renombramiento con nombres divergentes ocurre cuando dos desarrolladores comienzan con un fichero—llamémoslo foo—en sus repositorios respectivos.

1  $ hg clone orig anne
2  updating working directory
3  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
4  $ hg clone orig bob
5  updating working directory
6  1 files updated, 0 files merged, 0 files removed, 0 files unresolved

Anne renombra el fichero a bar.

1  $ cd anne
2  $ hg mv foo bar
3  $ hg ci -m 'Rename foo to bar'

Mientras que Bob lo renombra como quux.

1  $ cd ../bob
2  $ hg mv foo quux
3  $ hg ci -m 'Rename foo to quux'

Veo esto como un conflicto porque cada desarrollador ha expresado intenciones diferentes acerca de cómo considera debería haberse nombrado el fichero.

¿Qué cree que debería pasar cuando fusionen su trabajo? El comportamiento de Mercurial es que siempre preserva ambos nombres cuando fusiona los conjuntos de cambios que contienen nombres divergentes.

1  # See http://www.selenic.com/mercurial/bts/issue455
2  $ cd ../orig
3  $ hg pull -u ../anne
4  pulling from ../anne
5  searching for changes
6  adding changesets
7  adding manifests
8  adding file changes
9  added 1 changesets with 1 changes to 1 files
10  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
11  $ hg pull ../bob
12  pulling from ../bob
13  searching for changes
14  adding changesets
15  adding manifests
16  adding file changes
17  added 1 changesets with 1 changes to 1 files (+1 heads)
18  (run 'hg heads' to see heads, 'hg merge' to merge)
19  $ hg merge
20  warning: detected divergent renames of foo to:
21   bar
22   quux
23  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
24  (branch merge, don't forget to commit)
25  $ ls
26  bar  quux

Tenga en cuenta que Mercurial le advierte acerca de nombres divergentes, pero deja que usted decida qué hacer con la divergencia después de la fusión.

5.4.3. Cambios de nombre convergentes y fusión

Otra clase de conflicto al cambiar el nombre de ficheros ocurre cuando dos personas eligen renombrar diferentes ficheros fuente al mismo destino. En este caso Mercurial aplica su maquinaria de fusión usual, y le permite a usted guiar la situación a una resolución adecuada.

5.4.4. Otros casos límite relacionados con renombramientos

Mercurial tiene un fallo de mucho tiempo en el cual no es capaz de fusionar cuando por un lado hay un fichero con un nombre dado, mientras que en otro hay un directorio con el mismo nombre. Esto está documentado como http://www.selenic.com/mercurial/bts/issue29Fallo de Mercurial No. 29.

1  $ hg init issue29
2  $ cd issue29
3  $ echo a > a
4  $ hg ci -Ama
5  adding a
6  $ echo b > b
7  $ hg ci -Amb
8  adding b
9  $ hg up 0
10  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
11  $ mkdir b
12  $ echo b > b/b
13  $ hg ci -Amc
14  adding b/b
15  created new head
16  $ hg merge
17  abort: Is a directory: /tmp/issue29HYiaCx/issue29/b

5.5. Recuperarse de equivocaciones

Mercurial tiene unas órdenes poderosas que le ayudarán a recuperarse de equivocaciones comunes.

La orden hg revert” le permite deshacer cambios que haya hecho a su directorio de trabajo. Por ejemplo, si aplicó hg add” a un fichero por accidente, ejecute hg revert” con el nombre del fichero que añadió, y en tanto que el fichero no haya sido tocado de forma alguna, no será adicionado, ni seguido por Mercurial. También puede usar hg revert” para deshacerse de cambios erróneos a un fichero.

Tenga en cuenta que la orden hg revert” se usa para cambios que no han sido consignados. Cuando haya consignado un cambio, si decide que era un error, puede hacer algo todavía, pero sus opciones pueden estar más limitadas.

Para obtener información acerca de la orden hg revert” y detalles de cómo tratar con cambios consignados, vea el capítulo 9.

Capítulo 6
Colaborar con otros

Debido a su naturaleza descentralizada, Mercurial no impone política alguna de cómo deben trabajar los grupos de personas. Sin embargo, si el control distribuido de versiones es nuevo para usted, es bueno tener herramientas y ejemplos a la mano al pensar en posibles modelos de flujo de trabajo.

6.1. La interfaz web de Mercurial

Mercurial tiene una poderosa interfaz web que provee bastantes capacidades útiles.

Para uso interactivo, la interfaz le permite visualizar uno o varios repositorios. Puede ver el historial de un repositorio, examinar cada cambio (comentarios y diferencias), y ver los contenidos de cada directorio y fichero.

Adicionalmente la interfaz provee notificaciones RSS de los cambios del repositorio. Esto le permite “subscribirse”a un repositorio usando su herramienta de lectura de notificaciones favorita, y ser notificado automáticamente de la actividad en el repositorio tan pronto como sucede. Me gusta mucho más este modelo que el estar suscrito a una lista de correo a la cual se envían las notificaciones, dado que no requiere configuración adicional de parte de quien sea que está administrando el repositorio.

La interfaz web también permite a los usuarios remotos clonar repositorios, jalar cambios, y (cuando el servidor está configurado para permitirlo) empujar cambios al mismo. El protocolo de entunelamiento HTTP de Mercurial comprime datos agresivamente, de forma que trabaja eficientemente incluso en conexiones de red con poco ancho de banda.

La forma más sencilla de empezar a trabajar con la interfaz web es usar su navegador para visitar un repositorio existente, como por ejemplo el repositorio principal de Mercurial en http://www.selenic.com/repo/hg?style=gitweb.

Si está interesado en proveer una interfaz web a sus propios repositorios, Mercurial ofrece dos formas de hacerlo. La primera es usando la orden hg serve”, que está enfocada a servir “de forma liviana” y por intervalos cortos. Para más detalles de cómo usar esta orden vea la sección 6.4 más adelante. Si tiene un repositorio que desea hacer disponible de forma permanente, Mercurial tiene soporte embebido para el estándar CGI (Common Gategay Interface), que es manejado por todos los servidores web comunes. Vea la sección 6.6 para los detalles de la configuración a través de CGI.

6.2. Modelos de colaboración

Con una herramienta adecuadamente flexible, tomar decisiones acerca del flujo de trabajo es mucho más un reto de ingeniería social que un problema técnico. Mercurial impone pocas limitaciones sobre cómo puede estructurar el flujo de trabajo en un proyecto, así que depende de usted y de su grupo definir y trabajar con un modelo que se ajuste a sus necesidades particulares.

6.2.1. Factores a tener en cuenta

El aspecto más importante que usted debe considerar de cualquier modelo es qué tan bien se ajusta a las necesidades y capacidades de la gente que lo usará. Esto puede parecer auto-evidente; aún así, usted no puede permitirse olvidarlo ni por un momento.

Una vez definí un modelo de flujo de trabajo que parecía ser perfectamente adecuado para mí, pero que causó una cantidad considerable de consternación y fricción dentro de mi equipo de desarrollo. A pesar de mis intentos de explicar porqué necesitábamos un conjunto complejo de ramas, y de cómo debían fluir los cambios entre ellas, algunos miembros del equipo se molestaron. Aunque ellos eran personas inteligentes, no querían prestar atención a las limitaciones bajo las cuales estábamos operando, o comparar las consecuencias de esas limitaciones con los detalles del modelo que yo estaba proponiendo.

No esconda bajo la alfombra los problemas sociales o técnicos que usted pueda preveer. Sin importar el esquema que usted use, debe hacer planes para enfrentar posibles errores o problemas. Considere añadir procedimientos automatizados para prevenir, o recuperarse rápidamente de, los problemas que usted pueda anticipar. A manera de ejemplo, si usted planea tener una rama en la vayan los cambios que no estén listos para producción, haría bien en pensar en la posibilidad de que alguien fusione accidentalmente dichos cambios en una rama para publicación. Usted podría evitar este problema en particular escribiendo un gancho que evite que se fusionen cambios desde ramas inapropiadas.

6.2.2. Anarquía informal

No sugeriría un enfoque de “todo vale” como algo sostenible, pero es un modelo que es fácil de entender, y que funciona perfectamente bien en unas cuantas situaciones inusuales.

Como un ejemplo, muchos proyectos tienen un grupo informal de colaboradores que rara vez se reúnen físicamente. A algunos grupos les gusta evitar el aislamiento de trabajar a distancia organizando “sprints” ocasionales. En un sprint, una cantidad de gente se reúne en un mismo sitio (el cuarto de conferencias de una empresa, el cuarto de reuniones de un hotel, ése tipo de lugares) y pasar varios días más o menos encerrados allí, trabajando intensamente en un puñado de proyectos.

Un sprint es el lugar perfecto para usar el comando hg serve”, ya que hg serve” no requiere una infraestructura especial de servidores. Usted puede empezar a trabajar con hg serve” en momentos, leyendo la sección 6.4 más abajo. Luego simplemente dígale a las personas cerca suyo que usted está ejecutando un servidor, envíeles la URL a través de un mensaje instantáneo, y tendrá de inmediato una forma rápida de trabajar juntos. Ellos pueden escribir su URL en un navegador web y revisar sus cambios rápidamente; o ellos pueden jalar un arreglo de fallo que usted haya hecho y verificarlo; o pueden clonar una rama que contiene una nueva característica y probarla.

Lo bueno, y lo malo, de hacer cosas de manera ad hoc como aquí, es que sólo las personas que saben de sus cambios, y dónde están, pueden verlos. Un enfoque tan informal sencillamente no puede escalarse más allá de un puñado de personas, porque cada individuo tiene que saber de n repositorios diferentes de los cuales jalar.

6.2.3. Un repositorio central único

Para proyectos pequeños migrando desde una herramienta centralizada de control de revisiones, tal vez la forma más fácil de empezar es hacer que los cambios vayan a través de un repositorio central compartido. Éste es también el “bloque base” para esquemas más ambiciosos de flujo de trabajo.

Los colaboradores empiezan clonando una copia de este repositorio. Ellos pueden jalar cambios de él siempre que lo necesiten, y algunos (o tal vez todos los) desarrolladores tienen permiso para empujar cambios de vuelta cuando estén listos para que los demás los vean.

Bajo este modelo, para los usuarios tiene sentido jalar cambios directamente entre ellos, sin ir a través del repositorio central. Considere un caso en el que yo tengo un arreglo tentativo de fallo, pero me preocupa que al publicarlo en el repositorio central rompa los árboles de todos los demás cuando lo jalen. Para reducir el potencial de daño, puedo pedirle a usted que clone mi repositorio en un repositorio personal suyo y lo pruebe. Esto nos permite aplazar este cambio potencialmente inseguro hasta que haya tenido algo de pruebas.

En este tipo de escenario, la gente usualmente utiliza el protocolo ssh para empujar cambios de manera segura al repositorio central, como se documenta en la sección 6.5. También es usual publicar una copia de sólo lectura del repositorio sobre HTTP usando CGI, como en la sección 6.6. Publicar a través de HTTP satisface las necesidades de aquellos que no tienen permiso para empujar, y de aquellos que desean usar navegadores web para explorar el historial del repositorio.

6.2.4. Trabajo con muchas ramas

Los proyectos de cierta talla tienden de manera natural a progresar de forma simultánea en varios frentes. En el caso del software, es común que un proyecto tenga versiones periódicas oficiales. Una versión puede entrar a “modo mantenimiento” por un tiempo después de su primera publicación; las versiones de mantenimiento tienden a contener solamente arreglos de fallos, no nuevas características. En paralelo con las versiones de mantenimiento, puede haber una o varias versiones futuras en desarrollo. La gente usa normalmente la palabra “rama” para referirse a una de las direcciones ligeramente distintas en las cuales avanza el desarrollo.

Mercurial está especialmente preparado para administrar un buen número de ramas simultáneas pero no idénticas. Cada “dirección de desarrollo” puede vivir en su propio repositorio central, y usted puede mezclar los cambios de una a otra cuando sea necesario. Dado que los repositorios son independientes entre sí, los cambios inestables de una rama de desarrollo nunca afectarán una rama estable a menos que alguien mezcle explícitamente los cambios.

A continuación hay un ejemplo de cómo podría hacerse esto en la práctica. Digamos que tiene una “rama principal” en un servidor central.

1  $ hg init main
2  $ cd main
3  $ echo 'This is a boring feature.' > myfile
4  $ hg commit -A -m 'We have reached an important milestone!'
5  adding myfile

Alguien lo clona, hace cambios locales, los prueba, y los empuja de vuelta.

Una vez que la rama principal alcanza un hito de proyecto se puede usar la orden hg tag” para dar un nombre permanente a la revisión del hito.

1  $ hg tag v1.0
2  $ hg tip
3  changeset:   1:471e84684405
4  tag:         tip
5  user:        Bryan O'Sullivan <bos@serpentine.com>
6  date:        Tue Feb 10 18:23:16 2009 +0000
7  summary:     Added tag v1.0 for changeset a1e37e7a184f
8  
9  $ hg tags
10  tip                                1:471e84684405
11  v1.0                               0:a1e37e7a184f

Digamos que en la rama principal ocurre más desarrollo.

1  $ cd ../main
2  $ echo 'This is exciting and new!' >> myfile
3  $ hg commit -m 'Add a new feature'
4  $ cat myfile
5  This is a boring feature.
6  This is exciting and new!

Cuando se usa la etiqueta con que se identificó la versión, la gente que clone el repositorio en el futuro puede usar hg update” para obtener una copia del directorio de trabajo igual a cuando se creó la etiqueta de la revisión que se consignó.

1  $ cd ..
2  $ hg clone -U main main-old
3  $ cd main-old
4  $ hg update v1.0
5  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
6  $ cat myfile
7  This is a boring feature.

Adicionalmente, justo después de que la rama principal se etiquete, alguien puede clonarla en el servidor a una nueva rama “estable”, también en el servidor.

1  $ cd ..
2  $ hg clone -rv1.0 main stable
3  requesting all changes
4  adding changesets
5  adding manifests
6  adding file changes
7  added 1 changesets with 1 changes to 1 files
8  updating working directory
9  1 files updated, 0 files merged, 0 files removed, 0 files unresolved

Alguien que requiera hacer un cambio en la rama estable puede clonar ese repositorio, hacer sus cambios, consignarlos, y empujarlos de vuelta.

1  $ hg clone stable stable-fix
2  updating working directory
3  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
4  $ cd stable-fix
5  $ echo 'This is a fix to a boring feature.' > myfile
6  $ hg commit -m 'Fix a bug'
7  $ hg push
8  pushing to /tmp/branchingOpMbpu/stable
9  searching for changes
10  adding changesets
11  adding manifests
12  adding file changes
13  added 1 changesets with 1 changes to 1 files

Puesto que los repositorios de Mercurial son independientes, y que Mercurial no mueve los cambios de un lado a otro automáticamente, las ramas estable y principal están aisladas la una de la otra. Los cambios que haga en la rama principal no se “filtran” a la rama estable y viceversa.

Es usual que los arreglos de fallos de la rama estable deban hacerse en la rama principal también. En lugar de reescribir el arreglo del fallo en la rama principal, usted puede jalar y mezclar los cambios de la rama estable a la principal, y Mercurial traerá tales arreglos por usted.

1  $ cd ../main
2  $ hg pull ../stable
3  pulling from ../stable
4  searching for changes
5  adding changesets
6  adding manifests
7  adding file changes
8  added 1 changesets with 1 changes to 1 files (+1 heads)
9  (run 'hg heads' to see heads, 'hg merge' to merge)
10  $ hg merge
11  merging myfile
12  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
13  (branch merge, don't forget to commit)
14  $ hg commit -m 'Bring in bugfix from stable branch'
15  $ cat myfile
16  This is a fix to a boring feature.
17  This is exciting and new!

La rama principal aún contendrá los cambios que no están en la estable y contendrá además todos los arreglos de fallos de la rama estable. La rama estable permanece incólume a tales cambios.

6.2.5. Ramas de características

En proyectos grandes, una forma efectiva de administrar los cambios es dividir el equipo en grupos más pequeños. Cada grupo tiene una rama compartida, clonada de una rama “principal” que conforma el proyecto completo. Aquellos que trabajan en ramas individuales usualmente están aislados de los desarrollos de otras ramas.


PICwidth=]feature-branches

Figura 6.1: Ramas de Características

Cuando se considera que una característica particular está en buena forma, alguien de ése equipo de características jala y fusiona de la rama principal a la rama de características y empuja de vuelta a la rama principal.

6.2.6. El tren de publicación

Algunos proyectos se organizan al estilo“tren”: una versión se planifica para ser liberada cada cierto tiempo, y las características que estén listas cuando ha llegado el “tren”, se incorporan.

Este modelo tiene cierta similitud a las ramas de características. La diferencia es que cuando una característica pierde el tren, alguien en el equipo de características jala y fusiona los cambios que se fueron en la versión liberada hacia la rama de característica, y el trabajo continúa sobre lo fusionado para que la característica logre estar en la próxima versión.

6.2.7. El modelo del kernel Linux

El desarrollo del kernel Linux tiene una estructura jerárquica bastante horizontal, rodeada de una nube de caos aparente. Dado que la mayoría de los desarrolladores de Linux usan git, una herramienta de control de versiones distribuida con capacidades similares a Mercurial, resulta de utilidad describir la forma en que el trabajo fluye en tal ambiente; si le gustan éstas ideas, la aproximación se traduce bien de Git a Mercurial.

En el centro de la comunidad está Linus Torvalds, el creador de Linux. Él publica un único repositorio que es considerado el árbol “oficial” actual por la comunidad completa de desarrolladores. Cualquiera puede clonar el árbol de Linus, pero él es muy selectivo respecto a los árboles de los cuales jala.

Linus tiene varios “lugartenientes de confianza”. Como regla, él jala todos los cambios que ellos publican, en la mayoría de los casos sin siquiera revisarlos. Algunos de sus lugartenientes generalmente aceptan ser los “mantenedores”, responsables de subsistemas específicos dentro del kernel. Si un hacker cualquiera desea hacer un cambio a un subsistema y busca que termine en el árbol de Linus, debe encontrar quién es el mantenedor del subsistema y solicitarle que tome su cambio. Si el mantenedor revisa los cambios y está de acuerdo en tomarlos, éstos pasarán al árbol de Linus a su debido tiempo.

Cada lugarteniente tiene su forma particular de revisar, aceptar y publicar los cambios; y para decidir cuando presentarlos a Linus. Adicionalmente existen varias ramas conocidas que mucha gente usa para propósitos distintos. Por ejemplo, algunas personas mantienen repositorios “estables” de versiones anteriores del kernel, a los cuales aplican arreglos de fallos críticos cuando es necesario. Algunos mantenedores publican varios árboles: uno para cambios experimentales; uno para cambios que van a ofrecer al mantenedor principal; y así sucesivamente. Otros publican solo un árbol.

Este modelo tiene dos características notables. La primera es que es de “jalar exclusivamente”. Usted debe solicitar, convencer o incluso rogar a otro desarrollador para que tome sus cambios, porque casi no hay árboles en los cuales más de una persona pueda publicar, y no hay forma de publicar cambios en un árbol que alguien más controla.

La segunda es que está basado en reputación y meritocracia. Si usted es un desconocido, Linus probablemente ignorará sus cambios, sin siquiera responderle. Pero el mantenedor de un subsistema probablemente los revisara, y los acogerá en caso de que aprueben su criterio de aplicabilidad. A medida que usted ofrezca “mejores” cambios a un mantenedor, habrá más posibilidad de que se confíe en su juicio y se acepten los cambios. Si usted es reconocido y mantiene una rama durante bastante tiempo para algo que Linus no ha aceptado, personas con intereses similares pueden jalar sus cambios regularmente para estar al día con su trabajo.

La reputación y meritocracia no necesariamente son transversales entre “personas” de diferentes subsistemas. Si usted es un hacker respetado pero especializado en almacenamiento, y trata de arreglar un fallo de redes, tal cambio puede recibir un nivel de escrutinio de un mantenedor de redes comparable con el que se le haría a un completo extraño.

Personas que vienen de proyectos con un ordenamiento distinto, sienten que el proceso comparativamente caótico del Kernel Linux es completamente lunático. Es objeto de los caprichos individuales; la gente desecha cambios cuando lo desea; y el ritmo de desarrollo es alucinante. A pesar de eso Linux es una pieza de software exitosa y bien reconocida.

6.2.8. Solamente jalar frente a colaboración pública

Una fuente perpetua de discusiones en la comunidad de código abierto yace en la afirmación de que el modelo de desarrollo en el cual la gente solamente jala cambios de otros “es mejor que” uno en el cual muchas personas pueden publicar cambios a un repositorio compartido.

Típicamente los partidarios del modelo de publicar usan las herramientas que se apegan a este modelo. Si usted usa una herramienta centralizada de control de versiones como Subversion, no hay forma de elegir qué modelo va a usar: la herramienta le ofrece publicación compartida, y si desea hacer cualquier otra cosa, va a tener que tomar una aproximación artificial (tal como aplicar parches a mano).

Una buena herramienta de control de versiones distribuida, tal como Mercurial, soportará los dos modelos. Usted y sus colaboradores pueden estructurar cómo trabajarán juntos basados en sus propias necesidades y preferencias, sin tener que llevar a cabo las peripecias que la herramienta les obligue a hacer.

6.2.9. Cuando la colaboración encuentra la administración ramificada

Una vez que usted y su equipo configuren algunos repositorios compartidos y comiencen a propagar cambios entre sus repositorios locales y compartidos, usted comenzará a encarar un reto relacionado, pero un poco distinto: administrar las direcciones en las cuales su equipo puede moverse. A pesar de que está íntimamente ligado acerca de cómo interactúa su equipo, este es un tema lo suficientemente denso para ameritar un tratamiento aparte, en el capítulo 8.

6.3. Aspectos técnicos de la colaboración

Lo que resta del capítulo lo dedicamos a las cuestiones de servir datos a sus colaboradores.

6.4. Compartir informalmente con “hg serve

La orden hg serve” de Mercurial satisface de forma espectacular las necesidades de un grupo pequeño, acoplado y ágil. También sirve como una demostración de cómo se siente usar los comandos usando la red.

Ejecute hg serve” dentro de un repositorio, y en pocos segundos se iniciará un servidor HTTP especializado; aceptará conexiones desde cualquier cliente y servirá datos de este repositorio mientras lo mantenga funcionando. Todo el que conozca la URL del servidor que usted ha iniciado, y que pueda comunicarse con su computador a través de la red, puede usar un navegador web o Mercurial para leer datos del repositorio. Un URL para una instancia de hg serve” ejecutándose en un portátil debería lucir similar a http://my-laptop.local:8000/.

La orden hg serveno es un servidor web de propósito general. Solamente puede hacer dos cosas:

En particular, hg serve” no permitirá que los usuarios remotos modifiquen su repositorio. Es de tipo sólo lectura.

Si está familiarizándose con Mercurial, no hay nada que le impida usar hg serve” para servir un repositorio en su propio computador, y posteriormente usar órdenes como hg clone”, hg incoming”, para comunicarse con el servidor como si el repositorio estuviera alojado remotamente. Esto puede ayudarle a adecuarse rápidamente a usar comandos en repositorios alojados en la red.

6.4.1. Cuestiones adicionales para tener en cuenta

Dado que permite lectura sin autenticación a todos sus clientes, debería usar hg serve” exclusivamente en ambientes en los cuáles esto no sea importante, o en los cuales tenga control completo acerca de quién puede acceder a su red y jalar cambios de su repositorio.

La orden hg serve” no tiene conocimiento acerca de programas cortafuegos que puedan estar instalados en su sistema o en su red. No puede detectar o controlar sus cortafuegos. Si otras personas no pueden acceder a su instancia de hg serve”, lo siguiente que debería hacer (después de asegurarse que tienen el URL correcto) es verificar su configuración de cortafuegos.

De forma predeterminada, hg serve” escucha conexiones entrantes en el puerto 8000. Si otro proceso está escuchando en tal puerto, usted puede especificar un puerto distinto para escuchar con la opción -p.

Normalmente, cuando se inicia hg serve”, no muestra ningún mensaje, lo cual puede ser algo desconcertante. Si desea confirmar que en efecto está ejecutándose correctamente, y averiguar qué URL debería enviar a sus colaboradores, inícielo con la opción -v.

6.5. Uso del protocolo Secure Shell (ssh)

Usted puede publicar y jalar cambios en la red de forma segura usando el protocolo Secure Shell (ssh). Para usarlo exitosamente, tendrá que hacer algo de configuración a nivel de cliente o de servidor.

Si no está familiarizado con ssh, es un protocolo de red que le permite comunicarse de manera segura con otro computador. Para usarlo con Mercurial, deberá crear una o más cuentas de usuario en un servidor de forma tal que los usuarios remotos puedan entrar y ejecutar órdenes.

(Si ssh le es familiar, probablemente encontrará elemental una porción del material a continuación.)

6.5.1. Cómo leer y escribir URLs de ssh

Los URLs de ssh tienden a lucir de la siguiente forma:

1  ssh://bos@hg.serpentine.com:22/hg/hgbook

  1. La parte “ssh://” le indica a Mercurial que use el protocolo ssh.
  2. El componente “bos@” indica el nombre del usuario que está entrando al servidor. Puede omitirlo si el usuario remoto coincide con el usuario local.
  3. hg.serpentine.com” es el nombre del servidor al cual se desea entrar.
  4. El “:22” identifica el número del puerto en el servidor al cual se conectará. El predeterminado es el 22, así que solamente necesitará especificar esa porción si no está usando el puerto 22.
  5. La última porción del URL es la ruta local del repositorio en el servidor.

El componente de la ruta del URL para ssh es una fuente de confusión, puesto que no hay una forma estándar para que las herramientas puedan interpretarlo. Algunos programas se comportan de manera distinta a otros cuando manipulan estas rutas. No es la situación ideal, pero es muy poco probable que vaya a cambiar. Por favor lea los párrafos siguientes cuidadosamente.

Mercurial trata la ruta al repositorio en el servidor como relativa al directorio personal del usuario remoto. Por ejemplo, si el usuario foo en el servidor tiene el directorio personal /home/foo, entonces un URL ssh que tenga como ruta a bar realmente se refiere al directorio /home/foo/bar.

Si desea especificar una ruta relativa a otro directorio de usuario, puede usar una ruta que comience con una virgulilla, seguida del nombre del usuario (llamémosle otrousuario), así

1  ssh://server/~otheruser/hg/repo

Y si realmente desea especifica una ruta absoluta en el servidor, comience con el componente de la ruta con dos barras, como en el siguiente ejemplo:

1  ssh://server//absolute/path

6.5.2. Encontrar un cliente ssh para su sistema

Casi todos los sistemas tipo Unix vienen con OpenSSH preinstalado. Si usted está usando un sistema de estos, ejecute which ssh para identificar dónde está instalada la orden ssh (usualmente estará en /usr/bin). Si por casualidad no está presente, vea la documentación de sus sistema para averiguar cómo instalarlo.

En Windows, primero tendrá que descargar un cliente adecuado. Hay dos alternativas:

En cualquier caso, tendrá que editar su fichero Mercurial.ini para indicarle a Mercurial dónde encontrar la orden real del cliente. Por ejemplo, si está usando PuTTY, tendrá que usar la orden plink como un cliente de línea de comandos para ssh.

1  [ui]
2  ssh = C:/ruta/a/plink.exe -ssh -i "C:/ruta/a/mi/llave/privada"
Nota: La ruta a plink no debería contener espacios o caracteres en blanco, o Mercurial no podrá encontrarlo correctamente (por lo tanto, probablemente no sería buena idea colocarlo en C:Program Filesa

6.5.3. Generar un par de llaves

Para evitar la necesidad de teclear una clave de forma repetitiva cada vez que necesita usar el cliente, recomiendo generar un par de llaves. En un sistema tipo Unix, la orden ssh-keygen se encargará de la tarea. En Windows, si está usando PuTTY, necesitará la orden puttygen.

Cuando genera un par de llaves, se aconseja comedidamente protegerlas con una frase clave. (La única oportunidad en la cual usted no querría hacerlo, es cuando está usando el protocolo ssh para tareas automatizadas en una red segura.)

No basta con generar un par de llaves. Se requiere adicionar la llave pública al conjunto de llaves autorizadas del usuario que usted usa para acceder a la máquina remota. Para aquellos servidores que usen OpenSSH (la gran mayoría), significará añadir la llave pública a la lista en el fichero llamado authorized_keys en su directorio .ssh.

En sistemas tipo Unix, su llave pública tendrá la extensión .pub. Si usa puttygen en Windows, puede guardar la llave pública en un fichero de su elección, o pegarla desde la ventana en la cual se despliega directamente en el fichero authorized_keys.

6.5.4. Uso de un agente de autenticación

Un agente de autenticación es un demonio que almacena frases clave en memoria (olvidará las frases clave si sale de su sesión y vuelve a entrar). Un cliente ssh notará si el agente está corriendo, y le solicitará una frase clave. Si no hay un agente de autenticación corriendo, o el agente no almacena la frase clave necesaria, tendrá que teclear su frase clave cada vez que Mercurial intente comunicarse con un servidor para usted (p.e. cada vez que jale o publique cambios).

El problema de almacenar frases claves en un agente es que es posible para un atacante bien preparado recuperar el texto plano de su frase clave, en algunos casos incluso si su sistema ya ha sido reiniciado. Es su decisión si es un riesgo aceptable. Lo que sí es seguro es que evita reteclear.

En sistemas tipo Unix, el agente se llama ssh-agent, y usualmente se ejecuta automáticamente cuando usted inicia sesión. Tendrá que usar la orden ssh-add para añadir frases claves al agente. En Windows, si está usando PuTTY, la orden pageant actúa como el agente. Él añade un icono a su barra del sistema que le permitirá administrar las frases clave almacenadas.

6.5.5. Configurar el lado del servidor apropiadamente

Dado que puede ser dispendioso configurar ssh si es algo nuevo para usted, hay una variedad de cosas que podrían ir mal. Añada a eso Mercurial y hay mucho más en qué pensar. La mayor parte de estos problemas potenciales ocurren en el lado del servidor, no en el cliente. Las buenas noticias es que una vez tiene una configuración funcional, usualmente ésta continuará trabajando indefinidamente.

Antes de intentar que Mercurial hable con un servidor ssh, es mejor asegurarse de que puede usar la orden normal ssh o putty para comunicarse primero con el servidor. Si tiene problemas usando estas órdenes directamente, de seguro Mercurial no funcionará. Pero aún, esto esconderá el problema subyacente. Cuando desee revisar un problema relacionado con ssh y Mercurial, debería asegurarse primero que las órdenes de ssh en el lado del cliente funcionan primero, antes de preocuparse por si existe un problema con Mercurial.

Lo primero para asegurar en el lado del servidor es que usted pueda iniciar sesión desde otra máquina. Si no puede entrar usando ssh o putty, el mensaje de error que obtenga le puede dar pistas de qué ha ido mal. Los problemas más comunes son los siguientes:

En resumen, si tiene problemas al comunicarse con el demonio ssh del servidor, primero asegúrese de que se esté ejecutando. En muchos sistemas estará instalado, pero deshabilitado de forma predeterminada. Una vez que haya hecho esto tendrá que revisar si el cortafuegos del servidor está configurado para recibir conexiones entrantes en el puerto en el cual está escuchando el demonio de ssh (usualmente el 22). No trate de buscar otras posibilidades exóticas o configuraciones erradas hasta que haya revisado primero estas dos.

Si está usando un agente de autenticación en el lado del cliente para almacenar las frase claves de sus contraseñas, debería poder entrar al servidor sin necesidad de que se le soliciten frases claves o contraseñas. Si se le pregunta alguna, a continuación algunas posibilidades:

Si se le solicita la clave del usuario remoto, hay otras posibilidades que deben revisarse:

En un mundo ideal, debería poder ejecutar la siguiente orden exitosamente, y debería imprimir exactamente una línea de salida, la fecha y hora actual.

1  ssh miservidor date

Si en su servidor tiene guión que se ejecuta a la entrada e imprime letreros o cualquier otra cosa, incluso cuando se ejecutan órdenes no interactivas como esta, debería arreglarlo antes de continuar, de forma que solamente imprima algo si se ejecuta interactivamente. De otra forma estos letreros al menos llenarán la salida de Mercurial. Incluso podrían causar problemas potenciales cuando se ejecuten órdenes de forma remota. Mercurial intenta detectar e ignorar los letreros en sesiones no interactivas de ssh, pero no es a prueba de tontos. (Si edita sus guiones de entrada en el servidor, la forma usual de ver si un guión de línea de comandos se ejecuta en un intérprete interactivo es verificar el código de retorno de la orden tty -s.)

Cuando verifique que el venerado ssh funciona en su servidor, el siguiente paso es asegurarse de que Mercurial corre en el servidor. La siguiente orden debería ejecutarse satisfactoriamente:

1  ssh miservidor hg version

Si ve un mensaje de error en lugar de la salida usual de hg version”, será porque Mercurial no fue instalado en /usr/bin. Si este es el caso, no se preocupe; no necesita hacerlo. Pero debería revisar los posibles problemas:

Si puede ejecutar hg version” sobre una conexión ssh, ¡felicitaciones! Ha logrado la interacción entre el cliente y el servidor. Ahora debería poder acceder a los repositorios de Mercurial que tiene el usuario en el servidor. Si tiene problemas con Mercurial y ssh en este punto, intente usar la opción --debug para tener información más clara de lo que está sucediendo.

6.5.6. Compresión con ssh

Mercurial no comprime datos cuando usa el protocolo ssh, dado que el protocolo puede comprimir datos transparentemente. Pero el comportamiento predeterminado del cliente ssh es no utilizar compresión.

Sobre cualquier red distinta a una LAN rápida (incluso con una red inalámbrica), hacer uso de compresión puede mejorar el rendimiento de las operaciones de Mercurial que involucren la red. Por ejemplo, sobre WAN, alguien ha medido que la compresión reduce la cantidad de tiempo requerido para clonar un repositorio particularmente grande de 51 minutos a 17 minutos.

Tanto ssh como plink aceptan la opción -C para activar la compresión. Puede editar fácilmente su hgrc para habilitar la compresión para todos los usos de Mercurial con el protocolo ssh.

1  [ui]
2  ssh = ssh -C

Si usa ssh, puede reconfigurarlo para que siempre use compresión cuando se comunique con su servidor. Para hacerlo, edite su fichero .ssh/config (que puede no existir aún), de la siguiente forma:

1  Host hg
2    Compression yes
3    HostName hg.ejemplo.com

Que define un alias, hg. Cuando lo usa con la orden ssh o con una URL de Mercurial con protocolo ssh, hará que ssh se conecte a hg.ejemplo.com usando compresión. Esto le brindará un nombre más corto para teclear, junto con compresión, los cuales por derecho propio son buenos.

6.6. Servir sobre HTTP usando CGI

Dependiendo de qué tan ambicioso sea, configurar la interfaz CGI de Mercurial puede tomar desde unos minutos hasta varias horas.

Comenzaremos con el ejemplo más sencillo, y nos dirigiremos hacia configuraciones más complejas. Incluso para el caso más básico necesitará leer y modificar la configuración de su servidor web.

Nota: Configurar un servidor web es una actividad compleja, engorrosa y altamente dependiente del sistema. De ninguna manera podremos cubrir todos los casos posibles con los cuales pueda encontrarse. Use su discreción y juicio respecto a las siguientes secciones. Prepárese para cometer muchas equivocaciones, y emplear bastante tiempo leyendo las bitácoras de error de su servidor.

6.6.1. Lista de chequeo de la configuración del servidor web

Antes de continuar, tómese un tiempo para revisar ciertos aspectos de la configuración de su sistema:

  1. ¿Tiene un servidor web? Mac OS X viene con Apache, pero otros sistemas pueden no tener un servidor web instalado.
  2. Si tiene un servidor web instalado, ¿Está ejecutándose? En la mayoría de sistemas, aunque esté presente, puede no estar habilitado de forma predeterminada.
  3. ¿Está configurado su servidor para permitir ejecutar programas CGI en el directorio donde planea hacerlo? Casi todos los servidores de forma predeterminada deshabilitan explícitamente la habilidad de ejecutar programas CGI.

Si no tiene un servidor web instalado, y no tiene experiencia configurando Apache, debería considerar usar el servidor web lighttpd en lugar de Apache. Apache tiene una reputación bien ganada por su configuración barroca y confusa. A pesar de que lighttpd tiene menos características que Apache en ciertas áreas, muchas de ellas no son relevantes para servir repositorios de Mercurial. Y definitivamente es mucho más sencillo comenzar con lighttpd que con Apache.

6.6.2. Configuración básica de CGI

En sistemas tipo Unix es común que los usuarios tengan un subdirectorio con un nombre como public_html en su directorio personal, desde el cual pueden servir páginas web. Un fichero llamado foo en este directorio será visible en una URL de la forma http://www.example.com/ĩusername/foo.

Para comenzar, encuentre el guión hgweb.cgi que debería estar presente en su instalación de Mercurial. Si no puede encontrar rápidamente una copia local en su sistema, puede descargarlo del repositorio principal de Mercurial en http://www.selenic.com/repo/hg/raw-file/tip/hgweb.cgi.

Tendrá que copiar este guión en su directorio public_html, y asegurarse que sea ejecutable.

1  cp .../hgweb.cgi ~/public_html
2  chmod 755 ~/public_html/hgweb.cgi

El argumento 755 de la orden chmod es un poco más general que hacerlo ejecutable: asegura que el guión sea ejecutable por cualquiera, y que el “grupo” y los “otros” no tengan permiso de escritura. Si dejara los permisos de escritura abiertos, el subsistema suexec de Apache probablemente se negaría a ejecutar el guión. De hecho, suexec también insiste en que el directorio en el cual reside el guión no tenga permiso de escritura para otros.

1  chmod 755 ~/public_html

¿Qué podría resultar mal?

Cuando haya ubicado el CGI en el sitio correspondiente, abra un navegador e intente visitar el URL http://myhostname/ myuser/hgweb.cgi, sin dejarse abatir por un error. Hay una alta probabilidad de que esta primera visita al URL sea fallida, y hay muchas razones posibles para este comportamiento. De hecho, podría toparse con cada uno de los errores que describimos a continuación, así que no deje de leerlos cuidadosamente. A continuación presento los problemas que yo tuve en un sistema con Fedora 7, con una instalación nueva de Apache, y una cuenta de usuario que creé específicamente para desarrollar este ejercicio.

Su servidor web puede tener directorios por usuario deshabilitados. Si usa Apache, busque el fichero de configuración que contenga la directiva UserDir. Si no está presente en sitio alguno, los directorios por usuario están deshabilitados. Si la hay, pero su valor es disabled, los directorios por usuario estarán deshabilitados. En caso contrario, la directiva UserDir tendrá el nombre del subdirectorio bajo el cual Apache mirará en el directorio de cada usuario, por ejemplo public_html.

Los permisos de sus ficheros pueden ser demasiado restrictivos. El servidor web debe poder recorrer su directorio personal y los directorios que estén bajo public_html, además de tener permiso para leer aquellos que estén adentro. A continuación una receta rápida para hacer que sus permisos estén acordes con las necesidades básicas.

1  chmod 755 ~
2  find ~/public_html -type d -print0 | xargs -0r chmod 755
3  find ~/public_html -type f -print0 | xargs -0r chmod 644

Otra posibilidad con los permisos es que obtenga una ventana completamente en blanco cuando trata de cargar el guión. En este caso, es posible que los permisos que tiene son demasiado permisivos. El subsistema suexec de Apache no ejecutará un guión que tenga permisos de escritura para el grupo o el planeta, por ejemplo.

Su servidor web puede estar configurado para evitar la ejecución de programas CGI en los directorios de usuario. A continuación presento una configuración predeterminada por usuario en mi sistema Fedora.

1  <Directory /home/⋆/public_html>
2      AllowOverride FileInfo AuthConfig Limit
3      Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
4      <Limit GET POST OPTIONS>
5          Order allow,deny
6          Allow from all
7      </Limit>
8      <LimitExcept GET POST OPTIONS>
9          Order deny,allow
10          Deny from all
11      </LimitExcept>
12  </Directory>

Si encuentra un grupo de instrucciones de Directory similares en su configuración de Apache, la directiva a revisar es Options. Adicione ExecCGI al final de esta lista en caso de que haga falta y reinicie su servidor web.

Si resulta que Apache le muestra el texto del guión CGI en lugar de ejecutarlo, necesitará o bien descomentar (si se encuentra presente) o adicionar una directiva como la siguiente:

1  AddHandler cgi-script .cgi

Otra posibilidad es que observe una traza de Python en colores informando que no puede importar un módulo relacionado con mercurial. ¡Esto es un gran progreso! El servidor es capaz de ejecutar su guión CGI. Este error solamente ocurrirá si está ejecutando una instalación privada de Mercurial en lugar de una instalación para todo el sistema. Recuerde que el servidor que ejecuta el programa CGI no cuenta con variables de entorno de las cuales usted sí dispone en una sesión interactiva. Si este error le ocurre, edite su copia de hgweb.cgi y siga las indicaciones dentro del mismo para establecer de forma adecuada su variable de entorno PYTHONPATH.

Finalmente, puede que se encuentre con otra traza a todo color de Python al visitar el URL: esta seguramente se referirá a que no puede encontrar /path/to/repository1. Edite su guión hgweb.cgi y reemplace la cadena /path/to/repository con la ruta completa al repositorio que desea servir.

En este punto, cuando trate de recargar la página, deberá tener una linda vista HTML del historial de su repositorio. ¡Uff!

Configuración de lighttpd

En mi intención de ser exhaustivo, intenté configurar lighttpd, un servidor web con creciente aceptación, para servir los repositorios de la misma forma en que lo describí anteriormente con Apache. Ya superé los problemas que mostré con Apache, muchos de los cuáles no son específicos del servidor. Por lo tanto estaba seguro de que mis permisos para directorios y ficheros eran correctos y que mi guión hgweb.cgi también lo era.

Dado que ya Apache estaba en ejecución correctamente, lograr que lighttpd sirviera mi repositorio fue rápido (en otras palabras, si está tratando de usar lighttpd, deberá leer la sección de Apache). Primero tuve que editar la sección mod_access para habilitar mod_cgi y mod_userdir, los cuales estaban inhabilitados en mi instalación predeterminada. Añadí posteriormente unas líneas al final del fichero de configuración, para hacer lo propio con los módulos.

1  userdir.path = "public_html"
2  cgi.assign = ( ".cgi" => "" )

Hecho esto, lighttpd funcionó inmediatamente para mí. Si hubiera configurado lighttpd antes que Apache, habría tenido casi los mismos problemas a nivel de configuración del sistema que con Apache. De todas maneras, considero que lighttpd es bastante más sencillo de configurar que Apache, a pesar de haber usado Apache por lo menos por una década, y de que esta fue mi primera experiencia con lighttpd.

6.6.3. Compartir varios repositorios con un guión CGI

El guión hgweb.cgi permite publicar únicamente un repositorio, una restricción frustrante. Si desea publicar más de uno sin complicarse con varias copias del mismo guión, cada una con un nombre distinto, resulta mucho mejor usar el guión hgwebdir.cgi.

El procedimiento para configurar hgwebdir.cgi tiene una porción adicional respecto al trabajo requerido con hgweb.cgi. Primero se debe obtener una copia del guión. Si no tiene una a mano, puede descargarla del ftp principal del repositorio de Mercurial en http://www.selenic.com/repo/hg/raw-file/tip/hgwebdir.cgi.

Necesitará una copia del guión en su directorio public_html, y debe asegurarse de que tenga permisos de ejecución.

1  cp .../hgwebdir.cgi ~/public_html
2  chmod 755 ~/public_html ~/public_html/hgwebdir.cgi

Con la configuración básica, intente visitar en su navegador http://myhostname/ myuser/hgwebdir.cgi. Debería mostrar una lista vacía de repositorios. Si obtiene una ventana en blanco o un mensaje de error, verifique la lista de problemas potenciales en la sección 6.6.2.

El guión hgwebdir.cgi se apoya en un fichero externo de configuración. De forma predeterminada, busca un fichero llamado hgweb.config en el mismo directorio. Tendrá que crear el fichero, y permitir lectura de todo el mundo. El formato del fichero es similar a un fichero “ini” de Windows, que puede ser interpretado por el módulo ConfigParser [Pyt] de Python.

La forma más sencilla de configurar hgwebdir.cgi es mediante una sección llamada collections. Esta publicará automáticamente todos los repositorios en los directorios que usted especifique. La sección debería lucir así:

1  [collections]
2  /mi/ruta = /mi/ruta

Mercurial lo interpreta buscando el nombre del directorio que esté a la derecha del símbolo “=”; ubicando repositorios en la jerarquía de directorios; y usando el texto a la izquierda para eliminar el texto de los nombres que mostrará en la interfaz web. El componente restante de la ruta después de esta eliminación usualmente se llama “ruta virtual”.

Dado el ejemplo de arriba, si tenemos un repositorio cuya ruta local es /mi/ruta/este/repo, el guión CGI eliminará la porción inicial /mi/ruta del nombre y publicará el repositorio con una ruta virtual este/repo. Si el URL base de nuestro guión CGI es http://myhostname/ myuser/hgwebdir.cgi, el URL completo al repositorio será http://myhostname/ myuser/hgwebdir.cgi/this/repo.

Si reemplazamos /mi/ruta en el lado izquierdo de este ejemplo con /mi, hgwebdir.cgi eliminará solamente /mi del nombre del repositorio, y nos ofrecerá la ruta virtual ruta/este/repo en lugar de este/repo.

El guión hgwebdir.cgi buscará recursivamente en cada directorio listado en la sección collections de su fichero de configuración, pero no hará el recorrido recursivo dentro de los repositorios que encuentre.

El mecanismo de collections permite publicar fácilmente repositorios de una forma “hacer y olvidar”. Solamente requiere configurar el guión CGI y el fichero de configuración una vez. Después de eso puede publicar y sacar de publicación un repositorio en cualquier momento incluyéndolo o excluyéndolo de la jerarquía de directorios en la cual le haya indicado a hgwebdir.cgi que mirase.

Especificación explícita de los repositorios a publicar

Además del mecanismo collections, el guión hgwebdir.cgi le permite publicar una lista específica de repositorios. Para hacerlo, cree una sección paths, con los contenidos de la siguiente forma:

1  [paths]
2  repo1 = /mi/ruta/a/un/repo
3  repo2 = /ruta/a/otro/repo

En este caso, la ruta virtual (el componente que aparecerá en el URL) está en el lado derecho de cada definición, mientras que la ruta al repositorio está a la derecha. Note que no tiene que haber relación alguna entre la ruta virtual que elija y el lugar del repositorio en su sistema de ficheros.

Si lo desea, puede usar tanto collections como paths simultáneamente en un solo fichero de configuración.

Nota: Si varios repositorios tienen la misma ruta virtual, hgwebdir.cgi no reportará ningún error. Pero se comportará de manera impredecible.

6.6.4. Descarga de ficheros fuente

La interfaz web de Mercurial permite a los usuarios descargar un conjunto de cualquier revisión. Este conjunto contendrá una réplica del directorio de trabajo en la revisión en cuestión, pero no contendrá una copia de los datos del repositorio.

Esta característica no está habilitada de forma predeterminada. Para habilitarla adicione un allow_archive a la sección [web] de su fichero hgrc.

6.6.5. Opciones de configuración en Web

Las interfaces web de Mercurial (la orden hg serve”, y los guiones hgweb.cgi y hgwebdir.cgi) tienen varias opciones de configuración disponibles. Todas ellas van en la sección [web].

Si usa hgwebdir.cgi, puede añadir otras opciones de configuración en la sección [web] del fichero hgweb.config en lugar del fichero hgrc si lo considera más conveniente. Estas opciones son motd y style.

Opciones específicas para repositorios individuales

Ciertas opciones de configuración de [web] deben estar ubicadas en el .hg/hgrc de un repositorio en lugar del fichero del usuario o el hgrc global.

Opciones específicas a la orden “hg serve

Algunas opciones en la sección [web] de un fichero hgrc son de uso exclusivo de la orden hg serve”.

Elegir el fichero hgrc correcto para las configuraciones de [web]

Es importante recordar que un servidor web como Apache o lighttpd se ejecutará bajo un ID de usuario que generalmente no es el suyo Los guiones CGI ejecutados por su servidor, tales como hgweb.cgi, se ejecutarán también con ése ID de usuario.

Si añade opciones [web] a su fichero personal hgrc los guiones CGI no leerán tal fichero hgrc. Tales configuraciones solamente afectarán el comportamiento de la orden hg serve” cuando usted la ejecuta. Para logar que los guiones CGI vean sus configuraciones, o bien cree un fichero hgrc en el directorio personal del usuario bajo cuyo ID se ejecuta su servidor web, o añada tales opciones al fichero global hgrc.

Capítulo 7
Nombres de ficheros y asociación de patrones

Mercurial provee mecanismos que le permiten trabajar con nombres de ficheros en una manera consistente y expresiva.

7.1. Nombrado de ficheros simple

Mercurial usa un mecanismo unificado “bajo el capó” para manejar nombres de ficheros. Cada comando se comporta de manera uniforme con respecto a los nombres de fichero. La manera en que los comandos operan con nombres de fichero es la siguiente.

Si usted especifica explícitamente nombres reales de ficheros en la línea de comandos, Mercurial opera únicamente sobre dichos ficheros, como usted esperaría.

1  $ hg add COPYING README examples/simple.py

Cuando usted provee el nombre de un directorio, Mercurial interpreta eso como “opere en cada fichero en este directorio y sus subdirectorios”. Mercurial va por todos los ficheros y subdirectorios de un directorio en orden alfabético. Cuando encuentra un subdirectorio, lo recorrerá antes de continuar con el directorio actual.

1  $ hg status src
2  ? src/main.py
3  ? src/watcher/_watcher.c
4  ? src/watcher/watcher.py
5  ? src/xyzzy.txt

7.2. Ejecución de comandos sin ningún nombre de fichero

Los comandos de Mercurial que trabajan con nombres de fichero tienen comportamientos por defecto adecuados cuando son utilizados sin pasar ningún patrón o nombre de fichero. El tipo de comportamiento depende de lo que haga el comando. Aquí presento unas cuantas reglas generales que usted puede usar para que es lo que probablemente hará un comando si usted no le pasa ningún nombre de fichero con el cual trabajar.

Es fácil evitar este comportamiento por defecto, si no es el adecuado para usted. Si un comando opera normalmente en todo el directorio de trabajo, usted puede llamarlo para que trabaje sólo en el directorio actual y sus subdirectorio pasándole el nombre “.”.

1  $ cd src
2  $ hg add -n
3  adding ../MANIFEST.in
4  adding ../examples/performant.py
5  adding ../setup.py
6  adding main.py
7  adding watcher/_watcher.c
8  adding watcher/watcher.py
9  adding xyzzy.txt
10  $ hg add -n .
11  adding main.py
12  adding watcher/_watcher.c
13  adding watcher/watcher.py
14  adding xyzzy.txt

Siguiendo la misma línea, algunos comandos normalmente imprimen las rutas de ficheros con respecto a la raíz del repositorio, aún si usted los llama dentro de un subdirectorio. Dichos comandos imprimirán las rutas de los ficheros respecto al directorio en que usted se encuentra si se les pasan nombres explícitos. Vamos a ejecutar el comando hg status” desde un subdirectorio, y a hacer que opere en el directorio de trabajo completo, a la vez que todas las rutas de ficheros se imprimen respecto a nuestro subdirectorio, pasándole la salida del comando hg root”.

1  $ hg status
2  A COPYING
3  A README
4  A examples/simple.py
5  ? MANIFEST.in
6  ? examples/performant.py
7  ? setup.py
8  ? src/main.py
9  ? src/watcher/_watcher.c
10  ? src/watcher/watcher.py
11  ? src/xyzzy.txt
12  $ hg status ‘hg root‘
13  A ../COPYING
14  A ../README
15  A ../examples/simple.py
16  ? ../MANIFEST.in
17  ? ../examples/performant.py
18  ? ../setup.py
19  ? main.py
20  ? watcher/_watcher.c
21  ? watcher/watcher.py
22  ? xyzzy.txt

7.3. Reportar que está pasando

El ejemplo con el comando hg add” en la sección anterior ilustra algo más que es útil acerca de los comandos de Mercurial. Si un comando opera en un fichero que usted no pasó explícitamente en la línea de comandos, usualmente se imprimirá el nombre del fichero, para que usted no sea sorprendido por lo que sucede.

Esto es el principio de mínima sorpresa. Si usted se ha referido explícitamente a un fichero en la línea de comandos, no tiene mucho sentido repetir esto de vuelta a usted. Si Mercurial está actuando en un fichero implícitamente, porque usted no pasó nombres, ni directorios, ni patrones (ver más abajo), lo más seguro es decirle a usted qué se está haciendo.

Usted puede silenciar a los comandos que se comportan de esta manera usando la opción -q. También puede hacer que impriman el nombre de cada fichero, aún aquellos que usted indicó explícitamente, usando la opción -v.

7.4. Uso de patrones para identificar ficheros

Además de trabajar con nombres de ficheros y directorios, Mercurial le permite usar patrones para identificar ficheros. El manejo de patrones de Mercurial es expresivo.

En sistemas tipo Unix (Linux, MacOS, etc.), el trabajo de asociar patrones con nombres de ficheros recae sobre el intérprete de comandos. En estos sistemas, usted debe indicarle explícitamente a Mercurial que el nombre que se le pasa es un patrón. En Windows, el intérprete no expande los patrones, así que Mercurial identificará automáticamente los nombres que son patrones, y hará la expansión necesaria.

Para pasar un patrón en vez de un nombre normal en la línea de comandos, el mecanismo es simple:

1  syntax:patternbody

Un patrón es identificado por una cadena de texto corta que indica qué tipo de patrón es, seguido por un dos puntos, seguido por el patrón en sí.

Mercurial soporta dos tipos de sintaxis para patrones. La que se usa con más frecuencia se denomina glob1; es el mismo tipo de asociación de patrones usado por el intérprete de Unix, y también debería ser familiar para los usuarios de la línea de comandos de Windows.

Cuando Mercurial hace asociación automática de patrones en Windows, usa la sintaxis glob. Por esto, usted puede omitir el prefijo “glob:” en Windows, pero también es seguro usarlo.

La sintaxis re2 es más poderosa; le permite especificar patrones usando expresiones regulares, también conocidas como regexps.

A propósito, en los ejemplos siguientes, por favor note que yo tengo el cuidado de rodear todos mis patrones con comillas sencillas, para que no sean expandidos por el intérprete antes de que Mercurial pueda verlos.

7.4.1. Patrones glob estilo intérprete

Este es un vistazo general de los tipos de patrones que usted puede usar cuando está usando asociación con patrone glob.

La secuencia “” se asocia con cualquier cadena, dentro de un único directorio.

1  $ hg add 'glob:⋆.py'
2  adding main.py

La secuencia “⋆⋆” se asocia con cualquier cadena, y cruza los límites de los directorios. No es una elemento estándar de los tokens de glob de Unix, pero es aceptado por varios intérpretes Unix populares, y es muy útil.

1  $ cd ..
2  $ hg status 'glob:⋆⋆.py'
3  A examples/simple.py
4  A src/main.py
5  ? examples/performant.py
6  ? setup.py
7  ? src/watcher/watcher.py

La secuencia “?” se asocia con cualquier caracter sencillo.

1  $ hg status 'glob:⋆⋆.?'
2  ? src/watcher/_watcher.c

El caracter “[” marca el inicio de una clase de caracteres. Ella se asocia con cualquier caracter sencillo dentro de la clase. La clase se finaliza con un caracter “]”. Una clase puede contener múltiples rangos de la forma “a-f”, que en este caso es una abreviación para “abcdef”.

1  $ hg status 'glob:⋆⋆[nr-t]'
2  ? MANIFEST.in
3  ? src/xyzzy.txt

Si el primer caracter en aparecer después de “[” en la clase de caracteres es un “!”, se niega la clase, haciendo que se asocie con cualquier caracter sencillo que no se encuentre en la clase.

Un “{” marca el inicio de un grupo de subpatrones, en donde todo el grupo es asociado si cualquier subpatrón en el grupo puede ser asociado. El caracter “,” separa los subpatrones, y el “}” finaliza el grupo.

1  $ hg status 'glob:⋆.{in,py}'
2  ? MANIFEST.in
3  ? setup.py

Cuidado!

No olvide que si usted desea asocia un patrón con cualquier directorio, no debería usar el elemento para asociar con cualquier cadena “”, ya que éste sólo generará asociaciones dentro de un solo directorio. En vez de eso, use el caracter para asociar con cualquier cadena “⋆⋆”. Este pequeño ejemplo ilustra la diferencia entre los dos.

1  $ hg status 'glob:⋆.py'
2  ? setup.py
3  $ hg status 'glob:⋆⋆.py'
4  A examples/simple.py
5  A src/main.py
6  ? examples/performant.py
7  ? setup.py
8  ? src/watcher/watcher.py

7.4.2. Asociación con patrones de expresiones regulares re

Mercurial acepta la misma sintaxis para expresiones regulares del lenguaje de programación Python (internamente se usa el motor de expresiones regulares de Python). Esta sintaxis está basada en la misma del lenguaje Perl, que es el dialecto más popular en uso (por ejemplo, también se usa en Java).

No discutiré el dialecto de expresiones regulares de Mercurial en detalle aquí, ya que las mismas no son usadas frecuentemente. Las expresiones regulares al estilo Perl se encuentran documentadas exhaustivamente en una multitud de sitios web, y en muchos libros. En vez de eso, me enfocaré en unas cuantas cosas que usted debería conocer si tiene la necesidad de usar expresiones regulares en Mercurial.

Una expresión regular es comparada contra un nombre de fichero completo, relativo a la raíz del repositorio. En otras palabras, aún si usted se encuentra en un subdirectorio foo, si desea asociar ficheros en este directorio, su patrón debe empezar con “foo/”.

Un detalle a tener en cuenta es que, si le son familiares las expresiones regulares al estilo Perl, las de Mercurial están enraízadas. Esto es, que la asociación de una expresión se hace desde el inicio de la cadena; no se buscan coincidencias dentro de la cadena. Para buscar coincidencias en cualquier sitio dentro de una cadena, empiece su patrón con un “.⋆”.

7.5. Filtrado de ficheros

Mercurial no sólo le provee una variedad de formas para especificar ficheros; le permite limitar aún más dichos ficheros mediante el uso de filtros. Los comandos que operan con nombres de fichero aceptan dos opciones de filtrado.

Usted puede pasar múltiples veces las opciones -I y -X en la línea de comandos, e intercalarlos como desee. Por defecto, Mercurial interpreta los patrones que usted pase usando la sintaxis glob (pero usted puede usar expresiones regulares si lo necesita).

El filtro -I puede verse como un “procese todos los ficheros que coincidan con este filtro”.

1  $ hg status -I '⋆.in'
2  ? MANIFEST.in

El filtro -X puede verse como “procese únicamente los ficheros que no coincidan con este patrón”.

1  $ hg status -X '⋆⋆.py' src
2  ? src/watcher/_watcher.c
3  ? src/xyzzy.txt

7.6. Ignorar ficheros y directorios no deseados

XXX.

7.7. Sensibilidad a mayúsculas

Si usted está trabajando en un ambiente de desarrollo mixto que contiene tanto sistemas Linux (u otro Unix) y sistemas Mac o Windows, debería tener en mente el hecho de que ellos tratan case (“N” versus “n”) of file names in incompatible ways. This is not very likely to affect you, and it’s easy to deal with if it does, but it could surprise you if you don’t know about it.

Operating systems and filesystems differ in the way they handle the case of characters in file and directory names. There are three common ways to handle case in names.

On Unix-like systems, it is possible to have any or all of the above ways of handling case in action at once. For example, if you use a USB thumb drive formatted with a FAT32 filesystem on a Linux system, Linux will handle names on that filesystem in a case preserving, but insensitive, way.

7.7.1. Almacenamiento portable y seguro de repositorios

El mecanismo de almacenamiento de los repositorios en Mercurial es robusto frente a sensibilidad/insensibilidad a mayúsculas. Los nombres de fichero son traducidos para que puedan ser almacenados de manera segura tanto en sistemas sensibles como insensibles a mayúsculas. Esto significa que usted puede usar herramientas normales de copia de ficheros para transferir un repositorio Mercurial a, por ejemplo, una memoria USB, y trasladar de manera segura la memoria y el repositorio de ida y vuelta entre un Mac, un PC ejecutando Windows, y un sistema Linux

7.7.2. Detección de conflictos de mayúsculas/minúsculas

Al operar en el directorio de trabajo, Mercurial respeta la política de nombrado del sistema de ficheros en que se encuentre el directorio de trabajo. Si el sistema de ficheros conserva las diferencias entre mayúsculas, pero no es sensible a ellas, Mercurial tratará los nombres que sólo difieren en mayúsculas como uno solo y el mismo.

Un aspecto importante de este enfoque es que es posible consignar un conjunto de cambios en un sistema de ficheros sensible a mayúsculas (típicamente Linux o Unix) que terminará causando problemas para usuarios en sistemas insensibles a mayúsculas (usualmente en Windows o MacOS). Si un usuario de Linux consigna cambios a dos ficheros, uno de ellos llamado myfile.c y el otro llamado MyFile.C, ambos serán almacenados correctamente en el repositorio. Y serán representados correctamente como ficheros separados, en los directorios de trabajo de otros usuarios de Linux.

Si un usuario de Windows o Mac jalan este cambio, no tendrán problemas inicialmente, porque el mecanismo de almacenamiento de Mercurial es seguro frente a sensibilidad/insensibilidad a mayúsculas. Sin embargo, una vez que ellos traten de actualizar (hg update”) el directorio de trabajo con ese conjunto de cambios, o hagan fusión (hg merge”) con ese conjunto de cambios, Mercurial verá el conflicto entre los dos nombres de fichero que el sistema de ficheros trataría como el mismo, e impedirá que ocurra la actualización o fusión.

7.7.3. Arreglar un conflicto de mayúsculas/minúsculas

Si usted está usando Windows o Mac en un entorno mixto donde algunos de sus colaboradores están usando Linux o Unix, y Mercurial reporta un conflicto de mayúsculas/minúsculas cuando usted trata de actualizar (hg update”) o fusionar (hg merge”), el procedimiento para arreglar el problema es simple.

Sólo busque un sistema Linux o Unix cercano, clone el repositorio problema allí, y use el comando hg rename” de Mercurial para cambiar los nombres de cualquiera de los ficheros o directorios problemáticos para que no causen más conflictos. Consigne este cambio, y jálelo (hg pull”) o empújelo (hg push”) a su sistema Windows o MacOS, y actualícelo (hg update”) a la revisión con los nombres que ya no generan conflictos.

El conjunto de cambios con los nombres con conflictos de mayúsculas/minúsculas permanecerá en el historial de su proyecto, y usted no podrá actualizar (hg update”) su directorio de trabajo a dicho conjunto de cambios en un sistema Windows o MacOS, pero puede continuar el desarrollo sin impedimentos.

Nota: Antes de la versión 0.9.3, Mercurial no usaba un mecanismos seguro frente a sensibilidad/insensibilidad a mayúsculas o minúsculas, y no detectaba los conflictos con nombres de ficheros. Si usted está usando una versión más antigua de Mercurial en Windows o MacOS, le recomiendo enérgicamente que se actualice.

Capítulo 8
Administración de versiones y desarrollo ramificado

Mercurial ofrece varios mecanismos que le permiten administrar un proyecto que avanza en múltiples frentes simultáneamente. Para entender estos mecanismos, demos un vistazo a la estructura usual de un proyecto de software.

Muchos proyectos de software liberan una versión “mayor” que contiene nuevas características substanciales. En paralelo, pueden liberar versiones “menores”. Usualmente éstas son idénticas a las versiones mayores en las cuales están basadas, pero con arreglos para algunos fallos.

En este capítulo, comenzaremos hablando de cómo mantener registro de etapas del proyecto como las liberaciones de una versión. Continuaremos hablando del flujo de trabajo entre las diferentes fases de un proyecto, y cómo puede ayudar Mercurial a aislar y administrar tal trabajo.

8.1. Dar un nombre persistente a una revisión

Cuando usted decide otorgar a una revisión el nombre particular de una “versión”, es buena idea grabar la identidad de tal revisión. Esto le permitirá reproducir dicha versión en una fecha posterior, para cualquiera que sea el propósito que se tenga en ese momento (reproducir un fallo, portar a una nueva plataforma, etc).

1  $ hg init mytag
2  $ cd mytag
3  $ echo hello > myfile
4  $ hg commit -A -m 'Initial commit'
5  adding myfile

Mercurial le permite dar un nombre permanente a cualquier revisión usando la orden hg tag”. Sin causa de sorpresa, esos nombres se llaman “tags” (etiquetas).

1  $ hg tag v1.0

Una etiqueta no es más que un “nombre simbólico” para una revisión. Las etiquetas existen únicamente para su conveniencia, brindándole una forma permanente y sencilla de referirse a una revisión; Mercurial no interpreta de ninguna manera los nombres de las etiquetas que usted use. Mercurial tampoco impone restricción alguna al nombre de una etiqueta, más allá de lo necesario para asegurar que una etiqueta pueda procesarse sin ambigüedades. El nombre de una etiqueta no puede tener ninguno de los siguientes caracteres:

Puede usar la orden hg tags” para ver las etiquetas presentes en su repositorio. Al desplegarse, cada revisión marcada se identifica primero con su nombre, después con el número de revisión y finalmente con un hash único de la revisión.

1  $ hg tags
2  tip                                1:515e5c86f496
3  v1.0                               0:1d3fd3640aaa

Note que tip aparece en en listado generado por hg tags”. La etiqueta tip es una etiqueta “flotante” especial, que identifica siempre la revisión más reciente en el repositorio.

Al desplegar la orden hg tags”, las etiquetas se listan en orden inverso, por número de revisión. Lo que significa usualmente que las etiquetas más recientes se listan antes que las más antiguas. También significa que la etiqueta tip siempre aparecerá como primera etiqueta listada al desplegar la orden hg tags”.

Cuando usted ejecuta hg log”, si se muestra una revisión que tenga etiquetas asociadas a ella, se imprimirán tales etiquetas.

1  $ hg log
2  changeset:   1:515e5c86f496
3  tag:         tip
4  user:        Bryan O'Sullivan <bos@serpentine.com>
5  date:        Tue Feb 10 18:23:29 2009 +0000
6  summary:     Added tag v1.0 for changeset 1d3fd3640aaa
7  
8  changeset:   0:1d3fd3640aaa
9  tag:         v1.0
10  user:        Bryan O'Sullivan <bos@serpentine.com>
11  date:        Tue Feb 10 18:23:29 2009 +0000
12  summary:     Initial commit
13  

Siempre que requiera indicar un ID de revisión a una orden de Mercurial, aceptará un nombre de etiqueta en su lugar. Internamente, Mercurial traducirá su nombre de etiqueta en el ID de revisión correspondiente, y lo usará.

1  $ echo goodbye > myfile2
2  $ hg commit -A -m 'Second commit'
3  adding myfile2
4  $ hg log -r v1.0
5  changeset:   0:1d3fd3640aaa
6  tag:         v1.0
7  user:        Bryan O'Sullivan <bos@serpentine.com>
8  date:        Tue Feb 10 18:23:29 2009 +0000
9  summary:     Initial commit
10  

No hay límites en la cantidad de etiquetas por repositorio, o la cantidad de etiquetas que una misma revisión pueda tener. Siendo prácticos, no es muy buena idea tener “demasiadas” (la cantidad variará de un proyecto a otro), debido a que la intención es ayudarle a encontrar revisiones. Si tiene demasiadas etiquetas, la facilidad de usarlas para identificar revisiones disminuirá rápidamente.

Por ejemplo, si su proyecto tiene etapas (milestones) frecuentes, de pocos días, es perfectamente razonable asignarle una etiqueta a cada una de ellas. Pero si tiene un sistema de construcción automática de binarios que asegura que cada revisión puede generarse limpiamente, estaría introduciendo mucho ruido si se usara una etiqueta para cada generación exitosa. Más bien, podría usar tags para generaciones fallidas (¡en caso de que estas sean raras!), o simplemente evitar las etiquetas para llevar cuenta de la posibilidad de generación de binarios.

Si quiere eliminar una etiqueta que no desea, use hg tag --remove”.

1  $ hg tag --remove v1.0
2  $ hg tags
3  tip                                3:31eda97c4db9

También puede modificar una etiqueta en cualquier momento, para que identifique una revisión distinta, simplemente usando una nueva orden hg tag”. Deberá usar la opción -f para indicarle a Mercurial que realmente desea actualizar la etiqueta.

1  $ hg tag -r 1 v1.1
2  $ hg tags
3  tip                                4:eb33d1c642f4
4  v1.1                               1:515e5c86f496
5  $ hg tag -r 2 v1.1
6  abort: tag 'v1.1' already exists (use -f to force)
7  $ hg tag -f -r 2 v1.1
8  $ hg tags
9  tip                                5:b26c4daf4a9d
10  v1.1                               2:55d99d62d333

De todas maneras habrá un registro permanente de la antigua identidad de la etiqueta, pero Mercurial no la usará. Por lo tanto no hay problema al marcar con una etiqueta una revisión incorrecta; lo único que debe hacer es mover la etiqueta hacia la revisión correcta tan pronto como localice el error.

Mercurial almacena las etiquetas en un fichero controlado por revisiones en su repositorio. Si ha creado etiquetas, las encontrará en un fichero llamado .hgtags. Cuando invoca la orden hg tag”, Mercurial modifica este fichero, y hace la consignación del cambio al mismo automáticamente. Esto significa que cada vez que ejecuta hg tag”, verá un conjunto de cambios correspondiente en la salida de hg log”.

1  $ hg tip
2  changeset:   5:b26c4daf4a9d
3  tag:         tip
4  user:        Bryan O'Sullivan <bos@serpentine.com>
5  date:        Tue Feb 10 18:23:30 2009 +0000
6  summary:     Added tag v1.1 for changeset 55d99d62d333
7  

8.1.1. Manejo de conflictos entre etiquetas durante una fusión

Usualmente no tendrá que preocuparse por el fichero .hgtags, pero a veces hace su aparición durante una fusión. El formato del fichero es sencillo: Consiste de una serie de líneas. Cada línea comienza con un hash de conjunto de cambios, seguido por un espacio, seguido por el nombre de una etiqueta.

Si está resolviendo un conflicto en el fichero .hgtags durante una fusión, hay un detalle para tener en cuenta al modificar el fichero .hgtags: cuando Mercurial procesa las etiquetas en el repositorio, nunca lee la copia de trabajo del fichero .hgtags. En cambio, lee la versión consignada más reciente del fichero.

Una consecuencia desafortunada de este diseño es que usted no puede verificar que su fichero .hgtags fusionado sea correcto hasta después de haber consignado un cambio. Así que si se encuentra resolviendo un conflicto en .hgtags durante una fusión, asegúrese de ejecutar la orden hg tags” después de consignar. Si encuentra un error en el fichero .hgtags, la orden reportará el lugar del error, que podrá arreglar y después consignar. Posteriormente ejecute de nuevo la orden hg tags” para asegurarse de que su arreglo fue aplicado correctamente .

8.1.2. Etiquetas y clonado

Puede haber notado que la orden hg clone” tiene la opción -r que le permite clonar una copia exacta del repositorio hasta un conjunto de cambios específico. El nuevo clon no tendrá historial posterior a la revisión que usted haya especificado. Esto tiene una interacción con etiquetas que puede sorprender a los desprevenidos.

Recuerde que una etiqueta se almacena como una revisión al fichero .hgtags, así que cuando usted crea una etiqueta, el conjunto de cambios en el cual ésta se almacena necesariamente se refiere a un conjunto de cambios anterior. Cuando ejecuta hg clone -r foo” para clonar un repositorio hasta la etiqueta foo, el nuevo clon no contendrá el historial que creo la etiqueta que usó para clonar el repositorio. El resultado es que tendrá exactamente el subconjunto correcto del historial del proyecto en el nuevo repositorio, pero, no la etiqueta que podría haber esperado.

8.1.3. Cuando las etiquetas permanentes son demasiado

Dado que las etiquetas de Mercurial están controladas por revisiones y se llevan en el historial del proyecto, todas las personas involucradas verán las etiquetas que usted haya creado. El hecho de dar nombres a las revisiones tiene usos más allá que simplemente hacer notar que la revisión 4237e45506ee es realmente v2.0.2. Si está tratando de encontrar un fallo sutil, posiblemente desearía colocar una etiqueta recordándole algo como “Ana vio los síntomas en esta revisión”.

Para estos casos, lo que usted posiblemente desearía serían etiquetas locales. Puede crear una etiqueta local con la opción -l de la orden hg tag”. Esto guardará la etiqueta en un fichero llamado .hg/localtags. A diferencia de .hgtags, .hg/localtags no está controlado por revisiones. Cualquier etiqueta que usted cree usando -l se mantendrá local al repositorio en el que esté trabajando en ese momento.

8.2. El flujo de cambios—El gran cuadro vs. el pequeño

Retomando lo mencionado en el comienzo de un capítulo, pensemos en el hecho de que un proyecto tiene muchas piezas concurrentes de trabajo en desarrollo al mismo tiempo.

Puede haber prisa por una nueva versión “principal”; una nueva versión con un arreglo de fallo a la última versión; y una versión de “mantenimiento correctivo” a una versión antigua que ha entrado en modo de mantenimiento.

Usualmente la gente se refiere a esas direcciones concurrentes de desarrollo como “ramas”. Sin embargo, ya hemos visto que en varias ocasiones Mercurial trata a todo el historial como una serie de ramas y fusiones. Realmente lo que tenemos aquí es dos ideas que se relacionan periféricamente, pero que en esencia comparten un nombre.

8.3. Administrar ramas en repositorios estilo gran cuadro

En Mercurial la forma más sencilla de aislar una rama del “gran cuadro” es a través de un repositorio dedicado. Si cuenta con un repositorio compartido existente —llamémoslo myproject—que alcanzó la etapa “1.0”, puede comenzar a prepararse para versiones de mantenimiento futuras a partir de la versión 1.0 marcando con una etiqueta la revisión con la cual preparó la versión 1.0.

1  $ cd myproject
2  $ hg tag v1.0

Ahora puede clonar un repositorio compartido nuevo myproject-1.0.1 con tal etiqueta.

1  $ cd ..
2  $ hg clone myproject myproject-1.0.1
3  updating working directory
4  2 files updated, 0 files merged, 0 files removed, 0 files unresolved

Posteriormente, si alguien necesita trabajar en la reparación de un fallo debería dirigirse a la liberación de versión 1.0.1 que viene en camino, ellos clonarían el repositorio myproject-1.0.1, harían sus cambios y los empujarían de vuelta.

1  $ hg clone myproject-1.0.1 my-1.0.1-bugfix
2  updating working directory
3  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
4  $ cd my-1.0.1-bugfix
5  $ echo 'I fixed a bug using only echo!' >> myfile
6  $ hg commit -m 'Important fix for 1.0.1'
7  $ hg push
8  pushing to /tmp/branch-repo07QkL5/myproject-1.0.1
9  searching for changes
10  adding changesets
11  adding manifests
12  adding file changes
13  added 1 changesets with 1 changes to 1 files

Mientras tanto, el desarrollo para la siguiente versión mayor puede continuar aislado e incólume, en el repositorio myproject.

1  $ cd ..
2  $ hg clone myproject my-feature
3  updating working directory
4  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
5  $ cd my-feature
6  $ echo 'This sure is an exciting new feature!' > mynewfile
7  $ hg commit -A -m 'New feature'
8  adding mynewfile
9  $ hg push
10  pushing to /tmp/branch-repo07QkL5/myproject
11  searching for changes
12  adding changesets
13  adding manifests
14  adding file changes
15  added 1 changesets with 1 changes to 1 files

8.4. No repita trabajo: fusión entre ramas

En muchos casos, cuando tiene un fallo para arreglar en una rama de mantenimiento, es muy probable que el fallo también esté en la rama principal (y posiblemente en otras ramas de mantenimiento también). Solamente un desarrollador extraño desearía corregir el mismo fallo muchas veces, por tanto, veremos varias alternativas con las que Mercurial puede ayudarle a administrar tales arreglos de fallo sin duplicar su trabajo.

En el caso más sencillo, basta con jalar los cambios de la rama de mantenimiento a la rama objetivo en su clon local.

1  $ cd ..
2  $ hg clone myproject myproject-merge
3  updating working directory
4  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
5  $ cd myproject-merge
6  $ hg pull ../myproject-1.0.1
7  pulling from ../myproject-1.0.1
8  searching for changes
9  adding changesets
10  adding manifests
11  adding file changes
12  added 1 changesets with 1 changes to 1 files (+1 heads)
13  (run 'hg heads' to see heads, 'hg merge' to merge)

A continuación deberá mezclar las cabezas de las dos ramas, y empujar de nuevo a la rama principal.

1  $ hg merge
2  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
3  (branch merge, don't forget to commit)
4  $ hg commit -m 'Merge bugfix from 1.0.1 branch'
5  $ hg push
6  pushing to /tmp/branch-repo07QkL5/myproject
7  searching for changes
8  adding changesets
9  adding manifests
10  adding file changes
11  added 2 changesets with 1 changes to 1 files

8.5. Nombrar ramas dentro de un repositorio

La aproximación correcta en casi todas las oportunidades es aislar las ramas en los repositorios. Es fácil de entender gracias a su simplicidad; y es difícil cometer errores. Hay una relación uno a uno entre las ramas y los directorios con los que está trabajando en su sistema. Esto le permite usar emplear herramientas usuales (que no son conscientes de Mercurial) para trabajar con los ficheros dentro de una rama/repositorio.

Si se encuentra más en la categoría “usuario diestro” (y sus colaboradores también), puede considerar otra alternativa para administrar las ramas. He mencionado con anterioridad la distinción a nivel humano entre las ramas estilo “cuadro pequeño” y “gran cuadro”. Mientras que Mercurial trabaja con muchas ramas del estilo “cuadro pequeño” en el repositorio todo el tiempo (por ejemplo cuando usted jala cambios, pero antes de fusionarlos), también puede trabajar con varias ramas del “cuadro grande”.

El truco para trabajar de esta forma en Mercurial se logra gracias a que puede asignar un nombre persistente a una rama. Siempre existe una rama llamada default. Incluso antes de que empiece a nombrar ramas por su cuenta, puede encontrar indicios de la rama default si los busca.

Por ejemplo, cuando invoca la orden hg commit”, y se lanza su editor para introducir el mensaje de la consignación, busque la línea que contiene el texto “HG: branch default” al final. Le está indicando que su consignación ocurrirá en la rama llamada default.

Use la orden hg branches” para empezar a trabajar con ramas nombradas. Esta orden mostrará las ramas presentes en su repositorio, indicándole qué conjunto de cambios es la punta de cada una.

1  $ hg tip
2  changeset:   0:c54ce9a68981
3  tag:         tip
4  user:        Bryan O'Sullivan <bos@serpentine.com>
5  date:        Tue Feb 10 18:23:16 2009 +0000
6  summary:     Initial commit
7  
8  $ hg branches
9  default                        0:c54ce9a68981

Dado que todavía no ha creado ramas nombradas, la única que verá será default.

Para hallar cuál es la rama “actual”, invoque la orden hg branch”, sin argumento alguno. Le informará en qué rama se encuentra el padre del conjunto de cambios actual.

1  $ hg branch
2  default

Para crear una nueva rama, invoque la orden hg branch” de nuevo. En esta oportunidad, ofrezca un argumento: el nombre de la rama que desea crear.

1  $ hg branch foo
2  marked working directory as branch foo
3  $ hg branch
4  foo

Después de crear la rama, usted podría desear ver el efecto que tuvo la orden hg branch”. ¿Qué reportan las ordenes hg status” y hg tip”?

1  $ hg status
2  $ hg tip
3  changeset:   0:c54ce9a68981
4  tag:         tip
5  user:        Bryan O'Sullivan <bos@serpentine.com>
6  date:        Tue Feb 10 18:23:16 2009 +0000
7  summary:     Initial commit
8  

Nada cambia en el directorio actual, y no se ha añadido nada al historial. Esto sugiere que al ejecutar la orden hg branch” no hay un efecto permanente; solamente le indica a que nombre de rama usará la próxima vez que consigne un conjunto de cambios.

Cuando consigna un cambio, Mercurial almacena el nombre de la rama en la cual consignó. Una vez que haya cambiado de la rama default y haya consignado, verá que el nombre de la nueva rama se mostrará cuando use la orden hg log”, hg tip”, y otras órdenes que desplieguen la misma clase de información.

1  $ echo 'hello again' >> myfile
2  $ hg commit -m 'Second commit'
3  $ hg tip
4  changeset:   1:b108bc883cbc
5  branch:      foo
6  tag:         tip
7  user:        Bryan O'Sullivan <bos@serpentine.com>
8  date:        Tue Feb 10 18:23:17 2009 +0000
9  summary:     Second commit
10  

Las órdenes del tipo hg log” imprimirán el nombre de la rama de cualquier conjunto de cambios que no esté en la rama default. Como resultado, si nunca usa ramas nombradas, nunca verá esta información.

Una vez que haya nombrado una rama y consignado un cambio con ese nombre, todas las consignaciones subsecuentes que desciendan de ese cambio heredarán el mismo nombre de rama. Puede cambiar el nombre de una rama en cualquier momento con la orden hg branch”.

1  $ hg branch
2  foo
3  $ hg branch bar
4  marked working directory as branch bar
5  $ echo new file > newfile
6  $ hg commit -A -m 'Third commit'
7  adding newfile
8  $ hg tip
9  changeset:   2:7f37f4b1f9a3
10  branch:      bar
11  tag:         tip
12  user:        Bryan O'Sullivan <bos@serpentine.com>
13  date:        Tue Feb 10 18:23:17 2009 +0000
14  summary:     Third commit
15  

Esto es algo que no hará muy seguido en la práctica, debido que los nombres de las ramas tienden a tener vidas largas. (Esto no es una regla, solamente una observación.)

8.6. Tratamiento de varias ramas nombradas en un repositorio

Si tiene más de una rama nombrada en un repositorio, Mercurial recordará la rama en la cual está su directorio de trabajo cuando invoque una orden como hg update” o hg pull -u”. Se actualizará su directorio de trabajo actual a la punta de esta rama, sin importar cuál sea la punta “a lo largo del repositorio”. Para actualizar a una revisión que está en una rama con distinto nombre, puede necesitar la opción -C de hg update”.

Este comportamiento puede ser sutil, así que veámoslo en acción. Primero, recordemos en qué rama estamos trabajando, y qué ramas están en nuestro repositorio.

1  $ hg parents
2  changeset:   2:7f37f4b1f9a3
3  branch:      bar
4  tag:         tip
5  user:        Bryan O'Sullivan <bos@serpentine.com>
6  date:        Tue Feb 10 18:23:17 2009 +0000
7  summary:     Third commit
8  
9  $ hg branches
10  bar                            2:7f37f4b1f9a3
11  foo                            1:b108bc883cbc (inactive)
12  default                        0:c54ce9a68981 (inactive)

Estamos en la rama bar, pero existe otra rama más antigua llamada hg foo”.

Podemos hacer hg update” entre los tipos de las ramas foo y bar sin necesidad de usar la opción -C, puesto que esto solamente implica ir linealmente hacia adelante y atrás en nuestro historial de cambios.

1  $ hg update foo
2  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
3  $ hg parents
4  changeset:   1:b108bc883cbc
5  branch:      foo
6  user:        Bryan O'Sullivan <bos@serpentine.com>
7  date:        Tue Feb 10 18:23:17 2009 +0000
8  summary:     Second commit
9  
10  $ hg update bar
11  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
12  $ hg parents
13  changeset:   2:7f37f4b1f9a3
14  branch:      bar
15  tag:         tip
16  user:        Bryan O'Sullivan <bos@serpentine.com>
17  date:        Tue Feb 10 18:23:17 2009 +0000
18  summary:     Third commit
19  

Si volvemos a la rama foo e invocamos la orden hg update”, nos mantendrá en foo, sin movernos a la punta de bar.

1  $ hg update foo
2  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
3  $ hg update
4  0 files updated, 0 files merged, 0 files removed, 0 files unresolved

Al consignar un cambio a la rama foo se introducirá una nueva cabeza.

1  $ echo something > somefile
2  $ hg commit -A -m 'New file'
3  adding somefile
4  created new head
5  $ hg heads
6  changeset:   3:1ca81328634a
7  branch:      foo
8  tag:         tip
9  parent:      1:b108bc883cbc
10  user:        Bryan O'Sullivan <bos@serpentine.com>
11  date:        Tue Feb 10 18:23:17 2009 +0000
12  summary:     New file
13  
14  changeset:   2:7f37f4b1f9a3
15  branch:      bar
16  user:        Bryan O'Sullivan <bos@serpentine.com>
17  date:        Tue Feb 10 18:23:17 2009 +0000
18  summary:     Third commit
19  

8.7. Nombres de ramas y fusiones

Posiblemente ha notado que las fusiones en Mercurial no son simétricas. Supongamos que su repositorio tiene dos cabezas, 17 y 23. Si yo invoco hg update” a 17 y aplico hg merge” a 23, Mercurial almacena 17 como el primer padre de la fusión, y 23 como el segundo. Mientras que si hago hg update” a 23 y después aplico hg merge” con 17, grabará a 23 como el primer padre, y 17 como el segundo.

Esto afecta el cómo elige Mercurial el nombre de la rama cuando usted hace la fusión. Después de una fusión, Mercurial mantendrá el nombre de la rama del primer padre cuando consigne el resultado de la fusión. Si el primer nombre de su padre es foo, y fusiona con bar, el nombre de la rama continuará siendo foo después de fusionar.

No es inusual que un repositorio contenga varias cabezas, cada una con el mismo nombre de rama. Digamos que estoy trabajando en la rama foo, y usted también. Consignamos cambios distintos; yo jalo sus cambios; Ahora tengo dos cabezas, cada una afirmando estar en la rama foo. El resultado de una fusión será una única cabeza en la rama foo como usted esperaría.

Pero si estoy trabajando en la rama bar, y fusiono el trabajo de la rama foo, el resultado permanecerá en la rama bar.

1  $ hg branch
2  bar
3  $ hg merge foo
4  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
5  (branch merge, don't forget to commit)
6  $ hg commit -m 'Merge'
7  $ hg tip
8  changeset:   4:a6a8b6aebf33
9  branch:      bar
10  tag:         tip
11  parent:      2:7f37f4b1f9a3
12  parent:      3:1ca81328634a
13  user:        Bryan O'Sullivan <bos@serpentine.com>
14  date:        Tue Feb 10 18:23:17 2009 +0000
15  summary:     Merge
16  

En un ejemplo más concreto, si yo estoy trabajando en la rama bleeding-edge, y deseo traer los arreglos más recientes de la rama estable, Mercurial elegirá el nombre de rama “correcto” (bleeding-edge) cuando yo jale una fusión desde estable.

8.8. Normalmente es útil nombrar ramas

No debería considerar que las ramas nombradas son aplicables únicamente en situaciones con muchas ramas de larga vida cohabitando en un mismo repositorio. Son muy útiles incluso en los casos de una rama por repositorio.

En el caso más sencillo, dar un nombre a cada rama ofrece un registro permanente acerca de en qué conjunto de cambios se generó la rama. Esto le ofrece más contexto cuando esté tratando de seguir el historial de un proyecto ramificado de larga vida.

Si está trabajando con repositorios compartidos, puede configurar el gancho pretxnchangegroup para que cada uno bloquee los cambios con nombres de rama “incorrectos” que están por adicionarse. Este provee una defensa sencilla, pero efectiva, para evitar que la gente publique accidentalmente cambios de una rama “super nueva” a la rama “estable”. Tal gancho podría verse de la siguiente forma dentro de un repositorio compartido de hgrc.

1  [hooks]
2  pretxnchangegroup.branch = hg heads --template 'branches ' | grep mybranch

Capítulo 9
Encontrar y arreglar sus equivocaciones

Errar es humano, pero tratar adecuadamente las consecuencias requiere un sistema de control de revisiones de primera categoría. En este capítulo, discutiremos algunas técnicas que puede usar cuando encuentra que hay un problema enraizado en su proyecto. Mercurial tiene unas características poderosas que le ayudarán a isolar las fuentes de los problemas, y a dar cuenta de ellas apropiadamente.

9.1. Borrar el historial local

9.1.1. La consignación accidental

Tengo el problema ocasional, pero persistente de teclear más rápido de lo que pienso, que aveces resulta en consignar un conjunto de cambios incompleto o simplemente malo. En mi caso, el conjunto de cambios incompleto consiste en que creé un nuevo fichero fuente, pero olvidé hacerle hg add”. Un conjunto de cambios“simplemente malo” no es tan común, pero sí resulta muy molesto.

9.1.2. Hacer rollback una transacción

En la sección 4.2.2, mencioné que Mercurial trata modificación a un repositorio como una transacción. Cada vez que consigna un conjunto de cambios o lo jala de otro repositorio, Mercurial recuerda lo que hizo. Puede deshacer, o hacer roll back1, exactamente una de tales acciones usando la orden hg rollback”. (Ver en la sección 9.1.4 una anotación importante acerca del uso de esta orden.)

A continuación una equivocación que me sucede frecuentemente: consignar un cambio en el cual he creado un nuevo fichero, pero he olvidado hacerle hg add”.

1  $ hg status
2  M a
3  $ echo b > b
4  $ hg commit -m 'Add file b'

La salida de hg status” después de la consignación confirma inmediatamente este error.

1  $ hg status
2  ? b
3  $ hg tip
4  changeset:   1:be1cdd4c4fea
5  tag:         tip
6  user:        Bryan O'Sullivan <bos@serpentine.com>
7  date:        Tue Feb 10 18:23:29 2009 +0000
8  summary:     Add file b
9  

La consignación capturó los cambios en el fichero a, pero no el nuevo fichero b. Si yo publicara este conjunto de cambios a un repositorio compartido con un colega, es bastante probable que algo en a se refiriera a b, el cual podría no estar presente cuando jalen mis cambios del repositorio. Me convertiría el sujeto de cierta indignación.

Como sea, la suerte me acompaña—Encontré mi error antes de publicar el conjunto de cambios. Uso la orden hg rollback”, y Mercurial hace desaparecer el último conjunto de cambios.

1  $ hg rollback
2  rolling back last transaction
3  $ hg tip
4  changeset:   0:c40e0a2ea41e
5  tag:         tip
6  user:        Bryan O'Sullivan <bos@serpentine.com>
7  date:        Tue Feb 10 18:23:29 2009 +0000
8  summary:     First commit
9  
10  $ hg status
11  M a
12  ? b

El conjunto de cambios ya no está en el historial del repositorio, y el directorio de trabajo cree que el fichero a ha sido modificado. La consignación y el roll back dejaron el directorio de trabajo exactamente como estaba antes de la consignación; el conjunto de cambios ha sido eliminado totlamente. Ahora puedo hacer hg add” al fichero b, y hacer de nuevo la consignación.

1  $ hg add b
2  $ hg commit -m 'Add file b, this time for real'

9.1.3. Erroneamente jalado

Mantener ramas de desarrollo separadas de un proyecto en distintos repositorios es una práctica común con Mercurial. Su equipo de desarrollo puede tener un repositorio compartido para la versión “0.9” y otra con cambios distintos para la versión “1.0”.

Con este escenario, puede imaginar las consecuencias si tuviera un repositorio local “0.9”, y jalara accidentalmente los cambios del repositorio compartido de la versión “1.0” en este. En el peor de los casos, por falta de atención, es posible que publique tales cambios en el árbol compartido “0.9”, confundiendo a todo su equipo de trabajo (pero no se preocupe, volveremos a este terrorífico escenario posteriormente). En todo caso, es muy probable que usted se de cuenta inmediatamente, dado que Mercurial mostrará el URL de donde está jalando, o que vea jalando una sospechosa gran cantidad de cambios en el repositorio.

La orden hg rollback” excluirá eficientemente los conjuntos de cambios que haya acabado de jalar. Mercurial agrupa todos los cambios de un hg pull” a una única transacción y bastará con un hg rollback” para deshacer esta equivocación.

9.1.4. Después de publicar, un roll back es futil

El valor de hg rollback” se anula cuando ha publicado sus cambios a otro repositorio. Un cambio desaparece totalmente al hacer roll back, pero solamente en el repositorio en el cual aplica hg rollback”. Debido a que un roll back elimina el historial, no hay forma de que la desaparición de un cambio se propague entre repositorios.

Si ha publicado un cambio en otro repositorio—particularmente si es un repositorio público—esencialmente está “en terreno agreste,” y tendrá que reparar la equivocación de un modo distinto. Lo que pasará si publica un conjunto de cambios en algún sitio, hacer rollback y después volver a jalar del repositorio del cual había publicado, es que el conjunto de cambios reaparecerá en su repositorio.

(Si está absolutamente segruro de que el conjunto de cambios al que desea hacer rollback es el cambio más reciente del repositorio en el cual publicó, y sabe que nadie más pudo haber jalado de tal repositorio, puede hacer rollback del conjunto de cambios allí, pero es mejor no confiar en una solución de este estilo. Si lo hace, tarde o temprano un conjunto de cambios logrará colarse en un repositorio que usted no controle directamente (o del cual se ha olvidado), y volverá a hostigarle.)

9.1.5. Solamente hay un roll back

Mercurial almacena exactamente una transacción en su bitácora de transacciones; tal transacción es la más reciente de las que haya ocurrido en el repositorio. Esto significa que solamente puede hacer roll back a una transacción. Si espera poder hacer roll back a una transacción después al antecesor, observará que no es el comportamiento que obtendrá.

1  $ hg rollback
2  rolling back last transaction
3  $ hg rollback
4  no rollback information available

Una vez que haya aplicado un rollback en una transacción a un repositorio, no podrá volver a hacer rollback hasta que haga una consignación o haya jalado.

9.2. Revertir un cambio equivocado

Si modifica un fichero y se da cuenta que no quería realmente cambiar tal fichero, y todavía no ha consignado los cambios, la orden necesaria es hg revert”. Observa el conjunto de cambios padre del directorio y restaura los contenidos del fichero al estado de tal conjunto de cambios. (Es una forma larga de decirlo, usualmente deshace sus modificaciones.)

Ilustremos como actúa la orden hg revert” con un ejemplo pequeño. Comenzaremos modificando un fichero al cual Mercurial ya está siguiendo.

1  $ cat file
2  original content
3  $ echo unwanted change >> file
4  $ hg diff file
5  diff -r f8694d2c79ed file
6  --- a/file Tue Feb 10 18:23:21 2009 +0000
7  +++ b/file Tue Feb 10 18:23:21 2009 +0000
8  @@ -1,1 +1,2 @@
9   original content
10  +unwanted change

Si no queremos ese cambio, podemos aplicar hg revert” al fichero.

1  $ hg status
2  M file
3  $ hg revert file
4  $ cat file
5  original content

La orden hg revert” nos brinda un grado adicional de seguridad guardando nuestro fichero modificado con la extensión .orig.

1  $ hg status
2  ? file.orig
3  $ cat file.orig
4  original content
5  unwanted change

Este es un resumen de casos en los cuales la orden hg revert” es de utilidad. Describiremos cada uno de ellos con más detalle en la sección siguiente.

9.2.1. Errores al administrar ficheros

La orden hg revert” es útil para más que ficheros modificados. Le permite reversar los resultados de todas las órdenes de administración de ficheros que provee Mercurial—hg add”, hg remove”, y las demás.

Si usted hace hg add” a un fichero, y no deseaba que Mercurial le diera seguimiento, use hg revert” para deshacer la adición. No se preocupe; Mercurial no modificará de forma alguna el fichero. Solamente lo “desmarcará”.

1  $ echo oops > oops
2  $ hg add oops
3  $ hg status oops
4  A oops
5  $ hg revert oops
6  $ hg status
7  ? oops

De forma similar, Si le solicita a Mercurial hacer hg remove” a un fichero, puede usar hg revert” para restarurarlo a los contenidos que tenía la revisión padre del directorio de trabajo.

1  $ hg remove file
2  $ hg status
3  R file
4  $ hg revert file
5  $ hg status
6  $ ls file
7  file

Funciona de la misma manera para un fichero que usted haya eliminado manualmente, sin decirle a Mercurial (recuerde que en la terminología de Mercurial esta clase de fichero se llama “faltante”).

1  $ rm file
2  $ hg status
3  ! file
4  $ hg revert file
5  $ ls file
6  file

Si usted revierte un hg copy”, el fichero a donde se copió permanece en su directorio de trabajo, pero sin seguimiento. Dado que una copia no afecta el fichero fuente de copiado de ninguna maner, Mercurial no hace nada con este.

1  $ hg copy file new-file
2  $ hg revert new-file
3  $ hg status
4  ? new-file

Un caso ligeramente especial:revertir un renombramiento

Si hace hg rename” a un fichero, hay un detalle que debe tener en cuenta. Cuando aplica hg revert” a un cambio de nombre, no es suficiente proveer el nombre del fichero destino, como puede verlo en el siguiente ejemplo.

1  $ hg rename file new-file
2  $ hg revert new-file
3  $ hg status
4  ? new-file

Como puede ver en la salida de hg status”, el fichero con el nuevo nombre no se identifica más como agregado, pero el fichero con el nombre-inicial se elimna! Esto es contra-intuitivo (por lo menos para mí), pero por lo menos es fácil arreglarlo.

1  $ hg revert file
2  no changes needed to file
3  $ hg status
4  ? new-file

Por lo tanto, recuerde, para revertir un hg rename”, debe proveer ambos nombres, la fuente y el destino.

(A propósito, si elimina un fichero, y modifica el fichero con el nuevo nombre, al revertir ambos componentes del renombramiento, cuando Mercurial restaure el fichero que fue eliminado como parte del renombramiento, no será modificado. Si necesita que las modificaciones en el fichero destino del renombramiento se muestren, no olvide copiarlas encima.)

Estos aspectos engorrosos al revertir un renombramiento se constituyen discutiblemente en un fallo de Mercurial.

9.3. Tratar cambios consignados

Considere un caso en el que ha consignado el cambio a, y otro cambio b sobre este; se ha dado cuenta que el cambio a era incorrecto. Mercurial le permite “retroceder” un conjunto de cambios completo automáticamente, y construir bloques que le permitan revertir parte de un conjunto de cambios a mano.

Antes de leer esta sección, hay algo para tener en cuenta: la orden hg backout” deshace cambios adicionando al historial, sin modificar o borrar. Es la herramienta correcta si está arreglando fallos, pero no si está tratando de deshacer algún cambio que tiene consecuencias catastróficas. Para tratar con esos, vea la sección 9.4.

9.3.1. Retroceder un conjunto de cambios

La orden hg backout” le permite “deshacer” los efectos de todo un conjunto de cambios de forma automatizada. Dado que el historial de Mercurial es inmutable, esta orden no se deshace del conjunto de cambios que usted desea deshacer. En cambio, crea un nuevo conjunto de cambios que reversa el conjunto de cambios que usted indique.

La operación de la orden hg backout” es un poco intrincada, y lo ilustraremos con algunos ejemplos. Primero crearemos un repositorio con algunos cambios sencillos.

1  $ hg init myrepo
2  $ cd myrepo
3  $ echo first change >> myfile
4  $ hg add myfile
5  $ hg commit -m 'first change'
6  $ echo second change >> myfile
7  $ hg commit -m 'second change'

La orden hg backout” toma un ID de conjunto de cambios como su argumento; el conjunto de cambios a retroceder. Normalmente hg backout” le ofrecerá un editor de texto para escribir el mensaje de la consignación, para dejar un registro de por qué está retrocediendo. En este ejemplo, colocamos un mensaje en la consignación usando la opción -m.

9.3.2. Retroceder el conjunto de cambios punta

Comenzamos retrocediendo el último conjunto de cambios que consignamos.

1  $ hg backout -m 'back out second change' tip
2  reverting myfile
3  changeset 2:1d9ee76a7513 backs out changeset 1:cab6a78bf14b
4  $ cat myfile
5  first change

Puede ver que la segunda línea de myfile ya no está presente. La salida de hg log” nos da una idea de lo que la orden hg backout” ha hecho.

1  $ hg log --style compact
2  2[tip]   1d9ee76a7513   2009-02-10 18:23 +0000   bos
3    back out second change
4  
5  1   cab6a78bf14b   2009-02-10 18:23 +0000   bos
6    second change
7  
8  0   60b8d10ede6c   2009-02-10 18:23 +0000   bos
9    first change
10  

Vea que el nuevo conjunto de cambios que hg backout” ha creado es un hijo del conjunto de cambios que retrocedimos. Es más sencillo de ver en la figura 9.1, que presenta una vista gráfica del historial de cambios. Como puede ver, el historial es bonito y lineal.


PIC

Figura 9.1: Retroceso de un cambio con la orden hg backout

9.3.3. Retroceso de un cambio que no es la punta

Si desea retrocede un cambio distinto al último que ha consignado, use la opción --merge a la orden hg backout”.

1  $ cd ..
2  $ hg clone -r1 myrepo non-tip-repo
3  requesting all changes
4  adding changesets
5  adding manifests
6  adding file changes
7  added 2 changesets with 2 changes to 1 files
8  updating working directory
9  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
10  $ cd non-tip-repo

Que resulta en un retroceso de un conjunto de cambios “en un sólo tiro”, una operación que resulta normalmente rápida y sencilla.

1  $ echo third change >> myfile
2  $ hg commit -m 'third change'
3  $ hg backout --merge -m 'back out second change' 1
4  reverting myfile
5  created new head
6  changeset 3:688f1a6067e5 backs out changeset 1:cab6a78bf14b
7  merging with changeset 3:688f1a6067e5
8  merging myfile
9  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
10  (branch merge, don't forget to commit)

Si ve los contenidos del fichero myfile después de finalizar el retroceso, verá que el primer y el tercer cambio están presentes, pero no el segundo.

1  $ cat myfile
2  first change
3  third change

Como lo muestra el historial gráfico en la figura 9.2, Mercurial realmente consigna dos cambios en estas situaciones (los nodos encerrados en una caja son aquellos que Mercurial consigna automaticamente). Antes de que Mercurial comience el proceso de retroceso, primero recuerda cuál es el padre del directorio de trabajo. Posteriormente hace un retroceso al conjunto de cambios objetivo y lo consigna como un conjunto de cambios. Finalmente, fusiona con el padre anterior del directorio de trabajo, y consigna el resultado de la fusión.


PIC

Figura 9.2: Retroceso automatizado de un cambio a algo que no es la punta con la orden hg backout

El resultado es que usted termina “donde estaba”, solamente con un poco de historial adicional que deshace el efecto de un conjunto de cambios que usted quería evitar.

Use siempre la opción --merge

De hecho, dado que la opción --merge siempre hara lo “correcto” esté o no retrocediendo el conjunto de cambios punta (p.e. no tratará de fusionar si está retrocediendo la punta, dado que no es necesario), usted debería usar siempre esta opción cuando ejecuta la orden hg backout”.

9.3.4. Más control sobre el proceso de retroceso

A pesar de que recomiendo usar siempre la opción --merge cuando está retrocediendo un cambio, la orden hg backout” le permite decidir cómo mezclar un retroceso de un conjunto de cambios. Es muy extraño que usted necestite tomar control del proceso de retroceso de forma manual, pero puede ser útil entender lo que la orden hg backout” está haciendo automáticamente para usted. Para ilustrarlo, clonemos nuestro primer repositorio, pero omitamos el retroceso que contiene.

1  $ cd ..
2  $ hg clone -r1 myrepo newrepo
3  requesting all changes
4  adding changesets
5  adding manifests
6  adding file changes
7  added 2 changesets with 2 changes to 1 files
8  updating working directory
9  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
10  $ cd newrepo

Como en el ejemplo anterior, consignaremos un tercer cambio, después haremos retroceso de su padre, y veremos qué pasa.

1  $ echo third change >> myfile
2  $ hg commit -m 'third change'
3  $ hg backout -m 'back out second change' 1
4  reverting myfile
5  created new head
6  changeset 3:688f1a6067e5 backs out changeset 1:cab6a78bf14b
7  the backout changeset is a new head - do not forget to merge
8  (use "backout --merge" if you want to auto-merge)

Nuestro nuevo conjunto de cambios es de nuevo un descendiente del conjunto de cambio que retrocedimos; es por lo tanto una nueva cabeza, no un descendiente del conjunto de cambios que era la punta. La orden hg backout” fue muy explícita diciéndolo.

1  $ hg log --style compact
2  3[tip]:1   688f1a6067e5   2009-02-10 18:23 +0000   bos
3    back out second change
4  
5  2   72a18afb4ae5   2009-02-10 18:23 +0000   bos
6    third change
7  
8  1   cab6a78bf14b   2009-02-10 18:23 +0000   bos
9    second change
10  
11  0   60b8d10ede6c   2009-02-10 18:23 +0000   bos
12    first change
13  

De nuevo, es más sencillo lo que pasó viendo una gráfica del historial de revisiones, en la figura 9.3. Esto nos aclara que cuando usamos hg backout” para retroceder un cambio a algo que no sea la punta, Mercurial añade una nueva cabeza al repositorio (el cambio que consignó está encerrado en una caja).


PIC

Figura 9.3: Retroceso usando la orden hg backout

Después de que la orden hg backout” ha terminado, deja un nuevo conjunto de cambios de “retroceso” como el padre del directorio de trabajo.

1  $ hg parents
2  changeset:   2:72a18afb4ae5
3  user:        Bryan O'Sullivan <bos@serpentine.com>
4  date:        Tue Feb 10 18:23:13 2009 +0000
5  summary:     third change
6  

Ahora tenemos dos conjuntos de cambios aislados.

1  $ hg heads
2  changeset:   3:688f1a6067e5
3  tag:         tip
4  parent:      1:cab6a78bf14b
5  user:        Bryan O'Sullivan <bos@serpentine.com>
6  date:        Tue Feb 10 18:23:13 2009 +0000
7  summary:     back out second change
8  
9  changeset:   2:72a18afb4ae5
10  user:        Bryan O'Sullivan <bos@serpentine.com>
11  date:        Tue Feb 10 18:23:13 2009 +0000
12  summary:     third change
13  

Reflexionemos acerca de lo que esperamos ver como contenidos de myfile. El primer cambio debería estar presente, porque nunca le hicimos retroceso. El segundo cambio debió desaparecer, puesto que es el que retrocedimos. Dado que la gráfica del historial muestra que el tercer camlio es una cabeza separada, no esperamos ver el tercer cambio presente en myfile.

1  $ cat myfile
2  first change
3  second change
4  third change

Para que el tercer cambio esté en el fichero, hacemos una fusión usual de las dos cabezas.

1  $ hg merge
2  merging myfile
3  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
4  (branch merge, don't forget to commit)
5  $ hg commit -m 'merged backout with previous tip'
6  $ cat myfile
7  first change
8  third change

Después de eso, el historial gráfica de nuestro repositorio luce como la figura 9.4.


PIC

Figura 9.4: Fusión manual de un retroceso

9.3.5. Por qué “hg backout” hace lo que hace

Esta es una descripción corta de cómo trabaja la orden hg backout”.

  1. Se asegura de que el directorio de trabajo es “limpio”, esto es, que la salida de hg status” debería ser vacía.
  2. Recuerda el padre actual del directorio de trabajo. A este conjunto de cambio lo llamaremos orig
  3. Hace el equivalente de un hg update” para sincronizar el directorio de trabajo con el conjunto de cambios que usted quiere retroceder. Lo llamaremos backout
  4. Encuentra el padre del conjunto de cambios. Lo llamaremos parent.
  5. Para cada fichero del conjunto de cambios que el retroceso afecte, hará el equivalente a hg revert -r parent” sobre ese fichero, para restaurarlo a los contenidos que tenía antes de que el conjunto de cambios fuera consignado.
  6. Se consigna el resultado como un nuevo conjunto de cambios y tiene a backout como su padre.
  7. Si especifica --merge en la línea de comandos, se fusiona con orig, y se consigna el resultado de la fusión.

Una vía alternativa de implementar la orden hg backout” sería usar hg export” sobre el conjunto de cambios a retroceder como un diff y después usar laa opción --reverse de la orden patch para reversar el efecto del cambio sin molestar el directorio de trabajo. Suena mucho más simple, pero no funcionaría bien ni de cerca.

La razón por la cual hg backout” hace una actualización, una consignación, una fusión y otra consignación es para dar a la maquinaria de fusión la mayor oportunidad de hacer un buen trabajo cuando se trata con todos los cambios entre el cambio que está retrocediendo y la punta actual.

Si está retrocediendo un conjunto de cambios que está a unas  100 atrás en su historial del proyecto, las posibilidades de que una orden patch sea capaz de ser aplicada a un diff reverso, claramente no son altas, porque los cambios que intervienen podrían “no coincidir con el contexto” que patch usa para determinar si puede aplicar un parche (si esto suena como cháchara, vea una discusión de la orden patch en 12.4). Adicionalmente, la maquinaria de fusión de Mercurial manejará ficheros y directorios renombrados, cambios de permisos, y modificaciones a ficheros binarios, nada de lo cual la orden patch puede manejar.

9.4. Cambios que nunca debieron ocurrir

En la mayoría de los casos, la orden hg backout” es exactamente lo que necesita para deshacer los efectos de un cambio. Deja un registro permanente y exacto de lo que usted hizo, cuando se consignó el conjunto de cambios original y cuando se hizo la limpieza.

En ocasiones particulares, puede haber consignado un cambio que no debería estar de ninguna forma en el repositorio. Por ejemplo, sería muy inusual, y considerado como una equivocación, consignar los ficheros objeto junto con el código fuente. Los ficheros objeto no tienen valor intrínseco y son grandes, por lo tanto aumentan el tamaño del repositorio y la cantidad de tiempo que se emplea al clonar o jalar cambios.

Antes de discutir las opciones que tiene si consignó cambio del tipo “bolsa de papel deschable” (el tipo que es tan malo que le gustaría colocarse una bolsa de papel desechable en su cabeza), permítame discutir primero unas aproximaciones que probablemente no funcionen.

Dado que Mercurial trata de forma acumulativa al historial—cada cambio se coloca encima de todos los cambios que le preceden—usualmente usted no puede hacer que unos cambios desastrosos desaparezcan. La única excepción es cuando usted ha acabado de consignar un cambio y este no ha sido publicado o jalado en otro repositorio. Ahí es cuando puede usar la orden hg rollback” con seguridad, como detallé en la sección 9.1.2.

Después de que usted haya publicado un cambio en otro repositorio, usted podría usar la orden hg rollback” para hacer que en su copia local desaparezca el cambio, pero no tendrá las consecuencias que desea. El cambio estará presente en un repositorio remoto, y reaparecerá en su repositorio local la próxima vez que jale

Si una situación como esta se presenta, y usted sabe en qué repositorios su mal cambio se ha propagado, puede intentar deshacerse del conjunto de cambios de todos los repositorios en los que se pueda encontrar. Esta por supuesto, no es una solución satisfactoria: si usted deja de hacerlo en un solo repositorio, mientras esté eliminándolo, el cambio todavía estará “allí afuera”, y podría propagarse más tarde.

Si ha consignado uno o más cambios después del cambio que desea desaparecer, sus opciones son aún más reducidas. Mercurial no provee una forma de “cabar un hueco” en el historial, dejando los conjuntos de cambios intactos.

XXX This needs filling out. The hg-replay script in the examples directory works, but doesn’t handle merge changesets. Kind of an important omission.

9.4.1. Cómo protegerse de cambios que han “escapado”

Si ha consignado cambios a su repositorio local y estos han sido publicados o jalados en cualquier otro sitio, no es necesariamente un desastre. Puede protegerse de antemano de ciertas clases de conjuntos de cambios malos. Esto es particularmente sencillo si su equipo de trabajo jala cambios de un repositorio central.

Al configurar algunos ganchos en el repositorio central para validar conjuntos de cambios (ver capítulo 10), puede prevenir la publicación automáticamente de cierta clase de cambios malos. Con tal configuración, cierta clase de conjuntos de cambios malos tenderán naturalmente a“morir” debido a que no pueden propagarse al repositorio central. Esto sucederá sin necesidad de intervención explícita.

Por ejemplo, un gancho de cambios de entrada que verifique que un conjunto de cambios compila, puede prevenir que la gente “rompa la compilación” inadvertidamente.

9.5. Al encuentro de la fuente de un fallo

Aunque es muy bueno poder retroceder el conjunto de cambios que originó un fallo, se requiere que usted sepa cual conjunto de cambios retroceder. Mercurial brinda una orden invaluable, llamada hg bisect”, que ayuda a automatizar este proceso y a alcanzarlo muy eficientemente.

La idea tras la orden hg bisect” es que el conjunto de cambios que ha introducido un cambio de comportamiento pueda identificarse con una prueba binaria sencilla. No tiene que saber qué pieza de código introdujo el cambio, pero si requiere que sepa cómo probar la existencia de un fallo. La orden hg bisect” usa su prueba para dirigir su búsqueda del conjunto de cambios que introdujo el código causante del fallo.

A continuación un conjunto de escenarios que puede ayudarle a entender cómo puede aplicar esta orden.

Para estos ejemplos debería ser claro que la orden hg bisect” es útil no solamente para encontrar la fuente de los fallos. Puede usarla para encontrar cualquier “propiedad emergente” de un repositorio (Cualquier cosa que usted no pueda encontrar con una búsqueda de texto sencilla sobre los ficheros en el árbol) para la cual pueda escribir una prueba binaria.

A continuación introduciremos algo terminología, para aclarar qué partes del proceso de búsqueda son su responsabilidad y cuáles de Mercurial. Una prueba es algo que usted ejecuta cuando hg bisect” elige un conjunto de cambios. Un sondeo es lo que hg bisect” ejecuta para decidir si una revisión es buena. Finalmente, usaremos la palabra “biseccionar’, en frases como “buscar con la orden hg bisect””.

Una forma sencilla de automatizar el proceso de búsqueda sería probar cada conjunto de cambios. Lo cual escala muy poco. Si le tomó diez minutos hacer pruebas sobre un conjunto de cambios y tiene 10.000 conjuntos de cambios en su repositorio, esta aproximación exhaustiva tomaría en promedio 35 días para encontrar el conjunto de cambios que introdujo el fallo. Incluso si supiera que el fallo se introdujo en un de los últimos 500 conjuntos de cambios y limitara la búsqueda a ellos, estaría tomabdi más de 40 horas para encontrar al conjunto de cambios culpable.

La orden hg bisect” usa su conocimiento de la “forma” del historial de revisiones de su proyecto para hacer una búsqueda proporcional al logaritmo del número de conjunto de cambios a revisar (el tipo de búsqueda que realiza se llama búsqueda binaria). Con esta aproximación, el buscar entre 10.000 conjuntos de cambios tomará menos de 3 horas, incluso a diez minutos por prueba (La búsqueda requerirá cerca de 14 pruebas). Al limitar la búsqueda a la última centena de conjuntos de cambios, tomará a lo sumo una hora (Apenas unas 7 pruebas).

La orden hg bisect” tiene en cuenta la naturaleza “ramificada” del historial de revisiones del proyecto con Mercurial, así que no hay problemas al tratar con ramas, fusiones o cabezas múltiples en un repositorio. Puede evitar ramas enteras de historial con un solo sondeo.

9.5.1. Uso de la orden “hg bisect

A continuación un ejemplo de hg bisect” en acción.

Nota: En las versiones 0.9.5 y anteriores de Mercurial, hg bisect” no era una orden incluída en la distribución principal: se ofrecía como una extensión de Mercurial. Esta sección describe la orden embebida y no la extensión anterior.

Creamos un repostorio para probar el comando hg bisect” de forma aislada

1  $ hg init mybug
2  $ cd mybug

Simularemos de forma sencilla un proyecto con un fallo: haremos cambios triviales en un ciclo, e indicaremos que un cambio específico sea el “fallo”. Este ciclo crea 35 conjuntos de cambios, cada uno añade un único fichero al repositorio. Representaremos nuestro “fallo” con un fichero que contiene el texto “tengo un gub”.

1  $ buggy_change=22
2  $ for (( i = 0; i < 35; i++ )); do
3  >   if [[ $i = $buggy_change ]]; then
4  >     echo 'i have a gub' > myfile$i
5  >     hg commit -q -A -m 'buggy changeset'
6  >   else
7  >     echo 'nothing to see here, move along' > myfile$i
8  >     hg commit -q -A -m 'normal changeset'
9  >   fi
10  > done

A continuación observaremos cómo usar la orden hg bisect”. Podemos usar el mecanismo de ayuda embebida que trae Mercurial.

1  $ hg help bisect
2  hg bisect [-gbsr] [REV]
3  
4  subdivision search of changesets
5  
6      This command helps to find changesets which introduce problems.
7      To use, mark the earliest changeset you know exhibits the problem
8      as bad, then mark the latest changeset which is free from the
9      problem as good. Bisect will update your working directory to a
10      revision for testing. Once you have performed tests, mark the
11      working directory as bad or good and bisect will either update to
12      another candidate changeset or announce that it has found the bad
13      revision.
14  
15  options:
16  
17   -r --reset     reset bisect state
18   -g --good      mark changeset good
19   -b --bad       mark changeset bad
20   -s --skip      skip testing changeset
21   -U --noupdate  do not update to target
22  
23  use "hg -v help bisect" to show global options

La orden hg bisect” trabaja en etapas, de la siguiente forma:

  1. Usted ejecuta una prueba binaria.
  2. La orden usa su información para decidir qué conjuntos de cambios deben probarse a continuación.
  3. Actualiza el directorio de trabajo a tal conjunto de cambios y el proceso se lleva a cabo de nuevo.

El proceso termina cuando hg bisect” identifica un único conjunto de cambios que marca el punto donde se encontró la transición de “exitoso” a “fallido”.

Para comenzar la búsqueda, es indispensable ejecutar la orden hg bisect --reset”.

1  $ if  hg -v | head -1 | grep -e "version 0.⋆"
2  > then
3  #On mercurial 1.0 --init disappeared
4  > hg bisect --init
5  > fi

En nuestro caso, la prueba binaria es sencilla: revisamos si el fichero en el repositorio contiene la cadena “tengo un gub”. Si la tiene, este conjunto de cambios contiene aquel que “causó el fallo”. Por convención, un conjunto de cambios que tiene la propiedad que estamos buscando es “malo”, mientras que el otro que no la tiene es “bueno”.

En la mayoría de casos, la revisión del directorio actual (usualmente la punta) exhibe el problema introducido por el cambio con el fallo, por lo tanto la marcaremos como “mala”.

1  $ hg bisect --bad

Nuestra próxima tarea es nominar al conjunto de cambios que sabemos no tiene el fallo; la orden hg bisect” “acotará” su búsqueda entre el primer par de conjuntos de cambios buenos y malos. En nuestro caso, sabemos que la revisión 10 no tenía el fallo. (Más adelante diré un poco más acerca de la elección del conjunto de cambios “bueno”.)

1  $ hg bisect --good 10
2  Testing changeset 22:ec1c6526e0eb (24 changesets remaining, ~4 tests)
3  0 files updated, 0 files merged, 12 files removed, 0 files unresolved

Note que esta orden mostró algo.

Ahora ejecutamos nuestra prueba en el directorio de trabajo. Usamos la orden grep para ver si nuestro fichero “malo” está presente en el directorio de trabajo. Si lo está, esta revisión es mala; si no esta revisión es buena.

1  $ if grep -q 'i have a gub' ⋆
2  > then
3  >   result=bad
4  > else
5  >   result=good
6  > fi
7  $ echo this revision is $result
8  this revision is bad
9  $ hg bisect --$result
10  Testing changeset 16:8fb73e2ea5f9 (12 changesets remaining, ~3 tests)
11  0 files updated, 0 files merged, 6 files removed, 0 files unresolved

Esta prueba luce como candidata perfecta para automatizarse, por lo tanto la convertimos en una función de interfaz de comandos.

1  $ mytest() {
2  >   if grep -q 'i have a gub' ⋆
3  >   then
4  >     result=bad
5  >   else
6  >     result=good
7  >   fi
8  >   echo this revision is $result
9  >   hg bisect --$result
10  > }

Ahora podemos ejecutar un paso entero de pruebas con un solo comando, mytest.

1  $ mytest
2  this revision is good
3  Testing changeset 19:ffbbdeaade97 (6 changesets remaining, ~2 tests)
4  3 files updated, 0 files merged, 0 files removed, 0 files unresolved

Unas invocaciones más de nuestra prueba, y hemos terminado.

1  $ mytest
2  this revision is good
3  Testing changeset 20:c0c13593daf1 (3 changesets remaining, ~1 tests)
4  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
5  $ mytest
6  this revision is good
7  Testing changeset 21:d70e5938b22b (2 changesets remaining, ~1 tests)
8  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
9  $ mytest
10  this revision is good
11  The first bad revision is:
12  changeset:   22:ec1c6526e0eb
13  user:        Bryan O'Sullivan <bos@serpentine.com>
14  date:        Tue Feb 10 18:23:14 2009 +0000
15  summary:     buggy changeset
16  

Aunque teníamos unos 40 conjuntos de cambios en los cuales buscar, la orden hg bisect” nos permitió encontrar el conjunto de cambios que introdujo el “fallo” con sólo cinco pruebas. Porque el número de pruebas que la orden hg bisect” ejecuta crece logarítmicamente con la cantidad de conjuntos de cambios a buscar, la ventaja que esto tiene frente a la búsqueda por“fuerza bruta” crece con cada conjunto de cambios que usted adicione.

9.5.2. Limpieza después de la búsqueda

Cuando haya terminado de usar la orden hg bisect” en un repositorio, puede usar la orden hg bisect reset” para deshacerse de la información que se estaba usando para lograr la búsqueda. Lar orden no usa mucho espacio, así que no hay problema si olvida ejecutar la orden. En todo caso, hg bisect” no le permitirá comenzar una nueva búsqueda sobre el repositorio hasta que no aplique hg bisect reset”.

1  $ hg bisect --reset

9.6. Consejos para encontrar fallos efectivamente

9.6.1. Dar una entrada consistente

La orden hg bisect” requiere que usted ofrezca un reporte correcto del resultado de cada prueba que aplique. Si usted le dice que una prueba falla cuando en realidad era acertada, podría detectar la inconsistencia. Si puede identificar una inconsistencia en sus reportes, le dirá que un conjunto de cambios particular es a la vez bueno y malo. Aunque puede no hacerlo; estaría tratando de reportar un conjunto de cambios como el responsable de un fallo aunque no lo sea.

9.6.2. Automatizar tanto como se pueda

Cuando comencé a usar la orden hg bisect”, intenté ejecutar algunas veces las pruebas a mano desde la línea de comandos. Es una aproximación a la cual no esta acostumbrado. Después de algunos intentos, me di cuenta que estaba cometiendo tantas equivocaciones que tenía que comenzar de nuevo con mis búsquedas varias veces antes de llegar a los resultados deseados.

Mi problema inicial al dirigir a la orden hg bisect” manualmente ocurrieron incluso con búsquedas en repositorios pequeños; si el problema que está buscando es más sutil, o el número de pruebas que hg bisect” debe aplicar, la posibilidad de errar es mucho más alta. Una vez que comencé a automatizar mis pruebas, obtuve mejores resultados.

La clave para las pruebas automatizadas se puede resumir en:

En mi tutorial de ejemplo anterior, la orden grep busca el síntoma, y la construcción if toma el resultado de esta prueba y verifica que siempre alimentamos con los mismos datos a la orden hg bisect”. La función mytest los une de una forma reproducible, logrando que cada prueba sea uniforme y consistente.

9.6.3. Verificar los resultados

Dado que la salida de la búsqueda de hg bisect” es tan buena como los datos ofrecidos por usted, no confíe en esto como si fuera la verdad absoluta. Una forma sencilla de asegurarse es ejecutar manualmente su prueba a cada uno de los siguientes conjuntos de cambios:

9.6.4. Tener en cuenta la interferencia entre fallos

Es posible que su búsqueda de un fallo pueda viciarse por la presencia de otro. Por ejemplo, digamos que su programa se revienta en la revisión 100, y que funcionó correctamente en la revisión 50. Sin su conocimiento, alguien introdujo un fallo con consecuencias grandes en la revisión 60, y lo arregló en la revisión 80. Sus resultados estarían distorcionados de una o muchas formas.

Es posible que este fallo “enmascare” completamente al suyo, y que podría haberse revelado antes de que su propio fallo haya tenido oportunidad de manifestarse. Si no puede saltar el otro fallo (por ejemplo, este evita que su proyecto se arme o compile), y de esta forma no se pueda revisar si su fallo esté presente en un conjunto particular de cambios, la orden hg bisect” no podrá ayudarle directamente. En cambio, puede marcar este conjunto de cambios como al ejecutar hg bisect --skip”.

Un problema distinto podría surgir si su prueba de la presencia de un fallo no es suficientemente específica. Si usted busca “mi programa se revienta”, entonces tanto su fallo como el otro fallo sin relación que terminan presentando síntomas distintos, podría terminar confundiendo a hg bisect”.

Otra situación en la cual sería de mucha utilidad emplear a hg bisect --skip” surge cuando usted no puede probar una revisión porque su proyecto estaba en una situación de rompimiento y por lo tanto en un estado en el cual era imposible hacer la prueba en esa revisión, tal vez porque alguien consignó un cambio que hacía imposible la construcción del proyecto.

9.6.5. Acotar la búsqueda perezosamente

Elegir los primeros “buenos” y “malos” conjuntos de cambios que marcarán los límites de su búsqueda en general es sencillo, pero vale la pena discutirlo. Desde la perspectiva de hg bisect”, el conjunto de cambios “más nuevo” por convención es el “malo”, y el otro conjunto de cambios es el “bueno”.

Si no recuerda cuál podría ser el cambio “bueno”, para informar a hg bisect”, podría hacer pruebas aleatorias en el peor de los casos. Pero recuerde eliminar aquellos conjuntos de cambios que podrían no exhibir el fallo (tal vez porque la característica donde se presenta el fallo todavía no está presente) y aquellos en los cuales otro fallo puede enmascararlo (como se discutió anteriormente).

Incluso si termina “muy atrás” por miles de conjuntos de cambios o meses de historial, solamente estaŕa adicionando unas pruebas contadas para hg bisect”, gracias al comportamiento logarítmico.

Capítulo 10
Manejo de eventos en repositorios mediante ganchos

Mercurial ofrece un poderoso mecanismo para permitirle a usted automatizar la ejecución de acciones en respuesta a eventos que ocurran en un repositorio. En algunos casos, usted puede controlar incluso la respuesta de Mercurial a dichos eventos.

Mercurial usa el término gancho para identificar estas acciones. Los ganchos son conocidos como “disparadores” en algunos sistemas de control de revisiones, pero los dos nombres se refieren al mismo concepto.

10.1. Vistazo general de ganchos en Mercurial

A continuación se encuentra una breve lista de los ganchos que Mercurial soporta. Volveremos a cada uno de estos ganchos con más detalle después, en la sección 10.8.

Cada uno de los ganchos cuya descripción empieza con la frase “de control” tiene la facultad de determinar si una actividad puede continuar. Si el gancho se ejecuta con éxito, la actividad puede continuar; si falla, o bien la actividad no es permitida, o se deshacen los cambios que se puedan haber llevado a cabo, dependiendo del gancho involucrado.

10.2. Ganchos y seguridad

10.2.1. Los ganchos se ejecutan con sus privilegios de usuario

Cuando usted ejecuta un comando de Mercurial en un repositorio, y el comando causa la ejecución de un gancho, dicho gancho se ejecuta en su sistema, en su cuenta de usuario, con sus privilegios. Ya que los ganchos son elementos arbitrarios de código ejecutable, usted debería tratarlos con un nivel adecuado de desconfianza. No instale un gancho a menos en que confíe en quien lo creó y en lo que el gancho hace.

En algunos casos, usted puede estar expuesto a ganchos que usted no instaló. Si usted usa Mercurial en un sistema extraño, tenga en cuenta que Mercurial ejecutará los ganchos definidos en el fichero hgrc.

Si está trabajando con un repositorio propiedad de otro usuario, Mercurial podrá ejecutar los ganchos definidos en el repositorio de dicho usuario, pero los ejecutará como “usted”. Por ejemplo, si usted jala (hg pull”) desde ese repositorio, y el .hg/hgrc define un gancho saliente (outgoing), dicho gancho se ejecuta bajo su cuenta de usuario, aun cuando usted no es el propietario del repositorio.

Nota: Esto sólo aplica si usted está jalando desde un repositorio en un sistema de ficheros local o de red. Si está jalando a través de http o ssh, cualquier gancho saliente (outgoing) se ejecutará bajo la cuenta que está ejecutando el proceso servidor, en el servidor.

XXX Para ver qué ganchos han sido definidos en un repositorio, use el comando hg config hooks”. Si usted está trabajando en un repositorio, pero comunicándose con otro que no le pertenece (por ejemplo, usando hg pull” o hg incoming”), recuerde que los ganchos que debe considerar son los del otro repositorio, no los del suyo.

10.2.2. Los ganchos no se propagan

En Mercurial, no se hace control de revisiones de los ganchos, y no se propagan cuando usted clona, o jala de, un repositorio. El motivo para esto es simple: un gancho es código ejecutable arbitrario. Se ejecuta bajo su identidad, con su nivel de privilegios, en su máquina.

Sería extremadamente descuidado de parte de cualquier sistema distribuido de control de revisiones el implementar control de revisiones para ganchos, ya que esto ofrecería maneras fácilmente aprovechables de subvertir las cuentas de los usuarios del sistema de control de revisiones.

Ya que Mercurial no propaga los ganchos, si usted está colaborando con otras personas en un proyecto común, no debería asumir que ellos están usando los mismos ganchos para Mercurial que usted usa, o que los de ellos están configurado correctamente. Usted debería documentar los ganchos que usted espera que la gente use.

En una intranet corporativa, esto es algo más fácil de manejar, ya que usted puede, por ejemplo, proveer una instalación “estándar” de Mercurial en un sistema de ficheros NFS, y usar un fichero hgrc global para definir los ganchos que verán todos los usuarios. Sin embargo, este enfoque tiene sus límites; vea más abajo.

10.2.3. Es posible hacer caso omiso de los ganchos

Mercurial le permite hacer caso omiso de la deficinión de un gancho, a través de la redefinición del mismo. Usted puede deshabilitar el gancho fijando su valor como una cadena vacía, o cambiar su comportamiento como desee.

Si usted instala un fichero hgrc a nivel de sistema o sitio completo que define algunos ganchos, debe entender que sus usuarios pueden deshabilitar o hacer caso omiso de los mismos.

10.2.4. Asegurarse de que ganchos críticos sean ejecutados

Algunas veces usted puede querer hacer respetar una política, y no permitir que los demás sean capaces de evitarla. Por ejemplo, usted puede tener como requerimiento que cada conjunto de cambios debe pasar un riguroso conjunto de pruebas. Definir este requerimientos a través de un gancho en un fichero hgrc global no servirá con usuarios remotos en computadoras portátiles, y por supuesto que los usuarios locales pueden evitar esto a voluntad haciendo caso omiso del gancho.

En vez de eso, usted puede definir las políticas para usar Mercurial de tal forma que se espere que los usuarios propaguen los cambios a través de un servidor “canónico” bien conocido que usted ha asegurado y configurado apropiadamente.

Una manera de hacer esto es a través de una combinación de ingeniería social y tecnología. Cree una cuenta de acceso restringido; los usuarios pueden empujar cambios a través de la red a los repositorios administrados por esta cuenta, pero no podrán ingresar a dicha cuenta para ejecutar órdenes en el intérprete de comandos. En este escenario, un usuario puede enviar un conjunto de cambios que contenga la porquería que él desee.

Cuando alguien empuja un conjunto de cambios al servidor del que todos jalan, el servidor probará el conjunto de cambios antes de aceptarlo como permanente, y lo rechazará si no logra pasar el conjunto de pruebas. Si la gente sólo jala cambios desde este servidor de filtro, servirá para asegurarse de que todos los cambios que la gente jala han sido examinados automáticamente

10.3. Precauciones con ganchos pretxn en un repositorio de acceso compartido

Si usted desea usar ganchos para llevar a cabo automáticamente algún trabajo en un repositorio al que varias personas tienen acceso compartido, debe tener cuidado con la forma de hacerlo.

Mercurial sólo bloquea un repositorio cuando está escribiendo al mismo, y sólo las partes de Mercurial que escriben al repositorio le prestan atención a los bloqueos. Los bloqueos de escritura son necesarios para evitar que múltiples escritores simultáneos interfieran entre sí, corrompiendo el repositorio.

Ya que Mercurial tiene cuidado con el orden en que lee y escribe datos, no necesita adquirir un bloqueo cuando desea leer datos del repositorio. Las partes de Mercurial que leen del repositorio nunca le prestan atención a los bloqueos. Este esquema de lectura libre de bloqueos incremententa en gran medida el desempeño y la concurrencia.

Sin embargo, para tener un gran desempeño es necesario hacer sacrificios, uno de los cuales tiene el potencial de causarle problemas a menos de que usted esté consciente de él. Describirlo requiere algo de detalle respecto a cómo Mercurial añade conjuntos de cambios al repositorio y cómo lee esos cambios de vuelta.

Cuando Mercurial escribe metadatos, los escribe directamente en el fichero de destino. Primero escribe los datos del fichero, luego los datos del manifiesto (que contienen punteros a los nuevos datos del fichero), luego datos de la bitácora de cambios (que contienen punteros a los nuevos datos del manifiesto). Antes de la primera escritura a cada fichero, se guarda un registro de dónde estaba el final de fichero en su registro de transacciones. Si la transacción debe ser deshecha, Mercurial simplemente trunca cada fichero de vuelta al tamaño que tenía antes de que empezara la transacción.

Cuando Mercurial lee metadatos, lee la bitácora de cambios primero, y luego todo lo demás. Como un lector sólo accederá a las partes del manifiesto o de los metadatos de fichero que él puede ver en la bitácora de cambios, nunca puede ver datos parcialmente escritos.

Algunos ganchos de control (pretxncommit y pretxnchangegroup) se ejecutan cuando una transacción está casi completa. Todos los metadatos han sido escritos, pero Mercurial aún puede deshacer la transacción y hacer que los datos recién escritos desaparezcan.

Si alguno de estos ganchos permanece en ejecución por mucho tiempo, abre una ventana de tiempo en la que un lector puede ver los metadatos de conjuntos de cambios que aún no son permanentes y que no debería considerarse que estén “realmante ahí”. Entre más tiempo tome la ejecución del gancho, más tiempo estará abierta esta ventana.

10.3.1. Ilustración del problema

En principio, un buen uso del gancho pretxnchangegroup sería ensamblar y probar automáticamente todos los cambios entrantes antes de que sean aceptados en un repositorio central. Esto le permitiría a usted garantizar que nadie pueda empujar cambios que “rompan el ensamblaje”. Pero si un cliente puede jalar cambios mientras están siendo probados, la utilidad de esta prueba es nula; alguien confiado puede jalar cambios sin probar, lo que potencialmente podría romper su proceso de ensamblaje.

La respuesta técnica más segura frente a este retos es montar dicho repositorio “guardián” como unidireccional. Permita que reciba cambios desde el exterior, pero no permita que nadie jale cambios de él (use el gancho preoutgoing para bloquear esto). Configure un gancho changegroup para que si el ensamblaje o prueba tiene éxito, el gancho empuje los nuevos cambios a otro repositorio del que la gente pueda jalar.

En la práctica, montar un cuello de botella centralizado como éste a menudo no es una buena idea, y la visibilidad de las transacciones no tiene nada que ver con el problema. A medida que el tamaño de un proyecto—y el tiempo que toma ensamblarlo y probarlo—crece, usted se acerca rápidamente a un límite con este enfoque “pruebe antes de comprar”, en el que tiene más conjuntos de cambios a probar que tiempo para ocuparse de ellos. El resultado inevitable es frustración para todos los que estén involucrados.

Una aproximación que permite manejar mejor el crecimiento es hacer que la gente ensamble y pruebe antes de empujar, y ejecutar el ensamble y pruebas automáticas centralmente después de empujar, para asegurarse de que todo esté bien. La ventaja de este enfoque es que no impone un límite a la rata en la que un repositorio puede aceptar cambios.

10.4. Tutorial corto de uso de ganchos

Escribir un gancho para Mercurial es fácil. Empecemos con un gancho que se ejecute cuando usted termine un hg commit”, y simplemente muestre el hash del conjunto de cambios que usted acaba de crear. El gancho se llamará commit.


1  $ hg init hook-test
2  $ cd hook-test
3  $ echo '[hooks]' >> .hg/hgrc
4  $ echo 'commit = echo committed $HG_NODE' >> .hg/hgrc
5  $ cat .hg/hgrc
6  [hooks]
7  commit = echo committed $HG_NODE
8  $ echo a > a
9  $ hg add a
10  $ hg commit -m 'testing commit hook'
11  committed 233120f5d09c76e782c6ea86ccee729e735bf48f

Figura 10.1: Un gancho simple que se ejecuta al hacer la consignación de un conjunto de cambios

Todos los ganchos siguen el patrón del ejemplo 10.1. Usted puede añadir una entrada a la sección [hooks] de su fichero hgrc. A la izquierda está el nombre del evento respecto al cual dispararse; a la derecha está la acción a llevar a cabo. Como puede ver, es posible ejecutar cualquier orden de la línea de comandos en un gancho. Mercurial le pasa información extra al gancho usando variables de entorno (busque HG_NODE en el ejemplo).

10.4.1. Llevar a cabo varias acciones por evento

A menudo, usted querrá definir más de un gancho para un tipo de evento particular, como se muestra en el ejemplo 10.2. Mercurial le permite hacer esto añadiendo una extensión al final del nombre de un gancho. Usted extiende el nombre del gancho poniendo el nombre del gancho, seguido por una parada completa (el caracter “.”), seguido de algo más de texto de su elección. Por ejemplo, Mercurial ejecutará tanto commit.foo como commit.bar cuando ocurra el evento commit.


1  $ echo 'commit.when = echo -n "date of commit: "; date' >> .hg/hgrc
2  $ echo a >> a
3  $ hg commit -m 'i have two hooks'
4  committed df4f3f8972104d2a43d114e51edd05719efccd42
5  date of commit: Tue Feb 10 18:23:23 GMT 2009

Figura 10.2: Definición de un segundo gancho commit

Para dar un orden bien definido de ejecución cuando hay múltiples ganchos definidos para un evento, Mercurial ordena los ganchos de acuerdo a su extensión, y los ejecuta en dicho orden. En el ejemplo de arribam commit.bar se ejecutará antes que commit.foo, y commit se ejecutará antes de ambos.

Es una buena idea usar una extensión descriptiva cuando usted define un gancho. Esto le ayudará a recordar para qué se usa el gancho. Si el gancho falla, usted recibirá un mensaje de error que contiene el nombre y la extensión del gancho, así que usar una extensión descriptiva le dará una pista inmediata de porqué el gancho falló (vea un ejemplo en la sección 10.4.2).

10.4.2. Controlar cuándo puede llevarse a cabo una actividad

En los ejemplos anteriores, usamos el gancho commit, que es ejecutado después de que se ha completado una consignación. Este es uno de los varios ganchos que Mercurial ejecuta luego de que una actividad termina. Tales ganchos no tienen forma de influenciar la actividad como tal.

Mercurial define un número de eventos que ocurren antes de que una actividad empiece; o luego de que empiece, pero antes de que termine. Los ganchos que se disparan con estos eventos tienen la capacidad adicional de elegir si la actividad puede continuar, o si su ejecución es abortada.

El gancho pretxncommit se ejecuta justo antes de que una consignación se ejecute. En otras palabras, los metadatos que representan el conjunto de cambios han sido escritos al disco, pero no se ha terminado la transacción. El gancho pretxncommit tiene la capacidad de decidir si una transacción se completa, o debe deshacerse.

Si el gancho pretxncommit termina con un código de salida de cero, se permite que la transacción se complete; la consignación termina; y el gancho commit es ejecutado. Si el gancho pretxncommit termina con un código de salida diferente de cero, la transacción es revertida; los metadatos representando el conjunto de cambios son borrados; y el gancho commit no es ejecutado.


1  $ cat check_bug_id
2  #!/bin/sh
3  # check that a commit comment mentions a numeric bug id
4  hg log -r $1 --template {desc} | grep -q "<bug ⋆[0-9]"
5  $ echo 'pretxncommit.bug_id_required = ./check_bug_id $HG_NODE' >> .hg/hgrc
6  $ echo a >> a
7  $ hg commit -m 'i am not mentioning a bug id'
8  transaction abort!
9  rollback completed
10  abort: pretxncommit.bug_id_required hook exited with status 1
11  $ hg commit -m 'i refer you to bug 666'
12  committed d558e8617cbdf0b482db27a79d17f9a28af4701f
13  date of commit: Tue Feb 10 18:23:23 GMT 2009

Figura 10.3: Uso del gancho pretxncommit para controlar consignaciones

El gancho en el ejemplo 10.3 revisa si el mensaje de consignación contiene el ID de algún fallo. Si lo contiene, la consignación puede continuar. Si no, la consignación es cancelada.

10.5. Escribir sus propios ganchos

Cuando usted escriba un gancho, puede encontrar útil el ejecutar Mercurial o bien pasándole la opción -v, o con el valor de configuración verbose fijado en “true” (verdadero). Cuando lo haga, Mercurial imprimirá un mensaje antes de llamar cada gancho.

10.5.1. Escoger cómo debe ejecutarse su gancho

Usted puede escribir un gancho que funcione como un programa normal —típicamente un guión de línea de comandos—o como una función de Python que se ejecuta dentro del proceso Mercurial.

Escribir un gancho como un programa externo tiene la ventaja de que no requiere ningún conocimiento del funcionamiento interno de Mercurial. Usted puede ejecutar comandos Mercurial normales para obtener la informción extra que pueda necesitar. La contraparte de esto es que los ganchos externos son más lentos que los ganchos internos ejecutados dentro del proceso.

Un gancho Python interno tiene acceso completo a la API de Mercurial, y no se “externaliza” a otro proceso, así que es inherentemente más rápido que un gancho externo. Adicionalmente es más fácil obtener la mayoría de la información que un gancho requiere a través de llamadas directas a la API de Mercurial que hacerlo ejecutando comandos Mercurial.

Si se siente a gusto con Python, o requiere un alto desempeño, escribir sus ganchos en Python puede ser una buena elección. Sin embargo, cuando usted tiene un gancho bastante directo por escribir y no le importa el desempeño (el caso de la mayoría de los ganchos), es perfectamente admisible un guión de línea de comandos.

10.5.2. Parámetros para ganchos

Mercurial llama cada gancho con un conjunto de paŕametros bien definidos. En Python, un parámetro se pasa como argumento de palabra clave a su función de gancho. Para un programa externo, los parámetros son pasados como variables de entornos.

Sin importar si su gancho está escrito en Python o como guión de línea de comandos, los nombres y valores de los parámetros específicos de los ganchos serán los mismos. Un parámetro booleano será representado como un valor booleano en Python, pero como el número 1 (para “verdadero”) o 0 (para falso) en una variable de entorno para un gancho externo. Si un parámetro se llama foo, el argumento de palabra clave para un gancho en Python también se llamará foo, mientras que la variable de entorno para un gancho externo se llamará HG_FOO.

10.5.3. Valores de retorno de ganchos y control de actividades

Un gancho que se ejecuta exitosamente debe terminar con un código de salida de cero, si es externo, o retornar el valor booleano “falso”, si es interno. Un fallo se indica con un código de salida diferente de cero desde un gancho externo, o un valor de retorno booleano “verdadero”. Si un gancho interno genera una excepción, se considera que el gancho ha fallado.

Para los ganchos que controlan si una actividad puede continuar o no, cero/falso quiere decir “permitir”, mientras que no-cero/verdadero/excepción quiere decir “no permitir”.

10.5.4. Escribir un gancho externo

Cuando usted define un gancho externo en su fichero hgrc y el mismo es ejecutado, dicha definición pasa a su intérprete de comandos, que hace la interpretación correspondiente. Esto significa que usted puede usar elementos normales del intérprete en el cuerpo del gancho.

Un gancho ejecutable siempre es ejecutado con su directorio actual fijado al directorio raíz del repositorio.

Cada parámetro para el gancho es pasado como una variable de entorno; el nombre está en mayúsculas, y tiene como prefijo la cadena “HG_”.

Con la excepción de los parámetros para los ganchos, Mercurial no define o modifica ninguna variable de entorno al ejecutar un gancho. Es útil recordar esto al escribir un gancho global que podría ser ejecutado por varios usuarios con distintas variables de entorno fijadas. En situaciones con múltiples usuarios, usted no debería asumir la existencia de ninguna variable de entorno, ni que sus valores sean los mismos que tenían cuando usted probó el gancho en su ambiente de trabajo.

10.5.5. Indicar a Mercurial que use un gancho interno

La sintaxis para definir un gancho interno en el fichero hgrc es ligeramente diferente de la usada para un gancho externo. El valor del gancho debe comenzar con el texto “python:”, y continuar con el nombre completamente cualificado de un objeto invocable que se usará como el valor del gancho.

El módulo en que vive un gancho es importado automáticamente cuando se ejecuta un gancho. Siempre que usted tenga el nombre del módulo y la variable de entorno PYTHONPATH ajustada adecuadamente, todo debería funcionar sin problemas.

El siguiente fragmento de ejemplo de un fichero hgrc ilustra la sintaxis y significado de los conceptos que acabamos de describir.

1  [hooks]
2  commit.example = python:mymodule.submodule.myhook

Cuando Mercurial ejecuta el gancho commit.example, importa mymodule.submodule, busca el objeto invocable llamado myhook, y lo invoca (llama).

10.5.6. Escribir un gancho interno

El gancho interno más sencillo no hace nada, pero ilustra la estructura básica de la API1 para ganchos:

1  def myhook(ui, repo, ⋆⋆kwargs):
2      pass

El primer argumento para un gancho Python siempre es un objeto mercurial.ui.ui. El segundo es un objeto repositorio; de momento, siempre es una instancia de mercurial.localrepo.localrepository. Después de estos dos argumentos están los argumentos de palabra clave. Los argumentos que se pasen dependerán del tipo de gancho que se esté llamando, pero un gancho siempre puede ignorar los argumentos que no le interesen, relegándolos a un diccionario de argumentos por palabras clave, como se hizo arriba con ⋆⋆kwargs.

10.6. Ejemplos de ganchos

10.6.1. Escribir mensajes de consignación significativos

Es difícil de imaginar un mensaje de consignación útil y al mismo tiempo muy corto. El simple gancho pretxncommit de la figura 10.4 evitará que usted consigne un conjunto de cambios con un mensaje de menos de 10 bytes de longitud.


1  $ cat .hg/hgrc
2  [hooks]
3  pretxncommit.msglen = test ‘hg tip --template {desc} | wc -c‘ -ge 10
4  $ echo a > a
5  $ hg add a
6  $ hg commit -A -m 'too short'
7  transaction abort!
8  rollback completed
9  abort: pretxncommit.msglen hook exited with status 1
10  $ hg commit -A -m 'long enough'

Figura 10.4: Un gancho que prohíbe mensajes de consignación demasiado cortos

10.6.2. Comprobar espacios en blanco finales

Un uso interesante para ganchos relacionados con consignaciones es ayudarle a escribir código más limpio. Un ejemplo simple de “código más limpio” es la regla de que un cambio no debe añadir líneas de texto que contengan “espacios en blanco finales”. El espacio en blanco final es una serie de caracteres de espacio y tabulación que se encuentran al final de una línea de texto. En la mayoría de los casos, el espacio en blanco final es innecesario, ruido invisible, pero ocasionalmente es problemático, y la gente en general prefiere deshacerse de él.

Usted puede usar cualquiera de los ganchos precommit o pretxncommit para revisar si tiene el problema de los espacios en blanco finales. Si usa el gancho precommit, el gancho no sabrá qué ficheros se están consignando, por lo que se tendrá que revisar cada fichero modificado en el repositorio para ver si tiene espacios en blanco finales. Si usted sólo quiere consignar un cambio al fichero foo, y el fichero bar contiene espacios en blanco finales, hacer la revisión en el gancho precommit evitará que usted haga la consignación de foo debido al problem en bar. Este no parece el enfoque adeucado.

Si usted escogiera el gancho pretxncommit, la revisión no ocurriría sino hasta justo antes de que la transacción para la consignación se complete. Esto le permitirá comprobar por posibles problemas sólo en los ficheros que serán consignados. Sin embargo, si usted ingresó el mensaje de consignación de manera interactiva y el gancho falla, la transacción será deshecha; usted tendrá que reingresar el mensaje de consignación luego de que corrija el problema con los espacios en blanco finales y ejecute hg commit” de nuevo.


1  $ cat .hg/hgrc
2  [hooks]
3  pretxncommit.whitespace = hg export tip | (! egrep -q '̂+.⋆[ t]$')
4  $ echo 'a ' > a
5  $ hg commit -A -m 'test with trailing whitespace'
6  adding a
7  transaction abort!
8  rollback completed
9  abort: pretxncommit.whitespace hook exited with status 1
10  $ echo 'a' > a
11  $ hg commit -A -m 'drop trailing whitespace and try again'

Figura 10.5: Un gancho simple que revisa si hay espacios en blanco finales

La figura 10.5 presenta un gancho pretxncommit simple que comprueba la existencia de espacios en blanco finales. Este gancho es corto, pero no brinda mucha ayuda. Termina con un código de salida de error si un cambio añade una línea con espacio en blanco final a cualquier fichero, pero no muestra ninguna información que pueda ser útil para identificar el fichero o la línea de texto origen del problema. También tiene la agradable propiedad de no prestar atención a las líneas que no sufrieron modificaciones; sólo las líneas que introducen nuevos espacios en blanco finales causan problemas.


1  $ cat .hg/hgrc
2  [hooks]
3  pretxncommit.whitespace = .hg/check_whitespace.py
4  $ echo 'a ' >> a
5  $ hg commit -A -m 'add new line with trailing whitespace'
6  a, line 2: trailing whitespace added
7  commit message saved to .hg/commit.save
8  transaction abort!
9  rollback completed
10  abort: pretxncommit.whitespace hook exited with status 1
11  $ sed -i 's, ⋆$,,' a
12  $ hg commit -A -m 'trimmed trailing whitespace'
13  a, line 2: trailing whitespace added
14  commit message saved to .hg/commit.save
15  transaction abort!
16  rollback completed
17  abort: pretxncommit.whitespace hook exited with status 1

Figura 10.6: Un mejor gancho para espacios en blanco finales

El ejemplo de la figura 10.6 es mucho más complejo, pero también más útil. El gancho procesa un diff unificado para revisar si alguna línea añade espacios en blanco finales, e imprime el nombre del fichero y el número de línea de cada ocurrencia. Aún mejor, si el cambio añade espacios en blanco finales, este gancho guarda el mensaje de consignación e imprime el nombre del fichero en el que el mensaje fue guardado, antes de terminar e indicarle a Mercurial que deshaga la transacción, para que uste pueda usar hg commit -l nombre_fichero” para reutilizar el mensaje de consignación guardado anteriormente, una vez usted haya corregido el problema.

Como anotación final, note en la figura 10.6 el uso de la característica de edición in-situ de perl para eliminar los espacios en blanco finales en un fichero. Esto es lo suficientemente conciso y poderoso para que lo presente aquí.

1    perl -pi -e 's,s+$,,' nombre_fichero

10.7. Ganchos adicionales

Mercurial se instala con varios ganchos adicionales. Usted puede encontrarlos en el directorio hgext del árbol de ficheros fuente de Mercurial. Si usted está usando un paquete binario de Mercurial, los ganchos estarán ubicados en el directorio hgext en donde su instalador de paquetes haya puesto a Mercurial.

10.7.1. acl—control de acceso a partes de un repositorio

La extensión acl le permite controlar a qué usuarios remotos les está permitido empujar conjuntos de cambios a un servidor en red. Usted puede proteger cualquier porción de un repositorio (incluyendo el repositorio completo), de tal manera que un usuario remoto específico pueda empujar cambios que no afecten la porción protegida.

Esta extensión implementa control de acceso basado en la identidad del usuario que empuja los conjuntos de cambios, no en la identidad de quien hizo la consignación de los mismos. Usar este gancho tiene sentido sólo si se tiene un servidor adecuadamente asegurado que autentique a los usuarios remotos, y si usted desea segurarse de que sólo se le permita a ciertos usuarios empujar cambios a dicho servidor.

Configuración del gancho acl

Para administrar los conjuntos de cambios entrantes, se debe usar el gancho acl como un gancho de tipo pretxnchangegroup. Esto le permite ver qué ficheros son modificados por cada conjunto de cambios entrante, y deshacer el efecto de un grupo de conjuntos de cambios si alguno de ellos modifica algún fichero “prohibido”. Ejemplo:

1  [hooks]
2  pretxnchangegroup.acl = python:hgext.acl.hook

La extensión acl es configurada mediante tres secciones.

La sección [acl] sólo tiene una entrada, sources2, que lista las fuentes de los conjuntos de cambios entrantes a las que el gancho debe prestar atención. Usualmente usted no necesita configurar esta sección.

La sección [acl.allow] controla los usuarios a los que les está permitido añadir conjuntos de cambios al repositorio. Si esta sección no está presente, se le permite acceso a todos los usuarios excepto a los que se les haya negado explícitamente el acceso. Si esta sección no está presente, se niega el acceso a todos los usuarios excepto a todos a los que se les haya permitido de manera explícita (así que una sección vacía implica que se niega el acceso a todos los usuarios).

La sección [acl.deny] determina a qué usuarios no se les permite añadir conjuntos de cambios al repositorio. Si esta sección no está presente o está vacía, no se niega el acceso a ningún usuario.

La sintaxis para los ficheros [acl.allow] y [acl.deny] es idéntica. A la izquierda de cada entrada se encuentra un patrón glob que asocia ficheros o directorios, respecto a la raíz del repositorio; a la derecha, un nombre usuario.

En el siguiente ejemplo, el usuario escritordoc sólo puede empujar cambios al directorio docs del repositorio, mientras que practicante puede enviar cambios a cualquier fichero o directorio excepto fuentes/sensitivo.

1  [acl.allow]
2  docs/⋆⋆ = escritordoc
3  
4  [acl.deny]
5  fuentes/sensitivo/⋆⋆ = practicante
Pruebas y resolución de problemas

Si usted desea probar el gancho acl, ejecútelo habilitando la opción de salida de depuración habilitada. Ya que usted probablemente lo estará ejecutando en un servidor donde no es conveniente (o incluso posible) pasar la opción --debug, no olvide que usted puede habilitar la salida de depuración en su hgrc:

1  [ui]
2  debug = true

Con esto habilitado, el gancho acl imprimirá suficiente información para permitirle saber porqué está permitiendo o denegando la operación de empujar a usuarios específicos.

10.7.2. bugzilla—integración con Bugzilla

La extensión bugzilla añade un comentario a un fallo Bugzilla siempre que encuentre una referencia al ID de dicho fallo en un mensaje de consignación. Usted puede instalar este gancho en un servidor compartido, para que cada vez que un usuario remoto empuje cambios al servidor, el gancho sea ejecutado.

Se añade un comentario al fallo que se ve así (usted puede configurar los contenidos del comentario—vea más abajo):

1  Changeset aad8b264143a, made by Joe User <joe.user@domain.com> in
2  the frobnitz repository, refers to this bug.
3  
4  For complete details, see
5  http://hg.domain.com/frobnitz?cmd=changeset;node=aad8b264143a
6  
7  Changeset description:
8        Fix bug 10483 by guarding against some NULL pointers

El valor de este gancho se encuentra en que automatiza el proceso de actualizar un fallo cuando un conjunto de cambios se refiera a él. Si usted configura este gancho adecuadamente, hará fácil para la gente navegar directamente desde un fallo Bugzilla a un conjunto de cambios que se refiere a ese fallo.

Usted puede usar el código de este gancho como un punto de partida para otras recetas de integración con Bugzilla aún más exóticas. Acá hay algunas posibilidades:

Configuración del gancho bugzilla

Usted debería configurar este gancho en el hgrc de su servidor como un gancho incoming3, por ejemplo como sigue:

1  [hooks]
2  incoming.bugzilla = python:hgext.bugzilla.hook

Debido a la naturaleza especializada de este gancho, y porque Bugzilla no fue escrito con este tipo de integración en mente, configurar este gancho es un proceso algo complejo.

Antes de empezar, usted debe instalar la interfaz de Python para MySQL en los sistemas en los que se vaya a ejecutar el gancho. Si no está disponible como paquete binario para su sistema, usted puede descargar el paquete desde [Dus].

La información para configurar este gancho se ubica en la sección [bugzilla] de su hgrc.

Asociar nombres de consignadores a nombres de usuario Bugzilla

Por defecto, el gancho bugzilla trata de usar la dirección de correo electrónico de la persona que hizo la consignación del conjunto de cambios como el nombre de usuario Bugzilla con el cual debe actualizar el fallo. Si esto no se ajusta a sus necesidades, es posible asociar direcciones de correo a nombres de usuario Bugzilla usando una sección [usermap].

Cada ítem en la sección [usermap] contiene una dirección de correo electrónico a la izquierda, y un nombre de usuario Bugzilla a la derecha.

1  [usermap]
2  jane.user@example.com = jane

Usted puede mantener los datos de [usermap] en un fichero hgrc, o decirle al gancho bugzilla que lea la información desde un fichero usermap externo. En este caso, usted puede almacenar los datos de usermap en (por ejemplo) un repositorio modificable por los usuarios. Esto hace posible para sus usuarios mantener sus propias entradas usermap. El fichero hgrc principal se vería así:

1  # fichero hgrc normal se refiere a un fichero usermap externo
2  [bugzilla]
3  usermap = /home/hg/repos/userdata/bugzilla-usermap.conf

Mientras que el fichero usermap al que se hace referencia se vería así:

1  # bugzilla-usermap.conf - dentro de un repositorio hg
2  [usermap]
3  stephanie@example.com = steph

Configurar el texto que se añade a un fallo

Usted puede configurar el texto que este gancho añade como comentario; usted los especifica como una plantilla Mercurial. Varias entradas hgrc (aún en la sección [bugzilla]) controlan este comportamiento.

Adicionalmente, usted puede añadir un ítem baseurl a la sección [web] de su hgrc. El gancho bugzilla publicará esto cuando expanda una plantilla, como la cadena base a usar cuando se construya una URL que le permita a los usuarios navegar desde un comentario de Bugzilla a la vista de un conjunto de cambios. Ejemplo:

1  [web]
2  baseurl = http://hg.domain.com/

A continuación se presenta un ejemplo completo de configuración para el gancho bugzilla.

1  [bugzilla]
2  host = bugzilla.example.com
3  password = mypassword
4  version = 2.16
5  # server-side repos live in /home/hg/repos, so strip 4 leading
6  # separators
7  strip = 4
8  hgweb = http://hg.example.com/
9  usermap = /home/hg/repos/notify/bugzilla.conf
10  template = Changeset {node|short}, made by {author} in the {webroot}
11    repo, refers to this bug.
nFor complete details, see
12    {hgweb}{webroot}?cmd=changeset;node={node|short}
nChangeset
13    description:
n
t{desc|tabindent}

Pruebas y resolución de problemas

Los problemas más comunes que aparecen en la configuración del gancho bugzilla suelen estar relacionados con la ejecución del guión de Bugzilla processmail y la asociación de nombres de consignadores a nombres de usuario.

Recuerde que en la sección 10.7.2 arriba el usuario que ejecuta el proceso Mercurial en el servidor es también el usuario que ejecutará el guión processmail. El guión processmail algunas veces hace que Bugzilla escriba en ficheros en su directorio de configuración, y los ficheros de configuración de Bugzilla usualmente son propiedad del usuario bajo el cual se ejecuta el servidor web.

Usted puede hacer que processmail sea ejecutado con la identidad del usuario adecuado usando el comando sudo. A continuación se presenta una entrada de ejemplo para un fichero sudoers.

1  hg_user = (httpd_user) NOPASSWD: /var/www/html/bugzilla/processmail-wrapper %s

Esto permite que el usuario hg_user ejecute el programa processmail-wrapper con la identidad del usuario httpd_user.

Esta indirección a través de un guión envoltorio es necesaria, porque processmail espera que al ser ejecutado su directorio actual sea aquel en el cual se instaló Bugzilla; usted no puede especificar ese tipo de condición en un fichero sudoers. Los contenidos del giuón envoltorio son simples:

1  #!/bin/sh
2  cd ‘dirname $0‘ && ./processmail "$1" nobody@example.com

No parece importar qué dirección de correo se le pase a processmail.

Si su [usermap] no es configurada correctamente, los usuarios verán un mensaje de error del gancho bugzilla cuando empujen cambios al servidor. El mensaje de error se verá así:

1  cannot find bugzilla user id for john.q.public@example.com

Lo que esto quiere decir es que la dirección del consignador, john.q.public@example.com, no es un nombre de usuario Bugzilla válido, ni tiene una entrada en su [usermap] que lo asocie con un nombre de usuario válido Bugzilla.

10.7.3. notify—enviar notificaciones de correo electrónico

Aunque el servidor web embebido de Mercurial provee notificaciones de cambios en cada repositorio, muchas personas prefieren recibir las notificaciones de cambios vía correo electrónico. El gancho notify4 le permite a usted enviar notificaciones a un conjunto de direcciones de correo cuando lleguen conjuntos de cambios en los que los subscriptores estén interesados.

De la misma forma que con el gancho bugzilla, el gancho notify está orientado a plantillas, así que usted puede personalizar los contenidos del mensaje de notificación que se envía.

Por defecto, el gancho notify incluye un diff de cada conjunto de cambios que se envía; usted puede limitar el tamaño del diff, o desactivar completamente esta característica. Es útil para permitir a los subscriptores revisar los cambios inmediatamente, en vez de tener que hacer clic para visitar una URL.

Configuración del gancho notify

Usted puede configurar el gancho notify para enviar un mensaje de correo por conjunto de cambios entrante, o uno por grupo entrante de conjuntos de cambios (todos los que llegaron en un único empuje o jalado).

1  [hooks]
2  # enviar un correo por grupo de cambios
3  changegroup.notify = python:hgext.notify.hook
4  # enviar un correo por cambio
5  incoming.notify = python:hgext.notify.hook

La información para configurar este gancho se ubica en la sección [notify] de un fichero hgrc.

Si usted fija el ítem baseurl en la sección [web], usted lo puede usar en una plantilla; estará disponible como webroot.

A continuación se presenta un ejemplo completo de configuración para el gancho notify.

1  [notify]
2  # enviar correo
3  test = false
4  # datos de subscriptores están en el repositorio notify
5  config = /home/hg/repos/notify/notify.conf
6  # repos están en /home/hg/repos on server, así que elimine 4
7  # caracteres"/"
8  strip = 4
9  template = X-Hg-Repo: {webroot}
10    Subject: {webroot}: {desc|firstline|strip}
11    From: {author}
12  
13    changeset {node|short} in {root}
14    details: {baseurl}{webroot}?cmd=changeset;node={node|short}
15    description:
16      {desc|tabindent|strip}
17  
18  [web]
19  baseurl = http://hg.example.com/

Esto producirá un mensaje que se verá como el siguiente:

1  X-Hg-Repo: tests/slave
2  Subject: tests/slave: Handle error case when slave has no buffers
3  Date: Wed,  2 Aug 2006 15:25:46 -0700 (PDT)
4  
5  changeset 3cba9bfe74b5 in /home/hg/repos/tests/slave
6  details: http://hg.example.com/tests/slave?cmd=changeset;node=3cba9bfe74b5
7  description:
8          Handle error case when slave has no buffers
9  diffs (54 lines):
10  
11  diff -r 9d95df7cf2ad -r 3cba9bfe74b5 include/tests.h
12  --- a/include/tests.h      Wed Aug 02 15:19:52 2006 -0700
13  +++ b/include/tests.h      Wed Aug 02 15:25:26 2006 -0700
14  @@ -212,6 +212,15 @@ static __inline__ void test_headers(void ⋆h)
15  [...snip...]

Pruebas y resolución de problemas

No olvide que por defecto, la extensión notify no enviará ningún correo electrónico hasta que usted la configure explícitamente para hacerlo, fijando el valor de test a false. Hasta que usted haga eso, simplemente se imprimirá el mensaje que se enviaría.

10.8. Información para escritores de ganchos

10.8.1. Ejecución de ganchos internos

Un gancho interno es llamado con argumentos de la siguiente forma:

1  def myhook(ui, repo, ⋆⋆kwargs):
2      pass

El parámetro ui es un objeto mercurial.ui.ui. El parámetro repo es un objeto mercurial.localrepo.localrepository. Los nombres y valores de los parámetros en ⋆⋆kwargs dependen del gancho que se invoque, con las siguientes características en común:

Un gancho interno es ejecutado sin cambiar el directorio de trabajo del proceso (a diferencia de los ganchos externos, que son ejecutados desde la raíz del repositorio). El gancho no debe cambiar el directorio de trabajo del proceso, porque esto haría que falle cualquier llamada que se haga a la API de Mercurial.

Si un gancho retorna el valor booleano “false”5, se considera que éste tuvo éxito. Si retorna “true”6 o genera una excepción, se considera que ha fallado. Una manera útil de pensar en esta convención de llamado es “dígame si usted falló”.

Note que los IDs de conjuntos de cambios son pasados a los ganchos de Python como cadenas hexadecimales, no como los hashes binarios que la API de Mercurial usa normalmente. Para convertir un hash de hexadecimal a binario, use la función mercurial.node.bin.

10.8.2. Ejecución de ganchos externos

Un gancho externo es pasado al intérprete de comandos del usuario bajo el cual se ejecuta Mercurial. Las características del intérprete, como sustitución de variables y redirección de comandos, están disponibles. El gancho es ejecutado desde el directorio raíz del repositorio (a diferencia de los ganchos internos, que se ejecutan desde el mismo directorio en que Mercurial fue ejecutado).

Los parámetros para el gancho se pasan como variables de entorno. El nombre de cada variable de entorno se pasa a mayúsculas y se le añade el prefijo “HG_”. Por ejemplo, si el nombre de un parámetro es “node”, el nombre de la variable de entorno que almacena el parámetro se llamará “HG_NODE”.

Un parámetro booleano se representa con la cadena “1” para “true”, “0” para “false”. Si una variable se llama HG_NODE, HG_PARENT1 o HG_PARENT2, contendrá un ID de conjunto de cambios representado como una cadena hexadecimal. La cadena vacía es usada para representar un “ID de conjunto de cambios nulo” en vez de una cadena de ceros. Si una variable de entorno se llama HG_URL, contendrá la URL de un repositorio remoto, si puede ser determinada.

Si un gancho termina con un código de salida de cero, se considera que tuvo éxito. Si termina con un código de salida diferente de cero, se considera que falló.

10.8.3. Averiguar de dónde vienen los conjuntos de cambios

Un gancho que involucra la transferencia de conjuntos de cambios entre un repositorio local y otro puede ser capaz de averiguar información acerca de “el otro lado”. Mercurial sabe cómo son transferidos los conjuntos de cambios, y en muchos casos también desde o hacia donde están siendo transferidos.

Fuentes de conjuntos de cambios

Mercurial le indicará a un gancho cuáles son, o fueron, los medios usados para transferir los conjuntos de cambios entre repositorios. Esta información es provista por Mercurial en un parámetro Python llamado source7, o una variable de entorno llamada HG_SOURCE.

A dónde van los cambios—URLs de repositorios remotos

Cuando es posible, Mercurial le indicará a los ganchos la ubicación de “el otro lado” de una actividad que transfiera datos de conjuntos de cambios entre repositorios. Esto es provisto por Mercurial en un parámetro Python llamado url, o en una variable de entorno llamada HG_URL.

No siempre esta información está disponible. Si un gancho es invocado un repositorio que es servido a través de http o ssh, Mercurial no puede averiguar dónde está el repositorio remoto, pero puede saber desde dónde se conecta el cliente. En esos casos, la URL tendrá una de las siguientes formas:

10.9. Referencia de ganchos

10.9.1. changegroup—luego de añadir conjuntos de cambios remotos

Este gancho es ejecutado luego de que un grupo de conjuntos de cambios preexistentes ha sido añadido al repositorio, por ejemplo vía un hg pull” o hg unbundle”. Este gancho es ejecutado una vez por cada operación que añade uno o más conjuntos de cambios. Este gancho se diferencia del gancho incoming, que es ejecutado una vez por cada conjunto de cambios, sin importar si los cambios llegan en grupo.

Algunos usos posibles para este gancho includen el probar o ensamblar los conjuntos de cambios añadidos, actualizar una base de datos de fallos, o notificar a subscriptores de que el repositorio contiene nuevos cambios.

Parámetros para este gancho:

Veta también: incoming (sección 10.9.3), prechangegroup (sección 10.9.5), pretxnchangegroup (sección 10.9.9)

10.9.2. commit—luego de la creación de un nuevo conjunto de cambios

Este gancho es ejecutado luego de la creación de un nuevo conjunto de cambios.

Parámetros para este gancho:

Vea también: precommit (sección 10.9.6), pretxncommit (sección 10.9.10)

10.9.3. incoming—luego de que un conjunto de cambios remoto es añadido

Este gancho es ejecutado luego de que un conjunto de cambios preexistente ha sido añadido al repositorio, por ejemplo, vía un hg push”. Si un grupo de conjuntos de cambios fue añadido en una sola operación, este gancho es ejecutado una vez por cada conjunto de cambios añadido.

Usted puede usar este gancho para los mismos fines que el gancho changegroup (sección 10.9.1); simplemente algunas veces es más conveniente ejecutar un gancho una vez por cada grupo de conjuntos de cambios, mientras que otras es más útil correrlo por cada conjunto de cambios.

Parámetros para este gancho:

Vea también: changegroup (sección 10.9.1) prechangegroup (sección 10.9.5), pretxnchangegroup (sección 10.9.9)

10.9.4. outgoing—luego de la propagación de los conjuntos de cambios

Este gancho es ejecutado luego de que un grupo de conjuntos de cambios ha sido propagado fuera de éste repositorio, por ejemplo por un comando hg push” o hg bundle”.

Un uso posible para este gancho es notificar a los administradores que los cambios han sido jalados.

Parámetros para este gancho:

Vea también: preoutgoing (sección 10.9.7)

10.9.5. prechangegroup—antes de empezar la adición de conjuntos de cambios remotos

Este gancho de control es ejecutado antes de que Mercurial empiece a añadir un grupo de conjuntos de cambios de otro repositorio.

Este gancho no tiene ninguna información acerca de los conjuntos de cambios que van a ser añadidos, porque es ejecutado antes de que se permita que empiece la transmisión de dichos conjuntos de cambios. Si este gancho falla, los conjuntos de cambios no serán transmitidos.

Un uso para este gancho es prevenir que se añadan cambios externos a un repositorio. Por ejemplo, usted podría usarlo para “congelar” temporal o permanentemente una rama ubicada en un servidor para que los usuarios no puedan empujar cambios a ella, y permitiendo al mismo tiempo modificaciones al repositorio por parte de un administrador local.

Parámetros para este gancho:

Vea también: changegroup (sección 10.9.1), incoming (sección 10.9.3), , pretxnchangegroup (sección 10.9.9)

10.9.6. precommit—antes de iniciar la consignación de un conjunto de cambios

Este gancho es ejecutado antes de que Mercurial inicie la consignación de un nuevo conjunto de cambios. Es ejecutado antes de que Mercurial tenga cualquier de los metadatos para la consignación, como los ficheros a ser consignados, el mensaje de consignación, o la fecha de consignación.

Un uso para este gancho es deshabilitar la capacidad de consignar nuevos conjuntos de cambios, pero permitiendo conjuntos de cambios entrantes. Otro es ejecutar un proceso de ensamble/compilación o prueba, y permitir la consignación sólo si el ensamble/compilación o prueba tiene éxito.

Parámetros para este gancho:

Si la consignación continúa, los padres del directorio de trabajo se convertirán en los padres del nuevo conjunto de cambios.

Vea también: commit (sección 10.9.2), pretxncommit (sección 10.9.10)

10.9.7. preoutgoing—antes de empezar la propagación de conjuntos de cambios

Este gancho es ejecutado antes de que Mercurial conozca las identidades de los conjuntos de cambios que deben ser transmitidos.

Un uso para este gancho es evitar que los cambios sean transmitidos a otro repositorio.

Parámetros para este gancho:

Vea también: outgoing (sección 10.9.4)

10.9.8. pretag—antes de etiquetar un conjunto de cambios

Este gancho de control es ejecutado antes de la creación de una etiqueta. Si el gancho termina exitosamente, la creación de la etiqueta continúa. Si el gancho falla, no se crea la etiqueta.

Parámetros para este gancho:

Si la etiqueta que se va a crear se encuentra bajo control de revisiones, los ganchos precommit y pretxncommit (secciones 10.9.210.9.10) también serán ejecutados.

Vea también: tag (sección 10.9.12)

10.9.9. pretxnchangegroup—antes de completar la adición de conjuntos de cambios remotos

Este gancho de control es ejecutado antes de una transacción—la que maneja la adición de un grupo de conjuntos de cambios nuevos desde fuera del repositorio—se complete. Si el gancho tiene éxito, la transacción se completa, y todos los conjuntos de cambios se vuelven permanentes dentro de este repositorio. Si el gancho falla, la transacción es deshecha, y los datos para los conjuntos de cambios son eliminados.

Este gancho puede acceder a los metadatos asociados con los conjuntos de cambios casi añadidos, pero no debe hacer nada permanente con estos datos. Tampoco debe modificar el directorio de trabajo.

Mientras este gancho está corriendo, si otro proceso Mercurial accesa el repositorio, podrá ver los conjuntos de cambios casi añadidos como si fueran permanentes. Esto puede llevar a condiciones de carrera si usted no toma precauciones para evitarlas.

Este gancho puede ser usado para examinar automáticamente un grupo de conjuntos de cambios. Si el gancho falla, todos los conjuntos de cambios son “rechazados” cuando la transacción se deshace.

Parámetros para este gancho:

Vea también: changegroup (sección 10.9.1), incoming (sección 10.9.3), prechangegroup (sección 10.9.5)

10.9.10. pretxncommit—antes de completar la consignación de un nuevo conjunto de cambios

Este gancho de control es ejecutado antes de que una transacción—que maneja una nueva consignación—se complete. Si el gancho tiene éxito, la transacción se completa y el conjunto de cambios se hace permanente dentro de éste repositorio. Si el gancho falla, la transacción es deshecha, y los datos de consignación son borrados.

Este gancho tiene acceso a los metadatos asociados con el prácticamente nuevo conjunto de cambios, pero no debería hacer nada permanente con estos datos. Tampoco debe modificar el directorio de trabajo.

Mientras este gancho está corriendo, si otro proceso Mercurial accesa éste repositorio, podrá ver el prácticamente nuevo conjunto de cambios como si fuera permanente. Esto puede llevar a condiciones de carrera si usted no toma precauciones para evitarlas.

Parámetros para este gancho:

Vea también: precommit (sección 10.9.6)

10.9.11. preupdate—antes de actualizar o fusionar el directorio de trabajo

Este gancho de control es ejecutado antes de actualizar o fusionar el directorio de trabajo. Es ejecutado sólo si las revisiones usuales de Mercurial antes de las actualizaciones determinan que la actualización o fusión pueden proceder. Si el gancho termina exitosamente, la actualización o fusión pueden proceder.; si falla, la actualización o fusión no empiezan.

Parámetros para este gancho:

Vea también: update (sección 10.9.13)

10.9.12. tag—luego de etiquetar un conjunto de cambios

Este gancho es ejecutado luego de la creación de una etiqueta.

Parámetros para este gancho:

Si la etiqueta creada está bajo control de revisiones, el gancho commit (sección 10.9.2) es ejecutado antes de este gancho.

Vea también: pretag (sección 10.9.8)

10.9.13. update—luego de actualizar o fusionar el directorio de trabajo

Este gancho es ejecutado después de una actualización o fusión en el directorio de trabajo. Ya que una fusión puede fallar (si el comando externo hgmerge no puede resolver los conflictos en un fichero), este gancho indica si la actualización o fusión fueron completados adecuadamente.

Vea también: preupdate (sección 10.9.11)

Capítulo 11
Personalizar los mensajes de Mercurial

Mercurial provee un poderoso mecanismo que permite controlar como despliega la información. El mecanismo se basa en plantillas. Puede usar plantillas para generar salida específica para una orden particular o para especificar la visualización completa de la interfaz web embebida.

11.1. Usar estilos que vienen con Mercurial

Hay ciertos estilos listos que vienen con Mercurial. Un estilo es simplemente una plantilla predeterminada que alguien escribió e instaló en un sitio en el cual Mercurial puede encontrarla.

Antes de dar un vistazo a los estilos que trae Mercurial, revisemos su salida usual.

1  $ hg log -r1
2  changeset:   1:522d8d7a8fda
3  tag:         mytag
4  user:        Bryan O'Sullivan <bos@serpentine.com>
5  date:        Tue Feb 10 18:23:30 2009 +0000
6  summary:     added line to end of <<hello>> file.
7  

Es en cierta medida informativa, pero ocupa mucho espacio—cinco líneas de salida por cada conjunto de cambios. El estilo compact lo reduce a tres líneas, presentadas de forma suscinta.

1  $ hg log --style compact
2  3[tip]   b638ce454bd0   2009-02-10 18:23 +0000   bos
3    Added tag v0.1 for changeset 4b75acdd4698
4  
5  2[v0.1]   4b75acdd4698   2009-02-10 18:23 +0000   bos
6    Added tag mytag for changeset 522d8d7a8fda
7  
8  1[mytag]   522d8d7a8fda   2009-02-10 18:23 +0000   bos
9    added line to end of <<hello>> file.
10  
11  0   d0c3b909ac1b   2009-02-10 18:23 +0000   bos
12    added hello
13  

El estilo de la bitácora de cambios vislumbra el poder expresivo del sistema de plantillas de Mercurial. Este estilo busca seguir los estándares de bitácora de cambios del proyecto GNU[RS].

1  $ hg log --style changelog
2  2009-02-10  Bryan O'Sullivan  <bos@serpentine.com>
3  
4   ⋆ .hgtags:
5   Added tag v0.1 for changeset 4b75acdd4698
6   [b638ce454bd0] [tip]
7  
8   ⋆ .hgtags:
9   Added tag mytag for changeset 522d8d7a8fda
10   [4b75acdd4698] [v0.1]
11  
12   ⋆ goodbye, hello:
13   added line to end of <<hello>> file.
14  
15   in addition, added a file with the helpful name (at least i hope
16   that some might consider it so) of goodbye.
17   [522d8d7a8fda] [mytag]
18  
19   ⋆ hello:
20   added hello
21   [d0c3b909ac1b]
22  

No es una sorpresa que el estilo predeterminado de Mercurial se llame default1.

11.1.1. Especificar un estilo predeterminado

Puede modificar el estilo de presentación que Mercurial usará para toda orden vía el fichero hgrc indicando el estilo que prefiere usar.

1  [ui]
2  style = compact

Si escribe un estilo, puede usarlo bien sea proveyendo la ruta a su fichero de estilo o copiando su fichero de estilo a un lugar en el cual Mercurial pueda encontrarlo (típicamente el subdirectorio templates de su directorio de instalación de Mercurial).

11.2. Órdenes que soportan estilos y plantillas

Todas las órdenes de Mercurial“relacionadas con log” le permiten usar estilos y plantillas: hg incoming”, hg log”, hg outgoing” y hg tip”.

Al momento de la escritura del manual estas son las únicas órdenes que soportan estilos y plantillas. Dado que son las órdenes más importantes que necesitan personalización, no ha habido muchas solicitudes desde la comunidad de usuarios de Mercurial para añadir soporte de plantillas y estilos a otras órdenes.

11.3. Cuestiones básicas de plantillas

Una plantilla de Mercurial es sencillamente una pieza de texto. Cierta porción nunca cambia, otras partes se expanden, o reemplazan con texto nuevo cuando es necesario.

Antes de continuar, veamos de nuevo un ejemplo sencillo de la salida usual de Mercurial:

1  $ hg log -r1
2  changeset:   1:522d8d7a8fda
3  tag:         mytag
4  user:        Bryan O'Sullivan <bos@serpentine.com>
5  date:        Tue Feb 10 18:23:30 2009 +0000
6  summary:     added line to end of <<hello>> file.
7  

Ahora, ejecutemos la misma orden, pero usemos una plantilla para modificar su salida:

1  $ hg log -r1 --template 'i saw a changesetn'
2  i saw a changeset

El ejemplo anterior ilustra la plantilla más sencilla posible; es solamente una porción estática de código que se imprime una vez por cada conjunto de cambios. La opción --template de la orden hg log” indica a Mercurial usar el texto dado como la plantilla cuando se imprime cada conjunto de cambios.

Observe que la cadena de plantilla anterior termina con el texto “\n”. Es una secuencia de control, que le indica a Mercurial imprimira una nueva línea al final de cada objeto de la plantilla. Si omite esta nueva línea, Mercurial colocará cada pieza de salida seguida. Si desea más detalles acerca de secuencias de control, vea la sección 11.5.

Una plantilla que imprime una cadena fija de texto siempre no es muy útil; intentemos algo un poco más complejo.

1  $ hg log --template 'i saw a changeset: {desc}∖n'
2  i saw a changeset: Added tag v0.1 for changeset 4b75acdd4698
3  i saw a changeset: Added tag mytag for changeset 522d8d7a8fda
4  i saw a changeset: added line to end of <<hello>> file.
5  
6  in addition, added a file with the helpful name (at least i hope that some might consider it so) of goodbye.
7  i saw a changeset: added hello

Como puede ver, la cadena “{desc}” en la plantilla ha sido reemplazada en la salida con la descricipción de cada conjunto de cambios. Cada vez que Mercurial encuentra texto encerrado entre corchetes (“{” y “}”), intentará reemplazar los corchetes y el texto con la expansión de lo que sea está adentro. Para imprimir un corchete de forma literal, debe escaparlo, como se describe en la sección 11.5.

11.4. Palabras claves más comunes en las plantillas

Puede empezar a escribir plantillas sencillas rápidamente con las palabras claves descritas a continuación:

Unos experimentos sencillos nos mostrarán qué esperar cuando usamos estas palabras claves; puede ver los resultados en la figura 11.1.


1  $ hg log -r1 --template 'author: {author}∖n'
2  author: Bryan O'Sullivan <bos@serpentine.com>
3  $ hg log -r1 --template 'desc:n{desc}∖n'
4  desc:
5  added line to end of <<hello>> file.
6  
7  in addition, added a file with the helpful name (at least i hope that some might consider it so) of goodbye.
8  $ hg log -r1 --template 'files: {files}∖n'
9  files: goodbye hello
10  $ hg log -r1 --template 'file_adds: {file_adds}∖n'
11  file_adds: goodbye
12  $ hg log -r1 --template 'file_dels: {file_dels}∖n'
13  file_dels:
14  $ hg log -r1 --template 'node: {node}∖n'
15  node: 522d8d7a8fda9a24dcac9c5ccfcca067b933220b
16  $ hg log -r1 --template 'parents: {parents}∖n'
17  parents:
18  $ hg log -r1 --template 'rev: {rev}∖n'
19  rev: 1
20  $ hg log -r1 --template 'tags: {tags}∖n'
21  tags: mytag

Figura 11.1: Template keywords in use

Como mencionamos anteriormente, la palabra clave de fecha no produce salida legible por un humano, debemos tratarla de forma especial. Esto involucra usar un filtro, acerca de lo cual hay más en la sección 11.6.

1  $ hg log -r1 --template 'date: {date}∖n'
2  date: 1234290210.00
3  $ hg log -r1 --template 'date: {date|isodate}∖n'
4  date: 2009-02-10 18:23 +0000

11.5. Secuencias de Control

El motor de plantillas de Mercurial reconoce las secuencias de control más comunmente usadas dentro de las cadenas. Cuando ve un backslash (“\”), ve el caracter siguiente y sustituye los dos caracteres con un reemplazo sencillo, como se describe a continuación:

Como se indicó arriba, si desea que la expansión en una plantilla contenga un caracter literal “\”, “{”, o “{”, debe escaparlo.

11.6. Uso de filtros con palabras claves

Algunos de los resultados de la expansión de la plantilla no son fáciles de usar de inmediato. Mercurial le permite especificar una cadena de filtros opcionales para modificar el resultado de expandir una palabra clave. Ya ha visto el filtro usual isodate en acción con anterioridad para hacer legible la fecha.

A continuación hay una lista de los filtros de Mercurial más comunmente usados. Ciertos filtros pueden aplicarse a cualquier texto, otros pueden usarse únicamente en circunstancias específicas. El nombre de cada filtro está seguido de la indicación de dónde puede ser usado y una descripción de su efecto.


1  $ hg log -r1 --template '{author}∖n'
2  Bryan O'Sullivan <bos@serpentine.com>
3  $ hg log -r1 --template '{author|domain}∖n'
4  serpentine.com
5  $ hg log -r1 --template '{author|email}∖n'
6  bos@serpentine.com
7  $ hg log -r1 --template '{author|obfuscate}∖n' | cut -c-76
8  Bryan O'Sulli
9  $ hg log -r1 --template '{author|person}∖n'
10  Bryan O'Sullivan
11  $ hg log -r1 --template '{author|user}∖n'
12  bos
13  $ hg log -r1 --template 'looks almost right, but actually garbage: {date}∖n'
14  looks almost right, but actually garbage: 1234290210.00
15  $ hg log -r1 --template '{date|age}∖n'
16  1 second
17  $ hg log -r1 --template '{date|date}∖n'
18  Tue Feb 10 18:23:30 2009 +0000
19  $ hg log -r1 --template '{date|hgdate}∖n'
20  1234290210 0
21  $ hg log -r1 --template '{date|isodate}∖n'
22  2009-02-10 18:23 +0000
23  $ hg log -r1 --template '{date|rfc822date}∖n'
24  Tue, 10 Feb 2009 18:23:30 +0000
25  $ hg log -r1 --template '{date|shortdate}∖n'
26  2009-02-10
27  $ hg log -r1 --template '{desc}∖n' | cut -c-76
28  added line to end of <<hello>> file.
29  
30  in addition, added a file with the helpful name (at least i hope that some m
31  $ hg log -r1 --template '{desc|addbreaks}∖n' | cut -c-76
32  added line to end of <<hello>> file.<br/>
33  <br/>
34  in addition, added a file with the helpful name (at least i hope that some m
35  $ hg log -r1 --template '{desc|escape}∖n' | cut -c-76
36  added line to end of <<hello>> file.
37  
38  in addition, added a file with the helpful name (at least i hope that some m
39  $ hg log -r1 --template '{desc|fill68}∖n'
40  added line to end of <<hello>> file.
41  
42  in addition, added a file with the helpful name (at least i hope
43  that some might consider it so) of goodbye.
44  $ hg log -r1 --template '{desc|fill76}∖n'
45  added line to end of <<hello>> file.
46  
47  in addition, added a file with the helpful name (at least i hope that some
48  might consider it so) of goodbye.
49  $ hg log -r1 --template '{desc|firstline}∖n'
50  added line to end of <<hello>> file.
51  $ hg log -r1 --template '{desc|strip}∖n' | cut -c-76
52  added line to end of <<hello>> file.
53  
54  in addition, added a file with the helpful name (at least i hope that some m
55  $ hg log -r1 --template '{desc|tabindent}∖n' | expand | cut -c-76
56  added line to end of <<hello>> file.
57  
58          in addition, added a file with the helpful name (at least i hope tha
59  $ hg log -r1 --template '{node}∖n'
60  522d8d7a8fda9a24dcac9c5ccfcca067b933220b
61  $ hg log -r1 --template '{node|short}∖n'
62  522d8d7a8fda

Figura 11.2: Filtros de plantilla en acción

Nota: Si trata de aplicar un filtro a una porción de datos que no puede procesarse, Mercurial fallará e imprimirá una excepción de Python. Por ejemplo, el tratar de usar la salida de la palabra clave desc con el filtro isodate no resultará algo útil.

11.6.1. Combinar filtros

Combinar filtros es para generar una salida en la forma como usted lo desea es muy sencillo. La cadena de filtros siguientes arman una descripción, después aseguran que cabe limpiamente en 68 columnas, y las indenta con 8 caracteres (por lo menos en sistemas tipo Unix, en los que el tab por convención se extiende en 8 caracteres).

1  $ hg log -r1 --template 'description:nt{desc|strip|fill68|tabindent}∖n'
2  description:
3   added line to end of <<hello>> file.
4  
5   in addition, added a file with the helpful name (at least i hope
6   that some might consider it so) of goodbye.

Observe el uso de “\t” (un caracter tab) en la plantilla para forzar que la primera línea se indente; esto es necesario para lograr que la primera línea luzca indentada; es necesario debido a que tabindent indenta todas las líneas excepto la primera.

Tenga en cuenta que el orden de los filtros importa. El primer filtro se aplica primero al resultado de la palabra clave; el segundo al resultado de la aplicación del primer filtro y así sucesivamente. Por ejemplo, usar fill68|tabindent es muy distinto al resultado de usar tabindent|fill68.

11.7. De plantillas a estilos

Una plantilla provee una forma rápida y sencilla para dar formato a una salida. Las plantillas pueden volvers verbosas, y es útil poder darle un nombre a una plantilla. Un fichero de estilo es una plantilla con un nombre, almacenado en un fichero.

Más aún, al usar un fichero de estilo se dispara el poder del motor de plantillas en un nivel imposible de alcanzar usando las opción --template desde la línea de órdenes.

11.7.1. Los ficheros de estilo más sencillos

Nuestro fichero sencillo de estilo contiene una sola línea:

1  $ echo 'changeset = "rev: {rev}∖n"' > rev
2  $ hg log -l1 --style ./rev
3  rev: 3

Se le indica a Mercurial, “si está imprimiendo un conjunto de cambios, use el texto de la derecha como la plantilla”.

11.7.2. Sintaxis de ficheros de estilo

Las reglas de sintaxis para un fichero de estilo son sencillas:

11.8. Ejemplos de ficheros de estilos

Para ilustrar la creación de un fichero de estilo, construiremos algunos ejemplos. En lugar de ofrecer un fichero completo de estilo y analizarlo, replicaremos el proceso usual de desarrollo de un fichero de estilo comenzando con algo muy sencillo, y avanzando por una serie de ejemplos sucesivos más completos.

11.8.1. Identificar equivocaciones en ficheros de estilo

Si Mercurial encuentra un problema en un fichero de estilo en el cual usted está trabajando, imprime un mensaje de error suscinto, cuando usted identifique lo que significa, resulta muy útil.

1  $ cat broken.style
2  changeset =

Tenga en cuenta que broken.style trata de definir la palabra clave changeset, pero omite dar un contenido para esta. Cuando se le indica a Mercurial que use este fichero de estilo, se queja inmediatamente.

1  $ hg log -r1 --style broken.style
2  abort: broken.style:1: parse error

Este mensaje de error luce intimidante, pero no es muy difícil de seguir:

11.8.2. Identificar de forma única un repositorio

Si desea identificar un repositorio de Mercurial “de forma única” con una cadena corta como identificador, puede usar la primera revisión en el repositorio.

1  $ hg log -r0 --template '{node}'
2  92cb4692d38c537be0935f906eeff2a47033600c

No es garantía de unicidad, pero no es útill en ciertos casos: many cases.

Hay ciertos casos en los cuales podría colocar el identificador:

11.8.3. Mostrando salida parecida a Subversion

Intentemos emular la salida usual que usa otro sistema de control de revisiones, Subversion.

1  $ svn log -r9653
2  ------------------------------------------------------------------------
3  r9653 | sean.hefty | 2006-09-27 14:39:55 -0700 (Wed, 27 Sep 2006) | 5 lines
4  
5  On reporting a route error, also include the status for the error,
6  rather than indicating a status of 0 when an error has occurred.
7  
8  Signed-off-by: Sean Hefty <sean.hefty@intel.com>
9  
10  ------------------------------------------------------------------------

Dado que la salida de Subversion es sencilla, es fácil copiar y pegar una porción de su salida en un fichero, y reemplazar el texto producido previamente por Subversion con valores base que quisiéramos ver expandidos.

1  $ cat svn.template
2  r{rev} | {author|user} | {date|isodate} ({date|rfc822date})
3  
4  {desc|strip|fill76}
5  
6  ------------------------------------------------------------------------

Esta plantilla difiere en algunos detalles de la salida producida por Subversion:

No me tomó más de un minuto o dos de trabajo para reemplazar texto literal de un ejemplo de salida de la salida de Subversion con ciertas palabras claves y filtros para ofrecer la plantilla anterior. El fichero de estilo se refiere sencillamente a la plantilla.

1  $ cat svn.style
2  header = '------------------------------------------------------------------------nn'
3  changeset = svn.template

Podríamos haber incluído el texto del fichero plantilla directamente en el fichero de estilo encerrando entre comillas y reemplazando las nuevas líneas con secuencias “\n”, pero haría muy difícil de leer el fichero de estilos. La facilidad para leer es importante cuando está decidiendo si un texto pertenece a un fichero de estilo o a un fichero de plantilla incluído en el estilo. Si el fichero de estilo luce muy grande o complicado, si inserta una pieza de texto literal, mejor colóquelo en una plantilla.

Capítulo 12
Administración de cambios con Colas de Mercurial

12.1. El problema de la administración de parches

Un escenario frecuente: usted necesita instalar un paquete de software desde las fuentes, pero encuentra un fallo que debe arreglar antes de poder comenzar a usarlo. Hace sus cambios, y se olvida del paquete por un tiempo, unos meses después necesita actualizar a una nueva versión del paquete. Si la nueva versión del paquete todavía tiene el fallo, debe extraer su arreglo del árbol de fuentes anteriores y aplicarlo a la nueva versión. Una tarea tediosa en la cual es fácil equivocarse.

Este es un caso simple del problema del “manejo de parches”. Usted tiene un árbol de fuentes del “mantenedor principal” que no puede cambiar: necesita hacer algunos cambios locales sobre el árbol principal; y desearía poder mantener tales cambios separados, de forma tal que pueda aplicarlos a versiones más nuevas del árbol principal.

El problema de administración de parches surge en muchas situaciones. Probablemente la más visible es cuando un usuario de un proyecto de software de fuentes abiertas contribuye con un arreglo de un fallo o una nueva característica a los mantenedores del proyecto en la forma de un parche.

Aquellos que distribuyen sistemas operativos que incluyen programas abiertos usualmente requieren hacer cambios en los paquetes que distribuyen de tal forma que se armen apropiadamente en sus ambientes.

Cuando hay pocos cambios por mantener, es muy sencillo administrar un solo parche con los programas estándar diff y patch (ver la sección 12.4 para ver cómo emplear tales herramientas). Cuando la cantidad de cambios comienza a crecer, tiene sentido mantener parches como “porciones de trabajo” individual, de forma que cada cambio contiene solamente un arreglo de un fallo (el parche puede modificar varios ficheros, pero está “haciendo una sola cosa”), y puede tener cierta cantidad de tales parches para diferentes fallos y cambios locales. En esta situación, si envía un parche que arregla un fallo a los mantenedores principales de un paquete y ellos incluyen su arreglo en una publicación posterior, puede deshacerse de tal parche cuando se actualice a la nueva versión.

Mantener un solo parche frente a un árbol principal es algo tedioso y es fácil equivocarse, pero no es difícil. Aunque, la complejidad del problema crece rápidamente a medida que la cantidad de parches que tiene que mantener crece. Con más que una pequeña cantidad de cambios, entender cuáles ha aplicado se convierte de algo desordenado a algo avasallante.

Afortunadamente Mercurial provee una extensión poderos: Colas de Mercurial (o simplemente “MQ”), que simplifica en gran medida el problema de administración de parches.

12.2. La prehistoria de las Colas de Mercurial

A finales de los 90s, muchos desarrolladores del núcleo de Linux comenzaron a mantener “series de parches” que modificaron el comportamiento del núcleo de Linux. Algunos se enfocaban en estabilidad, otros en aumentar las características, y otros un poco más especulativos.

Los tamaños de las series de parches crecieron rápidamente. En el 2002, Andrew Morton publicó algunos guiones de línea de órdenes que estuvo usando para automatizar la tarea de administrar su cola de parches. Andrew usó exitósamente tales guiones para administrar centenas (a veces millares) de parches en el núcleo de Linux.

12.2.1. Trabajar parches con quilt

A comienzos del 2003, Andreas Gruenbacher y Martin Quinson tomaron la aproximación de los guiones de Andrew y publicaron una herramienta llamada “patchwork quilt” [AG], o simplemente “quilt” (ver [Gru05] el paper que lo describe). Dado que quilt automatizaba sustancialmente la administración de parches, fue adoptado en gran medida por desarrolladores de programas abiertos.

Quilt maneja una pila de parches sobre un árbol de directorios. Para comenzar, usted le indica a quilt que administre un árbol de directorios, le indica qué ficheros manejar; Este almacena los nombres y los contenidos de estos ficheros. Para arreglar un fallo, usted crea un nuevo parche (con una sola orden), edita los ficheros que está arreglando y “refresca” el parche.

El paso de refresco hace que quilt revise el árbol de directorios; actualiza el parche con todos los cambios que usted haya hecho. Puede crear otro parche sobre el primero, que hará seguimiento de los cambios requeridos para modificar el árbol desde “el árbol con un parch aplicado” a un “árbol con dos parches aplicados”.

Usted puede elegir qué cambios desea aplicar al árbol. Si “pop”1 un parche, los cambios hechos por tal parchve desapareceŕan del árbol de directorios. Quilt recuerda qué parches ha sacado, para que pueda “introducirlos”2 posteriormente, así el árbol de directorios se restaurará con las modificaciones que vienen del parche. Lo más importante es que puede ejecutar la orden “refresh” en cualquier momento, y el último parche será actualizado. Esto significa que puede, en cualquier momento, cambiar qué parches serán aplicados y qué modificaciones hacen ellos.

Quilt no tiene nada que ver con herramientas de control de versiones, y puede trabajar bien sobre un conjunto de fuentes que viene de un fichero comprimido y empaquetado o una copia de trabajo de Subversion.

12.2.2. Pasar de trabajo con parches con Quilt hacia Colas de Mercurial

A mediados de 2005, Chris Mason tomó las características de quilt y escribió una extensión que llamó Colas de Mercurial3, que proporcionó un comportamiento a Mercurial al estilo quilt.

La diferencia clave entre quilt y MQ es que quilt no sabe nada acerca del sistema de control de revisiones, mientras que MQ está integrado con Mercurial. Cada parche que usted introduce se representa como un conjunto de cambios en Mercurial. Si sustrae un parche, el conjunto de cambios desaparece.4

Dado que quilt no se preocupa por las herramientas de control de revisiones, continúa siendo una porción de software tremendamente útil para aquellas situaciones en las cuales no puede usar Mercurial y MQ.

12.3. La gran ventaja de MQ

No puedo sobreestimar el valor que MQ ofrece en la unificación de parches y el control de revisiones.

La principal razón por la cual los parches han persistido en el mundo del software libre y de fuentes abiertas–a pesar de la creciente disponibilidad de herramientas poderosas de control de revisiones– es la agilidad que ofrecen.

Las herramientas tradicionales de control de revisiones llevan un registro permanente e irreversible de todo lo que usted hace. A pesar de que esto tiene gran valor, también es bastante sutil. Si requiere realizar un experimento ((((wild-eyed)))), debe ser cuidadoso en cómo lo hace, o puede dejar trazas innecesarias–o peor aún, desconcertantes o desestabilizantes— de los pasos y errores en el registro de revisiones de forma permanente.

En contraste, con la cohesión de MQ con el control de revisiones distribuidos y los parches, resulta más sencillo aislar su trabajo. Sus parches viven encima del historial de revisiones normales, y puede hacer que ellos desaparezcan o reaparezcan cuando lo desee. Si no le gusta un parche, puede desecharlo. Si un parche no satisface todo lo que usted desea, puede arreglarlo—tantas veces como lo requiera, hasta que lo haya refinado lo suficiente hacia sus expectativas.

Por ejemplo, la integración de parches con el control de revisiones hace que el entender los parches y revisar sus efectos—y sus interacciones con el código en el cuál están enlazados— sea mucho más sencillo. Dado que todo parche que se aplique tiene un conjunto de cambios asociado, puede usar hg log filename” para ver qué conjuntos de cambios y parches afectaron un fichero. Puede usar la orden bisect para hacer una búsqueda binaria sobre todos los conjuntos de cambios y parches aplicados para ver dónde se introdujo un fallo o dónde fue arreglado. Puede usar la orden hg annotate” para ver qué conjuntos de cambios o parches modificaron una línea particular de un fichero fuente. Y mucho más.

12.4. Entender los parches

Dado que MQ no esconde su naturaleza parche-céntrica, es muy útil para entender de qué se tratan los parches, y un poco acerca de las herramientas que trabajan con ellos.

La orden de Unix tradicional diff compara dos ficheros, e imprime una lista de diferencias de sus líneas. La orden patch entiende esas diferencias como modificaciones para construir un fichero. Vea en la figura 12.1 un ejemplo sencillo de tales órdenes en acción.


1  $ echo 'this is my original thought' > oldfile
2  $ echo 'i have changed my mind' > newfile
3  $ diff -u oldfile newfile > tiny.patch
4  $ cat tiny.patch
5  --- oldfile 2009-02-10 18:23:25.000000000 +0000
6  +++ newfile 2009-02-10 18:23:25.000000000 +0000
7  @@ -1 +1 @@
8  -this is my original thought
9  +i have changed my mind
10  $ patch < tiny.patch
11  patching file oldfile
12  $ cat oldfile
13  i have changed my mind

Figura 12.1: Uso sencillo de las órdenes diff y patch

El tipo de fichero que diff genera (y que patch toma como entrada) se llama un “parche” o un “diff”; no hay diferencia entre un parche y un diff. (Usaremos el término “parche”, dado que es el que más comunmente se usa.)

Un parche puede comenzar con un texto arbitrario; la orden patch ignora este texto, pero MQ lo usa como el mensaje de consignación cuando se crean conjuntos de cambios. Para encontrar el inicio del contenido de un parche, la orden patch busca la primera línea que comience con la cadena “diff -”.

MQ trabaja con diffs unificados (patch acepta varios formatos de diff adicionales, pero MQ no). Un diff unificado contiene dos clases de encabezados. El encabezado de fichero describe el fichero que se está modificando; contiene el nombre del fichero a modificar. Cuando patch ve un nuevo encabezado de fichero, busca un fichero con ese nombre para modificarlo.

Después del encabezaado vienen varios trozos. Cada trozo comienza con un encabezado; que identifica el rango de líneas del fichero que el trozo debe modificar. Después del encabezado, un trozo comienza y termina con unas pocas líneas (usualmente tres) de texto del fichero que no han sido modificadas; las cuales llamamos el contexto del trozo. Si solamente hay una pequeña cantidad de contexto entre trozos sucesivos, diff no imprime un nuevo encabezado para el trozo, continua integrando los trozos, con unas líneas de contexto entre las modificaciones.

Cada línea de contexto comienza con un caracter de espacio. En el trozo, si una línea comienza con “-” significa “elimine esta línea”, si la línea comienza con un “+” significa “inserte esta línea”. Por ejemplo, una línea que se modifica se representa con una línea eliminada y una línea insertada.

Retomaremos aspectos más sutiles acerca de parches posteriormente (en la sección 12.6), pero en el momento usted ya debería tener suficiente información para usar MQ.

12.5. Comenzar a usar Colas de Mercurial

Dado que MQ está implementado como una extensión, debe habilitarla explícitamente antes de comenzar a usarla. (No necesita descargar nada; MQ viene con la distribución estándar de Mercurial.) Para habilitar MQ, edite su fichero ĩ/.hgrc, y añada las líneas de la figura 12.5.


1  [extensions]
2  hgext.mq =

Figura 12.2: Líneas a añadir en ĩ/.hgrc para habilitar la extensión MQ

Cuando la extensión esté habilitada, aparecerán varios comandos. Para verificar que la extensión está trabajando, puede usar hg help” para ver si la orden hg qinit” está disponible; vea un ejemplo en la figura 12.3.


1  $ hg help qinit
2  hg qinit [-c]
3  
4  init a new queue repository
5  
6      The queue repository is unversioned by default. If -c is
7      specified, qinit will create a separate nested repository
8      for patches (qinit -c may also be run later to convert
9      an unversioned patch repository into a versioned one).
10      You can use qcommit to commit changes to this queue repository.
11  
12  options:
13  
14   -c --create-repo  create queue repository
15  
16  use "hg -v help qinit" to show global options

Figura 12.3: Cómo verificar que MQ está habilitado

Puede usar MQ en cualquier repositorio de Mercurial, y sus comandos solamente operarán con tal repositorio. Para comenzar, basta con preparar el repositorio con la orden hg qinit” (ver la figura 12.4). Esta orden crea un directorio vacío llamado .hg/patches, donde MQ mantendrá sus metadatos. Como otras ordenes de Mercurial, la orden hg qinit” no imprime nada cuando es exitosa.


1  $ hg init mq-sandbox
2  $ cd mq-sandbox
3  $ echo 'line 1' > file1
4  $ echo 'another line 1' > file2
5  $ hg add file1 file2
6  $ hg commit -m'first change'
7  $ hg qinit

Figura 12.4: Preparar un repositorio para usar MQ


1  $ hg tip
2  changeset:   0:90039acadb36
3  tag:         tip
4  user:        Bryan O'Sullivan <bos@serpentine.com>
5  date:        Tue Feb 10 18:23:27 2009 +0000
6  summary:     first change
7  
8  $ hg qnew first.patch
9  $ hg tip
10  changeset:   1:495236f727e1
11  tag:         qtip
12  tag:         first.patch
13  tag:         tip
14  tag:         qbase
15  user:        Bryan O'Sullivan <bos@serpentine.com>
16  date:        Tue Feb 10 18:23:27 2009 +0000
17  summary:     [mq]: first.patch
18  
19  $ ls .hg/patches
20  first.patch  series  status

Figura 12.5: Crear un nuevo parche

12.5.1. Crear un nuevo parche

Para comenzar a trabajar en un nuevo parche use la orden hg qnew”. Esta orden recibe un argumento, el nombre del parche a crear. MQ lo usará como el nombre del fichero en el directorio .hg/patches, como puede apreciarlo en la figura 12.5.

También hay otros dos nuevos ficheros en el directorio .hg/patches: series y status. El fichero series lista todos los parches de los cuales MQ tiene noticia para este repositorio, con un parche por línea. Mercurial usa el fichero status para mantener registros interns; da seguimiento a todos los parches que MQ ha aplicado en el repositorio.

Nota: En ciertas ocasiones usted querrá editar el fichero series a mano; por ejemplo, cambiar el orden en que se aplican ciertos parches. A pesar de esto, es una mala idea editar manualmente el fichero status, dado que es fácil desorientar a MQ acerca de lo que está pasando.

Una vez que haya creado un nuevo parche, puede editar los ficheros en el directorio de trabajo, como lo haría usualmente. Toda las órdenes que de a Mercurial, tales como hg diff” y hg annotate”, trabajarán de la misma forma como lo han hecho antes.

12.5.2. Refrescar un parche

Cuando usted llega a un punto en el cual desea guardar su trabajo, use la orden hg qrefresh” (figura 12.5) para actualizar el parche en el cual está trabajando. Esta orden almacena los cambios que haya hecho al directorio actual de trabajo en su parche, y almacena el conjunto de cambios correspondiente que contiene los cambios.


1  $ echo 'line 2' >> file1
2  $ hg diff
3  diff -r 495236f727e1 file1
4  --- a/file1 Tue Feb 10 18:23:27 2009 +0000
5  +++ b/file1 Tue Feb 10 18:23:27 2009 +0000
6  @@ -1,1 +1,2 @@
7   line 1
8  +line 2
9  $ hg qrefresh
10  $ hg diff
11  $ hg tip --style=compact --patch
12  1[qtip,first.patch,tip,qbase]   131b8ed49ec4   2009-02-10 18:23 +0000   bos
13    [mq]: first.patch
14  
15  diff -r 90039acadb36 -r 131b8ed49ec4 file1
16  --- a/file1 Tue Feb 10 18:23:27 2009 +0000
17  +++ b/file1 Tue Feb 10 18:23:27 2009 +0000
18  @@ -1,1 +1,2 @@
19   line 1
20  +line 2
21  

Figura 12.6: Refrescar un parche

Puede ejecutar la orden hg qrefresh” tan seguido como quiera, y es una buena forma de “colocar marcas” a su trabajo. Refresque su parche en momentos oportunos; intente un experimento; si el experimento no funciona, Use hg revert” sobre sus modificaciones para volver al refresco anterior.


1  $ echo 'line 3' >> file1
2  $ hg status
3  M file1
4  $ hg qrefresh
5  $ hg tip --style=compact --patch
6  1[qtip,first.patch,tip,qbase]   4fef714d728c   2009-02-10 18:23 +0000   bos
7    [mq]: first.patch
8  
9  diff -r 90039acadb36 -r 4fef714d728c file1
10  --- a/file1 Tue Feb 10 18:23:27 2009 +0000
11  +++ b/file1 Tue Feb 10 18:23:27 2009 +0000
12  @@ -1,1 +1,3 @@
13   line 1
14  +line 2
15  +line 3
16  

Figura 12.7: Refrescar un parche muchas veces para acumular cambios

12.5.3. Aplicar un parche tras otro y dar seguimiento

Cuando haya terminado de trabajar en un parche, o necesite trabajar en otro, puede usar la orden hg qnew” para crear un nuevo parche. Mercurial aplicará este parche sobre su parche anterior. Para un ejemplo, ver la figura 12.8. Note que el parche contiene los cambios en nuestro parche anterior como parte de su contexto (lo verá más claramente en la salida de hg annotate”).


1  $ hg qnew second.patch
2  $ hg log --style=compact --limit=2
3  2[qtip,second.patch,tip]   17da5d88f25b   2009-02-10 18:23 +0000   bos
4    [mq]: second.patch
5  
6  1[first.patch,qbase]   4fef714d728c   2009-02-10 18:23 +0000   bos
7    [mq]: first.patch
8  
9  $ echo 'line 4' >> file1
10  $ hg qrefresh
11  $ hg tip --style=compact --patch
12  2[qtip,second.patch,tip]   7cf293b98474   2009-02-10 18:23 +0000   bos
13    [mq]: second.patch
14  
15  diff -r 4fef714d728c -r 7cf293b98474 file1
16  --- a/file1 Tue Feb 10 18:23:27 2009 +0000
17  +++ b/file1 Tue Feb 10 18:23:28 2009 +0000
18  @@ -1,3 +1,4 @@
19   line 1
20   line 2
21   line 3
22  +line 4
23  
24  $ hg annotate file1
25  0: line 1
26  1: line 2
27  1: line 3
28  2: line 4

Figura 12.8: Aplicar un parche después del primero

Hasta ahora, con excepción de hg qnew” y hg qrefresh”, hemos sido cuidadosos para aplicar únicamente órdenes usuaales de Mercurial. De todas maneras, MQ ofrece muchos comandos que son más sencillos de usar cuando esté pensando acerca de parches, como se puede ver en la figura 12.9:


1  $ hg qseries
2  first.patch
3  second.patch
4  $ hg qapplied
5  first.patch
6  second.patch

Figura 12.9: Entender la pila de parches con hg qseries” y hg qapplied

12.5.4. Manipular la pila de parches

La discusión previa indicó que debe haber una diferencia entre los parches “conocidos” y “aplicados”, y efectivamente la hay. MQ puede manejar un parche sin que este haya sido aplicado al repositorio.

Un parche aplicado tiene su correspondiente conjunto de cambios en el repositorio, y los efectos del parche y el conjunto de cambios son visibles en el directorio de trabajo. Puede deshacer la aplicación de un parche con la orden hg qpop”. MQ sabe acerca de, o maneja un parche sustraído, pero el parche ya no tendrá un conjunto de cambios correspondientes en el repositorio, y el directorio de trabajo no contendrá los cambios hechos por el parche. La figura 12.10 ilustra la diferencia entre parches aplicados y seguidos.


PIC

Figura 12.10: Parches aplicados y no aplicados en la pila de parches de MQ

Puede reaplicar un parche no aplicado o sustraído con la orden hg qpush”. Esto crea un nuevo conjunto de cambios correspondiente al parche, y los cambios del parche estarán presentes de nuevo en el directorio de trabajo. Vea ejemplos de hg qpop” y hg qpush” en acción en la figura 12.11. Vea que hemos sustraído uno o dos parches, la salida dehg qseries” continúa igual, mientras que hg qapplied” ha cambiado.


1  $ hg qapplied
2  first.patch
3  second.patch
4  $ hg qpop
5  Now at: first.patch
6  $ hg qseries
7  first.patch
8  second.patch
9  $ hg qapplied
10  first.patch
11  $ cat file1
12  line 1
13  line 2
14  line 3

Figura 12.11: Modificar la pila de parches aplicados

12.5.5. Introducir y sustraer muchos parches

Mientras que hg qpush” y hg qpop” operan sobre un único parche cada vez, puede introducir y sustraer varios parches de una vez. La opción -a de hg qpush” introduce todos los cambios que no hayan sido aplicados, mientras que la opción -a de hg qpop” sustrae todos los cambios aplicados. (Vea la sección 12.7 más adelante en la cual se explican otras formas de de introducir y sustraer varios cambios.)


1  $ hg qpush -a
2  applying second.patch
3  Now at: second.patch
4  $ cat file1
5  line 1
6  line 2
7  line 3
8  line 4

Figura 12.12: Pushing all unapplied patches

12.5.6. Medidas de seguridad y cómo saltarlas

Muchas órdenes MQ revisan el directorio de trabajo antes de hacer cualquier cosa, y fallan si encuentran alguna modificación. Lo hacen para garantizar que usted no pierda cambio alguno de los que haya hecho, pero que no hayan sido incorporados en algún parche. La figura 12.13 ilusta esto; la orden hg qnew” no creará un nuevo parche si hay cambios notorios, causados en este caso por aplicado la orden hg add” a file3.


1  $ echo 'file 3, line 1' >> file3
2  $ hg qnew add-file3.patch
3  $ hg qnew -f add-file3.patch
4  abort: patch "add-file3.patch" already exists

Figura 12.13: Crear un parche a la fuerza

Las órdenes que revisan el directorio actual cuentan con una opción “Se lo que estoy haciendo”, que siempre está nombrada como -f. El significado exacto de -f depende de la orden. Por ejemplo, hg qnew -f” incorporarán cualquier cambio notorio en el nuevo parche que crea pero hg qpop -f” revertirá las modificaciones a cualquier fichero que haya sido afectado por el parche que está siendo sustraído. ¡Asegúrese de leer la documentación de la opción -f de cada comando antes de usarla!

12.5.7. Trabajar con varios parches a la vez

La orden hg qrefresh” siempre refresca el último parche aplicado. Esto significa que usted puede suspender su trabajo en un parche (refrescándolo), sustraerlo o introducirlo para lograr que otro parche esté de último y trabajar en ese parche por un rato.

A continuación un ejemplo que ilustra cómo puede usar esta habilidad. Digamos que está desarrollando una nueva característica en dos parches. El primero es un cambio en la parte fundamental de su programa, y el segundo–sobre el primero—cambia la interfaz de usuario para usar el código que ha añadido a la parte fundamental. Si ve que hay un fallo en la parte fundamental mientras está trabajando en el parche de UI5, es fácil arreglar la parte fundamental. Simplemente use hg qrefresh” sobre el parche de la UI para guardar los cambios de su trabajo en progreso, y use hg qpop” para sacar sustraer el parche de la parte fundamental. Arregla el fallo sobre la parte fundamental, aplique hg qrefresh” sobre el parche fundamental, y aplique hg qpush” sobre el parche de UI para continuar donde había quedado.

12.6. Más acerca de parches

MQ usa la orden GNU patch para aplicar los parches, por lo tanto es útil conocer ciertos detalles de cómo trabaja patch, y también acerca de los parches.

12.6.1. La cantidad de franjas

Si ve el encabezado de un parche, notará que la ruta al fichero tiene un componente adicional al principio, que no está presente en la ruta. Esta es una traza de cómo generaba anteriormente los parches la gente (algunos aún lo hacen, pero es raro con las herramientas de control de revisiones del actuales).

Alicia desempaquetaría un comprimido, editaría sus ficheros, y querría crear un parche. Por lo tanto ella renombraría su directorio de trabajo, desempacaría el comprimido de nuevo (para lo cual necesitó el renombramiento), y usaría las opciones -r y -N de diff para generar recursivamente un parche entre el directorio original y el modificado. El resultado sería que el nombre del directorio original estaría al principio de toda ruta en cada encabezado de fichero, y el nombre del directorio modificado estaría al frente de la porción derecha de la ruta del fichero.

Como alguien que reciba un parche de Alicia en la red podría obtener dos directorios, uno original y el otro modificado con exactamente los mismos nombres, la orden patch tiene la opción -p que indica la cantidad de componentes de la ruta a eliminar cuando se vaya a aplicar el parche. Este número se llama la cantidad de eliminaciones.

La opción con “-p1” significa “elimine uno”. Si patch ve un nombre de fichero foo/bar/baz en el encabezado del fichero, eliminará foo y tratará de parchar un fichero llamado bar/baz. (Hablando estrictamente, la cantidad de eliminaciones se refiere a la cantidad de separadores de ruta (y los componentes que vayan con ellos) a eliminar. Si el contador es uno volverá foo/bar en bar, pero /foo/bar (note la barra extra) en foo/bar.)

La cantidad a eliminar“estándar” para parches es uno; casi todos los parches contienen un componente inicial de la ruta que necesita ser eliminado. La orden hg diff” de Mercurial genera nombres de ruta de esta forma, y la orden hg import” y MQ esperan parches que tengan a uno como cuenta de eliminaciones.

Si recibe un parche de alguien de quien desea adicionar adicionar a su cola de parches, y el parche necesita una cuenta de eliminación que no sea uno, no podrá aplicar hg qimport” en primera medida, porque hg qimport” no tiene todavía una opción -p option (ver http://www.selenic.com/mercurial/bts/issue311Fallo de Mercurial No. 311). Lo mejor que puede hacer es aplicar hg qnew” por su cuenta, y después usar patch -pN” para aplicar tal parche, seguido de hg addremove” para tener en cuenta cualquier fichero adicionado o eliminado por el parche, seguido de hg qrefresh”. Esta complejidad puede ser innecesaria; consulte http://www.selenic.com/mercurial/bts/issue311Fallo de Mercurial No. 311 para más información.

12.6.2. Estrategias para aplicar parches

Cuando patch aplica un trozo, intenta varias estrategias sucesivas que decrecen en precisión para intentar aplicarlo. Esta técnica de pruebas y error aveces permite que un parche que fue generado contra una versión anterior de un fichero, sea aplicada sobre una versión más nueva del mismo.

Primero patch intenta una correspondencia perfecta donde los números de línea, el contexto y el texto a modificar deben coincidir perfectamente. Si no lo logra, intenta encontrar una correspondencia exacta del contexto, sin tener en cuenta el número de línea. Si es exitoso, imprime una línea indicando que el trozo fue aplicado, pero a un corrimiento del número de línea original.

Si falla la correspondencia por contexto, patch elimina la primera y la última línea del contexto, e intenta una correspondencia reducida del contexto. Si el trozo con contexto reducido es exitoso, imprime un mensaje indicando que aplicó el trozo con un factor difuso (el número después del factor difuso indica cuántas líneas de contexto patch tuvo que eliminar antes de aplicar el parche).

Cuando ninguna de estas técnicas funciona, patch imprime un mensaje indicando que el trozo en cuestión se desechó. Almacena los trozos desechados (también llamados “descartados”) en un fichero con el mismo nombre, y la extensión .rej añadida. También almacena una copia igual al fichero original con la extensión .orig; la copia del fichero sin extensión contendrá cualquier cambio hecho por los trozos que sí se aplicaron sin problema. Si usted tiene un parche que modifica foo con seis trozos, y uno de ellos falla al aplicarse, tendrá : un fichero original foo.orig, un fichero foo.rej que contiene el trozo, y foo, que contiene los cambios que se aplicaron por los cinco trozos exitosos.

12.6.3. Algunos detalles de la representación de parches

Hay ciertas cosas útiles por saber acerca de cómo trabaja patch con los ficheros:

12.6.4. Cuidado con los difusos

Cuando aplique un trozo con un corrimiento, o con un factor difuso, aveces será taotalmente exitoso, tales técnicas inexactas dejan claramente la posibilidad de corromper el fichero parchado. Los casos más típicos involucran aplicar un parche dos veces o en un sitio incorrecto del fichero. Si patch o hg qpush” llegan a mencionar un corrimiento o un factor difuso, debería asegurarse que los ficheros modificados estén correctos después del suceso.

Casi siempre es buena idea refrescar un parche que fue aplicado con un corrimiento o un factor difuso; refrescar el parche genera nueva información de contexto que permitirá aplicarlo limpiamente. Digo “casi siempre,” no “siempre”, puesto que en ciertas ocasiones refrescar un parche lo hará fallar frente a una revisión diferente del fichero. En algunos casos, como por ejemplo, cuando usted está manteniendo un parche que debe estar encima de múltiples revisiones de un árbol de fuentes, es aceptable tener un parche aplicado algo difuso, siempre que haya verificado los resultados del proceso de parchar.

12.6.5. Manejo de descartes

Si hg qpush” falla al aplicar un parche, mostrará un texto de error y saldrá. Si ha dejado ficheros .rej, es mejor arreglar los trozos descartados antes de introducir parches adicionales o hacer cualquier otra cosa.

Si su parche solía aplicarse limpiamente, y ya no lo hace porque ha cambiado código subyacente en el cual se basa su parche, las Colas de Mercurial pueden ayudar; consulte la sección 12.8.

Desafortunadamente, no hay grandes técnicas para tratar los trozos descartados. Casi siempre deberá consultar el fichero .rej y editar el fichero objetivo, aplicando los trozos descartados a mano.

Si es aventurero, Neil Brown, un hacker del núcleo Linux, escribió una herramienta llamada wiggle [Bro], que es más vigorosa que patch en su intento de hacer que se aplique un parche.

Otro hacker del nucleo Linux, Chris Mason (el autor de las Colas de Mercurial), escribió una herramienta similar llamada mpatch [Mas], que sigue una aproximación sencilla para automatizar la aplicación de trozos descartados por patch. La orden mpatch puede ayudar con cuatro razones comunes por las cuales un parche ha sido descartado:

Si usted usa wiggle o mpatch, debería ser doblemente cuidadoso al revisar sus resultados cuando haya terminado. De hecho, mpatch refuerza este método de revisar por partida doble su salida, dejándolo a usted en un programa de fusión cuando la herramienta haya terminado su trabajo, de tal forma que usted pueda verificar lo que ha hecho y pueda terminar de aplicar cualquier fusión faltante.

12.7. maximizar el rendimiento de MQ

MQ es muy eficiente al tratar con una gran cantidad de parches. Corrí unos experimentos de desempeño a mediados del 2006 para una charla que dí en la conferencia EuroPython 2006 [O’S06]. Empleé la serie de parches para el núcleo Linux 2.6.17-mm1, que contaba con 1.738 parches. Los apliqué sobre un repositorio del núcleo de Linux con todas las 27.472 revisiones entre 2.6.12-rc2 y 2.6.17.

En mi portátil antiguo y lento, logré aplicar hg qpush -a” a los 1.738 parches en 3.5 minutos, y hg qpop -a” en 30 segundos. (En un portátil más nuevo, el tiempo para introducir todos los parches, se logró en menos de dos minutos.) Apliqué hg qrefresh” sobre uno de los parches más grandes (que hizo 22.779 líneas de cambios en 287 ficheros) en 6,6 segundos.

Claramente, MQ funciona adecuadamente en árboles grandes, y además hay unos trucos que pueden emplearse para obtener el máximo desempeño.

En primer lugar, trate de hacer “en lote” las operaciones. Cada vez que ejecute hg qpush” o hg qpop”, tales órdenes revisan el directorio de trabajo para asegurarse de que usted no ha hecho cambios y ha olvidado ejecutar hg qrefresh”. En un árbol pequeño, el tiempo de esta revisión puede ser mínimo, Pero en un árbol mediano (con decenas de miles de ficheros), puede tomar un segundo o más.

Las órdenes hg qpush” y hg qpop” le permiten introducir o sustraer varios parches en una operación. Puede identificar el “parche destino” que desee. Cuando aplique hg qpush” con un destino, introducirá tantos parches como sea necesario hasta que el especificado esté en el tope de la pila. Cuando emplee hg qpop” con un destino, MQ sustraerá parches hasta que el parche destino esté en el tope.

Puede identificar un parche destino con el nombre del parche o con el número. Si se refiere al número, los parches se contarán desde cero; esto significa que el primer parche es cero, el segundo es uno y así sucesivamente.

12.8. Actualiar los parches cuando el código cambia

Es común contar con una pila de parches sobre un repositorio que usted no modifica directamente. Si está trabajando en cambios de código de otros, o en una característica que tarda bastante en desarrollarse comparada con la tasa de cambio del código sobre la cual se está trabajando, necesitará sincronizarse con el código, y ajustar cualquier trozo en sus parches que ya no estén al día. A esto se le llama hacer rebase a su serie de parches.

La vía más sencilla de hacerlo es con hg qpop -a” sobre sus parches, después hacer hg pull” de los cambios en el repositorio, y finalmente hacer hg qpush -a” con sus parches de nuevo. MQ dejará de de introducir parches siempre que llegue a un parche que no se pueda aplicar debido a un conflicto, permitiéndole a usted arreglarlo, aplicar hg qrefresh” al parche afectado y continuar introduciendo hasta que haya arreglado la pila completa.

Esta aproximación es sencilla y funciona bien si no espera cambios en el código original que afecte en gran medida los parches que usted esté aplicando. Si su pila de parches toca código que es modificado frecuentemente o de forma invasiva sobre el código subyacente, arreglar trozos manualmente se vuelve desgastante.

Es posible automatizar de forma parcial el proceso de rebase. Si sus parches se aplican limpiamente sobre algunas revisiones del repositorio subyacente, MQ puede usar esta información para ayudarle a a resolver conflictos entre sus parches y una revisión distinta.

El proceso resulta un poco complejo:

  1. Para comenzar, haga hg qpush -a” sobre todos los parches que usted sepa se aplican limpiamente.
  2. Guarde una copia de seguridad de su directorio de parches con hg qsave -e -c”. Esto imprime el nombre del directorio en el cual se han guardado los parches. Guardará los parches en un directorio llamado .hg/patches.N, donde N es un entero pequeño. También consigna un “conjunto de cambios de seguridad” sobre sus parches aplicados; esto es para mantener el histórico, y guarda los estados de los ficheros series y status.
  3. Use hg pull” para traer los nuevos cambios en el repositorio subyacente. (No ejecute hg pull -u”; vea más adelante por qué.)
  4. Actualice a la nueva revisión punta con hg update -C” para sobreescribir los parches que haya introducido.
  5. Fusione todos los parches con hg qpush -m -a”. La opción -m de hg qpush” le indica a MQ que haga una fusión que involucra tres fuentes si el parche falla al aplicarse.

Durante el hg qpush -m”, cada parche en el fichero series se aplica normalmente. Si un parche se aplica difusamente o se niea a aplicarse, MQ consulta la cola que usted guardó con hg qsave”, y aplica una fusión de tres con el correspondiente conjunto de cambios. Esta fusión usa la maquinaria de Mercurial, por lo tanto puede mostrar una herramienta de fusión GUI para ayudarle a resolver los problemas.

Cuando termine de resolver los efectos de un parche, MQ refrescará su parche basado en el resultado de la fusión.

Al final de este proceso, su repositorio tendrá una cabeza extra de la antigua cola de parches, y una copia de la cola de parches anterio estará en .hg/patches.N. Puede eliminar la cabeza extra con hg qpop -a -n patches.N” o hg strip”. Puede eliminar .hg/patches.N una vez que esté seguro de que no lo necesita más como copia de seguridad.

12.9. Identificar parches

Las órdenes de MQ le permiten trabajar refiriéndose al nombre del parche o al número. Es obvio hacerlo por el nombre; por ejemplo se pasa el nombre foo.patch a hg qpush”, que introducirá los parches hasta que foo.patch se aplique.

Para hacerlo más corto, puede referirse a un parche con un nombre y un corrimiento de número; por ejemplo, foo.patch-2 significa “dos parches antes de foo.patch”, mientras que bar.patch+4 significa “cuatro parches después de bar.patch”.

Referirse a un parche por su índice no es muy diferente. El primer parche que se imprime en la salida de hg qseries” es el parche cero (si, es el primero en los sistemas que comienzan su conteo en cero); el segundo parche es uno y así sucesivamente.

MQ facilita el trabajo cuando está usando órdenes normales de Mercurial. Cada comando que acepte Identificadores de conjuntos de cambios también aceptará el nombre de un parche aplicado. MQ aumenta las etiquetas normalmente en el repositorio con un distintivo para cada parche aplicado. Adicionalmente, las etiquetas especiales qbase y qtip identifican los parches “primero” y último, respectivamente.

Junto con las capacidades de Mercurial para etiquetar, estas adiciones hacen que trabajar con parches sea muy sencillo.

Dado que MQ nombra los parches disponibles al resto de Mercurial con su maquinaria de etiquetas interna, usted no necesita teclear el nombre completo de un parche cuando desea identificarlo por su nombre.


1  $ hg qapplied
2  first.patch
3  second.patch
4  $ hg log -r qbase:qtip
5  changeset:   1:b3508ccf62ad
6  tag:         first.patch
7  tag:         qbase
8  user:        Bryan O'Sullivan <bos@serpentine.com>
9  date:        Tue Feb 10 18:23:26 2009 +0000
10  summary:     [mq]: first.patch
11  
12  changeset:   2:c836697fbf55
13  tag:         qtip
14  tag:         second.patch
15  tag:         tip
16  user:        Bryan O'Sullivan <bos@serpentine.com>
17  date:        Tue Feb 10 18:23:26 2009 +0000
18  summary:     [mq]: second.patch
19  
20  $ hg export second.patch
21  # HG changeset patch
22  # User Bryan O'Sullivan <bos@serpentine.com>
23  # Date 1234290206 0
24  # Node ID c836697fbf55654f972d867359527813dbbc5d44
25  # Parent  b3508ccf62adb8916c6a7b64105642fa516f5a90
26  [mq]: second.patch
27  
28  diff -r b3508ccf62ad -r c836697fbf55 other.c
29  --- /dev/null Thu Jan 01 00:00:00 1970 +0000
30  +++ b/other.c Tue Feb 10 18:23:26 2009 +0000
31  @@ -0,0 +1,1 @@
32  +double u;

Figura 12.14: Uso de las características de etiquetamiento al trabajar con MQ

Otra consecuencia deseable al representar los nombres de parches como etiquetas es que cuando ejecute la orden hg log”, desplegará el nombre del parche como una etiqueta, usualmente con la salida normal. Esto facilita distinguir visualmente los parches aplicados de las revisiones “normales”. La figura 12.14 muestra algunos comandos usuales de Mercurial al trabajar con parches.

12.10. Otra información útil

Hay una cantidad de aspectos que hacen que el uso de MQ no representen secciones en sí mismas, pero de los cuales es bueno estar enterado. Los presentamos en aquí:

12.11. Administrar parches en un repositorio

Dado que el directorio .hg/patches de MQ reside fuera del repositorio de trabajo de Mercurial, el repositorio “subyacente” de Mercurial no sabe nada acerca de la administración o presencia de parches.

Esto presenta la interesante posibilidad de administrar los contenidos del directorio de parches como un repositorio de Mercurial por su cuenta. Puede ser una forma útil de trabajar. Por ejemplo, puede trabajar en un parche por un rato, hacerle hg qrefresh” y después hacer hg commit” al estado actual del parche. Esto le permite “devolverse” a esa versión del parche posteriormente.

Puede también compartir diferentes versiones de la misma pila de parches entre varios repositorios subyacentes. Uso esto cuando estoy desarrollando una característica del núcleo Linux. Tengo una copia original de las fuentes del núcleo para varias arquitecturas, y cloné un rpositorio en cada una que contiene los parches en los cuales estoy trabajando. Cuando quiero probar un cambio en una arquitectura diferente, introduzco mis parches actuales al repositorio de parches asociado con el árbol del kernel, sustraigo e introduzco todos mis parches, armo y pruebo el núcleo.

Llevar los parches en un repositorio permite que varios desarrolladores puedan trabajar en la misma serie de parches sin sobreponerse, todo sobre la fuente base subyacente que pueden o no controlar.

12.11.1. Soporte de MQ para repositorios de parches

MQ le ayuda a trabajar con el directorio .hg/patches como un repositorio; cuando usted prepara un repositorio para trabajar con parches usando hg qinit”, puede pasarle la opción -c para que se cree el directorio .hg/patches como un repositorio de Mercurial.

Nota: Si olvida usar la opción -c option, puede ir al directorio .hg/patches en cualquier momento y ejecutar hg init”. No olvide añadir una entrada en el fichero status del fichero .hgignore, a pesar de que (hg qinit -c” hace estodo de forma automática para usted); usted seguro no quiere administrar el fichero status.

MQ nota convenientemente que el directorio .hg/patches es un repositorio, hará hg add” automáticamente a cada parche que usted cree e importe.

MQ provee una orden corta, hg qcommit”, que ejecuta hg commit” en el directorio .hg/patches. Lo que ahorra tecleo recurrente.

Finalmente, para administrar convenientemente el directorio de parches, puede definir el alias mq en sistemas Unix. Por ejemplo, en sistemas Linux con el intérprete bash, puede incluir el siguiente recorte de código6 en su fichero ĩ/.bashrc.

1  alias mq=‘hg -R $(hg root)/.hg/patches'

Puede aplicar las órdenes de la forma mq pull” al repositorio principal.

12.11.2. Detalles a tener en cuenta

El soporte de MQ para trabajar con un repositorio de parches es limitado en ciertos aspectos:

MQ no puede detectar automáticamente los cambios que haga al directorio de parches. Si aplica hg pull”, edita manualmente, o hace hg update” a los parches o el fichero series, tendrá que aplicar hg qpop -a” y después hg qpush -a” en el repositorio subyacente para que los cambios se reflejen allí. Si olvida hacerlo, puede confundir a MQ en cuanto a qué parches se han aplicado.

12.12. Otras herramientas para trabajar con parches

Cuando haya trabajado por cierto tiempo con parches, deseará herramientas que le ayuden a entender y manipular los parches con los que esté tratando.

La orden diffstat [Dic] genera un histograma de modificaciones hechas a cada fichero en un parche. Provee una interesante forma de “dar un vistazo” al parche—qué ficheros afecta, y cuántos cambios introduce a cada fichero y en total. (Me ha parecido interesante usar la opción -p de diffstat, puesto que de otra forma intentará hacer cosas inteligentes con prefijos de ficheros que terminan confundiéndome.)


1  $ diffstat -p1 remove-redundant-null-checks.patch
2   drivers/char/agp/sgi-agp.c        |    5 ++---
3   drivers/char/hvcs.c               |   11 +++++------
4   drivers/message/fusion/mptfc.c    |    6 ++----
5   drivers/message/fusion/mptsas.c   |    3 +--
6   drivers/net/fs_enet/fs_enet-mii.c |    3 +--
7   drivers/net/wireless/ipw2200.c    |   22 ++++++----------------
8   drivers/scsi/libata-scsi.c        |    4 +---
9   drivers/video/au1100fb.c          |    3 +--
10   8 files changed, 19 insertions(+), 38 deletions(-)
11  $ filterdiff -i '⋆/video/⋆' remove-redundant-null-checks.patch
12  --- a/drivers/video/au1100fb.c~remove-redundant-null-checks-before-free-in-drivers
13  +++ a/drivers/video/au1100fb.c
14  @@ -743,8 +743,7 @@ void __exit au1100fb_cleanup(void)
15   {
16    driver_unregister(&au1100fb_driver);
17  
18  - if (drv_info.opt_mode)
19  - kfree(drv_info.opt_mode);
20  + kfree(drv_info.opt_mode);
21   }
22  
23   module_init(au1100fb_init);

Figura 12.15: Las órdenes diffstat, filterdiff, y lsdiff

El paquete patchutils [Wau] es invaluable. Provee un conjunto de pequeñas utilidades que siguen la “filosofía Unix”; cada una hace una cosa muy bien hecha a un parche. La orden patchutils que más uso es filterdiff, que extrae subconjuntos de un fichero de parche. Por ejemplo, dado un parche que modifica centenas de ficheros en docenas de directorios, una única invocación de filterdiff puede generear un parche más pequeño que solamente toca aquellos ficheros con un patrón. Puede ver otro ejemplo en la sección 13.9.2.

12.13. Buenas prácticas de trabajo con parches

En caso de que esté trabajando en una serie de parches para enviar a un proyecto de software libre o de fuentes abiertas, o en una serie que desea tratar como un conjunto de cambios regular, cuando haya terminado, puede usar técnicas sencillas para mantener su trabajo bien organizado.

De nombres descriptivos a sus parches. Un buen nombre para un parche podría ser rework-device-alloc.patch, porque da de forma inmediata una pista del propósito del parche. Los nombres largos no deben ser un problema; no los estará tecleando repetidamente, pero estará ejecutando regularmente órdenes como hg qapplied” y hg qtop”. Los nombres adecuados son especialmente importantes cuando tiene bastantes parches con los cuales trabajar, o si está trabajando en diferentes tareas y sus parches toman solamente una porción de su atención.

Tenga en cuenta en qué parche está trabajando. Use la orden hg qtop” para dar un vistazo al texto de sus parches frecuentemente—por ejemplo, use hg tip -p”)—para asegurarse en dónde está ubicado. En distintas oportunidades me sucedió que apliqué hg qrefresh” a un parche distinto al que deseaba hacerlo, y usualmente es complejo migrar los cambios al parche correcto después de haberlo hecho mal.

Por este motivo, vale la pena invertir ese poco tiempo para aprender cómo usar otras herramientas que describí en la sección 12.12, particularmente diffstat y filterdiff. La primera le dará una idea de qué cambios está haciendo su parche, mientras que la segunda permite seleccionar trozos de un parche para colocarlos en otro.

12.14. Recetas de MQ

12.14.1. Administrar parches “triviales”

Puesto que colocar ficheros en un repositorio de Mercurial es tan sencillo, tiene bastante sentido administrar parches de esta forma incluso si desea hacer algunos cambios al paquete de ficheros que descargó.

Para comenzar a descargar y desempaqueter un paquete de ficheros, y volverlo en un repositorio de Mercurial:

1  $ download netplug-1.2.5.tar.bz2
2  $ tar jxf netplug-1.2.5.tar.bz2
3  $ cd netplug-1.2.5
4  $ hg init
5  $ hg commit -q --addremove --message netplug-1.2.5
6  $ cd ..
7  $ hg clone netplug-1.2.5 netplug
8  updating working directory
9  18 files updated, 0 files merged, 0 files removed, 0 files unresolved

Continue creando una pila de parches y haga sus cambios.

1  $ cd netplug
2  $ hg qinit
3  $ hg qnew -m 'fix build problem with gcc 4' build-fix.patch
4  $ perl -pi -e 's/int addr_len/socklen_t addr_len/' netlink.c
5  $ hg qrefresh
6  $ hg tip -p
7  changeset:   1:4adf6cb9cc16
8  tag:         qtip
9  tag:         build-fix.patch
10  tag:         tip
11  tag:         qbase
12  user:        Bryan O'Sullivan <bos@serpentine.com>
13  date:        Tue Feb 10 18:23:26 2009 +0000
14  summary:     fix build problem with gcc 4
15  
16  diff -r ca53495653b5 -r 4adf6cb9cc16 netlink.c
17  --- a/netlink.c Tue Feb 10 18:23:26 2009 +0000
18  +++ b/netlink.c Tue Feb 10 18:23:26 2009 +0000
19  @@ -275,7 +275,7 @@
20           exit(1);
21       }
22  
23  -    int addr_len = sizeof(addr);
24  +    socklen_t addr_len = sizeof(addr);
25  
26       if (getsockname(fd, (struct sockaddr ⋆) &addr, &addr_len) == -1) {
27           do_log(LOG_ERR, "Could not get socket details: %m");
28  

Digamos que pasan unas semanas o meses, y el autor del paquete libera una nueva versión. Primero se traen sus cambios al repositorio.

1  $ hg qpop -a
2  Patch queue now empty
3  $ cd ..
4  $ download netplug-1.2.8.tar.bz2
5  $ hg clone netplug-1.2.5 netplug-1.2.8
6  updating working directory
7  18 files updated, 0 files merged, 0 files removed, 0 files unresolved
8  $ cd netplug-1.2.8
9  $ hg locate -0 | xargs -0 rm
10  $ cd ..
11  $ tar jxf netplug-1.2.8.tar.bz2
12  $ cd netplug-1.2.8
13  $ hg commit --addremove --message netplug-1.2.8

La porción que comienza con hg locate” mostrada más arriba, borra todos los ficheros en el directorio de trabajo, así que la opción --addremove de hg commit” puede indicar qué ficheros se han eliminado en la nueva versión del árbol de fuentes.

Finalmente, puede aplicar sus parches encima del nuevo árbol de fuentes

1  $ cd ../netplug
2  $ hg pull ../netplug-1.2.8
3  pulling from ../netplug-1.2.8
4  searching for changes
5  adding changesets
6  adding manifests
7  adding file changes
8  added 1 changesets with 12 changes to 12 files
9  (run 'hg update' to get a working copy)
10  $ hg qpush -a
11  (working directory not at tip)
12  applying build-fix.patch
13  Now at: build-fix.patch

12.14.2. Combinar parches completos

MQ provee la orden hg qfold” que le permite combinar parches enteros. Se “integran”7 los parches que usted nombre, en el orden que especifique, en el último parche aplicado, y concatena sus descripciones al final de su descripción. Deberá sustraer los cambios para poder integrarlos.

El orden en el que integre los parches importa. Si el parche últimamente aplicado es foo, e integra hg qfoldbar y quux en él, terminará con un parche que tiene el mismo efecto que si hubiera aplicado primero foo, y después bar, seguido de quux.

12.14.3. Fusionar una porción de un parche dentro de otro

Fusionar partes de un parche dentro de otro es más complejo que combinar completamente dos parches.

Si desea mover cambios de ficheros completos, puede usar las opciones filterdiff’s -i y -x para elegir las modificaciones remover de un parche, concatenar su salida al final del parche donde desea fusionarlo. Usualmente no necesitará modificar el parche del cuál ha fusionado los cambios. En cambio, MQ reportará que hay unos trozos que se han desechado cuando usted aplique hg qpush” (de los trozos que haya movido al otro parche), y puede sencillamente aplicar hg qrefresh” para eliminar los trozos replicados.

Si tiene un parche que tiene varios trozos que modifican un fichero, y desea mover solamente unos de ellos, el trabajo es un poco más enredado, pero puede automatizarlo parcialmente. Use lsdiff -nvv” para imprimir algunos metadatos del parche.

1  $ lsdiff -nvv remove-redundant-null-checks.patch
2  22 File #1   a/drivers/char/agp/sgi-agp.c
3   24 Hunk #1 static int __devinit agp_sgi_init(void)
4  37 File #2   a/drivers/char/hvcs.c
5   39 Hunk #1 static struct tty_operations hvcs_ops =
6   53 Hunk #2 static int hvcs_alloc_index_list(int n)
7  69 File #3   a/drivers/message/fusion/mptfc.c
8   71 Hunk #1 mptfc_GetFcDevPage0(MPT_ADAPTER ⋆ioc, in
9  85 File #4   a/drivers/message/fusion/mptsas.c
10   87 Hunk #1 mptsas_probe_hba_phys(MPT_ADAPTER ⋆ioc)
11  98 File #5   a/drivers/net/fs_enet/fs_enet-mii.c
12   100 Hunk #1 static struct fs_enet_mii_bus ⋆create_bu
13  111 File #6   a/drivers/net/wireless/ipw2200.c
14   113 Hunk #1 static struct ipw_fw_error ⋆ipw_alloc_er
15   126 Hunk #2 static ssize_t clear_error(struct device
16   140 Hunk #3 static void ipw_irq_tasklet(struct ipw_p
17   150 Hunk #4 static void ipw_pci_remove(struct pci_de
18  164 File #7   a/drivers/scsi/libata-scsi.c
19   166 Hunk #1 int ata_cmd_ioctl(struct scsi_device ⋆sc
20  178 File #8   a/drivers/video/au1100fb.c
21   180 Hunk #1 void __exit au1100fb_cleanup(void)

Esta orden imprime tres clases diferentes de números:

Tendrá que hacer una inspección visual, y leer el parche para identificar los números de fichero y trozo que desea, pero puede pasar posteriormente a las opciones --files y --hunks de filterdiff, para seleccionar exactamente el fichero y el trozo que desea extraer.

Cuando tenga el trozo, puede concatenarlo al final de su parche objetivo y continuar como en la sección 12.14.2.

12.15. Diferencias entre quilt y MQ

Si le es familiar quilt, MQ provee un conjunto similar de órdenes. Hay algunas diferencias en cómo funcionan.

Debe haber notado que la mayoría de comandos de quilt tienen su contraparte en MQ, que simplemente comienzan con “q”. Las excepciones son las órdenes add y remove de quilt, que realmente son las órdenes hg add” y hg remove” de Mercurial. No hay un equivalente en MQ para la orden edit de quilt.

Capítulo 13
Usos avanzados de las Colas de Mercurial

Auunque es fácil aprender los usos más directos de las Colas de Mercurial, tener algo de disciplina junto con algunas de las capacidadees menos usadas de MQ hace posible trabajar en entornos de desarrollo complejos.

En este capítulo, usaré como ejemplo una técnica que he usado para administrar el desarrollo de un controlador de dispositivo Infiniband para el kernel de Linux. El controlador en cuestión es grande (al menos en lo que se refiere a controladores), con 25,000 líneas de código esparcidas en 35 ficheros fuente. Es mantenido por un equipo pequeño de desarrolladores.

Aunque mucho del material en este capítulo es específico de Linux, los mismos principios aplican a cualquier base de código de la que usted no sea el propietario principal, y sobre la que usted necesita hacer un montón de desarrollo.

13.1. El problema de múltiples objetivos

El kernel de Linux cambia con rapidez, y nunca ha sido estable internamente; los desarrolladores hacen cambios drásticos entre versiones frecuentemente. Esto significa que una versión del controlador que funciona bien con una versión particular del kernel ni siquiera compilará correctamente contra, típicamente, cualquier otra versión.

Para mantener un controlador, debemos tener en cuenta una buena cantidad de versiones de Linux en mente.

13.1.1. Aproximaciones tentadoras que no funcionan adecuadamente

Hay dos maneras estándar de mantener una porción de software que debe funcionar en muchos entornos diferentes.

La primera es mantener varias ramas, cada una pensada para un único entorno. El problema de esta aproximación es que usted debe tener una disciplina férrea con el flujo de cambios entre repositorios. Una nueva característica o un arreglo de fallo deben empezar su vida en un repositorio “prístino”, y luego propagarse a cada repositorio de backport. Los cambios para backports están más limitados respecto a las ramas a las que deberían propagarse; un cambio para backport que es aplicado a una rama en la que no corresponde probablemente hará que el controlador no compile.

La segunda es mantener un único árbol de código fuente lleno de declaraciones que activen o desactiven secciones de código dependiendo del entorno objetivo. Ya que estos “ifdefs” no están permitidos en el árbol del kernel de Linux, debe seguirse algún proceso manual o automático para eliminarlos y producir un árbol limpio. Una base de código mantenida de esta manera se convierte rápidamente en un nido de ratas de bloques condicionales que son difíciles de entender y mantener.

Ninguno de estos enfoques es adecuado para situaciones en las que usted no es “dueño” de la copia canónica de un árbol de fuentes. En el caso de un controlador de Linux que es distribuido con el kernel estándar, el árbol de Linux contiene la copia del código que será considerada por el mundo como la canónica. La versión oficial de “mi” controlador puede ser modificada por gente que no conozco, sin que yo siquiera me entere de ello hasta después de que los cambios aparecen en el árbol de Linus.

Estos enfoques tienen la debilidad adicional de dificultar la generación de parches bien formados para enviarlos a la versión oficial.

En principio, las Colas de Mercurial parecen ser un buen candidato para administrar un escenario de desarrollo como el de arriba. Aunque este es de hecho el caso, MQ tiene unas cuantas características adicionales que hacen el trabajo más agradable.

13.2. Aplicar parches condicionalmente mediante guardias

Tal vez la mejor manera de conservar la cordura con tantos entornos objetivo es poder escoger parches específicos para aplicar para cada situación. MQ provee una característica llamada “guardias” (que se origina del comando guards de Quilt) que hace precisamente ésto. Para empezar, creemos un repositorio sencillo para experimentar.

1  $ hg qinit
2  $ hg qnew hello.patch
3  $ echo hello > hello
4  $ hg add hello
5  $ hg qrefresh
6  $ hg qnew goodbye.patch
7  $ echo goodbye > goodbye
8  $ hg add goodbye
9  $ hg qrefresh

Esto nos brinda un pequeño repositorio que contiene dos parches que no tienen ninguna dependencia respecto al otro, porque tocan ficheros diferentes.

La idea detrás de la aplicación condicional es que usted puede “etiquetar” un parche con un guardia, que simplemente es una cadena de texto de su elección, y luego decirle a MQ que seleccione guardias específicos para usar cuando aplique parches. MQ entonces aplicará, u omitirá, un parche vigilado, dependiendo de los guardias que usted haya seleccionado.

Un parche puede tener una cantidad arbitraria de guardias; cada uno es positivo (“aplique el parche si este guardia es seleccionado”) o negativo (“omita este parche si este guardia es seleccionado”). Un parche sin guardias siempre es aplicado.

13.3. Controlar los guardias de un parche

El comando hg qguard” le permite determinar qué guardias deben aplicarse a un parche, o mostrar los guardias que están en efecto. Sin ningún argumento, el comando muestra los guardias del parche actual de la parte más alta de la pila.

1  $ hg qguard
2  goodbye.patch: unguarded

Para poner un guardia positivo en un parche, prefije el nombre del guardia con un “+”.

1  $ hg qguard +foo
2  $ hg qguard
3  goodbye.patch: +foo

Para poner un guardia negativo en un parche, prefije el nombre del guardia con un “-”.

1  $ hg qguard hello.patch -quux
2  $ hg qguard hello.patch
3  hello.patch: -quux
Nota: El comando hg qguardpone los guardias en un parche; no los modifica. Esto significa que si usted ejecuta hg qguard +a +b” sobre un parche, y luego hg qguard +c” en el mismo parche, el único guardia sobre el parche después del comando será +c.

Mercurial almacena los guardias en el fichero series; la forma en que son almacenados es fácil tanto de entender como de editar a mano. (En otras palabras, usted no tiene que usar el comando hg qguard” si no lo desea; está bien simplemente editar el fichero series)

1  $ cat .hg/patches/series
2  hello.patch #-quux
3  goodbye.patch #+foo

13.4. Selecccionar los guardias a usar

El comando hg qselect” determina qué guardias están activos en cualquier momento. El efecto de esto es determinar qué parches aplicará MQ la próxima vez que usted ejecute hg qpush”. No tiene ningún otro efecto; en particular, no hace nada a los parches que ya han sido aplicados.

Sin argumentos, el comando hg qselect” lista los guardias en efecto actualmente, uno por cada línea de salida. Cada argumento es tratado como el nombre de un guardia a aplicar.

1  $ hg qpop -a
2  Patch queue now empty
3  $ hg qselect
4  no active guards
5  $ hg qselect foo
6  number of unguarded, unapplied patches has changed from 1 to 2
7  $ hg qselect
8  foo

Si está interesado, los guardias seleccionados actualmente están almacenados en el fichero guards.

1  $ cat .hg/patches/guards
2  foo

Podemos ver el efecto que tienen los guardias seleccionados cuando ejecutamos hg qpush”.

1  $ hg qpush -a
2  applying hello.patch
3  applying goodbye.patch
4  Now at: goodbye.patch

Un guardia no puede empezar con un caracter “+” o “-”. El nombre del guardia no debe contener espacios en blanco, pero muchos otros caracteres son aceptables. Si usted trata de usar un guardia con un nombre inválido, MQ se quejará:

1  $ hg qselect +foo
2  abort: guard '+foo' starts with invalid character: '+'

Cambiar los guardias seleccionados cambia los parches que son aplicados.

1  $ hg qselect quux
2  number of guarded, applied patches has changed from 0 to 2
3  $ hg qpop -a
4  Patch queue now empty
5  $ hg qpush -a
6  patch series already fully applied

Usted puede ver en el ejemplo de abajo que los guardias negativos tienen precedencia sobre los guardias positivos.

1  $ hg qselect foo bar
2  number of unguarded, unapplied patches has changed from 0 to 2
3  $ hg qpop -a
4  no patches applied
5  $ hg qpush -a
6  applying hello.patch
7  applying goodbye.patch
8  Now at: goodbye.patch

13.5. Reglas de MQ para aplicar parches

Las reglas que MQ usa para decidir si debe aplicar un parche son las siguientes.

13.6. Podar el entorno de trabajo

En el trabajo del controlador de dispositivo que mencioné anteriormente, yo no aplico los parches a un árbol normal del kernel de Linux. En cambio, uso un repositorio que sólo contiene una instantánea de los ficheros fuente y de cabecera que son relevantes para el desarrollo de Infiniband. Este repositorio tiene un 1 % del tamaño del repositorio del kernel, por lo que es más fácil trabajar con él.

Luego escojo una versión “base” sobre la cual son aplicados los parches. Es una instantánea del árbol del kernel de Linux en una revisión de mi elección. Cuando tomo la instantánea, almaceno el ID de conjunto de cambios en el mensaje de consignación. Ya que la instantánea preserva la “forma” y el contenido de las partes relevantes del árbol del kernel, puedo aplicar mis parches sobre mi pequeño repositorio o sobre un árbol normal del kernel.

Normalmente, el árbol base sobre el que se aplican los parches debería ser una instantánea de un árbol de desarrollo muy reciente. Esto facilita mucho el desarrollo de parches que puedan ser enviados al árbol oficial con pocas o ninguna modificación.

13.7. Dividir el fichero series

Yo categorizo los parches en el fichero series en una serie de grupos lógicos. Cada sección de parches similares empieza con un bloque de comentarios que describen el propósito de los parches que le siguen.

La secuencia de grupos de parches que mantengo se muestra a continuación. El orden de los grupos es importante; explicaré porqué luego de que presente los grupos.

Ahora volvemos a las razones para ordenar los grupos de parches en esta manera. Quisiéramos que los parches del fondo de la pila sean tan estables como sea posible, para no tener que revisar parches más arriba debido a cambios de contexto. Poner los parches que nunca cambiarán en el primer lugar del fichero series sirve a este propósito.

También desearíamos que los parches que sabemos que debemos modificar sean aplicados sobre un árbol de fuentes que se parezca al oficial tanto como sea posible. Es por esto que mantenemos los parches aceptados disponibles por una buena cantidad de tiempo.

Los parches “backport” y “no enviar” flotan al final del fichero series. Los parches de backport deben ser aplicados encima de todos los otros parches, y los parches “no enviar” pueden perfectamente quedarse fuera del camino.

13.8. Mantener la serie de parches

En mi trabajo, uso varios guardias para controlar qué parches deben ser aplicados.

La variedad de guardias me brinda una flexibilidad considerable para determinar qué tipo de árbol de fuentes acabaré por obtener. En la mayoría de las situaciones, la selección de guardias apropiados es automatizada durante el proceso de compilación, pero puedo ajustar manualmente los guardias a usar para circunstancias poco comunes.

13.8.1. El arte de escribir parches de backport

Al usar MQ, escribir un parche de backport es un proceso simple. Todo lo que dicho parche debe hacer es modificar una sección de código que usa una característica del kernel que no está presente en la versión anterior del kernel, para que el controlador siga funcionando correctamente en esa versión anterior.

Una meta útil al escribir un buen parche de backport es hacer parecer que el código hubiera sido escrito para la versión vieja del kernel que usted tiene como objetivo. Entre menos intrusivo el parche, más fácil será entenderlo y mantenerlo. Si usted está escribiendo una colección de parches de backport para evitar el efecto de “nido de ratas” de tener muchos #ifdefs (secciones de código fuente que sólo son usados condicionalmente) en su código, no introduzca #ifdefs dependientes de versiones específicas en los parches. En vez de eso, escriba varios parches, cada uno de ellos haciendo cambios incondicionales, y controle su aplicación usando guardias.

Hay dos razones para ubicar los parches de backport en un grupo diferente, aparte de los parches “regulares” cuyos efectos son modificados por ellos. La primera es que mezclar los dos hace más difícil usar herramientas como la extensión patchbomb para automatizar el proceso de enviar los parches a un mantenedor oficial. La segunda es que un parche de backport puede perturbar el contexto en el que se aplica un parche regular subsecuente, haciendo imposible aplicar el parche normal limpiamente sin que el parche de backport sea aplicado antes.

13.9. Consejos útiles para hacer desarrollo con MQ

13.9.1. Organizar parches en directorios

Si está trabajando en un proyecto grande con MQ, no es difícil acumular un gran número de parches. Por ejemplo, tengo un repositorio de parches que contiene más de 250 parches.

Si usted puede agrupar estos parches en categorías lógicas separadas, usted puede almacenarlos en diferentes directorios si lo desea; MQ no tiene problemas manejando nombres de parches que contienen separadores de ruta.

13.9.2. Ver el historial de un parche

Si usted está desarrollando un conjunto de parches en un período de tiempo grande, es una buena idea mantenerlos en un repositorio, como se discutió en la sección 12.11. Si lo hace, notará rápidamente que usar el comando hg diff” para mirar el historial del repositorio no es viable. Esto es debido en parte a que usted está mirando la segunda derivada del código real (el diff de un diff), pero también porque MQ añade ruido al proceso al modificar las marcas de tiempo y los nombres de directorio cuando actualiza un parche.

Sin embargo, usted puede usar la extensión extdiff, que es provisto junto con Mercurial, para convertir un diff de dos versiones de un parche en algo legible. Para hacer esto, usted necesitará un paquete de un tercero llamado patchutils [Wau]. Éste paquete provee un comando llamado interdiff, que muestra las diferencias entre dos diffs como un diff. Al usarlo en dos versiones del mismo diff, genera un diff que representa el diff de la primera a la segunda versión.

Usted puede habilitar la extensión extdiff de la manera usual, añadiendo una línea a la sección [extensions] de su hgrc.

1  [extensions]
2  extdiff =

El comando interdiff espera recibir los nombres de dos ficheros, pero la extensión extdiff le pasa un par de directorios al programa que ejecuta, cada uno de los cuales puede contener una cantidad arbitraria de ficheros. Por esto necesitamos un programa pequeño que ejecute interdiff en cada par de ficheros de estos dos directorios. Este programa está disponible como hg-interdiff en el directorio examples del repositorio de código fuente que acompaña a este libro.

1  #!/usr/bin/env python
2  #
3  # Adapter for using interdiff with mercurial's extdiff extension.
4  #
5  # Copyright 2006 Bryan O'Sullivan <bos@serpentine.com>
6  #
7  # This software may be used and distributed according to the terms of
8  # the GNU General Public License, incorporated herein by reference.
9  
10  import os, sys
11  
12  def walk(base):
13      # yield all non-directories below the base path.
14      for root, dirs, files in os.walk(base):
15          for f in files:
16              path = os.path.join(root, f)
17              yield path[len(base)+1:], path
18      else:
19          if os.path.isfile(base):
20              yield '', base
21  
22  # create list of unique file names under both directories.
23  files = dict(walk(sys.argv[1]))
24  files.update(walk(sys.argv[2]))
25  files = files.keys()
26  files.sort()
27  
28  def name(base, f):
29      if f:
30          path = os.path.join(base, f)
31      else:
32          path = base
33      # interdiff requires two files; use /dev/null if one is missing.
34      if os.path.exists(path):
35          return path
36      return '/dev/null'
37  
38  ret = 0
39  
40  for f in files:
41      if os.system('interdiff "%s" "%s"' % (name(sys.argv[1], f),
42                                            name(sys.argv[2], f))):
43          ret = 1
44  
45  sys.exit(ret)

Con el programa hg-interdiff en la ruta de búsqueda de su intérprete de comandos, puede ejecutarlo como sigue, desde dentro de un directorio de parches MQ:

1  hg extdiff -p hg-interdiff -r A:B my-change.patch

Ya que usted seguramente querrá usar este comando tan largo a menudo, puede hacer que hgext lo haga disponible como un comando normal de Mercurial, editando de nuevo su hgrc.

1  [extdiff]
2  cmd.interdiff = hg-interdiff

Esto le indica a hgext que ponga a disposición un comando interdiff, con lo que usted puede abreviar la invocación anterior de hg extdiff” a algo un poco más manejable.

1  hg interdiff -r A:B my-change.patch
Nota: El comando interdiff trabaja bien sólo si los ficheros contra los cuales son generadas las versiones de un parche se mantienen iguales. Si usted crea un parche, modifica los ficheros subyacentes, y luego regenera el parche, interdiff podría no producir ningún resultado útil.

La extensión extdiff es útil para más que solamente mejorar la presentación de los parches MQ. Para leer más acerca de esto, vaya a la sección 14.2.

Capítulo 14
Añadir funcionalidad con extensiones

A pesar de que el corazón de Mercurial es muy completo desde el punto de vista de funcionalidad, carece de características rimbombantes deliberadamente. Esta aproximación de preservar la simplicidad mantiene el programa sencillo tanto para mantenedores como para usuarios.

Si embargo Mercurial no le cierra las posibilidades a un conjunto inflexible de órdenes: usted puede añadir características como extensiones (aveces llamadas añadidos1). Ya hemos discutido algunas de estas extensiones en capítulos anteriores:

En este capítulo cubriremos algunas extensiones adicionales disponibles para Mercurial, y daremos un vistazo a la maquinaria que necesita conocer en caso de que desee escribir una extensión.

14.1. Mejorar el desempeño con la extensión inotify

¿Desea lograr que las operaciones más comunmente usadas de Mercurial se ejecuten centenas de veces más rápido? ¡A leer!

Mercurial tiene gran desempeño bajo circunstancias normales. Por ejemplo, cuando ejecuta la orden hg status”, Mercurial tiene que revisar casi todos los ficheros y directorios en su repositorio de forma que pueda desplegar el estado de los ficheros. Muchas otras órdenes tienen que hacer tal trabajo tras bambalinas; por ejemplo la orden hg diff” usa la maquinaria de estado para evitar hacer operaciones de comparación costosas en ficheros que obviamente no han cambiado.

Dado que obtener el estado de los ficheros es crucial para obtener buen desempeño, los autores de Mercurial han optimizado este código en la medida de lo posible. Sin embargo, no puede obviarse el hecho de que cuando ejecuta hg status”, Mercurial tendrá que hacer por lo menos una costosa llamada al sistema por cada fichero administrado para determinar si ha cambiado desde la última vez que se consignó. Para un repositorio suficientemente grande, puede tardar bastante tiempo.

Para mostrar en números la magnitud de este efect, creé un repositorio que contenía 150.000 ficheros administrador. Tardó diez segundos para ejecutar hg status”, a pesar de que ninguno de los ficheros había sido modificado.

Muchos sistemas operativos modernos contienen una facilidad de notificación de ficheros. Si un programa se registra con un servicio apropiado, el sistema operativo le notificará siempre que un fichero de interés haya sido creado, modificado o borrado. En sistemas Linux, el componente del núcleo que lo hace se llama inotify.

La extensión inotify habla con el componente inotify del núcleo para optimizar las órdenes de hg status”. La extensión tiene dos componentes. Un daemonio está en el fondo recibiendo notificaciones del subsistema inotify. También escucha conexiones de una orden regular de Mercurial. La extensión modifica el comportamiento de Mercurial de tal forma que, en lugar de revisar el sistema de ficheros, le pregunta al daemonio. Dado que el daemonio tiene información perfecta acerca del estado del repositorio, puede responder instantáneamente con el resultado, evitando la necesidad de revisar cada directorio y fichero del repositorio.

Retomando los diez segundos que medí al ejecutar la orden hg status” de Mercurial sobre un repositorio de 150.000 ficheros. Con la extensión inotify habilitada, el tiempo se disipó a 0.1 seconds, un factor cien veces más rápido.

Antes de continuar, tenga en cuenta algunos detalles:

Hacia mayo de 2007 la extensión inotify no venía de forma predeterminada en Mercurial2, y es un poco más compleja de activar que otras extensiones. Pero la mejora en el desempeño bien vale la pena!

La extensión venía en dos partes: un conjunto de parches al código fuente de Mercurial, y una librería de interfaces de Python hacia el subsistema inotify.

Nota: Hay dos librerías de enlace de Python hacia inotify. Una de ellas se llama pyinotify, y en algunas distribuciones de Linux se encuentra como python-inotify. Esta es la que no necesita, puesto que tiene muchos fallos, y es ineficiente para ser práctica.
Para comenzar, es mejor tener una copia de Mercurial funcional instalada:
Nota: Si sigue las instrucciones a continuación, estará reemplazando y sobreescribiendo cualquier instalación previa de Mercurial que pudiera tener, con el código de Mercurial “más reciente y peligrosa”. No diga que no se le advirtio!
  1. Clone el repositorio de interfaz entre Python e inotify. Ármelo e instálelo:
    1  hg clone http://hg.kublai.com/python/inotify
    2  cd inotify
    3  python setup.py build --force
    4  sudo python setup.py install --skip-build
  2. Clone el repositorio crew de Mercurial. Clone el repositorio de parches de inotify de forma tal que las colas de Mercurial puedan aplicar los parches sobre el repositorio crew.
    1  hg clone http://hg.intevation.org/mercurial/crew
    2  hg clone crew inotify
    3  hg clone http://hg.kublai.com/mercurial/patches/inotify inotify/.hg/patches
  3. Asegúrese de instalar la extensión Colas de Mercurial mq y que estén habilitadas. Si nunca ha usado MQ, lea la sección 12.5 para poder comenzar rápidamente.
  4. Vaya al repositorio de inotify y aplique todos los parches de inotify con la opción -a de la orden hg qpush”.
    1  cd inotify
    2  hg qpush -a

    Si obtiene un mensaje de error de hg qpush”, no debería continuar. Mejor pida ayuda.

  5. Arme e instale la versión parchada de Mercurial.
    1  python setup.py build --force
    2  sudo python setup.py install --skip-build

Una vez que haya armado una versión funcional parchada de Mercurial, todo lo que necesita es habilitar la extensión inotify colocando una entrada en su hgrc.

1  [extensions]
2  inotify =

Cuando la extensión inotify esté habilitada, Mercurial iniciará transparente y automáticamente el daemonio de estado la primera vez que ejecute un comando que requiera estado del repositorio. Ejecuta un daemonio de estado por repositorio.

El daemonio de estado se inicia silenciosamente y se ejecuta en el fondo. Si mira a la lista de procesos en ejecución después de habilitar la extensión inotify y ejecuta unos pocos comandos en diferentes repositorios, verá que hay algunos procesos de hg por ahí, esperando actualizaciones del kernel y solicitudes de Mercurial.

La primera vez que ejecuta un comando de Mercurial en un repositorio cuando tiene la extensión inotify habilitada, correrá casi con el mismo desempeño que una orden usual de Mercurial. Esto es debido a que el estado del daemonio necesita aplicar una búsqueda normal sobre el estado para poder tener una línea de partida frente a la cual aplicar posteriormente actualizaciones del núcleo. De todas formas, todo comando posterior que haga cualquier clase de revisión del estado debería ser notablemente más rápido en repositorios con incluso un tamaño modesto. Aún mejor, a medida que su repositorio sea más grande, mejor desempeño verá. El daemonio inotify hace operaciones de estado de forma casi instantánea en repositorios de todos los tamaños!

Si lo desea, puede iniciar manualmente un daemonio de estado con la orden hg inserve”. Esto le da un control un poco más fino acerca de cómo debería ejecutarse el daemonio. Esta orden solamente estará disponible cuando haya habilitado la extensión inotify.

Cuando esté usando la extensión inotify, no debería ver diferencia en el comportamiento de Mercurial, con la única excepción de que los comandos relacionados con el estado deberían ejectuarse mucho más rápido que como solían hacerlo. Debería esperar específicamente que las órdenes no deberían ofrecer salidas distintas; ni ofrecer resultados diferentes. Si alguna de estas situaciones ocurre, por favor reporte el fallo.

14.2. Soporte flexible de diff con la extensión extdiff

La orden predeterminada hg diff” de Mercurial despliega diffs en texto plano unificadas.

1  $ hg diff
2  diff -r d6e1fbefacc8 myfile
3  --- a/myfile Tue Feb 10 18:23:22 2009 +0000
4  +++ b/myfile Tue Feb 10 18:23:22 2009 +0000
5  @@ -1,1 +1,2 @@
6   The first line.
7  +The second line.

Si dese emplear una herramienta externa para desplegar las modificaciones, querrá usar la extensión extdiff. Esta le permitirá usar por ejemplo una herramienta gráfica de diff.

La extensión extdiff viene con Mercurial, y es fácil configurar. En la sección [extensions] de su hgrc, basta con añadir una entrada de una línea para habilitar la extensión.

1  [extensions]
2  extdiff =

Esto introduce una orden llamada hg extdiff”, que de forma predeterminada usa su orden del sistema diff para generar un diff unificado de la misma forma que lo hace el comando predeterminado hg diff”.

1  $ hg extdiff
2  --- a.d6e1fbefacc8/myfile 2009-02-10 18:23:22.000000000 +0000
3  +++ /tmp/extdiff83LA2p/a/myfile 2009-02-10 18:23:22.000000000 +0000
4  @@ -1 +1,2 @@
5   The first line.
6  +The second line.

El resultado no será exactamente el mismo que con la orden interna hg diff”, puesto que la salida de diff varía de un sistema a otro, incluso pasando las mismas opciones.

Como lo indican las líneas“making snapshot”, la orden hg extdiff” funciona creando dos instantáneas de su árbol de fuentes. La primera instantánea es la revisión fuente; la segunda es la revisión objetivo del directorio de trabajo. La orden hg extdiff” genera estas instantáneas en un directorio temporal, pasa el nombre de cada directorio a un visor de diffs temporal y borra los directorios temporales. Por cuestiones de eficiencia solamente genera instantáneas de los directorios y ficheros que han cambiado entre dos revisiones.

Los nombres de los directorios de instantáneas tienen los mismos nombres base de su repositorio. Si su repositorio tiene por ruta /quux/bar/foo, foo será el nombre de cada instantánea de directorio. Cada instantánea de directorio tiene sus identificadores de conjuntos de cambios al final del nombre en caso de que sea apropiado. Si una instantánea viene de la revisión a631aca1083f, el directorio se llamará foo.a631aca1083f. Una instantánea del directorio de trabajo no tendrá el identificador del conjunto de cambios, y por lo tanto será solamente foo en este ejemplo. Para ver cómo luce en la práctica, veamos de nuevo el ejemplo hg extdiff” antes mencionado. Tenga en cuenta que los diffs tienen los nombres de las instantáneas de directorio dentro de su encabezado.

La orden hg extdiff” acepta dos opciones importantes. La opción -p le permite elegir un programa para ver las diferencias, en lugar de diff. Con la opción -o puede cambiar las opciones que hg extdiff” pasa a tal programa (de forma predeterminada las opciones son“-Npru”, que tienen sentido únicamente si está usando diff). En otros aspectos, la orden hg extdiff” actúa de forma similar a como lo hace la orden hg diff” de Mercurial: usted usa los mismos nombres de opciones, sintaxis y argumentos para especificar las revisiones y los ficheros que quiere, y así sucesivamente.

Por ejemplo, para ejecutar la orden usual del sistema diff, para lograr que se generen diferencias de contexto (con la opción -c) en lugar de diferencias unificadas, y cinco líneas de contexto en lugar de las tres predeterminadas (pasando 5 como argumento a la opción -C).

1  $ hg extdiff -o -NprcC5
2  ⋆⋆⋆ a.d6e1fbefacc8/myfile Tue Feb 10 18:23:22 2009
3  --- /tmp/extdiff83LA2p/a/myfile Tue Feb 10 18:23:22 2009
4  ⋆⋆⋆⋆⋆⋆⋆⋆⋆⋆⋆⋆⋆⋆⋆
5  ⋆⋆⋆ 1 ⋆⋆⋆⋆
6  --- 1,2 ----
7    The first line.
8  + The second line.

Es sencillo lanzar unas herramienta usual de diferencias. Para lanzar el visor kdiff3:

1  hg extdiff -p kdiff3 -o ''

Si su orden para visualizar diferencias no puede tratar con directorios, puede usar un poco de scripting para lograrlo. Un ejemplo de un script con la extensión mq junto con la orden interdiff está en la sección 13.9.2.

14.2.1. Definición de alias de comandos

Acordarse de todas las opciones de las órdenes hg extdiff” y el visor de diferencias de su preferencia puede ser dispendioso, y por lo tanto la extensión extdiff le permite definir nuevas órdenes que invocarán su visor de diferencias con las opciones exactas.

Basta con editar su fichero hgrc, y añadir una sección llamada [extdiff]. Dentro de esta sección puede definir varias órdenes. Mostraremos como añadir la orden kdiff3. Después de definido, puede teclear “hg kdiff3” y la extensión a extdiff ejecutará la orden kdiff3.

1  [extdiff]
2  cmd.kdiff3 =

Si deja vacía la porción derecha de la definición, como en el ejemplo, la extensión extdiff usa el nombre de la orden se definirá como el nombre del programa externo a ejecutar. Pero tales nombres no tienen por qué ser iguales. Definimos ahora la orden llamada “hg wibble”, que ejecuta kdiff3.

1  [extdiff]
2  cmd.wibble = kdiff3

También puede especificar las opciones predeterminadas con las cuales desea invocar el visor de diferencias. Se usa el prefijo “opts.”, seguido por el nombre de la orden a la cual se aplican las opciones. En este ejemplos se define la orden “hg vimdiff” que ejecuta la extensión DirDiff del editor vim.

1  [extdiff]
2  cmd.vimdiff = vim
3  opts.vimdiff = -f '+next' '+execute "DirDiff" argv(0) argv(1)'

14.3. Uso de la extensión transplant para seleccionar

Need to have a long chat with Brendan about this.

14.4. Enviar cambios vía correo electrónico con la extensión patchbomb

Varios proyectos tienen la cultura de “revisión de cambios”, en la cual la gente envía sus modificaciones a una lista de correo para que otros las lean y comenten antes de consignar la versión final a un repositorio compartido. Algunos proyectos tienen personas que actúan como cancerberos; ellos aplican los cambios de otras personas a un repositorio para aquellos que no tienen acceso.

Mercurial facilita enviar cambios por correo para revisión o aplicación gracias a su extensión patchbomb. La extensión es tan popular porque los cambios se formatean como parches y es usual que se envía un conjunto de cambios por cada correo. Enviar una gran cantidad de cambios por correos se llama por tanto “bombardear” el buzón de entrada del destinatario, de ahí su nombre “bombardeo de parches”.

Como es usual, la configuración básica de la extensión patchbomb consta de una o dos líneas en su hgrc.

1  [extensions]
2  patchbomb =

Cuando haya habilitado la extensión, dispondrá de una nueva orden, llamada hg email”.

La forma mejor y más segura para invocar la orden hg email” es ejecutarla siempre con la opción -n; que le mostrará lo que la orden enviaría, sin enviar nada. Una vez que haya dado un vistazo a los cambios y verificado que está enviando los correctos, puede volver a ejecutar la misma orden, sin la opción -n.

La orden hg email” acepta la misma clase de sintaxis de revisiones como cualquier otra orden de Mercurial. Por ejemplo, enviará todas las revisiones entre la 7 y la punta, inclusive.

1  hg email -n 7:tip

También puede especificar un repositorio para comparar. Si indica un repositoro sin revisiones, la orden hg email” enviará todas las revisiones en el repositorio local que no están presentes en el repositorio remoto. Si especifica revisiones adicionalmente o el nombre de una rama (la última con la opción -b), respetará las revisiones enviadas.

Ejecutar la orden hg email” sin los nombres de aquellas personas a las cuales desea enviar el correo es completamente seguro: si lo hace, solicitará tales valores de forma interactiva. (Si está usando Linux o un sistema tipo Unix, tendrá capacidades estilo–readline aumentadas cuando ingrese tales encabezados, lo cual es sumamente útil.)

Cuando envíe una sola revisión, la orden hg email” de forma predeterminada usará la primera línea de descripción del conjunto de cambios como el tema del único mensaje que se enviará.

Si envía varias revisiones, la orden hg email” enviará normalmente un mensaje por conjunto de cambios. Colocará como prefacio un mensaje introductorio en el cual usted debería describir el propósito de la serie de cambios que está enviando.

14.4.1. Cambiar el comportamiento de las bombas de parches

Cada proyecto tiene sus propias convenciones para enviar cambios en un correo electrónico; la extensión patchbomb intenta acomodarse a diferentes variaciones gracias a las opciones de la línea de órdenes:

Apéndice A
Referencia de Órdenes

A.1. hg add”—Añade ficheros en la próxima consignación

--include, también -I

--exclude, también -X

--dry-run, también -n

A.2. hg diff”—imprime los cambios en el historial o el directorio actual

Mostrar las diferencias entre revisiones para ficheros especificados o directorios, con el formato unificado diff. Si desea ver una descripción del formato unificado diff, ver la sección 12.4.

De forma predeterminada, esta orden no imprime las diferencias para los ficheros binarios que Mercurial esté siguiendo. Para controlar este comportamiento, vea las opciones -a y --git.

A.2.1. Options

opción --nodates

Omite la fecha y hora cuando se muestran los encabezados de las diferencias.

--ignore-blank-lines, también -B

No imprime los cambios que solamente insertan o eliminan líneas en blanco. Una línea que contiene espacios en blanco no se considera como una línea en blanco.

--include, también -I

Incluye ficheros y directorios cuyos nombres coinciden con los patrones elegidos.

--exclude, también -X

Excluye los ficheros y directorios cuyos nombres coinciden con los patrones elegidos.

--text, también -a

Si no especifica esta opción, hg diff” no mostrará las diferencias de los ficheros que detecte como binarios. Al especificar -a se forza a hg diff” a tratar los ficheros como texto, y generar diferencias para todos.

Esta opción es útil para los ficherso que son “texto en mayor medida” pero que tienen caracteres NUL. Si lo usa en ficheros que contienen muchos datos binarios, la salida será incomprensible.

--ignore-space-change, también -b

No imprime si el único cambio que en la línea es la cantidad de espacio en blanco.

--git, también -g

Mostrar diferencias compatibles con git. XXX reference a format description.

--show-function, también -p

Mostrar el nombre de la función que contiene el código en una porción del encabzado usando una heurística simple. Esta funcionalidad se habilita de forma predeterminada, así que la opción -p no tiene efectos a menos que cambie el valor de showfunc en la configuración, como en el ejemplo siguiente.

1  $ echo '[diff]' >> $HGRC
2  $ echo 'showfunc = False' >> $HGRC
3  $ hg diff
4  diff -r a38a6c74a605 myfile.c
5  --- a/myfile.c Tue Feb 10 18:23:18 2009 +0000
6  +++ b/myfile.c Tue Feb 10 18:23:18 2009 +0000
7  @@ -1,4 +1,4 @@
8   int myfunc()
9   {
10  -    return 1;
11  +    return 10;
12   }
13  $ hg diff -p
14  diff -r a38a6c74a605 myfile.c
15  --- a/myfile.c Tue Feb 10 18:23:18 2009 +0000
16  +++ b/myfile.c Tue Feb 10 18:23:18 2009 +0000
17  @@ -1,4 +1,4 @@ int myfunc()
18   int myfunc()
19   {
20  -    return 1;
21  +    return 10;
22   }

--rev, también -r

Especifique una o más revisiones para comparar. La orden hg diff” acepta hasta dos opciones -r para especificar las revisiones a comparar.

  1. Despliega las diferencias entre la revisión padre y del directorio de trabajo.
  2. Despliega las diferencias entre el conjunto de cambios especificados y el directorio de trabajo.
  3. Despliega las diferencias entre dos conjuntos de cambios especificados.

Puede especificar dos revisiones usando o bien sea las opciones -r o la notación de rango. Por ejemplo, las dos especificaciones de revisiones a continuación son equivalentes:

1  hg diff -r 10 -r 20
2  hg diff -r10:20

Cuando especifica dos revisiones, esto tiene significado para Mercurial. Esto significa que hg diff -r10:20” producirá un diff que transformará los ficheros desde los contenidos en la revisión 10 a los contenidos de la revisión 20, mientras que hg diff -r20:10” significa lo opuesto: el diff que transformaría los contenidos de los ficheros de la revisión 20 a los contenidos de la revisión 10. No puede invertir el orden de esta forma si está haciendo un diff frente al directorio de trabajo.

--ignore-all-space, también -w

A.3. hg version”—imprime la información de versión y derechos de reproducción

Esta orden despliega la versión de Mercurial que está usando, y su nota de derechos de reproducción. Hay cuatro clases de cadenas de versión posibles:

A.3.1. Consejos y trucos

¿Por qué difieren los resultados de “hg diff” y “hg status”?

Cuando ejecuta la orden hg status”, verá una lista de ficheros para los cuales Mercurial almacenará cambios la próxima vez que consigne. Si ejecuta la orden hg diff”, verá que imprime diferencias solamente para un subconjunto de los ficheros que hg status” liste. Hay dos posibles razones para este comportamiento:

La primera es que hg status” imprime cierta clase de modificaciones que hg diff” no despliega normalmente. La orden hg diff” usualmente despliega diferencias unificadas, las cuales no tienen la habilidad de representar algunos cambios que Mercurial puede seguir. Lo más notable es que las diferencias tradicionales no pueden representar un cambio acerca de la ejecutabilidad de un fichero, pero Mercurial sí almacena esta información.

Si usa la opción --git de hg diff”, mostrará diferencias compatibles con git que pueden desplegar esta información adicional.

La segunda razón posible para que hg diff” esté imprimiendo diferencias para un subconjunto de ficheros de lo que muestra hg status” es que si usted le invoca sin argumento alguno, hg diff” imprime diferencias frente al primer padre del directorio de trabajo. Si ha ejecutado hg merge” para fusionar dos conjuntos de cambios, pero no ha consignado aún los resultados de la fusión, su directorio de trabajo tiene dos padres (use hg parents” para verlos). Mientras que hg status” imprime modificaciones relativas a ambos padres después de una fusión que no se ha consignado, hg diff” opera aún relativo solamente al primer padre. Puede lograr que imprima las diferencias relativas al segundo padre especificando tal padre con la opción -r. No hay forma de hacer que imprima las diferencias relativas a los dos padres.

Generar diferencias seguras en binarios

Si usa la opción -a para forzar que Mercurial imprima las diferencias de los ficheros que so o bien “casi completamente texto” o contienen muchos datos binarios, tales diferencias no pueden aplicarse subsecuentemente a la orden hg import” de Mercurial o a la orden patch del sistema.

Si desea generar una diferencia de un fichero binario que es seguro para usarlo como entrada a la orden hg import”, use la opción hg diff”–git cuando genere el parche. La orden patch del sistema no puede tratar con parches binarios.

Apéndice B
Referencia de las Colas de Mercurial

B.1. Referencia de órdenes MQ

Si desea dar un vistazo a las órdenes que ofrece MQ, use la orden hg help mq”.

B.1.1. hg qapplied”—imprimir los parches aplicados

La orden hg qapplied” imprime la pila actual de parches aplicados. Los parches se imprimen en orden de antigüedad, primero los más antiguos y después los más recientes, por lo tanto el último parche de la lista es el que está en el “tope”.

B.1.2. hg qcommit”—consignar cambios en la cola del repositorio

La orden hg qcommit” consigna cualquier cambio sobresaliente en el repositorio .hg/patches. Esta orden solamente funciona si el directorio .hg/patches es un repositorio, p.e. usted creó el directorio con hg qinit -c” o ejecutó hg init” en el directorio después de correr hg qinit”.

Esta orden es un atajo para hg commit --cwd .hg/patches”.

B.1.3. hg qdelete”—eliminar un parche del fichero series

La orden hg qdelete” elimina la entrada del fichero series para el parche en el directorio .hg/patches. No sca el parche si ha sido aplicado. De forma predeterminada no borra el fichero del parche; use la opción -f para hacerlo.

Opciones:

B.1.4. hg qdiff”—imprimir la diferencia del último parche aplicado

La orden hg qdiff” imprime un diff del parche más recientemente aplicado. Es equivalente a hg diff -r-2:-1”.

B.1.5. hg qfold”—fusionar (“integrar”) varios parches en uno solo

La orden hg qfold” fusiona muchos parches en el último parche aplicado, de tal forma que el último parche aplicado es la unión de todos los cambios de los parches en cuestión.

Los parches a fusionar no deben haber sido aplicados; hg qfold” saldrá indicando un error si alguno ha sido aplicado. El orden en el cual los parches se pliegan es significativo; hg qfold a b” significa “aplique el parche más reciente, seguido de a, y seguido de b”.

Los comentarios de los parches integrados se colocan al final de los comentarios del parche destino, con cada bloque de comentarios separado con tres asteriscos (“”). Se usa la opción -e para editar el mensaje de consignación para el conjunto de cambios/parches después de completarse el pliegue.

Opciones:

B.1.6. hg qheader”—desplegar el encabezado/descripción de un parche

La orden hg qheader” imprime el encabezado o descripción de un parche. De forma predeterminada, imprime el encabezado del último parche aplicado. Si se da un argumento, imprime el encabezado del parche referenciado.

B.1.7. hg qimport”—importar el parche de un tercero en la cola

La orden hg qimport” añade una entrada de un parche externo al fichero series y copia el parche en el directorio .hg/patches. Añade la entrada inmediatamente después del último parche aplicado, pero no introduce el parche.

Si el directorio .hg/patches es un repositorio, hg qimport” automáticamente hace un hg add” del parche importado.

B.1.8. hg qinit”—preparar un repositorio para trabajar con MQ

La orden hg qinit” prepara un repositorio para trabajar con MQ. Crea un directorio llamado .hg/patches.

Opciones:

Cuando el directorio .hg/patches es un repositorio, las órdenes hg qimport” y hg qnew” hacen hg add” automáticamente a los parches nuevos.

B.1.9. hg qnew”—crear un parche nuevo

La orden hg qnew” crea un parche nuevo. Exige un argumento, el nombre que se usará para tal parche. El parche recién creado está vacío inicialmente. Se añade al fichero series después del último parche aplicado, y se introduce en el tope de ese parche.

Si hg qnew” encuentra ficheros modificados en el directorio de trabajo, rehusará crear un parche nuevo a meos que se emplee -f la opción (ver más adelante). Este comportamiento le permite hacer hg qrefresh” al último parche aplicado antes de aplicar un parche nuevo encima de este.

Opciones:

B.1.10. hg qnext”—imprimir el nombre del próximo parche

La orden hg qnext” imprime el nombre del siguiente parche en el fichero series a continuación del último parche aplicado. Este parche sería el próximo parche a aplicar si se ejecutara la orden hg qpush”.

B.1.11. hg qpop”—sustraer parches de la pila

La orden hg qpop” elimina los parches aplicados del tope de la pila de parches aplicados. De forma predeterminada solamente remueve un parche.

Esta orden elimina los conjuntos de cambios que representan los parches sustraídos del repositorio, y actualiza el directorio de trabajo para deshacer los efectos de los parches.

Esta orden toma un argumento opcional, que usa como el nombre o el índice del parche que desea sustraer. Si se da el nombre, sustraerá los parches hasta que el parche nombrado sea el último parche aplicado. Si se da un número, hg qpop” lo trata como un índice dentro del fichero series, contando desde cero (no cuenta las líneas vacías o aquellas que sean únicamente comentarios). Sustrae los parches hasta que el parche identificado por el índice sea el último parche aplicado.

La orden hg qpop” no lee o escribe parches en el fichero series. hg qpop” se constituye por tanto en una forma segura de sustraer un parche del fichero series o un parche que ha eliminado o renombrado completamente. En los dos últimos casos, use el nombre del parche tal como lo hizo cuando lo aplicó.

De forma predeterminada, la orden hg qpop” no sustraerá parche alguno si el directorio de trabajo ha sido modificado. Puede modificar este comportamiento con la opción -f, que revierte todas las modificaciones del directorio de trabajo.

Opciones:

La orden hg qpop” elimina una línea del final del fichero status por cada parche que se sustrae.

B.1.12. hg qprev”—imprimir el nombre del parche anterior

La orden hg qprev” imprime el nombre del parche en el fichero series que está antes del último parche aplicado. Este se volverá el último parche aplicado si ejecuta hg qpop”.

B.1.13. hg qpush”—introducir parches a la pila

La orden hg qpush” añade parches a la pila. De forma predeterminada añade solamente un parche.

Esta orden crea un conjunto de cambios que representa cada parche aplicado y actualiza el directorio de trabajo aplicando los efectos de los parches.

Los datos predeterminados cuando se crea un conjunto de cambios corresponde a:

Su un parche contiene un encabezado de parche de Mercurial (XXX add link), la información en el encabezado del parche tiene precedencia sobre el predeterminado.

Opciones:

La orden hg qpush” lee, pero no modifica el fichero series. Añade al final del fichero hg status” una línea por cada parche que se introduce.

B.1.14. hg qrefresh”—actualiza el último parche aplicado

La orden hg qrefresh” actualiza el último parche aplicado. Modifica el parche, elimina el último conjunto de cambios que representó el parche, y crea un nuevo conjunto de cambios para representar el parche modificado.

La orden hg qrefresh” busca las siguientes modificaciones:

Incluso si hg qrefresh” no detecta cambios, de todas maneras recrea el conjunto de cambios que representa el cambio. Esto causa que la identidad del conjunto de cambios difiera del conjunto de cambios previo que identificó al parche.

Opciones:

B.1.15. hg qrename”—renombrar un parche

La orden hg qrename” renombra un parche y cambia la entrada del parche en el fichero series.

Con un argumento sencillo, hg qrename” renombra el último parche aplicado. Con dos argumentos, renombra el primer argumento con el segundo.

B.1.16. hg qrestore”—restaurar el estado almacenado de la cola

XXX No idea what this does.

B.1.17. hg qsave”—almacena el estado actual de la cola

XXX Likewise.

B.1.18. hg qseries”—imprime la serie completa de parches

La orden hg qseries” imprime la serie completa de parches del fichero series. Imprime solamente los nombres de los parches sin las líneas en blanco o comentarios. Imprime primero el primero y de último, el último aplicado.

B.1.19. hg qtop”—imprime el nombre del parche actual

hg qtop” imprime el nombre del último parche aplicado.

B.1.20. hg qunapplied”—imprimir los parches que aún no se han aplicado

La orden hg qunapplied” imprime los nombres de los parches del fichero series que todavía no han sido aplicados. Los imprime de acuerdo al orden en el cual serían introducidos.

B.1.21. hg strip”—remover una revisión y sus descendientes

La orden hg strip” remueve una revisión, y todos sus descendientes del repositorio. Deshace los efectos de las revisiones removidas del repositorio, y actualiza el directorio de trabajo hasta el primer padre de la revisión removida.

La orden hg strip” almacena una copia de segurida de los conjuntos de cambios en un agrupamiento, de forma tal que puedan ser reaplicados en caso de que se hayan removido por equivocación.

Opciones:

B.2. Referencia de ficheros de MQ

B.2.1. El fichero series

El fichero series contiene una lista de los nombres de todos los parches que MQ puede aplicar. Se representa como una lista de nombres, uno por línea. Se ignora el espacio en blanco al principio y al final.

Las líneas pueden contener comentario. Un comentario comienza con el caracter “#”, y va hasta el final de la línea. Se ignoran las líneas vacías y las que solamente contengan comentarios.

En algún momento podría editar el fichero series a mano, por tal motivo se admiten comentarios y líneas en blanco como se menciono anteriormente. Por ejemplo, puede poner en comentario un parche temporalmente y hg qpush” omitirá tal parche cuando los aplique. También puede cambiar el orden en el cual se aplican los parches, reordenando las entradas en el fichero series.

También es posible colocar el fichero series bajo control de revisiones; también es favorable colocar todos los parches que refiera bajo control de revisiones. Si crea un directorio de parches con la opción -c de hg qinit”, esto se hará automáticamente.

B.2.2. El fichero status

El fichero status contiene los nombres y los hashes de los conjuntos de cambios de todos los parches que MQ ha aplicado. A diferencia del fichero series, este NO ha sido diseñado para ser editado. No debería colocar este fichero bajo el control de revisiones o modificarlo de forma alguna. MQ lo usa estrictamente para administración interna.

Apéndice C
Instalar Mercurial desde las fuentes

C.1. En un sistema tipo Unix

Si usa un sistema tipo Unix que tiene una versión suficientemente reciente de Python (2.3 o superior) disponible, es fácil instalar Mercurial desde las fuentes.

  1. Descargue un paquete fuente reciente de http://www.selenic.com/mercurial/download.
  2. Descomprímalo:
    1  gzip -dc mercurial-version.tar.gz | tar xf -
  3. Vaya al directorio fuente y ejecute el guión de instalación. Esto armará Mercurial y lo instalará en su directorio casa:
    1  cd mercurial-version
    2  python setup.py install --force --home=$HOME

Cuando termine la instalación, Mercurial estará en el subdirectorio bin de su directorio casa. No olvide asegurarse de que este directorio esté presente en el camino de búsqueda de su intérprete de órdenes.

Probablemente necesitará establecer la variable de ambiente PYTHONPATH de tal forma que los ejecutables de Mercurial puedan encontrar el resto de los paquetes de Mercurial. Por ejemplo, en mi portátil, la establecía a /home/bos/lib/python. La ruta exacta que usted use dependerá de como ha sido construído Python en su sistema, pero debería ser fácil deducirla. Si no está seguro, mire lo que haya mostrado el script en el paso anterior, y vea dónde se instalaron los contenidos del directorio mercurial se instalaron.

C.2. En Windows

Armar e instalar Mercurial en Windows requiere una variedad de herramientas, cierta suficiencia técnica y paciencia considerable. Personalmente, no le recomiendo hacerlo si es un “usuario casual”. A menos que intente hacer hacks a Mercurial, le recomiendo que mejor use un paquete binario.

Si está decidido a construir Mercurial desde las fuentes en Windows, siga el “camino difícil” indicado en el wiki de Mercurial en http://www.selenic.com/mercurial/wiki/index.cgi/WindowsInstall, y espere que el proceso sea realmente un trabajo duro.

Apéndice D
Licencia de Publicación Abierta

Versión 1.0, 8 Junio de 1999

D.1. Requerimientos en versiones modificadas y no modificadas

Los trabajos bajo Publicación Abierta pueden reproducirse y distribuirse enteros o en porciones, en cualquier medio físico o electrónico, siempre y cuando se respeten los términos de esta licencia, y se incorpore esta licencia o su referencia (con cualquiera de las opciones elegidas por el autor y el editor) en la reproducción.

A continuación mostramos la forma correcta de incorporar por referencia:

Copyright (c) año por nombre del autor o designado. Este material puede distribuirse solamente bajo los términos y condiciones especificados por la Licencia de Publicación Abierta, vx.y o posterior (la última versión disponible está en http://www.opencontent.org/openpub/).

La referencia debe estar seguida inmediatamente por cualquier opción elegida por el(os) autor(es) y/o editor(es) del documento (consulte la sección D.6).

Se permite la redistribución comercial de los materiales sujetos a la Publicación Abierta.

Cualquier publicación en forma estándar de libro (papel) requerirá citar al editor y autor original. Los nombres del editor y el autor aparecerán en todas las superficies externas del libro. En todas las superficies externas el nombre del editor deberá aparecer en tamaño de la misma medida que el título del trabajo y será citado como poseedor con respecto al título.

D.2. Derechos de reproducción

El derecho de reproducción de cada Publicación Abierta pertenece al(os) autor(es) o designados.

D.3. Alcance de la licencia

Los términos de licencia dsecritos aplican a todos los trabajos bajo licencia de publicación abierta a menos que se indique de otra forma en este documento.

La simple agregación de trabajos de Publicación Abierta o una porción de trabajos de Publicación Abierta con otros trabajos o programas en el mismo medio no causarán que esta licencia se aplique a los otros trabajos. Los agregados deberán contener una nota que especifique la inclusión de matrial de Publicación Abierta y una nota de derechos de reproducción acorde.

Separabilidad. Si cualquier porción de esta licencia no es aplicable en alguna jurisdicción, las porciones restantes se mantienen.

Sin garantía. Los trabajos de Publicación Abierta se licencian y ofrecen “como están” sin garantía de ninguna clase, expresa o implícita, incluyendo, pero no limitados a las garantías de mercabilidad y adaptabilidad para un propósito particular o garantía de no infracción.

D.4. Requerimientos sobre trabajos modificados

Todas las versiones modificadas de documentos cubiertos por esta licencia, incluyendo traducciones, antologías, compilaciones y documentos parciales, deben seguir estos requerimientos:

  1. La versión modificada debe estar etiquetada como tal.
  2. La persona que hace la modificación debe estar identificada y la modificación con fecha.
  3. El dar crédito al autor original y al editor si se requiere de acuerdo a las prácticas académicas de citas.
  4. Debe identificarse el lugar del documento original sin modificación.
  5. No puede usarse el(os) nombre(s) del autor (de los autores) para implicar relación alguna con el documento resultante sin el permiso explícito del autor (o de los autores).

D.5. Recomendaciones de buenas prácticas

Adicional a los requerimientos de esta licencia, se solicita a los redistribuidores y se recomienda en gran medida que:

  1. Si está distribuyendo trabajaos de Publicación Abierta en copia dura o CD-ROM, envíe una notificación por correo a los autores acerca de su intención de redistribuir por lo menos con treinta días antes de que su manuscrito o el medio se congelen, para permitir a los autores tiempo para proveer documentos actualizados. Esta notificación debería describir las modificaciones, en caso de que haya, al documento.
  2. Todas las modificaciones sustanciales (incluyendo eliminaciones) deben estar marcadas claramente en el documento o si no descritas en un adjunto del documento.
  3. Finalmente, aunque no es obligatorio bajo esta licencia, se considera de buenos modales enviar una copia gratis de cualquier expresión en copia dura o CD-ROM de un trabajo licenciado con Publicación Abierta a el(os) autor(es).

D.6. Opciones de licencia

El(os) autor(es) y/o editor de un documento licenciado con Publicación Abierta pueden elegir ciertas opciones añadiendo información a la referencia o a la copia de la licencia. Estas opciones se consideran parte de la instancia de la licencia y deben incluirse con la licencia (o su incorporación con referencia) en trabajos derivados.

  1. Prohibir la distribución de versiones substancialmente modificadas sin el permiso explícito del(os) autor(es). Se definen “modificaciones substanciales” como cambios en el contenido semántico del documento, y se excluyen simples cambios en el formato o correcciones tipográficas.

    Para lograr esto, añada la frase “Se prohibe la distribución de versiones substancialmente modificadas de este documento sin el permiso explícito del dueño de los derechos de reproducción.” a la referencia de la licencia o a la copia.

  2. Está prohibido prohibir cualquier publicación de este trabajo o derivados como un todo o una parte en libros estándar (de papel) con propósitos comerciales a menos que se obtenga un permiso previo del dueño de los derechos de reproducción.

    Para lograrlo, añada la frase “La distribución del trabajo o derivados en cualquier libro estándar (papel) se prohibe a menos que se obtenga un permiso previo del dueño de los derechos de reproducción.” a la referencia de la licencia o la copia.

Bibliografía

[AG]    Jean Delvare Andreas Gruenbacher, Martin Quinson. Patchwork quilt. http://savannah.nongnu.org/projects/quilt.

[BI]    Ronald Oussoren Bob Ippolito. Universal macpython. http://bob.pythonmac.org/archives/2006/04/10/python-and-universal-binaries-on-mac-os-x/.

[Bro]    Neil Brown. wiggle–apply conflicting patches. http://cgi.cse.unsw.edu.au/ neilb/source/wiggle/.

[Dic]    Thomas Dickey. diffstat–make a histogram of diff output. http://dickey.his.com/diffstat/diffstat.html.

[Dus]    Andy Dustman. Mysql for python. http://sourceforge.net/projects/mysql-python.

[Gru05]   Andreas Gruenbacher. How to survive with many patches (introduction to quilt). http://www.suse.de/ agruen/quilt.pdf, June 2005.

[Mas]    Chris Mason. mpatch–help solve patch rejects. http://oss.oracle.com/ mason/mpatch/.

[O’S06]   Bryan O’Sullivan. Achieving high performance in mercurial. In EuroPython Conference, July 2006. XXX.

[Pyt]    Python.org. ConfigParser—configuration file parser. http://docs.python.org/lib/module-ConfigParser.html.

[RS]    GNU Project volunteers Richard Stallman. Gnu coding standards—change logs. http://www.gnu.org/prep/standards/html˙node/Change-Logs.html.

[Tat]    Simon Tatham. Putty—open source ssh client for windows. http://www.chiark.greenend.org.uk/ sgtatham/putty/.

[Wau]    Tim Waugh. patchutils–programs that operate on patch files. http://cyberelk.net/tim/patchutils/.

Índice alfabético

.hg/hgrc, fichero, 134, 135, 195
.hg/localtags, fichero, 152, 230, 232
.hg/patches.N, directorio, 300
.hg/patches, directorio, 266, 272, 304, 305, 342, 343
.hg/store/data, directorio, 64
.hgignore, fichero, 305, 343
.hgrc, fichero, 31, 61
.hgtags, fichero, 151, 152, 230, 232
.orig, fichero, 297
.rej, fichero, 297, 298
.ssh/config, fichero, 129
.ssh, directorio, 126, 127
EMAIL, variable de entorno, 31
HGMERGE, variable de entorno, 55, 59
HGUSER, variable de entorno, 31
HG_NODE, variable de entorno, 200, 226
HG_PARENT1, variable de entorno, 226
HG_PARENT2, variable de entorno, 226
HG_SOURCE, variable de entorno, 226
HG_URL, variable de entorno, 226, 227
Mercurial.ini, fichero de configuración, 126
PATH, variable de entorno, 128
PYTHONPATH, variable de entorno, 128, 131, 207, 351
acl, extensión, 217, 218, 325
acl, gancho, 217
addbreaks, filtro de plantilla, 243
addremove, comando, 105, 297
add, comando, 96, 99, 100, 103, 107, 112, 139, 141, 163, 164, 166, 293, 305, 311, 335, 343, 346
    opción --dry-run, 335
    opción --exclude, 335
    opción --include, 335
    opción -I, 335
    opción -n, 335
    opción -X, 335
age, filtro de plantilla, 243
annotate, comando, 257, 272, 278
authorized_keys, fichero, 126, 127
author, palabra clave de plantilla, 239, 243, 244
    filtro domain, 243
    filtro email, 243
    filtro person, 243
    filtro user, 244
backout, comando, 168, 169, 171, 172, 174–176, 178, 179, 183
    opción --merge, 172, 175, 183
    opción -m, 169
basename, filtro de plantilla, 243
bash, comando de sistema, 305
bisect, comando, 184–187, 189–191
bisect, extensión, 3, 257
branches, comando, 155
branches, palabra clave de plantilla, 239
branch, comando, 156, 157
bugzilla, extensión, 219–223, 325
bugzilla, gancho, 219, 220
bundle, comando, 229
changegroup, gancho, 194, 197, 227–229, 231
chmod, comando de sistema, 130
clone, comando, 21, 29, 124, 134, 152
    opción -r, 152
commit, comando, 30–33, 51, 90, 99, 104, 105, 155, 197, 211, 217, 304, 305, 310, 342, 345
    opción --addremove, 310
    opción -A, 105
    opción -l, 217
    opción -u, 31
commit, gancho, 194, 197, 202, 203, 228, 230, 232
config, comando, 195
convert, comando (extensión conver), 15
convert, extensión, 15
conver, extensión
    comando convert, 15
copy, comando, 96, 105–109, 167, 346
    opción --after, 108
cp, comando, 108
cp, comando de sistema, 107, 108
date, filtro de plantilla, 243
date, palabra clave de plantilla, 239, 243, 244
    filtro age, 243
    filtro date, 243
    filtro hgdate, 243
    filtro isodate, 242, 243, 248
    filtro rfc822date, 244
    filtro shortdate, 244
desc, palabra clave de plantilla, 239, 248
diffstat, comando
    opción -p, 305
diffstat, comando de sistema, 305, 307, 308, 332
diff, comando, 30, 33, 272, 297, 321, 325, 328, 329, 335–339, 342
    opción --exclude, 336
    opción --git, 335, 336, 338
    opción --ignore-all-space, 337
    opción --ignore-blank-lines, 335
    opción --ignore-space-change, 336
    opción --include, 336
    opción --nodates, 335
    opción --rev, 337
    opción --show-function, 336
    opción --text, 336
    opción -a, 335, 336, 339
    opción -B, 335
    opción -b, 336
    opción -C, 329
    opción -c, 329
    opción -g, 336
    opción -I, 336
    opción -N, 296
    opción -p, 336
    opción -r, 296, 337, 338
    opción -w, 337
    opción -X, 336
diff, comando de sistema, 255, 257, 259, 260, 296, 328, 329
domain, filtro de plantilla, 243
email, comando (extensión patchbomb), 330, 331
email, comando (extensión patchbomb)
    opción --plain, 331
    opción -a, 331
    opción -b, 331
    opción -d, 332
    opción -f, 331
    opción -m, 331
    opción -n, 330
    opción -s, 331
email, filtro de plantilla, 243
escape, filtro de plantilla, 243
export, comando, 183
extdiff, comando (extensión extdiff), 322, 328, 329
extdiff, comando (extensión extdiff)
    opción -o, 329
    opción -p, 329
extdiff, extensión, 321, 322, 328, 329
    comando extdiff, 322, 328, 329
extdiff, extensión
    comando extdiff
        opción-o, 329
        opción-p, 329
fetch, comando, 61
fetch, comando (extensión fetch), 325
fetch, extensión, 61, 325
    comando fetch, 325
ficheros, palabra clave de plantilla, 243
file_adds, palabra clave de plantilla, 239
file_dels, palabra clave de plantilla, 239
files, palabra clave de plantilla, 239
fill68, filtro de plantilla, 243
fill76, filtro de plantilla, 243
filterdiff, comando
    opción --files, 311
    opción --hunks, 311
    opción -i, 310
    opción -x, 310
filterdiff, comando de sistema, 307, 308, 310, 311
firstline, filtro de plantilla, 243
foo, comando, 158
git, comando de sistema, 122, 336, 338
grep, comando de sistema, 188, 190
guards, fichero, 317
header, palabra clave de plantilla, 252
heads, comando, 48
help, comando, 20, 263, 342
hg-interdiff, fichero, 321, 322
hgdate, filtro de plantilla, 243
hgext, extensión, 322
hgmerge, comando de sistema, 55, 59, 232
hgrc, fichero
    sección acl.allow, 218
    sección acl.deny, 218
    sección acl, 217
        entrada bundle, 218
        entrada pull, 218
        entrada push, 218
        entrada serve, 218
        entrada sources, 217, 218
    sección bugzilla, 220, 221
        entrada db, 220
        entrada host, 220
        entrada notify, 220
        entrada password, 220
        entrada usermap, 221
        entrada user, 220
        entrada version, 220
    sección diff
        entrada showfunc, 336
    sección extdiff, 329
    sección extensions, 61, 321, 328
    sección hooks, 200
    sección notify, 223
        entrada config, 223
        entrada maxdiff, 224
        entrada sources, 224
        entrada strip, 224
        entrada template, 224
        entrada test, 223, 225
    sección ui
        entrada username, 31
        entrada verbose, 206
    sección usermap, 220–223
    sección web, 133–136, 221, 224
        entrada accesslog, 135
        entrada address, 135
        entrada allow_archive, 133, 134
        entrada allowpull, 134
        entrada baseurl, 221, 224
        entrada contact, 134
        entrada description, 135
        entrada errorlog, 135
        entrada ipv6, 135
        entrada maxchanges, 134
        entrada maxfiles, 134
        entrada motd, 135
        entrada name, 135
        entrada port, 135
        entrada stripes, 134
        entrada style, 134, 135
        entrada templates, 135
hgrc, fichero de configuración, 129, 133–136, 160, 195, 196, 200, 207, 218–221, 223, 237, 321, 322, 327–330
hgweb.cgi, fichero, 130–132, 134, 136
hgweb.config, fichero, 132, 135
hgwebdir.cgi, fichero, 132–135
hg, comando de sistema, 128
import, comando, 297, 339
incoming, comando, 34, 124, 195, 237
incoming, gancho, 194, 219, 227–229, 231
init, comando, 305, 342
inotify, extensión, 325–327
    comando inserve, 327
inserve, comando (extensión inotify), 327
interdiff, comando de sistema, 321, 322, 329
isodate, filtro de plantilla, 242, 243, 248
kdiff3, comando de sistema, 55, 57, 329
locate, comando, 310
log, comando, 22, 23, 26, 27, 29, 32, 33, 150, 151, 156, 157, 169, 237, 238, 257, 304
    opción --patch, 28
    opción --rev, 26, 29
    opción --template, 238, 248
    opción -p, 28
    opción -r, 26, 29
lsdiff comando de sistema, 310
lsdiff, comando de sistema, 307
mercurial.localrepo, módulo
    clase localrepository, 208, 225
mercurial.node, módulo
    función bin, 226
mercurial.ui, módulo
    clase ui, 208, 225
merge, comando, 48, 61, 78, 90, 93, 145, 159, 304, 338
merge, comando de sistema, 59, 60
mpatch, comando de sistema, 298, 299
mq comando de sistema, 305
mq, comando de sistema, 305
mq, extensión, 327, 329
    comando qapplied, 281, 283, 287, 308, 342
    comando qcommit, 305, 342
    comando qdelete, 342
    comando qdiff, 342
    comando qfold, 310, 342, 343
    comando qguard, 315, 316
    comando qheader, 343
    comando qimport, 297, 343
    comando qinit, 263, 266, 304, 342, 343, 348
    comando qnew, 272, 278, 281, 293, 297, 343, 344
    comando qnext, 344
    comando qpop, 284, 287, 290, 296, 299, 304, 344, 345
    comando qprev, 345
    comando qpush, 287, 290, 296, 298–300, 304, 310, 316, 317, 327, 344, 345, 348
    comando qrefresh, 272, 275, 281, 296, 297, 299, 304, 308, 310, 344, 346
    comando qrename, 346
    comando qrestore, 346
    comando qsave, 300, 346
    comando qselect, 316
    comando qseries, 281, 283, 287, 300, 347
    comando qtop, 308, 347
    comando qunapplied, 347
mq, extensión
    comando qdel
        opción-f, 342
    comando qfold
        opción-e, 343
        opción-l, 343
        opción-m, 343
    comando qinit
        opción-c, 304, 305, 342, 343, 348
    comando qnew
        opción-f, 296, 344
        opción-m, 344
    comando qpop
        opción-a, 290, 299, 300, 305, 344
        opción-f, 296, 344
        opción-n, 300, 345
    comando qpush
        opción-a, 290, 299, 300, 305, 327, 345
        opción-l, 345
        opción-m, 300, 345
        opción-n, 345
    comando qrefresh
        opción-e, 346
        opción-l, 346
        opción-m, 346
    comando qsave
        opción-c, 300
        opción-e, 300
node, palabra clave de plantilla, 239
    filtro short, 244
notify, extensión, 223–225, 325
obfuscate, filtro de plantilla, 243
outgoing, comando, 37, 237
outgoing, gancho, 194, 195, 229, 230
pageant, comando de sistema, 126, 127
parents, comando, 36, 51, 78, 338
parents, palabra clave de plantilla, 239
patchbomb, extensión, 320, 330, 331
    comando email, 330, 331
patchbomb, extensión
    comando email
        opción--plain, 331
        opción-a, 331
        opción-b, 331
        opción-d, 332
        opción-f, 331
        opción-m, 331
        opción-n, 330
        opción-s, 331
patchutils, paquete, 308, 321
patch comando de sistema, 297
patch, comando
    opción --reverse, 183
    opción -p, 296
patch, comando de sistema, 183, 255, 257, 259, 260, 296–298, 339
perl, comando de sistema, 217
person, filtro de plantilla, 243
plink, comando de sistema, 126, 129
prechangegroup, gancho, 194, 228, 229, 231
precommit, gancho, 194, 211, 228–231
preoutgoing, gancho, 194, 197, 229, 230
pretag, gancho, 194, 230, 232
pretxnchangegroup, gancho, 160, 194, 197, 217, 228, 229, 231
pretxncommit, gancho, 194, 197, 203, 205, 208, 211, 214, 219, 228, 230, 231
preupdate, gancho, 194, 232, 233
pull, comando, 34–37, 45, 61, 90, 124, 134, 145, 157, 164, 195, 227, 231, 299, 300, 305
    opción -u, 35, 36
push, comando, 37, 145, 227–229, 231
puttygen, comando de sistema, 126
putty, comando de sistema, 127
qapplied, comando (extensión mq), 281, 283, 287, 308, 342
qcommit, comando (extensión mq), 305, 342
qdelete, comando (extensión mq), 342
qdel, comando (extensión mq)
    opción -f, 342
qdiff, comando (extensión mq), 342
qfold, comando, 343
qfold, comando (extensión mq), 310, 342, 343
qfold, comando (extensión mq)
    opción -e, 343
    opción -l, 343
    opción -m, 343
qguard, comando, 316
qguard, comando (extensión mq), 315, 316
qheader, comando (extensión mq), 343
qimport, comando (extensión mq), 297, 343
qinit, comando, 305, 342
qinit, comando (extensión mq), 263, 266, 304, 342, 343, 348
qinit, comando (extensión mq)
    opción -c, 304, 305, 342, 343, 348
qnew, comando, 296
qnew, comando (extensión mq), 272, 278, 281, 293, 297, 343, 344
qnew, comando (extensión mq)
    opción -f, 296, 344
    opción -m, 344
qnext, comando (extensión mq), 344
qpop, comando, 296, 299, 300, 305
qpop, comando (extensión mq), 284, 287, 290, 296, 299, 304, 344, 345
qpop, comando (extensión mq)
    opción -a, 290, 299, 300, 305, 344
    opción -f, 296, 344
    opción -n, 300, 345
qprev, comando (extensión mq), 345
qpush, comando, 299, 300, 305
qpush, comando (extensión mq), 287, 290, 296, 298–300, 304, 310, 316, 317, 327, 344, 345, 348
qpush, comando (extensión mq)
    opción -a, 290, 299, 300, 305, 327, 345
    opción -l, 345
    opción -m, 300, 345
    opción -n, 345
qrefresh, comando (extensión mq), 272, 275, 281, 296, 297, 299, 304, 308, 310, 344, 346
qrefresh, comando (extensión mq)
    opción -e, 346
    opción -l, 346
    opción -m, 346
qrename, comando (extensión mq), 346
qrestore, comando (extensión mq), 346
qsave, comando, 300
qsave, comando (extensión mq), 300, 346
qsave, comando (extensión mq)
    opción -c, 300
    opción -e, 300
qselect, comando (extensión mq), 316
qseries, comando (extensión mq), 281, 283, 287, 300, 347
qtop, comando (extensión mq), 308, 347
qunapplied, comando (extensión mq), 347
remove, comando, 96, 103, 104, 109, 139, 166, 167, 311, 346
    opción --after, 104
rename, comando, 96, 108, 109, 145, 168, 346
    opción --after, 109
revert, comando, 104, 112, 165–168, 183, 275
rev, palabra clave de plantilla, 239
rfc822date, filtro de plantilla, 244
rollback, comando, 163–165, 184
root, comando, 140
sed, comando de sistema, 29
series, fichero, 272, 300, 305, 316, 318, 319, 342–348
serve, comando, 115, 116, 123, 124, 134–136
    opción -p, 124
shortdate, filtro de plantilla, 244
short, filtro de plantilla, 244
ssh-add, comando de sistema, 126, 127
ssh-agent, comando de sistema, 126
ssh-keygen, comando de sistema, 126
ssh, comando
    opción -C, 129
ssh, comando de sistema, 94, 116, 125, 127–129
status, comando, 30, 33, 99, 103, 104, 106, 109, 140, 156, 163, 168, 183, 325, 326, 338, 345
    opción -C, 106, 109
status, fichero, 272, 300, 305, 343, 345, 348
strip, comando, 300, 347
    opción -b, 347
    opción -f, 347
    opción -n, 347
strip, filtro de plantilla, 244
sudo apt-get install mercurial-py25, comando de sistema, 19
sudo port install mercurial, comando de sistema, 19
sudo, comando de sistema, 222
tabindent, filtro de plantilla, 243, 244
tabindent, palabra clave de plantilla, 248
tags, comando, 149, 150, 152
tags, palabra clave de plantilla, 239
tag, comando, 117, 149, 151, 152
    opción -f, 151
    opción -l, 152
tag, gancho, 194, 230, 232
tar, comando de sistema, 134
tip, comando, 33, 35, 156, 237, 308
    opción -p, 308
transplant, extensión, 330
unbundle, comando, 227, 231
update, comando, 35, 36, 48, 61, 78, 118, 145, 157–159, 183, 300, 305
    opción -C, 157, 158, 300
update, gancho, 194, 232
urlescape, filtro de plantilla, 244
user, filtro de plantilla, 244
version, comando, 19, 128, 338
vim, comando de sistema, 330
wiggle, comando de sistema, 298, 299
zip, comando de sistema, 134

Base de datos de fallos de Mercurial
    http://www.selenic.com/mercurial/bts/issue29fallo  29, 111
    http://www.selenic.com/mercurial/bts/issue311fallo  311, 297

fichero de configuración
    Mercurial.ini (Windows), 126
    hgrc (Linux/Unix), 129, 133–136, 160, 195, 196, 200, 207, 218–221, 223, 237, 321, 322, 327–330
filtros de plantilla
    addbreaks, 243
    age, 243
    basename, 243
    date, 243
    domain, 243
    email, 243
    escape, 243
    fill68, 243
    fill76, 243
    firstline, 243
    hgdate, 243
    isodate, 242, 243, 248
    obfuscate, 243
    person, 243
    rfc822date, 244
    shortdate, 244
    short, 244
    strip, 244
    tabindent, 243, 244
    urlescape, 244
    user, 244

ganchos
    acl, 217
    bugzilla, 219, 220
    changegroup, 194, 197, 227–229, 231
    commit, 194, 197, 202, 203, 228, 230, 232
    incoming, 194, 219, 227–229, 231
    outgoing, 194, 195, 229, 230
    prechangegroup, 194, 228, 229, 231
    precommit, 194, 211, 228–231
    preoutgoing, 194, 197, 229, 230
    pretag, 194, 230, 232
    pretxnchangegroup, 160, 194, 197, 217, 228, 229, 231
    pretxncommit, 194, 197, 203, 205, 208, 211, 214, 219, 228, 230, 231
    preupdate, 194, 232, 233
    tag, 194, 230, 232
    update, 194, 232

opciones globales
    opción --debug, 128, 218
    opción --exclude, 143
    opción --include, 143
    opción --quiet, 29
    opción --verbose, 20, 27, 29
    opción -I, 143, 144
    opción -q, 29, 141
    opción -v, 20, 27, 29, 124, 141, 206
    opción -X, 143, 144

palabras clave de plantilla
    author, 239, 243, 244
    branches, 239
    date, 239, 243, 244
    desc, 239, 248
    ficheros, 243
    file_adds, 239
    file_dels, 239
    files, 239
    header, 252
    node, 239
    parents, 239
    rev, 239
    tabindent, 248
    tags, 239

tags
    tip, 227, 231
    special tag names
        qbase, 300
        qtip, 300

variables de entorno
    EMAIL, 31
    HGMERGE, 55, 59
    HGUSER, 31
    HG_NODE, 200, 226
    HG_PARENT1, 226
    HG_PARENT2, 226
    HG_SOURCE, 226
    HG_URL, 226, 227
    PATH, 128
    PYTHONPATH, 128, 131, 207, 351