Mercurial > hgbook
view es/mq-collab.tex @ 592:26fb9b724588
translated some paragraphs to spanish from mq-ref.tex
author | Igor Támara <igor@tamarapatino.org> |
---|---|
date | Wed, 07 Jan 2009 23:47:09 -0500 |
parents | 795f2964e104 |
children | f89480678965 |
line wrap: on
line source
\chapter{Usos avanzados de las Colas de Mercurial} \label{chap:mq-collab} 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. \section{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 %TODO no encontré una traducción adecuada para "release". Por eso el %cambio versiones frecuentemente. Esto significa que una versión del controlador que funciona bien con una versión particular del kernel ni siquiera \emph{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. \begin{itemize} \item Un objetivo es el árbol de desarrollo principal del kernel de Linux. En este caso el mantenimiento del código es compartido parcialmente por otros desarrolladores en la comunidad del kernel, %TODO drive-by. quienes hacen modificaciones ``de-afán'' al controlador a medida que desarrollan y refinan subsistemas en el kernel. %TODO backport \item También mantenemos algunos ``backports'' para versiones antiguas del kernel de Linux, para dar soporte a las necesidades de los clientes que están corriendo versiones antiguas de Linux que no incorporan nuestros controladores. (Hacer el \emph{backport} de un pedazo de código es modificarlo para que trabaje en una versión de su entorno objetivo anterior a aquella para la cual fue escrito.) \item Finalmente, nosotros liberamos nuestro software de acuerdo a un cronograma que no necesariamente está alineado con el que usan los distribuidores de Linux y los desarrolladores del kernel, así que podemos entregar nuevas características a los clientes sin forzarlos a actualizar kernels completos o distribuciones. \end{itemize} \subsection{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. %TODO canónica? 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 %TODO upstream. no no es río arriba 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. \section{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 \texttt{guards} de Quilt) que hace precisamente ésto. Para empezar, creemos un repositorio sencillo para experimentar. \interaction{mq.guards.init} 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 \emph{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 \emph{positivo} (``aplique el parche si este guardia es seleccionado'') o \emph{negativo} (``omita este parche si este guardia es seleccionado''). Un parche sin guardias siempre es aplicado. \section{Controlar los guardias de un parche} %TODO tal vez no decir determinar, sino definir? El comando \hgxcmd{mq}{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. \interaction{mq.guards.qguard} Para poner un guardia positivo en un parche, prefije el nombre del guardia con un ``\texttt{+}''. \interaction{mq.guards.qguard.pos} Para poner un guardia negativo en un parche, prefije el nombre del guardia con un ``\texttt{-}''. \interaction{mq.guards.qguard.neg} \begin{note} El comando \hgxcmd{mq}{qguard} \emph{pone} los guardias en un parche; no los \emph{modifica}. Esto significa que si usted ejecuta \hgcmdargs{qguard}{+a +b} sobre un parche, y luego \hgcmdargs{qguard}{+c} en el mismo parche, el único guardia sobre el parche después del comando será \texttt{+c}. \end{note} Mercurial almacena los guardias en el fichero \sfilename{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 \hgxcmd{mq}{qguard} si no lo desea; está bien simplemente editar el fichero \sfilename{series}) \interaction{mq.guards.series} \section{Selecccionar los guardias a usar} %TODO tal vez no decir determinar, sino definir? El comando \hgxcmd{mq}{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 \hgxcmd{mq}{qpush}. No tiene ningún otro efecto; en particular, no hace nada a los parches que ya han sido aplicados. Sin argumentos, el comando \hgxcmd{mq}{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. \interaction{mq.guards.qselect.foo} Si está interesado, los guardias seleccionados actualmente están almacenados en el fichero \sfilename{guards}. \interaction{mq.guards.qselect.cat} Podemos ver el efecto que tienen los guardias seleccionados cuando ejecutamos \hgxcmd{mq}{qpush}. \interaction{mq.guards.qselect.qpush} Un guardia no puede empezar con un caracter ``\texttt{+}'' o ``\texttt{-}''. 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á: \interaction{mq.guards.qselect.error} Cambiar los guardias seleccionados cambia los parches que son aplicados. \interaction{mq.guards.qselect.quux} Usted puede ver en el ejemplo de abajo que los guardias negativos tienen precedencia sobre los guardias positivos. \interaction{mq.guards.qselect.foobar} \section{MQ's rules for applying patches} The rules that MQ uses when deciding whether to apply a patch are as follows. \begin{itemize} \item A patch that has no guards is always applied. \item If the patch has any negative guard that matches any currently selected guard, the patch is skipped. \item If the patch has any positive guard that matches any currently selected guard, the patch is applied. \item If the patch has positive or negative guards, but none matches any currently selected guard, the patch is skipped. \end{itemize} \section{Trimming the work environment} In working on the device driver I mentioned earlier, I don't apply the patches to a normal Linux kernel tree. Instead, I use a repository that contains only a snapshot of the source files and headers that are relevant to Infiniband development. This repository is~1\% the size of a kernel repository, so it's easier to work with. I then choose a ``base'' version on top of which the patches are applied. This is a snapshot of the Linux kernel tree as of a revision of my choosing. When I take the snapshot, I record the changeset ID from the kernel repository in the commit message. Since the snapshot preserves the ``shape'' and content of the relevant parts of the kernel tree, I can apply my patches on top of either my tiny repository or a normal kernel tree. Normally, the base tree atop which the patches apply should be a snapshot of a very recent upstream tree. This best facilitates the development of patches that can easily be submitted upstream with few or no modifications. \section{Dividing up the \sfilename{series} file} I categorise the patches in the \sfilename{series} file into a number of logical groups. Each section of like patches begins with a block of comments that describes the purpose of the patches that follow. The sequence of patch groups that I maintain follows. The ordering of these groups is important; I'll describe why after I introduce the groups. \begin{itemize} \item The ``accepted'' group. Patches that the development team has submitted to the maintainer of the Infiniband subsystem, and which he has accepted, but which are not present in the snapshot that the tiny repository is based on. These are ``read only'' patches, present only to transform the tree into a similar state as it is in the upstream maintainer's repository. \item The ``rework'' group. Patches that I have submitted, but that the upstream maintainer has requested modifications to before he will accept them. \item The ``pending'' group. Patches that I have not yet submitted to the upstream maintainer, but which we have finished working on. These will be ``read only'' for a while. If the upstream maintainer accepts them upon submission, I'll move them to the end of the ``accepted'' group. If he requests that I modify any, I'll move them to the beginning of the ``rework'' group. \item The ``in progress'' group. Patches that are actively being developed, and should not be submitted anywhere yet. \item The ``backport'' group. Patches that adapt the source tree to older versions of the kernel tree. \item The ``do not ship'' group. Patches that for some reason should never be submitted upstream. For example, one such patch might change embedded driver identification strings to make it easier to distinguish, in the field, between an out-of-tree version of the driver and a version shipped by a distribution vendor. \end{itemize} Now to return to the reasons for ordering groups of patches in this way. We would like the lowest patches in the stack to be as stable as possible, so that we will not need to rework higher patches due to changes in context. Putting patches that will never be changed first in the \sfilename{series} file serves this purpose. We would also like the patches that we know we'll need to modify to be applied on top of a source tree that resembles the upstream tree as closely as possible. This is why we keep accepted patches around for a while. The ``backport'' and ``do not ship'' patches float at the end of the \sfilename{series} file. The backport patches must be applied on top of all other patches, and the ``do not ship'' patches might as well stay out of harm's way. \section{Maintaining the patch series} In my work, I use a number of guards to control which patches are to be applied. \begin{itemize} \item ``Accepted'' patches are guarded with \texttt{accepted}. I enable this guard most of the time. When I'm applying the patches on top of a tree where the patches are already present, I can turn this patch off, and the patches that follow it will apply cleanly. \item Patches that are ``finished'', but not yet submitted, have no guards. If I'm applying the patch stack to a copy of the upstream tree, I don't need to enable any guards in order to get a reasonably safe source tree. \item Those patches that need reworking before being resubmitted are guarded with \texttt{rework}. \item For those patches that are still under development, I use \texttt{devel}. \item A backport patch may have several guards, one for each version of the kernel to which it applies. For example, a patch that backports a piece of code to~2.6.9 will have a~\texttt{2.6.9} guard. \end{itemize} This variety of guards gives me considerable flexibility in qdetermining what kind of source tree I want to end up with. For most situations, the selection of appropriate guards is automated during the build process, but I can manually tune the guards to use for less common circumstances. \subsection{The art of writing backport patches} Using MQ, writing a backport patch is a simple process. All such a patch has to do is modify a piece of code that uses a kernel feature not present in the older version of the kernel, so that the driver continues to work correctly under that older version. A useful goal when writing a good backport patch is to make your code look as if it was written for the older version of the kernel you're targeting. The less obtrusive the patch, the easier it will be to understand and maintain. If you're writing a collection of backport patches to avoid the ``rat's nest'' effect of lots of \texttt{\#ifdef}s (hunks of source code that are only used conditionally) in your code, don't introduce version-dependent \texttt{\#ifdef}s into the patches. Instead, write several patches, each of which makes unconditional changes, and control their application using guards. There are two reasons to divide backport patches into a distinct group, away from the ``regular'' patches whose effects they modify. The first is that intermingling the two makes it more difficult to use a tool like the \hgext{patchbomb} extension to automate the process of submitting the patches to an upstream maintainer. The second is that a backport patch could perturb the context in which a subsequent regular patch is applied, making it impossible to apply the regular patch cleanly \emph{without} the earlier backport patch already being applied. \section{Useful tips for developing with MQ} \subsection{Organising patches in directories} If you're working on a substantial project with MQ, it's not difficult to accumulate a large number of patches. For example, I have one patch repository that contains over 250 patches. If you can group these patches into separate logical categories, you can if you like store them in different directories; MQ has no problems with patch names that contain path separators. \subsection{Viewing the history of a patch} \label{mq-collab:tips:interdiff} If you're developing a set of patches over a long time, it's a good idea to maintain them in a repository, as discussed in section~\ref{sec:mq:repo}. If you do so, you'll quickly discover that using the \hgcmd{diff} command to look at the history of changes to a patch is unworkable. This is in part because you're looking at the second derivative of the real code (a diff of a diff), but also because MQ adds noise to the process by modifying time stamps and directory names when it updates a patch. However, you can use the \hgext{extdiff} extension, which is bundled with Mercurial, to turn a diff of two versions of a patch into something readable. To do this, you will need a third-party package called \package{patchutils}~\cite{web:patchutils}. This provides a command named \command{interdiff}, which shows the differences between two diffs as a diff. Used on two versions of the same diff, it generates a diff that represents the diff from the first to the second version. You can enable the \hgext{extdiff} extension in the usual way, by adding a line to the \rcsection{extensions} section of your \hgrc. \begin{codesample2} [extensions] extdiff = \end{codesample2} The \command{interdiff} command expects to be passed the names of two files, but the \hgext{extdiff} extension passes the program it runs a pair of directories, each of which can contain an arbitrary number of files. We thus need a small program that will run \command{interdiff} on each pair of files in these two directories. This program is available as \sfilename{hg-interdiff} in the \dirname{examples} directory of the source code repository that accompanies this book. \excode{hg-interdiff} With the \sfilename{hg-interdiff} program in your shell's search path, you can run it as follows, from inside an MQ patch directory: \begin{codesample2} hg extdiff -p hg-interdiff -r A:B my-change.patch \end{codesample2} Since you'll probably want to use this long-winded command a lot, you can get \hgext{hgext} to make it available as a normal Mercurial command, again by editing your \hgrc. \begin{codesample2} [extdiff] cmd.interdiff = hg-interdiff \end{codesample2} This directs \hgext{hgext} to make an \texttt{interdiff} command available, so you can now shorten the previous invocation of \hgxcmd{extdiff}{extdiff} to something a little more wieldy. \begin{codesample2} hg interdiff -r A:B my-change.patch \end{codesample2} \begin{note} The \command{interdiff} command works well only if the underlying files against which versions of a patch are generated remain the same. If you create a patch, modify the underlying files, and then regenerate the patch, \command{interdiff} may not produce useful output. \end{note} The \hgext{extdiff} extension is useful for more than merely improving the presentation of MQ~patches. To read more about it, go to section~\ref{sec:hgext:extdiff}. %%% Local Variables: %%% mode: latex %%% TeX-master: "00book" %%% End: