Mercurial > hgbook
changeset 654:b08f6a61bf15
Merge
author | Bryan O'Sullivan <bos@serpentine.com> |
---|---|
date | Mon, 09 Feb 2009 22:59:50 -0800 |
parents | 863a82f13901 (diff) 91adcea08b33 (current diff) |
children | dbb4c40e2609 |
files | en/ch09-undo.tex en/examples/bisect |
diffstat | 173 files changed, 30901 insertions(+), 9824 deletions(-) [+] |
line wrap: on
line diff
--- a/en/00book.tex Wed Jan 21 14:16:38 2009 +0100 +++ b/en/00book.tex Mon Feb 09 22:59:50 2009 -0800 @@ -40,27 +40,27 @@ \pagenumbering{arabic} -\include{preface} -\include{intro} -\include{tour-basic} -\include{tour-merge} -\include{concepts} -\include{daily} -\include{collab} -\include{filenames} -\include{branch} -\include{undo} -\include{hook} -\include{template} -\include{mq} -\include{mq-collab} -\include{hgext} +\include{ch00-preface} +\include{ch01-intro} +\include{ch02-tour-basic} +\include{ch03-tour-merge} +\include{ch04-concepts} +\include{ch05-daily} +\include{ch06-collab} +\include{ch07-filenames} +\include{ch08-branch} +\include{ch09-undo} +\include{ch10-hook} +\include{ch11-template} +\include{ch12-mq} +\include{ch13-mq-collab} +\include{ch14-hgext} \appendix -\include{cmdref} -\include{mq-ref} -\include{srcinstall} -\include{license} +\include{appA-cmdref} +\include{appB-mq-ref} +\include{appC-srcinstall} +\include{appD-license} \addcontentsline{toc}{chapter}{Bibliography} \bibliographystyle{alpha} \bibliography{99book}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/00book.xml Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- vim: set filetype=docbkxml shiftwidth=2 autoindent expandtab tw=77 : --> +<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" + "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" +[ +<!-- Below are references to files in this directory. --> + +<!-- Chapters. --> + +<!ENTITY ch01 SYSTEM "ch01-intro.xml"> +<!ENTITY ch02 SYSTEM "ch02-tour-basic.xml"> + +<!-- Include our standard shortcuts. --> + +<!ENTITY % SHORTCUTS SYSTEM "book-shortcuts.xml"> +%SHORTCUTS; +]> + +<book id="hg"> + <title>Mercurial: The Definitive Guide</title> + <bookinfo> + <authorgroup> + <author> + <firstname>Bryan</firstname> + <surname>O'Sullivan</surname> + </author> + </authorgroup> + + <editor> + <firstname>Mike</firstname> + <surname>Loukides</surname> + </editor> + + <copyright> + <year>2007</year> + <year>2008</year> + <holder>Bryan O'Sullivan</holder> + </copyright> + </bookinfo> + + &ch01; + &ch02; +</book>
--- a/en/99defs.tex Wed Jan 21 14:16:38 2009 +0100 +++ b/en/99defs.tex Mon Feb 09 22:59:50 2009 -0800 @@ -136,6 +136,10 @@ % Reference entry for a command option with only long form. \newcommand{\loptref}[2]{\subsubsection{\hgopt{#1}{--#2} option}} +% command to generate a footnote to be used as a translator's note +\newcommand{\ndt}[1]{\footnote{\textbf{N. del T.} #1}} + + %%% Local Variables: %%% mode: latex %%% TeX-master: "00book"
--- a/en/Makefile Wed Jan 21 14:16:38 2009 +0100 +++ b/en/Makefile Mon Feb 09 22:59:50 2009 -0800 @@ -4,26 +4,8 @@ 00book.tex \ 99book.bib \ 99defs.tex \ - build_id.tex \ - branch.tex \ - cmdref.tex \ - collab.tex \ - concepts.tex \ - daily.tex \ - filenames.tex \ - hg_id.tex \ - hgext.tex \ - hook.tex \ - intro.tex \ - mq.tex \ - mq-collab.tex \ - mq-ref.tex \ - preface.tex \ - srcinstall.tex \ - template.tex \ - tour-basic.tex \ - tour-merge.tex \ - undo.tex + app*.tex \ + ch*.tex image-sources := \ feature-branches.dot \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/appA-cmdref.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,175 @@ +\chapter{Command reference} +\label{cmdref} + +\cmdref{add}{add files at the next commit} +\optref{add}{I}{include} +\optref{add}{X}{exclude} +\optref{add}{n}{dry-run} + +\cmdref{diff}{print changes in history or working directory} + +Show differences between revisions for the specified files or +directories, using the unified diff format. For a description of the +unified diff format, see section~\ref{sec:mq:patch}. + +By default, this command does not print diffs for files that Mercurial +considers to contain binary data. To control this behaviour, see the +\hgopt{diff}{-a} and \hgopt{diff}{--git} options. + +\subsection{Options} + +\loptref{diff}{nodates} + +Omit date and time information when printing diff headers. + +\optref{diff}{B}{ignore-blank-lines} + +Do not print changes that only insert or delete blank lines. A line +that contains only whitespace is not considered blank. + +\optref{diff}{I}{include} + +Include files and directories whose names match the given patterns. + +\optref{diff}{X}{exclude} + +Exclude files and directories whose names match the given patterns. + +\optref{diff}{a}{text} + +If this option is not specified, \hgcmd{diff} will refuse to print +diffs for files that it detects as binary. Specifying \hgopt{diff}{-a} +forces \hgcmd{diff} to treat all files as text, and generate diffs for +all of them. + +This option is useful for files that are ``mostly text'' but have a +few embedded NUL characters. If you use it on files that contain a +lot of binary data, its output will be incomprehensible. + +\optref{diff}{b}{ignore-space-change} + +Do not print a line if the only change to that line is in the amount +of white space it contains. + +\optref{diff}{g}{git} + +Print \command{git}-compatible diffs. XXX reference a format +description. + +\optref{diff}{p}{show-function} + +Display the name of the enclosing function in a hunk header, using a +simple heuristic. This functionality is enabled by default, so the +\hgopt{diff}{-p} option has no effect unless you change the value of +the \rcitem{diff}{showfunc} config item, as in the following example. +\interaction{cmdref.diff-p} + +\optref{diff}{r}{rev} + +Specify one or more revisions to compare. The \hgcmd{diff} command +accepts up to two \hgopt{diff}{-r} options to specify the revisions to +compare. + +\begin{enumerate} +\setcounter{enumi}{0} +\item Display the differences between the parent revision of the + working directory and the working directory. +\item Display the differences between the specified changeset and the + working directory. +\item Display the differences between the two specified changesets. +\end{enumerate} + +You can specify two revisions using either two \hgopt{diff}{-r} +options or revision range notation. For example, the two revision +specifications below are equivalent. +\begin{codesample2} + hg diff -r 10 -r 20 + hg diff -r10:20 +\end{codesample2} + +When you provide two revisions, Mercurial treats the order of those +revisions as significant. Thus, \hgcmdargs{diff}{-r10:20} will +produce a diff that will transform files from their contents as of +revision~10 to their contents as of revision~20, while +\hgcmdargs{diff}{-r20:10} means the opposite: the diff that will +transform files from their revision~20 contents to their revision~10 +contents. You cannot reverse the ordering in this way if you are +diffing against the working directory. + +\optref{diff}{w}{ignore-all-space} + +\cmdref{version}{print version and copyright information} + +This command displays the version of Mercurial you are running, and +its copyright license. There are four kinds of version string that +you may see. +\begin{itemize} +\item The string ``\texttt{unknown}''. This version of Mercurial was + not built in a Mercurial repository, and cannot determine its own + version. +\item A short numeric string, such as ``\texttt{1.1}''. This is a + build of a revision of Mercurial that was identified by a specific + tag in the repository where it was built. (This doesn't necessarily + mean that you're running an official release; someone else could + have added that tag to any revision in the repository where they + built Mercurial.) +\item A hexadecimal string, such as ``\texttt{875489e31abe}''. This + is a build of the given revision of Mercurial. +\item A hexadecimal string followed by a date, such as + ``\texttt{875489e31abe+20070205}''. This is a build of the given + revision of Mercurial, where the build repository contained some + local changes that had not been committed. +\end{itemize} + +\subsection{Tips and tricks} + +\subsubsection{Why do the results of \hgcmd{diff} and \hgcmd{status} differ?} +\label{cmdref:diff-vs-status} + +When you run the \hgcmd{status} command, you'll see a list of files +that Mercurial will record changes for the next time you perform a +commit. If you run the \hgcmd{diff} command, you may notice that it +prints diffs for only a \emph{subset} of the files that \hgcmd{status} +listed. There are two possible reasons for this. + +The first is that \hgcmd{status} prints some kinds of modifications +that \hgcmd{diff} doesn't normally display. The \hgcmd{diff} command +normally outputs unified diffs, which don't have the ability to +represent some changes that Mercurial can track. Most notably, +traditional diffs can't represent a change in whether or not a file is +executable, but Mercurial records this information. + +If you use the \hgopt{diff}{--git} option to \hgcmd{diff}, it will +display \command{git}-compatible diffs that \emph{can} display this +extra information. + +The second possible reason that \hgcmd{diff} might be printing diffs +for a subset of the files displayed by \hgcmd{status} is that if you +invoke it without any arguments, \hgcmd{diff} prints diffs against the +first parent of the working directory. If you have run \hgcmd{merge} +to merge two changesets, but you haven't yet committed the results of +the merge, your working directory has two parents (use \hgcmd{parents} +to see them). While \hgcmd{status} prints modifications relative to +\emph{both} parents after an uncommitted merge, \hgcmd{diff} still +operates relative only to the first parent. You can get it to print +diffs relative to the second parent by specifying that parent with the +\hgopt{diff}{-r} option. There is no way to print diffs relative to +both parents. + +\subsubsection{Generating safe binary diffs} + +If you use the \hgopt{diff}{-a} option to force Mercurial to print +diffs of files that are either ``mostly text'' or contain lots of +binary data, those diffs cannot subsequently be applied by either +Mercurial's \hgcmd{import} command or the system's \command{patch} +command. + +If you want to generate a diff of a binary file that is safe to use as +input for \hgcmd{import}, use the \hgcmd{diff}{--git} option when you +generate the patch. The system \command{patch} command cannot handle +binary patches at all. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/appB-mq-ref.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,349 @@ +\chapter{Mercurial Queues reference} +\label{chap:mqref} + +\section{MQ command reference} +\label{sec:mqref:cmdref} + +For an overview of the commands provided by MQ, use the command +\hgcmdargs{help}{mq}. + +\subsection{\hgxcmd{mq}{qapplied}---print applied patches} + +The \hgxcmd{mq}{qapplied} command prints the current stack of applied +patches. Patches are printed in oldest-to-newest order, so the last +patch in the list is the ``top'' patch. + +\subsection{\hgxcmd{mq}{qcommit}---commit changes in the queue repository} + +The \hgxcmd{mq}{qcommit} command commits any outstanding changes in the +\sdirname{.hg/patches} repository. This command only works if the +\sdirname{.hg/patches} directory is a repository, i.e.~you created the +directory using \hgcmdargs{qinit}{\hgxopt{mq}{qinit}{-c}} or ran +\hgcmd{init} in the directory after running \hgxcmd{mq}{qinit}. + +This command is shorthand for \hgcmdargs{commit}{--cwd .hg/patches}. + +\subsection{\hgxcmd{mq}{qdelete}---delete a patch from the + \sfilename{series} file} + +The \hgxcmd{mq}{qdelete} command removes the entry for a patch from the +\sfilename{series} file in the \sdirname{.hg/patches} directory. It +does not pop the patch if the patch is already applied. By default, +it does not delete the patch file; use the \hgxopt{mq}{qdel}{-f} option to +do that. + +Options: +\begin{itemize} +\item[\hgxopt{mq}{qdel}{-f}] Delete the patch file. +\end{itemize} + +\subsection{\hgxcmd{mq}{qdiff}---print a diff of the topmost applied patch} + +The \hgxcmd{mq}{qdiff} command prints a diff of the topmost applied patch. +It is equivalent to \hgcmdargs{diff}{-r-2:-1}. + +\subsection{\hgxcmd{mq}{qfold}---merge (``fold'') several patches into one} + +The \hgxcmd{mq}{qfold} command merges multiple patches into the topmost +applied patch, so that the topmost applied patch makes the union of +all of the changes in the patches in question. + +The patches to fold must not be applied; \hgxcmd{mq}{qfold} will exit with +an error if any is. The order in which patches are folded is +significant; \hgcmdargs{qfold}{a b} means ``apply the current topmost +patch, followed by \texttt{a}, followed by \texttt{b}''. + +The comments from the folded patches are appended to the comments of +the destination patch, with each block of comments separated by three +asterisk (``\texttt{*}'') characters. Use the \hgxopt{mq}{qfold}{-e} +option to edit the commit message for the combined patch/changeset +after the folding has completed. + +Options: +\begin{itemize} +\item[\hgxopt{mq}{qfold}{-e}] Edit the commit message and patch description + for the newly folded patch. +\item[\hgxopt{mq}{qfold}{-l}] Use the contents of the given file as the new + commit message and patch description for the folded patch. +\item[\hgxopt{mq}{qfold}{-m}] Use the given text as the new commit message + and patch description for the folded patch. +\end{itemize} + +\subsection{\hgxcmd{mq}{qheader}---display the header/description of a patch} + +The \hgxcmd{mq}{qheader} command prints the header, or description, of a +patch. By default, it prints the header of the topmost applied patch. +Given an argument, it prints the header of the named patch. + +\subsection{\hgxcmd{mq}{qimport}---import a third-party patch into the queue} + +The \hgxcmd{mq}{qimport} command adds an entry for an external patch to the +\sfilename{series} file, and copies the patch into the +\sdirname{.hg/patches} directory. It adds the entry immediately after +the topmost applied patch, but does not push the patch. + +If the \sdirname{.hg/patches} directory is a repository, +\hgxcmd{mq}{qimport} automatically does an \hgcmd{add} of the imported +patch. + +\subsection{\hgxcmd{mq}{qinit}---prepare a repository to work with MQ} + +The \hgxcmd{mq}{qinit} command prepares a repository to work with MQ. It +creates a directory called \sdirname{.hg/patches}. + +Options: +\begin{itemize} +\item[\hgxopt{mq}{qinit}{-c}] Create \sdirname{.hg/patches} as a repository + in its own right. Also creates a \sfilename{.hgignore} file that + will ignore the \sfilename{status} file. +\end{itemize} + +When the \sdirname{.hg/patches} directory is a repository, the +\hgxcmd{mq}{qimport} and \hgxcmd{mq}{qnew} commands automatically \hgcmd{add} +new patches. + +\subsection{\hgxcmd{mq}{qnew}---create a new patch} + +The \hgxcmd{mq}{qnew} command creates a new patch. It takes one mandatory +argument, the name to use for the patch file. The newly created patch +is created empty by default. It is added to the \sfilename{series} +file after the current topmost applied patch, and is immediately +pushed on top of that patch. + +If \hgxcmd{mq}{qnew} finds modified files in the working directory, it will +refuse to create a new patch unless the \hgxopt{mq}{qnew}{-f} option is +used (see below). This behaviour allows you to \hgxcmd{mq}{qrefresh} your +topmost applied patch before you apply a new patch on top of it. + +Options: +\begin{itemize} +\item[\hgxopt{mq}{qnew}{-f}] Create a new patch if the contents of the + working directory are modified. Any outstanding modifications are + added to the newly created patch, so after this command completes, + the working directory will no longer be modified. +\item[\hgxopt{mq}{qnew}{-m}] Use the given text as the commit message. + This text will be stored at the beginning of the patch file, before + the patch data. +\end{itemize} + +\subsection{\hgxcmd{mq}{qnext}---print the name of the next patch} + +The \hgxcmd{mq}{qnext} command prints the name name of the next patch in +the \sfilename{series} file after the topmost applied patch. This +patch will become the topmost applied patch if you run \hgxcmd{mq}{qpush}. + +\subsection{\hgxcmd{mq}{qpop}---pop patches off the stack} + +The \hgxcmd{mq}{qpop} command removes applied patches from the top of the +stack of applied patches. By default, it removes only one patch. + +This command removes the changesets that represent the popped patches +from the repository, and updates the working directory to undo the +effects of the patches. + +This command takes an optional argument, which it uses as the name or +index of the patch to pop to. If given a name, it will pop patches +until the named patch is the topmost applied patch. If given a +number, \hgxcmd{mq}{qpop} treats the number as an index into the entries in +the series file, counting from zero (empty lines and lines containing +only comments do not count). It pops patches until the patch +identified by the given index is the topmost applied patch. + +The \hgxcmd{mq}{qpop} command does not read or write patches or the +\sfilename{series} file. It is thus safe to \hgxcmd{mq}{qpop} a patch that +you have removed from the \sfilename{series} file, or a patch that you +have renamed or deleted entirely. In the latter two cases, use the +name of the patch as it was when you applied it. + +By default, the \hgxcmd{mq}{qpop} command will not pop any patches if the +working directory has been modified. You can override this behaviour +using the \hgxopt{mq}{qpop}{-f} option, which reverts all modifications in +the working directory. + +Options: +\begin{itemize} +\item[\hgxopt{mq}{qpop}{-a}] Pop all applied patches. This returns the + repository to its state before you applied any patches. +\item[\hgxopt{mq}{qpop}{-f}] Forcibly revert any modifications to the + working directory when popping. +\item[\hgxopt{mq}{qpop}{-n}] Pop a patch from the named queue. +\end{itemize} + +The \hgxcmd{mq}{qpop} command removes one line from the end of the +\sfilename{status} file for each patch that it pops. + +\subsection{\hgxcmd{mq}{qprev}---print the name of the previous patch} + +The \hgxcmd{mq}{qprev} command prints the name of the patch in the +\sfilename{series} file that comes before the topmost applied patch. +This will become the topmost applied patch if you run \hgxcmd{mq}{qpop}. + +\subsection{\hgxcmd{mq}{qpush}---push patches onto the stack} +\label{sec:mqref:cmd:qpush} + +The \hgxcmd{mq}{qpush} command adds patches onto the applied stack. By +default, it adds only one patch. + +This command creates a new changeset to represent each applied patch, +and updates the working directory to apply the effects of the patches. + +The default data used when creating a changeset are as follows: +\begin{itemize} +\item The commit date and time zone are the current date and time + zone. Because these data are used to compute the identity of a + changeset, this means that if you \hgxcmd{mq}{qpop} a patch and + \hgxcmd{mq}{qpush} it again, the changeset that you push will have a + different identity than the changeset you popped. +\item The author is the same as the default used by the \hgcmd{commit} + command. +\item The commit message is any text from the patch file that comes + before the first diff header. If there is no such text, a default + commit message is used that identifies the name of the patch. +\end{itemize} +If a patch contains a Mercurial patch header (XXX add link), the +information in the patch header overrides these defaults. + +Options: +\begin{itemize} +\item[\hgxopt{mq}{qpush}{-a}] Push all unapplied patches from the + \sfilename{series} file until there are none left to push. +\item[\hgxopt{mq}{qpush}{-l}] Add the name of the patch to the end + of the commit message. +\item[\hgxopt{mq}{qpush}{-m}] If a patch fails to apply cleanly, use the + entry for the patch in another saved queue to compute the parameters + for a three-way merge, and perform a three-way merge using the + normal Mercurial merge machinery. Use the resolution of the merge + as the new patch content. +\item[\hgxopt{mq}{qpush}{-n}] Use the named queue if merging while pushing. +\end{itemize} + +The \hgxcmd{mq}{qpush} command reads, but does not modify, the +\sfilename{series} file. It appends one line to the \hgcmd{status} +file for each patch that it pushes. + +\subsection{\hgxcmd{mq}{qrefresh}---update the topmost applied patch} + +The \hgxcmd{mq}{qrefresh} command updates the topmost applied patch. It +modifies the patch, removes the old changeset that represented the +patch, and creates a new changeset to represent the modified patch. + +The \hgxcmd{mq}{qrefresh} command looks for the following modifications: +\begin{itemize} +\item Changes to the commit message, i.e.~the text before the first + diff header in the patch file, are reflected in the new changeset + that represents the patch. +\item Modifications to tracked files in the working directory are + added to the patch. +\item Changes to the files tracked using \hgcmd{add}, \hgcmd{copy}, + \hgcmd{remove}, or \hgcmd{rename}. Added files and copy and rename + destinations are added to the patch, while removed files and rename + sources are removed. +\end{itemize} + +Even if \hgxcmd{mq}{qrefresh} detects no changes, it still recreates the +changeset that represents the patch. This causes the identity of the +changeset to differ from the previous changeset that identified the +patch. + +Options: +\begin{itemize} +\item[\hgxopt{mq}{qrefresh}{-e}] Modify the commit and patch description, + using the preferred text editor. +\item[\hgxopt{mq}{qrefresh}{-m}] Modify the commit message and patch + description, using the given text. +\item[\hgxopt{mq}{qrefresh}{-l}] Modify the commit message and patch + description, using text from the given file. +\end{itemize} + +\subsection{\hgxcmd{mq}{qrename}---rename a patch} + +The \hgxcmd{mq}{qrename} command renames a patch, and changes the entry for +the patch in the \sfilename{series} file. + +With a single argument, \hgxcmd{mq}{qrename} renames the topmost applied +patch. With two arguments, it renames its first argument to its +second. + +\subsection{\hgxcmd{mq}{qrestore}---restore saved queue state} + +XXX No idea what this does. + +\subsection{\hgxcmd{mq}{qsave}---save current queue state} + +XXX Likewise. + +\subsection{\hgxcmd{mq}{qseries}---print the entire patch series} + +The \hgxcmd{mq}{qseries} command prints the entire patch series from the +\sfilename{series} file. It prints only patch names, not empty lines +or comments. It prints in order from first to be applied to last. + +\subsection{\hgxcmd{mq}{qtop}---print the name of the current patch} + +The \hgxcmd{mq}{qtop} prints the name of the topmost currently applied +patch. + +\subsection{\hgxcmd{mq}{qunapplied}---print patches not yet applied} + +The \hgxcmd{mq}{qunapplied} command prints the names of patches from the +\sfilename{series} file that are not yet applied. It prints them in +order from the next patch that will be pushed to the last. + +\subsection{\hgcmd{strip}---remove a revision and descendants} + +The \hgcmd{strip} command removes a revision, and all of its +descendants, from the repository. It undoes the effects of the +removed revisions from the repository, and updates the working +directory to the first parent of the removed revision. + +The \hgcmd{strip} command saves a backup of the removed changesets in +a bundle, so that they can be reapplied if removed in error. + +Options: +\begin{itemize} +\item[\hgopt{strip}{-b}] Save unrelated changesets that are intermixed + with the stripped changesets in the backup bundle. +\item[\hgopt{strip}{-f}] If a branch has multiple heads, remove all + heads. XXX This should be renamed, and use \texttt{-f} to strip revs + when there are pending changes. +\item[\hgopt{strip}{-n}] Do not save a backup bundle. +\end{itemize} + +\section{MQ file reference} + +\subsection{The \sfilename{series} file} + +The \sfilename{series} file contains a list of the names of all +patches that MQ can apply. It is represented as a list of names, with +one name saved per line. Leading and trailing white space in each +line are ignored. + +Lines may contain comments. A comment begins with the ``\texttt{\#}'' +character, and extends to the end of the line. Empty lines, and lines +that contain only comments, are ignored. + +You will often need to edit the \sfilename{series} file by hand, hence +the support for comments and empty lines noted above. For example, +you can comment out a patch temporarily, and \hgxcmd{mq}{qpush} will skip +over that patch when applying patches. You can also change the order +in which patches are applied by reordering their entries in the +\sfilename{series} file. + +Placing the \sfilename{series} file under revision control is also +supported; it is a good idea to place all of the patches that it +refers to under revision control, as well. If you create a patch +directory using the \hgxopt{mq}{qinit}{-c} option to \hgxcmd{mq}{qinit}, this +will be done for you automatically. + +\subsection{The \sfilename{status} file} + +The \sfilename{status} file contains the names and changeset hashes of +all patches that MQ currently has applied. Unlike the +\sfilename{series} file, this file is not intended for editing. You +should not place this file under revision control, or modify it in any +way. It is used by MQ strictly for internal book-keeping. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/appC-srcinstall.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,53 @@ +\chapter{Installing Mercurial from source} +\label{chap:srcinstall} + +\section{On a Unix-like system} +\label{sec:srcinstall:unixlike} + +If you are using a Unix-like system that has a sufficiently recent +version of Python (2.3~or newer) available, it is easy to install +Mercurial from source. +\begin{enumerate} +\item Download a recent source tarball from + \url{http://www.selenic.com/mercurial/download}. +\item Unpack the tarball: + \begin{codesample4} + gzip -dc mercurial-\emph{version}.tar.gz | tar xf - + \end{codesample4} +\item Go into the source directory and run the installer script. This + will build Mercurial and install it in your home directory. + \begin{codesample4} + cd mercurial-\emph{version} + python setup.py install --force --home=\$HOME + \end{codesample4} +\end{enumerate} +Once the install finishes, Mercurial will be in the \texttt{bin} +subdirectory of your home directory. Don't forget to make sure that +this directory is present in your shell's search path. + +You will probably need to set the \envar{PYTHONPATH} environment +variable so that the Mercurial executable can find the rest of the +Mercurial packages. For example, on my laptop, I have set it to +\texttt{/home/bos/lib/python}. The exact path that you will need to +use depends on how Python was built for your system, but should be +easy to figure out. If you're uncertain, look through the output of +the installer script above, and see where the contents of the +\texttt{mercurial} directory were installed to. + +\section{On Windows} + +Building and installing Mercurial on Windows requires a variety of +tools, a fair amount of technical knowledge, and considerable +patience. I very much \emph{do not recommend} this route if you are a +``casual user''. Unless you intend to hack on Mercurial, I strongly +suggest that you use a binary package instead. + +If you are intent on building Mercurial from source on Windows, follow +the ``hard way'' directions on the Mercurial wiki at +\url{http://www.selenic.com/mercurial/wiki/index.cgi/WindowsInstall}, +and expect the process to involve a lot of fiddly work. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/appD-license.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,138 @@ +\chapter{Open Publication License} +\label{cha:opl} + +Version 1.0, 8 June 1999 + +\section{Requirements on both unmodified and modified versions} + +The Open Publication works may be reproduced and distributed in whole +or in part, in any medium physical or electronic, provided that the +terms of this license are adhered to, and that this license or an +incorporation of it by reference (with any options elected by the +author(s) and/or publisher) is displayed in the reproduction. + +Proper form for an incorporation by reference is as follows: + +\begin{quote} + Copyright (c) \emph{year} by \emph{author's name or designee}. This + material may be distributed only subject to the terms and conditions + set forth in the Open Publication License, v\emph{x.y} or later (the + latest version is presently available at + \url{http://www.opencontent.org/openpub/}). +\end{quote} + +The reference must be immediately followed with any options elected by +the author(s) and/or publisher of the document (see +section~\ref{sec:opl:options}). + +Commercial redistribution of Open Publication-licensed material is +permitted. + +Any publication in standard (paper) book form shall require the +citation of the original publisher and author. The publisher and +author's names shall appear on all outer surfaces of the book. On all +outer surfaces of the book the original publisher's name shall be as +large as the title of the work and cited as possessive with respect to +the title. + +\section{Copyright} + +The copyright to each Open Publication is owned by its author(s) or +designee. + +\section{Scope of license} + +The following license terms apply to all Open Publication works, +unless otherwise explicitly stated in the document. + +Mere aggregation of Open Publication works or a portion of an Open +Publication work with other works or programs on the same media shall +not cause this license to apply to those other works. The aggregate +work shall contain a notice specifying the inclusion of the Open +Publication material and appropriate copyright notice. + +\textbf{Severability}. If any part of this license is found to be +unenforceable in any jurisdiction, the remaining portions of the +license remain in force. + +\textbf{No warranty}. Open Publication works are licensed and provided +``as is'' without warranty of any kind, express or implied, including, +but not limited to, the implied warranties of merchantability and +fitness for a particular purpose or a warranty of non-infringement. + +\section{Requirements on modified works} + +All modified versions of documents covered by this license, including +translations, anthologies, compilations and partial documents, must +meet the following requirements: + +\begin{enumerate} +\item The modified version must be labeled as such. +\item The person making the modifications must be identified and the + modifications dated. +\item Acknowledgement of the original author and publisher if + applicable must be retained according to normal academic citation + practices. +\item The location of the original unmodified document must be + identified. +\item The original author's (or authors') name(s) may not be used to + assert or imply endorsement of the resulting document without the + original author's (or authors') permission. +\end{enumerate} + +\section{Good-practice recommendations} + +In addition to the requirements of this license, it is requested from +and strongly recommended of redistributors that: + +\begin{enumerate} +\item If you are distributing Open Publication works on hardcopy or + CD-ROM, you provide email notification to the authors of your intent + to redistribute at least thirty days before your manuscript or media + freeze, to give the authors time to provide updated documents. This + notification should describe modifications, if any, made to the + document. +\item All substantive modifications (including deletions) be either + clearly marked up in the document or else described in an attachment + to the document. +\item Finally, while it is not mandatory under this license, it is + considered good form to offer a free copy of any hardcopy and CD-ROM + expression of an Open Publication-licensed work to its author(s). +\end{enumerate} + +\section{License options} +\label{sec:opl:options} + +The author(s) and/or publisher of an Open Publication-licensed +document may elect certain options by appending language to the +reference to or copy of the license. These options are considered part +of the license instance and must be included with the license (or its +incorporation by reference) in derived works. + +\begin{enumerate}[A] +\item To prohibit distribution of substantively modified versions + without the explicit permission of the author(s). ``Substantive + modification'' is defined as a change to the semantic content of the + document, and excludes mere changes in format or typographical + corrections. + + To accomplish this, add the phrase ``Distribution of substantively + modified versions of this document is prohibited without the + explicit permission of the copyright holder.'' to the license + reference or copy. + +\item To prohibit any publication of this work or derivative works in + whole or in part in standard (paper) book form for commercial + purposes is prohibited unless prior permission is obtained from the + copyright holder. + + To accomplish this, add the phrase ``Distribution of the work or + derivative of the work in any standard (paper) book form is + prohibited unless prior permission is obtained from the copyright + holder.'' to the license reference or copy. +\end{enumerate} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- a/en/branch.tex Wed Jan 21 14:16:38 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,392 +0,0 @@ -\chapter{Managing releases and branchy development} -\label{chap:branch} - -Mercurial provides several mechanisms for you to manage a project that -is making progress on multiple fronts at once. To understand these -mechanisms, let's first take a brief look at a fairly normal software -project structure. - -Many software projects issue periodic ``major'' releases that contain -substantial new features. In parallel, they may issue ``minor'' -releases. These are usually identical to the major releases off which -they're based, but with a few bugs fixed. - -In this chapter, we'll start by talking about how to keep records of -project milestones such as releases. We'll then continue on to talk -about the flow of work between different phases of a project, and how -Mercurial can help you to isolate and manage this work. - -\section{Giving a persistent name to a revision} - -Once you decide that you'd like to call a particular revision a -``release'', it's a good idea to record the identity of that revision. -This will let you reproduce that release at a later date, for whatever -purpose you might need at the time (reproducing a bug, porting to a -new platform, etc). -\interaction{tag.init} - -Mercurial lets you give a permanent name to any revision using the -\hgcmd{tag} command. Not surprisingly, these names are called -``tags''. -\interaction{tag.tag} - -A tag is nothing more than a ``symbolic name'' for a revision. Tags -exist purely for your convenience, so that you have a handy permanent -way to refer to a revision; Mercurial doesn't interpret the tag names -you use in any way. Neither does Mercurial place any restrictions on -the name of a tag, beyond a few that are necessary to ensure that a -tag can be parsed unambiguously. A tag name cannot contain any of the -following characters: -\begin{itemize} -\item Colon (ASCII 58, ``\texttt{:}'') -\item Carriage return (ASCII 13, ``\Verb+\r+'') -\item Newline (ASCII 10, ``\Verb+\n+'') -\end{itemize} - -You can use the \hgcmd{tags} command to display the tags present in -your repository. In the output, each tagged revision is identified -first by its name, then by revision number, and finally by the unique -hash of the revision. -\interaction{tag.tags} -Notice that \texttt{tip} is listed in the output of \hgcmd{tags}. The -\texttt{tip} tag is a special ``floating'' tag, which always -identifies the newest revision in the repository. - -In the output of the \hgcmd{tags} command, tags are listed in reverse -order, by revision number. This usually means that recent tags are -listed before older tags. It also means that \texttt{tip} is always -going to be the first tag listed in the output of \hgcmd{tags}. - -When you run \hgcmd{log}, if it displays a revision that has tags -associated with it, it will print those tags. -\interaction{tag.log} - -Any time you need to provide a revision~ID to a Mercurial command, the -command will accept a tag name in its place. Internally, Mercurial -will translate your tag name into the corresponding revision~ID, then -use that. -\interaction{tag.log.v1.0} - -There's no limit on the number of tags you can have in a repository, -or on the number of tags that a single revision can have. As a -practical matter, it's not a great idea to have ``too many'' (a number -which will vary from project to project), simply because tags are -supposed to help you to find revisions. If you have lots of tags, the -ease of using them to identify revisions diminishes rapidly. - -For example, if your project has milestones as frequent as every few -days, it's perfectly reasonable to tag each one of those. But if you -have a continuous build system that makes sure every revision can be -built cleanly, you'd be introducing a lot of noise if you were to tag -every clean build. Instead, you could tag failed builds (on the -assumption that they're rare!), or simply not use tags to track -buildability. - -If you want to remove a tag that you no longer want, use -\hgcmdargs{tag}{--remove}. -\interaction{tag.remove} -You can also modify a tag at any time, so that it identifies a -different revision, by simply issuing a new \hgcmd{tag} command. -You'll have to use the \hgopt{tag}{-f} option to tell Mercurial that -you \emph{really} want to update the tag. -\interaction{tag.replace} -There will still be a permanent record of the previous identity of the -tag, but Mercurial will no longer use it. There's thus no penalty to -tagging the wrong revision; all you have to do is turn around and tag -the correct revision once you discover your error. - -Mercurial stores tags in a normal revision-controlled file in your -repository. If you've created any tags, you'll find them in a file -named \sfilename{.hgtags}. When you run the \hgcmd{tag} command, -Mercurial modifies this file, then automatically commits the change to -it. This means that every time you run \hgcmd{tag}, you'll see a -corresponding changeset in the output of \hgcmd{log}. -\interaction{tag.tip} - -\subsection{Handling tag conflicts during a merge} - -You won't often need to care about the \sfilename{.hgtags} file, but -it sometimes makes its presence known during a merge. The format of -the file is simple: it consists of a series of lines. Each line -starts with a changeset hash, followed by a space, followed by the -name of a tag. - -If you're resolving a conflict in the \sfilename{.hgtags} file during -a merge, there's one twist to modifying the \sfilename{.hgtags} file: -when Mercurial is parsing the tags in a repository, it \emph{never} -reads the working copy of the \sfilename{.hgtags} file. Instead, it -reads the \emph{most recently committed} revision of the file. - -An unfortunate consequence of this design is that you can't actually -verify that your merged \sfilename{.hgtags} file is correct until -\emph{after} you've committed a change. So if you find yourself -resolving a conflict on \sfilename{.hgtags} during a merge, be sure to -run \hgcmd{tags} after you commit. If it finds an error in the -\sfilename{.hgtags} file, it will report the location of the error, -which you can then fix and commit. You should then run \hgcmd{tags} -again, just to be sure that your fix is correct. - -\subsection{Tags and cloning} - -You may have noticed that the \hgcmd{clone} command has a -\hgopt{clone}{-r} option that lets you clone an exact copy of the -repository as of a particular changeset. The new clone will not -contain any project history that comes after the revision you -specified. This has an interaction with tags that can surprise the -unwary. - -Recall that a tag is stored as a revision to the \sfilename{.hgtags} -file, so that when you create a tag, the changeset in which it's -recorded necessarily refers to an older changeset. When you run -\hgcmdargs{clone}{-r foo} to clone a repository as of tag -\texttt{foo}, the new clone \emph{will not contain the history that - created the tag} that you used to clone the repository. The result -is that you'll get exactly the right subset of the project's history -in the new repository, but \emph{not} the tag you might have expected. - -\subsection{When permanent tags are too much} - -Since Mercurial's tags are revision controlled and carried around with -a project's history, everyone you work with will see the tags you -create. But giving names to revisions has uses beyond simply noting -that revision \texttt{4237e45506ee} is really \texttt{v2.0.2}. If -you're trying to track down a subtle bug, you might want a tag to -remind you of something like ``Anne saw the symptoms with this -revision''. - -For cases like this, what you might want to use are \emph{local} tags. -You can create a local tag with the \hgopt{tag}{-l} option to the -\hgcmd{tag} command. This will store the tag in a file called -\sfilename{.hg/localtags}. Unlike \sfilename{.hgtags}, -\sfilename{.hg/localtags} is not revision controlled. Any tags you -create using \hgopt{tag}{-l} remain strictly local to the repository -you're currently working in. - -\section{The flow of changes---big picture vs. little} - -To return to the outline I sketched at the beginning of a chapter, -let's think about a project that has multiple concurrent pieces of -work under development at once. - -There might be a push for a new ``main'' release; a new minor bugfix -release to the last main release; and an unexpected ``hot fix'' to an -old release that is now in maintenance mode. - -The usual way people refer to these different concurrent directions of -development is as ``branches''. However, we've already seen numerous -times that Mercurial treats \emph{all of history} as a series of -branches and merges. Really, what we have here is two ideas that are -peripherally related, but which happen to share a name. -\begin{itemize} -\item ``Big picture'' branches represent the sweep of a project's - evolution; people give them names, and talk about them in - conversation. -\item ``Little picture'' branches are artefacts of the day-to-day - activity of developing and merging changes. They expose the - narrative of how the code was developed. -\end{itemize} - -\section{Managing big-picture branches in repositories} - -The easiest way to isolate a ``big picture'' branch in Mercurial is in -a dedicated repository. If you have an existing shared -repository---let's call it \texttt{myproject}---that reaches a ``1.0'' -milestone, you can start to prepare for future maintenance releases on -top of version~1.0 by tagging the revision from which you prepared -the~1.0 release. -\interaction{branch-repo.tag} -You can then clone a new shared \texttt{myproject-1.0.1} repository as -of that tag. -\interaction{branch-repo.clone} - -Afterwards, if someone needs to work on a bug fix that ought to go -into an upcoming~1.0.1 minor release, they clone the -\texttt{myproject-1.0.1} repository, make their changes, and push them -back. -\interaction{branch-repo.bugfix} -Meanwhile, development for the next major release can continue, -isolated and unabated, in the \texttt{myproject} repository. -\interaction{branch-repo.new} - -\section{Don't repeat yourself: merging across branches} - -In many cases, if you have a bug to fix on a maintenance branch, the -chances are good that the bug exists on your project's main branch -(and possibly other maintenance branches, too). It's a rare developer -who wants to fix the same bug multiple times, so let's look at a few -ways that Mercurial can help you to manage these bugfixes without -duplicating your work. - -In the simplest instance, all you need to do is pull changes from your -maintenance branch into your local clone of the target branch. -\interaction{branch-repo.pull} -You'll then need to merge the heads of the two branches, and push back -to the main branch. -\interaction{branch-repo.merge} - -\section{Naming branches within one repository} - -In most instances, isolating branches in repositories is the right -approach. Its simplicity makes it easy to understand; and so it's -hard to make mistakes. There's a one-to-one relationship between -branches you're working in and directories on your system. This lets -you use normal (non-Mercurial-aware) tools to work on files within a -branch/repository. - -If you're more in the ``power user'' category (\emph{and} your -collaborators are too), there is an alternative way of handling -branches that you can consider. I've already mentioned the -human-level distinction between ``small picture'' and ``big picture'' -branches. While Mercurial works with multiple ``small picture'' -branches in a repository all the time (for example after you pull -changes in, but before you merge them), it can \emph{also} work with -multiple ``big picture'' branches. - -The key to working this way is that Mercurial lets you assign a -persistent \emph{name} to a branch. There always exists a branch -named \texttt{default}. Even before you start naming branches -yourself, you can find traces of the \texttt{default} branch if you -look for them. - -As an example, when you run the \hgcmd{commit} command, and it pops up -your editor so that you can enter a commit message, look for a line -that contains the text ``\texttt{HG: branch default}'' at the bottom. -This is telling you that your commit will occur on the branch named -\texttt{default}. - -To start working with named branches, use the \hgcmd{branches} -command. This command lists the named branches already present in -your repository, telling you which changeset is the tip of each. -\interaction{branch-named.branches} -Since you haven't created any named branches yet, the only one that -exists is \texttt{default}. - -To find out what the ``current'' branch is, run the \hgcmd{branch} -command, giving it no arguments. This tells you what branch the -parent of the current changeset is on. -\interaction{branch-named.branch} - -To create a new branch, run the \hgcmd{branch} command again. This -time, give it one argument: the name of the branch you want to create. -\interaction{branch-named.create} - -After you've created a branch, you might wonder what effect the -\hgcmd{branch} command has had. What do the \hgcmd{status} and -\hgcmd{tip} commands report? -\interaction{branch-named.status} -Nothing has changed in the working directory, and there's been no new -history created. As this suggests, running the \hgcmd{branch} command -has no permanent effect; it only tells Mercurial what branch name to -use the \emph{next} time you commit a changeset. - -When you commit a change, Mercurial records the name of the branch on -which you committed. Once you've switched from the \texttt{default} -branch to another and committed, you'll see the name of the new branch -show up in the output of \hgcmd{log}, \hgcmd{tip}, and other commands -that display the same kind of output. -\interaction{branch-named.commit} -The \hgcmd{log}-like commands will print the branch name of every -changeset that's not on the \texttt{default} branch. As a result, if -you never use named branches, you'll never see this information. - -Once you've named a branch and committed a change with that name, -every subsequent commit that descends from that change will inherit -the same branch name. You can change the name of a branch at any -time, using the \hgcmd{branch} command. -\interaction{branch-named.rebranch} -In practice, this is something you won't do very often, as branch -names tend to have fairly long lifetimes. (This isn't a rule, just an -observation.) - -\section{Dealing with multiple named branches in a repository} - -If you have more than one named branch in a repository, Mercurial will -remember the branch that your working directory on when you start a -command like \hgcmd{update} or \hgcmdargs{pull}{-u}. It will update -the working directory to the tip of this branch, no matter what the -``repo-wide'' tip is. To update to a revision that's on a different -named branch, you may need to use the \hgopt{update}{-C} option to -\hgcmd{update}. - -This behaviour is a little subtle, so let's see it in action. First, -let's remind ourselves what branch we're currently on, and what -branches are in our repository. -\interaction{branch-named.parents} -We're on the \texttt{bar} branch, but there also exists an older -\hgcmd{foo} branch. - -We can \hgcmd{update} back and forth between the tips of the -\texttt{foo} and \texttt{bar} branches without needing to use the -\hgopt{update}{-C} option, because this only involves going backwards -and forwards linearly through our change history. -\interaction{branch-named.update-switchy} - -If we go back to the \texttt{foo} branch and then run \hgcmd{update}, -it will keep us on \texttt{foo}, not move us to the tip of -\texttt{bar}. -\interaction{branch-named.update-nothing} - -Committing a new change on the \texttt{foo} branch introduces a new -head. -\interaction{branch-named.foo-commit} - -\section{Branch names and merging} - -As you've probably noticed, merges in Mercurial are not symmetrical. -Let's say our repository has two heads, 17 and 23. If I -\hgcmd{update} to 17 and then \hgcmd{merge} with 23, Mercurial records -17 as the first parent of the merge, and 23 as the second. Whereas if -I \hgcmd{update} to 23 and then \hgcmd{merge} with 17, it records 23 -as the first parent, and 17 as the second. - -This affects Mercurial's choice of branch name when you merge. After -a merge, Mercurial will retain the branch name of the first parent -when you commit the result of the merge. If your first parent's -branch name is \texttt{foo}, and you merge with \texttt{bar}, the -branch name will still be \texttt{foo} after you merge. - -It's not unusual for a repository to contain multiple heads, each with -the same branch name. Let's say I'm working on the \texttt{foo} -branch, and so are you. We commit different changes; I pull your -changes; I now have two heads, each claiming to be on the \texttt{foo} -branch. The result of a merge will be a single head on the -\texttt{foo} branch, as you might hope. - -But if I'm working on the \texttt{bar} branch, and I merge work from -the \texttt{foo} branch, the result will remain on the \texttt{bar} -branch. -\interaction{branch-named.merge} - -To give a more concrete example, if I'm working on the -\texttt{bleeding-edge} branch, and I want to bring in the latest fixes -from the \texttt{stable} branch, Mercurial will choose the ``right'' -(\texttt{bleeding-edge}) branch name when I pull and merge from -\texttt{stable}. - -\section{Branch naming is generally useful} - -You shouldn't think of named branches as applicable only to situations -where you have multiple long-lived branches cohabiting in a single -repository. They're very useful even in the one-branch-per-repository -case. - -In the simplest case, giving a name to each branch gives you a -permanent record of which branch a changeset originated on. This -gives you more context when you're trying to follow the history of a -long-lived branchy project. - -If you're working with shared repositories, you can set up a -\hook{pretxnchangegroup} hook on each that will block incoming changes -that have the ``wrong'' branch name. This provides a simple, but -effective, defence against people accidentally pushing changes from a -``bleeding edge'' branch to a ``stable'' branch. Such a hook might -look like this inside the shared repo's \hgrc. -\begin{codesample2} - [hooks] - pretxnchangegroup.branch = hg heads --template '{branches} ' | grep mybranch -\end{codesample2} - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "00book" -%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/ch00-preface.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,67 @@ +\chapter*{Preface} +\addcontentsline{toc}{chapter}{Preface} +\label{chap:preface} + +Distributed revision control is a relatively new territory, and has +thus far grown due to people's willingness to strike out into +ill-charted territory. + +I am writing a book about distributed revision control because I +believe that it is an important subject that deserves a field guide. +I chose to write about Mercurial because it is the easiest tool to +learn the terrain with, and yet it scales to the demands of real, +challenging environments where many other revision control tools fail. + +\section{This book is a work in progress} + +I am releasing this book while I am still writing it, in the hope that +it will prove useful to others. I also hope that readers will +contribute as they see fit. + +\section{About the examples in this book} + +This book takes an unusual approach to code samples. Every example is +``live''---each one is actually the result of a shell script that +executes the Mercurial commands you see. Every time an image of the +book is built from its sources, all the example scripts are +automatically run, and their current results compared against their +expected results. + +The advantage of this approach is that the examples are always +accurate; they describe \emph{exactly} the behaviour of the version of +Mercurial that's mentioned at the front of the book. If I update the +version of Mercurial that I'm documenting, and the output of some +command changes, the build fails. + +There is a small disadvantage to this approach, which is that the +dates and times you'll see in examples tend to be ``squashed'' +together in a way that they wouldn't be if the same commands were +being typed by a human. Where a human can issue no more than one +command every few seconds, with any resulting timestamps +correspondingly spread out, my automated example scripts run many +commands in one second. + +As an instance of this, several consecutive commits in an example can +show up as having occurred during the same second. You can see this +occur in the \hgext{bisect} example in section~\ref{sec:undo:bisect}, +for instance. + +So when you're reading examples, don't place too much weight on the +dates or times you see in the output of commands. But \emph{do} be +confident that the behaviour you're seeing is consistent and +reproducible. + +\section{Colophon---this book is Free} + +This book is licensed under the Open Publication License, and is +produced entirely using Free Software tools. It is typeset with +\LaTeX{}; illustrations are drawn and rendered with +\href{http://www.inkscape.org/}{Inkscape}. + +The complete source code for this book is published as a Mercurial +repository, at \url{http://hg.serpentine.com/mercurial/book}. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/ch01-intro.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,560 @@ +\chapter{Introduction} +\label{chap:intro} + +\section{About revision control} + +Revision control is the process of managing multiple versions of a +piece of information. In its simplest form, this is something that +many people do by hand: every time you modify a file, save it under a +new name that contains a number, each one higher than the number of +the preceding version. + +Manually managing multiple versions of even a single file is an +error-prone task, though, so software tools to help automate this +process have long been available. The earliest automated revision +control tools were intended to help a single user to manage revisions +of a single file. Over the past few decades, the scope of revision +control tools has expanded greatly; they now manage multiple files, +and help multiple people to work together. The best modern revision +control tools have no problem coping with thousands of people working +together on projects that consist of hundreds of thousands of files. + +\subsection{Why use revision control?} + +There are a number of reasons why you or your team might want to use +an automated revision control tool for a project. +\begin{itemize} +\item It will track the history and evolution of your project, so you + don't have to. For every change, you'll have a log of \emph{who} + made it; \emph{why} they made it; \emph{when} they made it; and + \emph{what} the change was. +\item When you're working with other people, revision control software + makes it easier for you to collaborate. For example, when people + more or less simultaneously make potentially incompatible changes, + the software will help you to identify and resolve those conflicts. +\item It can help you to recover from mistakes. If you make a change + that later turns out to be in error, you can revert to an earlier + version of one or more files. In fact, a \emph{really} good + revision control tool will even help you to efficiently figure out + exactly when a problem was introduced (see + section~\ref{sec:undo:bisect} for details). +\item It will help you to work simultaneously on, and manage the drift + between, multiple versions of your project. +\end{itemize} +Most of these reasons are equally valid---at least in theory---whether +you're working on a project by yourself, or with a hundred other +people. + +A key question about the practicality of revision control at these two +different scales (``lone hacker'' and ``huge team'') is how its +\emph{benefits} compare to its \emph{costs}. A revision control tool +that's difficult to understand or use is going to impose a high cost. + +A five-hundred-person project is likely to collapse under its own +weight almost immediately without a revision control tool and process. +In this case, the cost of using revision control might hardly seem +worth considering, since \emph{without} it, failure is almost +guaranteed. + +On the other hand, a one-person ``quick hack'' might seem like a poor +place to use a revision control tool, because surely the cost of using +one must be close to the overall cost of the project. Right? + +Mercurial uniquely supports \emph{both} of these scales of +development. You can learn the basics in just a few minutes, and due +to its low overhead, you can apply revision control to the smallest of +projects with ease. Its simplicity means you won't have a lot of +abstruse concepts or command sequences competing for mental space with +whatever you're \emph{really} trying to do. At the same time, +Mercurial's high performance and peer-to-peer nature let you scale +painlessly to handle large projects. + +No revision control tool can rescue a poorly run project, but a good +choice of tools can make a huge difference to the fluidity with which +you can work on a project. + +\subsection{The many names of revision control} + +Revision control is a diverse field, so much so that it doesn't +actually have a single name or acronym. Here are a few of the more +common names and acronyms you'll encounter: +\begin{itemize} +\item Revision control (RCS) +\item Software configuration management (SCM), or configuration management +\item Source code management +\item Source code control, or source control +\item Version control (VCS) +\end{itemize} +Some people claim that these terms actually have different meanings, +but in practice they overlap so much that there's no agreed or even +useful way to tease them apart. + +\section{A short history of revision control} + +The best known of the old-time revision control tools is SCCS (Source +Code Control System), which Marc Rochkind wrote at Bell Labs, in the +early 1970s. SCCS operated on individual files, and required every +person working on a project to have access to a shared workspace on a +single system. Only one person could modify a file at any time; +arbitration for access to files was via locks. It was common for +people to lock files, and later forget to unlock them, preventing +anyone else from modifying those files without the help of an +administrator. + +Walter Tichy developed a free alternative to SCCS in the early 1980s; +he called his program RCS (Revison Control System). Like SCCS, RCS +required developers to work in a single shared workspace, and to lock +files to prevent multiple people from modifying them simultaneously. + +Later in the 1980s, Dick Grune used RCS as a building block for a set +of shell scripts he initially called cmt, but then renamed to CVS +(Concurrent Versions System). The big innovation of CVS was that it +let developers work simultaneously and somewhat independently in their +own personal workspaces. The personal workspaces prevented developers +from stepping on each other's toes all the time, as was common with +SCCS and RCS. Each developer had a copy of every project file, and +could modify their copies independently. They had to merge their +edits prior to committing changes to the central repository. + +Brian Berliner took Grune's original scripts and rewrote them in~C, +releasing in 1989 the code that has since developed into the modern +version of CVS. CVS subsequently acquired the ability to operate over +a network connection, giving it a client/server architecture. CVS's +architecture is centralised; only the server has a copy of the history +of the project. Client workspaces just contain copies of recent +versions of the project's files, and a little metadata to tell them +where the server is. CVS has been enormously successful; it is +probably the world's most widely used revision control system. + +In the early 1990s, Sun Microsystems developed an early distributed +revision control system, called TeamWare. A TeamWare workspace +contains a complete copy of the project's history. TeamWare has no +notion of a central repository. (CVS relied upon RCS for its history +storage; TeamWare used SCCS.) + +As the 1990s progressed, awareness grew of a number of problems with +CVS. It records simultaneous changes to multiple files individually, +instead of grouping them together as a single logically atomic +operation. It does not manage its file hierarchy well; it is easy to +make a mess of a repository by renaming files and directories. Worse, +its source code is difficult to read and maintain, which made the +``pain level'' of fixing these architectural problems prohibitive. + +In 2001, Jim Blandy and Karl Fogel, two developers who had worked on +CVS, started a project to replace it with a tool that would have a +better architecture and cleaner code. The result, Subversion, does +not stray from CVS's centralised client/server model, but it adds +multi-file atomic commits, better namespace management, and a number +of other features that make it a generally better tool than CVS. +Since its initial release, it has rapidly grown in popularity. + +More or less simultaneously, Graydon Hoare began working on an +ambitious distributed revision control system that he named Monotone. +While Monotone addresses many of CVS's design flaws and has a +peer-to-peer architecture, it goes beyond earlier (and subsequent) +revision control tools in a number of innovative ways. It uses +cryptographic hashes as identifiers, and has an integral notion of +``trust'' for code from different sources. + +Mercurial began life in 2005. While a few aspects of its design are +influenced by Monotone, Mercurial focuses on ease of use, high +performance, and scalability to very large projects. + +\section{Trends in revision control} + +There has been an unmistakable trend in the development and use of +revision control tools over the past four decades, as people have +become familiar with the capabilities of their tools and constrained +by their limitations. + +The first generation began by managing single files on individual +computers. Although these tools represented a huge advance over +ad-hoc manual revision control, their locking model and reliance on a +single computer limited them to small, tightly-knit teams. + +The second generation loosened these constraints by moving to +network-centered architectures, and managing entire projects at a +time. As projects grew larger, they ran into new problems. With +clients needing to talk to servers very frequently, server scaling +became an issue for large projects. An unreliable network connection +could prevent remote users from being able to talk to the server at +all. As open source projects started making read-only access +available anonymously to anyone, people without commit privileges +found that they could not use the tools to interact with a project in +a natural way, as they could not record their changes. + +The current generation of revision control tools is peer-to-peer in +nature. All of these systems have dropped the dependency on a single +central server, and allow people to distribute their revision control +data to where it's actually needed. Collaboration over the Internet +has moved from constrained by technology to a matter of choice and +consensus. Modern tools can operate offline indefinitely and +autonomously, with a network connection only needed when syncing +changes with another repository. + +\section{A few of the advantages of distributed revision control} + +Even though distributed revision control tools have for several years +been as robust and usable as their previous-generation counterparts, +people using older tools have not yet necessarily woken up to their +advantages. There are a number of ways in which distributed tools +shine relative to centralised ones. + +For an individual developer, distributed tools are almost always much +faster than centralised tools. This is for a simple reason: a +centralised tool needs to talk over the network for many common +operations, because most metadata is stored in a single copy on the +central server. A distributed tool stores all of its metadata +locally. All else being equal, talking over the network adds overhead +to a centralised tool. Don't underestimate the value of a snappy, +responsive tool: you're going to spend a lot of time interacting with +your revision control software. + +Distributed tools are indifferent to the vagaries of your server +infrastructure, again because they replicate metadata to so many +locations. If you use a centralised system and your server catches +fire, you'd better hope that your backup media are reliable, and that +your last backup was recent and actually worked. With a distributed +tool, you have many backups available on every contributor's computer. + +The reliability of your network will affect distributed tools far less +than it will centralised tools. You can't even use a centralised tool +without a network connection, except for a few highly constrained +commands. With a distributed tool, if your network connection goes +down while you're working, you may not even notice. The only thing +you won't be able to do is talk to repositories on other computers, +something that is relatively rare compared with local operations. If +you have a far-flung team of collaborators, this may be significant. + +\subsection{Advantages for open source projects} + +If you take a shine to an open source project and decide that you +would like to start hacking on it, and that project uses a distributed +revision control tool, you are at once a peer with the people who +consider themselves the ``core'' of that project. If they publish +their repositories, you can immediately copy their project history, +start making changes, and record your work, using the same tools in +the same ways as insiders. By contrast, with a centralised tool, you +must use the software in a ``read only'' mode unless someone grants +you permission to commit changes to their central server. Until then, +you won't be able to record changes, and your local modifications will +be at risk of corruption any time you try to update your client's view +of the repository. + +\subsubsection{The forking non-problem} + +It has been suggested that distributed revision control tools pose +some sort of risk to open source projects because they make it easy to +``fork'' the development of a project. A fork happens when there are +differences in opinion or attitude between groups of developers that +cause them to decide that they can't work together any longer. Each +side takes a more or less complete copy of the project's source code, +and goes off in its own direction. + +Sometimes the camps in a fork decide to reconcile their differences. +With a centralised revision control system, the \emph{technical} +process of reconciliation is painful, and has to be performed largely +by hand. You have to decide whose revision history is going to +``win'', and graft the other team's changes into the tree somehow. +This usually loses some or all of one side's revision history. + +What distributed tools do with respect to forking is they make forking +the \emph{only} way to develop a project. Every single change that +you make is potentially a fork point. The great strength of this +approach is that a distributed revision control tool has to be really +good at \emph{merging} forks, because forks are absolutely +fundamental: they happen all the time. + +If every piece of work that everybody does, all the time, is framed in +terms of forking and merging, then what the open source world refers +to as a ``fork'' becomes \emph{purely} a social issue. If anything, +distributed tools \emph{lower} the likelihood of a fork: +\begin{itemize} +\item They eliminate the social distinction that centralised tools + impose: that between insiders (people with commit access) and + outsiders (people without). +\item They make it easier to reconcile after a social fork, because + all that's involved from the perspective of the revision control + software is just another merge. +\end{itemize} + +Some people resist distributed tools because they want to retain tight +control over their projects, and they believe that centralised tools +give them this control. However, if you're of this belief, and you +publish your CVS or Subversion repositories publically, there are +plenty of tools available that can pull out your entire project's +history (albeit slowly) and recreate it somewhere that you don't +control. So while your control in this case is illusory, you are +forgoing the ability to fluidly collaborate with whatever people feel +compelled to mirror and fork your history. + +\subsection{Advantages for commercial projects} + +Many commercial projects are undertaken by teams that are scattered +across the globe. Contributors who are far from a central server will +see slower command execution and perhaps less reliability. Commercial +revision control systems attempt to ameliorate these problems with +remote-site replication add-ons that are typically expensive to buy +and cantankerous to administer. A distributed system doesn't suffer +from these problems in the first place. Better yet, you can easily +set up multiple authoritative servers, say one per site, so that +there's no redundant communication between repositories over expensive +long-haul network links. + +Centralised revision control systems tend to have relatively low +scalability. It's not unusual for an expensive centralised system to +fall over under the combined load of just a few dozen concurrent +users. Once again, the typical response tends to be an expensive and +clunky replication facility. Since the load on a central server---if +you have one at all---is many times lower with a distributed +tool (because all of the data is replicated everywhere), a single +cheap server can handle the needs of a much larger team, and +replication to balance load becomes a simple matter of scripting. + +If you have an employee in the field, troubleshooting a problem at a +customer's site, they'll benefit from distributed revision control. +The tool will let them generate custom builds, try different fixes in +isolation from each other, and search efficiently through history for +the sources of bugs and regressions in the customer's environment, all +without needing to connect to your company's network. + +\section{Why choose Mercurial?} + +Mercurial has a unique set of properties that make it a particularly +good choice as a revision control system. +\begin{itemize} +\item It is easy to learn and use. +\item It is lightweight. +\item It scales excellently. +\item It is easy to customise. +\end{itemize} + +If you are at all familiar with revision control systems, you should +be able to get up and running with Mercurial in less than five +minutes. Even if not, it will take no more than a few minutes +longer. Mercurial's command and feature sets are generally uniform +and consistent, so you can keep track of a few general rules instead +of a host of exceptions. + +On a small project, you can start working with Mercurial in moments. +Creating new changes and branches; transferring changes around +(whether locally or over a network); and history and status operations +are all fast. Mercurial attempts to stay nimble and largely out of +your way by combining low cognitive overhead with blazingly fast +operations. + +The usefulness of Mercurial is not limited to small projects: it is +used by projects with hundreds to thousands of contributors, each +containing tens of thousands of files and hundreds of megabytes of +source code. + +If the core functionality of Mercurial is not enough for you, it's +easy to build on. Mercurial is well suited to scripting tasks, and +its clean internals and implementation in Python make it easy to add +features in the form of extensions. There are a number of popular and +useful extensions already available, ranging from helping to identify +bugs to improving performance. + +\section{Mercurial compared with other tools} + +Before you read on, please understand that this section necessarily +reflects my own experiences, interests, and (dare I say it) biases. I +have used every one of the revision control tools listed below, in +most cases for several years at a time. + + +\subsection{Subversion} + +Subversion is a popular revision control tool, developed to replace +CVS. It has a centralised client/server architecture. + +Subversion and Mercurial have similarly named commands for performing +the same operations, so if you're familiar with one, it is easy to +learn to use the other. Both tools are portable to all popular +operating systems. + +Prior to version 1.5, Subversion had no useful support for merges. +At the time of writing, its merge tracking capability is new, and known to be +\href{http://svnbook.red-bean.com/nightly/en/svn.branchmerge.advanced.html#svn.branchmerge.advanced.finalword}{complicated and buggy}. + +Mercurial has a substantial performance advantage over Subversion on +every revision control operation I have benchmarked. I have measured +its advantage as ranging from a factor of two to a factor of six when +compared with Subversion~1.4.3's \emph{ra\_local} file store, which is +the fastest access method available. In more realistic deployments +involving a network-based store, Subversion will be at a substantially +larger disadvantage. Because many Subversion commands must talk to +the server and Subversion does not have useful replication facilities, +server capacity and network bandwidth become bottlenecks for modestly +large projects. + +Additionally, Subversion incurs substantial storage overhead to avoid +network transactions for a few common operations, such as finding +modified files (\texttt{status}) and displaying modifications against +the current revision (\texttt{diff}). As a result, a Subversion +working copy is often the same size as, or larger than, a Mercurial +repository and working directory, even though the Mercurial repository +contains a complete history of the project. + +Subversion is widely supported by third party tools. Mercurial +currently lags considerably in this area. This gap is closing, +however, and indeed some of Mercurial's GUI tools now outshine their +Subversion equivalents. Like Mercurial, Subversion has an excellent +user manual. + +Because Subversion doesn't store revision history on the client, it is +well suited to managing projects that deal with lots of large, opaque +binary files. If you check in fifty revisions to an incompressible +10MB file, Subversion's client-side space usage stays constant The +space used by any distributed SCM will grow rapidly in proportion to +the number of revisions, because the differences between each revision +are large. + +In addition, it's often difficult or, more usually, impossible to +merge different versions of a binary file. Subversion's ability to +let a user lock a file, so that they temporarily have the exclusive +right to commit changes to it, can be a significant advantage to a +project where binary files are widely used. + +Mercurial can import revision history from a Subversion repository. +It can also export revision history to a Subversion repository. This +makes it easy to ``test the waters'' and use Mercurial and Subversion +in parallel before deciding to switch. History conversion is +incremental, so you can perform an initial conversion, then small +additional conversions afterwards to bring in new changes. + + +\subsection{Git} + +Git is a distributed revision control tool that was developed for +managing the Linux kernel source tree. Like Mercurial, its early +design was somewhat influenced by Monotone. + +Git has a very large command set, with version~1.5.0 providing~139 +individual commands. It has something of a reputation for being +difficult to learn. Compared to Git, Mercurial has a strong focus on +simplicity. + +In terms of performance, Git is extremely fast. In several cases, it +is faster than Mercurial, at least on Linux, while Mercurial performs +better on other operations. However, on Windows, the performance and +general level of support that Git provides is, at the time of writing, +far behind that of Mercurial. + +While a Mercurial repository needs no maintenance, a Git repository +requires frequent manual ``repacks'' of its metadata. Without these, +performance degrades, while space usage grows rapidly. A server that +contains many Git repositories that are not rigorously and frequently +repacked will become heavily disk-bound during backups, and there have +been instances of daily backups taking far longer than~24 hours as a +result. A freshly packed Git repository is slightly smaller than a +Mercurial repository, but an unpacked repository is several orders of +magnitude larger. + +The core of Git is written in C. Many Git commands are implemented as +shell or Perl scripts, and the quality of these scripts varies widely. +I have encountered several instances where scripts charged along +blindly in the presence of errors that should have been fatal. + +Mercurial can import revision history from a Git repository. + + +\subsection{CVS} + +CVS is probably the most widely used revision control tool in the +world. Due to its age and internal untidiness, it has been only +lightly maintained for many years. + +It has a centralised client/server architecture. It does not group +related file changes into atomic commits, making it easy for people to +``break the build'': one person can successfully commit part of a +change and then be blocked by the need for a merge, causing other +people to see only a portion of the work they intended to do. This +also affects how you work with project history. If you want to see +all of the modifications someone made as part of a task, you will need +to manually inspect the descriptions and timestamps of the changes +made to each file involved (if you even know what those files were). + +CVS has a muddled notion of tags and branches that I will not attempt +to even describe. It does not support renaming of files or +directories well, making it easy to corrupt a repository. It has +almost no internal consistency checking capabilities, so it is usually +not even possible to tell whether or how a repository is corrupt. I +would not recommend CVS for any project, existing or new. + +Mercurial can import CVS revision history. However, there are a few +caveats that apply; these are true of every other revision control +tool's CVS importer, too. Due to CVS's lack of atomic changes and +unversioned filesystem hierarchy, it is not possible to reconstruct +CVS history completely accurately; some guesswork is involved, and +renames will usually not show up. Because a lot of advanced CVS +administration has to be done by hand and is hence error-prone, it's +common for CVS importers to run into multiple problems with corrupted +repositories (completely bogus revision timestamps and files that have +remained locked for over a decade are just two of the less interesting +problems I can recall from personal experience). + +Mercurial can import revision history from a CVS repository. + + +\subsection{Commercial tools} + +Perforce has a centralised client/server architecture, with no +client-side caching of any data. Unlike modern revision control +tools, Perforce requires that a user run a command to inform the +server about every file they intend to edit. + +The performance of Perforce is quite good for small teams, but it +falls off rapidly as the number of users grows beyond a few dozen. +Modestly large Perforce installations require the deployment of +proxies to cope with the load their users generate. + + +\subsection{Choosing a revision control tool} + +With the exception of CVS, all of the tools listed above have unique +strengths that suit them to particular styles of work. There is no +single revision control tool that is best in all situations. + +As an example, Subversion is a good choice for working with frequently +edited binary files, due to its centralised nature and support for +file locking. + +I personally find Mercurial's properties of simplicity, performance, +and good merge support to be a compelling combination that has served +me well for several years. + + +\section{Switching from another tool to Mercurial} + +Mercurial is bundled with an extension named \hgext{convert}, which +can incrementally import revision history from several other revision +control tools. By ``incremental'', I mean that you can convert all of +a project's history to date in one go, then rerun the conversion later +to obtain new changes that happened after the initial conversion. + +The revision control tools supported by \hgext{convert} are as +follows: +\begin{itemize} +\item Subversion +\item CVS +\item Git +\item Darcs +\end{itemize} + +In addition, \hgext{convert} can export changes from Mercurial to +Subversion. This makes it possible to try Subversion and Mercurial in +parallel before committing to a switchover, without risking the loss +of any work. + +The \hgxcmd{conver}{convert} command is easy to use. Simply point it +at the path or URL of the source repository, optionally give it the +name of the destination repository, and it will start working. After +the initial conversion, just run the same command again to import new +changes. + + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/ch01-intro.xml Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,680 @@ +<!-- vim: set filetype=docbkxml shiftwidth=2 autoindent expandtab tw=77 : --> + +<chapter> + <title>Introduction</title> + <para>\label{chap:intro}</para> + + <sect1> + <title>About revision control</title> + + <para>Revision control is the process of managing multiple + versions of a piece of information. In its simplest form, this + is something that many people do by hand: every time you modify + a file, save it under a new name that contains a number, each + one higher than the number of the preceding version.</para> + + <para>Manually managing multiple versions of even a single file is + an error-prone task, though, so software tools to help automate + this process have long been available. The earliest automated + revision control tools were intended to help a single user to + manage revisions of a single file. Over the past few decades, + the scope of revision control tools has expanded greatly; they + now manage multiple files, and help multiple people to work + together. The best modern revision control tools have no + problem coping with thousands of people working together on + projects that consist of hundreds of thousands of files.</para> + + <sect2> + <title>Why use revision control?</title> + + <para>There are a number of reasons why you or your team might + want to use an automated revision control tool for a + project.</para> + <itemizedlist> + <listitem><para>It will track the history and evolution of + your project, so you don't have to. For every change, + you'll have a log of <emphasis>who</emphasis> made it; + <emphasis>why</emphasis> they made it; + <emphasis>when</emphasis> they made it; and + <emphasis>what</emphasis> the change + was.</para></listitem> + <listitem><para>When you're working with other people, + revision control software makes it easier for you to + collaborate. For example, when people more or less + simultaneously make potentially incompatible changes, the + software will help you to identify and resolve those + conflicts.</para></listitem> + <listitem><para>It can help you to recover from mistakes. If + you make a change that later turns out to be in error, you + can revert to an earlier version of one or more files. In + fact, a <emphasis>really</emphasis> good revision control + tool will even help you to efficiently figure out exactly + when a problem was introduced (see section <xref + id="sec:undo:bisect"/> for details).</para></listitem> + <listitem><para>It will help you to work simultaneously on, + and manage the drift between, multiple versions of your + project.</para></listitem></itemizedlist> + <para>Most of these reasons are equally valid---at least in + theory---whether you're working on a project by yourself, or + with a hundred other people.</para> + + <para>A key question about the practicality of revision control + at these two different scales (<quote>lone hacker</quote> and + <quote>huge team</quote>) is how its + <emphasis>benefits</emphasis> compare to its + <emphasis>costs</emphasis>. A revision control tool that's + difficult to understand or use is going to impose a high + cost.</para> + + <para>A five-hundred-person project is likely to collapse under + its own weight almost immediately without a revision control + tool and process. In this case, the cost of using revision + control might hardly seem worth considering, since + <emphasis>without</emphasis> it, failure is almost + guaranteed.</para> + + <para>On the other hand, a one-person <quote>quick hack</quote> + might seem like a poor place to use a revision control tool, + because surely the cost of using one must be close to the + overall cost of the project. Right?</para> + + <para>Mercurial uniquely supports <emphasis>both</emphasis> of + these scales of development. You can learn the basics in just + a few minutes, and due to its low overhead, you can apply + revision control to the smallest of projects with ease. Its + simplicity means you won't have a lot of abstruse concepts or + command sequences competing for mental space with whatever + you're <emphasis>really</emphasis> trying to do. At the same + time, Mercurial's high performance and peer-to-peer nature let + you scale painlessly to handle large projects.</para> + + <para>No revision control tool can rescue a poorly run project, + but a good choice of tools can make a huge difference to the + fluidity with which you can work on a project.</para> + + </sect2> + <sect2> + <title>The many names of revision control</title> + + <para>Revision control is a diverse field, so much so that it + doesn't actually have a single name or acronym. Here are a + few of the more common names and acronyms you'll + encounter:</para> + <itemizedlist> + <listitem><para>Revision control (RCS)</para></listitem> + <listitem><para>Software configuration management (SCM), or + configuration management</para></listitem> + <listitem><para>Source code management</para></listitem> + <listitem><para>Source code control, or source + control</para></listitem> + <listitem><para>Version control + (VCS)</para></listitem></itemizedlist> + <para>Some people claim that these terms actually have different + meanings, but in practice they overlap so much that there's no + agreed or even useful way to tease them apart.</para> + + </sect2> + </sect1> + <sect1> + <title>A short history of revision control</title> + + <para>The best known of the old-time revision control tools is + SCCS (Source Code Control System), which Marc Rochkind wrote at + Bell Labs, in the early 1970s. SCCS operated on individual + files, and required every person working on a project to have + access to a shared workspace on a single system. Only one + person could modify a file at any time; arbitration for access + to files was via locks. It was common for people to lock files, + and later forget to unlock them, preventing anyone else from + modifying those files without the help of an + administrator.</para> + + <para>Walter Tichy developed a free alternative to SCCS in the + early 1980s; he called his program RCS (Revison Control System). + Like SCCS, RCS required developers to work in a single shared + workspace, and to lock files to prevent multiple people from + modifying them simultaneously.</para> + + <para>Later in the 1980s, Dick Grune used RCS as a building block + for a set of shell scripts he initially called cmt, but then + renamed to CVS (Concurrent Versions System). The big innovation + of CVS was that it let developers work simultaneously and + somewhat independently in their own personal workspaces. The + personal workspaces prevented developers from stepping on each + other's toes all the time, as was common with SCCS and RCS. Each + developer had a copy of every project file, and could modify + their copies independently. They had to merge their edits prior + to committing changes to the central repository.</para> + + <para>Brian Berliner took Grune's original scripts and rewrote + them in C, releasing in 1989 the code that has since developed + into the modern version of CVS. CVS subsequently acquired the + ability to operate over a network connection, giving it a + client/server architecture. CVS's architecture is centralised; + only the server has a copy of the history of the project. Client + workspaces just contain copies of recent versions of the + project's files, and a little metadata to tell them where the + server is. CVS has been enormously successful; it is probably + the world's most widely used revision control system.</para> + + <para>In the early 1990s, Sun Microsystems developed an early + distributed revision control system, called TeamWare. A + TeamWare workspace contains a complete copy of the project's + history. TeamWare has no notion of a central repository. (CVS + relied upon RCS for its history storage; TeamWare used + SCCS.)</para> + + <para>As the 1990s progressed, awareness grew of a number of + problems with CVS. It records simultaneous changes to multiple + files individually, instead of grouping them together as a + single logically atomic operation. It does not manage its file + hierarchy well; it is easy to make a mess of a repository by + renaming files and directories. Worse, its source code is + difficult to read and maintain, which made the <quote>pain + level</quote> of fixing these architectural problems + prohibitive.</para> + + <para>In 2001, Jim Blandy and Karl Fogel, two developers who had + worked on CVS, started a project to replace it with a tool that + would have a better architecture and cleaner code. The result, + Subversion, does not stray from CVS's centralised client/server + model, but it adds multi-file atomic commits, better namespace + management, and a number of other features that make it a + generally better tool than CVS. Since its initial release, it + has rapidly grown in popularity.</para> + + <para>More or less simultaneously, Graydon Hoare began working on + an ambitious distributed revision control system that he named + Monotone. While Monotone addresses many of CVS's design flaws + and has a peer-to-peer architecture, it goes beyond earlier (and + subsequent) revision control tools in a number of innovative + ways. It uses cryptographic hashes as identifiers, and has an + integral notion of <quote>trust</quote> for code from different + sources.</para> + + <para>Mercurial began life in 2005. While a few aspects of its + design are influenced by Monotone, Mercurial focuses on ease of + use, high performance, and scalability to very large + projects.</para> + + </sect1> + <sect1> + <title>Trends in revision control</title> + + <para>There has been an unmistakable trend in the development and + use of revision control tools over the past four decades, as + people have become familiar with the capabilities of their tools + and constrained by their limitations.</para> + + <para>The first generation began by managing single files on + individual computers. Although these tools represented a huge + advance over ad-hoc manual revision control, their locking model + and reliance on a single computer limited them to small, + tightly-knit teams.</para> + + <para>The second generation loosened these constraints by moving + to network-centered architectures, and managing entire projects + at a time. As projects grew larger, they ran into new problems. + With clients needing to talk to servers very frequently, server + scaling became an issue for large projects. An unreliable + network connection could prevent remote users from being able to + talk to the server at all. As open source projects started + making read-only access available anonymously to anyone, people + without commit privileges found that they could not use the + tools to interact with a project in a natural way, as they could + not record their changes.</para> + + <para>The current generation of revision control tools is + peer-to-peer in nature. All of these systems have dropped the + dependency on a single central server, and allow people to + distribute their revision control data to where it's actually + needed. Collaboration over the Internet has moved from + constrained by technology to a matter of choice and consensus. + Modern tools can operate offline indefinitely and autonomously, + with a network connection only needed when syncing changes with + another repository.</para> + + </sect1> + <sect1> + <title>A few of the advantages of distributed revision + control</title> + + <para>Even though distributed revision control tools have for + several years been as robust and usable as their + previous-generation counterparts, people using older tools have + not yet necessarily woken up to their advantages. There are a + number of ways in which distributed tools shine relative to + centralised ones.</para> + + <para>For an individual developer, distributed tools are almost + always much faster than centralised tools. This is for a simple + reason: a centralised tool needs to talk over the network for + many common operations, because most metadata is stored in a + single copy on the central server. A distributed tool stores + all of its metadata locally. All else being equal, talking over + the network adds overhead to a centralised tool. Don't + underestimate the value of a snappy, responsive tool: you're + going to spend a lot of time interacting with your revision + control software.</para> + + <para>Distributed tools are indifferent to the vagaries of your + server infrastructure, again because they replicate metadata to + so many locations. If you use a centralised system and your + server catches fire, you'd better hope that your backup media + are reliable, and that your last backup was recent and actually + worked. With a distributed tool, you have many backups + available on every contributor's computer.</para> + + <para>The reliability of your network will affect distributed + tools far less than it will centralised tools. You can't even + use a centralised tool without a network connection, except for + a few highly constrained commands. With a distributed tool, if + your network connection goes down while you're working, you may + not even notice. The only thing you won't be able to do is talk + to repositories on other computers, something that is relatively + rare compared with local operations. If you have a far-flung + team of collaborators, this may be significant.</para> + + <sect2> + <title>Advantages for open source projects</title> + + <para>If you take a shine to an open source project and decide + that you would like to start hacking on it, and that project + uses a distributed revision control tool, you are at once a + peer with the people who consider themselves the + <quote>core</quote> of that project. If they publish their + repositories, you can immediately copy their project history, + start making changes, and record your work, using the same + tools in the same ways as insiders. By contrast, with a + centralised tool, you must use the software in a <quote>read + only</quote> mode unless someone grants you permission to + commit changes to their central server. Until then, you won't + be able to record changes, and your local modifications will + be at risk of corruption any time you try to update your + client's view of the repository.</para> + + <sect3> + <title>The forking non-problem</title> + + <para>It has been suggested that distributed revision control + tools pose some sort of risk to open source projects because + they make it easy to <quote>fork</quote> the development of + a project. A fork happens when there are differences in + opinion or attitude between groups of developers that cause + them to decide that they can't work together any longer. + Each side takes a more or less complete copy of the + project's source code, and goes off in its own + direction.</para> + + <para>Sometimes the camps in a fork decide to reconcile their + differences. With a centralised revision control system, the + <emphasis>technical</emphasis> process of reconciliation is + painful, and has to be performed largely by hand. You have + to decide whose revision history is going to + <quote>win</quote>, and graft the other team's changes into + the tree somehow. This usually loses some or all of one + side's revision history.</para> + + <para>What distributed tools do with respect to forking is + they make forking the <emphasis>only</emphasis> way to + develop a project. Every single change that you make is + potentially a fork point. The great strength of this + approach is that a distributed revision control tool has to + be really good at <emphasis>merging</emphasis> forks, + because forks are absolutely fundamental: they happen all + the time.</para> + + <para>If every piece of work that everybody does, all the + time, is framed in terms of forking and merging, then what + the open source world refers to as a <quote>fork</quote> + becomes <emphasis>purely</emphasis> a social issue. If + anything, distributed tools <emphasis>lower</emphasis> the + likelihood of a fork:</para> + <itemizedlist> + <listitem><para>They eliminate the social distinction that + centralised tools impose: that between insiders (people + with commit access) and outsiders (people + without).</para></listitem> + <listitem><para>They make it easier to reconcile after a + social fork, because all that's involved from the + perspective of the revision control software is just + another merge.</para></listitem></itemizedlist> + + <para>Some people resist distributed tools because they want + to retain tight control over their projects, and they + believe that centralised tools give them this control. + However, if you're of this belief, and you publish your CVS + or Subversion repositories publically, there are plenty of + tools available that can pull out your entire project's + history (albeit slowly) and recreate it somewhere that you + don't control. So while your control in this case is + illusory, you are forgoing the ability to fluidly + collaborate with whatever people feel compelled to mirror + and fork your history.</para> + + </sect3> + </sect2> + <sect2> + <title>Advantages for commercial projects</title> + + <para>Many commercial projects are undertaken by teams that are + scattered across the globe. Contributors who are far from a + central server will see slower command execution and perhaps + less reliability. Commercial revision control systems attempt + to ameliorate these problems with remote-site replication + add-ons that are typically expensive to buy and cantankerous + to administer. A distributed system doesn't suffer from these + problems in the first place. Better yet, you can easily set + up multiple authoritative servers, say one per site, so that + there's no redundant communication between repositories over + expensive long-haul network links.</para> + + <para>Centralised revision control systems tend to have + relatively low scalability. It's not unusual for an expensive + centralised system to fall over under the combined load of + just a few dozen concurrent users. Once again, the typical + response tends to be an expensive and clunky replication + facility. Since the load on a central server---if you have + one at all---is many times lower with a distributed tool + (because all of the data is replicated everywhere), a single + cheap server can handle the needs of a much larger team, and + replication to balance load becomes a simple matter of + scripting.</para> + + <para>If you have an employee in the field, troubleshooting a + problem at a customer's site, they'll benefit from distributed + revision control. The tool will let them generate custom + builds, try different fixes in isolation from each other, and + search efficiently through history for the sources of bugs and + regressions in the customer's environment, all without needing + to connect to your company's network.</para> + + </sect2> + </sect1> + <sect1> + <title>Why choose Mercurial?</title> + + <para>Mercurial has a unique set of properties that make it a + particularly good choice as a revision control system.</para> + <itemizedlist> + <listitem><para>It is easy to learn and use.</para></listitem> + <listitem><para>It is lightweight.</para></listitem> + <listitem><para>It scales excellently.</para></listitem> + <listitem><para>It is easy to + customise.</para></listitem></itemizedlist> + + <para>If you are at all familiar with revision control systems, + you should be able to get up and running with Mercurial in less + than five minutes. Even if not, it will take no more than a few + minutes longer. Mercurial's command and feature sets are + generally uniform and consistent, so you can keep track of a few + general rules instead of a host of exceptions.</para> + + <para>On a small project, you can start working with Mercurial in + moments. Creating new changes and branches; transferring changes + around (whether locally or over a network); and history and + status operations are all fast. Mercurial attempts to stay + nimble and largely out of your way by combining low cognitive + overhead with blazingly fast operations.</para> + + <para>The usefulness of Mercurial is not limited to small + projects: it is used by projects with hundreds to thousands of + contributors, each containing tens of thousands of files and + hundreds of megabytes of source code.</para> + + <para>If the core functionality of Mercurial is not enough for + you, it's easy to build on. Mercurial is well suited to + scripting tasks, and its clean internals and implementation in + Python make it easy to add features in the form of extensions. + There are a number of popular and useful extensions already + available, ranging from helping to identify bugs to improving + performance.</para> + + </sect1> + <sect1> + <title>Mercurial compared with other tools</title> + + <para>Before you read on, please understand that this section + necessarily reflects my own experiences, interests, and (dare I + say it) biases. I have used every one of the revision control + tools listed below, in most cases for several years at a + time.</para> + + + <sect2> + <title>Subversion</title> + + <para>Subversion is a popular revision control tool, developed + to replace CVS. It has a centralised client/server + architecture.</para> + + <para>Subversion and Mercurial have similarly named commands for + performing the same operations, so if you're familiar with + one, it is easy to learn to use the other. Both tools are + portable to all popular operating systems.</para> + + <para>Prior to version 1.5, Subversion had no useful support for + merges. At the time of writing, its merge tracking capability + is new, and known to be <ulink + url="http://svnbook.red-bean.com/nightly/en/svn.branchmerge.advanced.html#svn.branchmerge.advanced.finalword">complicated + and buggy</ulink>.</para> + + <para>Mercurial has a substantial performance advantage over + Subversion on every revision control operation I have + benchmarked. I have measured its advantage as ranging from a + factor of two to a factor of six when compared with Subversion + 1.4.3's <emphasis>ra_local</emphasis> file store, which is the + fastest access method available. In more realistic + deployments involving a network-based store, Subversion will + be at a substantially larger disadvantage. Because many + Subversion commands must talk to the server and Subversion + does not have useful replication facilities, server capacity + and network bandwidth become bottlenecks for modestly large + projects.</para> + + <para>Additionally, Subversion incurs substantial storage + overhead to avoid network transactions for a few common + operations, such as finding modified files + (<literal>status</literal>) and displaying modifications + against the current revision (<literal>diff</literal>). As a + result, a Subversion working copy is often the same size as, + or larger than, a Mercurial repository and working directory, + even though the Mercurial repository contains a complete + history of the project.</para> + + <para>Subversion is widely supported by third party tools. + Mercurial currently lags considerably in this area. This gap + is closing, however, and indeed some of Mercurial's GUI tools + now outshine their Subversion equivalents. Like Mercurial, + Subversion has an excellent user manual.</para> + + <para>Because Subversion doesn't store revision history on the + client, it is well suited to managing projects that deal with + lots of large, opaque binary files. If you check in fifty + revisions to an incompressible 10MB file, Subversion's + client-side space usage stays constant The space used by any + distributed SCM will grow rapidly in proportion to the number + of revisions, because the differences between each revision + are large.</para> + + <para>In addition, it's often difficult or, more usually, + impossible to merge different versions of a binary file. + Subversion's ability to let a user lock a file, so that they + temporarily have the exclusive right to commit changes to it, + can be a significant advantage to a project where binary files + are widely used.</para> + + <para>Mercurial can import revision history from a Subversion + repository. It can also export revision history to a + Subversion repository. This makes it easy to <quote>test the + waters</quote> and use Mercurial and Subversion in parallel + before deciding to switch. History conversion is incremental, + so you can perform an initial conversion, then small + additional conversions afterwards to bring in new + changes.</para> + + + </sect2> + <sect2> + <title>Git</title> + + <para>Git is a distributed revision control tool that was + developed for managing the Linux kernel source tree. Like + Mercurial, its early design was somewhat influenced by + Monotone.</para> + + <para>Git has a very large command set, with version 1.5.0 + providing 139 individual commands. It has something of a + reputation for being difficult to learn. Compared to Git, + Mercurial has a strong focus on simplicity.</para> + + <para>In terms of performance, Git is extremely fast. In + several cases, it is faster than Mercurial, at least on Linux, + while Mercurial performs better on other operations. However, + on Windows, the performance and general level of support that + Git provides is, at the time of writing, far behind that of + Mercurial.</para> + + <para>While a Mercurial repository needs no maintenance, a Git + repository requires frequent manual <quote>repacks</quote> of + its metadata. Without these, performance degrades, while + space usage grows rapidly. A server that contains many Git + repositories that are not rigorously and frequently repacked + will become heavily disk-bound during backups, and there have + been instances of daily backups taking far longer than 24 + hours as a result. A freshly packed Git repository is + slightly smaller than a Mercurial repository, but an unpacked + repository is several orders of magnitude larger.</para> + + <para>The core of Git is written in C. Many Git commands are + implemented as shell or Perl scripts, and the quality of these + scripts varies widely. I have encountered several instances + where scripts charged along blindly in the presence of errors + that should have been fatal.</para> + + <para>Mercurial can import revision history from a Git + repository.</para> + + + </sect2> + <sect2> + <title>CVS</title> + + <para>CVS is probably the most widely used revision control tool + in the world. Due to its age and internal untidiness, it has + been only lightly maintained for many years.</para> + + <para>It has a centralised client/server architecture. It does + not group related file changes into atomic commits, making it + easy for people to <quote>break the build</quote>: one person + can successfully commit part of a change and then be blocked + by the need for a merge, causing other people to see only a + portion of the work they intended to do. This also affects + how you work with project history. If you want to see all of + the modifications someone made as part of a task, you will + need to manually inspect the descriptions and timestamps of + the changes made to each file involved (if you even know what + those files were).</para> + + <para>CVS has a muddled notion of tags and branches that I will + not attempt to even describe. It does not support renaming of + files or directories well, making it easy to corrupt a + repository. It has almost no internal consistency checking + capabilities, so it is usually not even possible to tell + whether or how a repository is corrupt. I would not recommend + CVS for any project, existing or new.</para> + + <para>Mercurial can import CVS revision history. However, there + are a few caveats that apply; these are true of every other + revision control tool's CVS importer, too. Due to CVS's lack + of atomic changes and unversioned filesystem hierarchy, it is + not possible to reconstruct CVS history completely accurately; + some guesswork is involved, and renames will usually not show + up. Because a lot of advanced CVS administration has to be + done by hand and is hence error-prone, it's common for CVS + importers to run into multiple problems with corrupted + repositories (completely bogus revision timestamps and files + that have remained locked for over a decade are just two of + the less interesting problems I can recall from personal + experience).</para> + + <para>Mercurial can import revision history from a CVS + repository.</para> + + + </sect2> + <sect2> + <title>Commercial tools</title> + + <para>Perforce has a centralised client/server architecture, + with no client-side caching of any data. Unlike modern + revision control tools, Perforce requires that a user run a + command to inform the server about every file they intend to + edit.</para> + + <para>The performance of Perforce is quite good for small teams, + but it falls off rapidly as the number of users grows beyond a + few dozen. Modestly large Perforce installations require the + deployment of proxies to cope with the load their users + generate.</para> + + + </sect2> + <sect2> + <title>Choosing a revision control tool</title> + + <para>With the exception of CVS, all of the tools listed above + have unique strengths that suit them to particular styles of + work. There is no single revision control tool that is best + in all situations.</para> + + <para>As an example, Subversion is a good choice for working + with frequently edited binary files, due to its centralised + nature and support for file locking.</para> + + <para>I personally find Mercurial's properties of simplicity, + performance, and good merge support to be a compelling + combination that has served me well for several years.</para> + + + </sect2> + </sect1> + <sect1> + <title>Switching from another tool to Mercurial</title> + + <para>Mercurial is bundled with an extension named <literal + role="hg-ext">convert</literal>, which can incrementally + import revision history from several other revision control + tools. By <quote>incremental</quote>, I mean that you can + convert all of a project's history to date in one go, then rerun + the conversion later to obtain new changes that happened after + the initial conversion.</para> + + <para>The revision control tools supported by <literal + role="hg-ext">convert</literal> are as follows:</para> + <itemizedlist> + <listitem><para>Subversion</para></listitem> + <listitem><para>CVS</para></listitem> + <listitem><para>Git</para></listitem> + <listitem><para>Darcs</para></listitem></itemizedlist> + + <para>In addition, <literal role="hg-ext">convert</literal> can + export changes from Mercurial to Subversion. This makes it + possible to try Subversion and Mercurial in parallel before + committing to a switchover, without risking the loss of any + work.</para> + + <para>The <command role="hg-ext-conver">convert</command> command + is easy to use. Simply point it at the path or URL of the + source repository, optionally give it the name of the + destination repository, and it will start working. After the + initial conversion, just run the same command again to import + new changes.</para> + </sect1> +</chapter> + +<!-- +local variables: +sgml-parent-document: ("00book.xml" "book" "chapter") +end: +-->
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/ch02-tour-basic.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,622 @@ +\chapter{A tour of Mercurial: the basics} +\label{chap:tour-basic} + +\section{Installing Mercurial on your system} +\label{sec:tour:install} + +Prebuilt binary packages of Mercurial are available for every popular +operating system. These make it easy to start using Mercurial on your +computer immediately. + +\subsection{Linux} + +Because each Linux distribution has its own packaging tools, policies, +and rate of development, it's difficult to give a comprehensive set of +instructions on how to install Mercurial binaries. The version of +Mercurial that you will end up with can vary depending on how active +the person is who maintains the package for your distribution. + +To keep things simple, I will focus on installing Mercurial from the +command line under the most popular Linux distributions. Most of +these distributions provide graphical package managers that will let +you install Mercurial with a single click; the package name to look +for is \texttt{mercurial}. + +\begin{itemize} +\item[Debian] + \begin{codesample4} + apt-get install mercurial + \end{codesample4} + +\item[Fedora Core] + \begin{codesample4} + yum install mercurial + \end{codesample4} + +\item[Gentoo] + \begin{codesample4} + emerge mercurial + \end{codesample4} + +\item[OpenSUSE] + \begin{codesample4} + yum install mercurial + \end{codesample4} + +\item[Ubuntu] Ubuntu's Mercurial package is based on Debian's. To + install it, run the following command. + \begin{codesample4} + apt-get install mercurial + \end{codesample4} + The Ubuntu package for Mercurial tends to lag behind the Debian + version by a considerable time margin (at the time of writing, seven + months), which in some cases will mean that on Ubuntu, you may run + into problems that have since been fixed in the Debian package. +\end{itemize} + +\subsection{Solaris} + +SunFreeWare, at \url{http://www.sunfreeware.com}, is a good source for a +large number of pre-built Solaris packages for 32 and 64 bit Intel and +Sparc architectures, including current versions of Mercurial. + +\subsection{Mac OS X} + +Lee Cantey publishes an installer of Mercurial for Mac OS~X at +\url{http://mercurial.berkwood.com}. This package works on both +Intel-~and Power-based Macs. Before you can use it, you must install +a compatible version of Universal MacPython~\cite{web:macpython}. This +is easy to do; simply follow the instructions on Lee's site. + +It's also possible to install Mercurial using Fink or MacPorts, +two popular free package managers for Mac OS X. If you have Fink, +use \command{sudo apt-get install mercurial-py25}. If MacPorts, +\command{sudo port install mercurial}. + +\subsection{Windows} + +Lee Cantey publishes an installer of Mercurial for Windows at +\url{http://mercurial.berkwood.com}. This package has no external +dependencies; it ``just works''. + +\begin{note} + The Windows version of Mercurial does not automatically convert line + endings between Windows and Unix styles. If you want to share work + with Unix users, you must do a little additional configuration + work. XXX Flesh this out. +\end{note} + +\section{Getting started} + +To begin, we'll use the \hgcmd{version} command to find out whether +Mercurial is actually installed properly. The actual version +information that it prints isn't so important; it's whether it prints +anything at all that we care about. +\interaction{tour.version} + +\subsection{Built-in help} + +Mercurial provides a built-in help system. This is invaluable for those +times when you find yourself stuck trying to remember how to run a +command. If you are completely stuck, simply run \hgcmd{help}; it +will print a brief list of commands, along with a description of what +each does. If you ask for help on a specific command (as below), it +prints more detailed information. +\interaction{tour.help} +For a more impressive level of detail (which you won't usually need) +run \hgcmdargs{help}{\hggopt{-v}}. The \hggopt{-v} option is short +for \hggopt{--verbose}, and tells Mercurial to print more information +than it usually would. + +\section{Working with a repository} + +In Mercurial, everything happens inside a \emph{repository}. The +repository for a project contains all of the files that ``belong to'' +that project, along with a historical record of the project's files. + +There's nothing particularly magical about a repository; it is simply +a directory tree in your filesystem that Mercurial treats as special. +You can rename or delete a repository any time you like, using either the +command line or your file browser. + +\subsection{Making a local copy of a repository} + +\emph{Copying} a repository is just a little bit special. While you +could use a normal file copying command to make a copy of a +repository, it's best to use a built-in command that Mercurial +provides. This command is called \hgcmd{clone}, because it creates an +identical copy of an existing repository. +\interaction{tour.clone} +If our clone succeeded, we should now have a local directory called +\dirname{hello}. This directory will contain some files. +\interaction{tour.ls} +These files have the same contents and history in our repository as +they do in the repository we cloned. + +Every Mercurial repository is complete, self-contained, and +independent. It contains its own private copy of a project's files +and history. A cloned repository remembers the location of the +repository it was cloned from, but it does not communicate with that +repository, or any other, unless you tell it to. + +What this means for now is that we're free to experiment with our +repository, safe in the knowledge that it's a private ``sandbox'' that +won't affect anyone else. + +\subsection{What's in a repository?} + +When we take a more detailed look inside a repository, we can see that +it contains a directory named \dirname{.hg}. This is where Mercurial +keeps all of its metadata for the repository. +\interaction{tour.ls-a} + +The contents of the \dirname{.hg} directory and its subdirectories are +private to Mercurial. Every other file and directory in the +repository is yours to do with as you please. + +To introduce a little terminology, the \dirname{.hg} directory is the +``real'' repository, and all of the files and directories that coexist +with it are said to live in the \emph{working directory}. An easy way +to remember the distinction is that the \emph{repository} contains the +\emph{history} of your project, while the \emph{working directory} +contains a \emph{snapshot} of your project at a particular point in +history. + +\section{A tour through history} + +One of the first things we might want to do with a new, unfamiliar +repository is understand its history. The \hgcmd{log} command gives +us a view of history. +\interaction{tour.log} +By default, this command prints a brief paragraph of output for each +change to the project that was recorded. In Mercurial terminology, we +call each of these recorded events a \emph{changeset}, because it can +contain a record of changes to several files. + +The fields in a record of output from \hgcmd{log} are as follows. +\begin{itemize} +\item[\texttt{changeset}] This field has the format of a number, + followed by a colon, followed by a hexadecimal string. These are + \emph{identifiers} for the changeset. There are two identifiers + because the number is shorter and easier to type than the hex + string. +\item[\texttt{user}] The identity of the person who created the + changeset. This is a free-form field, but it most often contains a + person's name and email address. +\item[\texttt{date}] The date and time on which the changeset was + created, and the timezone in which it was created. (The date and + time are local to that timezone; they display what time and date it + was for the person who created the changeset.) +\item[\texttt{summary}] The first line of the text message that the + creator of the changeset entered to describe the changeset. +\end{itemize} +The default output printed by \hgcmd{log} is purely a summary; it is +missing a lot of detail. + +Figure~\ref{fig:tour-basic:history} provides a graphical representation of +the history of the \dirname{hello} repository, to make it a little +easier to see which direction history is ``flowing'' in. We'll be +returning to this figure several times in this chapter and the chapter +that follows. + +\begin{figure}[ht] + \centering + \grafix{tour-history} + \caption{Graphical history of the \dirname{hello} repository} + \label{fig:tour-basic:history} +\end{figure} + +\subsection{Changesets, revisions, and talking to other people} + +As English is a notoriously sloppy language, and computer science has +a hallowed history of terminological confusion (why use one term when +four will do?), revision control has a variety of words and phrases +that mean the same thing. If you are talking about Mercurial history +with other people, you will find that the word ``changeset'' is often +compressed to ``change'' or (when written) ``cset'', and sometimes a +changeset is referred to as a ``revision'' or a ``rev''. + +While it doesn't matter what \emph{word} you use to refer to the +concept of ``a~changeset'', the \emph{identifier} that you use to +refer to ``a~\emph{specific} changeset'' is of great importance. +Recall that the \texttt{changeset} field in the output from +\hgcmd{log} identifies a changeset using both a number and a +hexadecimal string. +\begin{itemize} +\item The revision number is \emph{only valid in that repository}, +\item while the hex string is the \emph{permanent, unchanging identifier} that will always identify that exact changeset in + \emph{every} copy of the repository. +\end{itemize} +This distinction is important. If you send someone an email talking +about ``revision~33'', there's a high likelihood that their +revision~33 will \emph{not be the same} as yours. The reason for this +is that a revision number depends on the order in which changes +arrived in a repository, and there is no guarantee that the same +changes will happen in the same order in different repositories. +Three changes $a,b,c$ can easily appear in one repository as $0,1,2$, +while in another as $1,0,2$. + +Mercurial uses revision numbers purely as a convenient shorthand. If +you need to discuss a changeset with someone, or make a record of a +changeset for some other reason (for example, in a bug report), use +the hexadecimal identifier. + +\subsection{Viewing specific revisions} + +To narrow the output of \hgcmd{log} down to a single revision, use the +\hgopt{log}{-r} (or \hgopt{log}{--rev}) option. You can use either a +revision number or a long-form changeset identifier, and you can +provide as many revisions as you want. \interaction{tour.log-r} + +If you want to see the history of several revisions without having to +list each one, you can use \emph{range notation}; this lets you +express the idea ``I want all revisions between $a$ and $b$, +inclusive''. +\interaction{tour.log.range} +Mercurial also honours the order in which you specify revisions, so +\hgcmdargs{log}{-r 2:4} prints $2,3,4$ while \hgcmdargs{log}{-r 4:2} +prints $4,3,2$. + +\subsection{More detailed information} + +While the summary information printed by \hgcmd{log} is useful if you +already know what you're looking for, you may need to see a complete +description of the change, or a list of the files changed, if you're +trying to decide whether a changeset is the one you're looking for. +The \hgcmd{log} command's \hggopt{-v} (or \hggopt{--verbose}) +option gives you this extra detail. +\interaction{tour.log-v} + +If you want to see both the description and content of a change, add +the \hgopt{log}{-p} (or \hgopt{log}{--patch}) option. This displays +the content of a change as a \emph{unified diff} (if you've never seen +a unified diff before, see section~\ref{sec:mq:patch} for an overview). +\interaction{tour.log-vp} + +\section{All about command options} + +Let's take a brief break from exploring Mercurial commands to discuss +a pattern in the way that they work; you may find this useful to keep +in mind as we continue our tour. + +Mercurial has a consistent and straightforward approach to dealing +with the options that you can pass to commands. It follows the +conventions for options that are common to modern Linux and Unix +systems. +\begin{itemize} +\item Every option has a long name. For example, as we've already + seen, the \hgcmd{log} command accepts a \hgopt{log}{--rev} option. +\item Most options have short names, too. Instead of + \hgopt{log}{--rev}, we can use \hgopt{log}{-r}. (The reason that + some options don't have short names is that the options in question + are rarely used.) +\item Long options start with two dashes (e.g.~\hgopt{log}{--rev}), + while short options start with one (e.g.~\hgopt{log}{-r}). +\item Option naming and usage is consistent across commands. For + example, every command that lets you specify a changeset~ID or + revision number accepts both \hgopt{log}{-r} and \hgopt{log}{--rev} + arguments. +\end{itemize} +In the examples throughout this book, I use short options instead of +long. This just reflects my own preference, so don't read anything +significant into it. + +Most commands that print output of some kind will print more output +when passed a \hggopt{-v} (or \hggopt{--verbose}) option, and less +when passed \hggopt{-q} (or \hggopt{--quiet}). + +\section{Making and reviewing changes} + +Now that we have a grasp of viewing history in Mercurial, let's take a +look at making some changes and examining them. + +The first thing we'll do is isolate our experiment in a repository of +its own. We use the \hgcmd{clone} command, but we don't need to +clone a copy of the remote repository. Since we already have a copy +of it locally, we can just clone that instead. This is much faster +than cloning over the network, and cloning a local repository uses +less disk space in most cases, too. +\interaction{tour.reclone} +As an aside, it's often good practice to keep a ``pristine'' copy of a +remote repository around, which you can then make temporary clones of +to create sandboxes for each task you want to work on. This lets you +work on multiple tasks in parallel, each isolated from the others +until it's complete and you're ready to integrate it back. Because +local clones are so cheap, there's almost no overhead to cloning and +destroying repositories whenever you want. + +In our \dirname{my-hello} repository, we have a file +\filename{hello.c} that contains the classic ``hello, world'' program. +Let's use the ancient and venerable \command{sed} command to edit this +file so that it prints a second line of output. (I'm only using +\command{sed} to do this because it's easy to write a scripted example +this way. Since you're not under the same constraint, you probably +won't want to use \command{sed}; simply use your preferred text editor to +do the same thing.) +\interaction{tour.sed} + +Mercurial's \hgcmd{status} command will tell us what Mercurial knows +about the files in the repository. +\interaction{tour.status} +The \hgcmd{status} command prints no output for some files, but a line +starting with ``\texttt{M}'' for \filename{hello.c}. Unless you tell +it to, \hgcmd{status} will not print any output for files that have +not been modified. + +The ``\texttt{M}'' indicates that Mercurial has noticed that we +modified \filename{hello.c}. We didn't need to \emph{inform} +Mercurial that we were going to modify the file before we started, or +that we had modified the file after we were done; it was able to +figure this out itself. + +It's a little bit helpful to know that we've modified +\filename{hello.c}, but we might prefer to know exactly \emph{what} +changes we've made to it. To do this, we use the \hgcmd{diff} +command. +\interaction{tour.diff} + +\section{Recording changes in a new changeset} + +We can modify files, build and test our changes, and use +\hgcmd{status} and \hgcmd{diff} to review our changes, until we're +satisfied with what we've done and arrive at a natural stopping point +where we want to record our work in a new changeset. + +The \hgcmd{commit} command lets us create a new changeset; we'll +usually refer to this as ``making a commit'' or ``committing''. + +\subsection{Setting up a username} + +When you try to run \hgcmd{commit} for the first time, it is not +guaranteed to succeed. Mercurial records your name and address with +each change that you commit, so that you and others will later be able +to tell who made each change. Mercurial tries to automatically figure +out a sensible username to commit the change with. It will attempt +each of the following methods, in order: +\begin{enumerate} +\item If you specify a \hgopt{commit}{-u} option to the \hgcmd{commit} + command on the command line, followed by a username, this is always + given the highest precedence. +\item If you have set the \envar{HGUSER} environment variable, this is + checked next. +\item If you create a file in your home directory called + \sfilename{.hgrc}, with a \rcitem{ui}{username} entry, that will be + used next. To see what the contents of this file should look like, + refer to section~\ref{sec:tour-basic:username} below. +\item If you have set the \envar{EMAIL} environment variable, this + will be used next. +\item Mercurial will query your system to find out your local user + name and host name, and construct a username from these components. + Since this often results in a username that is not very useful, it + will print a warning if it has to do this. +\end{enumerate} +If all of these mechanisms fail, Mercurial will fail, printing an +error message. In this case, it will not let you commit until you set +up a username. + +You should think of the \envar{HGUSER} environment variable and the +\hgopt{commit}{-u} option to the \hgcmd{commit} command as ways to +\emph{override} Mercurial's default selection of username. For normal +use, the simplest and most robust way to set a username for yourself +is by creating a \sfilename{.hgrc} file; see below for details. + +\subsubsection{Creating a Mercurial configuration file} +\label{sec:tour-basic:username} + +To set a user name, use your favourite editor to create a file called +\sfilename{.hgrc} in your home directory. Mercurial will use this +file to look up your personalised configuration settings. The initial +contents of your \sfilename{.hgrc} should look like this. +\begin{codesample2} + # This is a Mercurial configuration file. + [ui] + username = Firstname Lastname <email.address@domain.net> +\end{codesample2} +The ``\texttt{[ui]}'' line begins a \emph{section} of the config file, +so you can read the ``\texttt{username = ...}'' line as meaning ``set +the value of the \texttt{username} item in the \texttt{ui} section''. +A section continues until a new section begins, or the end of the +file. Mercurial ignores empty lines and treats any text from +``\texttt{\#}'' to the end of a line as a comment. + +\subsubsection{Choosing a user name} + +You can use any text you like as the value of the \texttt{username} +config item, since this information is for reading by other people, +but for interpreting by Mercurial. The convention that most people +follow is to use their name and email address, as in the example +above. + +\begin{note} + Mercurial's built-in web server obfuscates email addresses, to make + it more difficult for the email harvesting tools that spammers use. + This reduces the likelihood that you'll start receiving more junk + email if you publish a Mercurial repository on the web. +\end{note} + +\subsection{Writing a commit message} + +When we commit a change, Mercurial drops us into a text editor, to +enter a message that will describe the modifications we've made in +this changeset. This is called the \emph{commit message}. It will be +a record for readers of what we did and why, and it will be printed by +\hgcmd{log} after we've finished committing. +\interaction{tour.commit} + +The editor that the \hgcmd{commit} command drops us into will contain +an empty line, followed by a number of lines starting with +``\texttt{HG:}''. +\begin{codesample2} + \emph{empty line} + HG: changed hello.c +\end{codesample2} +Mercurial ignores the lines that start with ``\texttt{HG:}''; it uses +them only to tell us which files it's recording changes to. Modifying +or deleting these lines has no effect. + +\subsection{Writing a good commit message} + +Since \hgcmd{log} only prints the first line of a commit message by +default, it's best to write a commit message whose first line stands +alone. Here's a real example of a commit message that \emph{doesn't} +follow this guideline, and hence has a summary that is not readable. +\begin{codesample2} + changeset: 73:584af0e231be + user: Censored Person <censored.person@example.org> + date: Tue Sep 26 21:37:07 2006 -0700 + summary: include buildmeister/commondefs. Add an exports and install +\end{codesample2} + +As far as the remainder of the contents of the commit message are +concerned, there are no hard-and-fast rules. Mercurial itself doesn't +interpret or care about the contents of the commit message, though +your project may have policies that dictate a certain kind of +formatting. + +My personal preference is for short, but informative, commit messages +that tell me something that I can't figure out with a quick glance at +the output of \hgcmdargs{log}{--patch}. + +\subsection{Aborting a commit} + +If you decide that you don't want to commit while in the middle of +editing a commit message, simply exit from your editor without saving +the file that it's editing. This will cause nothing to happen to +either the repository or the working directory. + +If we run the \hgcmd{commit} command without any arguments, it records +all of the changes we've made, as reported by \hgcmd{status} and +\hgcmd{diff}. + +\subsection{Admiring our new handiwork} + +Once we've finished the commit, we can use the \hgcmd{tip} command to +display the changeset we just created. This command produces output +that is identical to \hgcmd{log}, but it only displays the newest +revision in the repository. +\interaction{tour.tip} +We refer to the newest revision in the repository as the tip revision, +or simply the tip. + +\section{Sharing changes} + +We mentioned earlier that repositories in Mercurial are +self-contained. This means that the changeset we just created exists +only in our \dirname{my-hello} repository. Let's look at a few ways +that we can propagate this change into other repositories. + +\subsection{Pulling changes from another repository} +\label{sec:tour:pull} + +To get started, let's clone our original \dirname{hello} repository, +which does not contain the change we just committed. We'll call our +temporary repository \dirname{hello-pull}. +\interaction{tour.clone-pull} + +We'll use the \hgcmd{pull} command to bring changes from +\dirname{my-hello} into \dirname{hello-pull}. However, blindly +pulling unknown changes into a repository is a somewhat scary +prospect. Mercurial provides the \hgcmd{incoming} command to tell us +what changes the \hgcmd{pull} command \emph{would} pull into the +repository, without actually pulling the changes in. +\interaction{tour.incoming} +(Of course, someone could cause more changesets to appear in the +repository that we ran \hgcmd{incoming} in, before we get a chance to +\hgcmd{pull} the changes, so that we could end up pulling changes that we +didn't expect.) + +Bringing changes into a repository is a simple matter of running the +\hgcmd{pull} command, and telling it which repository to pull from. +\interaction{tour.pull} +As you can see from the before-and-after output of \hgcmd{tip}, we +have successfully pulled changes into our repository. There remains +one step before we can see these changes in the working directory. + +\subsection{Updating the working directory} + +We have so far glossed over the relationship between a repository and +its working directory. The \hgcmd{pull} command that we ran in +section~\ref{sec:tour:pull} brought changes into the repository, but +if we check, there's no sign of those changes in the working +directory. This is because \hgcmd{pull} does not (by default) touch +the working directory. Instead, we use the \hgcmd{update} command to +do this. +\interaction{tour.update} + +It might seem a bit strange that \hgcmd{pull} doesn't update the +working directory automatically. There's actually a good reason for +this: you can use \hgcmd{update} to update the working directory to +the state it was in at \emph{any revision} in the history of the +repository. If you had the working directory updated to an old +revision---to hunt down the origin of a bug, say---and ran a +\hgcmd{pull} which automatically updated the working directory to a +new revision, you might not be terribly happy. + +However, since pull-then-update is such a common thing to do, +Mercurial lets you combine the two by passing the \hgopt{pull}{-u} +option to \hgcmd{pull}. +\begin{codesample2} + hg pull -u +\end{codesample2} +If you look back at the output of \hgcmd{pull} in +section~\ref{sec:tour:pull} when we ran it without \hgopt{pull}{-u}, +you can see that it printed a helpful reminder that we'd have to take +an explicit step to update the working directory: +\begin{codesample2} + (run 'hg update' to get a working copy) +\end{codesample2} + +To find out what revision the working directory is at, use the +\hgcmd{parents} command. +\interaction{tour.parents} +If you look back at figure~\ref{fig:tour-basic:history}, you'll see +arrows connecting each changeset. The node that the arrow leads +\emph{from} in each case is a parent, and the node that the arrow +leads \emph{to} is its child. The working directory has a parent in +just the same way; this is the changeset that the working directory +currently contains. + +To update the working directory to a particular revision, give a +revision number or changeset~ID to the \hgcmd{update} command. +\interaction{tour.older} +If you omit an explicit revision, \hgcmd{update} will update to the +tip revision, as shown by the second call to \hgcmd{update} in the +example above. + +\subsection{Pushing changes to another repository} + +Mercurial lets us push changes to another repository, from the +repository we're currently visiting. As with the example of +\hgcmd{pull} above, we'll create a temporary repository to push our +changes into. +\interaction{tour.clone-push} +The \hgcmd{outgoing} command tells us what changes would be pushed +into another repository. +\interaction{tour.outgoing} +And the \hgcmd{push} command does the actual push. +\interaction{tour.push} +As with \hgcmd{pull}, the \hgcmd{push} command does not update the +working directory in the repository that it's pushing changes into. +(Unlike \hgcmd{pull}, \hgcmd{push} does not provide a \texttt{-u} +option that updates the other repository's working directory.) + +What happens if we try to pull or push changes and the receiving +repository already has those changes? Nothing too exciting. +\interaction{tour.push.nothing} + +\subsection{Sharing changes over a network} + +The commands we have covered in the previous few sections are not +limited to working with local repositories. Each works in exactly the +same fashion over a network connection; simply pass in a URL instead +of a local path. +\interaction{tour.outgoing.net} +In this example, we can see what changes we could push to the remote +repository, but the repository is understandably not set up to let +anonymous users push to it. +\interaction{tour.push.net} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/ch02-tour-basic.xml Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,787 @@ +<!-- vim: set filetype=docbkxml shiftwidth=2 autoindent expandtab tw=77 : --> + +<chapter> + <title>A tour of Mercurial: the basics</title> + <para>\label{chap:tour-basic}</para> + + <sect1> + <title>Installing Mercurial on your system</title> + <para>\label{sec:tour:install}</para> + + <para>Prebuilt binary packages of Mercurial are available for + every popular operating system. These make it easy to start + using Mercurial on your computer immediately.</para> + + <sect2> + <title>Linux</title> + + <para>Because each Linux distribution has its own packaging + tools, policies, and rate of development, it's difficult to + give a comprehensive set of instructions on how to install + Mercurial binaries. The version of Mercurial that you will + end up with can vary depending on how active the person is who + maintains the package for your distribution.</para> + + <para>To keep things simple, I will focus on installing + Mercurial from the command line under the most popular Linux + distributions. Most of these distributions provide graphical + package managers that will let you install Mercurial with a + single click; the package name to look for is + <literal>mercurial</literal>.</para> + + <itemizedlist> + <listitem><para>Debian:</para> + <programlisting>apt-get install + mercurial</programlisting></listitem> + <listitem><para>Fedora Core:</para> + <programlisting>yum install + mercurial</programlisting></listitem> + <listitem><para>Gentoo:</para> + <programlisting>emerge mercurial</programlisting></listitem> + <listitem><para>OpenSUSE:</para> + <programlisting>yum install + mercurial</programlisting></listitem> + <listitem><para>Ubuntu: Ubuntu's Mercurial package is based on + Debian's. To install it, run the following + command.</para> + <programlisting>apt-get install + mercurial</programlisting></listitem> + </itemizedlist> + + </sect2> + <sect2> + <title>Solaris</title> + + <para>SunFreeWare, at <ulink + url="http://www.sunfreeware.com">http://www.sunfreeware.com</ulink>, + is a good source for a large number of pre-built Solaris + packages for 32 and 64 bit Intel and Sparc architectures, + including current versions of Mercurial.</para> + + </sect2> + <sect2> + <title>Mac OS X</title> + + <para>Lee Cantey publishes an installer of Mercurial for Mac OS + X at <ulink + url="http://mercurial.berkwood.com">http://mercurial.berkwood.com</ulink>. + This package works on both Intel- and Power-based Macs. + Before you can use it, you must install a compatible version + of Universal MacPython <citation>web:macpython</citation>. + This is easy to do; simply follow the instructions on Lee's + site.</para> + + <para>It's also possible to install Mercurial using Fink or + MacPorts, two popular free package managers for Mac OS X. If + you have Fink, use <command>sudo apt-get install + mercurial-py25</command>. If MacPorts, <command>sudo port + install mercurial</command>.</para> + + </sect2> + <sect2> + <title>Windows</title> + + <para>Lee Cantey publishes an installer of Mercurial for Windows + at <ulink + url="http://mercurial.berkwood.com">http://mercurial.berkwood.com</ulink>. + This package has no external dependencies; it <quote>just + works</quote>.</para> + + <note> + <para> The Windows version of Mercurial does not + automatically convert line endings between Windows and Unix + styles. If you want to share work with Unix users, you must + do a little additional configuration work. XXX Flesh this + out.</para> + </note> + + </sect2> + </sect1> + <sect1> + <title>Getting started</title> + + <para>To begin, we'll use the <command role="hg-cmd">hg + version</command> command to find out whether Mercurial is + actually installed properly. The actual version information + that it prints isn't so important; it's whether it prints + anything at all that we care about. <!-- + &interaction.tour.version; --></para> + + <sect2> + <title>Built-in help</title> + + <para>Mercurial provides a built-in help system. This is + invaluable for those times when you find yourself stuck trying + to remember how to run a command. If you are completely + stuck, simply run <command role="hg-cmd">hg help</command>; it + will print a brief list of commands, along with a description + of what each does. If you ask for help on a specific command + (as below), it prints more detailed information. <!-- + &interaction.tour.help; --> For a more impressive level of + detail (which you won't usually need) run <command + role="hg-cmd">hg help <option + role="hg-opt-global">-v</option></command>. The <option + role="hg-opt-global">-v</option> option is short for <option + role="hg-opt-global">--verbose</option>, and tells Mercurial + to print more information than it usually would.</para> + + </sect2> + </sect1> + <sect1> + <title>Working with a repository</title> + + <para>In Mercurial, everything happens inside a + <emphasis>repository</emphasis>. The repository for a project + contains all of the files that <quote>belong to</quote> that + project, along with a historical record of the project's + files.</para> + + <para>There's nothing particularly magical about a repository; it + is simply a directory tree in your filesystem that Mercurial + treats as special. You can rename or delete a repository any + time you like, using either the command line or your file + browser.</para> + + <sect2> + <title>Making a local copy of a repository</title> + + <para><emphasis>Copying</emphasis> a repository is just a little + bit special. While you could use a normal file copying + command to make a copy of a repository, it's best to use a + built-in command that Mercurial provides. This command is + called <command role="hg-cmd">hg clone</command>, because it + creates an identical copy of an existing repository. <!-- + &interaction.tour.clone; --> If our clone succeeded, we should + now have a local directory called <filename + class="directory">hello</filename>. This directory will + contain some files. <!-- &interaction.tour.ls; --> These files + have the same contents and history in our repository as they + do in the repository we cloned.</para> + + <para>Every Mercurial repository is complete, self-contained, + and independent. It contains its own private copy of a + project's files and history. A cloned repository remembers + the location of the repository it was cloned from, but it does + not communicate with that repository, or any other, unless you + tell it to.</para> + + <para>What this means for now is that we're free to experiment + with our repository, safe in the knowledge that it's a private + <quote>sandbox</quote> that won't affect anyone else.</para> + + </sect2> + <sect2> + <title>What's in a repository?</title> + + <para>When we take a more detailed look inside a repository, we + can see that it contains a directory named <filename + class="directory">.hg</filename>. This is where Mercurial + keeps all of its metadata for the repository. <!-- + &interaction.tour.ls-a; --></para> + + <para>The contents of the <filename + class="directory">.hg</filename> directory and its + subdirectories are private to Mercurial. Every other file and + directory in the repository is yours to do with as you + please.</para> + + <para>To introduce a little terminology, the <filename + class="directory">.hg</filename> directory is the + <quote>real</quote> repository, and all of the files and + directories that coexist with it are said to live in the + <emphasis>working directory</emphasis>. An easy way to + remember the distinction is that the + <emphasis>repository</emphasis> contains the + <emphasis>history</emphasis> of your project, while the + <emphasis>working directory</emphasis> contains a + <emphasis>snapshot</emphasis> of your project at a particular + point in history.</para> + + </sect2> + </sect1> + <sect1> + <title>A tour through history</title> + + <para>One of the first things we might want to do with a new, + unfamiliar repository is understand its history. The <command + role="hg-cmd">hg log</command> command gives us a view of + history. <!-- &interaction.tour.log; --> By default, this + command prints a brief paragraph of output for each change to + the project that was recorded. In Mercurial terminology, we + call each of these recorded events a + <emphasis>changeset</emphasis>, because it can contain a record + of changes to several files.</para> + + <para>The fields in a record of output from <command + role="hg-cmd">hg log</command> are as follows.</para> + <itemizedlist> + <listitem><para><literal>changeset</literal>: This field has the + format of a number, followed by a colon, followed by a + hexadecimal string. These are + <emphasis>identifiers</emphasis> for the changeset. There + are two identifiers because the number is shorter and easier + to type than the hex string.</para></listitem> + <listitem><para><literal>user</literal>: The identity of the + person who created the changeset. This is a free-form + field, but it most often contains a person's name and email + address.</para></listitem> + <listitem><para><literal>date</literal>: The date and time on + which the changeset was created, and the timezone in which + it was created. (The date and time are local to that + timezone; they display what time and date it was for the + person who created the changeset.)</para></listitem> + <listitem><para><literal>summary</literal>: The first line of + the text message that the creator of the changeset entered + to describe the changeset.</para></listitem></itemizedlist> + <para>The default output printed by <command role="hg-cmd">hg + log</command> is purely a summary; it is missing a lot of + detail.</para> + + <para>Figure <xref id="fig:tour-basic:history"/> provides a + graphical representation of the history of the <filename + class="directory">hello</filename> repository, to make it a + little easier to see which direction history is + <quote>flowing</quote> in. We'll be returning to this figure + several times in this chapter and the chapter that + follows.</para> + + <figure> + + <para> <mediaobject><imageobject><imagedata + fileref="tour-history"/></imageobject><textobject><phrase>XXX + add text</phrase></textobject></mediaobject> + <caption>Graphical history of the <filename + class="directory">hello</filename> repository</caption> + \label{fig:tour-basic:history}</para> + </figure> + + <sect2> + <title>Changesets, revisions, and talking to other + people</title> + + <para>As English is a notoriously sloppy language, and computer + science has a hallowed history of terminological confusion + (why use one term when four will do?), revision control has a + variety of words and phrases that mean the same thing. If you + are talking about Mercurial history with other people, you + will find that the word <quote>changeset</quote> is often + compressed to <quote>change</quote> or (when written) + <quote>cset</quote>, and sometimes a changeset is referred to + as a <quote>revision</quote> or a <quote>rev</quote>.</para> + + <para>While it doesn't matter what <emphasis>word</emphasis> you + use to refer to the concept of <quote>a changeset</quote>, the + <emphasis>identifier</emphasis> that you use to refer to + <quote>a <emphasis>specific</emphasis> changeset</quote> is of + great importance. Recall that the <literal>changeset</literal> + field in the output from <command role="hg-cmd">hg + log</command> identifies a changeset using both a number and + a hexadecimal string.</para> + <itemizedlist> + <listitem><para>The revision number is <emphasis>only valid in + that repository</emphasis>,</para></listitem> + <listitem><para>while the hex string is the + <emphasis>permanent, unchanging identifier</emphasis> that + will always identify that exact changeset in + <emphasis>every</emphasis> copy of the + repository.</para></listitem></itemizedlist> + <para>This distinction is important. If you send someone an + email talking about <quote>revision 33</quote>, there's a high + likelihood that their revision 33 will <emphasis>not be the + same</emphasis> as yours. The reason for this is that a + revision number depends on the order in which changes arrived + in a repository, and there is no guarantee that the same + changes will happen in the same order in different + repositories. Three changes $a,b,c$ can easily appear in one + repository as $0,1,2$, while in another as $1,0,2$.</para> + + <para>Mercurial uses revision numbers purely as a convenient + shorthand. If you need to discuss a changeset with someone, + or make a record of a changeset for some other reason (for + example, in a bug report), use the hexadecimal + identifier.</para> + + </sect2> + <sect2> + <title>Viewing specific revisions</title> + + <para>To narrow the output of <command role="hg-cmd">hg + log</command> down to a single revision, use the <option + role="hg-opt-log">-r</option> (or <option + role="hg-opt-log">--rev</option>) option. You can use + either a revision number or a long-form changeset identifier, + and you can provide as many revisions as you want. <!-- + &interaction.tour.log-r; --></para> + + <para>If you want to see the history of several revisions + without having to list each one, you can use <emphasis>range + notation</emphasis>; this lets you express the idea <quote>I + want all revisions between $a$ and $b$, inclusive</quote>. + <!-- &interaction.tour.log.range; --> Mercurial also honours + the order in which you specify revisions, so <command + role="hg-cmd">hg log -r 2:4</command> prints $2,3,4$ while + <command role="hg-cmd">hg log -r 4:2</command> prints + $4,3,2$.</para> + + </sect2> + <sect2> + <title>More detailed information</title> + + <para>While the summary information printed by <command + role="hg-cmd">hg log</command> is useful if you already know + what you're looking for, you may need to see a complete + description of the change, or a list of the files changed, if + you're trying to decide whether a changeset is the one you're + looking for. The <command role="hg-cmd">hg log</command> + command's <option role="hg-opt-global">-v</option> (or <option + role="hg-opt-global">--verbose</option>) option gives you + this extra detail. <!-- &interaction.tour.log-v; --></para> + + <para>If you want to see both the description and content of a + change, add the <option role="hg-opt-log">-p</option> (or + <option role="hg-opt-log">--patch</option>) option. This + displays the content of a change as a <emphasis>unified + diff</emphasis> (if you've never seen a unified diff before, + see section <xref id="sec:mq:patch"/> for an + overview). <!-- &interaction.tour.log-vp; --></para> + + </sect2> + </sect1> + <sect1> + <title>All about command options</title> + + <para>Let's take a brief break from exploring Mercurial commands + to discuss a pattern in the way that they work; you may find + this useful to keep in mind as we continue our tour.</para> + + <para>Mercurial has a consistent and straightforward approach to + dealing with the options that you can pass to commands. It + follows the conventions for options that are common to modern + Linux and Unix systems.</para> + <itemizedlist> + <listitem><para>Every option has a long name. For example, as + we've already seen, the <command role="hg-cmd">hg + log</command> command accepts a <option + role="hg-opt-log">--rev</option> option.</para></listitem> + <listitem><para>Most options have short names, too. Instead of + <option role="hg-opt-log">--rev</option>, we can use <option + role="hg-opt-log">-r</option>. (The reason that some + options don't have short names is that the options in + question are rarely used.)</para></listitem> + <listitem><para>Long options start with two dashes (e.g. <option + role="hg-opt-log">--rev</option>), while short options + start with one (e.g. <option + role="hg-opt-log">-r</option>).</para></listitem> + <listitem><para>Option naming and usage is consistent across + commands. For example, every command that lets you specify + a changeset ID or revision number accepts both <option + role="hg-opt-log">-r</option> and <option + role="hg-opt-log">--rev</option> + arguments.</para></listitem></itemizedlist> + <para>In the examples throughout this book, I use short options + instead of long. This just reflects my own preference, so don't + read anything significant into it.</para> + + <para>Most commands that print output of some kind will print more + output when passed a <option role="hg-opt-global">-v</option> + (or <option role="hg-opt-global">--verbose</option>) option, and + less when passed <option role="hg-opt-global">-q</option> (or + <option role="hg-opt-global">--quiet</option>).</para> + + </sect1> + <sect1> + <title>Making and reviewing changes</title> + + <para>Now that we have a grasp of viewing history in Mercurial, + let's take a look at making some changes and examining + them.</para> + + <para>The first thing we'll do is isolate our experiment in a + repository of its own. We use the <command role="hg-cmd">hg + clone</command> command, but we don't need to clone a copy of + the remote repository. Since we already have a copy of it + locally, we can just clone that instead. This is much faster + than cloning over the network, and cloning a local repository + uses less disk space in most cases, too. <!-- + &interaction.tour.reclone; --> As an aside, it's often good + practice to keep a <quote>pristine</quote> copy of a remote + repository around, which you can then make temporary clones of + to create sandboxes for each task you want to work on. This + lets you work on multiple tasks in parallel, each isolated from + the others until it's complete and you're ready to integrate it + back. Because local clones are so cheap, there's almost no + overhead to cloning and destroying repositories whenever you + want.</para> + + <para>In our <filename class="directory">my-hello</filename> + repository, we have a file <filename>hello.c</filename> that + contains the classic <quote>hello, world</quote> program. Let's + use the ancient and venerable <command>sed</command> command to + edit this file so that it prints a second line of output. (I'm + only using <command>sed</command> to do this because it's easy + to write a scripted example this way. Since you're not under + the same constraint, you probably won't want to use + <command>sed</command>; simply use your preferred text editor to + do the same thing.) <!-- &interaction.tour.sed; --></para> + + <para>Mercurial's <command role="hg-cmd">hg status</command> + command will tell us what Mercurial knows about the files in the + repository. <!-- &interaction.tour.status; --> The <command + role="hg-cmd">hg status</command> command prints no output for + some files, but a line starting with + <quote><literal>M</literal></quote> for + <filename>hello.c</filename>. Unless you tell it to, <command + role="hg-cmd">hg status</command> will not print any output + for files that have not been modified.</para> + + <para>The <quote><literal>M</literal></quote> indicates that + Mercurial has noticed that we modified + <filename>hello.c</filename>. We didn't need to + <emphasis>inform</emphasis> Mercurial that we were going to + modify the file before we started, or that we had modified the + file after we were done; it was able to figure this out + itself.</para> + + <para>It's a little bit helpful to know that we've modified + <filename>hello.c</filename>, but we might prefer to know + exactly <emphasis>what</emphasis> changes we've made to it. To + do this, we use the <command role="hg-cmd">hg diff</command> + command. <!-- &interaction.tour.diff; --></para> + + </sect1> + <sect1> + <title>Recording changes in a new changeset</title> + + <para>We can modify files, build and test our changes, and use + <command role="hg-cmd">hg status</command> and <command + role="hg-cmd">hg diff</command> to review our changes, until + we're satisfied with what we've done and arrive at a natural + stopping point where we want to record our work in a new + changeset.</para> + + <para>The <command role="hg-cmd">hg commit</command> command lets + us create a new changeset; we'll usually refer to this as + <quote>making a commit</quote> or + <quote>committing</quote>.</para> + + <sect2> + <title>Setting up a username</title> + + <para>When you try to run <command role="hg-cmd">hg + commit</command> for the first time, it is not guaranteed to + succeed. Mercurial records your name and address with each + change that you commit, so that you and others will later be + able to tell who made each change. Mercurial tries to + automatically figure out a sensible username to commit the + change with. It will attempt each of the following methods, + in order:</para> + <orderedlist> + <listitem><para>If you specify a <option + role="hg-opt-commit">-u</option> option to the <command + role="hg-cmd">hg commit</command> command on the command + line, followed by a username, this is always given the + highest precedence.</para></listitem> + <listitem><para>If you have set the <envar>HGUSER</envar> + environment variable, this is checked + next.</para></listitem> + <listitem><para>If you create a file in your home directory + called <filename role="special">.hgrc</filename>, with a + <envar role="rc-item-ui">username</envar> entry, that will + be used next. To see what the contents of this file + should look like, refer to section <xref + id="sec:tour-basic:username"/> + below.</para></listitem> + <listitem><para>If you have set the <envar>EMAIL</envar> + environment variable, this will be used + next.</para></listitem> + <listitem><para>Mercurial will query your system to find out + your local user name and host name, and construct a + username from these components. Since this often results + in a username that is not very useful, it will print a + warning if it has to do + this.</para></listitem></orderedlist> + <listitem><para>If all of these mechanisms fail, Mercurial will + fail, printing an error message. In this case, it will not + let you commit until you set up a + username.</para></listitem> + <listitem><para>You should think of the <envar>HGUSER</envar> + environment variable and the <option + role="hg-opt-commit">-u</option> option to the <command + role="hg-cmd">hg commit</command> command as ways to + <emphasis>override</emphasis> Mercurial's default selection + of username. For normal use, the simplest and most robust + way to set a username for yourself is by creating a + <filename role="special">.hgrc</filename> file; see below + for details.</para></listitem> + <sect3> + <title>Creating a Mercurial configuration file</title> + <listitem><para>\label{sec:tour-basic:username}</para></listitem> + <listitem><para>To set a user name, use your favourite editor + to create a file called <filename + role="special">.hgrc</filename> in your home directory. + Mercurial will use this file to look up your personalised + configuration settings. The initial contents of your + <filename role="special">.hgrc</filename> should look like + this.</para></listitem><programlisting> + <listitem><para> # This is a Mercurial configuration file. + [ui] username = Firstname Lastname + <email.address@domain.net></para></listitem></programlisting> + <listitem><para>The <quote><literal>[ui]</literal></quote> + line begins a <emphasis>section</emphasis> of the config + file, so you can read the <quote><literal>username = + ...</literal></quote> line as meaning <quote>set the + value of the <literal>username</literal> item in the + <literal>ui</literal> section</quote>. A section + continues until a new section begins, or the end of the + file. Mercurial ignores empty lines and treats any text + from <quote><literal>#</literal></quote> to the end of a + line as a comment.</para></listitem> + </sect3> + <sect3> + <title>Choosing a user name</title> + + <listitem><para>You can use any text you like as the value of + the <literal>username</literal> config item, since this + information is for reading by other people, but for + interpreting by Mercurial. The convention that most + people follow is to use their name and email address, as + in the example above.</para></listitem> + <note> + <listitem><para> Mercurial's built-in web server obfuscates + email addresses, to make it more difficult for the email + harvesting tools that spammers use. This reduces the + likelihood that you'll start receiving more junk email + if you publish a Mercurial repository on the + web.</para></listitem></note> + + </sect3> + </sect2> + <sect2> + <title>Writing a commit message</title> + + <listitem><para>When we commit a change, Mercurial drops us into + a text editor, to enter a message that will describe the + modifications we've made in this changeset. This is called + the <emphasis>commit message</emphasis>. It will be a + record for readers of what we did and why, and it will be + printed by <command role="hg-cmd">hg log</command> after + we've finished committing. <!-- &interaction.tour.commit; + --></para></listitem> + <listitem><para>The editor that the <command role="hg-cmd">hg + commit</command> command drops us into will contain an + empty line, followed by a number of lines starting with + <quote><literal>HG:</literal></quote>.</para></listitem><programlisting> + <listitem><para> <emphasis>empty line</emphasis> HG: changed + hello.c</para></listitem></programlisting> + <listitem><para>Mercurial ignores the lines that start with + <quote><literal>HG:</literal></quote>; it uses them only to + tell us which files it's recording changes to. Modifying or + deleting these lines has no effect.</para></listitem> + </sect2> + <sect2> + <title>Writing a good commit message</title> + + <listitem><para>Since <command role="hg-cmd">hg log</command> + only prints the first line of a commit message by default, + it's best to write a commit message whose first line stands + alone. Here's a real example of a commit message that + <emphasis>doesn't</emphasis> follow this guideline, and + hence has a summary that is not + readable.</para></listitem><programlisting> + <listitem><para> changeset: 73:584af0e231be user: Censored + Person <censored.person@example.org> date: Tue Sep + 26 21:37:07 2006 -0700 summary: include + buildmeister/commondefs. Add an exports and + install</para></listitem></programlisting> + + <listitem><para>As far as the remainder of the contents of the + commit message are concerned, there are no hard-and-fast + rules. Mercurial itself doesn't interpret or care about the + contents of the commit message, though your project may have + policies that dictate a certain kind of + formatting.</para></listitem> + <listitem><para>My personal preference is for short, but + informative, commit messages that tell me something that I + can't figure out with a quick glance at the output of + <command role="hg-cmd">hg log + --patch</command>.</para></listitem> + </sect2> + <sect2> + <title>Aborting a commit</title> + + <listitem><para>If you decide that you don't want to commit + while in the middle of editing a commit message, simply exit + from your editor without saving the file that it's editing. + This will cause nothing to happen to either the repository + or the working directory.</para></listitem> + <listitem><para>If we run the <command role="hg-cmd">hg + commit</command> command without any arguments, it records + all of the changes we've made, as reported by <command + role="hg-cmd">hg status</command> and <command + role="hg-cmd">hg diff</command>.</para></listitem> + </sect2> + <sect2> + <title>Admiring our new handiwork</title> + + <listitem><para>Once we've finished the commit, we can use the + <command role="hg-cmd">hg tip</command> command to display + the changeset we just created. This command produces output + that is identical to <command role="hg-cmd">hg + log</command>, but it only displays the newest revision in + the repository. <!-- &interaction.tour.tip; --> We refer to + the newest revision in the repository as the tip revision, + or simply the tip.</para></listitem> + </sect2> + </sect1> + <sect1> + <title>Sharing changes</title> + + <listitem><para>We mentioned earlier that repositories in + Mercurial are self-contained. This means that the changeset + we just created exists only in our <filename + class="directory">my-hello</filename> repository. Let's + look at a few ways that we can propagate this change into + other repositories.</para></listitem> + <sect2> + <title>Pulling changes from another repository</title> + <listitem><para>\label{sec:tour:pull}</para></listitem> + <listitem><para>To get started, let's clone our original + <filename class="directory">hello</filename> repository, + which does not contain the change we just committed. We'll + call our temporary repository <filename + class="directory">hello-pull</filename>. <!-- + &interaction.tour.clone-pull; --></para></listitem> + <listitem><para>We'll use the <command role="hg-cmd">hg + pull</command> command to bring changes from <filename + class="directory">my-hello</filename> into <filename + class="directory">hello-pull</filename>. However, blindly + pulling unknown changes into a repository is a somewhat + scary prospect. Mercurial provides the <command + role="hg-cmd">hg incoming</command> command to tell us + what changes the <command role="hg-cmd">hg pull</command> + command <emphasis>would</emphasis> pull into the repository, + without actually pulling the changes in. <!-- + &interaction.tour.incoming; --> (Of course, someone could + cause more changesets to appear in the repository that we + ran <command role="hg-cmd">hg incoming</command> in, before + we get a chance to <command role="hg-cmd">hg pull</command> + the changes, so that we could end up pulling changes that we + didn't expect.)</para></listitem> + <listitem><para>Bringing changes into a repository is a simple + matter of running the <command role="hg-cmd">hg + pull</command> command, and telling it which repository to + pull from. <!-- &interaction.tour.pull; --> As you can see + from the before-and-after output of <command + role="hg-cmd">hg tip</command>, we have successfully + pulled changes into our repository. There remains one step + before we can see these changes in the working + directory.</para></listitem> + </sect2> + <sect2> + <title>Updating the working directory</title> + + <listitem><para>We have so far glossed over the relationship + between a repository and its working directory. The + <command role="hg-cmd">hg pull</command> command that we ran + in section <xref id="sec:tour:pull"/> brought changes into + the + repository, but if we check, there's no sign of those + changes in the working directory. This is because <command + role="hg-cmd">hg pull</command> does not (by default) + touch the working directory. Instead, we use the <command + role="hg-cmd">hg update</command> command to do this. <!-- + &interaction.tour.update; --></para></listitem> + <listitem><para>It might seem a bit strange that <command + role="hg-cmd">hg pull</command> doesn't update the working + directory automatically. There's actually a good reason for + this: you can use <command role="hg-cmd">hg update</command> + to update the working directory to the state it was in at + <emphasis>any revision</emphasis> in the history of the + repository. If you had the working directory updated to an + old revision---to hunt down the origin of a bug, say---and + ran a <command role="hg-cmd">hg pull</command> which + automatically updated the working directory to a new + revision, you might not be terribly happy.</para></listitem> + <listitem><para>However, since pull-then-update is such a common + thing to do, Mercurial lets you combine the two by passing + the <option role="hg-opt-pull">-u</option> option to + <command role="hg-cmd">hg + pull</command>.</para></listitem><programlisting> + <listitem><para> hg pull + -u</para></listitem></programlisting> + <listitem><para>If you look back at the output of <command + role="hg-cmd">hg pull</command> in section <xref + id="sec:tour:pull"/> when we ran it without <option + role="hg-opt-pull">-u</option>, you can see that it + printed a helpful reminder that we'd have to take an + explicit step to update the working + directory:</para></listitem><programlisting> + <listitem><para> (run 'hg update' to get a working + copy)</para></listitem></programlisting> + + <listitem><para>To find out what revision the working directory + is at, use the <command role="hg-cmd">hg parents</command> + command. <!-- &interaction.tour.parents; --> If you look + back at figure <xref id="fig:tour-basic:history"/>, you'll + see arrows connecting each changeset. The node that the + arrow leads <emphasis>from</emphasis> in each case is a + parent, and the node that the arrow leads + <emphasis>to</emphasis> is its child. The working directory + has a parent in just the same way; this is the changeset + that the working directory currently + contains.</para></listitem> + <listitem><para>To update the working directory to a particular + revision, give a revision number or changeset ID to the + <command role="hg-cmd">hg update</command> command. <!-- + &interaction.tour.older; --> If you omit an explicit + revision, <command role="hg-cmd">hg update</command> will + update to the tip revision, as shown by the second call to + <command role="hg-cmd">hg update</command> in the example + above.</para></listitem> + </sect2> + <sect2> + <title>Pushing changes to another repository</title> + + <listitem><para>Mercurial lets us push changes to another + repository, from the repository we're currently visiting. + As with the example of <command role="hg-cmd">hg + pull</command> above, we'll create a temporary repository + to push our changes into. <!-- &interaction.tour.clone-push; + --> The <command role="hg-cmd">hg outgoing</command> command + tells us what changes would be pushed into another + repository. <!-- &interaction.tour.outgoing; --> And the + <command role="hg-cmd">hg push</command> command does the + actual push. <!-- &interaction.tour.push; --> As with + <command role="hg-cmd">hg pull</command>, the <command + role="hg-cmd">hg push</command> command does not update + the working directory in the repository that it's pushing + changes into. (Unlike <command role="hg-cmd">hg + pull</command>, <command role="hg-cmd">hg push</command> + does not provide a <literal>-u</literal> option that updates + the other repository's working directory.)</para></listitem> + <listitem><para>What happens if we try to pull or push changes + and the receiving repository already has those changes? + Nothing too exciting. <!-- &interaction.tour.push.nothing; + --></para></listitem> + </sect2> + <sect2> + <title>Sharing changes over a network</title> + + <listitem><para>The commands we have covered in the previous few + sections are not limited to working with local repositories. + Each works in exactly the same fashion over a network + connection; simply pass in a URL instead of a local path. + <!-- &interaction.tour.outgoing.net; --> In this example, we + can see what changes we could push to the remote repository, + but the repository is understandably not set up to let + anonymous users push to it. <!-- &interaction.tour.push.net; + --></para></listitem> + </sect2> + </sect1> +</chapter> + +<!-- +local variables: +sgml-parent-document: ("00book.xml" "book" "chapter") +end: +-->
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/ch03-tour-merge.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,285 @@ +\chapter{A tour of Mercurial: merging work} +\label{chap:tour-merge} + +We've now covered cloning a repository, making changes in a +repository, and pulling or pushing changes from one repository into +another. Our next step is \emph{merging} changes from separate +repositories. + +\section{Merging streams of work} + +Merging is a fundamental part of working with a distributed revision +control tool. +\begin{itemize} +\item Alice and Bob each have a personal copy of a repository for a + project they're collaborating on. Alice fixes a bug in her + repository; Bob adds a new feature in his. They want the shared + repository to contain both the bug fix and the new feature. +\item I frequently work on several different tasks for a single + project at once, each safely isolated in its own repository. + Working this way means that I often need to merge one piece of my + own work with another. +\end{itemize} + +Because merging is such a common thing to need to do, Mercurial makes +it easy. Let's walk through the process. We'll begin by cloning yet +another repository (see how often they spring up?) and making a change +in it. +\interaction{tour.merge.clone} +We should now have two copies of \filename{hello.c} with different +contents. The histories of the two repositories have also diverged, +as illustrated in figure~\ref{fig:tour-merge:sep-repos}. +\interaction{tour.merge.cat} + +\begin{figure}[ht] + \centering + \grafix{tour-merge-sep-repos} + \caption{Divergent recent histories of the \dirname{my-hello} and + \dirname{my-new-hello} repositories} + \label{fig:tour-merge:sep-repos} +\end{figure} + +We already know that pulling changes from our \dirname{my-hello} +repository will have no effect on the working directory. +\interaction{tour.merge.pull} +However, the \hgcmd{pull} command says something about ``heads''. + +\subsection{Head changesets} + +A head is a change that has no descendants, or children, as they're +also known. The tip revision is thus a head, because the newest +revision in a repository doesn't have any children, but a repository +can contain more than one head. + +\begin{figure}[ht] + \centering + \grafix{tour-merge-pull} + \caption{Repository contents after pulling from \dirname{my-hello} into + \dirname{my-new-hello}} + \label{fig:tour-merge:pull} +\end{figure} + +In figure~\ref{fig:tour-merge:pull}, you can see the effect of the +pull from \dirname{my-hello} into \dirname{my-new-hello}. The history +that was already present in \dirname{my-new-hello} is untouched, but a +new revision has been added. By referring to +figure~\ref{fig:tour-merge:sep-repos}, we can see that the +\emph{changeset ID} remains the same in the new repository, but the +\emph{revision number} has changed. (This, incidentally, is a fine +example of why it's not safe to use revision numbers when discussing +changesets.) We can view the heads in a repository using the +\hgcmd{heads} command. +\interaction{tour.merge.heads} + +\subsection{Performing the merge} + +What happens if we try to use the normal \hgcmd{update} command to +update to the new tip? +\interaction{tour.merge.update} +Mercurial is telling us that the \hgcmd{update} command won't do a +merge; it won't update the working directory when it thinks we might +be wanting to do a merge, unless we force it to do so. Instead, we +use the \hgcmd{merge} command to merge the two heads. +\interaction{tour.merge.merge} + +\begin{figure}[ht] + \centering + \grafix{tour-merge-merge} + \caption{Working directory and repository during merge, and following commit} + \label{fig:tour-merge:merge} +\end{figure} + +This updates the working directory so that it contains changes from +\emph{both} heads, which is reflected in both the output of +\hgcmd{parents} and the contents of \filename{hello.c}. +\interaction{tour.merge.parents} + +\subsection{Committing the results of the merge} + +Whenever we've done a merge, \hgcmd{parents} will display two parents +until we \hgcmd{commit} the results of the merge. +\interaction{tour.merge.commit} +We now have a new tip revision; notice that it has \emph{both} of +our former heads as its parents. These are the same revisions that +were previously displayed by \hgcmd{parents}. +\interaction{tour.merge.tip} +In figure~\ref{fig:tour-merge:merge}, you can see a representation of +what happens to the working directory during the merge, and how this +affects the repository when the commit happens. During the merge, the +working directory has two parent changesets, and these become the +parents of the new changeset. + +\section{Merging conflicting changes} + +Most merges are simple affairs, but sometimes you'll find yourself +merging changes where each modifies the same portions of the same +files. Unless both modifications are identical, this results in a +\emph{conflict}, where you have to decide how to reconcile the +different changes into something coherent. + +\begin{figure}[ht] + \centering + \grafix{tour-merge-conflict} + \caption{Conflicting changes to a document} + \label{fig:tour-merge:conflict} +\end{figure} + +Figure~\ref{fig:tour-merge:conflict} illustrates an instance of two +conflicting changes to a document. We started with a single version +of the file; then we made some changes; while someone else made +different changes to the same text. Our task in resolving the +conflicting changes is to decide what the file should look like. + +Mercurial doesn't have a built-in facility for handling conflicts. +Instead, it runs an external program called \command{hgmerge}. This +is a shell script that is bundled with Mercurial; you can change it to +behave however you please. What it does by default is try to find one +of several different merging tools that are likely to be installed on +your system. It first tries a few fully automatic merging tools; if +these don't succeed (because the resolution process requires human +guidance) or aren't present, the script tries a few different +graphical merging tools. + +It's also possible to get Mercurial to run another program or script +instead of \command{hgmerge}, by setting the \envar{HGMERGE} +environment variable to the name of your preferred program. + +\subsection{Using a graphical merge tool} + +My preferred graphical merge tool is \command{kdiff3}, which I'll use +to describe the features that are common to graphical file merging +tools. You can see a screenshot of \command{kdiff3} in action in +figure~\ref{fig:tour-merge:kdiff3}. The kind of merge it is +performing is called a \emph{three-way merge}, because there are three +different versions of the file of interest to us. The tool thus +splits the upper portion of the window into three panes: +\begin{itemize} +\item At the left is the \emph{base} version of the file, i.e.~the + most recent version from which the two versions we're trying to + merge are descended. +\item In the middle is ``our'' version of the file, with the contents + that we modified. +\item On the right is ``their'' version of the file, the one that + from the changeset that we're trying to merge with. +\end{itemize} +In the pane below these is the current \emph{result} of the merge. +Our task is to replace all of the red text, which indicates unresolved +conflicts, with some sensible merger of the ``ours'' and ``theirs'' +versions of the file. + +All four of these panes are \emph{locked together}; if we scroll +vertically or horizontally in any of them, the others are updated to +display the corresponding sections of their respective files. + +\begin{figure}[ht] + \centering + \grafix{kdiff3} + \caption{Using \command{kdiff3} to merge versions of a file} + \label{fig:tour-merge:kdiff3} +\end{figure} + +For each conflicting portion of the file, we can choose to resolve +the conflict using some combination of text from the base version, +ours, or theirs. We can also manually edit the merged file at any +time, in case we need to make further modifications. + +There are \emph{many} file merging tools available, too many to cover +here. They vary in which platforms they are available for, and in +their particular strengths and weaknesses. Most are tuned for merging +files containing plain text, while a few are aimed at specialised file +formats (generally XML). + +\subsection{A worked example} + +In this example, we will reproduce the file modification history of +figure~\ref{fig:tour-merge:conflict} above. Let's begin by creating a +repository with a base version of our document. +\interaction{tour-merge-conflict.wife} +We'll clone the repository and make a change to the file. +\interaction{tour-merge-conflict.cousin} +And another clone, to simulate someone else making a change to the +file. (This hints at the idea that it's not all that unusual to merge +with yourself when you isolate tasks in separate repositories, and +indeed to find and resolve conflicts while doing so.) +\interaction{tour-merge-conflict.son} +Having created two different versions of the file, we'll set up an +environment suitable for running our merge. +\interaction{tour-merge-conflict.pull} + +In this example, I won't use Mercurial's normal \command{hgmerge} +program to do the merge, because it would drop my nice automated +example-running tool into a graphical user interface. Instead, I'll +set \envar{HGMERGE} to tell Mercurial to use the non-interactive +\command{merge} command. This is bundled with many Unix-like systems. +If you're following this example on your computer, don't bother +setting \envar{HGMERGE}. + +\textbf{XXX FIX THIS EXAMPLE.} + +\interaction{tour-merge-conflict.merge} +Because \command{merge} can't resolve the conflicting changes, it +leaves \emph{merge markers} inside the file that has conflicts, +indicating which lines have conflicts, and whether they came from our +version of the file or theirs. + +Mercurial can tell from the way \command{merge} exits that it wasn't +able to merge successfully, so it tells us what commands we'll need to +run if we want to redo the merging operation. This could be useful +if, for example, we were running a graphical merge tool and quit +because we were confused or realised we had made a mistake. + +If automatic or manual merges fail, there's nothing to prevent us from +``fixing up'' the affected files ourselves, and committing the results +of our merge: +\interaction{tour-merge-conflict.commit} + +\section{Simplifying the pull-merge-commit sequence} +\label{sec:tour-merge:fetch} + +The process of merging changes as outlined above is straightforward, +but requires running three commands in sequence. +\begin{codesample2} + hg pull + hg merge + hg commit -m 'Merged remote changes' +\end{codesample2} +In the case of the final commit, you also need to enter a commit +message, which is almost always going to be a piece of uninteresting +``boilerplate'' text. + +It would be nice to reduce the number of steps needed, if this were +possible. Indeed, Mercurial is distributed with an extension called +\hgext{fetch} that does just this. + +Mercurial provides a flexible extension mechanism that lets people +extend its functionality, while keeping the core of Mercurial small +and easy to deal with. Some extensions add new commands that you can +use from the command line, while others work ``behind the scenes,'' +for example adding capabilities to the server. + +The \hgext{fetch} extension adds a new command called, not +surprisingly, \hgcmd{fetch}. This extension acts as a combination of +\hgcmd{pull}, \hgcmd{update} and \hgcmd{merge}. It begins by pulling +changes from another repository into the current repository. If it +finds that the changes added a new head to the repository, it begins a +merge, then commits the result of the merge with an +automatically-generated commit message. If no new heads were added, +it updates the working directory to the new tip changeset. + +Enabling the \hgext{fetch} extension is easy. Edit your +\sfilename{.hgrc}, and either go to the \rcsection{extensions} section +or create an \rcsection{extensions} section. Then add a line that +simply reads ``\Verb+fetch +''. +\begin{codesample2} + [extensions] + fetch = +\end{codesample2} +(Normally, on the right-hand side of the ``\texttt{=}'' would appear +the location of the extension, but since the \hgext{fetch} extension +is in the standard distribution, Mercurial knows where to search for +it.) + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/ch04-concepts.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,574 @@ +\chapter{Behind the scenes} +\label{chap:concepts} + +Unlike many revision control systems, the concepts upon which +Mercurial is built are simple enough that it's easy to understand how +the software really works. Knowing this certainly isn't necessary, +but I find it useful to have a ``mental model'' of what's going on. + +This understanding gives me confidence that Mercurial has been +carefully designed to be both \emph{safe} and \emph{efficient}. And +just as importantly, if it's easy for me to retain a good idea of what +the software is doing when I perform a revision control task, I'm less +likely to be surprised by its behaviour. + +In this chapter, we'll initially cover the core concepts behind +Mercurial's design, then continue to discuss some of the interesting +details of its implementation. + +\section{Mercurial's historical record} + +\subsection{Tracking the history of a single file} + +When Mercurial tracks modifications to a file, it stores the history +of that file in a metadata object called a \emph{filelog}. Each entry +in the filelog contains enough information to reconstruct one revision +of the file that is being tracked. Filelogs are stored as files in +the \sdirname{.hg/store/data} directory. A filelog contains two kinds +of information: revision data, and an index to help Mercurial to find +a revision efficiently. + +A file that is large, or has a lot of history, has its filelog stored +in separate data (``\texttt{.d}'' suffix) and index (``\texttt{.i}'' +suffix) files. For small files without much history, the revision +data and index are combined in a single ``\texttt{.i}'' file. The +correspondence between a file in the working directory and the filelog +that tracks its history in the repository is illustrated in +figure~\ref{fig:concepts:filelog}. + +\begin{figure}[ht] + \centering + \grafix{filelog} + \caption{Relationships between files in working directory and filelogs in repository} + \label{fig:concepts:filelog} +\end{figure} + +\subsection{Managing tracked files} + +Mercurial uses a structure called a \emph{manifest} to collect +together information about the files that it tracks. Each entry in +the manifest contains information about the files present in a single +changeset. An entry records which files are present in the changeset, +the revision of each file, and a few other pieces of file metadata. + +\subsection{Recording changeset information} + +The \emph{changelog} contains information about each changeset. Each +revision records who committed a change, the changeset comment, other +pieces of changeset-related information, and the revision of the +manifest to use. + +\subsection{Relationships between revisions} + +Within a changelog, a manifest, or a filelog, each revision stores a +pointer to its immediate parent (or to its two parents, if it's a +merge revision). As I mentioned above, there are also relationships +between revisions \emph{across} these structures, and they are +hierarchical in nature. + +For every changeset in a repository, there is exactly one revision +stored in the changelog. Each revision of the changelog contains a +pointer to a single revision of the manifest. A revision of the +manifest stores a pointer to a single revision of each filelog tracked +when that changeset was created. These relationships are illustrated +in figure~\ref{fig:concepts:metadata}. + +\begin{figure}[ht] + \centering + \grafix{metadata} + \caption{Metadata relationships} + \label{fig:concepts:metadata} +\end{figure} + +As the illustration shows, there is \emph{not} a ``one to one'' +relationship between revisions in the changelog, manifest, or filelog. +If the manifest hasn't changed between two changesets, the changelog +entries for those changesets will point to the same revision of the +manifest. If a file that Mercurial tracks hasn't changed between two +changesets, the entry for that file in the two revisions of the +manifest will point to the same revision of its filelog. + +\section{Safe, efficient storage} + +The underpinnings of changelogs, manifests, and filelogs are provided +by a single structure called the \emph{revlog}. + +\subsection{Efficient storage} + +The revlog provides efficient storage of revisions using a +\emph{delta} mechanism. Instead of storing a complete copy of a file +for each revision, it stores the changes needed to transform an older +revision into the new revision. For many kinds of file data, these +deltas are typically a fraction of a percent of the size of a full +copy of a file. + +Some obsolete revision control systems can only work with deltas of +text files. They must either store binary files as complete snapshots +or encoded into a text representation, both of which are wasteful +approaches. Mercurial can efficiently handle deltas of files with +arbitrary binary contents; it doesn't need to treat text as special. + +\subsection{Safe operation} +\label{sec:concepts:txn} + +Mercurial only ever \emph{appends} data to the end of a revlog file. +It never modifies a section of a file after it has written it. This +is both more robust and efficient than schemes that need to modify or +rewrite data. + +In addition, Mercurial treats every write as part of a +\emph{transaction} that can span a number of files. A transaction is +\emph{atomic}: either the entire transaction succeeds and its effects +are all visible to readers in one go, or the whole thing is undone. +This guarantee of atomicity means that if you're running two copies of +Mercurial, where one is reading data and one is writing it, the reader +will never see a partially written result that might confuse it. + +The fact that Mercurial only appends to files makes it easier to +provide this transactional guarantee. The easier it is to do stuff +like this, the more confident you should be that it's done correctly. + +\subsection{Fast retrieval} + +Mercurial cleverly avoids a pitfall common to all earlier +revision control systems: the problem of \emph{inefficient retrieval}. +Most revision control systems store the contents of a revision as an +incremental series of modifications against a ``snapshot''. To +reconstruct a specific revision, you must first read the snapshot, and +then every one of the revisions between the snapshot and your target +revision. The more history that a file accumulates, the more +revisions you must read, hence the longer it takes to reconstruct a +particular revision. + +\begin{figure}[ht] + \centering + \grafix{snapshot} + \caption{Snapshot of a revlog, with incremental deltas} + \label{fig:concepts:snapshot} +\end{figure} + +The innovation that Mercurial applies to this problem is simple but +effective. Once the cumulative amount of delta information stored +since the last snapshot exceeds a fixed threshold, it stores a new +snapshot (compressed, of course), instead of another delta. This +makes it possible to reconstruct \emph{any} revision of a file +quickly. This approach works so well that it has since been copied by +several other revision control systems. + +Figure~\ref{fig:concepts:snapshot} illustrates the idea. In an entry +in a revlog's index file, Mercurial stores the range of entries from +the data file that it must read to reconstruct a particular revision. + +\subsubsection{Aside: the influence of video compression} + +If you're familiar with video compression or have ever watched a TV +feed through a digital cable or satellite service, you may know that +most video compression schemes store each frame of video as a delta +against its predecessor frame. In addition, these schemes use +``lossy'' compression techniques to increase the compression ratio, so +visual errors accumulate over the course of a number of inter-frame +deltas. + +Because it's possible for a video stream to ``drop out'' occasionally +due to signal glitches, and to limit the accumulation of artefacts +introduced by the lossy compression process, video encoders +periodically insert a complete frame (called a ``key frame'') into the +video stream; the next delta is generated against that frame. This +means that if the video signal gets interrupted, it will resume once +the next key frame is received. Also, the accumulation of encoding +errors restarts anew with each key frame. + +\subsection{Identification and strong integrity} + +Along with delta or snapshot information, a revlog entry contains a +cryptographic hash of the data that it represents. This makes it +difficult to forge the contents of a revision, and easy to detect +accidental corruption. + +Hashes provide more than a mere check against corruption; they are +used as the identifiers for revisions. The changeset identification +hashes that you see as an end user are from revisions of the +changelog. Although filelogs and the manifest also use hashes, +Mercurial only uses these behind the scenes. + +Mercurial verifies that hashes are correct when it retrieves file +revisions and when it pulls changes from another repository. If it +encounters an integrity problem, it will complain and stop whatever +it's doing. + +In addition to the effect it has on retrieval efficiency, Mercurial's +use of periodic snapshots makes it more robust against partial data +corruption. If a revlog becomes partly corrupted due to a hardware +error or system bug, it's often possible to reconstruct some or most +revisions from the uncorrupted sections of the revlog, both before and +after the corrupted section. This would not be possible with a +delta-only storage model. + +\section{Revision history, branching, and merging} + +Every entry in a Mercurial revlog knows the identity of its immediate +ancestor revision, usually referred to as its \emph{parent}. In fact, +a revision contains room for not one parent, but two. Mercurial uses +a special hash, called the ``null ID'', to represent the idea ``there +is no parent here''. This hash is simply a string of zeroes. + +In figure~\ref{fig:concepts:revlog}, you can see an example of the +conceptual structure of a revlog. Filelogs, manifests, and changelogs +all have this same structure; they differ only in the kind of data +stored in each delta or snapshot. + +The first revision in a revlog (at the bottom of the image) has the +null ID in both of its parent slots. For a ``normal'' revision, its +first parent slot contains the ID of its parent revision, and its +second contains the null ID, indicating that the revision has only one +real parent. Any two revisions that have the same parent ID are +branches. A revision that represents a merge between branches has two +normal revision IDs in its parent slots. + +\begin{figure}[ht] + \centering + \grafix{revlog} + \caption{} + \label{fig:concepts:revlog} +\end{figure} + +\section{The working directory} + +In the working directory, Mercurial stores a snapshot of the files +from the repository as of a particular changeset. + +The working directory ``knows'' which changeset it contains. When you +update the working directory to contain a particular changeset, +Mercurial looks up the appropriate revision of the manifest to find +out which files it was tracking at the time that changeset was +committed, and which revision of each file was then current. It then +recreates a copy of each of those files, with the same contents it had +when the changeset was committed. + +The \emph{dirstate} contains Mercurial's knowledge of the working +directory. This details which changeset the working directory is +updated to, and all of the files that Mercurial is tracking in the +working directory. + +Just as a revision of a revlog has room for two parents, so that it +can represent either a normal revision (with one parent) or a merge of +two earlier revisions, the dirstate has slots for two parents. When +you use the \hgcmd{update} command, the changeset that you update to +is stored in the ``first parent'' slot, and the null ID in the second. +When you \hgcmd{merge} with another changeset, the first parent +remains unchanged, and the second parent is filled in with the +changeset you're merging with. The \hgcmd{parents} command tells you +what the parents of the dirstate are. + +\subsection{What happens when you commit} + +The dirstate stores parent information for more than just book-keeping +purposes. Mercurial uses the parents of the dirstate as \emph{the parents of a new changeset} when you perform a commit. + +\begin{figure}[ht] + \centering + \grafix{wdir} + \caption{The working directory can have two parents} + \label{fig:concepts:wdir} +\end{figure} + +Figure~\ref{fig:concepts:wdir} shows the normal state of the working +directory, where it has a single changeset as parent. That changeset +is the \emph{tip}, the newest changeset in the repository that has no +children. + +\begin{figure}[ht] + \centering + \grafix{wdir-after-commit} + \caption{The working directory gains new parents after a commit} + \label{fig:concepts:wdir-after-commit} +\end{figure} + +It's useful to think of the working directory as ``the changeset I'm +about to commit''. Any files that you tell Mercurial that you've +added, removed, renamed, or copied will be reflected in that +changeset, as will modifications to any files that Mercurial is +already tracking; the new changeset will have the parents of the +working directory as its parents. + +After a commit, Mercurial will update the parents of the working +directory, so that the first parent is the ID of the new changeset, +and the second is the null ID. This is shown in +figure~\ref{fig:concepts:wdir-after-commit}. Mercurial doesn't touch +any of the files in the working directory when you commit; it just +modifies the dirstate to note its new parents. + +\subsection{Creating a new head} + +It's perfectly normal to update the working directory to a changeset +other than the current tip. For example, you might want to know what +your project looked like last Tuesday, or you could be looking through +changesets to see which one introduced a bug. In cases like this, the +natural thing to do is update the working directory to the changeset +you're interested in, and then examine the files in the working +directory directly to see their contents as they were when you +committed that changeset. The effect of this is shown in +figure~\ref{fig:concepts:wdir-pre-branch}. + +\begin{figure}[ht] + \centering + \grafix{wdir-pre-branch} + \caption{The working directory, updated to an older changeset} + \label{fig:concepts:wdir-pre-branch} +\end{figure} + +Having updated the working directory to an older changeset, what +happens if you make some changes, and then commit? Mercurial behaves +in the same way as I outlined above. The parents of the working +directory become the parents of the new changeset. This new changeset +has no children, so it becomes the new tip. And the repository now +contains two changesets that have no children; we call these +\emph{heads}. You can see the structure that this creates in +figure~\ref{fig:concepts:wdir-branch}. + +\begin{figure}[ht] + \centering + \grafix{wdir-branch} + \caption{After a commit made while synced to an older changeset} + \label{fig:concepts:wdir-branch} +\end{figure} + +\begin{note} + If you're new to Mercurial, you should keep in mind a common + ``error'', which is to use the \hgcmd{pull} command without any + options. By default, the \hgcmd{pull} command \emph{does not} + update the working directory, so you'll bring new changesets into + your repository, but the working directory will stay synced at the + same changeset as before the pull. If you make some changes and + commit afterwards, you'll thus create a new head, because your + working directory isn't synced to whatever the current tip is. + + I put the word ``error'' in quotes because all that you need to do + to rectify this situation is \hgcmd{merge}, then \hgcmd{commit}. In + other words, this almost never has negative consequences; it just + surprises people. I'll discuss other ways to avoid this behaviour, + and why Mercurial behaves in this initially surprising way, later + on. +\end{note} + +\subsection{Merging heads} + +When you run the \hgcmd{merge} command, Mercurial leaves the first +parent of the working directory unchanged, and sets the second parent +to the changeset you're merging with, as shown in +figure~\ref{fig:concepts:wdir-merge}. + +\begin{figure}[ht] + \centering + \grafix{wdir-merge} + \caption{Merging two heads} + \label{fig:concepts:wdir-merge} +\end{figure} + +Mercurial also has to modify the working directory, to merge the files +managed in the two changesets. Simplified a little, the merging +process goes like this, for every file in the manifests of both +changesets. +\begin{itemize} +\item If neither changeset has modified a file, do nothing with that + file. +\item If one changeset has modified a file, and the other hasn't, + create the modified copy of the file in the working directory. +\item If one changeset has removed a file, and the other hasn't (or + has also deleted it), delete the file from the working directory. +\item If one changeset has removed a file, but the other has modified + the file, ask the user what to do: keep the modified file, or remove + it? +\item If both changesets have modified a file, invoke an external + merge program to choose the new contents for the merged file. This + may require input from the user. +\item If one changeset has modified a file, and the other has renamed + or copied the file, make sure that the changes follow the new name + of the file. +\end{itemize} +There are more details---merging has plenty of corner cases---but +these are the most common choices that are involved in a merge. As +you can see, most cases are completely automatic, and indeed most +merges finish automatically, without requiring your input to resolve +any conflicts. + +When you're thinking about what happens when you commit after a merge, +once again the working directory is ``the changeset I'm about to +commit''. After the \hgcmd{merge} command completes, the working +directory has two parents; these will become the parents of the new +changeset. + +Mercurial lets you perform multiple merges, but you must commit the +results of each individual merge as you go. This is necessary because +Mercurial only tracks two parents for both revisions and the working +directory. While it would be technically possible to merge multiple +changesets at once, the prospect of user confusion and making a +terrible mess of a merge immediately becomes overwhelming. + +\section{Other interesting design features} + +In the sections above, I've tried to highlight some of the most +important aspects of Mercurial's design, to illustrate that it pays +careful attention to reliability and performance. However, the +attention to detail doesn't stop there. There are a number of other +aspects of Mercurial's construction that I personally find +interesting. I'll detail a few of them here, separate from the ``big +ticket'' items above, so that if you're interested, you can gain a +better idea of the amount of thinking that goes into a well-designed +system. + +\subsection{Clever compression} + +When appropriate, Mercurial will store both snapshots and deltas in +compressed form. It does this by always \emph{trying to} compress a +snapshot or delta, but only storing the compressed version if it's +smaller than the uncompressed version. + +This means that Mercurial does ``the right thing'' when storing a file +whose native form is compressed, such as a \texttt{zip} archive or a +JPEG image. When these types of files are compressed a second time, +the resulting file is usually bigger than the once-compressed form, +and so Mercurial will store the plain \texttt{zip} or JPEG. + +Deltas between revisions of a compressed file are usually larger than +snapshots of the file, and Mercurial again does ``the right thing'' in +these cases. It finds that such a delta exceeds the threshold at +which it should store a complete snapshot of the file, so it stores +the snapshot, again saving space compared to a naive delta-only +approach. + +\subsubsection{Network recompression} + +When storing revisions on disk, Mercurial uses the ``deflate'' +compression algorithm (the same one used by the popular \texttt{zip} +archive format), which balances good speed with a respectable +compression ratio. However, when transmitting revision data over a +network connection, Mercurial uncompresses the compressed revision +data. + +If the connection is over HTTP, Mercurial recompresses the entire +stream of data using a compression algorithm that gives a better +compression ratio (the Burrows-Wheeler algorithm from the widely used +\texttt{bzip2} compression package). This combination of algorithm +and compression of the entire stream (instead of a revision at a time) +substantially reduces the number of bytes to be transferred, yielding +better network performance over almost all kinds of network. + +(If the connection is over \command{ssh}, Mercurial \emph{doesn't} +recompress the stream, because \command{ssh} can already do this +itself.) + +\subsection{Read/write ordering and atomicity} + +Appending to files isn't the whole story when it comes to guaranteeing +that a reader won't see a partial write. If you recall +figure~\ref{fig:concepts:metadata}, revisions in the changelog point to +revisions in the manifest, and revisions in the manifest point to +revisions in filelogs. This hierarchy is deliberate. + +A writer starts a transaction by writing filelog and manifest data, +and doesn't write any changelog data until those are finished. A +reader starts by reading changelog data, then manifest data, followed +by filelog data. + +Since the writer has always finished writing filelog and manifest data +before it writes to the changelog, a reader will never read a pointer +to a partially written manifest revision from the changelog, and it will +never read a pointer to a partially written filelog revision from the +manifest. + +\subsection{Concurrent access} + +The read/write ordering and atomicity guarantees mean that Mercurial +never needs to \emph{lock} a repository when it's reading data, even +if the repository is being written to while the read is occurring. +This has a big effect on scalability; you can have an arbitrary number +of Mercurial processes safely reading data from a repository safely +all at once, no matter whether it's being written to or not. + +The lockless nature of reading means that if you're sharing a +repository on a multi-user system, you don't need to grant other local +users permission to \emph{write} to your repository in order for them +to be able to clone it or pull changes from it; they only need +\emph{read} permission. (This is \emph{not} a common feature among +revision control systems, so don't take it for granted! Most require +readers to be able to lock a repository to access it safely, and this +requires write permission on at least one directory, which of course +makes for all kinds of nasty and annoying security and administrative +problems.) + +Mercurial uses locks to ensure that only one process can write to a +repository at a time (the locking mechanism is safe even over +filesystems that are notoriously hostile to locking, such as NFS). If +a repository is locked, a writer will wait for a while to retry if the +repository becomes unlocked, but if the repository remains locked for +too long, the process attempting to write will time out after a while. +This means that your daily automated scripts won't get stuck forever +and pile up if a system crashes unnoticed, for example. (Yes, the +timeout is configurable, from zero to infinity.) + +\subsubsection{Safe dirstate access} + +As with revision data, Mercurial doesn't take a lock to read the +dirstate file; it does acquire a lock to write it. To avoid the +possibility of reading a partially written copy of the dirstate file, +Mercurial writes to a file with a unique name in the same directory as +the dirstate file, then renames the temporary file atomically to +\filename{dirstate}. The file named \filename{dirstate} is thus +guaranteed to be complete, not partially written. + +\subsection{Avoiding seeks} + +Critical to Mercurial's performance is the avoidance of seeks of the +disk head, since any seek is far more expensive than even a +comparatively large read operation. + +This is why, for example, the dirstate is stored in a single file. If +there were a dirstate file per directory that Mercurial tracked, the +disk would seek once per directory. Instead, Mercurial reads the +entire single dirstate file in one step. + +Mercurial also uses a ``copy on write'' scheme when cloning a +repository on local storage. Instead of copying every revlog file +from the old repository into the new repository, it makes a ``hard +link'', which is a shorthand way to say ``these two names point to the +same file''. When Mercurial is about to write to one of a revlog's +files, it checks to see if the number of names pointing at the file is +greater than one. If it is, more than one repository is using the +file, so Mercurial makes a new copy of the file that is private to +this repository. + +A few revision control developers have pointed out that this idea of +making a complete private copy of a file is not very efficient in its +use of storage. While this is true, storage is cheap, and this method +gives the highest performance while deferring most book-keeping to the +operating system. An alternative scheme would most likely reduce +performance and increase the complexity of the software, each of which +is much more important to the ``feel'' of day-to-day use. + +\subsection{Other contents of the dirstate} + +Because Mercurial doesn't force you to tell it when you're modifying a +file, it uses the dirstate to store some extra information so it can +determine efficiently whether you have modified a file. For each file +in the working directory, it stores the time that it last modified the +file itself, and the size of the file at that time. + +When you explicitly \hgcmd{add}, \hgcmd{remove}, \hgcmd{rename} or +\hgcmd{copy} files, Mercurial updates the dirstate so that it knows +what to do with those files when you commit. + +When Mercurial is checking the states of files in the working +directory, it first checks a file's modification time. If that has +not changed, the file must not have been modified. If the file's size +has changed, the file must have been modified. If the modification +time has changed, but the size has not, only then does Mercurial need +to read the actual contents of the file to see if they've changed. +Storing these few extra pieces of information dramatically reduces the +amount of data that Mercurial needs to read, which yields large +performance improvements compared to other revision control systems. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/ch05-daily.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,379 @@ +\chapter{Mercurial in daily use} +\label{chap:daily} + +\section{Telling Mercurial which files to track} + +Mercurial does not work with files in your repository unless you tell +it to manage them. The \hgcmd{status} command will tell you which +files Mercurial doesn't know about; it uses a ``\texttt{?}'' to +display such files. + +To tell Mercurial to track a file, use the \hgcmd{add} command. Once +you have added a file, the entry in the output of \hgcmd{status} for +that file changes from ``\texttt{?}'' to ``\texttt{A}''. +\interaction{daily.files.add} + +After you run a \hgcmd{commit}, the files that you added before the +commit will no longer be listed in the output of \hgcmd{status}. The +reason for this is that \hgcmd{status} only tells you about +``interesting'' files---those that you have modified or told Mercurial +to do something with---by default. If you have a repository that +contains thousands of files, you will rarely want to know about files +that Mercurial is tracking, but that have not changed. (You can still +get this information; we'll return to this later.) + +Once you add a file, Mercurial doesn't do anything with it +immediately. Instead, it will take a snapshot of the file's state the +next time you perform a commit. It will then continue to track the +changes you make to the file every time you commit, until you remove +the file. + +\subsection{Explicit versus implicit file naming} + +A useful behaviour that Mercurial has is that if you pass the name of +a directory to a command, every Mercurial command will treat this as +``I want to operate on every file in this directory and its +subdirectories''. +\interaction{daily.files.add-dir} +Notice in this example that Mercurial printed the names of the files +it added, whereas it didn't do so when we added the file named +\filename{a} in the earlier example. + +What's going on is that in the former case, we explicitly named the +file to add on the command line, so the assumption that Mercurial +makes in such cases is that you know what you were doing, and it +doesn't print any output. + +However, when we \emph{imply} the names of files by giving the name of +a directory, Mercurial takes the extra step of printing the name of +each file that it does something with. This makes it more clear what +is happening, and reduces the likelihood of a silent and nasty +surprise. This behaviour is common to most Mercurial commands. + +\subsection{Aside: Mercurial tracks files, not directories} + +Mercurial does not track directory information. Instead, it tracks +the path to a file. Before creating a file, it first creates any +missing directory components of the path. After it deletes a file, it +then deletes any empty directories that were in the deleted file's +path. This sounds like a trivial distinction, but it has one minor +practical consequence: it is not possible to represent a completely +empty directory in Mercurial. + +Empty directories are rarely useful, and there are unintrusive +workarounds that you can use to achieve an appropriate effect. The +developers of Mercurial thus felt that the complexity that would be +required to manage empty directories was not worth the limited benefit +this feature would bring. + +If you need an empty directory in your repository, there are a few +ways to achieve this. One is to create a directory, then \hgcmd{add} a +``hidden'' file to that directory. On Unix-like systems, any file +name that begins with a period (``\texttt{.}'') is treated as hidden +by most commands and GUI tools. This approach is illustrated in +figure~\ref{ex:daily:hidden}. + +\begin{figure}[ht] + \interaction{daily.files.hidden} + \caption{Simulating an empty directory using a hidden file} + \label{ex:daily:hidden} +\end{figure} + +Another way to tackle a need for an empty directory is to simply +create one in your automated build scripts before they will need it. + +\section{How to stop tracking a file} + +Once you decide that a file no longer belongs in your repository, use +the \hgcmd{remove} command; this deletes the file, and tells Mercurial +to stop tracking it. A removed file is represented in the output of +\hgcmd{status} with a ``\texttt{R}''. +\interaction{daily.files.remove} + +After you \hgcmd{remove} a file, Mercurial will no longer track +changes to that file, even if you recreate a file with the same name +in your working directory. If you do recreate a file with the same +name and want Mercurial to track the new file, simply \hgcmd{add} it. +Mercurial will know that the newly added file is not related to the +old file of the same name. + +\subsection{Removing a file does not affect its history} + +It is important to understand that removing a file has only two +effects. +\begin{itemize} +\item It removes the current version of the file from the working + directory. +\item It stops Mercurial from tracking changes to the file, from the + time of the next commit. +\end{itemize} +Removing a file \emph{does not} in any way alter the \emph{history} of +the file. + +If you update the working directory to a changeset in which a file +that you have removed was still tracked, it will reappear in the +working directory, with the contents it had when you committed that +changeset. If you then update the working directory to a later +changeset, in which the file had been removed, Mercurial will once +again remove the file from the working directory. + +\subsection{Missing files} + +Mercurial considers a file that you have deleted, but not used +\hgcmd{remove} to delete, to be \emph{missing}. A missing file is +represented with ``\texttt{!}'' in the output of \hgcmd{status}. +Mercurial commands will not generally do anything with missing files. +\interaction{daily.files.missing} + +If your repository contains a file that \hgcmd{status} reports as +missing, and you want the file to stay gone, you can run +\hgcmdargs{remove}{\hgopt{remove}{--after}} at any time later on, to +tell Mercurial that you really did mean to remove the file. +\interaction{daily.files.remove-after} + +On the other hand, if you deleted the missing file by accident, use +\hgcmdargs{revert}{\emph{filename}} to recover the file. It will +reappear, in unmodified form. +\interaction{daily.files.recover-missing} + +\subsection{Aside: why tell Mercurial explicitly to remove a file?} + +You might wonder why Mercurial requires you to explicitly tell it that +you are deleting a file. Early during the development of Mercurial, +it let you delete a file however you pleased; Mercurial would notice +the absence of the file automatically when you next ran a +\hgcmd{commit}, and stop tracking the file. In practice, this made it +too easy to accidentally remove a file without noticing. + +\subsection{Useful shorthand---adding and removing files in one step} + +Mercurial offers a combination command, \hgcmd{addremove}, that adds +untracked files and marks missing files as removed. +\interaction{daily.files.addremove} +The \hgcmd{commit} command also provides a \hgopt{commit}{-A} option +that performs this same add-and-remove, immediately followed by a +commit. +\interaction{daily.files.commit-addremove} + +\section{Copying files} + +Mercurial provides a \hgcmd{copy} command that lets you make a new +copy of a file. When you copy a file using this command, Mercurial +makes a record of the fact that the new file is a copy of the original +file. It treats these copied files specially when you merge your work +with someone else's. + +\subsection{The results of copying during a merge} + +What happens during a merge is that changes ``follow'' a copy. To +best illustrate what this means, let's create an example. We'll start +with the usual tiny repository that contains a single file. +\interaction{daily.copy.init} +We need to do some work in parallel, so that we'll have something to +merge. So let's clone our repository. +\interaction{daily.copy.clone} +Back in our initial repository, let's use the \hgcmd{copy} command to +make a copy of the first file we created. +\interaction{daily.copy.copy} + +If we look at the output of the \hgcmd{status} command afterwards, the +copied file looks just like a normal added file. +\interaction{daily.copy.status} +But if we pass the \hgopt{status}{-C} option to \hgcmd{status}, it +prints another line of output: this is the file that our newly-added +file was copied \emph{from}. +\interaction{daily.copy.status-copy} + +Now, back in the repository we cloned, let's make a change in +parallel. We'll add a line of content to the original file that we +created. +\interaction{daily.copy.other} +Now we have a modified \filename{file} in this repository. When we +pull the changes from the first repository, and merge the two heads, +Mercurial will propagate the changes that we made locally to +\filename{file} into its copy, \filename{new-file}. +\interaction{daily.copy.merge} + +\subsection{Why should changes follow copies?} +\label{sec:daily:why-copy} + +This behaviour, of changes to a file propagating out to copies of the +file, might seem esoteric, but in most cases it's highly desirable. + +First of all, remember that this propagation \emph{only} happens when +you merge. So if you \hgcmd{copy} a file, and subsequently modify the +original file during the normal course of your work, nothing will +happen. + +The second thing to know is that modifications will only propagate +across a copy as long as the repository that you're pulling changes +from \emph{doesn't know} about the copy. + +The reason that Mercurial does this is as follows. Let's say I make +an important bug fix in a source file, and commit my changes. +Meanwhile, you've decided to \hgcmd{copy} the file in your repository, +without knowing about the bug or having seen the fix, and you have +started hacking on your copy of the file. + +If you pulled and merged my changes, and Mercurial \emph{didn't} +propagate changes across copies, your source file would now contain +the bug, and unless you remembered to propagate the bug fix by hand, +the bug would \emph{remain} in your copy of the file. + +By automatically propagating the change that fixed the bug from the +original file to the copy, Mercurial prevents this class of problem. +To my knowledge, Mercurial is the \emph{only} revision control system +that propagates changes across copies like this. + +Once your change history has a record that the copy and subsequent +merge occurred, there's usually no further need to propagate changes +from the original file to the copied file, and that's why Mercurial +only propagates changes across copies until this point, and no +further. + +\subsection{How to make changes \emph{not} follow a copy} + +If, for some reason, you decide that this business of automatically +propagating changes across copies is not for you, simply use your +system's normal file copy command (on Unix-like systems, that's +\command{cp}) to make a copy of a file, then \hgcmd{add} the new copy +by hand. Before you do so, though, please do reread +section~\ref{sec:daily:why-copy}, and make an informed decision that +this behaviour is not appropriate to your specific case. + +\subsection{Behaviour of the \hgcmd{copy} command} + +When you use the \hgcmd{copy} command, Mercurial makes a copy of each +source file as it currently stands in the working directory. This +means that if you make some modifications to a file, then \hgcmd{copy} +it without first having committed those changes, the new copy will +also contain the modifications you have made up until that point. (I +find this behaviour a little counterintuitive, which is why I mention +it here.) + +The \hgcmd{copy} command acts similarly to the Unix \command{cp} +command (you can use the \hgcmd{cp} alias if you prefer). The last +argument is the \emph{destination}, and all prior arguments are +\emph{sources}. If you pass it a single file as the source, and the +destination does not exist, it creates a new file with that name. +\interaction{daily.copy.simple} +If the destination is a directory, Mercurial copies its sources into +that directory. +\interaction{daily.copy.dir-dest} +Copying a directory is recursive, and preserves the directory +structure of the source. +\interaction{daily.copy.dir-src} +If the source and destination are both directories, the source tree is +recreated in the destination directory. +\interaction{daily.copy.dir-src-dest} + +As with the \hgcmd{rename} command, if you copy a file manually and +then want Mercurial to know that you've copied the file, simply use +the \hgopt{copy}{--after} option to \hgcmd{copy}. +\interaction{daily.copy.after} + +\section{Renaming files} + +It's rather more common to need to rename a file than to make a copy +of it. The reason I discussed the \hgcmd{copy} command before talking +about renaming files is that Mercurial treats a rename in essentially +the same way as a copy. Therefore, knowing what Mercurial does when +you copy a file tells you what to expect when you rename a file. + +When you use the \hgcmd{rename} command, Mercurial makes a copy of +each source file, then deletes it and marks the file as removed. +\interaction{daily.rename.rename} +The \hgcmd{status} command shows the newly copied file as added, and +the copied-from file as removed. +\interaction{daily.rename.status} +As with the results of a \hgcmd{copy}, we must use the +\hgopt{status}{-C} option to \hgcmd{status} to see that the added file +is really being tracked by Mercurial as a copy of the original, now +removed, file. +\interaction{daily.rename.status-copy} + +As with \hgcmd{remove} and \hgcmd{copy}, you can tell Mercurial about +a rename after the fact using the \hgopt{rename}{--after} option. In +most other respects, the behaviour of the \hgcmd{rename} command, and +the options it accepts, are similar to the \hgcmd{copy} command. + +\subsection{Renaming files and merging changes} + +Since Mercurial's rename is implemented as copy-and-remove, the same +propagation of changes happens when you merge after a rename as after +a copy. + +If I modify a file, and you rename it to a new name, and then we merge +our respective changes, my modifications to the file under its +original name will be propagated into the file under its new name. +(This is something you might expect to ``simply work,'' but not all +revision control systems actually do this.) + +Whereas having changes follow a copy is a feature where you can +perhaps nod and say ``yes, that might be useful,'' it should be clear +that having them follow a rename is definitely important. Without +this facility, it would simply be too easy for changes to become +orphaned when files are renamed. + +\subsection{Divergent renames and merging} + +The case of diverging names occurs when two developers start with a +file---let's call it \filename{foo}---in their respective +repositories. + +\interaction{rename.divergent.clone} +Anne renames the file to \filename{bar}. +\interaction{rename.divergent.rename.anne} +Meanwhile, Bob renames it to \filename{quux}. +\interaction{rename.divergent.rename.bob} + +I like to think of this as a conflict because each developer has +expressed different intentions about what the file ought to be named. + +What do you think should happen when they merge their work? +Mercurial's actual behaviour is that it always preserves \emph{both} +names when it merges changesets that contain divergent renames. +\interaction{rename.divergent.merge} + +Notice that Mercurial does warn about the divergent renames, but it +leaves it up to you to do something about the divergence after the merge. + +\subsection{Convergent renames and merging} + +Another kind of rename conflict occurs when two people choose to +rename different \emph{source} files to the same \emph{destination}. +In this case, Mercurial runs its normal merge machinery, and lets you +guide it to a suitable resolution. + +\subsection{Other name-related corner cases} + +Mercurial has a longstanding bug in which it fails to handle a merge +where one side has a file with a given name, while another has a +directory with the same name. This is documented as~\bug{29}. +\interaction{issue29.go} + +\section{Recovering from mistakes} + +Mercurial has some useful commands that will help you to recover from +some common mistakes. + +The \hgcmd{revert} command lets you undo changes that you have made to +your working directory. For example, if you \hgcmd{add} a file by +accident, just run \hgcmd{revert} with the name of the file you added, +and while the file won't be touched in any way, it won't be tracked +for adding by Mercurial any longer, either. You can also use +\hgcmd{revert} to get rid of erroneous changes to a file. + +It's useful to remember that the \hgcmd{revert} command is useful for +changes that you have not yet committed. Once you've committed a +change, if you decide it was a mistake, you can still do something +about it, though your options may be more limited. + +For more information about the \hgcmd{revert} command, and details +about how to deal with changes you have already committed, see +chapter~\ref{chap:undo}. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/ch06-collab.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,1117 @@ +\chapter{Collaborating with other people} +\label{cha:collab} + +As a completely decentralised tool, Mercurial doesn't impose any +policy on how people ought to work with each other. However, if +you're new to distributed revision control, it helps to have some +tools and examples in mind when you're thinking about possible +workflow models. + +\section{Mercurial's web interface} + +Mercurial has a powerful web interface that provides several +useful capabilities. + +For interactive use, the web interface lets you browse a single +repository or a collection of repositories. You can view the history +of a repository, examine each change (comments and diffs), and view +the contents of each directory and file. + +Also for human consumption, the web interface provides an RSS feed of +the changes in a repository. This lets you ``subscribe'' to a +repository using your favourite feed reader, and be automatically +notified of activity in that repository as soon as it happens. I find +this capability much more convenient than the model of subscribing to +a mailing list to which notifications are sent, as it requires no +additional configuration on the part of whoever is serving the +repository. + +The web interface also lets remote users clone a repository, pull +changes from it, and (when the server is configured to permit it) push +changes back to it. Mercurial's HTTP tunneling protocol aggressively +compresses data, so that it works efficiently even over low-bandwidth +network connections. + +The easiest way to get started with the web interface is to use your +web browser to visit an existing repository, such as the master +Mercurial repository at +\url{http://www.selenic.com/repo/hg?style=gitweb}. + +If you're interested in providing a web interface to your own +repositories, Mercurial provides two ways to do this. The first is +using the \hgcmd{serve} command, which is best suited to short-term +``lightweight'' serving. See section~\ref{sec:collab:serve} below for +details of how to use this command. If you have a long-lived +repository that you'd like to make permanently available, Mercurial +has built-in support for the CGI (Common Gateway Interface) standard, +which all common web servers support. See +section~\ref{sec:collab:cgi} for details of CGI configuration. + +\section{Collaboration models} + +With a suitably flexible tool, making decisions about workflow is much +more of a social engineering challenge than a technical one. +Mercurial imposes few limitations on how you can structure the flow of +work in a project, so it's up to you and your group to set up and live +with a model that matches your own particular needs. + +\subsection{Factors to keep in mind} + +The most important aspect of any model that you must keep in mind is +how well it matches the needs and capabilities of the people who will +be using it. This might seem self-evident; even so, you still can't +afford to forget it for a moment. + +I once put together a workflow model that seemed to make perfect sense +to me, but that caused a considerable amount of consternation and +strife within my development team. In spite of my attempts to explain +why we needed a complex set of branches, and how changes ought to flow +between them, a few team members revolted. Even though they were +smart people, they didn't want to pay attention to the constraints we +were operating under, or face the consequences of those constraints in +the details of the model that I was advocating. + +Don't sweep foreseeable social or technical problems under the rug. +Whatever scheme you put into effect, you should plan for mistakes and +problem scenarios. Consider adding automated machinery to prevent, or +quickly recover from, trouble that you can anticipate. As an example, +if you intend to have a branch with not-for-release changes in it, +you'd do well to think early about the possibility that someone might +accidentally merge those changes into a release branch. You could +avoid this particular problem by writing a hook that prevents changes +from being merged from an inappropriate branch. + +\subsection{Informal anarchy} + +I wouldn't suggest an ``anything goes'' approach as something +sustainable, but it's a model that's easy to grasp, and it works +perfectly well in a few unusual situations. + +As one example, many projects have a loose-knit group of collaborators +who rarely physically meet each other. Some groups like to overcome +the isolation of working at a distance by organising occasional +``sprints''. In a sprint, a number of people get together in a single +location (a company's conference room, a hotel meeting room, that kind +of place) and spend several days more or less locked in there, hacking +intensely on a handful of projects. + +A sprint is the perfect place to use the \hgcmd{serve} command, since +\hgcmd{serve} does not requires any fancy server infrastructure. You +can get started with \hgcmd{serve} in moments, by reading +section~\ref{sec:collab:serve} below. Then simply tell the person +next to you that you're running a server, send the URL to them in an +instant message, and you immediately have a quick-turnaround way to +work together. They can type your URL into their web browser and +quickly review your changes; or they can pull a bugfix from you and +verify it; or they can clone a branch containing a new feature and try +it out. + +The charm, and the problem, with doing things in an ad hoc fashion +like this is that only people who know about your changes, and where +they are, can see them. Such an informal approach simply doesn't +scale beyond a handful people, because each individual needs to know +about $n$ different repositories to pull from. + +\subsection{A single central repository} + +For smaller projects migrating from a centralised revision control +tool, perhaps the easiest way to get started is to have changes flow +through a single shared central repository. This is also the +most common ``building block'' for more ambitious workflow schemes. + +Contributors start by cloning a copy of this repository. They can +pull changes from it whenever they need to, and some (perhaps all) +developers have permission to push a change back when they're ready +for other people to see it. + +Under this model, it can still often make sense for people to pull +changes directly from each other, without going through the central +repository. Consider a case in which I have a tentative bug fix, but +I am worried that if I were to publish it to the central repository, +it might subsequently break everyone else's trees as they pull it. To +reduce the potential for damage, I can ask you to clone my repository +into a temporary repository of your own and test it. This lets us put +off publishing the potentially unsafe change until it has had a little +testing. + +In this kind of scenario, people usually use the \command{ssh} +protocol to securely push changes to the central repository, as +documented in section~\ref{sec:collab:ssh}. It's also usual to +publish a read-only copy of the repository over HTTP using CGI, as in +section~\ref{sec:collab:cgi}. Publishing over HTTP satisfies the +needs of people who don't have push access, and those who want to use +web browsers to browse the repository's history. + +\subsection{Working with multiple branches} + +Projects of any significant size naturally tend to make progress on +several fronts simultaneously. In the case of software, it's common +for a project to go through periodic official releases. A release +might then go into ``maintenance mode'' for a while after its first +publication; maintenance releases tend to contain only bug fixes, not +new features. In parallel with these maintenance releases, one or +more future releases may be under development. People normally use +the word ``branch'' to refer to one of these many slightly different +directions in which development is proceeding. + +Mercurial is particularly well suited to managing a number of +simultaneous, but not identical, branches. Each ``development +direction'' can live in its own central repository, and you can merge +changes from one to another as the need arises. Because repositories +are independent of each other, unstable changes in a development +branch will never affect a stable branch unless someone explicitly +merges those changes in. + +Here's an example of how this can work in practice. Let's say you +have one ``main branch'' on a central server. +\interaction{branching.init} +People clone it, make changes locally, test them, and push them back. + +Once the main branch reaches a release milestone, you can use the +\hgcmd{tag} command to give a permanent name to the milestone +revision. +\interaction{branching.tag} +Let's say some ongoing development occurs on the main branch. +\interaction{branching.main} +Using the tag that was recorded at the milestone, people who clone +that repository at any time in the future can use \hgcmd{update} to +get a copy of the working directory exactly as it was when that tagged +revision was committed. +\interaction{branching.update} + +In addition, immediately after the main branch is tagged, someone can +then clone the main branch on the server to a new ``stable'' branch, +also on the server. +\interaction{branching.clone} + +Someone who needs to make a change to the stable branch can then clone +\emph{that} repository, make their changes, commit, and push their +changes back there. +\interaction{branching.stable} +Because Mercurial repositories are independent, and Mercurial doesn't +move changes around automatically, the stable and main branches are +\emph{isolated} from each other. The changes that you made on the +main branch don't ``leak'' to the stable branch, and vice versa. + +You'll often want all of your bugfixes on the stable branch to show up +on the main branch, too. Rather than rewrite a bugfix on the main +branch, you can simply pull and merge changes from the stable to the +main branch, and Mercurial will bring those bugfixes in for you. +\interaction{branching.merge} +The main branch will still contain changes that are not on the stable +branch, but it will also contain all of the bugfixes from the stable +branch. The stable branch remains unaffected by these changes. + +\subsection{Feature branches} + +For larger projects, an effective way to manage change is to break up +a team into smaller groups. Each group has a shared branch of its +own, cloned from a single ``master'' branch used by the entire +project. People working on an individual branch are typically quite +isolated from developments on other branches. + +\begin{figure}[ht] + \centering + \grafix{feature-branches} + \caption{Feature branches} + \label{fig:collab:feature-branches} +\end{figure} + +When a particular feature is deemed to be in suitable shape, someone +on that feature team pulls and merges from the master branch into the +feature branch, then pushes back up to the master branch. + +\subsection{The release train} + +Some projects are organised on a ``train'' basis: a release is +scheduled to happen every few months, and whatever features are ready +when the ``train'' is ready to leave are allowed in. + +This model resembles working with feature branches. The difference is +that when a feature branch misses a train, someone on the feature team +pulls and merges the changes that went out on that train release into +the feature branch, and the team continues its work on top of that +release so that their feature can make the next release. + +\subsection{The Linux kernel model} + +The development of the Linux kernel has a shallow hierarchical +structure, surrounded by a cloud of apparent chaos. Because most +Linux developers use \command{git}, a distributed revision control +tool with capabilities similar to Mercurial, it's useful to describe +the way work flows in that environment; if you like the ideas, the +approach translates well across tools. + +At the center of the community sits Linus Torvalds, the creator of +Linux. He publishes a single source repository that is considered the +``authoritative'' current tree by the entire developer community. +Anyone can clone Linus's tree, but he is very choosy about whose trees +he pulls from. + +Linus has a number of ``trusted lieutenants''. As a general rule, he +pulls whatever changes they publish, in most cases without even +reviewing those changes. Some of those lieutenants are generally +agreed to be ``maintainers'', responsible for specific subsystems +within the kernel. If a random kernel hacker wants to make a change +to a subsystem that they want to end up in Linus's tree, they must +find out who the subsystem's maintainer is, and ask that maintainer to +take their change. If the maintainer reviews their changes and agrees +to take them, they'll pass them along to Linus in due course. + +Individual lieutenants have their own approaches to reviewing, +accepting, and publishing changes; and for deciding when to feed them +to Linus. In addition, there are several well known branches that +people use for different purposes. For example, a few people maintain +``stable'' repositories of older versions of the kernel, to which they +apply critical fixes as needed. Some maintainers publish multiple +trees: one for experimental changes; one for changes that they are +about to feed upstream; and so on. Others just publish a single +tree. + +This model has two notable features. The first is that it's ``pull +only''. You have to ask, convince, or beg another developer to take a +change from you, because there are almost no trees to which more than +one person can push, and there's no way to push changes into a tree +that someone else controls. + +The second is that it's based on reputation and acclaim. If you're an +unknown, Linus will probably ignore changes from you without even +responding. But a subsystem maintainer will probably review them, and +will likely take them if they pass their criteria for suitability. +The more ``good'' changes you contribute to a maintainer, the more +likely they are to trust your judgment and accept your changes. If +you're well-known and maintain a long-lived branch for something Linus +hasn't yet accepted, people with similar interests may pull your +changes regularly to keep up with your work. + +Reputation and acclaim don't necessarily cross subsystem or ``people'' +boundaries. If you're a respected but specialised storage hacker, and +you try to fix a networking bug, that change will receive a level of +scrutiny from a network maintainer comparable to a change from a +complete stranger. + +To people who come from more orderly project backgrounds, the +comparatively chaotic Linux kernel development process often seems +completely insane. It's subject to the whims of individuals; people +make sweeping changes whenever they deem it appropriate; and the pace +of development is astounding. And yet Linux is a highly successful, +well-regarded piece of software. + +\subsection{Pull-only versus shared-push collaboration} + +A perpetual source of heat in the open source community is whether a +development model in which people only ever pull changes from others +is ``better than'' one in which multiple people can push changes to a +shared repository. + +Typically, the backers of the shared-push model use tools that +actively enforce this approach. If you're using a centralised +revision control tool such as Subversion, there's no way to make a +choice over which model you'll use: the tool gives you shared-push, +and if you want to do anything else, you'll have to roll your own +approach on top (such as applying a patch by hand). + +A good distributed revision control tool, such as Mercurial, will +support both models. You and your collaborators can then structure +how you work together based on your own needs and preferences, not on +what contortions your tools force you into. + +\subsection{Where collaboration meets branch management} + +Once you and your team set up some shared repositories and start +propagating changes back and forth between local and shared repos, you +begin to face a related, but slightly different challenge: that of +managing the multiple directions in which your team may be moving at +once. Even though this subject is intimately related to how your team +collaborates, it's dense enough to merit treatment of its own, in +chapter~\ref{chap:branch}. + +\section{The technical side of sharing} + +The remainder of this chapter is devoted to the question of serving +data to your collaborators. + +\section{Informal sharing with \hgcmd{serve}} +\label{sec:collab:serve} + +Mercurial's \hgcmd{serve} command is wonderfully suited to small, +tight-knit, and fast-paced group environments. It also provides a +great way to get a feel for using Mercurial commands over a network. + +Run \hgcmd{serve} inside a repository, and in under a second it will +bring up a specialised HTTP server; this will accept connections from +any client, and serve up data for that repository until you terminate +it. Anyone who knows the URL of the server you just started, and can +talk to your computer over the network, can then use a web browser or +Mercurial to read data from that repository. A URL for a +\hgcmd{serve} instance running on a laptop is likely to look something +like \Verb|http://my-laptop.local:8000/|. + +The \hgcmd{serve} command is \emph{not} a general-purpose web server. +It can do only two things: +\begin{itemize} +\item Allow people to browse the history of the repository it's + serving, from their normal web browsers. +\item Speak Mercurial's wire protocol, so that people can + \hgcmd{clone} or \hgcmd{pull} changes from that repository. +\end{itemize} +In particular, \hgcmd{serve} won't allow remote users to \emph{modify} +your repository. It's intended for read-only use. + +If you're getting started with Mercurial, there's nothing to prevent +you from using \hgcmd{serve} to serve up a repository on your own +computer, then use commands like \hgcmd{clone}, \hgcmd{incoming}, and +so on to talk to that server as if the repository was hosted remotely. +This can help you to quickly get acquainted with using commands on +network-hosted repositories. + +\subsection{A few things to keep in mind} + +Because it provides unauthenticated read access to all clients, you +should only use \hgcmd{serve} in an environment where you either don't +care, or have complete control over, who can access your network and +pull data from your repository. + +The \hgcmd{serve} command knows nothing about any firewall software +you might have installed on your system or network. It cannot detect +or control your firewall software. If other people are unable to talk +to a running \hgcmd{serve} instance, the second thing you should do +(\emph{after} you make sure that they're using the correct URL) is +check your firewall configuration. + +By default, \hgcmd{serve} listens for incoming connections on +port~8000. If another process is already listening on the port you +want to use, you can specify a different port to listen on using the +\hgopt{serve}{-p} option. + +Normally, when \hgcmd{serve} starts, it prints no output, which can be +a bit unnerving. If you'd like to confirm that it is indeed running +correctly, and find out what URL you should send to your +collaborators, start it with the \hggopt{-v} option. + +\section{Using the Secure Shell (ssh) protocol} +\label{sec:collab:ssh} + +You can pull and push changes securely over a network connection using +the Secure Shell (\texttt{ssh}) protocol. To use this successfully, +you may have to do a little bit of configuration on the client or +server sides. + +If you're not familiar with ssh, it's a network protocol that lets you +securely communicate with another computer. To use it with Mercurial, +you'll be setting up one or more user accounts on a server so that +remote users can log in and execute commands. + +(If you \emph{are} familiar with ssh, you'll probably find some of the +material that follows to be elementary in nature.) + +\subsection{How to read and write ssh URLs} + +An ssh URL tends to look like this: +\begin{codesample2} + ssh://bos@hg.serpentine.com:22/hg/hgbook +\end{codesample2} +\begin{enumerate} +\item The ``\texttt{ssh://}'' part tells Mercurial to use the ssh + protocol. +\item The ``\texttt{bos@}'' component indicates what username to log + into the server as. You can leave this out if the remote username + is the same as your local username. +\item The ``\texttt{hg.serpentine.com}'' gives the hostname of the + server to log into. +\item The ``:22'' identifies the port number to connect to the server + on. The default port is~22, so you only need to specify this part + if you're \emph{not} using port~22. +\item The remainder of the URL is the local path to the repository on + the server. +\end{enumerate} + +There's plenty of scope for confusion with the path component of ssh +URLs, as there is no standard way for tools to interpret it. Some +programs behave differently than others when dealing with these paths. +This isn't an ideal situation, but it's unlikely to change. Please +read the following paragraphs carefully. + +Mercurial treats the path to a repository on the server as relative to +the remote user's home directory. For example, if user \texttt{foo} +on the server has a home directory of \dirname{/home/foo}, then an ssh +URL that contains a path component of \dirname{bar} +\emph{really} refers to the directory \dirname{/home/foo/bar}. + +If you want to specify a path relative to another user's home +directory, you can use a path that starts with a tilde character +followed by the user's name (let's call them \texttt{otheruser}), like +this. +\begin{codesample2} + ssh://server/~otheruser/hg/repo +\end{codesample2} + +And if you really want to specify an \emph{absolute} path on the +server, begin the path component with two slashes, as in this example. +\begin{codesample2} + ssh://server//absolute/path +\end{codesample2} + +\subsection{Finding an ssh client for your system} + +Almost every Unix-like system comes with OpenSSH preinstalled. If +you're using such a system, run \Verb|which ssh| to find out if +the \command{ssh} command is installed (it's usually in +\dirname{/usr/bin}). In the unlikely event that it isn't present, +take a look at your system documentation to figure out how to install +it. + +On Windows, you'll first need to download a suitable ssh +client. There are two alternatives. +\begin{itemize} +\item Simon Tatham's excellent PuTTY package~\cite{web:putty} provides + a complete suite of ssh client commands. +\item If you have a high tolerance for pain, you can use the Cygwin + port of OpenSSH. +\end{itemize} +In either case, you'll need to edit your \hgini\ file to tell +Mercurial where to find the actual client command. For example, if +you're using PuTTY, you'll need to use the \command{plink} command as +a command-line ssh client. +\begin{codesample2} + [ui] + ssh = C:/path/to/plink.exe -ssh -i "C:/path/to/my/private/key" +\end{codesample2} + +\begin{note} + The path to \command{plink} shouldn't contain any whitespace + characters, or Mercurial may not be able to run it correctly (so + putting it in \dirname{C:\\Program Files} is probably not a good + idea). +\end{note} + +\subsection{Generating a key pair} + +To avoid the need to repetitively type a password every time you need +to use your ssh client, I recommend generating a key pair. On a +Unix-like system, the \command{ssh-keygen} command will do the trick. +On Windows, if you're using PuTTY, the \command{puttygen} command is +what you'll need. + +When you generate a key pair, it's usually \emph{highly} advisable to +protect it with a passphrase. (The only time that you might not want +to do this is when you're using the ssh protocol for automated tasks +on a secure network.) + +Simply generating a key pair isn't enough, however. You'll need to +add the public key to the set of authorised keys for whatever user +you're logging in remotely as. For servers using OpenSSH (the vast +majority), this will mean adding the public key to a list in a file +called \sfilename{authorized\_keys} in their \sdirname{.ssh} +directory. + +On a Unix-like system, your public key will have a \filename{.pub} +extension. If you're using \command{puttygen} on Windows, you can +save the public key to a file of your choosing, or paste it from the +window it's displayed in straight into the +\sfilename{authorized\_keys} file. + +\subsection{Using an authentication agent} + +An authentication agent is a daemon that stores passphrases in memory +(so it will forget passphrases if you log out and log back in again). +An ssh client will notice if it's running, and query it for a +passphrase. If there's no authentication agent running, or the agent +doesn't store the necessary passphrase, you'll have to type your +passphrase every time Mercurial tries to communicate with a server on +your behalf (e.g.~whenever you pull or push changes). + +The downside of storing passphrases in an agent is that it's possible +for a well-prepared attacker to recover the plain text of your +passphrases, in some cases even if your system has been power-cycled. +You should make your own judgment as to whether this is an acceptable +risk. It certainly saves a lot of repeated typing. + +On Unix-like systems, the agent is called \command{ssh-agent}, and +it's often run automatically for you when you log in. You'll need to +use the \command{ssh-add} command to add passphrases to the agent's +store. On Windows, if you're using PuTTY, the \command{pageant} +command acts as the agent. It adds an icon to your system tray that +will let you manage stored passphrases. + +\subsection{Configuring the server side properly} + +Because ssh can be fiddly to set up if you're new to it, there's a +variety of things that can go wrong. Add Mercurial on top, and +there's plenty more scope for head-scratching. Most of these +potential problems occur on the server side, not the client side. The +good news is that once you've gotten a configuration working, it will +usually continue to work indefinitely. + +Before you try using Mercurial to talk to an ssh server, it's best to +make sure that you can use the normal \command{ssh} or \command{putty} +command to talk to the server first. If you run into problems with +using these commands directly, Mercurial surely won't work. Worse, it +will obscure the underlying problem. Any time you want to debug +ssh-related Mercurial problems, you should drop back to making sure +that plain ssh client commands work first, \emph{before} you worry +about whether there's a problem with Mercurial. + +The first thing to be sure of on the server side is that you can +actually log in from another machine at all. If you can't use +\command{ssh} or \command{putty} to log in, the error message you get +may give you a few hints as to what's wrong. The most common problems +are as follows. +\begin{itemize} +\item If you get a ``connection refused'' error, either there isn't an + SSH daemon running on the server at all, or it's inaccessible due to + firewall configuration. +\item If you get a ``no route to host'' error, you either have an + incorrect address for the server or a seriously locked down firewall + that won't admit its existence at all. +\item If you get a ``permission denied'' error, you may have mistyped + the username on the server, or you could have mistyped your key's + passphrase or the remote user's password. +\end{itemize} +In summary, if you're having trouble talking to the server's ssh +daemon, first make sure that one is running at all. On many systems +it will be installed, but disabled, by default. Once you're done with +this step, you should then check that the server's firewall is +configured to allow incoming connections on the port the ssh daemon is +listening on (usually~22). Don't worry about more exotic +possibilities for misconfiguration until you've checked these two +first. + +If you're using an authentication agent on the client side to store +passphrases for your keys, you ought to be able to log into the server +without being prompted for a passphrase or a password. If you're +prompted for a passphrase, there are a few possible culprits. +\begin{itemize} +\item You might have forgotten to use \command{ssh-add} or + \command{pageant} to store the passphrase. +\item You might have stored the passphrase for the wrong key. +\end{itemize} +If you're being prompted for the remote user's password, there are +another few possible problems to check. +\begin{itemize} +\item Either the user's home directory or their \sdirname{.ssh} + directory might have excessively liberal permissions. As a result, + the ssh daemon will not trust or read their + \sfilename{authorized\_keys} file. For example, a group-writable + home or \sdirname{.ssh} directory will often cause this symptom. +\item The user's \sfilename{authorized\_keys} file may have a problem. + If anyone other than the user owns or can write to that file, the + ssh daemon will not trust or read it. +\end{itemize} + +In the ideal world, you should be able to run the following command +successfully, and it should print exactly one line of output, the +current date and time. +\begin{codesample2} + ssh myserver date +\end{codesample2} + +If, on your server, you have login scripts that print banners or other +junk even when running non-interactive commands like this, you should +fix them before you continue, so that they only print output if +they're run interactively. Otherwise these banners will at least +clutter up Mercurial's output. Worse, they could potentially cause +problems with running Mercurial commands remotely. Mercurial makes +tries to detect and ignore banners in non-interactive \command{ssh} +sessions, but it is not foolproof. (If you're editing your login +scripts on your server, the usual way to see if a login script is +running in an interactive shell is to check the return code from the +command \Verb|tty -s|.) + +Once you've verified that plain old ssh is working with your server, +the next step is to ensure that Mercurial runs on the server. The +following command should run successfully: +\begin{codesample2} + ssh myserver hg version +\end{codesample2} +If you see an error message instead of normal \hgcmd{version} output, +this is usually because you haven't installed Mercurial to +\dirname{/usr/bin}. Don't worry if this is the case; you don't need +to do that. But you should check for a few possible problems. +\begin{itemize} +\item Is Mercurial really installed on the server at all? I know this + sounds trivial, but it's worth checking! +\item Maybe your shell's search path (usually set via the \envar{PATH} + environment variable) is simply misconfigured. +\item Perhaps your \envar{PATH} environment variable is only being set + to point to the location of the \command{hg} executable if the login + session is interactive. This can happen if you're setting the path + in the wrong shell login script. See your shell's documentation for + details. +\item The \envar{PYTHONPATH} environment variable may need to contain + the path to the Mercurial Python modules. It might not be set at + all; it could be incorrect; or it may be set only if the login is + interactive. +\end{itemize} + +If you can run \hgcmd{version} over an ssh connection, well done! +You've got the server and client sorted out. You should now be able +to use Mercurial to access repositories hosted by that username on +that server. If you run into problems with Mercurial and ssh at this +point, try using the \hggopt{--debug} option to get a clearer picture +of what's going on. + +\subsection{Using compression with ssh} + +Mercurial does not compress data when it uses the ssh protocol, +because the ssh protocol can transparently compress data. However, +the default behaviour of ssh clients is \emph{not} to request +compression. + +Over any network other than a fast LAN (even a wireless network), +using compression is likely to significantly speed up Mercurial's +network operations. For example, over a WAN, someone measured +compression as reducing the amount of time required to clone a +particularly large repository from~51 minutes to~17 minutes. + +Both \command{ssh} and \command{plink} accept a \cmdopt{ssh}{-C} +option which turns on compression. You can easily edit your \hgrc\ to +enable compression for all of Mercurial's uses of the ssh protocol. +\begin{codesample2} + [ui] + ssh = ssh -C +\end{codesample2} + +If you use \command{ssh}, you can configure it to always use +compression when talking to your server. To do this, edit your +\sfilename{.ssh/config} file (which may not yet exist), as follows. +\begin{codesample2} + Host hg + Compression yes + HostName hg.example.com +\end{codesample2} +This defines an alias, \texttt{hg}. When you use it on the +\command{ssh} command line or in a Mercurial \texttt{ssh}-protocol +URL, it will cause \command{ssh} to connect to \texttt{hg.example.com} +and use compression. This gives you both a shorter name to type and +compression, each of which is a good thing in its own right. + +\section{Serving over HTTP using CGI} +\label{sec:collab:cgi} + +Depending on how ambitious you are, configuring Mercurial's CGI +interface can take anything from a few moments to several hours. + +We'll begin with the simplest of examples, and work our way towards a +more complex configuration. Even for the most basic case, you're +almost certainly going to need to read and modify your web server's +configuration. + +\begin{note} + Configuring a web server is a complex, fiddly, and highly + system-dependent activity. I can't possibly give you instructions + that will cover anything like all of the cases you will encounter. + Please use your discretion and judgment in following the sections + below. Be prepared to make plenty of mistakes, and to spend a lot + of time reading your server's error logs. +\end{note} + +\subsection{Web server configuration checklist} + +Before you continue, do take a few moments to check a few aspects of +your system's setup. + +\begin{enumerate} +\item Do you have a web server installed at all? Mac OS X ships with + Apache, but many other systems may not have a web server installed. +\item If you have a web server installed, is it actually running? On + most systems, even if one is present, it will be disabled by + default. +\item Is your server configured to allow you to run CGI programs in + the directory where you plan to do so? Most servers default to + explicitly disabling the ability to run CGI programs. +\end{enumerate} + +If you don't have a web server installed, and don't have substantial +experience configuring Apache, you should consider using the +\texttt{lighttpd} web server instead of Apache. Apache has a +well-deserved reputation for baroque and confusing configuration. +While \texttt{lighttpd} is less capable in some ways than Apache, most +of these capabilities are not relevant to serving Mercurial +repositories. And \texttt{lighttpd} is undeniably \emph{much} easier +to get started with than Apache. + +\subsection{Basic CGI configuration} + +On Unix-like systems, it's common for users to have a subdirectory +named something like \dirname{public\_html} in their home directory, +from which they can serve up web pages. A file named \filename{foo} +in this directory will be accessible at a URL of the form +\texttt{http://www.example.com/\~{}username/foo}. + +To get started, find the \sfilename{hgweb.cgi} script that should be +present in your Mercurial installation. If you can't quickly find a +local copy on your system, simply download one from the master +Mercurial repository at +\url{http://www.selenic.com/repo/hg/raw-file/tip/hgweb.cgi}. + +You'll need to copy this script into your \dirname{public\_html} +directory, and ensure that it's executable. +\begin{codesample2} + cp .../hgweb.cgi ~/public_html + chmod 755 ~/public_html/hgweb.cgi +\end{codesample2} +The \texttt{755} argument to \command{chmod} is a little more general +than just making the script executable: it ensures that the script is +executable by anyone, and that ``group'' and ``other'' write +permissions are \emph{not} set. If you were to leave those write +permissions enabled, Apache's \texttt{suexec} subsystem would likely +refuse to execute the script. In fact, \texttt{suexec} also insists +that the \emph{directory} in which the script resides must not be +writable by others. +\begin{codesample2} + chmod 755 ~/public_html +\end{codesample2} + +\subsubsection{What could \emph{possibly} go wrong?} +\label{sec:collab:wtf} + +Once you've copied the CGI script into place, go into a web browser, +and try to open the URL \url{http://myhostname/~myuser/hgweb.cgi}, +\emph{but} brace yourself for instant failure. There's a high +probability that trying to visit this URL will fail, and there are +many possible reasons for this. In fact, you're likely to stumble +over almost every one of the possible errors below, so please read +carefully. The following are all of the problems I ran into on a +system running Fedora~7, with a fresh installation of Apache, and a +user account that I created specially to perform this exercise. + +Your web server may have per-user directories disabled. If you're +using Apache, search your config file for a \texttt{UserDir} +directive. If there's none present, per-user directories will be +disabled. If one exists, but its value is \texttt{disabled}, then +per-user directories will be disabled. Otherwise, the string after +\texttt{UserDir} gives the name of the subdirectory that Apache will +look in under your home directory, for example \dirname{public\_html}. + +Your file access permissions may be too restrictive. The web server +must be able to traverse your home directory and directories under +your \dirname{public\_html} directory, and read files under the latter +too. Here's a quick recipe to help you to make your permissions more +appropriate. +\begin{codesample2} + chmod 755 ~ + find ~/public_html -type d -print0 | xargs -0r chmod 755 + find ~/public_html -type f -print0 | xargs -0r chmod 644 +\end{codesample2} + +The other possibility with permissions is that you might get a +completely empty window when you try to load the script. In this +case, it's likely that your access permissions are \emph{too + permissive}. Apache's \texttt{suexec} subsystem won't execute a +script that's group-~or world-writable, for example. + +Your web server may be configured to disallow execution of CGI +programs in your per-user web directory. Here's Apache's +default per-user configuration from my Fedora system. +\begin{codesample2} + <Directory /home/*/public_html> + AllowOverride FileInfo AuthConfig Limit + Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec + <Limit GET POST OPTIONS> + Order allow,deny + Allow from all + </Limit> + <LimitExcept GET POST OPTIONS> + Order deny,allow + Deny from all + </LimitExcept> + </Directory> +\end{codesample2} +If you find a similar-looking \texttt{Directory} group in your Apache +configuration, the directive to look at inside it is \texttt{Options}. +Add \texttt{ExecCGI} to the end of this list if it's missing, and +restart the web server. + +If you find that Apache serves you the text of the CGI script instead +of executing it, you may need to either uncomment (if already present) +or add a directive like this. +\begin{codesample2} + AddHandler cgi-script .cgi +\end{codesample2} + +The next possibility is that you might be served with a colourful +Python backtrace claiming that it can't import a +\texttt{mercurial}-related module. This is actually progress! The +server is now capable of executing your CGI script. This error is +only likely to occur if you're running a private installation of +Mercurial, instead of a system-wide version. Remember that the web +server runs the CGI program without any of the environment variables +that you take for granted in an interactive session. If this error +happens to you, edit your copy of \sfilename{hgweb.cgi} and follow the +directions inside it to correctly set your \envar{PYTHONPATH} +environment variable. + +Finally, you are \emph{certain} to by served with another colourful +Python backtrace: this one will complain that it can't find +\dirname{/path/to/repository}. Edit your \sfilename{hgweb.cgi} script +and replace the \dirname{/path/to/repository} string with the complete +path to the repository you want to serve up. + +At this point, when you try to reload the page, you should be +presented with a nice HTML view of your repository's history. Whew! + +\subsubsection{Configuring lighttpd} + +To be exhaustive in my experiments, I tried configuring the +increasingly popular \texttt{lighttpd} web server to serve the same +repository as I described with Apache above. I had already overcome +all of the problems I outlined with Apache, many of which are not +server-specific. As a result, I was fairly sure that my file and +directory permissions were good, and that my \sfilename{hgweb.cgi} +script was properly edited. + +Once I had Apache running, getting \texttt{lighttpd} to serve the +repository was a snap (in other words, even if you're trying to use +\texttt{lighttpd}, you should read the Apache section). I first had +to edit the \texttt{mod\_access} section of its config file to enable +\texttt{mod\_cgi} and \texttt{mod\_userdir}, both of which were +disabled by default on my system. I then added a few lines to the end +of the config file, to configure these modules. +\begin{codesample2} + userdir.path = "public_html" + cgi.assign = ( ".cgi" => "" ) +\end{codesample2} +With this done, \texttt{lighttpd} ran immediately for me. If I had +configured \texttt{lighttpd} before Apache, I'd almost certainly have +run into many of the same system-level configuration problems as I did +with Apache. However, I found \texttt{lighttpd} to be noticeably +easier to configure than Apache, even though I've used Apache for over +a decade, and this was my first exposure to \texttt{lighttpd}. + +\subsection{Sharing multiple repositories with one CGI script} + +The \sfilename{hgweb.cgi} script only lets you publish a single +repository, which is an annoying restriction. If you want to publish +more than one without wracking yourself with multiple copies of the +same script, each with different names, a better choice is to use the +\sfilename{hgwebdir.cgi} script. + +The procedure to configure \sfilename{hgwebdir.cgi} is only a little +more involved than for \sfilename{hgweb.cgi}. First, you must obtain +a copy of the script. If you don't have one handy, you can download a +copy from the master Mercurial repository at +\url{http://www.selenic.com/repo/hg/raw-file/tip/hgwebdir.cgi}. + +You'll need to copy this script into your \dirname{public\_html} +directory, and ensure that it's executable. +\begin{codesample2} + cp .../hgwebdir.cgi ~/public_html + chmod 755 ~/public_html ~/public_html/hgwebdir.cgi +\end{codesample2} +With basic configuration out of the way, try to visit +\url{http://myhostname/~myuser/hgwebdir.cgi} in your browser. It +should display an empty list of repositories. If you get a blank +window or error message, try walking through the list of potential +problems in section~\ref{sec:collab:wtf}. + +The \sfilename{hgwebdir.cgi} script relies on an external +configuration file. By default, it searches for a file named +\sfilename{hgweb.config} in the same directory as itself. You'll need +to create this file, and make it world-readable. The format of the +file is similar to a Windows ``ini'' file, as understood by Python's +\texttt{ConfigParser}~\cite{web:configparser} module. + +The easiest way to configure \sfilename{hgwebdir.cgi} is with a +section named \texttt{collections}. This will automatically publish +\emph{every} repository under the directories you name. The section +should look like this: +\begin{codesample2} + [collections] + /my/root = /my/root +\end{codesample2} +Mercurial interprets this by looking at the directory name on the +\emph{right} hand side of the ``\texttt{=}'' sign; finding +repositories in that directory hierarchy; and using the text on the +\emph{left} to strip off matching text from the names it will actually +list in the web interface. The remaining component of a path after +this stripping has occurred is called a ``virtual path''. + +Given the example above, if we have a repository whose local path is +\dirname{/my/root/this/repo}, the CGI script will strip the leading +\dirname{/my/root} from the name, and publish the repository with a +virtual path of \dirname{this/repo}. If the base URL for our CGI +script is \url{http://myhostname/~myuser/hgwebdir.cgi}, the complete +URL for that repository will be +\url{http://myhostname/~myuser/hgwebdir.cgi/this/repo}. + +If we replace \dirname{/my/root} on the left hand side of this example +with \dirname{/my}, then \sfilename{hgwebdir.cgi} will only strip off +\dirname{/my} from the repository name, and will give us a virtual +path of \dirname{root/this/repo} instead of \dirname{this/repo}. + +The \sfilename{hgwebdir.cgi} script will recursively search each +directory listed in the \texttt{collections} section of its +configuration file, but it will \texttt{not} recurse into the +repositories it finds. + +The \texttt{collections} mechanism makes it easy to publish many +repositories in a ``fire and forget'' manner. You only need to set up +the CGI script and configuration file one time. Afterwards, you can +publish or unpublish a repository at any time by simply moving it +into, or out of, the directory hierarchy in which you've configured +\sfilename{hgwebdir.cgi} to look. + +\subsubsection{Explicitly specifying which repositories to publish} + +In addition to the \texttt{collections} mechanism, the +\sfilename{hgwebdir.cgi} script allows you to publish a specific list +of repositories. To do so, create a \texttt{paths} section, with +contents of the following form. +\begin{codesample2} + [paths] + repo1 = /my/path/to/some/repo + repo2 = /some/path/to/another +\end{codesample2} +In this case, the virtual path (the component that will appear in a +URL) is on the left hand side of each definition, while the path to +the repository is on the right. Notice that there does not need to be +any relationship between the virtual path you choose and the location +of a repository in your filesystem. + +If you wish, you can use both the \texttt{collections} and +\texttt{paths} mechanisms simultaneously in a single configuration +file. + +\begin{note} + If multiple repositories have the same virtual path, + \sfilename{hgwebdir.cgi} will not report an error. Instead, it will + behave unpredictably. +\end{note} + +\subsection{Downloading source archives} + +Mercurial's web interface lets users download an archive of any +revision. This archive will contain a snapshot of the working +directory as of that revision, but it will not contain a copy of the +repository data. + +By default, this feature is not enabled. To enable it, you'll need to +add an \rcitem{web}{allow\_archive} item to the \rcsection{web} +section of your \hgrc. + +\subsection{Web configuration options} + +Mercurial's web interfaces (the \hgcmd{serve} command, and the +\sfilename{hgweb.cgi} and \sfilename{hgwebdir.cgi} scripts) have a +number of configuration options that you can set. These belong in a +section named \rcsection{web}. +\begin{itemize} +\item[\rcitem{web}{allow\_archive}] Determines which (if any) archive + download mechanisms Mercurial supports. If you enable this + feature, users of the web interface will be able to download an + archive of whatever revision of a repository they are viewing. + To enable the archive feature, this item must take the form of a + sequence of words drawn from the list below. + \begin{itemize} + \item[\texttt{bz2}] A \command{tar} archive, compressed using + \texttt{bzip2} compression. This has the best compression ratio, + but uses the most CPU time on the server. + \item[\texttt{gz}] A \command{tar} archive, compressed using + \texttt{gzip} compression. + \item[\texttt{zip}] A \command{zip} archive, compressed using LZW + compression. This format has the worst compression ratio, but is + widely used in the Windows world. + \end{itemize} + If you provide an empty list, or don't have an + \rcitem{web}{allow\_archive} entry at all, this feature will be + disabled. Here is an example of how to enable all three supported + formats. + \begin{codesample4} + [web] + allow_archive = bz2 gz zip + \end{codesample4} +\item[\rcitem{web}{allowpull}] Boolean. Determines whether the web + interface allows remote users to \hgcmd{pull} and \hgcmd{clone} this + repository over~HTTP. If set to \texttt{no} or \texttt{false}, only + the ``human-oriented'' portion of the web interface is available. +\item[\rcitem{web}{contact}] String. A free-form (but preferably + brief) string identifying the person or group in charge of the + repository. This often contains the name and email address of a + person or mailing list. It often makes sense to place this entry in + a repository's own \sfilename{.hg/hgrc} file, but it can make sense + to use in a global \hgrc\ if every repository has a single + maintainer. +\item[\rcitem{web}{maxchanges}] Integer. The default maximum number + of changesets to display in a single page of output. +\item[\rcitem{web}{maxfiles}] Integer. The default maximum number + of modified files to display in a single page of output. +\item[\rcitem{web}{stripes}] Integer. If the web interface displays + alternating ``stripes'' to make it easier to visually align rows + when you are looking at a table, this number controls the number of + rows in each stripe. +\item[\rcitem{web}{style}] Controls the template Mercurial uses to + display the web interface. Mercurial ships with two web templates, + named \texttt{default} and \texttt{gitweb} (the latter is much more + visually attractive). You can also specify a custom template of + your own; see chapter~\ref{chap:template} for details. Here, you + can see how to enable the \texttt{gitweb} style. + \begin{codesample4} + [web] + style = gitweb + \end{codesample4} +\item[\rcitem{web}{templates}] Path. The directory in which to search + for template files. By default, Mercurial searches in the directory + in which it was installed. +\end{itemize} +If you are using \sfilename{hgwebdir.cgi}, you can place a few +configuration items in a \rcsection{web} section of the +\sfilename{hgweb.config} file instead of a \hgrc\ file, for +convenience. These items are \rcitem{web}{motd} and +\rcitem{web}{style}. + +\subsubsection{Options specific to an individual repository} + +A few \rcsection{web} configuration items ought to be placed in a +repository's local \sfilename{.hg/hgrc}, rather than a user's or +global \hgrc. +\begin{itemize} +\item[\rcitem{web}{description}] String. A free-form (but preferably + brief) string that describes the contents or purpose of the + repository. +\item[\rcitem{web}{name}] String. The name to use for the repository + in the web interface. This overrides the default name, which is the + last component of the repository's path. +\end{itemize} + +\subsubsection{Options specific to the \hgcmd{serve} command} + +Some of the items in the \rcsection{web} section of a \hgrc\ file are +only for use with the \hgcmd{serve} command. +\begin{itemize} +\item[\rcitem{web}{accesslog}] Path. The name of a file into which to + write an access log. By default, the \hgcmd{serve} command writes + this information to standard output, not to a file. Log entries are + written in the standard ``combined'' file format used by almost all + web servers. +\item[\rcitem{web}{address}] String. The local address on which the + server should listen for incoming connections. By default, the + server listens on all addresses. +\item[\rcitem{web}{errorlog}] Path. The name of a file into which to + write an error log. By default, the \hgcmd{serve} command writes this + information to standard error, not to a file. +\item[\rcitem{web}{ipv6}] Boolean. Whether to use the IPv6 protocol. + By default, IPv6 is not used. +\item[\rcitem{web}{port}] Integer. The TCP~port number on which the + server should listen. The default port number used is~8000. +\end{itemize} + +\subsubsection{Choosing the right \hgrc\ file to add \rcsection{web} items to} + +It is important to remember that a web server like Apache or +\texttt{lighttpd} will run under a user~ID that is different to yours. +CGI scripts run by your server, such as \sfilename{hgweb.cgi}, will +usually also run under that user~ID. + +If you add \rcsection{web} items to your own personal \hgrc\ file, CGI +scripts won't read that \hgrc\ file. Those settings will thus only +affect the behaviour of the \hgcmd{serve} command when you run it. To +cause CGI scripts to see your settings, either create a \hgrc\ file in +the home directory of the user ID that runs your web server, or add +those settings to a system-wide \hgrc\ file. + + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/ch07-filenames.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,305 @@ +\chapter{File names and pattern matching} +\label{chap:names} + +Mercurial provides mechanisms that let you work with file names in a +consistent and expressive way. + +\section{Simple file naming} + +Mercurial uses a unified piece of machinery ``under the hood'' to +handle file names. Every command behaves uniformly with respect to +file names. The way in which commands work with file names is as +follows. + +If you explicitly name real files on the command line, Mercurial works +with exactly those files, as you would expect. +\interaction{filenames.files} + +When you provide a directory name, Mercurial will interpret this as +``operate on every file in this directory and its subdirectories''. +Mercurial traverses the files and subdirectories in a directory in +alphabetical order. When it encounters a subdirectory, it will +traverse that subdirectory before continuing with the current +directory. +\interaction{filenames.dirs} + +\section{Running commands without any file names} + +Mercurial's commands that work with file names have useful default +behaviours when you invoke them without providing any file names or +patterns. What kind of behaviour you should expect depends on what +the command does. Here are a few rules of thumb you can use to +predict what a command is likely to do if you don't give it any names +to work with. +\begin{itemize} +\item Most commands will operate on the entire working directory. + This is what the \hgcmd{add} command does, for example. +\item If the command has effects that are difficult or impossible to + reverse, it will force you to explicitly provide at least one name + or pattern (see below). This protects you from accidentally + deleting files by running \hgcmd{remove} with no arguments, for + example. +\end{itemize} + +It's easy to work around these default behaviours if they don't suit +you. If a command normally operates on the whole working directory, +you can invoke it on just the current directory and its subdirectories +by giving it the name ``\dirname{.}''. +\interaction{filenames.wdir-subdir} + +Along the same lines, some commands normally print file names relative +to the root of the repository, even if you're invoking them from a +subdirectory. Such a command will print file names relative to your +subdirectory if you give it explicit names. Here, we're going to run +\hgcmd{status} from a subdirectory, and get it to operate on the +entire working directory while printing file names relative to our +subdirectory, by passing it the output of the \hgcmd{root} command. +\interaction{filenames.wdir-relname} + +\section{Telling you what's going on} + +The \hgcmd{add} example in the preceding section illustrates something +else that's helpful about Mercurial commands. If a command operates +on a file that you didn't name explicitly on the command line, it will +usually print the name of the file, so that you will not be surprised +what's going on. + +The principle here is of \emph{least surprise}. If you've exactly +named a file on the command line, there's no point in repeating it +back at you. If Mercurial is acting on a file \emph{implicitly}, +because you provided no names, or a directory, or a pattern (see +below), it's safest to tell you what it's doing. + +For commands that behave this way, you can silence them using the +\hggopt{-q} option. You can also get them to print the name of every +file, even those you've named explicitly, using the \hggopt{-v} +option. + +\section{Using patterns to identify files} + +In addition to working with file and directory names, Mercurial lets +you use \emph{patterns} to identify files. Mercurial's pattern +handling is expressive. + +On Unix-like systems (Linux, MacOS, etc.), the job of matching file +names to patterns normally falls to the shell. On these systems, you +must explicitly tell Mercurial that a name is a pattern. On Windows, +the shell does not expand patterns, so Mercurial will automatically +identify names that are patterns, and expand them for you. + +To provide a pattern in place of a regular name on the command line, +the mechanism is simple: +\begin{codesample2} + syntax:patternbody +\end{codesample2} +That is, a pattern is identified by a short text string that says what +kind of pattern this is, followed by a colon, followed by the actual +pattern. + +Mercurial supports two kinds of pattern syntax. The most frequently +used is called \texttt{glob}; this is the same kind of pattern +matching used by the Unix shell, and should be familiar to Windows +command prompt users, too. + +When Mercurial does automatic pattern matching on Windows, it uses +\texttt{glob} syntax. You can thus omit the ``\texttt{glob:}'' prefix +on Windows, but it's safe to use it, too. + +The \texttt{re} syntax is more powerful; it lets you specify patterns +using regular expressions, also known as regexps. + +By the way, in the examples that follow, notice that I'm careful to +wrap all of my patterns in quote characters, so that they won't get +expanded by the shell before Mercurial sees them. + +\subsection{Shell-style \texttt{glob} patterns} + +This is an overview of the kinds of patterns you can use when you're +matching on glob patterns. + +The ``\texttt{*}'' character matches any string, within a single +directory. +\interaction{filenames.glob.star} + +The ``\texttt{**}'' pattern matches any string, and crosses directory +boundaries. It's not a standard Unix glob token, but it's accepted by +several popular Unix shells, and is very useful. +\interaction{filenames.glob.starstar} + +The ``\texttt{?}'' pattern matches any single character. +\interaction{filenames.glob.question} + +The ``\texttt{[}'' character begins a \emph{character class}. This +matches any single character within the class. The class ends with a +``\texttt{]}'' character. A class may contain multiple \emph{range}s +of the form ``\texttt{a-f}'', which is shorthand for +``\texttt{abcdef}''. +\interaction{filenames.glob.range} +If the first character after the ``\texttt{[}'' in a character class +is a ``\texttt{!}'', it \emph{negates} the class, making it match any +single character not in the class. + +A ``\texttt{\{}'' begins a group of subpatterns, where the whole group +matches if any subpattern in the group matches. The ``\texttt{,}'' +character separates subpatterns, and ``\texttt{\}}'' ends the group. +\interaction{filenames.glob.group} + +\subsubsection{Watch out!} + +Don't forget that if you want to match a pattern in any directory, you +should not be using the ``\texttt{*}'' match-any token, as this will +only match within one directory. Instead, use the ``\texttt{**}'' +token. This small example illustrates the difference between the two. +\interaction{filenames.glob.star-starstar} + +\subsection{Regular expression matching with \texttt{re} patterns} + +Mercurial accepts the same regular expression syntax as the Python +programming language (it uses Python's regexp engine internally). +This is based on the Perl language's regexp syntax, which is the most +popular dialect in use (it's also used in Java, for example). + +I won't discuss Mercurial's regexp dialect in any detail here, as +regexps are not often used. Perl-style regexps are in any case +already exhaustively documented on a multitude of web sites, and in +many books. Instead, I will focus here on a few things you should +know if you find yourself needing to use regexps with Mercurial. + +A regexp is matched against an entire file name, relative to the root +of the repository. In other words, even if you're already in +subbdirectory \dirname{foo}, if you want to match files under this +directory, your pattern must start with ``\texttt{foo/}''. + +One thing to note, if you're familiar with Perl-style regexps, is that +Mercurial's are \emph{rooted}. That is, a regexp starts matching +against the beginning of a string; it doesn't look for a match +anywhere within the string. To match anywhere in a string, start +your pattern with ``\texttt{.*}''. + +\section{Filtering files} + +Not only does Mercurial give you a variety of ways to specify files; +it lets you further winnow those files using \emph{filters}. Commands +that work with file names accept two filtering options. +\begin{itemize} +\item \hggopt{-I}, or \hggopt{--include}, lets you specify a pattern + that file names must match in order to be processed. +\item \hggopt{-X}, or \hggopt{--exclude}, gives you a way to + \emph{avoid} processing files, if they match this pattern. +\end{itemize} +You can provide multiple \hggopt{-I} and \hggopt{-X} options on the +command line, and intermix them as you please. Mercurial interprets +the patterns you provide using glob syntax by default (but you can use +regexps if you need to). + +You can read a \hggopt{-I} filter as ``process only the files that +match this filter''. +\interaction{filenames.filter.include} +The \hggopt{-X} filter is best read as ``process only the files that +don't match this pattern''. +\interaction{filenames.filter.exclude} + +\section{Ignoring unwanted files and directories} + +XXX. + +\section{Case sensitivity} +\label{sec:names:case} + +If you're working in a mixed development environment that contains +both Linux (or other Unix) systems and Macs or Windows systems, you +should keep in the back of your mind the knowledge that they treat the +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 +\emph{case} of characters in file and directory names. There are +three common ways to handle case in names. +\begin{itemize} +\item Completely case insensitive. Uppercase and lowercase versions + of a letter are treated as identical, both when creating a file and + during subsequent accesses. This is common on older DOS-based + systems. +\item Case preserving, but insensitive. When a file or directory is + created, the case of its name is stored, and can be retrieved and + displayed by the operating system. When an existing file is being + looked up, its case is ignored. This is the standard arrangement on + Windows and MacOS. The names \filename{foo} and \filename{FoO} + identify the same file. This treatment of uppercase and lowercase + letters as interchangeable is also referred to as \emph{case folding}. +\item Case sensitive. The case of a name is significant at all times. + The names \filename{foo} and {FoO} identify different files. This + is the way Linux and Unix systems normally work. +\end{itemize} + +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. + +\subsection{Safe, portable repository storage} + +Mercurial's repository storage mechanism is \emph{case safe}. It +translates file names so that they can be safely stored on both case +sensitive and case insensitive filesystems. This means that you can +use normal file copying tools to transfer a Mercurial repository onto, +for example, a USB thumb drive, and safely move that drive and +repository back and forth between a Mac, a PC running Windows, and a +Linux box. + +\subsection{Detecting case conflicts} + +When operating in the working directory, Mercurial honours the naming +policy of the filesystem where the working directory is located. If +the filesystem is case preserving, but insensitive, Mercurial will +treat names that differ only in case as the same. + +An important aspect of this approach is that it is possible to commit +a changeset on a case sensitive (typically Linux or Unix) filesystem +that will cause trouble for users on case insensitive (usually Windows +and MacOS) users. If a Linux user commits changes to two files, one +named \filename{myfile.c} and the other named \filename{MyFile.C}, +they will be stored correctly in the repository. And in the working +directories of other Linux users, they will be correctly represented +as separate files. + +If a Windows or Mac user pulls this change, they will not initially +have a problem, because Mercurial's repository storage mechanism is +case safe. However, once they try to \hgcmd{update} the working +directory to that changeset, or \hgcmd{merge} with that changeset, +Mercurial will spot the conflict between the two file names that the +filesystem would treat as the same, and forbid the update or merge +from occurring. + +\subsection{Fixing a case conflict} + +If you are using Windows or a Mac in a mixed environment where some of +your collaborators are using Linux or Unix, and Mercurial reports a +case folding conflict when you try to \hgcmd{update} or \hgcmd{merge}, +the procedure to fix the problem is simple. + +Just find a nearby Linux or Unix box, clone the problem repository +onto it, and use Mercurial's \hgcmd{rename} command to change the +names of any offending files or directories so that they will no +longer cause case folding conflicts. Commit this change, \hgcmd{pull} +or \hgcmd{push} it across to your Windows or MacOS system, and +\hgcmd{update} to the revision with the non-conflicting names. + +The changeset with case-conflicting names will remain in your +project's history, and you still won't be able to \hgcmd{update} your +working directory to that changeset on a Windows or MacOS system, but +you can continue development unimpeded. + +\begin{note} + Prior to version~0.9.3, Mercurial did not use a case safe repository + storage mechanism, and did not detect case folding conflicts. If + you are using an older version of Mercurial on Windows or MacOS, I + strongly recommend that you upgrade. +\end{note} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/ch08-branch.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,391 @@ +\chapter{Managing releases and branchy development} +\label{chap:branch} + +Mercurial provides several mechanisms for you to manage a project that +is making progress on multiple fronts at once. To understand these +mechanisms, let's first take a brief look at a fairly normal software +project structure. + +Many software projects issue periodic ``major'' releases that contain +substantial new features. In parallel, they may issue ``minor'' +releases. These are usually identical to the major releases off which +they're based, but with a few bugs fixed. + +In this chapter, we'll start by talking about how to keep records of +project milestones such as releases. We'll then continue on to talk +about the flow of work between different phases of a project, and how +Mercurial can help you to isolate and manage this work. + +\section{Giving a persistent name to a revision} + +Once you decide that you'd like to call a particular revision a +``release'', it's a good idea to record the identity of that revision. +This will let you reproduce that release at a later date, for whatever +purpose you might need at the time (reproducing a bug, porting to a +new platform, etc). +\interaction{tag.init} + +Mercurial lets you give a permanent name to any revision using the +\hgcmd{tag} command. Not surprisingly, these names are called +``tags''. +\interaction{tag.tag} + +A tag is nothing more than a ``symbolic name'' for a revision. Tags +exist purely for your convenience, so that you have a handy permanent +way to refer to a revision; Mercurial doesn't interpret the tag names +you use in any way. Neither does Mercurial place any restrictions on +the name of a tag, beyond a few that are necessary to ensure that a +tag can be parsed unambiguously. A tag name cannot contain any of the +following characters: +\begin{itemize} +\item Colon (ASCII 58, ``\texttt{:}'') +\item Carriage return (ASCII 13, ``\Verb+\r+'') +\item Newline (ASCII 10, ``\Verb+\n+'') +\end{itemize} + +You can use the \hgcmd{tags} command to display the tags present in +your repository. In the output, each tagged revision is identified +first by its name, then by revision number, and finally by the unique +hash of the revision. +\interaction{tag.tags} +Notice that \texttt{tip} is listed in the output of \hgcmd{tags}. The +\texttt{tip} tag is a special ``floating'' tag, which always +identifies the newest revision in the repository. + +In the output of the \hgcmd{tags} command, tags are listed in reverse +order, by revision number. This usually means that recent tags are +listed before older tags. It also means that \texttt{tip} is always +going to be the first tag listed in the output of \hgcmd{tags}. + +When you run \hgcmd{log}, if it displays a revision that has tags +associated with it, it will print those tags. +\interaction{tag.log} + +Any time you need to provide a revision~ID to a Mercurial command, the +command will accept a tag name in its place. Internally, Mercurial +will translate your tag name into the corresponding revision~ID, then +use that. +\interaction{tag.log.v1.0} + +There's no limit on the number of tags you can have in a repository, +or on the number of tags that a single revision can have. As a +practical matter, it's not a great idea to have ``too many'' (a number +which will vary from project to project), simply because tags are +supposed to help you to find revisions. If you have lots of tags, the +ease of using them to identify revisions diminishes rapidly. + +For example, if your project has milestones as frequent as every few +days, it's perfectly reasonable to tag each one of those. But if you +have a continuous build system that makes sure every revision can be +built cleanly, you'd be introducing a lot of noise if you were to tag +every clean build. Instead, you could tag failed builds (on the +assumption that they're rare!), or simply not use tags to track +buildability. + +If you want to remove a tag that you no longer want, use +\hgcmdargs{tag}{--remove}. +\interaction{tag.remove} +You can also modify a tag at any time, so that it identifies a +different revision, by simply issuing a new \hgcmd{tag} command. +You'll have to use the \hgopt{tag}{-f} option to tell Mercurial that +you \emph{really} want to update the tag. +\interaction{tag.replace} +There will still be a permanent record of the previous identity of the +tag, but Mercurial will no longer use it. There's thus no penalty to +tagging the wrong revision; all you have to do is turn around and tag +the correct revision once you discover your error. + +Mercurial stores tags in a normal revision-controlled file in your +repository. If you've created any tags, you'll find them in a file +named \sfilename{.hgtags}. When you run the \hgcmd{tag} command, +Mercurial modifies this file, then automatically commits the change to +it. This means that every time you run \hgcmd{tag}, you'll see a +corresponding changeset in the output of \hgcmd{log}. +\interaction{tag.tip} + +\subsection{Handling tag conflicts during a merge} + +You won't often need to care about the \sfilename{.hgtags} file, but +it sometimes makes its presence known during a merge. The format of +the file is simple: it consists of a series of lines. Each line +starts with a changeset hash, followed by a space, followed by the +name of a tag. + +If you're resolving a conflict in the \sfilename{.hgtags} file during +a merge, there's one twist to modifying the \sfilename{.hgtags} file: +when Mercurial is parsing the tags in a repository, it \emph{never} +reads the working copy of the \sfilename{.hgtags} file. Instead, it +reads the \emph{most recently committed} revision of the file. + +An unfortunate consequence of this design is that you can't actually +verify that your merged \sfilename{.hgtags} file is correct until +\emph{after} you've committed a change. So if you find yourself +resolving a conflict on \sfilename{.hgtags} during a merge, be sure to +run \hgcmd{tags} after you commit. If it finds an error in the +\sfilename{.hgtags} file, it will report the location of the error, +which you can then fix and commit. You should then run \hgcmd{tags} +again, just to be sure that your fix is correct. + +\subsection{Tags and cloning} + +You may have noticed that the \hgcmd{clone} command has a +\hgopt{clone}{-r} option that lets you clone an exact copy of the +repository as of a particular changeset. The new clone will not +contain any project history that comes after the revision you +specified. This has an interaction with tags that can surprise the +unwary. + +Recall that a tag is stored as a revision to the \sfilename{.hgtags} +file, so that when you create a tag, the changeset in which it's +recorded necessarily refers to an older changeset. When you run +\hgcmdargs{clone}{-r foo} to clone a repository as of tag +\texttt{foo}, the new clone \emph{will not contain the history that created the tag} that you used to clone the repository. The result +is that you'll get exactly the right subset of the project's history +in the new repository, but \emph{not} the tag you might have expected. + +\subsection{When permanent tags are too much} + +Since Mercurial's tags are revision controlled and carried around with +a project's history, everyone you work with will see the tags you +create. But giving names to revisions has uses beyond simply noting +that revision \texttt{4237e45506ee} is really \texttt{v2.0.2}. If +you're trying to track down a subtle bug, you might want a tag to +remind you of something like ``Anne saw the symptoms with this +revision''. + +For cases like this, what you might want to use are \emph{local} tags. +You can create a local tag with the \hgopt{tag}{-l} option to the +\hgcmd{tag} command. This will store the tag in a file called +\sfilename{.hg/localtags}. Unlike \sfilename{.hgtags}, +\sfilename{.hg/localtags} is not revision controlled. Any tags you +create using \hgopt{tag}{-l} remain strictly local to the repository +you're currently working in. + +\section{The flow of changes---big picture vs. little} + +To return to the outline I sketched at the beginning of a chapter, +let's think about a project that has multiple concurrent pieces of +work under development at once. + +There might be a push for a new ``main'' release; a new minor bugfix +release to the last main release; and an unexpected ``hot fix'' to an +old release that is now in maintenance mode. + +The usual way people refer to these different concurrent directions of +development is as ``branches''. However, we've already seen numerous +times that Mercurial treats \emph{all of history} as a series of +branches and merges. Really, what we have here is two ideas that are +peripherally related, but which happen to share a name. +\begin{itemize} +\item ``Big picture'' branches represent the sweep of a project's + evolution; people give them names, and talk about them in + conversation. +\item ``Little picture'' branches are artefacts of the day-to-day + activity of developing and merging changes. They expose the + narrative of how the code was developed. +\end{itemize} + +\section{Managing big-picture branches in repositories} + +The easiest way to isolate a ``big picture'' branch in Mercurial is in +a dedicated repository. If you have an existing shared +repository---let's call it \texttt{myproject}---that reaches a ``1.0'' +milestone, you can start to prepare for future maintenance releases on +top of version~1.0 by tagging the revision from which you prepared +the~1.0 release. +\interaction{branch-repo.tag} +You can then clone a new shared \texttt{myproject-1.0.1} repository as +of that tag. +\interaction{branch-repo.clone} + +Afterwards, if someone needs to work on a bug fix that ought to go +into an upcoming~1.0.1 minor release, they clone the +\texttt{myproject-1.0.1} repository, make their changes, and push them +back. +\interaction{branch-repo.bugfix} +Meanwhile, development for the next major release can continue, +isolated and unabated, in the \texttt{myproject} repository. +\interaction{branch-repo.new} + +\section{Don't repeat yourself: merging across branches} + +In many cases, if you have a bug to fix on a maintenance branch, the +chances are good that the bug exists on your project's main branch +(and possibly other maintenance branches, too). It's a rare developer +who wants to fix the same bug multiple times, so let's look at a few +ways that Mercurial can help you to manage these bugfixes without +duplicating your work. + +In the simplest instance, all you need to do is pull changes from your +maintenance branch into your local clone of the target branch. +\interaction{branch-repo.pull} +You'll then need to merge the heads of the two branches, and push back +to the main branch. +\interaction{branch-repo.merge} + +\section{Naming branches within one repository} + +In most instances, isolating branches in repositories is the right +approach. Its simplicity makes it easy to understand; and so it's +hard to make mistakes. There's a one-to-one relationship between +branches you're working in and directories on your system. This lets +you use normal (non-Mercurial-aware) tools to work on files within a +branch/repository. + +If you're more in the ``power user'' category (\emph{and} your +collaborators are too), there is an alternative way of handling +branches that you can consider. I've already mentioned the +human-level distinction between ``small picture'' and ``big picture'' +branches. While Mercurial works with multiple ``small picture'' +branches in a repository all the time (for example after you pull +changes in, but before you merge them), it can \emph{also} work with +multiple ``big picture'' branches. + +The key to working this way is that Mercurial lets you assign a +persistent \emph{name} to a branch. There always exists a branch +named \texttt{default}. Even before you start naming branches +yourself, you can find traces of the \texttt{default} branch if you +look for them. + +As an example, when you run the \hgcmd{commit} command, and it pops up +your editor so that you can enter a commit message, look for a line +that contains the text ``\texttt{HG: branch default}'' at the bottom. +This is telling you that your commit will occur on the branch named +\texttt{default}. + +To start working with named branches, use the \hgcmd{branches} +command. This command lists the named branches already present in +your repository, telling you which changeset is the tip of each. +\interaction{branch-named.branches} +Since you haven't created any named branches yet, the only one that +exists is \texttt{default}. + +To find out what the ``current'' branch is, run the \hgcmd{branch} +command, giving it no arguments. This tells you what branch the +parent of the current changeset is on. +\interaction{branch-named.branch} + +To create a new branch, run the \hgcmd{branch} command again. This +time, give it one argument: the name of the branch you want to create. +\interaction{branch-named.create} + +After you've created a branch, you might wonder what effect the +\hgcmd{branch} command has had. What do the \hgcmd{status} and +\hgcmd{tip} commands report? +\interaction{branch-named.status} +Nothing has changed in the working directory, and there's been no new +history created. As this suggests, running the \hgcmd{branch} command +has no permanent effect; it only tells Mercurial what branch name to +use the \emph{next} time you commit a changeset. + +When you commit a change, Mercurial records the name of the branch on +which you committed. Once you've switched from the \texttt{default} +branch to another and committed, you'll see the name of the new branch +show up in the output of \hgcmd{log}, \hgcmd{tip}, and other commands +that display the same kind of output. +\interaction{branch-named.commit} +The \hgcmd{log}-like commands will print the branch name of every +changeset that's not on the \texttt{default} branch. As a result, if +you never use named branches, you'll never see this information. + +Once you've named a branch and committed a change with that name, +every subsequent commit that descends from that change will inherit +the same branch name. You can change the name of a branch at any +time, using the \hgcmd{branch} command. +\interaction{branch-named.rebranch} +In practice, this is something you won't do very often, as branch +names tend to have fairly long lifetimes. (This isn't a rule, just an +observation.) + +\section{Dealing with multiple named branches in a repository} + +If you have more than one named branch in a repository, Mercurial will +remember the branch that your working directory on when you start a +command like \hgcmd{update} or \hgcmdargs{pull}{-u}. It will update +the working directory to the tip of this branch, no matter what the +``repo-wide'' tip is. To update to a revision that's on a different +named branch, you may need to use the \hgopt{update}{-C} option to +\hgcmd{update}. + +This behaviour is a little subtle, so let's see it in action. First, +let's remind ourselves what branch we're currently on, and what +branches are in our repository. +\interaction{branch-named.parents} +We're on the \texttt{bar} branch, but there also exists an older +\hgcmd{foo} branch. + +We can \hgcmd{update} back and forth between the tips of the +\texttt{foo} and \texttt{bar} branches without needing to use the +\hgopt{update}{-C} option, because this only involves going backwards +and forwards linearly through our change history. +\interaction{branch-named.update-switchy} + +If we go back to the \texttt{foo} branch and then run \hgcmd{update}, +it will keep us on \texttt{foo}, not move us to the tip of +\texttt{bar}. +\interaction{branch-named.update-nothing} + +Committing a new change on the \texttt{foo} branch introduces a new +head. +\interaction{branch-named.foo-commit} + +\section{Branch names and merging} + +As you've probably noticed, merges in Mercurial are not symmetrical. +Let's say our repository has two heads, 17 and 23. If I +\hgcmd{update} to 17 and then \hgcmd{merge} with 23, Mercurial records +17 as the first parent of the merge, and 23 as the second. Whereas if +I \hgcmd{update} to 23 and then \hgcmd{merge} with 17, it records 23 +as the first parent, and 17 as the second. + +This affects Mercurial's choice of branch name when you merge. After +a merge, Mercurial will retain the branch name of the first parent +when you commit the result of the merge. If your first parent's +branch name is \texttt{foo}, and you merge with \texttt{bar}, the +branch name will still be \texttt{foo} after you merge. + +It's not unusual for a repository to contain multiple heads, each with +the same branch name. Let's say I'm working on the \texttt{foo} +branch, and so are you. We commit different changes; I pull your +changes; I now have two heads, each claiming to be on the \texttt{foo} +branch. The result of a merge will be a single head on the +\texttt{foo} branch, as you might hope. + +But if I'm working on the \texttt{bar} branch, and I merge work from +the \texttt{foo} branch, the result will remain on the \texttt{bar} +branch. +\interaction{branch-named.merge} + +To give a more concrete example, if I'm working on the +\texttt{bleeding-edge} branch, and I want to bring in the latest fixes +from the \texttt{stable} branch, Mercurial will choose the ``right'' +(\texttt{bleeding-edge}) branch name when I pull and merge from +\texttt{stable}. + +\section{Branch naming is generally useful} + +You shouldn't think of named branches as applicable only to situations +where you have multiple long-lived branches cohabiting in a single +repository. They're very useful even in the one-branch-per-repository +case. + +In the simplest case, giving a name to each branch gives you a +permanent record of which branch a changeset originated on. This +gives you more context when you're trying to follow the history of a +long-lived branchy project. + +If you're working with shared repositories, you can set up a +\hook{pretxnchangegroup} hook on each that will block incoming changes +that have the ``wrong'' branch name. This provides a simple, but +effective, defence against people accidentally pushing changes from a +``bleeding edge'' branch to a ``stable'' branch. Such a hook might +look like this inside the shared repo's \hgrc. +\begin{codesample2} + [hooks] + pretxnchangegroup.branch = hg heads --template '{branches} ' | grep mybranch +\end{codesample2} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/ch09-undo.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,767 @@ +\chapter{Finding and fixing your mistakes} +\label{chap:undo} + +To err might be human, but to really handle the consequences well +takes a top-notch revision control system. In this chapter, we'll +discuss some of the techniques you can use when you find that a +problem has crept into your project. Mercurial has some highly +capable features that will help you to isolate the sources of +problems, and to handle them appropriately. + +\section{Erasing local history} + +\subsection{The accidental commit} + +I have the occasional but persistent problem of typing rather more +quickly than I can think, which sometimes results in me committing a +changeset that is either incomplete or plain wrong. In my case, the +usual kind of incomplete changeset is one in which I've created a new +source file, but forgotten to \hgcmd{add} it. A ``plain wrong'' +changeset is not as common, but no less annoying. + +\subsection{Rolling back a transaction} +\label{sec:undo:rollback} + +In section~\ref{sec:concepts:txn}, I mentioned that Mercurial treats +each modification of a repository as a \emph{transaction}. Every time +you commit a changeset or pull changes from another repository, +Mercurial remembers what you did. You can undo, or \emph{roll back}, +exactly one of these actions using the \hgcmd{rollback} command. (See +section~\ref{sec:undo:rollback-after-push} for an important caveat +about the use of this command.) + +Here's a mistake that I often find myself making: committing a change +in which I've created a new file, but forgotten to \hgcmd{add} it. +\interaction{rollback.commit} +Looking at the output of \hgcmd{status} after the commit immediately +confirms the error. +\interaction{rollback.status} +The commit captured the changes to the file \filename{a}, but not the +new file \filename{b}. If I were to push this changeset to a +repository that I shared with a colleague, the chances are high that +something in \filename{a} would refer to \filename{b}, which would not +be present in their repository when they pulled my changes. I would +thus become the object of some indignation. + +However, luck is with me---I've caught my error before I pushed the +changeset. I use the \hgcmd{rollback} command, and Mercurial makes +that last changeset vanish. +\interaction{rollback.rollback} +Notice that the changeset is no longer present in the repository's +history, and the working directory once again thinks that the file +\filename{a} is modified. The commit and rollback have left the +working directory exactly as it was prior to the commit; the changeset +has been completely erased. I can now safely \hgcmd{add} the file +\filename{b}, and rerun my commit. +\interaction{rollback.add} + +\subsection{The erroneous pull} + +It's common practice with Mercurial to maintain separate development +branches of a project in different repositories. Your development +team might have one shared repository for your project's ``0.9'' +release, and another, containing different changes, for the ``1.0'' +release. + +Given this, you can imagine that the consequences could be messy if +you had a local ``0.9'' repository, and accidentally pulled changes +from the shared ``1.0'' repository into it. At worst, you could be +paying insufficient attention, and push those changes into the shared +``0.9'' tree, confusing your entire team (but don't worry, we'll +return to this horror scenario later). However, it's more likely that +you'll notice immediately, because Mercurial will display the URL it's +pulling from, or you will see it pull a suspiciously large number of +changes into the repository. + +The \hgcmd{rollback} command will work nicely to expunge all of the +changesets that you just pulled. Mercurial groups all changes from +one \hgcmd{pull} into a single transaction, so one \hgcmd{rollback} is +all you need to undo this mistake. + +\subsection{Rolling back is useless once you've pushed} +\label{sec:undo:rollback-after-push} + +The value of the \hgcmd{rollback} command drops to zero once you've +pushed your changes to another repository. Rolling back a change +makes it disappear entirely, but \emph{only} in the repository in +which you perform the \hgcmd{rollback}. Because a rollback eliminates +history, there's no way for the disappearance of a change to propagate +between repositories. + +If you've pushed a change to another repository---particularly if it's +a shared repository---it has essentially ``escaped into the wild,'' +and you'll have to recover from your mistake in a different way. What +will happen if you push a changeset somewhere, then roll it back, then +pull from the repository you pushed to, is that the changeset will +reappear in your repository. + +(If you absolutely know for sure that the change you want to roll back +is the most recent change in the repository that you pushed to, +\emph{and} you know that nobody else could have pulled it from that +repository, you can roll back the changeset there, too, but you really +should really not rely on this working reliably. If you do this, +sooner or later a change really will make it into a repository that +you don't directly control (or have forgotten about), and come back to +bite you.) + +\subsection{You can only roll back once} + +Mercurial stores exactly one transaction in its transaction log; that +transaction is the most recent one that occurred in the repository. +This means that you can only roll back one transaction. If you expect +to be able to roll back one transaction, then its predecessor, this is +not the behaviour you will get. +\interaction{rollback.twice} +Once you've rolled back one transaction in a repository, you can't +roll back again in that repository until you perform another commit or +pull. + +\section{Reverting the mistaken change} + +If you make a modification to a file, and decide that you really +didn't want to change the file at all, and you haven't yet committed +your changes, the \hgcmd{revert} command is the one you'll need. It +looks at the changeset that's the parent of the working directory, and +restores the contents of the file to their state as of that changeset. +(That's a long-winded way of saying that, in the normal case, it +undoes your modifications.) + +Let's illustrate how the \hgcmd{revert} command works with yet another +small example. We'll begin by modifying a file that Mercurial is +already tracking. +\interaction{daily.revert.modify} +If we don't want that change, we can simply \hgcmd{revert} the file. +\interaction{daily.revert.unmodify} +The \hgcmd{revert} command provides us with an extra degree of safety +by saving our modified file with a \filename{.orig} extension. +\interaction{daily.revert.status} + +Here is a summary of the cases that the \hgcmd{revert} command can +deal with. We will describe each of these in more detail in the +section that follows. +\begin{itemize} +\item If you modify a file, it will restore the file to its unmodified + state. +\item If you \hgcmd{add} a file, it will undo the ``added'' state of + the file, but leave the file itself untouched. +\item If you delete a file without telling Mercurial, it will restore + the file to its unmodified contents. +\item If you use the \hgcmd{remove} command to remove a file, it will + undo the ``removed'' state of the file, and restore the file to its + unmodified contents. +\end{itemize} + +\subsection{File management errors} +\label{sec:undo:mgmt} + +The \hgcmd{revert} command is useful for more than just modified +files. It lets you reverse the results of all of Mercurial's file +management commands---\hgcmd{add}, \hgcmd{remove}, and so on. + +If you \hgcmd{add} a file, then decide that in fact you don't want +Mercurial to track it, use \hgcmd{revert} to undo the add. Don't +worry; Mercurial will not modify the file in any way. It will just +``unmark'' the file. +\interaction{daily.revert.add} + +Similarly, if you ask Mercurial to \hgcmd{remove} a file, you can use +\hgcmd{revert} to restore it to the contents it had as of the parent +of the working directory. +\interaction{daily.revert.remove} +This works just as well for a file that you deleted by hand, without +telling Mercurial (recall that in Mercurial terminology, this kind of +file is called ``missing''). +\interaction{daily.revert.missing} + +If you revert a \hgcmd{copy}, the copied-to file remains in your +working directory afterwards, untracked. Since a copy doesn't affect +the copied-from file in any way, Mercurial doesn't do anything with +the copied-from file. +\interaction{daily.revert.copy} + +\subsubsection{A slightly special case: reverting a rename} + +If you \hgcmd{rename} a file, there is one small detail that +you should remember. When you \hgcmd{revert} a rename, it's not +enough to provide the name of the renamed-to file, as you can see +here. +\interaction{daily.revert.rename} +As you can see from the output of \hgcmd{status}, the renamed-to file +is no longer identified as added, but the renamed-\emph{from} file is +still removed! This is counter-intuitive (at least to me), but at +least it's easy to deal with. +\interaction{daily.revert.rename-orig} +So remember, to revert a \hgcmd{rename}, you must provide \emph{both} +the source and destination names. + +% TODO: the output doesn't look like it will be removed! + +(By the way, if you rename a file, then modify the renamed-to file, +then revert both components of the rename, when Mercurial restores the +file that was removed as part of the rename, it will be unmodified. +If you need the modifications in the renamed-to file to show up in the +renamed-from file, don't forget to copy them over.) + +These fiddly aspects of reverting a rename arguably constitute a small +bug in Mercurial. + +\section{Dealing with committed changes} + +Consider a case where you have committed a change $a$, and another +change $b$ on top of it; you then realise that change $a$ was +incorrect. Mercurial lets you ``back out'' an entire changeset +automatically, and building blocks that let you reverse part of a +changeset by hand. + +Before you read this section, here's something to keep in mind: the +\hgcmd{backout} command undoes changes by \emph{adding} history, not +by modifying or erasing it. It's the right tool to use if you're +fixing bugs, but not if you're trying to undo some change that has +catastrophic consequences. To deal with those, see +section~\ref{sec:undo:aaaiiieee}. + +\subsection{Backing out a changeset} + +The \hgcmd{backout} command lets you ``undo'' the effects of an entire +changeset in an automated fashion. Because Mercurial's history is +immutable, this command \emph{does not} get rid of the changeset you +want to undo. Instead, it creates a new changeset that +\emph{reverses} the effect of the to-be-undone changeset. + +The operation of the \hgcmd{backout} command is a little intricate, so +let's illustrate it with some examples. First, we'll create a +repository with some simple changes. +\interaction{backout.init} + +The \hgcmd{backout} command takes a single changeset ID as its +argument; this is the changeset to back out. Normally, +\hgcmd{backout} will drop you into a text editor to write a commit +message, so you can record why you're backing the change out. In this +example, we provide a commit message on the command line using the +\hgopt{backout}{-m} option. + +\subsection{Backing out the tip changeset} + +We're going to start by backing out the last changeset we committed. +\interaction{backout.simple} +You can see that the second line from \filename{myfile} is no longer +present. Taking a look at the output of \hgcmd{log} gives us an idea +of what the \hgcmd{backout} command has done. +\interaction{backout.simple.log} +Notice that the new changeset that \hgcmd{backout} has created is a +child of the changeset we backed out. It's easier to see this in +figure~\ref{fig:undo:backout}, which presents a graphical view of the +change history. As you can see, the history is nice and linear. + +\begin{figure}[htb] + \centering + \grafix{undo-simple} + \caption{Backing out a change using the \hgcmd{backout} command} + \label{fig:undo:backout} +\end{figure} + +\subsection{Backing out a non-tip change} + +If you want to back out a change other than the last one you +committed, pass the \hgopt{backout}{--merge} option to the +\hgcmd{backout} command. +\interaction{backout.non-tip.clone} +This makes backing out any changeset a ``one-shot'' operation that's +usually simple and fast. +\interaction{backout.non-tip.backout} + +If you take a look at the contents of \filename{myfile} after the +backout finishes, you'll see that the first and third changes are +present, but not the second. +\interaction{backout.non-tip.cat} + +As the graphical history in figure~\ref{fig:undo:backout-non-tip} +illustrates, Mercurial actually commits \emph{two} changes in this +kind of situation (the box-shaped nodes are the ones that Mercurial +commits automatically). Before Mercurial begins the backout process, +it first remembers what the current parent of the working directory +is. It then backs out the target changeset, and commits that as a +changeset. Finally, it merges back to the previous parent of the +working directory, and commits the result of the merge. + +% TODO: to me it looks like mercurial doesn't commit the second merge automatically! + +\begin{figure}[htb] + \centering + \grafix{undo-non-tip} + \caption{Automated backout of a non-tip change using the \hgcmd{backout} command} + \label{fig:undo:backout-non-tip} +\end{figure} + +The result is that you end up ``back where you were'', only with some +extra history that undoes the effect of the changeset you wanted to +back out. + +\subsubsection{Always use the \hgopt{backout}{--merge} option} + +In fact, since the \hgopt{backout}{--merge} option will do the ``right +thing'' whether or not the changeset you're backing out is the tip +(i.e.~it won't try to merge if it's backing out the tip, since there's +no need), you should \emph{always} use this option when you run the +\hgcmd{backout} command. + +\subsection{Gaining more control of the backout process} + +While I've recommended that you always use the +\hgopt{backout}{--merge} option when backing out a change, the +\hgcmd{backout} command lets you decide how to merge a backout +changeset. Taking control of the backout process by hand is something +you will rarely need to do, but it can be useful to understand what +the \hgcmd{backout} command is doing for you automatically. To +illustrate this, let's clone our first repository, but omit the +backout change that it contains. + +\interaction{backout.manual.clone} +As with our earlier example, We'll commit a third changeset, then back +out its parent, and see what happens. +\interaction{backout.manual.backout} +Our new changeset is again a descendant of the changeset we backout +out; it's thus a new head, \emph{not} a descendant of the changeset +that was the tip. The \hgcmd{backout} command was quite explicit in +telling us this. +\interaction{backout.manual.log} + +Again, it's easier to see what has happened by looking at a graph of +the revision history, in figure~\ref{fig:undo:backout-manual}. This +makes it clear that when we use \hgcmd{backout} to back out a change +other than the tip, Mercurial adds a new head to the repository (the +change it committed is box-shaped). + +\begin{figure}[htb] + \centering + \grafix{undo-manual} + \caption{Backing out a change using the \hgcmd{backout} command} + \label{fig:undo:backout-manual} +\end{figure} + +After the \hgcmd{backout} command has completed, it leaves the new +``backout'' changeset as the parent of the working directory. +\interaction{backout.manual.parents} +Now we have two isolated sets of changes. +\interaction{backout.manual.heads} + +Let's think about what we expect to see as the contents of +\filename{myfile} now. The first change should be present, because +we've never backed it out. The second change should be missing, as +that's the change we backed out. Since the history graph shows the +third change as a separate head, we \emph{don't} expect to see the +third change present in \filename{myfile}. +\interaction{backout.manual.cat} +To get the third change back into the file, we just do a normal merge +of our two heads. +\interaction{backout.manual.merge} +Afterwards, the graphical history of our repository looks like +figure~\ref{fig:undo:backout-manual-merge}. + +\begin{figure}[htb] + \centering + \grafix{undo-manual-merge} + \caption{Manually merging a backout change} + \label{fig:undo:backout-manual-merge} +\end{figure} + +\subsection{Why \hgcmd{backout} works as it does} + +Here's a brief description of how the \hgcmd{backout} command works. +\begin{enumerate} +\item It ensures that the working directory is ``clean'', i.e.~that + the output of \hgcmd{status} would be empty. +\item It remembers the current parent of the working directory. Let's + call this changeset \texttt{orig} +\item It does the equivalent of a \hgcmd{update} to sync the working + directory to the changeset you want to back out. Let's call this + changeset \texttt{backout} +\item It finds the parent of that changeset. Let's call that + changeset \texttt{parent}. +\item For each file that the \texttt{backout} changeset affected, it + does the equivalent of a \hgcmdargs{revert}{-r parent} on that file, + to restore it to the contents it had before that changeset was + committed. +\item It commits the result as a new changeset. This changeset has + \texttt{backout} as its parent. +\item If you specify \hgopt{backout}{--merge} on the command line, it + merges with \texttt{orig}, and commits the result of the merge. +\end{enumerate} + +An alternative way to implement the \hgcmd{backout} command would be +to \hgcmd{export} the to-be-backed-out changeset as a diff, then use +the \cmdopt{patch}{--reverse} option to the \command{patch} command to +reverse the effect of the change without fiddling with the working +directory. This sounds much simpler, but it would not work nearly as +well. + +The reason that \hgcmd{backout} does an update, a commit, a merge, and +another commit is to give the merge machinery the best chance to do a +good job when dealing with all the changes \emph{between} the change +you're backing out and the current tip. + +If you're backing out a changeset that's~100 revisions back in your +project's history, the chances that the \command{patch} command will +be able to apply a reverse diff cleanly are not good, because +intervening changes are likely to have ``broken the context'' that +\command{patch} uses to determine whether it can apply a patch (if +this sounds like gibberish, see \ref{sec:mq:patch} for a +discussion of the \command{patch} command). Also, Mercurial's merge +machinery will handle files and directories being renamed, permission +changes, and modifications to binary files, none of which +\command{patch} can deal with. + +\section{Changes that should never have been} +\label{sec:undo:aaaiiieee} + +Most of the time, the \hgcmd{backout} command is exactly what you need +if you want to undo the effects of a change. It leaves a permanent +record of exactly what you did, both when committing the original +changeset and when you cleaned up after it. + +On rare occasions, though, you may find that you've committed a change +that really should not be present in the repository at all. For +example, it would be very unusual, and usually considered a mistake, +to commit a software project's object files as well as its source +files. Object files have almost no intrinsic value, and they're +\emph{big}, so they increase the size of the repository and the amount +of time it takes to clone or pull changes. + +Before I discuss the options that you have if you commit a ``brown +paper bag'' change (the kind that's so bad that you want to pull a +brown paper bag over your head), let me first discuss some approaches +that probably won't work. + +Since Mercurial treats history as accumulative---every change builds +on top of all changes that preceded it---you generally can't just make +disastrous changes disappear. The one exception is when you've just +committed a change, and it hasn't been pushed or pulled into another +repository. That's when you can safely use the \hgcmd{rollback} +command, as I detailed in section~\ref{sec:undo:rollback}. + +After you've pushed a bad change to another repository, you +\emph{could} still use \hgcmd{rollback} to make your local copy of the +change disappear, but it won't have the consequences you want. The +change will still be present in the remote repository, so it will +reappear in your local repository the next time you pull. + +If a situation like this arises, and you know which repositories your +bad change has propagated into, you can \emph{try} to get rid of the +changeefrom \emph{every} one of those repositories. This is, of +course, not a satisfactory solution: if you miss even a single +repository while you're expunging, the change is still ``in the +wild'', and could propagate further. + +If you've committed one or more changes \emph{after} the change that +you'd like to see disappear, your options are further reduced. +Mercurial doesn't provide a way to ``punch a hole'' in history, +leaving changesets intact. + +XXX This needs filling out. The \texttt{hg-replay} script in the +\texttt{examples} directory works, but doesn't handle merge +changesets. Kind of an important omission. + +\subsection{Protect yourself from ``escaped'' changes} + +If you've committed some changes to your local repository and they've +been pushed or pulled somewhere else, this isn't necessarily a +disaster. You can protect yourself ahead of time against some classes +of bad changeset. This is particularly easy if your team usually +pulls changes from a central repository. + +By configuring some hooks on that repository to validate incoming +changesets (see chapter~\ref{chap:hook}), you can automatically +prevent some kinds of bad changeset from being pushed to the central +repository at all. With such a configuration in place, some kinds of +bad changeset will naturally tend to ``die out'' because they can't +propagate into the central repository. Better yet, this happens +without any need for explicit intervention. + +For instance, an incoming change hook that verifies that a changeset +will actually compile can prevent people from inadvertantly ``breaking +the build''. + +\section{Finding the source of a bug} +\label{sec:undo:bisect} + +While it's all very well to be able to back out a changeset that +introduced a bug, this requires that you know which changeset to back +out. Mercurial provides an invaluable command, called +\hgcmd{bisect}, that helps you to automate this process and accomplish +it very efficiently. + +The idea behind the \hgcmd{bisect} command is that a changeset has +introduced some change of behaviour that you can identify with a +simple binary test. You don't know which piece of code introduced the +change, but you know how to test for the presence of the bug. The +\hgcmd{bisect} command uses your test to direct its search for the +changeset that introduced the code that caused the bug. + +Here are a few scenarios to help you understand how you might apply +this command. +\begin{itemize} +\item The most recent version of your software has a bug that you + remember wasn't present a few weeks ago, but you don't know when it + was introduced. Here, your binary test checks for the presence of + that bug. +\item You fixed a bug in a rush, and now it's time to close the entry + in your team's bug database. The bug database requires a changeset + ID when you close an entry, but you don't remember which changeset + you fixed the bug in. Once again, your binary test checks for the + presence of the bug. +\item Your software works correctly, but runs~15\% slower than the + last time you measured it. You want to know which changeset + introduced the performance regression. In this case, your binary + test measures the performance of your software, to see whether it's + ``fast'' or ``slow''. +\item The sizes of the components of your project that you ship + exploded recently, and you suspect that something changed in the way + you build your project. +\end{itemize} + +From these examples, it should be clear that the \hgcmd{bisect} +command is not useful only for finding the sources of bugs. You can +use it to find any ``emergent property'' of a repository (anything +that you can't find from a simple text search of the files in the +tree) for which you can write a binary test. + +We'll introduce a little bit of terminology here, just to make it +clear which parts of the search process are your responsibility, and +which are Mercurial's. A \emph{test} is something that \emph{you} run +when \hgcmd{bisect} chooses a changeset. A \emph{probe} is what +\hgcmd{bisect} runs to tell whether a revision is good. Finally, +we'll use the word ``bisect'', as both a noun and a verb, to stand in +for the phrase ``search using the \hgcmd{bisect} command. + +One simple way to automate the searching process would be simply to +probe every changeset. However, this scales poorly. If it took ten +minutes to test a single changeset, and you had 10,000 changesets in +your repository, the exhaustive approach would take on average~35 +\emph{days} to find the changeset that introduced a bug. Even if you +knew that the bug was introduced by one of the last 500 changesets, +and limited your search to those, you'd still be looking at over 40 +hours to find the changeset that introduced your bug. + +What the \hgcmd{bisect} command does is use its knowledge of the +``shape'' of your project's revision history to perform a search in +time proportional to the \emph{logarithm} of the number of changesets +to check (the kind of search it performs is called a dichotomic +search). With this approach, searching through 10,000 changesets will +take less than three hours, even at ten minutes per test (the search +will require about 14 tests). Limit your search to the last hundred +changesets, and it will take only about an hour (roughly seven tests). + +The \hgcmd{bisect} command is aware of the ``branchy'' nature of a +Mercurial project's revision history, so it has no problems dealing +with branches, merges, or multiple heads in a repository. It can +prune entire branches of history with a single probe, which is how it +operates so efficiently. + +\subsection{Using the \hgcmd{bisect} command} + +Here's an example of \hgcmd{bisect} in action. + +\begin{note} + In versions 0.9.5 and earlier of Mercurial, \hgcmd{bisect} was not a + core command: it was distributed with Mercurial as an extension. + This section describes the built-in command, not the old extension. +\end{note} + +Now let's create a repository, so that we can try out the +\hgcmd{bisect} command in isolation. +\interaction{bisect.init} +We'll simulate a project that has a bug in it in a simple-minded way: +create trivial changes in a loop, and nominate one specific change +that will have the ``bug''. This loop creates 35 changesets, each +adding a single file to the repository. We'll represent our ``bug'' +with a file that contains the text ``i have a gub''. +\interaction{bisect.commits} + +The next thing that we'd like to do is figure out how to use the +\hgcmd{bisect} command. We can use Mercurial's normal built-in help +mechanism for this. +\interaction{bisect.help} + +The \hgcmd{bisect} command works in steps. Each step proceeds as follows. +\begin{enumerate} +\item You run your binary test. + \begin{itemize} + \item If the test succeeded, you tell \hgcmd{bisect} by running the + \hgcmdargs{bisect}{good} command. + \item If it failed, run the \hgcmdargs{bisect}{--bad} command. + \end{itemize} +\item The command uses your information to decide which changeset to + test next. +\item It updates the working directory to that changeset, and the + process begins again. +\end{enumerate} +The process ends when \hgcmd{bisect} identifies a unique changeset +that marks the point where your test transitioned from ``succeeding'' +to ``failing''. + +To start the search, we must run the \hgcmdargs{bisect}{--reset} command. +\interaction{bisect.search.init} + +In our case, the binary test we use is simple: we check to see if any +file in the repository contains the string ``i have a gub''. If it +does, this changeset contains the change that ``caused the bug''. By +convention, a changeset that has the property we're searching for is +``bad'', while one that doesn't is ``good''. + +Most of the time, the revision to which the working directory is +synced (usually the tip) already exhibits the problem introduced by +the buggy change, so we'll mark it as ``bad''. +\interaction{bisect.search.bad-init} + +Our next task is to nominate a changeset that we know \emph{doesn't} +have the bug; the \hgcmd{bisect} command will ``bracket'' its search +between the first pair of good and bad changesets. In our case, we +know that revision~10 didn't have the bug. (I'll have more words +about choosing the first ``good'' changeset later.) +\interaction{bisect.search.good-init} + +Notice that this command printed some output. +\begin{itemize} +\item It told us how many changesets it must consider before it can + identify the one that introduced the bug, and how many tests that + will require. +\item It updated the working directory to the next changeset to test, + and told us which changeset it's testing. +\end{itemize} + +We now run our test in the working directory. We use the +\command{grep} command to see if our ``bad'' file is present in the +working directory. If it is, this revision is bad; if not, this +revision is good. +\interaction{bisect.search.step1} + +This test looks like a perfect candidate for automation, so let's turn +it into a shell function. +\interaction{bisect.search.mytest} +We can now run an entire test step with a single command, +\texttt{mytest}. +\interaction{bisect.search.step2} +A few more invocations of our canned test step command, and we're +done. +\interaction{bisect.search.rest} + +Even though we had~40 changesets to search through, the \hgcmd{bisect} +command let us find the changeset that introduced our ``bug'' with +only five tests. Because the number of tests that the \hgcmd{bisect} +command performs grows logarithmically with the number of changesets to +search, the advantage that it has over the ``brute force'' search +approach increases with every changeset you add. + +\subsection{Cleaning up after your search} + +When you're finished using the \hgcmd{bisect} command in a +repository, you can use the \hgcmdargs{bisect}{reset} command to drop +the information it was using to drive your search. The command +doesn't use much space, so it doesn't matter if you forget to run this +command. However, \hgcmd{bisect} won't let you start a new search in +that repository until you do a \hgcmdargs{bisect}{reset}. +\interaction{bisect.search.reset} + +\section{Tips for finding bugs effectively} + +\subsection{Give consistent input} + +The \hgcmd{bisect} command requires that you correctly report the +result of every test you perform. If you tell it that a test failed +when it really succeeded, it \emph{might} be able to detect the +inconsistency. If it can identify an inconsistency in your reports, +it will tell you that a particular changeset is both good and bad. +However, it can't do this perfectly; it's about as likely to report +the wrong changeset as the source of the bug. + +\subsection{Automate as much as possible} + +When I started using the \hgcmd{bisect} command, I tried a few times +to run my tests by hand, on the command line. This is an approach +that I, at least, am not suited to. After a few tries, I found that I +was making enough mistakes that I was having to restart my searches +several times before finally getting correct results. + +My initial problems with driving the \hgcmd{bisect} command by hand +occurred even with simple searches on small repositories; if the +problem you're looking for is more subtle, or the number of tests that +\hgcmd{bisect} must perform increases, the likelihood of operator +error ruining the search is much higher. Once I started automating my +tests, I had much better results. + +The key to automated testing is twofold: +\begin{itemize} +\item always test for the same symptom, and +\item always feed consistent input to the \hgcmd{bisect} command. +\end{itemize} +In my tutorial example above, the \command{grep} command tests for the +symptom, and the \texttt{if} statement takes the result of this check +and ensures that we always feed the same input to the \hgcmd{bisect} +command. The \texttt{mytest} function marries these together in a +reproducible way, so that every test is uniform and consistent. + +\subsection{Check your results} + +Because the output of a \hgcmd{bisect} search is only as good as the +input you give it, don't take the changeset it reports as the +absolute truth. A simple way to cross-check its report is to manually +run your test at each of the following changesets: +\begin{itemize} +\item The changeset that it reports as the first bad revision. Your + test should still report this as bad. +\item The parent of that changeset (either parent, if it's a merge). + Your test should report this changeset as good. +\item A child of that changeset. Your test should report this + changeset as bad. +\end{itemize} + +\subsection{Beware interference between bugs} + +It's possible that your search for one bug could be disrupted by the +presence of another. For example, let's say your software crashes at +revision 100, and worked correctly at revision 50. Unknown to you, +someone else introduced a different crashing bug at revision 60, and +fixed it at revision 80. This could distort your results in one of +several ways. + +It is possible that this other bug completely ``masks'' yours, which +is to say that it occurs before your bug has a chance to manifest +itself. If you can't avoid that other bug (for example, it prevents +your project from building), and so can't tell whether your bug is +present in a particular changeset, the \hgcmd{bisect} command cannot +help you directly. Instead, you can mark a changeset as untested by +running \hgcmdargs{bisect}{--skip}. + +A different problem could arise if your test for a bug's presence is +not specific enough. If you check for ``my program crashes'', then +both your crashing bug and an unrelated crashing bug that masks it +will look like the same thing, and mislead \hgcmd{bisect}. + +Another useful situation in which to use \hgcmdargs{bisect}{--skip} is +if you can't test a revision because your project was in a broken and +hence untestable state at that revision, perhaps because someone +checked in a change that prevented the project from building. + +\subsection{Bracket your search lazily} + +Choosing the first ``good'' and ``bad'' changesets that will mark the +end points of your search is often easy, but it bears a little +discussion nevertheless. From the perspective of \hgcmd{bisect}, the +``newest'' changeset is conventionally ``bad'', and the older +changeset is ``good''. + +If you're having trouble remembering when a suitable ``good'' change +was, so that you can tell \hgcmd{bisect}, you could do worse than +testing changesets at random. Just remember to eliminate contenders +that can't possibly exhibit the bug (perhaps because the feature with +the bug isn't present yet) and those where another problem masks the +bug (as I discussed above). + +Even if you end up ``early'' by thousands of changesets or months of +history, you will only add a handful of tests to the total number that +\hgcmd{bisect} must perform, thanks to its logarithmic behaviour. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/ch10-hook.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,1413 @@ +\chapter{Handling repository events with hooks} +\label{chap:hook} + +Mercurial offers a powerful mechanism to let you perform automated +actions in response to events that occur in a repository. In some +cases, you can even control Mercurial's response to those events. + +The name Mercurial uses for one of these actions is a \emph{hook}. +Hooks are called ``triggers'' in some revision control systems, but +the two names refer to the same idea. + +\section{An overview of hooks in Mercurial} + +Here is a brief list of the hooks that Mercurial supports. We will +revisit each of these hooks in more detail later, in +section~\ref{sec:hook:ref}. + +\begin{itemize} +\item[\small\hook{changegroup}] This is run after a group of + changesets has been brought into the repository from elsewhere. +\item[\small\hook{commit}] This is run after a new changeset has been + created in the local repository. +\item[\small\hook{incoming}] This is run once for each new changeset + that is brought into the repository from elsewhere. Notice the + difference from \hook{changegroup}, which is run once per + \emph{group} of changesets brought in. +\item[\small\hook{outgoing}] This is run after a group of changesets + has been transmitted from this repository. +\item[\small\hook{prechangegroup}] This is run before starting to + bring a group of changesets into the repository. +\item[\small\hook{precommit}] Controlling. This is run before starting + a commit. +\item[\small\hook{preoutgoing}] Controlling. This is run before + starting to transmit a group of changesets from this repository. +\item[\small\hook{pretag}] Controlling. This is run before creating a tag. +\item[\small\hook{pretxnchangegroup}] Controlling. This is run after a + group of changesets has been brought into the local repository from + another, but before the transaction completes that will make the + changes permanent in the repository. +\item[\small\hook{pretxncommit}] Controlling. This is run after a new + changeset has been created in the local repository, but before the + transaction completes that will make it permanent. +\item[\small\hook{preupdate}] Controlling. This is run before starting + an update or merge of the working directory. +\item[\small\hook{tag}] This is run after a tag is created. +\item[\small\hook{update}] This is run after an update or merge of the + working directory has finished. +\end{itemize} +Each of the hooks whose description begins with the word +``Controlling'' has the ability to determine whether an activity can +proceed. If the hook succeeds, the activity may proceed; if it fails, +the activity is either not permitted or undone, depending on the hook. + +\section{Hooks and security} + +\subsection{Hooks are run with your privileges} + +When you run a Mercurial command in a repository, and the command +causes a hook to run, that hook runs on \emph{your} system, under +\emph{your} user account, with \emph{your} privilege level. Since +hooks are arbitrary pieces of executable code, you should treat them +with an appropriate level of suspicion. Do not install a hook unless +you are confident that you know who created it and what it does. + +In some cases, you may be exposed to hooks that you did not install +yourself. If you work with Mercurial on an unfamiliar system, +Mercurial will run hooks defined in that system's global \hgrc\ file. + +If you are working with a repository owned by another user, Mercurial +can run hooks defined in that user's repository, but it will still run +them as ``you''. For example, if you \hgcmd{pull} from that +repository, and its \sfilename{.hg/hgrc} defines a local +\hook{outgoing} hook, that hook will run under your user account, even +though you don't own that repository. + +\begin{note} + This only applies if you are pulling from a repository on a local or + network filesystem. If you're pulling over http or ssh, any + \hook{outgoing} hook will run under whatever account is executing + the server process, on the server. +\end{note} + +XXX To see what hooks are defined in a repository, use the +\hgcmdargs{config}{hooks} command. If you are working in one +repository, but talking to another that you do not own (e.g.~using +\hgcmd{pull} or \hgcmd{incoming}), remember that it is the other +repository's hooks you should be checking, not your own. + +\subsection{Hooks do not propagate} + +In Mercurial, hooks are not revision controlled, and do not propagate +when you clone, or pull from, a repository. The reason for this is +simple: a hook is a completely arbitrary piece of executable code. It +runs under your user identity, with your privilege level, on your +machine. + +It would be extremely reckless for any distributed revision control +system to implement revision-controlled hooks, as this would offer an +easily exploitable way to subvert the accounts of users of the +revision control system. + +Since Mercurial does not propagate hooks, if you are collaborating +with other people on a common project, you should not assume that they +are using the same Mercurial hooks as you are, or that theirs are +correctly configured. You should document the hooks you expect people +to use. + +In a corporate intranet, this is somewhat easier to control, as you +can for example provide a ``standard'' installation of Mercurial on an +NFS filesystem, and use a site-wide \hgrc\ file to define hooks that +all users will see. However, this too has its limits; see below. + +\subsection{Hooks can be overridden} + +Mercurial allows you to override a hook definition by redefining the +hook. You can disable it by setting its value to the empty string, or +change its behaviour as you wish. + +If you deploy a system-~or site-wide \hgrc\ file that defines some +hooks, you should thus understand that your users can disable or +override those hooks. + +\subsection{Ensuring that critical hooks are run} + +Sometimes you may want to enforce a policy that you do not want others +to be able to work around. For example, you may have a requirement +that every changeset must pass a rigorous set of tests. Defining this +requirement via a hook in a site-wide \hgrc\ won't work for remote +users on laptops, and of course local users can subvert it at will by +overriding the hook. + +Instead, you can set up your policies for use of Mercurial so that +people are expected to propagate changes through a well-known +``canonical'' server that you have locked down and configured +appropriately. + +One way to do this is via a combination of social engineering and +technology. Set up a restricted-access account; users can push +changes over the network to repositories managed by this account, but +they cannot log into the account and run normal shell commands. In +this scenario, a user can commit a changeset that contains any old +garbage they want. + +When someone pushes a changeset to the server that everyone pulls +from, the server will test the changeset before it accepts it as +permanent, and reject it if it fails to pass the test suite. If +people only pull changes from this filtering server, it will serve to +ensure that all changes that people pull have been automatically +vetted. + +\section{Care with \texttt{pretxn} hooks in a shared-access repository} + +If you want to use hooks to do some automated work in a repository +that a number of people have shared access to, you need to be careful +in how you do this. + +Mercurial only locks a repository when it is writing to the +repository, and only the parts of Mercurial that write to the +repository pay attention to locks. Write locks are necessary to +prevent multiple simultaneous writers from scribbling on each other's +work, corrupting the repository. + +Because Mercurial is careful with the order in which it reads and +writes data, it does not need to acquire a lock when it wants to read +data from the repository. The parts of Mercurial that read from the +repository never pay attention to locks. This lockless reading scheme +greatly increases performance and concurrency. + +With great performance comes a trade-off, though, one which has the +potential to cause you trouble unless you're aware of it. To describe +this requires a little detail about how Mercurial adds changesets to a +repository and reads those changes. + +When Mercurial \emph{writes} metadata, it writes it straight into the +destination file. It writes file data first, then manifest data +(which contains pointers to the new file data), then changelog data +(which contains pointers to the new manifest data). Before the first +write to each file, it stores a record of where the end of the file +was in its transaction log. If the transaction must be rolled back, +Mercurial simply truncates each file back to the size it was before the +transaction began. + +When Mercurial \emph{reads} metadata, it reads the changelog first, +then everything else. Since a reader will only access parts of the +manifest or file metadata that it can see in the changelog, it can +never see partially written data. + +Some controlling hooks (\hook{pretxncommit} and +\hook{pretxnchangegroup}) run when a transaction is almost complete. +All of the metadata has been written, but Mercurial can still roll the +transaction back and cause the newly-written data to disappear. + +If one of these hooks runs for long, it opens a window of time during +which a reader can see the metadata for changesets that are not yet +permanent, and should not be thought of as ``really there''. The +longer the hook runs, the longer that window is open. + +\subsection{The problem illustrated} + +In principle, a good use for the \hook{pretxnchangegroup} hook would +be to automatically build and test incoming changes before they are +accepted into a central repository. This could let you guarantee that +nobody can push changes to this repository that ``break the build''. +But if a client can pull changes while they're being tested, the +usefulness of the test is zero; an unsuspecting someone can pull +untested changes, potentially breaking their build. + +The safest technological answer to this challenge is to set up such a +``gatekeeper'' repository as \emph{unidirectional}. Let it take +changes pushed in from the outside, but do not allow anyone to pull +changes from it (use the \hook{preoutgoing} hook to lock it down). +Configure a \hook{changegroup} hook so that if a build or test +succeeds, the hook will push the new changes out to another repository +that people \emph{can} pull from. + +In practice, putting a centralised bottleneck like this in place is +not often a good idea, and transaction visibility has nothing to do +with the problem. As the size of a project---and the time it takes to +build and test---grows, you rapidly run into a wall with this ``try +before you buy'' approach, where you have more changesets to test than +time in which to deal with them. The inevitable result is frustration +on the part of all involved. + +An approach that scales better is to get people to build and test +before they push, then run automated builds and tests centrally +\emph{after} a push, to be sure all is well. The advantage of this +approach is that it does not impose a limit on the rate at which the +repository can accept changes. + +\section{A short tutorial on using hooks} +\label{sec:hook:simple} + +It is easy to write a Mercurial hook. Let's start with a hook that +runs when you finish a \hgcmd{commit}, and simply prints the hash of +the changeset you just created. The hook is called \hook{commit}. + +\begin{figure}[ht] + \interaction{hook.simple.init} + \caption{A simple hook that runs when a changeset is committed} + \label{ex:hook:init} +\end{figure} + +All hooks follow the pattern in example~\ref{ex:hook:init}. You add +an entry to the \rcsection{hooks} section of your \hgrc. On the left +is the name of the event to trigger on; on the right is the action to +take. As you can see, you can run an arbitrary shell command in a +hook. Mercurial passes extra information to the hook using +environment variables (look for \envar{HG\_NODE} in the example). + +\subsection{Performing multiple actions per event} + +Quite often, you will want to define more than one hook for a +particular kind of event, as shown in example~\ref{ex:hook:ext}. +Mercurial lets you do this by adding an \emph{extension} to the end of +a hook's name. You extend a hook's name by giving the name of the +hook, followed by a full stop (the ``\texttt{.}'' character), followed +by some more text of your choosing. For example, Mercurial will run +both \texttt{commit.foo} and \texttt{commit.bar} when the +\texttt{commit} event occurs. + +\begin{figure}[ht] + \interaction{hook.simple.ext} + \caption{Defining a second \hook{commit} hook} + \label{ex:hook:ext} +\end{figure} + +To give a well-defined order of execution when there are multiple +hooks defined for an event, Mercurial sorts hooks by extension, and +executes the hook commands in this sorted order. In the above +example, it will execute \texttt{commit.bar} before +\texttt{commit.foo}, and \texttt{commit} before both. + +It is a good idea to use a somewhat descriptive extension when you +define a new hook. This will help you to remember what the hook was +for. If the hook fails, you'll get an error message that contains the +hook name and extension, so using a descriptive extension could give +you an immediate hint as to why the hook failed (see +section~\ref{sec:hook:perm} for an example). + +\subsection{Controlling whether an activity can proceed} +\label{sec:hook:perm} + +In our earlier examples, we used the \hook{commit} hook, which is +run after a commit has completed. This is one of several Mercurial +hooks that run after an activity finishes. Such hooks have no way of +influencing the activity itself. + +Mercurial defines a number of events that occur before an activity +starts; or after it starts, but before it finishes. Hooks that +trigger on these events have the added ability to choose whether the +activity can continue, or will abort. + +The \hook{pretxncommit} hook runs after a commit has all but +completed. In other words, the metadata representing the changeset +has been written out to disk, but the transaction has not yet been +allowed to complete. The \hook{pretxncommit} hook has the ability to +decide whether the transaction can complete, or must be rolled back. + +If the \hook{pretxncommit} hook exits with a status code of zero, the +transaction is allowed to complete; the commit finishes; and the +\hook{commit} hook is run. If the \hook{pretxncommit} hook exits with +a non-zero status code, the transaction is rolled back; the metadata +representing the changeset is erased; and the \hook{commit} hook is +not run. + +\begin{figure}[ht] + \interaction{hook.simple.pretxncommit} + \caption{Using the \hook{pretxncommit} hook to control commits} + \label{ex:hook:pretxncommit} +\end{figure} + +The hook in example~\ref{ex:hook:pretxncommit} checks that a commit +comment contains a bug ID. If it does, the commit can complete. If +not, the commit is rolled back. + +\section{Writing your own hooks} + +When you are writing a hook, you might find it useful to run Mercurial +either with the \hggopt{-v} option, or the \rcitem{ui}{verbose} config +item set to ``true''. When you do so, Mercurial will print a message +before it calls each hook. + +\subsection{Choosing how your hook should run} +\label{sec:hook:lang} + +You can write a hook either as a normal program---typically a shell +script---or as a Python function that is executed within the Mercurial +process. + +Writing a hook as an external program has the advantage that it +requires no knowledge of Mercurial's internals. You can call normal +Mercurial commands to get any added information you need. The +trade-off is that external hooks are slower than in-process hooks. + +An in-process Python hook has complete access to the Mercurial API, +and does not ``shell out'' to another process, so it is inherently +faster than an external hook. It is also easier to obtain much of the +information that a hook requires by using the Mercurial API than by +running Mercurial commands. + +If you are comfortable with Python, or require high performance, +writing your hooks in Python may be a good choice. However, when you +have a straightforward hook to write and you don't need to care about +performance (probably the majority of hooks), a shell script is +perfectly fine. + +\subsection{Hook parameters} +\label{sec:hook:param} + +Mercurial calls each hook with a set of well-defined parameters. In +Python, a parameter is passed as a keyword argument to your hook +function. For an external program, a parameter is passed as an +environment variable. + +Whether your hook is written in Python or as a shell script, the +hook-specific parameter names and values will be the same. A boolean +parameter will be represented as a boolean value in Python, but as the +number 1 (for ``true'') or 0 (for ``false'') as an environment +variable for an external hook. If a hook parameter is named +\texttt{foo}, the keyword argument for a Python hook will also be +named \texttt{foo}, while the environment variable for an external +hook will be named \texttt{HG\_FOO}. + +\subsection{Hook return values and activity control} + +A hook that executes successfully must exit with a status of zero if +external, or return boolean ``false'' if in-process. Failure is +indicated with a non-zero exit status from an external hook, or an +in-process hook returning boolean ``true''. If an in-process hook +raises an exception, the hook is considered to have failed. + +For a hook that controls whether an activity can proceed, zero/false +means ``allow'', while non-zero/true/exception means ``deny''. + +\subsection{Writing an external hook} + +When you define an external hook in your \hgrc\ and the hook is run, +its value is passed to your shell, which interprets it. This means +that you can use normal shell constructs in the body of the hook. + +An executable hook is always run with its current directory set to a +repository's root directory. + +Each hook parameter is passed in as an environment variable; the name +is upper-cased, and prefixed with the string ``\texttt{HG\_}''. + +With the exception of hook parameters, Mercurial does not set or +modify any environment variables when running a hook. This is useful +to remember if you are writing a site-wide hook that may be run by a +number of different users with differing environment variables set. +In multi-user situations, you should not rely on environment variables +being set to the values you have in your environment when testing the +hook. + +\subsection{Telling Mercurial to use an in-process hook} + +The \hgrc\ syntax for defining an in-process hook is slightly +different than for an executable hook. The value of the hook must +start with the text ``\texttt{python:}'', and continue with the +fully-qualified name of a callable object to use as the hook's value. + +The module in which a hook lives is automatically imported when a hook +is run. So long as you have the module name and \envar{PYTHONPATH} +right, it should ``just work''. + +The following \hgrc\ example snippet illustrates the syntax and +meaning of the notions we just described. +\begin{codesample2} + [hooks] + commit.example = python:mymodule.submodule.myhook +\end{codesample2} +When Mercurial runs the \texttt{commit.example} hook, it imports +\texttt{mymodule.submodule}, looks for the callable object named +\texttt{myhook}, and calls it. + +\subsection{Writing an in-process hook} + +The simplest in-process hook does nothing, but illustrates the basic +shape of the hook API: +\begin{codesample2} + def myhook(ui, repo, **kwargs): + pass +\end{codesample2} +The first argument to a Python hook is always a +\pymodclass{mercurial.ui}{ui} object. The second is a repository object; +at the moment, it is always an instance of +\pymodclass{mercurial.localrepo}{localrepository}. Following these two +arguments are other keyword arguments. Which ones are passed in +depends on the hook being called, but a hook can ignore arguments it +doesn't care about by dropping them into a keyword argument dict, as +with \texttt{**kwargs} above. + +\section{Some hook examples} + +\subsection{Writing meaningful commit messages} + +It's hard to imagine a useful commit message being very short. The +simple \hook{pretxncommit} hook of figure~\ref{ex:hook:msglen.go} +will prevent you from committing a changeset with a message that is +less than ten bytes long. + +\begin{figure}[ht] + \interaction{hook.msglen.go} + \caption{A hook that forbids overly short commit messages} + \label{ex:hook:msglen.go} +\end{figure} + +\subsection{Checking for trailing whitespace} + +An interesting use of a commit-related hook is to help you to write +cleaner code. A simple example of ``cleaner code'' is the dictum that +a change should not add any new lines of text that contain ``trailing +whitespace''. Trailing whitespace is a series of space and tab +characters at the end of a line of text. In most cases, trailing +whitespace is unnecessary, invisible noise, but it is occasionally +problematic, and people often prefer to get rid of it. + +You can use either the \hook{precommit} or \hook{pretxncommit} hook to +tell whether you have a trailing whitespace problem. If you use the +\hook{precommit} hook, the hook will not know which files you are +committing, so it will have to check every modified file in the +repository for trailing white space. If you want to commit a change +to just the file \filename{foo}, but the file \filename{bar} contains +trailing whitespace, doing a check in the \hook{precommit} hook will +prevent you from committing \filename{foo} due to the problem with +\filename{bar}. This doesn't seem right. + +Should you choose the \hook{pretxncommit} hook, the check won't occur +until just before the transaction for the commit completes. This will +allow you to check for problems only the exact files that are being +committed. However, if you entered the commit message interactively +and the hook fails, the transaction will roll back; you'll have to +re-enter the commit message after you fix the trailing whitespace and +run \hgcmd{commit} again. + +\begin{figure}[ht] + \interaction{hook.ws.simple} + \caption{A simple hook that checks for trailing whitespace} + \label{ex:hook:ws.simple} +\end{figure} + +Figure~\ref{ex:hook:ws.simple} introduces a simple \hook{pretxncommit} +hook that checks for trailing whitespace. This hook is short, but not +very helpful. It exits with an error status if a change adds a line +with trailing whitespace to any file, but does not print any +information that might help us to identify the offending file or +line. It also has the nice property of not paying attention to +unmodified lines; only lines that introduce new trailing whitespace +cause problems. + +\begin{figure}[ht] + \interaction{hook.ws.better} + \caption{A better trailing whitespace hook} + \label{ex:hook:ws.better} +\end{figure} + +The example of figure~\ref{ex:hook:ws.better} is much more complex, +but also more useful. It parses a unified diff to see if any lines +add trailing whitespace, and prints the name of the file and the line +number of each such occurrence. Even better, if the change adds +trailing whitespace, this hook saves the commit comment and prints the +name of the save file before exiting and telling Mercurial to roll the +transaction back, so you can use +\hgcmdargs{commit}{\hgopt{commit}{-l}~\emph{filename}} to reuse the +saved commit message once you've corrected the problem. + +As a final aside, note in figure~\ref{ex:hook:ws.better} the use of +\command{perl}'s in-place editing feature to get rid of trailing +whitespace from a file. This is concise and useful enough that I will +reproduce it here. +\begin{codesample2} + perl -pi -e 's,\textbackslash{}s+\$,,' filename +\end{codesample2} + +\section{Bundled hooks} + +Mercurial ships with several bundled hooks. You can find them in the +\dirname{hgext} directory of a Mercurial source tree. If you are +using a Mercurial binary package, the hooks will be located in the +\dirname{hgext} directory of wherever your package installer put +Mercurial. + +\subsection{\hgext{acl}---access control for parts of a repository} + +The \hgext{acl} extension lets you control which remote users are +allowed to push changesets to a networked server. You can protect any +portion of a repository (including the entire repo), so that a +specific remote user can push changes that do not affect the protected +portion. + +This extension implements access control based on the identity of the +user performing a push, \emph{not} on who committed the changesets +they're pushing. It makes sense to use this hook only if you have a +locked-down server environment that authenticates remote users, and +you want to be sure that only specific users are allowed to push +changes to that server. + +\subsubsection{Configuring the \hook{acl} hook} + +In order to manage incoming changesets, the \hgext{acl} hook must be +used as a \hook{pretxnchangegroup} hook. This lets it see which files +are modified by each incoming changeset, and roll back a group of +changesets if they modify ``forbidden'' files. Example: +\begin{codesample2} + [hooks] + pretxnchangegroup.acl = python:hgext.acl.hook +\end{codesample2} + +The \hgext{acl} extension is configured using three sections. + +The \rcsection{acl} section has only one entry, \rcitem{acl}{sources}, +which lists the sources of incoming changesets that the hook should +pay attention to. You don't normally need to configure this section. +\begin{itemize} +\item[\rcitem{acl}{serve}] Control incoming changesets that are arriving + from a remote repository over http or ssh. This is the default + value of \rcitem{acl}{sources}, and usually the only setting you'll + need for this configuration item. +\item[\rcitem{acl}{pull}] Control incoming changesets that are + arriving via a pull from a local repository. +\item[\rcitem{acl}{push}] Control incoming changesets that are + arriving via a push from a local repository. +\item[\rcitem{acl}{bundle}] Control incoming changesets that are + arriving from another repository via a bundle. +\end{itemize} + +The \rcsection{acl.allow} section controls the users that are allowed to +add changesets to the repository. If this section is not present, all +users that are not explicitly denied are allowed. If this section is +present, all users that are not explicitly allowed are denied (so an +empty section means that all users are denied). + +The \rcsection{acl.deny} section determines which users are denied +from adding changesets to the repository. If this section is not +present or is empty, no users are denied. + +The syntaxes for the \rcsection{acl.allow} and \rcsection{acl.deny} +sections are identical. On the left of each entry is a glob pattern +that matches files or directories, relative to the root of the +repository; on the right, a user name. + +In the following example, the user \texttt{docwriter} can only push +changes to the \dirname{docs} subtree of the repository, while +\texttt{intern} can push changes to any file or directory except +\dirname{source/sensitive}. +\begin{codesample2} + [acl.allow] + docs/** = docwriter + + [acl.deny] + source/sensitive/** = intern +\end{codesample2} + +\subsubsection{Testing and troubleshooting} + +If you want to test the \hgext{acl} hook, run it with Mercurial's +debugging output enabled. Since you'll probably be running it on a +server where it's not convenient (or sometimes possible) to pass in +the \hggopt{--debug} option, don't forget that you can enable +debugging output in your \hgrc: +\begin{codesample2} + [ui] + debug = true +\end{codesample2} +With this enabled, the \hgext{acl} hook will print enough information +to let you figure out why it is allowing or forbidding pushes from +specific users. + +\subsection{\hgext{bugzilla}---integration with Bugzilla} + +The \hgext{bugzilla} extension adds a comment to a Bugzilla bug +whenever it finds a reference to that bug ID in a commit comment. You +can install this hook on a shared server, so that any time a remote +user pushes changes to this server, the hook gets run. + +It adds a comment to the bug that looks like this (you can configure +the contents of the comment---see below): +\begin{codesample2} + Changeset aad8b264143a, made by Joe User <joe.user@domain.com> in + the frobnitz repository, refers to this bug. + + For complete details, see + http://hg.domain.com/frobnitz?cmd=changeset;node=aad8b264143a + + Changeset description: + Fix bug 10483 by guarding against some NULL pointers +\end{codesample2} +The value of this hook is that it automates the process of updating a +bug any time a changeset refers to it. If you configure the hook +properly, it makes it easy for people to browse straight from a +Bugzilla bug to a changeset that refers to that bug. + +You can use the code in this hook as a starting point for some more +exotic Bugzilla integration recipes. Here are a few possibilities: +\begin{itemize} +\item Require that every changeset pushed to the server have a valid + bug~ID in its commit comment. In this case, you'd want to configure + the hook as a \hook{pretxncommit} hook. This would allow the hook + to reject changes that didn't contain bug IDs. +\item Allow incoming changesets to automatically modify the + \emph{state} of a bug, as well as simply adding a comment. For + example, the hook could recognise the string ``fixed bug 31337'' as + indicating that it should update the state of bug 31337 to + ``requires testing''. +\end{itemize} + +\subsubsection{Configuring the \hook{bugzilla} hook} +\label{sec:hook:bugzilla:config} + +You should configure this hook in your server's \hgrc\ as an +\hook{incoming} hook, for example as follows: +\begin{codesample2} + [hooks] + incoming.bugzilla = python:hgext.bugzilla.hook +\end{codesample2} + +Because of the specialised nature of this hook, and because Bugzilla +was not written with this kind of integration in mind, configuring +this hook is a somewhat involved process. + +Before you begin, you must install the MySQL bindings for Python on +the host(s) where you'll be running the hook. If this is not +available as a binary package for your system, you can download it +from~\cite{web:mysql-python}. + +Configuration information for this hook lives in the +\rcsection{bugzilla} section of your \hgrc. +\begin{itemize} +\item[\rcitem{bugzilla}{version}] The version of Bugzilla installed on + the server. The database schema that Bugzilla uses changes + occasionally, so this hook has to know exactly which schema to use. + At the moment, the only version supported is \texttt{2.16}. +\item[\rcitem{bugzilla}{host}] The hostname of the MySQL server that + stores your Bugzilla data. The database must be configured to allow + connections from whatever host you are running the \hook{bugzilla} + hook on. +\item[\rcitem{bugzilla}{user}] The username with which to connect to + the MySQL server. The database must be configured to allow this + user to connect from whatever host you are running the + \hook{bugzilla} hook on. This user must be able to access and + modify Bugzilla tables. The default value of this item is + \texttt{bugs}, which is the standard name of the Bugzilla user in a + MySQL database. +\item[\rcitem{bugzilla}{password}] The MySQL password for the user you + configured above. This is stored as plain text, so you should make + sure that unauthorised users cannot read the \hgrc\ file where you + store this information. +\item[\rcitem{bugzilla}{db}] The name of the Bugzilla database on the + MySQL server. The default value of this item is \texttt{bugs}, + which is the standard name of the MySQL database where Bugzilla + stores its data. +\item[\rcitem{bugzilla}{notify}] If you want Bugzilla to send out a + notification email to subscribers after this hook has added a + comment to a bug, you will need this hook to run a command whenever + it updates the database. The command to run depends on where you + have installed Bugzilla, but it will typically look something like + this, if you have Bugzilla installed in + \dirname{/var/www/html/bugzilla}: + \begin{codesample4} + cd /var/www/html/bugzilla && ./processmail %s nobody@nowhere.com + \end{codesample4} + The Bugzilla \texttt{processmail} program expects to be given a + bug~ID (the hook replaces ``\texttt{\%s}'' with the bug~ID) and an + email address. It also expects to be able to write to some files in + the directory that it runs in. If Bugzilla and this hook are not + installed on the same machine, you will need to find a way to run + \texttt{processmail} on the server where Bugzilla is installed. +\end{itemize} + +\subsubsection{Mapping committer names to Bugzilla user names} + +By default, the \hgext{bugzilla} hook tries to use the email address +of a changeset's committer as the Bugzilla user name with which to +update a bug. If this does not suit your needs, you can map committer +email addresses to Bugzilla user names using a \rcsection{usermap} +section. + +Each item in the \rcsection{usermap} section contains an email address +on the left, and a Bugzilla user name on the right. +\begin{codesample2} + [usermap] + jane.user@example.com = jane +\end{codesample2} +You can either keep the \rcsection{usermap} data in a normal \hgrc, or +tell the \hgext{bugzilla} hook to read the information from an +external \filename{usermap} file. In the latter case, you can store +\filename{usermap} data by itself in (for example) a user-modifiable +repository. This makes it possible to let your users maintain their +own \rcitem{bugzilla}{usermap} entries. The main \hgrc\ file might +look like this: +\begin{codesample2} + # regular hgrc file refers to external usermap file + [bugzilla] + usermap = /home/hg/repos/userdata/bugzilla-usermap.conf +\end{codesample2} +While the \filename{usermap} file that it refers to might look like +this: +\begin{codesample2} + # bugzilla-usermap.conf - inside a hg repository + [usermap] + stephanie@example.com = steph +\end{codesample2} + +\subsubsection{Configuring the text that gets added to a bug} + +You can configure the text that this hook adds as a comment; you +specify it in the form of a Mercurial template. Several \hgrc\ +entries (still in the \rcsection{bugzilla} section) control this +behaviour. +\begin{itemize} +\item[\texttt{strip}] The number of leading path elements to strip + from a repository's path name to construct a partial path for a URL. + For example, if the repositories on your server live under + \dirname{/home/hg/repos}, and you have a repository whose path is + \dirname{/home/hg/repos/app/tests}, then setting \texttt{strip} to + \texttt{4} will give a partial path of \dirname{app/tests}. The + hook will make this partial path available when expanding a + template, as \texttt{webroot}. +\item[\texttt{template}] The text of the template to use. In addition + to the usual changeset-related variables, this template can use + \texttt{hgweb} (the value of the \texttt{hgweb} configuration item + above) and \texttt{webroot} (the path constructed using + \texttt{strip} above). +\end{itemize} + +In addition, you can add a \rcitem{web}{baseurl} item to the +\rcsection{web} section of your \hgrc. The \hgext{bugzilla} hook will +make this available when expanding a template, as the base string to +use when constructing a URL that will let users browse from a Bugzilla +comment to view a changeset. Example: +\begin{codesample2} + [web] + baseurl = http://hg.domain.com/ +\end{codesample2} + +Here is an example set of \hgext{bugzilla} hook config information. +\begin{codesample2} + [bugzilla] + host = bugzilla.example.com + password = mypassword + version = 2.16 + # server-side repos live in /home/hg/repos, so strip 4 leading + # separators + strip = 4 + hgweb = http://hg.example.com/ + usermap = /home/hg/repos/notify/bugzilla.conf + template = Changeset \{node|short\}, made by \{author\} in the \{webroot\} + repo, refers to this bug.\\nFor complete details, see + \{hgweb\}\{webroot\}?cmd=changeset;node=\{node|short\}\\nChangeset + description:\\n\\t\{desc|tabindent\} +\end{codesample2} + +\subsubsection{Testing and troubleshooting} + +The most common problems with configuring the \hgext{bugzilla} hook +relate to running Bugzilla's \filename{processmail} script and mapping +committer names to user names. + +Recall from section~\ref{sec:hook:bugzilla:config} above that the user +that runs the Mercurial process on the server is also the one that +will run the \filename{processmail} script. The +\filename{processmail} script sometimes causes Bugzilla to write to +files in its configuration directory, and Bugzilla's configuration +files are usually owned by the user that your web server runs under. + +You can cause \filename{processmail} to be run with the suitable +user's identity using the \command{sudo} command. Here is an example +entry for a \filename{sudoers} file. +\begin{codesample2} + hg_user = (httpd_user) NOPASSWD: /var/www/html/bugzilla/processmail-wrapper %s +\end{codesample2} +This allows the \texttt{hg\_user} user to run a +\filename{processmail-wrapper} program under the identity of +\texttt{httpd\_user}. + +This indirection through a wrapper script is necessary, because +\filename{processmail} expects to be run with its current directory +set to wherever you installed Bugzilla; you can't specify that kind of +constraint in a \filename{sudoers} file. The contents of the wrapper +script are simple: +\begin{codesample2} + #!/bin/sh + cd `dirname $0` && ./processmail "$1" nobody@example.com +\end{codesample2} +It doesn't seem to matter what email address you pass to +\filename{processmail}. + +If your \rcsection{usermap} is not set up correctly, users will see an +error message from the \hgext{bugzilla} hook when they push changes +to the server. The error message will look like this: +\begin{codesample2} + cannot find bugzilla user id for john.q.public@example.com +\end{codesample2} +What this means is that the committer's address, +\texttt{john.q.public@example.com}, is not a valid Bugzilla user name, +nor does it have an entry in your \rcsection{usermap} that maps it to +a valid Bugzilla user name. + +\subsection{\hgext{notify}---send email notifications} + +Although Mercurial's built-in web server provides RSS feeds of changes +in every repository, many people prefer to receive change +notifications via email. The \hgext{notify} hook lets you send out +notifications to a set of email addresses whenever changesets arrive +that those subscribers are interested in. + +As with the \hgext{bugzilla} hook, the \hgext{notify} hook is +template-driven, so you can customise the contents of the notification +messages that it sends. + +By default, the \hgext{notify} hook includes a diff of every changeset +that it sends out; you can limit the size of the diff, or turn this +feature off entirely. It is useful for letting subscribers review +changes immediately, rather than clicking to follow a URL. + +\subsubsection{Configuring the \hgext{notify} hook} + +You can set up the \hgext{notify} hook to send one email message per +incoming changeset, or one per incoming group of changesets (all those +that arrived in a single pull or push). +\begin{codesample2} + [hooks] + # send one email per group of changes + changegroup.notify = python:hgext.notify.hook + # send one email per change + incoming.notify = python:hgext.notify.hook +\end{codesample2} + +Configuration information for this hook lives in the +\rcsection{notify} section of a \hgrc\ file. +\begin{itemize} +\item[\rcitem{notify}{test}] By default, this hook does not send out + email at all; instead, it prints the message that it \emph{would} + send. Set this item to \texttt{false} to allow email to be sent. + The reason that sending of email is turned off by default is that it + takes several tries to configure this extension exactly as you would + like, and it would be bad form to spam subscribers with a number of + ``broken'' notifications while you debug your configuration. +\item[\rcitem{notify}{config}] The path to a configuration file that + contains subscription information. This is kept separate from the + main \hgrc\ so that you can maintain it in a repository of its own. + People can then clone that repository, update their subscriptions, + and push the changes back to your server. +\item[\rcitem{notify}{strip}] The number of leading path separator + characters to strip from a repository's path, when deciding whether + a repository has subscribers. For example, if the repositories on + your server live in \dirname{/home/hg/repos}, and \hgext{notify} is + considering a repository named \dirname{/home/hg/repos/shared/test}, + setting \rcitem{notify}{strip} to \texttt{4} will cause + \hgext{notify} to trim the path it considers down to + \dirname{shared/test}, and it will match subscribers against that. +\item[\rcitem{notify}{template}] The template text to use when sending + messages. This specifies both the contents of the message header + and its body. +\item[\rcitem{notify}{maxdiff}] The maximum number of lines of diff + data to append to the end of a message. If a diff is longer than + this, it is truncated. By default, this is set to 300. Set this to + \texttt{0} to omit diffs from notification emails. +\item[\rcitem{notify}{sources}] A list of sources of changesets to + consider. This lets you limit \hgext{notify} to only sending out + email about changes that remote users pushed into this repository + via a server, for example. See section~\ref{sec:hook:sources} for + the sources you can specify here. +\end{itemize} + +If you set the \rcitem{web}{baseurl} item in the \rcsection{web} +section, you can use it in a template; it will be available as +\texttt{webroot}. + +Here is an example set of \hgext{notify} configuration information. +\begin{codesample2} + [notify] + # really send email + test = false + # subscriber data lives in the notify repo + config = /home/hg/repos/notify/notify.conf + # repos live in /home/hg/repos on server, so strip 4 "/" chars + strip = 4 + template = X-Hg-Repo: \{webroot\} + Subject: \{webroot\}: \{desc|firstline|strip\} + From: \{author\} + + changeset \{node|short\} in \{root\} + details: \{baseurl\}\{webroot\}?cmd=changeset;node=\{node|short\} + description: + \{desc|tabindent|strip\} + + [web] + baseurl = http://hg.example.com/ +\end{codesample2} + +This will produce a message that looks like the following: +\begin{codesample2} + X-Hg-Repo: tests/slave + Subject: tests/slave: Handle error case when slave has no buffers + Date: Wed, 2 Aug 2006 15:25:46 -0700 (PDT) + + changeset 3cba9bfe74b5 in /home/hg/repos/tests/slave + details: http://hg.example.com/tests/slave?cmd=changeset;node=3cba9bfe74b5 + description: + Handle error case when slave has no buffers + diffs (54 lines): + + diff -r 9d95df7cf2ad -r 3cba9bfe74b5 include/tests.h + --- a/include/tests.h Wed Aug 02 15:19:52 2006 -0700 + +++ b/include/tests.h Wed Aug 02 15:25:26 2006 -0700 + @@ -212,6 +212,15 @@ static __inline__ void test_headers(void *h) + [...snip...] +\end{codesample2} + +\subsubsection{Testing and troubleshooting} + +Do not forget that by default, the \hgext{notify} extension \emph{will + not send any mail} until you explicitly configure it to do so, by +setting \rcitem{notify}{test} to \texttt{false}. Until you do that, +it simply prints the message it \emph{would} send. + +\section{Information for writers of hooks} +\label{sec:hook:ref} + +\subsection{In-process hook execution} + +An in-process hook is called with arguments of the following form: +\begin{codesample2} + def myhook(ui, repo, **kwargs): + pass +\end{codesample2} +The \texttt{ui} parameter is a \pymodclass{mercurial.ui}{ui} object. +The \texttt{repo} parameter is a +\pymodclass{mercurial.localrepo}{localrepository} object. The +names and values of the \texttt{**kwargs} parameters depend on the +hook being invoked, with the following common features: +\begin{itemize} +\item If a parameter is named \texttt{node} or + \texttt{parent\emph{N}}, it will contain a hexadecimal changeset ID. + The empty string is used to represent ``null changeset ID'' instead + of a string of zeroes. +\item If a parameter is named \texttt{url}, it will contain the URL of + a remote repository, if that can be determined. +\item Boolean-valued parameters are represented as Python + \texttt{bool} objects. +\end{itemize} + +An in-process hook is called without a change to the process's working +directory (unlike external hooks, which are run in the root of the +repository). It must not change the process's working directory, or +it will cause any calls it makes into the Mercurial API to fail. + +If a hook returns a boolean ``false'' value, it is considered to have +succeeded. If it returns a boolean ``true'' value or raises an +exception, it is considered to have failed. A useful way to think of +the calling convention is ``tell me if you fail''. + +Note that changeset IDs are passed into Python hooks as hexadecimal +strings, not the binary hashes that Mercurial's APIs normally use. To +convert a hash from hex to binary, use the +\pymodfunc{mercurial.node}{bin} function. + +\subsection{External hook execution} + +An external hook is passed to the shell of the user running Mercurial. +Features of that shell, such as variable substitution and command +redirection, are available. The hook is run in the root directory of +the repository (unlike in-process hooks, which are run in the same +directory that Mercurial was run in). + +Hook parameters are passed to the hook as environment variables. Each +environment variable's name is converted in upper case and prefixed +with the string ``\texttt{HG\_}''. For example, if the name of a +parameter is ``\texttt{node}'', the name of the environment variable +representing that parameter will be ``\texttt{HG\_NODE}''. + +A boolean parameter is represented as the string ``\texttt{1}'' for +``true'', ``\texttt{0}'' for ``false''. If an environment variable is +named \envar{HG\_NODE}, \envar{HG\_PARENT1} or \envar{HG\_PARENT2}, it +contains a changeset ID represented as a hexadecimal string. The +empty string is used to represent ``null changeset ID'' instead of a +string of zeroes. If an environment variable is named +\envar{HG\_URL}, it will contain the URL of a remote repository, if +that can be determined. + +If a hook exits with a status of zero, it is considered to have +succeeded. If it exits with a non-zero status, it is considered to +have failed. + +\subsection{Finding out where changesets come from} + +A hook that involves the transfer of changesets between a local +repository and another may be able to find out information about the +``far side''. Mercurial knows \emph{how} changes are being +transferred, and in many cases \emph{where} they are being transferred +to or from. + +\subsubsection{Sources of changesets} +\label{sec:hook:sources} + +Mercurial will tell a hook what means are, or were, used to transfer +changesets between repositories. This is provided by Mercurial in a +Python parameter named \texttt{source}, or an environment variable named +\envar{HG\_SOURCE}. + +\begin{itemize} +\item[\texttt{serve}] Changesets are transferred to or from a remote + repository over http or ssh. +\item[\texttt{pull}] Changesets are being transferred via a pull from + one repository into another. +\item[\texttt{push}] Changesets are being transferred via a push from + one repository into another. +\item[\texttt{bundle}] Changesets are being transferred to or from a + bundle. +\end{itemize} + +\subsubsection{Where changes are going---remote repository URLs} +\label{sec:hook:url} + +When possible, Mercurial will tell a hook the location of the ``far +side'' of an activity that transfers changeset data between +repositories. This is provided by Mercurial in a Python parameter +named \texttt{url}, or an environment variable named \envar{HG\_URL}. + +This information is not always known. If a hook is invoked in a +repository that is being served via http or ssh, Mercurial cannot tell +where the remote repository is, but it may know where the client is +connecting from. In such cases, the URL will take one of the +following forms: +\begin{itemize} +\item \texttt{remote:ssh:\emph{ip-address}}---remote ssh client, at + the given IP address. +\item \texttt{remote:http:\emph{ip-address}}---remote http client, at + the given IP address. If the client is using SSL, this will be of + the form \texttt{remote:https:\emph{ip-address}}. +\item Empty---no information could be discovered about the remote + client. +\end{itemize} + +\section{Hook reference} + +\subsection{\hook{changegroup}---after remote changesets added} +\label{sec:hook:changegroup} + +This hook is run after a group of pre-existing changesets has been +added to the repository, for example via a \hgcmd{pull} or +\hgcmd{unbundle}. This hook is run once per operation that added one +or more changesets. This is in contrast to the \hook{incoming} hook, +which is run once per changeset, regardless of whether the changesets +arrive in a group. + +Some possible uses for this hook include kicking off an automated +build or test of the added changesets, updating a bug database, or +notifying subscribers that a repository contains new changes. + +Parameters to this hook: +\begin{itemize} +\item[\texttt{node}] A changeset ID. The changeset ID of the first + changeset in the group that was added. All changesets between this + and \index{tags!\texttt{tip}}\texttt{tip}, inclusive, were added by + a single \hgcmd{pull}, \hgcmd{push} or \hgcmd{unbundle}. +\item[\texttt{source}] A string. The source of these changes. See + section~\ref{sec:hook:sources} for details. +\item[\texttt{url}] A URL. The location of the remote repository, if + known. See section~\ref{sec:hook:url} for more information. +\end{itemize} + +See also: \hook{incoming} (section~\ref{sec:hook:incoming}), +\hook{prechangegroup} (section~\ref{sec:hook:prechangegroup}), +\hook{pretxnchangegroup} (section~\ref{sec:hook:pretxnchangegroup}) + +\subsection{\hook{commit}---after a new changeset is created} +\label{sec:hook:commit} + +This hook is run after a new changeset has been created. + +Parameters to this hook: +\begin{itemize} +\item[\texttt{node}] A changeset ID. The changeset ID of the newly + committed changeset. +\item[\texttt{parent1}] A changeset ID. The changeset ID of the first + parent of the newly committed changeset. +\item[\texttt{parent2}] A changeset ID. The changeset ID of the second + parent of the newly committed changeset. +\end{itemize} + +See also: \hook{precommit} (section~\ref{sec:hook:precommit}), +\hook{pretxncommit} (section~\ref{sec:hook:pretxncommit}) + +\subsection{\hook{incoming}---after one remote changeset is added} +\label{sec:hook:incoming} + +This hook is run after a pre-existing changeset has been added to the +repository, for example via a \hgcmd{push}. If a group of changesets +was added in a single operation, this hook is called once for each +added changeset. + +You can use this hook for the same purposes as the \hook{changegroup} +hook (section~\ref{sec:hook:changegroup}); it's simply more convenient +sometimes to run a hook once per group of changesets, while other +times it's handier once per changeset. + +Parameters to this hook: +\begin{itemize} +\item[\texttt{node}] A changeset ID. The ID of the newly added + changeset. +\item[\texttt{source}] A string. The source of these changes. See + section~\ref{sec:hook:sources} for details. +\item[\texttt{url}] A URL. The location of the remote repository, if + known. See section~\ref{sec:hook:url} for more information. +\end{itemize} + +See also: \hook{changegroup} (section~\ref{sec:hook:changegroup}) \hook{prechangegroup} (section~\ref{sec:hook:prechangegroup}), \hook{pretxnchangegroup} (section~\ref{sec:hook:pretxnchangegroup}) + +\subsection{\hook{outgoing}---after changesets are propagated} +\label{sec:hook:outgoing} + +This hook is run after a group of changesets has been propagated out +of this repository, for example by a \hgcmd{push} or \hgcmd{bundle} +command. + +One possible use for this hook is to notify administrators that +changes have been pulled. + +Parameters to this hook: +\begin{itemize} +\item[\texttt{node}] A changeset ID. The changeset ID of the first + changeset of the group that was sent. +\item[\texttt{source}] A string. The source of the of the operation + (see section~\ref{sec:hook:sources}). If a remote client pulled + changes from this repository, \texttt{source} will be + \texttt{serve}. If the client that obtained changes from this + repository was local, \texttt{source} will be \texttt{bundle}, + \texttt{pull}, or \texttt{push}, depending on the operation the + client performed. +\item[\texttt{url}] A URL. The location of the remote repository, if + known. See section~\ref{sec:hook:url} for more information. +\end{itemize} + +See also: \hook{preoutgoing} (section~\ref{sec:hook:preoutgoing}) + +\subsection{\hook{prechangegroup}---before starting to add remote changesets} +\label{sec:hook:prechangegroup} + +This controlling hook is run before Mercurial begins to add a group of +changesets from another repository. + +This hook does not have any information about the changesets to be +added, because it is run before transmission of those changesets is +allowed to begin. If this hook fails, the changesets will not be +transmitted. + +One use for this hook is to prevent external changes from being added +to a repository. For example, you could use this to ``freeze'' a +server-hosted branch temporarily or permanently so that users cannot +push to it, while still allowing a local administrator to modify the +repository. + +Parameters to this hook: +\begin{itemize} +\item[\texttt{source}] A string. The source of these changes. See + section~\ref{sec:hook:sources} for details. +\item[\texttt{url}] A URL. The location of the remote repository, if + known. See section~\ref{sec:hook:url} for more information. +\end{itemize} + +See also: \hook{changegroup} (section~\ref{sec:hook:changegroup}), +\hook{incoming} (section~\ref{sec:hook:incoming}), , +\hook{pretxnchangegroup} (section~\ref{sec:hook:pretxnchangegroup}) + +\subsection{\hook{precommit}---before starting to commit a changeset} +\label{sec:hook:precommit} + +This hook is run before Mercurial begins to commit a new changeset. +It is run before Mercurial has any of the metadata for the commit, +such as the files to be committed, the commit message, or the commit +date. + +One use for this hook is to disable the ability to commit new +changesets, while still allowing incoming changesets. Another is to +run a build or test, and only allow the commit to begin if the build +or test succeeds. + +Parameters to this hook: +\begin{itemize} +\item[\texttt{parent1}] A changeset ID. The changeset ID of the first + parent of the working directory. +\item[\texttt{parent2}] A changeset ID. The changeset ID of the second + parent of the working directory. +\end{itemize} +If the commit proceeds, the parents of the working directory will +become the parents of the new changeset. + +See also: \hook{commit} (section~\ref{sec:hook:commit}), +\hook{pretxncommit} (section~\ref{sec:hook:pretxncommit}) + +\subsection{\hook{preoutgoing}---before starting to propagate changesets} +\label{sec:hook:preoutgoing} + +This hook is invoked before Mercurial knows the identities of the +changesets to be transmitted. + +One use for this hook is to prevent changes from being transmitted to +another repository. + +Parameters to this hook: +\begin{itemize} +\item[\texttt{source}] A string. The source of the operation that is + attempting to obtain changes from this repository (see + section~\ref{sec:hook:sources}). See the documentation for the + \texttt{source} parameter to the \hook{outgoing} hook, in + section~\ref{sec:hook:outgoing}, for possible values of this + parameter. +\item[\texttt{url}] A URL. The location of the remote repository, if + known. See section~\ref{sec:hook:url} for more information. +\end{itemize} + +See also: \hook{outgoing} (section~\ref{sec:hook:outgoing}) + +\subsection{\hook{pretag}---before tagging a changeset} +\label{sec:hook:pretag} + +This controlling hook is run before a tag is created. If the hook +succeeds, creation of the tag proceeds. If the hook fails, the tag is +not created. + +Parameters to this hook: +\begin{itemize} +\item[\texttt{local}] A boolean. Whether the tag is local to this + repository instance (i.e.~stored in \sfilename{.hg/localtags}) or + managed by Mercurial (stored in \sfilename{.hgtags}). +\item[\texttt{node}] A changeset ID. The ID of the changeset to be tagged. +\item[\texttt{tag}] A string. The name of the tag to be created. +\end{itemize} + +If the tag to be created is revision-controlled, the \hook{precommit} +and \hook{pretxncommit} hooks (sections~\ref{sec:hook:commit} +and~\ref{sec:hook:pretxncommit}) will also be run. + +See also: \hook{tag} (section~\ref{sec:hook:tag}) + +\subsection{\hook{pretxnchangegroup}---before completing addition of + remote changesets} +\label{sec:hook:pretxnchangegroup} + +This controlling hook is run before a transaction---that manages the +addition of a group of new changesets from outside the +repository---completes. If the hook succeeds, the transaction +completes, and all of the changesets become permanent within this +repository. If the hook fails, the transaction is rolled back, and +the data for the changesets is erased. + +This hook can access the metadata associated with the almost-added +changesets, but it should not do anything permanent with this data. +It must also not modify the working directory. + +While this hook is running, if other Mercurial processes access this +repository, they will be able to see the almost-added changesets as if +they are permanent. This may lead to race conditions if you do not +take steps to avoid them. + +This hook can be used to automatically vet a group of changesets. If +the hook fails, all of the changesets are ``rejected'' when the +transaction rolls back. + +Parameters to this hook: +\begin{itemize} +\item[\texttt{node}] A changeset ID. The changeset ID of the first + changeset in the group that was added. All changesets between this + and \index{tags!\texttt{tip}}\texttt{tip}, inclusive, were added by + a single \hgcmd{pull}, \hgcmd{push} or \hgcmd{unbundle}. +\item[\texttt{source}] A string. The source of these changes. See + section~\ref{sec:hook:sources} for details. +\item[\texttt{url}] A URL. The location of the remote repository, if + known. See section~\ref{sec:hook:url} for more information. +\end{itemize} + +See also: \hook{changegroup} (section~\ref{sec:hook:changegroup}), +\hook{incoming} (section~\ref{sec:hook:incoming}), +\hook{prechangegroup} (section~\ref{sec:hook:prechangegroup}) + +\subsection{\hook{pretxncommit}---before completing commit of new changeset} +\label{sec:hook:pretxncommit} + +This controlling hook is run before a transaction---that manages a new +commit---completes. If the hook succeeds, the transaction completes +and the changeset becomes permanent within this repository. If the +hook fails, the transaction is rolled back, and the commit data is +erased. + +This hook can access the metadata associated with the almost-new +changeset, but it should not do anything permanent with this data. It +must also not modify the working directory. + +While this hook is running, if other Mercurial processes access this +repository, they will be able to see the almost-new changeset as if it +is permanent. This may lead to race conditions if you do not take +steps to avoid them. + +Parameters to this hook: +\begin{itemize} +\item[\texttt{node}] A changeset ID. The changeset ID of the newly + committed changeset. +\item[\texttt{parent1}] A changeset ID. The changeset ID of the first + parent of the newly committed changeset. +\item[\texttt{parent2}] A changeset ID. The changeset ID of the second + parent of the newly committed changeset. +\end{itemize} + +See also: \hook{precommit} (section~\ref{sec:hook:precommit}) + +\subsection{\hook{preupdate}---before updating or merging working directory} +\label{sec:hook:preupdate} + +This controlling hook is run before an update or merge of the working +directory begins. It is run only if Mercurial's normal pre-update +checks determine that the update or merge can proceed. If the hook +succeeds, the update or merge may proceed; if it fails, the update or +merge does not start. + +Parameters to this hook: +\begin{itemize} +\item[\texttt{parent1}] A changeset ID. The ID of the parent that the + working directory is to be updated to. If the working directory is + being merged, it will not change this parent. +\item[\texttt{parent2}] A changeset ID. Only set if the working + directory is being merged. The ID of the revision that the working + directory is being merged with. +\end{itemize} + +See also: \hook{update} (section~\ref{sec:hook:update}) + +\subsection{\hook{tag}---after tagging a changeset} +\label{sec:hook:tag} + +This hook is run after a tag has been created. + +Parameters to this hook: +\begin{itemize} +\item[\texttt{local}] A boolean. Whether the new tag is local to this + repository instance (i.e.~stored in \sfilename{.hg/localtags}) or + managed by Mercurial (stored in \sfilename{.hgtags}). +\item[\texttt{node}] A changeset ID. The ID of the changeset that was + tagged. +\item[\texttt{tag}] A string. The name of the tag that was created. +\end{itemize} + +If the created tag is revision-controlled, the \hook{commit} hook +(section~\ref{sec:hook:commit}) is run before this hook. + +See also: \hook{pretag} (section~\ref{sec:hook:pretag}) + +\subsection{\hook{update}---after updating or merging working directory} +\label{sec:hook:update} + +This hook is run after an update or merge of the working directory +completes. Since a merge can fail (if the external \command{hgmerge} +command fails to resolve conflicts in a file), this hook communicates +whether the update or merge completed cleanly. + +\begin{itemize} +\item[\texttt{error}] A boolean. Indicates whether the update or + merge completed successfully. +\item[\texttt{parent1}] A changeset ID. The ID of the parent that the + working directory was updated to. If the working directory was + merged, it will not have changed this parent. +\item[\texttt{parent2}] A changeset ID. Only set if the working + directory was merged. The ID of the revision that the working + directory was merged with. +\end{itemize} + +See also: \hook{preupdate} (section~\ref{sec:hook:preupdate}) + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/ch11-template.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,475 @@ +\chapter{Customising the output of Mercurial} +\label{chap:template} + +Mercurial provides a powerful mechanism to let you control how it +displays information. The mechanism is based on templates. You can +use templates to generate specific output for a single command, or to +customise the entire appearance of the built-in web interface. + +\section{Using precanned output styles} +\label{sec:style} + +Packaged with Mercurial are some output styles that you can use +immediately. A style is simply a precanned template that someone +wrote and installed somewhere that Mercurial can find. + +Before we take a look at Mercurial's bundled styles, let's review its +normal output. + +\interaction{template.simple.normal} + +This is somewhat informative, but it takes up a lot of space---five +lines of output per changeset. The \texttt{compact} style reduces +this to three lines, presented in a sparse manner. + +\interaction{template.simple.compact} + +The \texttt{changelog} style hints at the expressive power of +Mercurial's templating engine. This style attempts to follow the GNU +Project's changelog guidelines\cite{web:changelog}. + +\interaction{template.simple.changelog} + +You will not be shocked to learn that Mercurial's default output style +is named \texttt{default}. + +\subsection{Setting a default style} + +You can modify the output style that Mercurial will use for every +command by editing your \hgrc\ file, naming the style you would +prefer to use. + +\begin{codesample2} + [ui] + style = compact +\end{codesample2} + +If you write a style of your own, you can use it by either providing +the path to your style file, or copying your style file into a +location where Mercurial can find it (typically the \texttt{templates} +subdirectory of your Mercurial install directory). + +\section{Commands that support styles and templates} + +All of Mercurial's ``\texttt{log}-like'' commands let you use styles +and templates: \hgcmd{incoming}, \hgcmd{log}, \hgcmd{outgoing}, and +\hgcmd{tip}. + +As I write this manual, these are so far the only commands that +support styles and templates. Since these are the most important +commands that need customisable output, there has been little pressure +from the Mercurial user community to add style and template support to +other commands. + +\section{The basics of templating} + +At its simplest, a Mercurial template is a piece of text. Some of the +text never changes, while other parts are \emph{expanded}, or replaced +with new text, when necessary. + +Before we continue, let's look again at a simple example of +Mercurial's normal output. + +\interaction{template.simple.normal} + +Now, let's run the same command, but using a template to change its +output. + +\interaction{template.simple.simplest} + +The example above illustrates the simplest possible template; it's +just a piece of static text, printed once for each changeset. The +\hgopt{log}{--template} option to the \hgcmd{log} command tells +Mercurial to use the given text as the template when printing each +changeset. + +Notice that the template string above ends with the text +``\Verb+\n+''. This is an \emph{escape sequence}, telling Mercurial +to print a newline at the end of each template item. If you omit this +newline, Mercurial will run each piece of output together. See +section~\ref{sec:template:escape} for more details of escape sequences. + +A template that prints a fixed string of text all the time isn't very +useful; let's try something a bit more complex. + +\interaction{template.simple.simplesub} + +As you can see, the string ``\Verb+{desc}+'' in the template has been +replaced in the output with the description of each changeset. Every +time Mercurial finds text enclosed in curly braces (``\texttt{\{}'' +and ``\texttt{\}}''), it will try to replace the braces and text with +the expansion of whatever is inside. To print a literal curly brace, +you must escape it, as described in section~\ref{sec:template:escape}. + +\section{Common template keywords} +\label{sec:template:keyword} + +You can start writing simple templates immediately using the keywords +below. + +\begin{itemize} +\item[\tplkword{author}] String. The unmodified author of the changeset. +\item[\tplkword{branches}] String. The name of the branch on which + the changeset was committed. Will be empty if the branch name was + \texttt{default}. +\item[\tplkword{date}] Date information. The date when the changeset + was committed. This is \emph{not} human-readable; you must pass it + through a filter that will render it appropriately. See + section~\ref{sec:template:filter} for more information on filters. + The date is expressed as a pair of numbers. The first number is a + Unix UTC timestamp (seconds since January 1, 1970); the second is + the offset of the committer's timezone from UTC, in seconds. +\item[\tplkword{desc}] String. The text of the changeset description. +\item[\tplkword{files}] List of strings. All files modified, added, or + removed by this changeset. +\item[\tplkword{file\_adds}] List of strings. Files added by this + changeset. +\item[\tplkword{file\_dels}] List of strings. Files removed by this + changeset. +\item[\tplkword{node}] String. The changeset identification hash, as a + 40-character hexadecimal string. +\item[\tplkword{parents}] List of strings. The parents of the + changeset. +\item[\tplkword{rev}] Integer. The repository-local changeset revision + number. +\item[\tplkword{tags}] List of strings. Any tags associated with the + changeset. +\end{itemize} + +A few simple experiments will show us what to expect when we use these +keywords; you can see the results in +figure~\ref{fig:template:keywords}. + +\begin{figure} + \interaction{template.simple.keywords} + \caption{Template keywords in use} + \label{fig:template:keywords} +\end{figure} + +As we noted above, the date keyword does not produce human-readable +output, so we must treat it specially. This involves using a +\emph{filter}, about which more in section~\ref{sec:template:filter}. + +\interaction{template.simple.datekeyword} + +\section{Escape sequences} +\label{sec:template:escape} + +Mercurial's templating engine recognises the most commonly used escape +sequences in strings. When it sees a backslash (``\Verb+\+'') +character, it looks at the following character and substitutes the two +characters with a single replacement, as described below. + +\begin{itemize} +\item[\Verb+\textbackslash\textbackslash+] Backslash, ``\Verb+\+'', + ASCII~134. +\item[\Verb+\textbackslash n+] Newline, ASCII~12. +\item[\Verb+\textbackslash r+] Carriage return, ASCII~15. +\item[\Verb+\textbackslash t+] Tab, ASCII~11. +\item[\Verb+\textbackslash v+] Vertical tab, ASCII~13. +\item[\Verb+\textbackslash \{+] Open curly brace, ``\Verb+{+'', ASCII~173. +\item[\Verb+\textbackslash \}+] Close curly brace, ``\Verb+}+'', ASCII~175. +\end{itemize} + +As indicated above, if you want the expansion of a template to contain +a literal ``\Verb+\+'', ``\Verb+{+'', or ``\Verb+{+'' character, you +must escape it. + +\section{Filtering keywords to change their results} +\label{sec:template:filter} + +Some of the results of template expansion are not immediately easy to +use. Mercurial lets you specify an optional chain of \emph{filters} +to modify the result of expanding a keyword. You have already seen a +common filter, \tplkwfilt{date}{isodate}, in action above, to make a +date readable. + +Below is a list of the most commonly used filters that Mercurial +supports. While some filters can be applied to any text, others can +only be used in specific circumstances. The name of each filter is +followed first by an indication of where it can be used, then a +description of its effect. + +\begin{itemize} +\item[\tplfilter{addbreaks}] Any text. Add an XHTML ``\Verb+<br/>+'' + tag before the end of every line except the last. For example, + ``\Verb+foo\nbar+'' becomes ``\Verb+foo<br/>\nbar+''. +\item[\tplkwfilt{date}{age}] \tplkword{date} keyword. Render the + age of the date, relative to the current time. Yields a string like + ``\Verb+10 minutes+''. +\item[\tplfilter{basename}] Any text, but most useful for the + \tplkword{files} keyword and its relatives. Treat the text as a + path, and return the basename. For example, ``\Verb+foo/bar/baz+'' + becomes ``\Verb+baz+''. +\item[\tplkwfilt{date}{date}] \tplkword{date} keyword. Render a date + in a similar format to the Unix \tplkword{date} command, but with + timezone included. Yields a string like + ``\Verb+Mon Sep 04 15:13:13 2006 -0700+''. +\item[\tplkwfilt{author}{domain}] Any text, but most useful for the + \tplkword{author} keyword. Finds the first string that looks like + an email address, and extract just the domain component. For + example, ``\Verb+Bryan O'Sullivan <bos@serpentine.com>+'' becomes + ``\Verb+serpentine.com+''. +\item[\tplkwfilt{author}{email}] Any text, but most useful for the + \tplkword{author} keyword. Extract the first string that looks like + an email address. For example, + ``\Verb+Bryan O'Sullivan <bos@serpentine.com>+'' becomes + ``\Verb+bos@serpentine.com+''. +\item[\tplfilter{escape}] Any text. Replace the special XML/XHTML + characters ``\Verb+&+'', ``\Verb+<+'' and ``\Verb+>+'' with + XML entities. +\item[\tplfilter{fill68}] Any text. Wrap the text to fit in 68 + columns. This is useful before you pass text through the + \tplfilter{tabindent} filter, and still want it to fit in an + 80-column fixed-font window. +\item[\tplfilter{fill76}] Any text. Wrap the text to fit in 76 + columns. +\item[\tplfilter{firstline}] Any text. Yield the first line of text, + without any trailing newlines. +\item[\tplkwfilt{date}{hgdate}] \tplkword{date} keyword. Render the + date as a pair of readable numbers. Yields a string like + ``\Verb+1157407993 25200+''. +\item[\tplkwfilt{date}{isodate}] \tplkword{date} keyword. Render the + date as a text string in ISO~8601 format. Yields a string like + ``\Verb+2006-09-04 15:13:13 -0700+''. +\item[\tplfilter{obfuscate}] Any text, but most useful for the + \tplkword{author} keyword. Yield the input text rendered as a + sequence of XML entities. This helps to defeat some particularly + stupid screen-scraping email harvesting spambots. +\item[\tplkwfilt{author}{person}] Any text, but most useful for the + \tplkword{author} keyword. Yield the text before an email address. + For example, ``\Verb+Bryan O'Sullivan <bos@serpentine.com>+'' + becomes ``\Verb+Bryan O'Sullivan+''. +\item[\tplkwfilt{date}{rfc822date}] \tplkword{date} keyword. Render a + date using the same format used in email headers. Yields a string + like ``\Verb+Mon, 04 Sep 2006 15:13:13 -0700+''. +\item[\tplkwfilt{node}{short}] Changeset hash. Yield the short form + of a changeset hash, i.e.~a 12-character hexadecimal string. +\item[\tplkwfilt{date}{shortdate}] \tplkword{date} keyword. Render + the year, month, and day of the date. Yields a string like + ``\Verb+2006-09-04+''. +\item[\tplfilter{strip}] Any text. Strip all leading and trailing + whitespace from the string. +\item[\tplfilter{tabindent}] Any text. Yield the text, with every line + except the first starting with a tab character. +\item[\tplfilter{urlescape}] Any text. Escape all characters that are + considered ``special'' by URL parsers. For example, \Verb+foo bar+ + becomes \Verb+foo%20bar+. +\item[\tplkwfilt{author}{user}] Any text, but most useful for the + \tplkword{author} keyword. Return the ``user'' portion of an email + address. For example, + ``\Verb+Bryan O'Sullivan <bos@serpentine.com>+'' becomes + ``\Verb+bos+''. +\end{itemize} + +\begin{figure} + \interaction{template.simple.manyfilters} + \caption{Template filters in action} + \label{fig:template:filters} +\end{figure} + +\begin{note} + If you try to apply a filter to a piece of data that it cannot + process, Mercurial will fail and print a Python exception. For + example, trying to run the output of the \tplkword{desc} keyword + into the \tplkwfilt{date}{isodate} filter is not a good idea. +\end{note} + +\subsection{Combining filters} + +It is easy to combine filters to yield output in the form you would +like. The following chain of filters tidies up a description, then +makes sure that it fits cleanly into 68 columns, then indents it by a +further 8~characters (at least on Unix-like systems, where a tab is +conventionally 8~characters wide). + +\interaction{template.simple.combine} + +Note the use of ``\Verb+\t+'' (a tab character) in the template to +force the first line to be indented; this is necessary since +\tplkword{tabindent} indents all lines \emph{except} the first. + +Keep in mind that the order of filters in a chain is significant. The +first filter is applied to the result of the keyword; the second to +the result of the first filter; and so on. For example, using +\Verb+fill68|tabindent+ gives very different results from +\Verb+tabindent|fill68+. + + +\section{From templates to styles} + +A command line template provides a quick and simple way to format some +output. Templates can become verbose, though, and it's useful to be +able to give a template a name. A style file is a template with a +name, stored in a file. + +More than that, using a style file unlocks the power of Mercurial's +templating engine in ways that are not possible using the command line +\hgopt{log}{--template} option. + +\subsection{The simplest of style files} + +Our simple style file contains just one line: + +\interaction{template.simple.rev} + +This tells Mercurial, ``if you're printing a changeset, use the text +on the right as the template''. + +\subsection{Style file syntax} + +The syntax rules for a style file are simple. + +\begin{itemize} +\item The file is processed one line at a time. + +\item Leading and trailing white space are ignored. + +\item Empty lines are skipped. + +\item If a line starts with either of the characters ``\texttt{\#}'' or + ``\texttt{;}'', the entire line is treated as a comment, and skipped + as if empty. + +\item A line starts with a keyword. This must start with an + alphabetic character or underscore, and can subsequently contain any + alphanumeric character or underscore. (In regexp notation, a + keyword must match \Verb+[A-Za-z_][A-Za-z0-9_]*+.) + +\item The next element must be an ``\texttt{=}'' character, which can + be preceded or followed by an arbitrary amount of white space. + +\item If the rest of the line starts and ends with matching quote + characters (either single or double quote), it is treated as a + template body. + +\item If the rest of the line \emph{does not} start with a quote + character, it is treated as the name of a file; the contents of this + file will be read and used as a template body. +\end{itemize} + +\section{Style files by example} + +To illustrate how to write a style file, we will construct a few by +example. Rather than provide a complete style file and walk through +it, we'll mirror the usual process of developing a style file by +starting with something very simple, and walking through a series of +successively more complete examples. + +\subsection{Identifying mistakes in style files} + +If Mercurial encounters a problem in a style file you are working on, +it prints a terse error message that, once you figure out what it +means, is actually quite useful. + +\interaction{template.svnstyle.syntax.input} + +Notice that \filename{broken.style} attempts to define a +\texttt{changeset} keyword, but forgets to give any content for it. +When instructed to use this style file, Mercurial promptly complains. + +\interaction{template.svnstyle.syntax.error} + +This error message looks intimidating, but it is not too hard to +follow. + +\begin{itemize} +\item The first component is simply Mercurial's way of saying ``I am + giving up''. + \begin{codesample4} + \textbf{abort:} broken.style:1: parse error + \end{codesample4} + +\item Next comes the name of the style file that contains the error. + \begin{codesample4} + abort: \textbf{broken.style}:1: parse error + \end{codesample4} + +\item Following the file name is the line number where the error was + encountered. + \begin{codesample4} + abort: broken.style:\textbf{1}: parse error + \end{codesample4} + +\item Finally, a description of what went wrong. + \begin{codesample4} + abort: broken.style:1: \textbf{parse error} + \end{codesample4} + The description of the problem is not always clear (as in this + case), but even when it is cryptic, it is almost always trivial to + visually inspect the offending line in the style file and see what + is wrong. +\end{itemize} + +\subsection{Uniquely identifying a repository} + +If you would like to be able to identify a Mercurial repository +``fairly uniquely'' using a short string as an identifier, you can +use the first revision in the repository. +\interaction{template.svnstyle.id} +This is not guaranteed to be unique, but it is nevertheless useful in +many cases. +\begin{itemize} +\item It will not work in a completely empty repository, because such + a repository does not have a revision~zero. +\item Neither will it work in the (extremely rare) case where a + repository is a merge of two or more formerly independent + repositories, and you still have those repositories around. +\end{itemize} +Here are some uses to which you could put this identifier: +\begin{itemize} +\item As a key into a table for a database that manages repositories + on a server. +\item As half of a \{\emph{repository~ID}, \emph{revision~ID}\} tuple. + Save this information away when you run an automated build or other + activity, so that you can ``replay'' the build later if necessary. +\end{itemize} + +\subsection{Mimicking Subversion's output} + +Let's try to emulate the default output format used by another +revision control tool, Subversion. +\interaction{template.svnstyle.short} + +Since Subversion's output style is fairly simple, it is easy to +copy-and-paste a hunk of its output into a file, and replace the text +produced above by Subversion with the template values we'd like to see +expanded. +\interaction{template.svnstyle.template} + +There are a few small ways in which this template deviates from the +output produced by Subversion. +\begin{itemize} +\item Subversion prints a ``readable'' date (the ``\texttt{Wed, 27 Sep + 2006}'' in the example output above) in parentheses. Mercurial's + templating engine does not provide a way to display a date in this + format without also printing the time and time zone. +\item We emulate Subversion's printing of ``separator'' lines full of + ``\texttt{-}'' characters by ending the template with such a line. + We use the templating engine's \tplkword{header} keyword to print a + separator line as the first line of output (see below), thus + achieving similar output to Subversion. +\item Subversion's output includes a count in the header of the number + of lines in the commit message. We cannot replicate this in + Mercurial; the templating engine does not currently provide a filter + that counts the number of lines the template generates. +\end{itemize} +It took me no more than a minute or two of work to replace literal +text from an example of Subversion's output with some keywords and +filters to give the template above. The style file simply refers to +the template. +\interaction{template.svnstyle.style} + +We could have included the text of the template file directly in the +style file by enclosing it in quotes and replacing the newlines with +``\verb!\n!'' sequences, but it would have made the style file too +difficult to read. Readability is a good guide when you're trying to +decide whether some text belongs in a style file, or in a template +file that the style file points to. If the style file will look too +big or cluttered if you insert a literal piece of text, drop it into a +template instead. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/ch12-mq.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,1043 @@ +\chapter{Managing change with Mercurial Queues} +\label{chap:mq} + +\section{The patch management problem} +\label{sec:mq:patch-mgmt} + +Here is a common scenario: you need to install a software package from +source, but you find a bug that you must fix in the source before you +can start using the package. You make your changes, forget about the +package for a while, and a few months later you need to upgrade to a +newer version of the package. If the newer version of the package +still has the bug, you must extract your fix from the older source +tree and apply it against the newer version. This is a tedious task, +and it's easy to make mistakes. + +This is a simple case of the ``patch management'' problem. You have +an ``upstream'' source tree that you can't change; you need to make +some local changes on top of the upstream tree; and you'd like to be +able to keep those changes separate, so that you can apply them to +newer versions of the upstream source. + +The patch management problem arises in many situations. Probably the +most visible is that a user of an open source software project will +contribute a bug fix or new feature to the project's maintainers in the +form of a patch. + +Distributors of operating systems that include open source software +often need to make changes to the packages they distribute so that +they will build properly in their environments. + +When you have few changes to maintain, it is easy to manage a single +patch using the standard \command{diff} and \command{patch} programs +(see section~\ref{sec:mq:patch} for a discussion of these tools). +Once the number of changes grows, it starts to make sense to maintain +patches as discrete ``chunks of work,'' so that for example a single +patch will contain only one bug fix (the patch might modify several +files, but it's doing ``only one thing''), and you may have a number +of such patches for different bugs you need fixed and local changes +you require. In this situation, if you submit a bug fix patch to the +upstream maintainers of a package and they include your fix in a +subsequent release, you can simply drop that single patch when you're +updating to the newer release. + +Maintaining a single patch against an upstream tree is a little +tedious and error-prone, but not difficult. However, the complexity +of the problem grows rapidly as the number of patches you have to +maintain increases. With more than a tiny number of patches in hand, +understanding which ones you have applied and maintaining them moves +from messy to overwhelming. + +Fortunately, Mercurial includes a powerful extension, Mercurial Queues +(or simply ``MQ''), that massively simplifies the patch management +problem. + +\section{The prehistory of Mercurial Queues} +\label{sec:mq:history} + +During the late 1990s, several Linux kernel developers started to +maintain ``patch series'' that modified the behaviour of the Linux +kernel. Some of these series were focused on stability, some on +feature coverage, and others were more speculative. + +The sizes of these patch series grew rapidly. In 2002, Andrew Morton +published some shell scripts he had been using to automate the task of +managing his patch queues. Andrew was successfully using these +scripts to manage hundreds (sometimes thousands) of patches on top of +the Linux kernel. + +\subsection{A patchwork quilt} +\label{sec:mq:quilt} + +In early 2003, Andreas Gruenbacher and Martin Quinson borrowed the +approach of Andrew's scripts and published a tool called ``patchwork +quilt''~\cite{web:quilt}, or simply ``quilt'' +(see~\cite{gruenbacher:2005} for a paper describing it). Because +quilt substantially automated patch management, it rapidly gained a +large following among open source software developers. + +Quilt manages a \emph{stack of patches} on top of a directory tree. +To begin, you tell quilt to manage a directory tree, and tell it which +files you want to manage; it stores away the names and contents of +those files. To fix a bug, you create a new patch (using a single +command), edit the files you need to fix, then ``refresh'' the patch. + +The refresh step causes quilt to scan the directory tree; it updates +the patch with all of the changes you have made. You can create +another patch on top of the first, which will track the changes +required to modify the tree from ``tree with one patch applied'' to +``tree with two patches applied''. + +You can \emph{change} which patches are applied to the tree. If you +``pop'' a patch, the changes made by that patch will vanish from the +directory tree. Quilt remembers which patches you have popped, +though, so you can ``push'' a popped patch again, and the directory +tree will be restored to contain the modifications in the patch. Most +importantly, you can run the ``refresh'' command at any time, and the +topmost applied patch will be updated. This means that you can, at +any time, change both which patches are applied and what +modifications those patches make. + +Quilt knows nothing about revision control tools, so it works equally +well on top of an unpacked tarball or a Subversion working copy. + +\subsection{From patchwork quilt to Mercurial Queues} +\label{sec:mq:quilt-mq} + +In mid-2005, Chris Mason took the features of quilt and wrote an +extension that he called Mercurial Queues, which added quilt-like +behaviour to Mercurial. + +The key difference between quilt and MQ is that quilt knows nothing +about revision control systems, while MQ is \emph{integrated} into +Mercurial. Each patch that you push is represented as a Mercurial +changeset. Pop a patch, and the changeset goes away. + +Because quilt does not care about revision control tools, it is still +a tremendously useful piece of software to know about for situations +where you cannot use Mercurial and MQ. + +\section{The huge advantage of MQ} + +I cannot overstate the value that MQ offers through the unification of +patches and revision control. + +A major reason that patches have persisted in the free software and +open source world---in spite of the availability of increasingly +capable revision control tools over the years---is the \emph{agility} +they offer. + +Traditional revision control tools make a permanent, irreversible +record of everything that you do. While this has great value, it's +also somewhat stifling. If you want to perform a wild-eyed +experiment, you have to be careful in how you go about it, or you risk +leaving unneeded---or worse, misleading or destabilising---traces of +your missteps and errors in the permanent revision record. + +By contrast, MQ's marriage of distributed revision control with +patches makes it much easier to isolate your work. Your patches live +on top of normal revision history, and you can make them disappear or +reappear at will. If you don't like a patch, you can drop it. If a +patch isn't quite as you want it to be, simply fix it---as many times +as you need to, until you have refined it into the form you desire. + +As an example, the integration of patches with revision control makes +understanding patches and debugging their effects---and their +interplay with the code they're based on---\emph{enormously} easier. +Since every applied patch has an associated changeset, you can use +\hgcmdargs{log}{\emph{filename}} to see which changesets and patches +affected a file. You can use the \hgext{bisect} command to +binary-search through all changesets and applied patches to see where +a bug got introduced or fixed. You can use the \hgcmd{annotate} +command to see which changeset or patch modified a particular line of +a source file. And so on. + +\section{Understanding patches} +\label{sec:mq:patch} + +Because MQ doesn't hide its patch-oriented nature, it is helpful to +understand what patches are, and a little about the tools that work +with them. + +The traditional Unix \command{diff} command compares two files, and +prints a list of differences between them. The \command{patch} command +understands these differences as \emph{modifications} to make to a +file. Take a look at figure~\ref{ex:mq:diff} for a simple example of +these commands in action. + +\begin{figure}[ht] + \interaction{mq.dodiff.diff} + \caption{Simple uses of the \command{diff} and \command{patch} commands} + \label{ex:mq:diff} +\end{figure} + +The type of file that \command{diff} generates (and \command{patch} +takes as input) is called a ``patch'' or a ``diff''; there is no +difference between a patch and a diff. (We'll use the term ``patch'', +since it's more commonly used.) + +A patch file can start with arbitrary text; the \command{patch} +command ignores this text, but MQ uses it as the commit message when +creating changesets. To find the beginning of the patch content, +\command{patch} searches for the first line that starts with the +string ``\texttt{diff~-}''. + +MQ works with \emph{unified} diffs (\command{patch} can accept several +other diff formats, but MQ doesn't). A unified diff contains two +kinds of header. The \emph{file header} describes the file being +modified; it contains the name of the file to modify. When +\command{patch} sees a new file header, it looks for a file with that +name to start modifying. + +After the file header comes a series of \emph{hunks}. Each hunk +starts with a header; this identifies the range of line numbers within +the file that the hunk should modify. Following the header, a hunk +starts and ends with a few (usually three) lines of text from the +unmodified file; these are called the \emph{context} for the hunk. If +there's only a small amount of context between successive hunks, +\command{diff} doesn't print a new hunk header; it just runs the hunks +together, with a few lines of context between modifications. + +Each line of context begins with a space character. Within the hunk, +a line that begins with ``\texttt{-}'' means ``remove this line,'' +while a line that begins with ``\texttt{+}'' means ``insert this +line.'' For example, a line that is modified is represented by one +deletion and one insertion. + +We will return to some of the more subtle aspects of patches later (in +section~\ref{sec:mq:adv-patch}), but you should have enough information +now to use MQ. + +\section{Getting started with Mercurial Queues} +\label{sec:mq:start} + +Because MQ is implemented as an extension, you must explicitly enable +before you can use it. (You don't need to download anything; MQ ships +with the standard Mercurial distribution.) To enable MQ, edit your +\tildefile{.hgrc} file, and add the lines in figure~\ref{ex:mq:config}. + +\begin{figure}[ht] + \begin{codesample4} + [extensions] + hgext.mq = + \end{codesample4} + \label{ex:mq:config} + \caption{Contents to add to \tildefile{.hgrc} to enable the MQ extension} +\end{figure} + +Once the extension is enabled, it will make a number of new commands +available. To verify that the extension is working, you can use +\hgcmd{help} to see if the \hgxcmd{mq}{qinit} command is now available; see +the example in figure~\ref{ex:mq:enabled}. + +\begin{figure}[ht] + \interaction{mq.qinit-help.help} + \caption{How to verify that MQ is enabled} + \label{ex:mq:enabled} +\end{figure} + +You can use MQ with \emph{any} Mercurial repository, and its commands +only operate within that repository. To get started, simply prepare +the repository using the \hgxcmd{mq}{qinit} command (see +figure~\ref{ex:mq:qinit}). This command creates an empty directory +called \sdirname{.hg/patches}, where MQ will keep its metadata. As +with many Mercurial commands, the \hgxcmd{mq}{qinit} command prints nothing +if it succeeds. + +\begin{figure}[ht] + \interaction{mq.tutorial.qinit} + \caption{Preparing a repository for use with MQ} + \label{ex:mq:qinit} +\end{figure} + +\begin{figure}[ht] + \interaction{mq.tutorial.qnew} + \caption{Creating a new patch} + \label{ex:mq:qnew} +\end{figure} + +\subsection{Creating a new patch} + +To begin work on a new patch, use the \hgxcmd{mq}{qnew} command. This +command takes one argument, the name of the patch to create. MQ will +use this as the name of an actual file in the \sdirname{.hg/patches} +directory, as you can see in figure~\ref{ex:mq:qnew}. + +Also newly present in the \sdirname{.hg/patches} directory are two +other files, \sfilename{series} and \sfilename{status}. The +\sfilename{series} file lists all of the patches that MQ knows about +for this repository, with one patch per line. Mercurial uses the +\sfilename{status} file for internal book-keeping; it tracks all of the +patches that MQ has \emph{applied} in this repository. + +\begin{note} + You may sometimes want to edit the \sfilename{series} file by hand; + for example, to change the sequence in which some patches are + applied. However, manually editing the \sfilename{status} file is + almost always a bad idea, as it's easy to corrupt MQ's idea of what + is happening. +\end{note} + +Once you have created your new patch, you can edit files in the +working directory as you usually would. All of the normal Mercurial +commands, such as \hgcmd{diff} and \hgcmd{annotate}, work exactly as +they did before. + +\subsection{Refreshing a patch} + +When you reach a point where you want to save your work, use the +\hgxcmd{mq}{qrefresh} command (figure~\ref{ex:mq:qnew}) to update the patch +you are working on. This command folds the changes you have made in +the working directory into your patch, and updates its corresponding +changeset to contain those changes. + +\begin{figure}[ht] + \interaction{mq.tutorial.qrefresh} + \caption{Refreshing a patch} + \label{ex:mq:qrefresh} +\end{figure} + +You can run \hgxcmd{mq}{qrefresh} as often as you like, so it's a good way +to ``checkpoint'' your work. Refresh your patch at an opportune +time; try an experiment; and if the experiment doesn't work out, +\hgcmd{revert} your modifications back to the last time you refreshed. + +\begin{figure}[ht] + \interaction{mq.tutorial.qrefresh2} + \caption{Refresh a patch many times to accumulate changes} + \label{ex:mq:qrefresh2} +\end{figure} + +\subsection{Stacking and tracking patches} + +Once you have finished working on a patch, or need to work on another, +you can use the \hgxcmd{mq}{qnew} command again to create a new patch. +Mercurial will apply this patch on top of your existing patch. See +figure~\ref{ex:mq:qnew2} for an example. Notice that the patch +contains the changes in our prior patch as part of its context (you +can see this more clearly in the output of \hgcmd{annotate}). + +\begin{figure}[ht] + \interaction{mq.tutorial.qnew2} + \caption{Stacking a second patch on top of the first} + \label{ex:mq:qnew2} +\end{figure} + +So far, with the exception of \hgxcmd{mq}{qnew} and \hgxcmd{mq}{qrefresh}, we've +been careful to only use regular Mercurial commands. However, MQ +provides many commands that are easier to use when you are thinking +about patches, as illustrated in figure~\ref{ex:mq:qseries}: + +\begin{itemize} +\item The \hgxcmd{mq}{qseries} command lists every patch that MQ knows + about in this repository, from oldest to newest (most recently + \emph{created}). +\item The \hgxcmd{mq}{qapplied} command lists every patch that MQ has + \emph{applied} in this repository, again from oldest to newest (most + recently applied). +\end{itemize} + +\begin{figure}[ht] + \interaction{mq.tutorial.qseries} + \caption{Understanding the patch stack with \hgxcmd{mq}{qseries} and + \hgxcmd{mq}{qapplied}} + \label{ex:mq:qseries} +\end{figure} + +\subsection{Manipulating the patch stack} + +The previous discussion implied that there must be a difference +between ``known'' and ``applied'' patches, and there is. MQ can +manage a patch without it being applied in the repository. + +An \emph{applied} patch has a corresponding changeset in the +repository, and the effects of the patch and changeset are visible in +the working directory. You can undo the application of a patch using +the \hgxcmd{mq}{qpop} command. MQ still \emph{knows about}, or manages, a +popped patch, but the patch no longer has a corresponding changeset in +the repository, and the working directory does not contain the changes +made by the patch. Figure~\ref{fig:mq:stack} illustrates the +difference between applied and tracked patches. + +\begin{figure}[ht] + \centering + \grafix{mq-stack} + \caption{Applied and unapplied patches in the MQ patch stack} + \label{fig:mq:stack} +\end{figure} + +You can reapply an unapplied, or popped, patch using the \hgxcmd{mq}{qpush} +command. This creates a new changeset to correspond to the patch, and +the patch's changes once again become present in the working +directory. See figure~\ref{ex:mq:qpop} for examples of \hgxcmd{mq}{qpop} +and \hgxcmd{mq}{qpush} in action. Notice that once we have popped a patch +or two patches, the output of \hgxcmd{mq}{qseries} remains the same, while +that of \hgxcmd{mq}{qapplied} has changed. + +\begin{figure}[ht] + \interaction{mq.tutorial.qpop} + \caption{Modifying the stack of applied patches} + \label{ex:mq:qpop} +\end{figure} + +\subsection{Pushing and popping many patches} + +While \hgxcmd{mq}{qpush} and \hgxcmd{mq}{qpop} each operate on a single patch at +a time by default, you can push and pop many patches in one go. The +\hgxopt{mq}{qpush}{-a} option to \hgxcmd{mq}{qpush} causes it to push all +unapplied patches, while the \hgxopt{mq}{qpop}{-a} option to \hgxcmd{mq}{qpop} +causes it to pop all applied patches. (For some more ways to push and +pop many patches, see section~\ref{sec:mq:perf} below.) + +\begin{figure}[ht] + \interaction{mq.tutorial.qpush-a} + \caption{Pushing all unapplied patches} + \label{ex:mq:qpush-a} +\end{figure} + +\subsection{Safety checks, and overriding them} + +Several MQ commands check the working directory before they do +anything, and fail if they find any modifications. They do this to +ensure that you won't lose any changes that you have made, but not yet +incorporated into a patch. Figure~\ref{ex:mq:add} illustrates this; +the \hgxcmd{mq}{qnew} command will not create a new patch if there are +outstanding changes, caused in this case by the \hgcmd{add} of +\filename{file3}. + +\begin{figure}[ht] + \interaction{mq.tutorial.add} + \caption{Forcibly creating a patch} + \label{ex:mq:add} +\end{figure} + +Commands that check the working directory all take an ``I know what +I'm doing'' option, which is always named \option{-f}. The exact +meaning of \option{-f} depends on the command. For example, +\hgcmdargs{qnew}{\hgxopt{mq}{qnew}{-f}} will incorporate any outstanding +changes into the new patch it creates, but +\hgcmdargs{qpop}{\hgxopt{mq}{qpop}{-f}} will revert modifications to any +files affected by the patch that it is popping. Be sure to read the +documentation for a command's \option{-f} option before you use it! + +\subsection{Working on several patches at once} + +The \hgxcmd{mq}{qrefresh} command always refreshes the \emph{topmost} +applied patch. This means that you can suspend work on one patch (by +refreshing it), pop or push to make a different patch the top, and +work on \emph{that} patch for a while. + +Here's an example that illustrates how you can use this ability. +Let's say you're developing a new feature as two patches. The first +is a change to the core of your software, and the second---layered on +top of the first---changes the user interface to use the code you just +added to the core. If you notice a bug in the core while you're +working on the UI patch, it's easy to fix the core. Simply +\hgxcmd{mq}{qrefresh} the UI patch to save your in-progress changes, and +\hgxcmd{mq}{qpop} down to the core patch. Fix the core bug, +\hgxcmd{mq}{qrefresh} the core patch, and \hgxcmd{mq}{qpush} back to the UI +patch to continue where you left off. + +\section{More about patches} +\label{sec:mq:adv-patch} + +MQ uses the GNU \command{patch} command to apply patches, so it's +helpful to know a few more detailed aspects of how \command{patch} +works, and about patches themselves. + +\subsection{The strip count} + +If you look at the file headers in a patch, you will notice that the +pathnames usually have an extra component on the front that isn't +present in the actual path name. This is a holdover from the way that +people used to generate patches (people still do this, but it's +somewhat rare with modern revision control tools). + +Alice would unpack a tarball, edit her files, then decide that she +wanted to create a patch. So she'd rename her working directory, +unpack the tarball again (hence the need for the rename), and use the +\cmdopt{diff}{-r} and \cmdopt{diff}{-N} options to \command{diff} to +recursively generate a patch between the unmodified directory and the +modified one. The result would be that the name of the unmodified +directory would be at the front of the left-hand path in every file +header, and the name of the modified directory would be at the front +of the right-hand path. + +Since someone receiving a patch from the Alices of the net would be +unlikely to have unmodified and modified directories with exactly the +same names, the \command{patch} command has a \cmdopt{patch}{-p} +option that indicates the number of leading path name components to +strip when trying to apply a patch. This number is called the +\emph{strip count}. + +An option of ``\texttt{-p1}'' means ``use a strip count of one''. If +\command{patch} sees a file name \filename{foo/bar/baz} in a file +header, it will strip \filename{foo} and try to patch a file named +\filename{bar/baz}. (Strictly speaking, the strip count refers to the +number of \emph{path separators} (and the components that go with them +) to strip. A strip count of one will turn \filename{foo/bar} into +\filename{bar}, but \filename{/foo/bar} (notice the extra leading +slash) into \filename{foo/bar}.) + +The ``standard'' strip count for patches is one; almost all patches +contain one leading path name component that needs to be stripped. +Mercurial's \hgcmd{diff} command generates path names in this form, +and the \hgcmd{import} command and MQ expect patches to have a strip +count of one. + +If you receive a patch from someone that you want to add to your patch +queue, and the patch needs a strip count other than one, you cannot +just \hgxcmd{mq}{qimport} the patch, because \hgxcmd{mq}{qimport} does not yet +have a \texttt{-p} option (see~\bug{311}). Your best bet is to +\hgxcmd{mq}{qnew} a patch of your own, then use \cmdargs{patch}{-p\emph{N}} +to apply their patch, followed by \hgcmd{addremove} to pick up any +files added or removed by the patch, followed by \hgxcmd{mq}{qrefresh}. +This complexity may become unnecessary; see~\bug{311} for details. +\subsection{Strategies for applying a patch} + +When \command{patch} applies a hunk, it tries a handful of +successively less accurate strategies to try to make the hunk apply. +This falling-back technique often makes it possible to take a patch +that was generated against an old version of a file, and apply it +against a newer version of that file. + +First, \command{patch} tries an exact match, where the line numbers, +the context, and the text to be modified must apply exactly. If it +cannot make an exact match, it tries to find an exact match for the +context, without honouring the line numbering information. If this +succeeds, it prints a line of output saying that the hunk was applied, +but at some \emph{offset} from the original line number. + +If a context-only match fails, \command{patch} removes the first and +last lines of the context, and tries a \emph{reduced} context-only +match. If the hunk with reduced context succeeds, it prints a message +saying that it applied the hunk with a \emph{fuzz factor} (the number +after the fuzz factor indicates how many lines of context +\command{patch} had to trim before the patch applied). + +When neither of these techniques works, \command{patch} prints a +message saying that the hunk in question was rejected. It saves +rejected hunks (also simply called ``rejects'') to a file with the +same name, and an added \sfilename{.rej} extension. It also saves an +unmodified copy of the file with a \sfilename{.orig} extension; the +copy of the file without any extensions will contain any changes made +by hunks that \emph{did} apply cleanly. If you have a patch that +modifies \filename{foo} with six hunks, and one of them fails to +apply, you will have: an unmodified \filename{foo.orig}, a +\filename{foo.rej} containing one hunk, and \filename{foo}, containing +the changes made by the five successful hunks. + +\subsection{Some quirks of patch representation} + +There are a few useful things to know about how \command{patch} works +with files. +\begin{itemize} +\item This should already be obvious, but \command{patch} cannot + handle binary files. +\item Neither does it care about the executable bit; it creates new + files as readable, but not executable. +\item \command{patch} treats the removal of a file as a diff between + the file to be removed and the empty file. So your idea of ``I + deleted this file'' looks like ``every line of this file was + deleted'' in a patch. +\item It treats the addition of a file as a diff between the empty + file and the file to be added. So in a patch, your idea of ``I + added this file'' looks like ``every line of this file was added''. +\item It treats a renamed file as the removal of the old name, and the + addition of the new name. This means that renamed files have a big + footprint in patches. (Note also that Mercurial does not currently + try to infer when files have been renamed or copied in a patch.) +\item \command{patch} cannot represent empty files, so you cannot use + a patch to represent the notion ``I added this empty file to the + tree''. +\end{itemize} +\subsection{Beware the fuzz} + +While applying a hunk at an offset, or with a fuzz factor, will often +be completely successful, these inexact techniques naturally leave +open the possibility of corrupting the patched file. The most common +cases typically involve applying a patch twice, or at an incorrect +location in the file. If \command{patch} or \hgxcmd{mq}{qpush} ever +mentions an offset or fuzz factor, you should make sure that the +modified files are correct afterwards. + +It's often a good idea to refresh a patch that has applied with an +offset or fuzz factor; refreshing the patch generates new context +information that will make it apply cleanly. I say ``often,'' not +``always,'' because sometimes refreshing a patch will make it fail to +apply against a different revision of the underlying files. In some +cases, such as when you're maintaining a patch that must sit on top of +multiple versions of a source tree, it's acceptable to have a patch +apply with some fuzz, provided you've verified the results of the +patching process in such cases. + +\subsection{Handling rejection} + +If \hgxcmd{mq}{qpush} fails to apply a patch, it will print an error +message and exit. If it has left \sfilename{.rej} files behind, it is +usually best to fix up the rejected hunks before you push more patches +or do any further work. + +If your patch \emph{used to} apply cleanly, and no longer does because +you've changed the underlying code that your patches are based on, +Mercurial Queues can help; see section~\ref{sec:mq:merge} for details. + +Unfortunately, there aren't any great techniques for dealing with +rejected hunks. Most often, you'll need to view the \sfilename{.rej} +file and edit the target file, applying the rejected hunks by hand. + +If you're feeling adventurous, Neil Brown, a Linux kernel hacker, +wrote a tool called \command{wiggle}~\cite{web:wiggle}, which is more +vigorous than \command{patch} in its attempts to make a patch apply. + +Another Linux kernel hacker, Chris Mason (the author of Mercurial +Queues), wrote a similar tool called +\command{mpatch}~\cite{web:mpatch}, which takes a simple approach to +automating the application of hunks rejected by \command{patch}. The +\command{mpatch} command can help with four common reasons that a hunk +may be rejected: + +\begin{itemize} +\item The context in the middle of a hunk has changed. +\item A hunk is missing some context at the beginning or end. +\item A large hunk might apply better---either entirely or in + part---if it was broken up into smaller hunks. +\item A hunk removes lines with slightly different content than those + currently present in the file. +\end{itemize} + +If you use \command{wiggle} or \command{mpatch}, you should be doubly +careful to check your results when you're done. In fact, +\command{mpatch} enforces this method of double-checking the tool's +output, by automatically dropping you into a merge program when it has +done its job, so that you can verify its work and finish off any +remaining merges. + +\section{Getting the best performance out of MQ} +\label{sec:mq:perf} + +MQ is very efficient at handling a large number of patches. I ran +some performance experiments in mid-2006 for a talk that I gave at the +2006 EuroPython conference~\cite{web:europython}. I used as my data +set the Linux 2.6.17-mm1 patch series, which consists of 1,738 +patches. I applied these on top of a Linux kernel repository +containing all 27,472 revisions between Linux 2.6.12-rc2 and Linux +2.6.17. + +On my old, slow laptop, I was able to +\hgcmdargs{qpush}{\hgxopt{mq}{qpush}{-a}} all 1,738 patches in 3.5 minutes, +and \hgcmdargs{qpop}{\hgxopt{mq}{qpop}{-a}} them all in 30 seconds. (On a +newer laptop, the time to push all patches dropped to two minutes.) I +could \hgxcmd{mq}{qrefresh} one of the biggest patches (which made 22,779 +lines of changes to 287 files) in 6.6 seconds. + +Clearly, MQ is well suited to working in large trees, but there are a +few tricks you can use to get the best performance of it. + +First of all, try to ``batch'' operations together. Every time you +run \hgxcmd{mq}{qpush} or \hgxcmd{mq}{qpop}, these commands scan the working +directory once to make sure you haven't made some changes and then +forgotten to run \hgxcmd{mq}{qrefresh}. On a small tree, the time that +this scan takes is unnoticeable. However, on a medium-sized tree +(containing tens of thousands of files), it can take a second or more. + +The \hgxcmd{mq}{qpush} and \hgxcmd{mq}{qpop} commands allow you to push and pop +multiple patches at a time. You can identify the ``destination +patch'' that you want to end up at. When you \hgxcmd{mq}{qpush} with a +destination specified, it will push patches until that patch is at the +top of the applied stack. When you \hgxcmd{mq}{qpop} to a destination, MQ +will pop patches until the destination patch is at the top. + +You can identify a destination patch using either the name of the +patch, or by number. If you use numeric addressing, patches are +counted from zero; this means that the first patch is zero, the second +is one, and so on. + +\section{Updating your patches when the underlying code changes} +\label{sec:mq:merge} + +It's common to have a stack of patches on top of an underlying +repository that you don't modify directly. If you're working on +changes to third-party code, or on a feature that is taking longer to +develop than the rate of change of the code beneath, you will often +need to sync up with the underlying code, and fix up any hunks in your +patches that no longer apply. This is called \emph{rebasing} your +patch series. + +The simplest way to do this is to \hgcmdargs{qpop}{\hgxopt{mq}{qpop}{-a}} +your patches, then \hgcmd{pull} changes into the underlying +repository, and finally \hgcmdargs{qpush}{\hgxopt{mq}{qpop}{-a}} your +patches again. MQ will stop pushing any time it runs across a patch +that fails to apply during conflicts, allowing you to fix your +conflicts, \hgxcmd{mq}{qrefresh} the affected patch, and continue pushing +until you have fixed your entire stack. + +This approach is easy to use and works well if you don't expect +changes to the underlying code to affect how well your patches apply. +If your patch stack touches code that is modified frequently or +invasively in the underlying repository, however, fixing up rejected +hunks by hand quickly becomes tiresome. + +It's possible to partially automate the rebasing process. If your +patches apply cleanly against some revision of the underlying repo, MQ +can use this information to help you to resolve conflicts between your +patches and a different revision. + +The process is a little involved. +\begin{enumerate} +\item To begin, \hgcmdargs{qpush}{-a} all of your patches on top of + the revision where you know that they apply cleanly. +\item Save a backup copy of your patch directory using + \hgcmdargs{qsave}{\hgxopt{mq}{qsave}{-e} \hgxopt{mq}{qsave}{-c}}. This prints + the name of the directory that it has saved the patches in. It will + save the patches to a directory called + \sdirname{.hg/patches.\emph{N}}, where \texttt{\emph{N}} is a small + integer. It also commits a ``save changeset'' on top of your + applied patches; this is for internal book-keeping, and records the + states of the \sfilename{series} and \sfilename{status} files. +\item Use \hgcmd{pull} to bring new changes into the underlying + repository. (Don't run \hgcmdargs{pull}{-u}; see below for why.) +\item Update to the new tip revision, using + \hgcmdargs{update}{\hgopt{update}{-C}} to override the patches you + have pushed. +\item Merge all patches using \hgcmdargs{qpush}{\hgxopt{mq}{qpush}{-m} + \hgxopt{mq}{qpush}{-a}}. The \hgxopt{mq}{qpush}{-m} option to \hgxcmd{mq}{qpush} + tells MQ to perform a three-way merge if the patch fails to apply. +\end{enumerate} + +During the \hgcmdargs{qpush}{\hgxopt{mq}{qpush}{-m}}, each patch in the +\sfilename{series} file is applied normally. If a patch applies with +fuzz or rejects, MQ looks at the queue you \hgxcmd{mq}{qsave}d, and +performs a three-way merge with the corresponding changeset. This +merge uses Mercurial's normal merge machinery, so it may pop up a GUI +merge tool to help you to resolve problems. + +When you finish resolving the effects of a patch, MQ refreshes your +patch based on the result of the merge. + +At the end of this process, your repository will have one extra head +from the old patch queue, and a copy of the old patch queue will be in +\sdirname{.hg/patches.\emph{N}}. You can remove the extra head using +\hgcmdargs{qpop}{\hgxopt{mq}{qpop}{-a} \hgxopt{mq}{qpop}{-n} patches.\emph{N}} +or \hgcmd{strip}. You can delete \sdirname{.hg/patches.\emph{N}} once +you are sure that you no longer need it as a backup. + +\section{Identifying patches} + +MQ commands that work with patches let you refer to a patch either by +using its name or by a number. By name is obvious enough; pass the +name \filename{foo.patch} to \hgxcmd{mq}{qpush}, for example, and it will +push patches until \filename{foo.patch} is applied. + +As a shortcut, you can refer to a patch using both a name and a +numeric offset; \texttt{foo.patch-2} means ``two patches before +\texttt{foo.patch}'', while \texttt{bar.patch+4} means ``four patches +after \texttt{bar.patch}''. + +Referring to a patch by index isn't much different. The first patch +printed in the output of \hgxcmd{mq}{qseries} is patch zero (yes, it's one +of those start-at-zero counting systems); the second is patch one; and +so on. + +MQ also makes it easy to work with patches when you are using normal +Mercurial commands. Every command that accepts a changeset ID will +also accept the name of an applied patch. MQ augments the tags +normally in the repository with an eponymous one for each applied +patch. In addition, the special tags \index{tags!special tag + names!\texttt{qbase}}\texttt{qbase} and \index{tags!special tag + names!\texttt{qtip}}\texttt{qtip} identify the ``bottom-most'' and +topmost applied patches, respectively. + +These additions to Mercurial's normal tagging capabilities make +dealing with patches even more of a breeze. +\begin{itemize} +\item Want to patchbomb a mailing list with your latest series of + changes? + \begin{codesample4} + hg email qbase:qtip + \end{codesample4} + (Don't know what ``patchbombing'' is? See + section~\ref{sec:hgext:patchbomb}.) +\item Need to see all of the patches since \texttt{foo.patch} that + have touched files in a subdirectory of your tree? + \begin{codesample4} + hg log -r foo.patch:qtip \emph{subdir} + \end{codesample4} +\end{itemize} + +Because MQ makes the names of patches available to the rest of +Mercurial through its normal internal tag machinery, you don't need to +type in the entire name of a patch when you want to identify it by +name. + +\begin{figure}[ht] + \interaction{mq.id.output} + \caption{Using MQ's tag features to work with patches} + \label{ex:mq:id} +\end{figure} + +Another nice consequence of representing patch names as tags is that +when you run the \hgcmd{log} command, it will display a patch's name +as a tag, simply as part of its normal output. This makes it easy to +visually distinguish applied patches from underlying ``normal'' +revisions. Figure~\ref{ex:mq:id} shows a few normal Mercurial +commands in use with applied patches. + +\section{Useful things to know about} + +There are a number of aspects of MQ usage that don't fit tidily into +sections of their own, but that are good to know. Here they are, in +one place. + +\begin{itemize} +\item Normally, when you \hgxcmd{mq}{qpop} a patch and \hgxcmd{mq}{qpush} it + again, the changeset that represents the patch after the pop/push + will have a \emph{different identity} than the changeset that + represented the hash beforehand. See + section~\ref{sec:mqref:cmd:qpush} for information as to why this is. +\item It's not a good idea to \hgcmd{merge} changes from another + branch with a patch changeset, at least if you want to maintain the + ``patchiness'' of that changeset and changesets below it on the + patch stack. If you try to do this, it will appear to succeed, but + MQ will become confused. +\end{itemize} + +\section{Managing patches in a repository} +\label{sec:mq:repo} + +Because MQ's \sdirname{.hg/patches} directory resides outside a +Mercurial repository's working directory, the ``underlying'' Mercurial +repository knows nothing about the management or presence of patches. + +This presents the interesting possibility of managing the contents of +the patch directory as a Mercurial repository in its own right. This +can be a useful way to work. For example, you can work on a patch for +a while, \hgxcmd{mq}{qrefresh} it, then \hgcmd{commit} the current state of +the patch. This lets you ``roll back'' to that version of the patch +later on. + +You can then share different versions of the same patch stack among +multiple underlying repositories. I use this when I am developing a +Linux kernel feature. I have a pristine copy of my kernel sources for +each of several CPU architectures, and a cloned repository under each +that contains the patches I am working on. When I want to test a +change on a different architecture, I push my current patches to the +patch repository associated with that kernel tree, pop and push all of +my patches, and build and test that kernel. + +Managing patches in a repository makes it possible for multiple +developers to work on the same patch series without colliding with +each other, all on top of an underlying source base that they may or +may not control. + +\subsection{MQ support for patch repositories} + +MQ helps you to work with the \sdirname{.hg/patches} directory as a +repository; when you prepare a repository for working with patches +using \hgxcmd{mq}{qinit}, you can pass the \hgxopt{mq}{qinit}{-c} option to +create the \sdirname{.hg/patches} directory as a Mercurial repository. + +\begin{note} + If you forget to use the \hgxopt{mq}{qinit}{-c} option, you can simply go + into the \sdirname{.hg/patches} directory at any time and run + \hgcmd{init}. Don't forget to add an entry for the + \sfilename{status} file to the \sfilename{.hgignore} file, though + + (\hgcmdargs{qinit}{\hgxopt{mq}{qinit}{-c}} does this for you + automatically); you \emph{really} don't want to manage the + \sfilename{status} file. +\end{note} + +As a convenience, if MQ notices that the \dirname{.hg/patches} +directory is a repository, it will automatically \hgcmd{add} every +patch that you create and import. + +MQ provides a shortcut command, \hgxcmd{mq}{qcommit}, that runs +\hgcmd{commit} in the \sdirname{.hg/patches} directory. This saves +some bothersome typing. + +Finally, as a convenience to manage the patch directory, you can +define the alias \command{mq} on Unix systems. For example, on Linux +systems using the \command{bash} shell, you can include the following +snippet in your \tildefile{.bashrc}. + +\begin{codesample2} + alias mq=`hg -R \$(hg root)/.hg/patches' +\end{codesample2} + +You can then issue commands of the form \cmdargs{mq}{pull} from +the main repository. + +\subsection{A few things to watch out for} + +MQ's support for working with a repository full of patches is limited +in a few small respects. + +MQ cannot automatically detect changes that you make to the patch +directory. If you \hgcmd{pull}, manually edit, or \hgcmd{update} +changes to patches or the \sfilename{series} file, you will have to +\hgcmdargs{qpop}{\hgxopt{mq}{qpop}{-a}} and then +\hgcmdargs{qpush}{\hgxopt{mq}{qpush}{-a}} in the underlying repository to +see those changes show up there. If you forget to do this, you can +confuse MQ's idea of which patches are applied. + +\section{Third party tools for working with patches} +\label{sec:mq:tools} + +Once you've been working with patches for a while, you'll find +yourself hungry for tools that will help you to understand and +manipulate the patches you're dealing with. + +The \command{diffstat} command~\cite{web:diffstat} generates a +histogram of the modifications made to each file in a patch. It +provides a good way to ``get a sense of'' a patch---which files it +affects, and how much change it introduces to each file and as a +whole. (I find that it's a good idea to use \command{diffstat}'s +\cmdopt{diffstat}{-p} option as a matter of course, as otherwise it +will try to do clever things with prefixes of file names that +inevitably confuse at least me.) + +\begin{figure}[ht] + \interaction{mq.tools.tools} + \caption{The \command{diffstat}, \command{filterdiff}, and \command{lsdiff} commands} + \label{ex:mq:tools} +\end{figure} + +The \package{patchutils} package~\cite{web:patchutils} is invaluable. +It provides a set of small utilities that follow the ``Unix +philosophy;'' each does one useful thing with a patch. The +\package{patchutils} command I use most is \command{filterdiff}, which +extracts subsets from a patch file. For example, given a patch that +modifies hundreds of files across dozens of directories, a single +invocation of \command{filterdiff} can generate a smaller patch that +only touches files whose names match a particular glob pattern. See +section~\ref{mq-collab:tips:interdiff} for another example. + +\section{Good ways to work with patches} + +Whether you are working on a patch series to submit to a free software +or open source project, or a series that you intend to treat as a +sequence of regular changesets when you're done, you can use some +simple techniques to keep your work well organised. + +Give your patches descriptive names. A good name for a patch might be +\filename{rework-device-alloc.patch}, because it will immediately give +you a hint what the purpose of the patch is. Long names shouldn't be +a problem; you won't be typing the names often, but you \emph{will} be +running commands like \hgxcmd{mq}{qapplied} and \hgxcmd{mq}{qtop} over and over. +Good naming becomes especially important when you have a number of +patches to work with, or if you are juggling a number of different +tasks and your patches only get a fraction of your attention. + +Be aware of what patch you're working on. Use the \hgxcmd{mq}{qtop} +command and skim over the text of your patches frequently---for +example, using \hgcmdargs{tip}{\hgopt{tip}{-p}})---to be sure of where +you stand. I have several times worked on and \hgxcmd{mq}{qrefresh}ed a +patch other than the one I intended, and it's often tricky to migrate +changes into the right patch after making them in the wrong one. + +For this reason, it is very much worth investing a little time to +learn how to use some of the third-party tools I described in +section~\ref{sec:mq:tools}, particularly \command{diffstat} and +\command{filterdiff}. The former will give you a quick idea of what +changes your patch is making, while the latter makes it easy to splice +hunks selectively out of one patch and into another. + +\section{MQ cookbook} + +\subsection{Manage ``trivial'' patches} + +Because the overhead of dropping files into a new Mercurial repository +is so low, it makes a lot of sense to manage patches this way even if +you simply want to make a few changes to a source tarball that you +downloaded. + +Begin by downloading and unpacking the source tarball, +and turning it into a Mercurial repository. +\interaction{mq.tarball.download} + +Continue by creating a patch stack and making your changes. +\interaction{mq.tarball.qinit} + +Let's say a few weeks or months pass, and your package author releases +a new version. First, bring their changes into the repository. +\interaction{mq.tarball.newsource} +The pipeline starting with \hgcmd{locate} above deletes all files in +the working directory, so that \hgcmd{commit}'s +\hgopt{commit}{--addremove} option can actually tell which files have +really been removed in the newer version of the source. + +Finally, you can apply your patches on top of the new tree. +\interaction{mq.tarball.repush} + +\subsection{Combining entire patches} +\label{sec:mq:combine} + +MQ provides a command, \hgxcmd{mq}{qfold} that lets you combine entire +patches. This ``folds'' the patches you name, in the order you name +them, into the topmost applied patch, and concatenates their +descriptions onto the end of its description. The patches that you +fold must be unapplied before you fold them. + +The order in which you fold patches matters. If your topmost applied +patch is \texttt{foo}, and you \hgxcmd{mq}{qfold} \texttt{bar} and +\texttt{quux} into it, you will end up with a patch that has the same +effect as if you applied first \texttt{foo}, then \texttt{bar}, +followed by \texttt{quux}. + +\subsection{Merging part of one patch into another} + +Merging \emph{part} of one patch into another is more difficult than +combining entire patches. + +If you want to move changes to entire files, you can use +\command{filterdiff}'s \cmdopt{filterdiff}{-i} and +\cmdopt{filterdiff}{-x} options to choose the modifications to snip +out of one patch, concatenating its output onto the end of the patch +you want to merge into. You usually won't need to modify the patch +you've merged the changes from. Instead, MQ will report some rejected +hunks when you \hgxcmd{mq}{qpush} it (from the hunks you moved into the +other patch), and you can simply \hgxcmd{mq}{qrefresh} the patch to drop +the duplicate hunks. + +If you have a patch that has multiple hunks modifying a file, and you +only want to move a few of those hunks, the job becomes more messy, +but you can still partly automate it. Use \cmdargs{lsdiff}{-nvv} to +print some metadata about the patch. +\interaction{mq.tools.lsdiff} + +This command prints three different kinds of number: +\begin{itemize} +\item (in the first column) a \emph{file number} to identify each file + modified in the patch; +\item (on the next line, indented) the line number within a modified + file where a hunk starts; and +\item (on the same line) a \emph{hunk number} to identify that hunk. +\end{itemize} + +You'll have to use some visual inspection, and reading of the patch, +to identify the file and hunk numbers you'll want, but you can then +pass them to to \command{filterdiff}'s \cmdopt{filterdiff}{--files} +and \cmdopt{filterdiff}{--hunks} options, to select exactly the file +and hunk you want to extract. + +Once you have this hunk, you can concatenate it onto the end of your +destination patch and continue with the remainder of +section~\ref{sec:mq:combine}. + +\section{Differences between quilt and MQ} + +If you are already familiar with quilt, MQ provides a similar command +set. There are a few differences in the way that it works. + +You will already have noticed that most quilt commands have MQ +counterparts that simply begin with a ``\texttt{q}''. The exceptions +are quilt's \texttt{add} and \texttt{remove} commands, the +counterparts for which are the normal Mercurial \hgcmd{add} and +\hgcmd{remove} commands. There is no MQ equivalent of the quilt +\texttt{edit} command. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/ch13-mq-collab.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,392 @@ +\chapter{Advanced uses of Mercurial Queues} +\label{chap:mq-collab} + +While it's easy to pick up straightforward uses of Mercurial Queues, +use of a little discipline and some of MQ's less frequently used +capabilities makes it possible to work in complicated development +environments. + +In this chapter, I will use as an example a technique I have used to +manage the development of an Infiniband device driver for the Linux +kernel. The driver in question is large (at least as drivers go), +with 25,000 lines of code spread across 35 source files. It is +maintained by a small team of developers. + +While much of the material in this chapter is specific to Linux, the +same principles apply to any code base for which you're not the +primary owner, and upon which you need to do a lot of development. + +\section{The problem of many targets} + +The Linux kernel changes rapidly, and has never been internally +stable; developers frequently make drastic changes between releases. +This means that a version of the driver that works well with a +particular released version of the kernel will not even \emph{compile} +correctly against, typically, any other version. + +To maintain a driver, we have to keep a number of distinct versions of +Linux in mind. +\begin{itemize} +\item One target is the main Linux kernel development tree. + Maintenance of the code is in this case partly shared by other + developers in the kernel community, who make ``drive-by'' + modifications to the driver as they develop and refine kernel + subsystems. +\item We also maintain a number of ``backports'' to older versions of + the Linux kernel, to support the needs of customers who are running + older Linux distributions that do not incorporate our drivers. (To + \emph{backport} a piece of code is to modify it to work in an older + version of its target environment than the version it was developed + for.) +\item Finally, we make software releases on a schedule that is + necessarily not aligned with those used by Linux distributors and + kernel developers, so that we can deliver new features to customers + without forcing them to upgrade their entire kernels or + distributions. +\end{itemize} + +\subsection{Tempting approaches that don't work well} + +There are two ``standard'' ways to maintain a piece of software that +has to target many different environments. + +The first is to maintain a number of branches, each intended for a +single target. The trouble with this approach is that you must +maintain iron discipline in the flow of changes between repositories. +A new feature or bug fix must start life in a ``pristine'' repository, +then percolate out to every backport repository. Backport changes are +more limited in the branches they should propagate to; a backport +change that is applied to a branch where it doesn't belong will +probably stop the driver from compiling. + +The second is to maintain a single source tree filled with conditional +statements that turn chunks of code on or off depending on the +intended target. Because these ``ifdefs'' are not allowed in the +Linux kernel tree, a manual or automatic process must be followed to +strip them out and yield a clean tree. A code base maintained in this +fashion rapidly becomes a rat's nest of conditional blocks that are +difficult to understand and maintain. + +Neither of these approaches is well suited to a situation where you +don't ``own'' the canonical copy of a source tree. In the case of a +Linux driver that is distributed with the standard kernel, Linus's +tree contains the copy of the code that will be treated by the world +as canonical. The upstream version of ``my'' driver can be modified +by people I don't know, without me even finding out about it until +after the changes show up in Linus's tree. + +These approaches have the added weakness of making it difficult to +generate well-formed patches to submit upstream. + +In principle, Mercurial Queues seems like a good candidate to manage a +development scenario such as the above. While this is indeed the +case, MQ contains a few added features that make the job more +pleasant. + +\section{Conditionally applying patches with guards} + +Perhaps the best way to maintain sanity with so many targets is to be +able to choose specific patches to apply for a given situation. MQ +provides a feature called ``guards'' (which originates with quilt's +\texttt{guards} command) that does just this. To start off, let's +create a simple repository for experimenting in. +\interaction{mq.guards.init} +This gives us a tiny repository that contains two patches that don't +have any dependencies on each other, because they touch different files. + +The idea behind conditional application is that you can ``tag'' a +patch with a \emph{guard}, which is simply a text string of your +choosing, then tell MQ to select specific guards to use when applying +patches. MQ will then either apply, or skip over, a guarded patch, +depending on the guards that you have selected. + +A patch can have an arbitrary number of guards; +each one is \emph{positive} (``apply this patch if this guard is +selected'') or \emph{negative} (``skip this patch if this guard is +selected''). A patch with no guards is always applied. + +\section{Controlling the guards on a patch} + +The \hgxcmd{mq}{qguard} command lets you determine which guards should +apply to a patch, or display the guards that are already in effect. +Without any arguments, it displays the guards on the current topmost +patch. +\interaction{mq.guards.qguard} +To set a positive guard on a patch, prefix the name of the guard with +a ``\texttt{+}''. +\interaction{mq.guards.qguard.pos} +To set a negative guard on a patch, prefix the name of the guard with +a ``\texttt{-}''. +\interaction{mq.guards.qguard.neg} + +\begin{note} + The \hgxcmd{mq}{qguard} command \emph{sets} the guards on a patch; it + doesn't \emph{modify} them. What this means is that if you run + \hgcmdargs{qguard}{+a +b} on a patch, then \hgcmdargs{qguard}{+c} on + the same patch, the \emph{only} guard that will be set on it + afterwards is \texttt{+c}. +\end{note} + +Mercurial stores guards in the \sfilename{series} file; the form in +which they are stored is easy both to understand and to edit by hand. +(In other words, you don't have to use the \hgxcmd{mq}{qguard} command if +you don't want to; it's okay to simply edit the \sfilename{series} +file.) +\interaction{mq.guards.series} + +\section{Selecting the guards to use} + +The \hgxcmd{mq}{qselect} command determines which guards are active at a +given time. The effect of this is to determine which patches MQ will +apply the next time you run \hgxcmd{mq}{qpush}. It has no other effect; in +particular, it doesn't do anything to patches that are already +applied. + +With no arguments, the \hgxcmd{mq}{qselect} command lists the guards +currently in effect, one per line of output. Each argument is treated +as the name of a guard to apply. +\interaction{mq.guards.qselect.foo} +In case you're interested, the currently selected guards are stored in +the \sfilename{guards} file. +\interaction{mq.guards.qselect.cat} +We can see the effect the selected guards have when we run +\hgxcmd{mq}{qpush}. +\interaction{mq.guards.qselect.qpush} + +A guard cannot start with a ``\texttt{+}'' or ``\texttt{-}'' +character. The name of a guard must not contain white space, but most +other characters are acceptable. If you try to use a guard with an +invalid name, MQ will complain: +\interaction{mq.guards.qselect.error} +Changing the selected guards changes the patches that are applied. +\interaction{mq.guards.qselect.quux} +You can see in the example below that negative guards take precedence +over positive guards. +\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 +determining 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:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/ch14-hgext.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,428 @@ +\chapter{Adding functionality with extensions} +\label{chap:hgext} + +While the core of Mercurial is quite complete from a functionality +standpoint, it's deliberately shorn of fancy features. This approach +of preserving simplicity keeps the software easy to deal with for both +maintainers and users. + +However, Mercurial doesn't box you in with an inflexible command set: +you can add features to it as \emph{extensions} (sometimes known as +\emph{plugins}). We've already discussed a few of these extensions in +earlier chapters. +\begin{itemize} +\item Section~\ref{sec:tour-merge:fetch} covers the \hgext{fetch} + extension; this combines pulling new changes and merging them with + local changes into a single command, \hgxcmd{fetch}{fetch}. +\item In chapter~\ref{chap:hook}, we covered several extensions that + are useful for hook-related functionality: \hgext{acl} adds access + control lists; \hgext{bugzilla} adds integration with the Bugzilla + bug tracking system; and \hgext{notify} sends notification emails on + new changes. +\item The Mercurial Queues patch management extension is so invaluable + that it merits two chapters and an appendix all to itself. + Chapter~\ref{chap:mq} covers the basics; + chapter~\ref{chap:mq-collab} discusses advanced topics; and + appendix~\ref{chap:mqref} goes into detail on each command. +\end{itemize} + +In this chapter, we'll cover some of the other extensions that are +available for Mercurial, and briefly touch on some of the machinery +you'll need to know about if you want to write an extension of your +own. +\begin{itemize} +\item In section~\ref{sec:hgext:inotify}, we'll discuss the + possibility of \emph{huge} performance improvements using the + \hgext{inotify} extension. +\end{itemize} + +\section{Improve performance with the \hgext{inotify} extension} +\label{sec:hgext:inotify} + +Are you interested in having some of the most common Mercurial +operations run as much as a hundred times faster? Read on! + +Mercurial has great performance under normal circumstances. For +example, when you run the \hgcmd{status} command, Mercurial has to +scan almost every directory and file in your repository so that it can +display file status. Many other Mercurial commands need to do the +same work behind the scenes; for example, the \hgcmd{diff} command +uses the status machinery to avoid doing an expensive comparison +operation on files that obviously haven't changed. + +Because obtaining file status is crucial to good performance, the +authors of Mercurial have optimised this code to within an inch of its +life. However, there's no avoiding the fact that when you run +\hgcmd{status}, Mercurial is going to have to perform at least one +expensive system call for each managed file to determine whether it's +changed since the last time Mercurial checked. For a sufficiently +large repository, this can take a long time. + +To put a number on the magnitude of this effect, I created a +repository containing 150,000 managed files. I timed \hgcmd{status} +as taking ten seconds to run, even when \emph{none} of those files had +been modified. + +Many modern operating systems contain a file notification facility. +If a program signs up to an appropriate service, the operating system +will notify it every time a file of interest is created, modified, or +deleted. On Linux systems, the kernel component that does this is +called \texttt{inotify}. + +Mercurial's \hgext{inotify} extension talks to the kernel's +\texttt{inotify} component to optimise \hgcmd{status} commands. The +extension has two components. A daemon sits in the background and +receives notifications from the \texttt{inotify} subsystem. It also +listens for connections from a regular Mercurial command. The +extension modifies Mercurial's behaviour so that instead of scanning +the filesystem, it queries the daemon. Since the daemon has perfect +information about the state of the repository, it can respond with a +result instantaneously, avoiding the need to scan every directory and +file in the repository. + +Recall the ten seconds that I measured plain Mercurial as taking to +run \hgcmd{status} on a 150,000 file repository. With the +\hgext{inotify} extension enabled, the time dropped to 0.1~seconds, a +factor of \emph{one hundred} faster. + +Before we continue, please pay attention to some caveats. +\begin{itemize} +\item The \hgext{inotify} extension is Linux-specific. Because it + interfaces directly to the Linux kernel's \texttt{inotify} + subsystem, it does not work on other operating systems. +\item It should work on any Linux distribution that was released after + early~2005. Older distributions are likely to have a kernel that + lacks \texttt{inotify}, or a version of \texttt{glibc} that does not + have the necessary interfacing support. +\item Not all filesystems are suitable for use with the + \hgext{inotify} extension. Network filesystems such as NFS are a + non-starter, for example, particularly if you're running Mercurial + on several systems, all mounting the same network filesystem. The + kernel's \texttt{inotify} system has no way of knowing about changes + made on another system. Most local filesystems (e.g.~ext3, XFS, + ReiserFS) should work fine. +\end{itemize} + +The \hgext{inotify} extension is not yet shipped with Mercurial as of +May~2007, so it's a little more involved to set up than other +extensions. But the performance improvement is worth it! + +The extension currently comes in two parts: a set of patches to the +Mercurial source code, and a library of Python bindings to the +\texttt{inotify} subsystem. +\begin{note} + There are \emph{two} Python \texttt{inotify} binding libraries. One + of them is called \texttt{pyinotify}, and is packaged by some Linux + distributions as \texttt{python-inotify}. This is \emph{not} the + one you'll need, as it is too buggy and inefficient to be practical. +\end{note} +To get going, it's best to already have a functioning copy of +Mercurial installed. +\begin{note} + If you follow the instructions below, you'll be \emph{replacing} and + overwriting any existing installation of Mercurial that you might + already have, using the latest ``bleeding edge'' Mercurial code. + Don't say you weren't warned! +\end{note} +\begin{enumerate} +\item Clone the Python \texttt{inotify} binding repository. Build and + install it. + \begin{codesample4} + hg clone http://hg.kublai.com/python/inotify + cd inotify + python setup.py build --force + sudo python setup.py install --skip-build + \end{codesample4} +\item Clone the \dirname{crew} Mercurial repository. Clone the + \hgext{inotify} patch repository so that Mercurial Queues will be + able to apply patches to your cope of the \dirname{crew} repository. + \begin{codesample4} + hg clone http://hg.intevation.org/mercurial/crew + hg clone crew inotify + hg clone http://hg.kublai.com/mercurial/patches/inotify inotify/.hg/patches + \end{codesample4} +\item Make sure that you have the Mercurial Queues extension, + \hgext{mq}, enabled. If you've never used MQ, read + section~\ref{sec:mq:start} to get started quickly. +\item Go into the \dirname{inotify} repo, and apply all of the + \hgext{inotify} patches using the \hgxopt{mq}{qpush}{-a} option to + the \hgxcmd{mq}{qpush} command. + \begin{codesample4} + cd inotify + hg qpush -a + \end{codesample4} + If you get an error message from \hgxcmd{mq}{qpush}, you should not + continue. Instead, ask for help. +\item Build and install the patched version of Mercurial. + \begin{codesample4} + python setup.py build --force + sudo python setup.py install --skip-build + \end{codesample4} +\end{enumerate} +Once you've build a suitably patched version of Mercurial, all you +need to do to enable the \hgext{inotify} extension is add an entry to +your \hgrc. +\begin{codesample2} + [extensions] + inotify = +\end{codesample2} +When the \hgext{inotify} extension is enabled, Mercurial will +automatically and transparently start the status daemon the first time +you run a command that needs status in a repository. It runs one +status daemon per repository. + +The status daemon is started silently, and runs in the background. If +you look at a list of running processes after you've enabled the +\hgext{inotify} extension and run a few commands in different +repositories, you'll thus see a few \texttt{hg} processes sitting +around, waiting for updates from the kernel and queries from +Mercurial. + +The first time you run a Mercurial command in a repository when you +have the \hgext{inotify} extension enabled, it will run with about the +same performance as a normal Mercurial command. This is because the +status daemon needs to perform a normal status scan so that it has a +baseline against which to apply later updates from the kernel. +However, \emph{every} subsequent command that does any kind of status +check should be noticeably faster on repositories of even fairly +modest size. Better yet, the bigger your repository is, the greater a +performance advantage you'll see. The \hgext{inotify} daemon makes +status operations almost instantaneous on repositories of all sizes! + +If you like, you can manually start a status daemon using the +\hgxcmd{inotify}{inserve} command. This gives you slightly finer +control over how the daemon ought to run. This command will of course +only be available when the \hgext{inotify} extension is enabled. + +When you're using the \hgext{inotify} extension, you should notice +\emph{no difference at all} in Mercurial's behaviour, with the sole +exception of status-related commands running a whole lot faster than +they used to. You should specifically expect that commands will not +print different output; neither should they give different results. +If either of these situations occurs, please report a bug. + +\section{Flexible diff support with the \hgext{extdiff} extension} +\label{sec:hgext:extdiff} + +Mercurial's built-in \hgcmd{diff} command outputs plaintext unified +diffs. +\interaction{extdiff.diff} +If you would like to use an external tool to display modifications, +you'll want to use the \hgext{extdiff} extension. This will let you +use, for example, a graphical diff tool. + +The \hgext{extdiff} extension is bundled with Mercurial, so it's easy +to set up. In the \rcsection{extensions} section of your \hgrc, +simply add a one-line entry to enable the extension. +\begin{codesample2} + [extensions] + extdiff = +\end{codesample2} +This introduces a command named \hgxcmd{extdiff}{extdiff}, which by +default uses your system's \command{diff} command to generate a +unified diff in the same form as the built-in \hgcmd{diff} command. +\interaction{extdiff.extdiff} +The result won't be exactly the same as with the built-in \hgcmd{diff} +variations, because the output of \command{diff} varies from one +system to another, even when passed the same options. + +As the ``\texttt{making snapshot}'' lines of output above imply, the +\hgxcmd{extdiff}{extdiff} command works by creating two snapshots of +your source tree. The first snapshot is of the source revision; the +second, of the target revision or working directory. The +\hgxcmd{extdiff}{extdiff} command generates these snapshots in a +temporary directory, passes the name of each directory to an external +diff viewer, then deletes the temporary directory. For efficiency, it +only snapshots the directories and files that have changed between the +two revisions. + +Snapshot directory names have the same base name as your repository. +If your repository path is \dirname{/quux/bar/foo}, then \dirname{foo} +will be the name of each snapshot directory. Each snapshot directory +name has its changeset ID appended, if appropriate. If a snapshot is +of revision \texttt{a631aca1083f}, the directory will be named +\dirname{foo.a631aca1083f}. A snapshot of the working directory won't +have a changeset ID appended, so it would just be \dirname{foo} in +this example. To see what this looks like in practice, look again at +the \hgxcmd{extdiff}{extdiff} example above. Notice that the diff has +the snapshot directory names embedded in its header. + +The \hgxcmd{extdiff}{extdiff} command accepts two important options. +The \hgxopt{extdiff}{extdiff}{-p} option lets you choose a program to +view differences with, instead of \command{diff}. With the +\hgxopt{extdiff}{extdiff}{-o} option, you can change the options that +\hgxcmd{extdiff}{extdiff} passes to the program (by default, these +options are ``\texttt{-Npru}'', which only make sense if you're +running \command{diff}). In other respects, the +\hgxcmd{extdiff}{extdiff} command acts similarly to the built-in +\hgcmd{diff} command: you use the same option names, syntax, and +arguments to specify the revisions you want, the files you want, and +so on. + +As an example, here's how to run the normal system \command{diff} +command, getting it to generate context diffs (using the +\cmdopt{diff}{-c} option) instead of unified diffs, and five lines of +context instead of the default three (passing \texttt{5} as the +argument to the \cmdopt{diff}{-C} option). +\interaction{extdiff.extdiff-ctx} + +Launching a visual diff tool is just as easy. Here's how to launch +the \command{kdiff3} viewer. +\begin{codesample2} + hg extdiff -p kdiff3 -o '' +\end{codesample2} + +If your diff viewing command can't deal with directories, you can +easily work around this with a little scripting. For an example of +such scripting in action with the \hgext{mq} extension and the +\command{interdiff} command, see +section~\ref{mq-collab:tips:interdiff}. + +\subsection{Defining command aliases} + +It can be cumbersome to remember the options to both the +\hgxcmd{extdiff}{extdiff} command and the diff viewer you want to use, +so the \hgext{extdiff} extension lets you define \emph{new} commands +that will invoke your diff viewer with exactly the right options. + +All you need to do is edit your \hgrc, and add a section named +\rcsection{extdiff}. Inside this section, you can define multiple +commands. Here's how to add a \texttt{kdiff3} command. Once you've +defined this, you can type ``\texttt{hg kdiff3}'' and the +\hgext{extdiff} extension will run \command{kdiff3} for you. +\begin{codesample2} + [extdiff] + cmd.kdiff3 = +\end{codesample2} +If you leave the right hand side of the definition empty, as above, +the \hgext{extdiff} extension uses the name of the command you defined +as the name of the external program to run. But these names don't +have to be the same. Here, we define a command named ``\texttt{hg wibble}'', which runs \command{kdiff3}. +\begin{codesample2} + [extdiff] + cmd.wibble = kdiff3 +\end{codesample2} + +You can also specify the default options that you want to invoke your +diff viewing program with. The prefix to use is ``\texttt{opts.}'', +followed by the name of the command to which the options apply. This +example defines a ``\texttt{hg vimdiff}'' command that runs the +\command{vim} editor's \texttt{DirDiff} extension. +\begin{codesample2} + [extdiff] + cmd.vimdiff = vim + opts.vimdiff = -f '+next' '+execute "DirDiff" argv(0) argv(1)' +\end{codesample2} + +\section{Cherrypicking changes with the \hgext{transplant} extension} +\label{sec:hgext:transplant} + +Need to have a long chat with Brendan about this. + +\section{Send changes via email with the \hgext{patchbomb} extension} +\label{sec:hgext:patchbomb} + +Many projects have a culture of ``change review'', in which people +send their modifications to a mailing list for others to read and +comment on before they commit the final version to a shared +repository. Some projects have people who act as gatekeepers; they +apply changes from other people to a repository to which those others +don't have access. + +Mercurial makes it easy to send changes over email for review or +application, via its \hgext{patchbomb} extension. The extension is so +namd because changes are formatted as patches, and it's usual to send +one changeset per email message. Sending a long series of changes by +email is thus much like ``bombing'' the recipient's inbox, hence +``patchbomb''. + +As usual, the basic configuration of the \hgext{patchbomb} extension +takes just one or two lines in your \hgrc. +\begin{codesample2} + [extensions] + patchbomb = +\end{codesample2} +Once you've enabled the extension, you will have a new command +available, named \hgxcmd{patchbomb}{email}. + +The safest and best way to invoke the \hgxcmd{patchbomb}{email} +command is to \emph{always} run it first with the +\hgxopt{patchbomb}{email}{-n} option. This will show you what the +command \emph{would} send, without actually sending anything. Once +you've had a quick glance over the changes and verified that you are +sending the right ones, you can rerun the same command, with the +\hgxopt{patchbomb}{email}{-n} option removed. + +The \hgxcmd{patchbomb}{email} command accepts the same kind of +revision syntax as every other Mercurial command. For example, this +command will send every revision between 7 and \texttt{tip}, +inclusive. +\begin{codesample2} + hg email -n 7:tip +\end{codesample2} +You can also specify a \emph{repository} to compare with. If you +provide a repository but no revisions, the \hgxcmd{patchbomb}{email} +command will send all revisions in the local repository that are not +present in the remote repository. If you additionally specify +revisions or a branch name (the latter using the +\hgxopt{patchbomb}{email}{-b} option), this will constrain the +revisions sent. + +It's perfectly safe to run the \hgxcmd{patchbomb}{email} command +without the names of the people you want to send to: if you do this, +it will just prompt you for those values interactively. (If you're +using a Linux or Unix-like system, you should have enhanced +\texttt{readline}-style editing capabilities when entering those +headers, too, which is useful.) + +When you are sending just one revision, the \hgxcmd{patchbomb}{email} +command will by default use the first line of the changeset +description as the subject of the single email message it sends. + +If you send multiple revisions, the \hgxcmd{patchbomb}{email} command +will usually send one message per changeset. It will preface the +series with an introductory message, in which you should describe the +purpose of the series of changes you're sending. + +\subsection{Changing the behaviour of patchbombs} + +Not every project has exactly the same conventions for sending changes +in email; the \hgext{patchbomb} extension tries to accommodate a +number of variations through command line options. +\begin{itemize} +\item You can write a subject for the introductory message on the + command line using the \hgxopt{patchbomb}{email}{-s} option. This + takes one argument, the text of the subject to use. +\item To change the email address from which the messages originate, + use the \hgxopt{patchbomb}{email}{-f} option. This takes one + argument, the email address to use. +\item The default behaviour is to send unified diffs (see + section~\ref{sec:mq:patch} for a description of the format), one per + message. You can send a binary bundle instead with the + \hgxopt{patchbomb}{email}{-b} option. +\item Unified diffs are normally prefaced with a metadata header. You + can omit this, and send unadorned diffs, with the + \hgxopt{patchbomb}{email}{--plain} option. +\item Diffs are normally sent ``inline'', in the same body part as the + description of a patch. This makes it easiest for the largest + number of readers to quote and respond to parts of a diff, as some + mail clients will only quote the first MIME body part in a message. + If you'd prefer to send the description and the diff in separate + body parts, use the \hgxopt{patchbomb}{email}{-a} option. +\item Instead of sending mail messages, you can write them to an + \texttt{mbox}-format mail folder using the + \hgxopt{patchbomb}{email}{-m} option. That option takes one + argument, the name of the file to write to. +\item If you would like to add a \command{diffstat}-format summary to + each patch, and one to the introductory message, use the + \hgxopt{patchbomb}{email}{-d} option. The \command{diffstat} + command displays a table containing the name of each file patched, + the number of lines affected, and a histogram showing how much each + file is modified. This gives readers a qualitative glance at how + complex a patch is. +\end{itemize} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- a/en/cmdref.tex Wed Jan 21 14:16:38 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,176 +0,0 @@ -\chapter{Command reference} -\label{cmdref} - -\cmdref{add}{add files at the next commit} -\optref{add}{I}{include} -\optref{add}{X}{exclude} -\optref{add}{n}{dry-run} - -\cmdref{diff}{print changes in history or working directory} - -Show differences between revisions for the specified files or -directories, using the unified diff format. For a description of the -unified diff format, see section~\ref{sec:mq:patch}. - -By default, this command does not print diffs for files that Mercurial -considers to contain binary data. To control this behaviour, see the -\hgopt{diff}{-a} and \hgopt{diff}{--git} options. - -\subsection{Options} - -\loptref{diff}{nodates} - -Omit date and time information when printing diff headers. - -\optref{diff}{B}{ignore-blank-lines} - -Do not print changes that only insert or delete blank lines. A line -that contains only whitespace is not considered blank. - -\optref{diff}{I}{include} - -Include files and directories whose names match the given patterns. - -\optref{diff}{X}{exclude} - -Exclude files and directories whose names match the given patterns. - -\optref{diff}{a}{text} - -If this option is not specified, \hgcmd{diff} will refuse to print -diffs for files that it detects as binary. Specifying \hgopt{diff}{-a} -forces \hgcmd{diff} to treat all files as text, and generate diffs for -all of them. - -This option is useful for files that are ``mostly text'' but have a -few embedded NUL characters. If you use it on files that contain a -lot of binary data, its output will be incomprehensible. - -\optref{diff}{b}{ignore-space-change} - -Do not print a line if the only change to that line is in the amount -of white space it contains. - -\optref{diff}{g}{git} - -Print \command{git}-compatible diffs. XXX reference a format -description. - -\optref{diff}{p}{show-function} - -Display the name of the enclosing function in a hunk header, using a -simple heuristic. This functionality is enabled by default, so the -\hgopt{diff}{-p} option has no effect unless you change the value of -the \rcitem{diff}{showfunc} config item, as in the following example. -\interaction{cmdref.diff-p} - -\optref{diff}{r}{rev} - -Specify one or more revisions to compare. The \hgcmd{diff} command -accepts up to two \hgopt{diff}{-r} options to specify the revisions to -compare. - -\begin{enumerate} -\setcounter{enumi}{0} -\item Display the differences between the parent revision of the - working directory and the working directory. -\item Display the differences between the specified changeset and the - working directory. -\item Display the differences between the two specified changesets. -\end{enumerate} - -You can specify two revisions using either two \hgopt{diff}{-r} -options or revision range notation. For example, the two revision -specifications below are equivalent. -\begin{codesample2} - hg diff -r 10 -r 20 - hg diff -r10:20 -\end{codesample2} - -When you provide two revisions, Mercurial treats the order of those -revisions as significant. Thus, \hgcmdargs{diff}{-r10:20} will -produce a diff that will transform files from their contents as of -revision~10 to their contents as of revision~20, while -\hgcmdargs{diff}{-r20:10} means the opposite: the diff that will -transform files from their revision~20 contents to their revision~10 -contents. You cannot reverse the ordering in this way if you are -diffing against the working directory. - -\optref{diff}{w}{ignore-all-space} - -\cmdref{version}{print version and copyright information} - -This command displays the version of Mercurial you are running, and -its copyright license. There are four kinds of version string that -you may see. -\begin{itemize} -\item The string ``\texttt{unknown}''. This version of Mercurial was - not built in a Mercurial repository, and cannot determine its own - version. -\item A short numeric string, such as ``\texttt{1.1}''. This is a - build of a revision of Mercurial that was identified by a specific - tag in the repository where it was built. (This doesn't necessarily - mean that you're running an official release; someone else could - have added that tag to any revision in the repository where they - built Mercurial.) -\item A hexadecimal string, such as ``\texttt{875489e31abe}''. This - is a build of the given revision of Mercurial. -\item A hexadecimal string followed by a date, such as - ``\texttt{875489e31abe+20070205}''. This is a build of the given - revision of Mercurial, where the build repository contained some - local changes that had not been committed. -\end{itemize} - -\subsection{Tips and tricks} - -\subsubsection{Why do the results of \hgcmd{diff} and \hgcmd{status} - differ?} -\label{cmdref:diff-vs-status} - -When you run the \hgcmd{status} command, you'll see a list of files -that Mercurial will record changes for the next time you perform a -commit. If you run the \hgcmd{diff} command, you may notice that it -prints diffs for only a \emph{subset} of the files that \hgcmd{status} -listed. There are two possible reasons for this. - -The first is that \hgcmd{status} prints some kinds of modifications -that \hgcmd{diff} doesn't normally display. The \hgcmd{diff} command -normally outputs unified diffs, which don't have the ability to -represent some changes that Mercurial can track. Most notably, -traditional diffs can't represent a change in whether or not a file is -executable, but Mercurial records this information. - -If you use the \hgopt{diff}{--git} option to \hgcmd{diff}, it will -display \command{git}-compatible diffs that \emph{can} display this -extra information. - -The second possible reason that \hgcmd{diff} might be printing diffs -for a subset of the files displayed by \hgcmd{status} is that if you -invoke it without any arguments, \hgcmd{diff} prints diffs against the -first parent of the working directory. If you have run \hgcmd{merge} -to merge two changesets, but you haven't yet committed the results of -the merge, your working directory has two parents (use \hgcmd{parents} -to see them). While \hgcmd{status} prints modifications relative to -\emph{both} parents after an uncommitted merge, \hgcmd{diff} still -operates relative only to the first parent. You can get it to print -diffs relative to the second parent by specifying that parent with the -\hgopt{diff}{-r} option. There is no way to print diffs relative to -both parents. - -\subsubsection{Generating safe binary diffs} - -If you use the \hgopt{diff}{-a} option to force Mercurial to print -diffs of files that are either ``mostly text'' or contain lots of -binary data, those diffs cannot subsequently be applied by either -Mercurial's \hgcmd{import} command or the system's \command{patch} -command. - -If you want to generate a diff of a binary file that is safe to use as -input for \hgcmd{import}, use the \hgcmd{diff}{--git} option when you -generate the patch. The system \command{patch} command cannot handle -binary patches at all. - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "00book" -%%% End:
--- a/en/collab.tex Wed Jan 21 14:16:38 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1118 +0,0 @@ -\chapter{Collaborating with other people} -\label{cha:collab} - -As a completely decentralised tool, Mercurial doesn't impose any -policy on how people ought to work with each other. However, if -you're new to distributed revision control, it helps to have some -tools and examples in mind when you're thinking about possible -workflow models. - -\section{Mercurial's web interface} - -Mercurial has a powerful web interface that provides several -useful capabilities. - -For interactive use, the web interface lets you browse a single -repository or a collection of repositories. You can view the history -of a repository, examine each change (comments and diffs), and view -the contents of each directory and file. - -Also for human consumption, the web interface provides an RSS feed of -the changes in a repository. This lets you ``subscribe'' to a -repository using your favourite feed reader, and be automatically -notified of activity in that repository as soon as it happens. I find -this capability much more convenient than the model of subscribing to -a mailing list to which notifications are sent, as it requires no -additional configuration on the part of whoever is serving the -repository. - -The web interface also lets remote users clone a repository, pull -changes from it, and (when the server is configured to permit it) push -changes back to it. Mercurial's HTTP tunneling protocol aggressively -compresses data, so that it works efficiently even over low-bandwidth -network connections. - -The easiest way to get started with the web interface is to use your -web browser to visit an existing repository, such as the master -Mercurial repository at -\url{http://www.selenic.com/repo/hg?style=gitweb}. - -If you're interested in providing a web interface to your own -repositories, Mercurial provides two ways to do this. The first is -using the \hgcmd{serve} command, which is best suited to short-term -``lightweight'' serving. See section~\ref{sec:collab:serve} below for -details of how to use this command. If you have a long-lived -repository that you'd like to make permanently available, Mercurial -has built-in support for the CGI (Common Gateway Interface) standard, -which all common web servers support. See -section~\ref{sec:collab:cgi} for details of CGI configuration. - -\section{Collaboration models} - -With a suitably flexible tool, making decisions about workflow is much -more of a social engineering challenge than a technical one. -Mercurial imposes few limitations on how you can structure the flow of -work in a project, so it's up to you and your group to set up and live -with a model that matches your own particular needs. - -\subsection{Factors to keep in mind} - -The most important aspect of any model that you must keep in mind is -how well it matches the needs and capabilities of the people who will -be using it. This might seem self-evident; even so, you still can't -afford to forget it for a moment. - -I once put together a workflow model that seemed to make perfect sense -to me, but that caused a considerable amount of consternation and -strife within my development team. In spite of my attempts to explain -why we needed a complex set of branches, and how changes ought to flow -between them, a few team members revolted. Even though they were -smart people, they didn't want to pay attention to the constraints we -were operating under, or face the consequences of those constraints in -the details of the model that I was advocating. - -Don't sweep foreseeable social or technical problems under the rug. -Whatever scheme you put into effect, you should plan for mistakes and -problem scenarios. Consider adding automated machinery to prevent, or -quickly recover from, trouble that you can anticipate. As an example, -if you intend to have a branch with not-for-release changes in it, -you'd do well to think early about the possibility that someone might -accidentally merge those changes into a release branch. You could -avoid this particular problem by writing a hook that prevents changes -from being merged from an inappropriate branch. - -\subsection{Informal anarchy} - -I wouldn't suggest an ``anything goes'' approach as something -sustainable, but it's a model that's easy to grasp, and it works -perfectly well in a few unusual situations. - -As one example, many projects have a loose-knit group of collaborators -who rarely physically meet each other. Some groups like to overcome -the isolation of working at a distance by organising occasional -``sprints''. In a sprint, a number of people get together in a single -location (a company's conference room, a hotel meeting room, that kind -of place) and spend several days more or less locked in there, hacking -intensely on a handful of projects. - -A sprint is the perfect place to use the \hgcmd{serve} command, since -\hgcmd{serve} does not requires any fancy server infrastructure. You -can get started with \hgcmd{serve} in moments, by reading -section~\ref{sec:collab:serve} below. Then simply tell the person -next to you that you're running a server, send the URL to them in an -instant message, and you immediately have a quick-turnaround way to -work together. They can type your URL into their web browser and -quickly review your changes; or they can pull a bugfix from you and -verify it; or they can clone a branch containing a new feature and try -it out. - -The charm, and the problem, with doing things in an ad hoc fashion -like this is that only people who know about your changes, and where -they are, can see them. Such an informal approach simply doesn't -scale beyond a handful people, because each individual needs to know -about $n$ different repositories to pull from. - -\subsection{A single central repository} - -For smaller projects migrating from a centralised revision control -tool, perhaps the easiest way to get started is to have changes flow -through a single shared central repository. This is also the -most common ``building block'' for more ambitious workflow schemes. - -Contributors start by cloning a copy of this repository. They can -pull changes from it whenever they need to, and some (perhaps all) -developers have permission to push a change back when they're ready -for other people to see it. - -Under this model, it can still often make sense for people to pull -changes directly from each other, without going through the central -repository. Consider a case in which I have a tentative bug fix, but -I am worried that if I were to publish it to the central repository, -it might subsequently break everyone else's trees as they pull it. To -reduce the potential for damage, I can ask you to clone my repository -into a temporary repository of your own and test it. This lets us put -off publishing the potentially unsafe change until it has had a little -testing. - -In this kind of scenario, people usually use the \command{ssh} -protocol to securely push changes to the central repository, as -documented in section~\ref{sec:collab:ssh}. It's also usual to -publish a read-only copy of the repository over HTTP using CGI, as in -section~\ref{sec:collab:cgi}. Publishing over HTTP satisfies the -needs of people who don't have push access, and those who want to use -web browsers to browse the repository's history. - -\subsection{Working with multiple branches} - -Projects of any significant size naturally tend to make progress on -several fronts simultaneously. In the case of software, it's common -for a project to go through periodic official releases. A release -might then go into ``maintenance mode'' for a while after its first -publication; maintenance releases tend to contain only bug fixes, not -new features. In parallel with these maintenance releases, one or -more future releases may be under development. People normally use -the word ``branch'' to refer to one of these many slightly different -directions in which development is proceeding. - -Mercurial is particularly well suited to managing a number of -simultaneous, but not identical, branches. Each ``development -direction'' can live in its own central repository, and you can merge -changes from one to another as the need arises. Because repositories -are independent of each other, unstable changes in a development -branch will never affect a stable branch unless someone explicitly -merges those changes in. - -Here's an example of how this can work in practice. Let's say you -have one ``main branch'' on a central server. -\interaction{branching.init} -People clone it, make changes locally, test them, and push them back. - -Once the main branch reaches a release milestone, you can use the -\hgcmd{tag} command to give a permanent name to the milestone -revision. -\interaction{branching.tag} -Let's say some ongoing development occurs on the main branch. -\interaction{branching.main} -Using the tag that was recorded at the milestone, people who clone -that repository at any time in the future can use \hgcmd{update} to -get a copy of the working directory exactly as it was when that tagged -revision was committed. -\interaction{branching.update} - -In addition, immediately after the main branch is tagged, someone can -then clone the main branch on the server to a new ``stable'' branch, -also on the server. -\interaction{branching.clone} - -Someone who needs to make a change to the stable branch can then clone -\emph{that} repository, make their changes, commit, and push their -changes back there. -\interaction{branching.stable} -Because Mercurial repositories are independent, and Mercurial doesn't -move changes around automatically, the stable and main branches are -\emph{isolated} from each other. The changes that you made on the -main branch don't ``leak'' to the stable branch, and vice versa. - -You'll often want all of your bugfixes on the stable branch to show up -on the main branch, too. Rather than rewrite a bugfix on the main -branch, you can simply pull and merge changes from the stable to the -main branch, and Mercurial will bring those bugfixes in for you. -\interaction{branching.merge} -The main branch will still contain changes that are not on the stable -branch, but it will also contain all of the bugfixes from the stable -branch. The stable branch remains unaffected by these changes. - -\subsection{Feature branches} - -For larger projects, an effective way to manage change is to break up -a team into smaller groups. Each group has a shared branch of its -own, cloned from a single ``master'' branch used by the entire -project. People working on an individual branch are typically quite -isolated from developments on other branches. - -\begin{figure}[ht] - \centering - \grafix{feature-branches} - \caption{Feature branches} - \label{fig:collab:feature-branches} -\end{figure} - -When a particular feature is deemed to be in suitable shape, someone -on that feature team pulls and merges from the master branch into the -feature branch, then pushes back up to the master branch. - -\subsection{The release train} - -Some projects are organised on a ``train'' basis: a release is -scheduled to happen every few months, and whatever features are ready -when the ``train'' is ready to leave are allowed in. - -This model resembles working with feature branches. The difference is -that when a feature branch misses a train, someone on the feature team -pulls and merges the changes that went out on that train release into -the feature branch, and the team continues its work on top of that -release so that their feature can make the next release. - -\subsection{The Linux kernel model} - -The development of the Linux kernel has a shallow hierarchical -structure, surrounded by a cloud of apparent chaos. Because most -Linux developers use \command{git}, a distributed revision control -tool with capabilities similar to Mercurial, it's useful to describe -the way work flows in that environment; if you like the ideas, the -approach translates well across tools. - -At the center of the community sits Linus Torvalds, the creator of -Linux. He publishes a single source repository that is considered the -``authoritative'' current tree by the entire developer community. -Anyone can clone Linus's tree, but he is very choosy about whose trees -he pulls from. - -Linus has a number of ``trusted lieutenants''. As a general rule, he -pulls whatever changes they publish, in most cases without even -reviewing those changes. Some of those lieutenants are generally -agreed to be ``maintainers'', responsible for specific subsystems -within the kernel. If a random kernel hacker wants to make a change -to a subsystem that they want to end up in Linus's tree, they must -find out who the subsystem's maintainer is, and ask that maintainer to -take their change. If the maintainer reviews their changes and agrees -to take them, they'll pass them along to Linus in due course. - -Individual lieutenants have their own approaches to reviewing, -accepting, and publishing changes; and for deciding when to feed them -to Linus. In addition, there are several well known branches that -people use for different purposes. For example, a few people maintain -``stable'' repositories of older versions of the kernel, to which they -apply critical fixes as needed. Some maintainers publish multiple -trees: one for experimental changes; one for changes that they are -about to feed upstream; and so on. Others just publish a single -tree. - -This model has two notable features. The first is that it's ``pull -only''. You have to ask, convince, or beg another developer to take a -change from you, because there are almost no trees to which more than -one person can push, and there's no way to push changes into a tree -that someone else controls. - -The second is that it's based on reputation and acclaim. If you're an -unknown, Linus will probably ignore changes from you without even -responding. But a subsystem maintainer will probably review them, and -will likely take them if they pass their criteria for suitability. -The more ``good'' changes you contribute to a maintainer, the more -likely they are to trust your judgment and accept your changes. If -you're well-known and maintain a long-lived branch for something Linus -hasn't yet accepted, people with similar interests may pull your -changes regularly to keep up with your work. - -Reputation and acclaim don't necessarily cross subsystem or ``people'' -boundaries. If you're a respected but specialised storage hacker, and -you try to fix a networking bug, that change will receive a level of -scrutiny from a network maintainer comparable to a change from a -complete stranger. - -To people who come from more orderly project backgrounds, the -comparatively chaotic Linux kernel development process often seems -completely insane. It's subject to the whims of individuals; people -make sweeping changes whenever they deem it appropriate; and the pace -of development is astounding. And yet Linux is a highly successful, -well-regarded piece of software. - -\subsection{Pull-only versus shared-push collaboration} - -A perpetual source of heat in the open source community is whether a -development model in which people only ever pull changes from others -is ``better than'' one in which multiple people can push changes to a -shared repository. - -Typically, the backers of the shared-push model use tools that -actively enforce this approach. If you're using a centralised -revision control tool such as Subversion, there's no way to make a -choice over which model you'll use: the tool gives you shared-push, -and if you want to do anything else, you'll have to roll your own -approach on top (such as applying a patch by hand). - -A good distributed revision control tool, such as Mercurial, will -support both models. You and your collaborators can then structure -how you work together based on your own needs and preferences, not on -what contortions your tools force you into. - -\subsection{Where collaboration meets branch management} - -Once you and your team set up some shared repositories and start -propagating changes back and forth between local and shared repos, you -begin to face a related, but slightly different challenge: that of -managing the multiple directions in which your team may be moving at -once. Even though this subject is intimately related to how your team -collaborates, it's dense enough to merit treatment of its own, in -chapter~\ref{chap:branch}. - -\section{The technical side of sharing} - -The remainder of this chapter is devoted to the question of serving -data to your collaborators. - -\section{Informal sharing with \hgcmd{serve}} -\label{sec:collab:serve} - -Mercurial's \hgcmd{serve} command is wonderfully suited to small, -tight-knit, and fast-paced group environments. It also provides a -great way to get a feel for using Mercurial commands over a network. - -Run \hgcmd{serve} inside a repository, and in under a second it will -bring up a specialised HTTP server; this will accept connections from -any client, and serve up data for that repository until you terminate -it. Anyone who knows the URL of the server you just started, and can -talk to your computer over the network, can then use a web browser or -Mercurial to read data from that repository. A URL for a -\hgcmd{serve} instance running on a laptop is likely to look something -like \Verb|http://my-laptop.local:8000/|. - -The \hgcmd{serve} command is \emph{not} a general-purpose web server. -It can do only two things: -\begin{itemize} -\item Allow people to browse the history of the repository it's - serving, from their normal web browsers. -\item Speak Mercurial's wire protocol, so that people can - \hgcmd{clone} or \hgcmd{pull} changes from that repository. -\end{itemize} -In particular, \hgcmd{serve} won't allow remote users to \emph{modify} -your repository. It's intended for read-only use. - -If you're getting started with Mercurial, there's nothing to prevent -you from using \hgcmd{serve} to serve up a repository on your own -computer, then use commands like \hgcmd{clone}, \hgcmd{incoming}, and -so on to talk to that server as if the repository was hosted remotely. -This can help you to quickly get acquainted with using commands on -network-hosted repositories. - -\subsection{A few things to keep in mind} - -Because it provides unauthenticated read access to all clients, you -should only use \hgcmd{serve} in an environment where you either don't -care, or have complete control over, who can access your network and -pull data from your repository. - -The \hgcmd{serve} command knows nothing about any firewall software -you might have installed on your system or network. It cannot detect -or control your firewall software. If other people are unable to talk -to a running \hgcmd{serve} instance, the second thing you should do -(\emph{after} you make sure that they're using the correct URL) is -check your firewall configuration. - -By default, \hgcmd{serve} listens for incoming connections on -port~8000. If another process is already listening on the port you -want to use, you can specify a different port to listen on using the -\hgopt{serve}{-p} option. - -Normally, when \hgcmd{serve} starts, it prints no output, which can be -a bit unnerving. If you'd like to confirm that it is indeed running -correctly, and find out what URL you should send to your -collaborators, start it with the \hggopt{-v} option. - -\section{Using the Secure Shell (ssh) protocol} -\label{sec:collab:ssh} - -You can pull and push changes securely over a network connection using -the Secure Shell (\texttt{ssh}) protocol. To use this successfully, -you may have to do a little bit of configuration on the client or -server sides. - -If you're not familiar with ssh, it's a network protocol that lets you -securely communicate with another computer. To use it with Mercurial, -you'll be setting up one or more user accounts on a server so that -remote users can log in and execute commands. - -(If you \emph{are} familiar with ssh, you'll probably find some of the -material that follows to be elementary in nature.) - -\subsection{How to read and write ssh URLs} - -An ssh URL tends to look like this: -\begin{codesample2} - ssh://bos@hg.serpentine.com:22/hg/hgbook -\end{codesample2} -\begin{enumerate} -\item The ``\texttt{ssh://}'' part tells Mercurial to use the ssh - protocol. -\item The ``\texttt{bos@}'' component indicates what username to log - into the server as. You can leave this out if the remote username - is the same as your local username. -\item The ``\texttt{hg.serpentine.com}'' gives the hostname of the - server to log into. -\item The ``:22'' identifies the port number to connect to the server - on. The default port is~22, so you only need to specify this part - if you're \emph{not} using port~22. -\item The remainder of the URL is the local path to the repository on - the server. -\end{enumerate} - -There's plenty of scope for confusion with the path component of ssh -URLs, as there is no standard way for tools to interpret it. Some -programs behave differently than others when dealing with these paths. -This isn't an ideal situation, but it's unlikely to change. Please -read the following paragraphs carefully. - -Mercurial treats the path to a repository on the server as relative to -the remote user's home directory. For example, if user \texttt{foo} -on the server has a home directory of \dirname{/home/foo}, then an ssh -URL that contains a path component of \dirname{bar} -\emph{really} refers to the directory \dirname{/home/foo/bar}. - -If you want to specify a path relative to another user's home -directory, you can use a path that starts with a tilde character -followed by the user's name (let's call them \texttt{otheruser}), like -this. -\begin{codesample2} - ssh://server/~otheruser/hg/repo -\end{codesample2} - -And if you really want to specify an \emph{absolute} path on the -server, begin the path component with two slashes, as in this example. -\begin{codesample2} - ssh://server//absolute/path -\end{codesample2} - -\subsection{Finding an ssh client for your system} - -Almost every Unix-like system comes with OpenSSH preinstalled. If -you're using such a system, run \Verb|which ssh| to find out if -the \command{ssh} command is installed (it's usually in -\dirname{/usr/bin}). In the unlikely event that it isn't present, -take a look at your system documentation to figure out how to install -it. - -On Windows, you'll first need to download a suitable ssh -client. There are two alternatives. -\begin{itemize} -\item Simon Tatham's excellent PuTTY package~\cite{web:putty} provides - a complete suite of ssh client commands. -\item If you have a high tolerance for pain, you can use the Cygwin - port of OpenSSH. -\end{itemize} -In either case, you'll need to edit your \hgini\ file to tell -Mercurial where to find the actual client command. For example, if -you're using PuTTY, you'll need to use the \command{plink} command as -a command-line ssh client. -\begin{codesample2} - [ui] - ssh = C:/path/to/plink.exe -ssh -i "C:/path/to/my/private/key" -\end{codesample2} - -\begin{note} - The path to \command{plink} shouldn't contain any whitespace - characters, or Mercurial may not be able to run it correctly (so - putting it in \dirname{C:\\Program Files} is probably not a good - idea). -\end{note} - -\subsection{Generating a key pair} - -To avoid the need to repetitively type a password every time you need -to use your ssh client, I recommend generating a key pair. On a -Unix-like system, the \command{ssh-keygen} command will do the trick. -On Windows, if you're using PuTTY, the \command{puttygen} command is -what you'll need. - -When you generate a key pair, it's usually \emph{highly} advisable to -protect it with a passphrase. (The only time that you might not want -to do this is when you're using the ssh protocol for automated tasks -on a secure network.) - -Simply generating a key pair isn't enough, however. You'll need to -add the public key to the set of authorised keys for whatever user -you're logging in remotely as. For servers using OpenSSH (the vast -majority), this will mean adding the public key to a list in a file -called \sfilename{authorized\_keys} in their \sdirname{.ssh} -directory. - -On a Unix-like system, your public key will have a \filename{.pub} -extension. If you're using \command{puttygen} on Windows, you can -save the public key to a file of your choosing, or paste it from the -window it's displayed in straight into the -\sfilename{authorized\_keys} file. - -\subsection{Using an authentication agent} - -An authentication agent is a daemon that stores passphrases in memory -(so it will forget passphrases if you log out and log back in again). -An ssh client will notice if it's running, and query it for a -passphrase. If there's no authentication agent running, or the agent -doesn't store the necessary passphrase, you'll have to type your -passphrase every time Mercurial tries to communicate with a server on -your behalf (e.g.~whenever you pull or push changes). - -The downside of storing passphrases in an agent is that it's possible -for a well-prepared attacker to recover the plain text of your -passphrases, in some cases even if your system has been power-cycled. -You should make your own judgment as to whether this is an acceptable -risk. It certainly saves a lot of repeated typing. - -On Unix-like systems, the agent is called \command{ssh-agent}, and -it's often run automatically for you when you log in. You'll need to -use the \command{ssh-add} command to add passphrases to the agent's -store. On Windows, if you're using PuTTY, the \command{pageant} -command acts as the agent. It adds an icon to your system tray that -will let you manage stored passphrases. - -\subsection{Configuring the server side properly} - -Because ssh can be fiddly to set up if you're new to it, there's a -variety of things that can go wrong. Add Mercurial on top, and -there's plenty more scope for head-scratching. Most of these -potential problems occur on the server side, not the client side. The -good news is that once you've gotten a configuration working, it will -usually continue to work indefinitely. - -Before you try using Mercurial to talk to an ssh server, it's best to -make sure that you can use the normal \command{ssh} or \command{putty} -command to talk to the server first. If you run into problems with -using these commands directly, Mercurial surely won't work. Worse, it -will obscure the underlying problem. Any time you want to debug -ssh-related Mercurial problems, you should drop back to making sure -that plain ssh client commands work first, \emph{before} you worry -about whether there's a problem with Mercurial. - -The first thing to be sure of on the server side is that you can -actually log in from another machine at all. If you can't use -\command{ssh} or \command{putty} to log in, the error message you get -may give you a few hints as to what's wrong. The most common problems -are as follows. -\begin{itemize} -\item If you get a ``connection refused'' error, either there isn't an - SSH daemon running on the server at all, or it's inaccessible due to - firewall configuration. -\item If you get a ``no route to host'' error, you either have an - incorrect address for the server or a seriously locked down firewall - that won't admit its existence at all. -\item If you get a ``permission denied'' error, you may have mistyped - the username on the server, or you could have mistyped your key's - passphrase or the remote user's password. -\end{itemize} -In summary, if you're having trouble talking to the server's ssh -daemon, first make sure that one is running at all. On many systems -it will be installed, but disabled, by default. Once you're done with -this step, you should then check that the server's firewall is -configured to allow incoming connections on the port the ssh daemon is -listening on (usually~22). Don't worry about more exotic -possibilities for misconfiguration until you've checked these two -first. - -If you're using an authentication agent on the client side to store -passphrases for your keys, you ought to be able to log into the server -without being prompted for a passphrase or a password. If you're -prompted for a passphrase, there are a few possible culprits. -\begin{itemize} -\item You might have forgotten to use \command{ssh-add} or - \command{pageant} to store the passphrase. -\item You might have stored the passphrase for the wrong key. -\end{itemize} -If you're being prompted for the remote user's password, there are -another few possible problems to check. -\begin{itemize} -\item Either the user's home directory or their \sdirname{.ssh} - directory might have excessively liberal permissions. As a result, - the ssh daemon will not trust or read their - \sfilename{authorized\_keys} file. For example, a group-writable - home or \sdirname{.ssh} directory will often cause this symptom. -\item The user's \sfilename{authorized\_keys} file may have a problem. - If anyone other than the user owns or can write to that file, the - ssh daemon will not trust or read it. -\end{itemize} - -In the ideal world, you should be able to run the following command -successfully, and it should print exactly one line of output, the -current date and time. -\begin{codesample2} - ssh myserver date -\end{codesample2} - -If, on your server, you have login scripts that print banners or other -junk even when running non-interactive commands like this, you should -fix them before you continue, so that they only print output if -they're run interactively. Otherwise these banners will at least -clutter up Mercurial's output. Worse, they could potentially cause -problems with running Mercurial commands remotely. Mercurial makes -tries to detect and ignore banners in non-interactive \command{ssh} -sessions, but it is not foolproof. (If you're editing your login -scripts on your server, the usual way to see if a login script is -running in an interactive shell is to check the return code from the -command \Verb|tty -s|.) - -Once you've verified that plain old ssh is working with your server, -the next step is to ensure that Mercurial runs on the server. The -following command should run successfully: -\begin{codesample2} - ssh myserver hg version -\end{codesample2} -If you see an error message instead of normal \hgcmd{version} output, -this is usually because you haven't installed Mercurial to -\dirname{/usr/bin}. Don't worry if this is the case; you don't need -to do that. But you should check for a few possible problems. -\begin{itemize} -\item Is Mercurial really installed on the server at all? I know this - sounds trivial, but it's worth checking! -\item Maybe your shell's search path (usually set via the \envar{PATH} - environment variable) is simply misconfigured. -\item Perhaps your \envar{PATH} environment variable is only being set - to point to the location of the \command{hg} executable if the login - session is interactive. This can happen if you're setting the path - in the wrong shell login script. See your shell's documentation for - details. -\item The \envar{PYTHONPATH} environment variable may need to contain - the path to the Mercurial Python modules. It might not be set at - all; it could be incorrect; or it may be set only if the login is - interactive. -\end{itemize} - -If you can run \hgcmd{version} over an ssh connection, well done! -You've got the server and client sorted out. You should now be able -to use Mercurial to access repositories hosted by that username on -that server. If you run into problems with Mercurial and ssh at this -point, try using the \hggopt{--debug} option to get a clearer picture -of what's going on. - -\subsection{Using compression with ssh} - -Mercurial does not compress data when it uses the ssh protocol, -because the ssh protocol can transparently compress data. However, -the default behaviour of ssh clients is \emph{not} to request -compression. - -Over any network other than a fast LAN (even a wireless network), -using compression is likely to significantly speed up Mercurial's -network operations. For example, over a WAN, someone measured -compression as reducing the amount of time required to clone a -particularly large repository from~51 minutes to~17 minutes. - -Both \command{ssh} and \command{plink} accept a \cmdopt{ssh}{-C} -option which turns on compression. You can easily edit your \hgrc\ to -enable compression for all of Mercurial's uses of the ssh protocol. -\begin{codesample2} - [ui] - ssh = ssh -C -\end{codesample2} - -If you use \command{ssh}, you can configure it to always use -compression when talking to your server. To do this, edit your -\sfilename{.ssh/config} file (which may not yet exist), as follows. -\begin{codesample2} - Host hg - Compression yes - HostName hg.example.com -\end{codesample2} -This defines an alias, \texttt{hg}. When you use it on the -\command{ssh} command line or in a Mercurial \texttt{ssh}-protocol -URL, it will cause \command{ssh} to connect to \texttt{hg.example.com} -and use compression. This gives you both a shorter name to type and -compression, each of which is a good thing in its own right. - -\section{Serving over HTTP using CGI} -\label{sec:collab:cgi} - -Depending on how ambitious you are, configuring Mercurial's CGI -interface can take anything from a few moments to several hours. - -We'll begin with the simplest of examples, and work our way towards a -more complex configuration. Even for the most basic case, you're -almost certainly going to need to read and modify your web server's -configuration. - -\begin{note} - Configuring a web server is a complex, fiddly, and highly - system-dependent activity. I can't possibly give you instructions - that will cover anything like all of the cases you will encounter. - Please use your discretion and judgment in following the sections - below. Be prepared to make plenty of mistakes, and to spend a lot - of time reading your server's error logs. -\end{note} - -\subsection{Web server configuration checklist} - -Before you continue, do take a few moments to check a few aspects of -your system's setup. - -\begin{enumerate} -\item Do you have a web server installed at all? Mac OS X ships with - Apache, but many other systems may not have a web server installed. -\item If you have a web server installed, is it actually running? On - most systems, even if one is present, it will be disabled by - default. -\item Is your server configured to allow you to run CGI programs in - the directory where you plan to do so? Most servers default to - explicitly disabling the ability to run CGI programs. -\end{enumerate} - -If you don't have a web server installed, and don't have substantial -experience configuring Apache, you should consider using the -\texttt{lighttpd} web server instead of Apache. Apache has a -well-deserved reputation for baroque and confusing configuration. -While \texttt{lighttpd} is less capable in some ways than Apache, most -of these capabilities are not relevant to serving Mercurial -repositories. And \texttt{lighttpd} is undeniably \emph{much} easier -to get started with than Apache. - -\subsection{Basic CGI configuration} - -On Unix-like systems, it's common for users to have a subdirectory -named something like \dirname{public\_html} in their home directory, -from which they can serve up web pages. A file named \filename{foo} -in this directory will be accessible at a URL of the form -\texttt{http://www.example.com/\~{}username/foo}. - -To get started, find the \sfilename{hgweb.cgi} script that should be -present in your Mercurial installation. If you can't quickly find a -local copy on your system, simply download one from the master -Mercurial repository at -\url{http://www.selenic.com/repo/hg/raw-file/tip/hgweb.cgi}. - -You'll need to copy this script into your \dirname{public\_html} -directory, and ensure that it's executable. -\begin{codesample2} - cp .../hgweb.cgi ~/public_html - chmod 755 ~/public_html/hgweb.cgi -\end{codesample2} -The \texttt{755} argument to \command{chmod} is a little more general -than just making the script executable: it ensures that the script is -executable by anyone, and that ``group'' and ``other'' write -permissions are \emph{not} set. If you were to leave those write -permissions enabled, Apache's \texttt{suexec} subsystem would likely -refuse to execute the script. In fact, \texttt{suexec} also insists -that the \emph{directory} in which the script resides must not be -writable by others. -\begin{codesample2} - chmod 755 ~/public_html -\end{codesample2} - -\subsubsection{What could \emph{possibly} go wrong?} -\label{sec:collab:wtf} - -Once you've copied the CGI script into place, go into a web browser, -and try to open the URL \url{http://myhostname/~myuser/hgweb.cgi}, -\emph{but} brace yourself for instant failure. There's a high -probability that trying to visit this URL will fail, and there are -many possible reasons for this. In fact, you're likely to stumble -over almost every one of the possible errors below, so please read -carefully. The following are all of the problems I ran into on a -system running Fedora~7, with a fresh installation of Apache, and a -user account that I created specially to perform this exercise. - -Your web server may have per-user directories disabled. If you're -using Apache, search your config file for a \texttt{UserDir} -directive. If there's none present, per-user directories will be -disabled. If one exists, but its value is \texttt{disabled}, then -per-user directories will be disabled. Otherwise, the string after -\texttt{UserDir} gives the name of the subdirectory that Apache will -look in under your home directory, for example \dirname{public\_html}. - -Your file access permissions may be too restrictive. The web server -must be able to traverse your home directory and directories under -your \dirname{public\_html} directory, and read files under the latter -too. Here's a quick recipe to help you to make your permissions more -appropriate. -\begin{codesample2} - chmod 755 ~ - find ~/public_html -type d -print0 | xargs -0r chmod 755 - find ~/public_html -type f -print0 | xargs -0r chmod 644 -\end{codesample2} - -The other possibility with permissions is that you might get a -completely empty window when you try to load the script. In this -case, it's likely that your access permissions are \emph{too - permissive}. Apache's \texttt{suexec} subsystem won't execute a -script that's group-~or world-writable, for example. - -Your web server may be configured to disallow execution of CGI -programs in your per-user web directory. Here's Apache's -default per-user configuration from my Fedora system. -\begin{codesample2} - <Directory /home/*/public_html> - AllowOverride FileInfo AuthConfig Limit - Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec - <Limit GET POST OPTIONS> - Order allow,deny - Allow from all - </Limit> - <LimitExcept GET POST OPTIONS> - Order deny,allow - Deny from all - </LimitExcept> - </Directory> -\end{codesample2} -If you find a similar-looking \texttt{Directory} group in your Apache -configuration, the directive to look at inside it is \texttt{Options}. -Add \texttt{ExecCGI} to the end of this list if it's missing, and -restart the web server. - -If you find that Apache serves you the text of the CGI script instead -of executing it, you may need to either uncomment (if already present) -or add a directive like this. -\begin{codesample2} - AddHandler cgi-script .cgi -\end{codesample2} - -The next possibility is that you might be served with a colourful -Python backtrace claiming that it can't import a -\texttt{mercurial}-related module. This is actually progress! The -server is now capable of executing your CGI script. This error is -only likely to occur if you're running a private installation of -Mercurial, instead of a system-wide version. Remember that the web -server runs the CGI program without any of the environment variables -that you take for granted in an interactive session. If this error -happens to you, edit your copy of \sfilename{hgweb.cgi} and follow the -directions inside it to correctly set your \envar{PYTHONPATH} -environment variable. - -Finally, you are \emph{certain} to by served with another colourful -Python backtrace: this one will complain that it can't find -\dirname{/path/to/repository}. Edit your \sfilename{hgweb.cgi} script -and replace the \dirname{/path/to/repository} string with the complete -path to the repository you want to serve up. - -At this point, when you try to reload the page, you should be -presented with a nice HTML view of your repository's history. Whew! - -\subsubsection{Configuring lighttpd} - -To be exhaustive in my experiments, I tried configuring the -increasingly popular \texttt{lighttpd} web server to serve the same -repository as I described with Apache above. I had already overcome -all of the problems I outlined with Apache, many of which are not -server-specific. As a result, I was fairly sure that my file and -directory permissions were good, and that my \sfilename{hgweb.cgi} -script was properly edited. - -Once I had Apache running, getting \texttt{lighttpd} to serve the -repository was a snap (in other words, even if you're trying to use -\texttt{lighttpd}, you should read the Apache section). I first had -to edit the \texttt{mod\_access} section of its config file to enable -\texttt{mod\_cgi} and \texttt{mod\_userdir}, both of which were -disabled by default on my system. I then added a few lines to the end -of the config file, to configure these modules. -\begin{codesample2} - userdir.path = "public_html" - cgi.assign = ( ".cgi" => "" ) -\end{codesample2} -With this done, \texttt{lighttpd} ran immediately for me. If I had -configured \texttt{lighttpd} before Apache, I'd almost certainly have -run into many of the same system-level configuration problems as I did -with Apache. However, I found \texttt{lighttpd} to be noticeably -easier to configure than Apache, even though I've used Apache for over -a decade, and this was my first exposure to \texttt{lighttpd}. - -\subsection{Sharing multiple repositories with one CGI script} - -The \sfilename{hgweb.cgi} script only lets you publish a single -repository, which is an annoying restriction. If you want to publish -more than one without wracking yourself with multiple copies of the -same script, each with different names, a better choice is to use the -\sfilename{hgwebdir.cgi} script. - -The procedure to configure \sfilename{hgwebdir.cgi} is only a little -more involved than for \sfilename{hgweb.cgi}. First, you must obtain -a copy of the script. If you don't have one handy, you can download a -copy from the master Mercurial repository at -\url{http://www.selenic.com/repo/hg/raw-file/tip/hgwebdir.cgi}. - -You'll need to copy this script into your \dirname{public\_html} -directory, and ensure that it's executable. -\begin{codesample2} - cp .../hgwebdir.cgi ~/public_html - chmod 755 ~/public_html ~/public_html/hgwebdir.cgi -\end{codesample2} -With basic configuration out of the way, try to visit -\url{http://myhostname/~myuser/hgwebdir.cgi} in your browser. It -should display an empty list of repositories. If you get a blank -window or error message, try walking through the list of potential -problems in section~\ref{sec:collab:wtf}. - -The \sfilename{hgwebdir.cgi} script relies on an external -configuration file. By default, it searches for a file named -\sfilename{hgweb.config} in the same directory as itself. You'll need -to create this file, and make it world-readable. The format of the -file is similar to a Windows ``ini'' file, as understood by Python's -\texttt{ConfigParser}~\cite{web:configparser} module. - -The easiest way to configure \sfilename{hgwebdir.cgi} is with a -section named \texttt{collections}. This will automatically publish -\emph{every} repository under the directories you name. The section -should look like this: -\begin{codesample2} - [collections] - /my/root = /my/root -\end{codesample2} -Mercurial interprets this by looking at the directory name on the -\emph{right} hand side of the ``\texttt{=}'' sign; finding -repositories in that directory hierarchy; and using the text on the -\emph{left} to strip off matching text from the names it will actually -list in the web interface. The remaining component of a path after -this stripping has occurred is called a ``virtual path''. - -Given the example above, if we have a repository whose local path is -\dirname{/my/root/this/repo}, the CGI script will strip the leading -\dirname{/my/root} from the name, and publish the repository with a -virtual path of \dirname{this/repo}. If the base URL for our CGI -script is \url{http://myhostname/~myuser/hgwebdir.cgi}, the complete -URL for that repository will be -\url{http://myhostname/~myuser/hgwebdir.cgi/this/repo}. - -If we replace \dirname{/my/root} on the left hand side of this example -with \dirname{/my}, then \sfilename{hgwebdir.cgi} will only strip off -\dirname{/my} from the repository name, and will give us a virtual -path of \dirname{root/this/repo} instead of \dirname{this/repo}. - -The \sfilename{hgwebdir.cgi} script will recursively search each -directory listed in the \texttt{collections} section of its -configuration file, but it will \texttt{not} recurse into the -repositories it finds. - -The \texttt{collections} mechanism makes it easy to publish many -repositories in a ``fire and forget'' manner. You only need to set up -the CGI script and configuration file one time. Afterwards, you can -publish or unpublish a repository at any time by simply moving it -into, or out of, the directory hierarchy in which you've configured -\sfilename{hgwebdir.cgi} to look. - -\subsubsection{Explicitly specifying which repositories to publish} - -In addition to the \texttt{collections} mechanism, the -\sfilename{hgwebdir.cgi} script allows you to publish a specific list -of repositories. To do so, create a \texttt{paths} section, with -contents of the following form. -\begin{codesample2} - [paths] - repo1 = /my/path/to/some/repo - repo2 = /some/path/to/another -\end{codesample2} -In this case, the virtual path (the component that will appear in a -URL) is on the left hand side of each definition, while the path to -the repository is on the right. Notice that there does not need to be -any relationship between the virtual path you choose and the location -of a repository in your filesystem. - -If you wish, you can use both the \texttt{collections} and -\texttt{paths} mechanisms simultaneously in a single configuration -file. - -\begin{note} - If multiple repositories have the same virtual path, - \sfilename{hgwebdir.cgi} will not report an error. Instead, it will - behave unpredictably. -\end{note} - -\subsection{Downloading source archives} - -Mercurial's web interface lets users download an archive of any -revision. This archive will contain a snapshot of the working -directory as of that revision, but it will not contain a copy of the -repository data. - -By default, this feature is not enabled. To enable it, you'll need to -add an \rcitem{web}{allow\_archive} item to the \rcsection{web} -section of your \hgrc. - -\subsection{Web configuration options} - -Mercurial's web interfaces (the \hgcmd{serve} command, and the -\sfilename{hgweb.cgi} and \sfilename{hgwebdir.cgi} scripts) have a -number of configuration options that you can set. These belong in a -section named \rcsection{web}. -\begin{itemize} -\item[\rcitem{web}{allow\_archive}] Determines which (if any) archive - download mechanisms Mercurial supports. If you enable this - feature, users of the web interface will be able to download an - archive of whatever revision of a repository they are viewing. - To enable the archive feature, this item must take the form of a - sequence of words drawn from the list below. - \begin{itemize} - \item[\texttt{bz2}] A \command{tar} archive, compressed using - \texttt{bzip2} compression. This has the best compression ratio, - but uses the most CPU time on the server. - \item[\texttt{gz}] A \command{tar} archive, compressed using - \texttt{gzip} compression. - \item[\texttt{zip}] A \command{zip} archive, compressed using LZW - compression. This format has the worst compression ratio, but is - widely used in the Windows world. - \end{itemize} - If you provide an empty list, or don't have an - \rcitem{web}{allow\_archive} entry at all, this feature will be - disabled. Here is an example of how to enable all three supported - formats. - \begin{codesample4} - [web] - allow_archive = bz2 gz zip - \end{codesample4} -\item[\rcitem{web}{allowpull}] Boolean. Determines whether the web - interface allows remote users to \hgcmd{pull} and \hgcmd{clone} this - repository over~HTTP. If set to \texttt{no} or \texttt{false}, only - the ``human-oriented'' portion of the web interface is available. -\item[\rcitem{web}{contact}] String. A free-form (but preferably - brief) string identifying the person or group in charge of the - repository. This often contains the name and email address of a - person or mailing list. It often makes sense to place this entry in - a repository's own \sfilename{.hg/hgrc} file, but it can make sense - to use in a global \hgrc\ if every repository has a single - maintainer. -\item[\rcitem{web}{maxchanges}] Integer. The default maximum number - of changesets to display in a single page of output. -\item[\rcitem{web}{maxfiles}] Integer. The default maximum number - of modified files to display in a single page of output. -\item[\rcitem{web}{stripes}] Integer. If the web interface displays - alternating ``stripes'' to make it easier to visually align rows - when you are looking at a table, this number controls the number of - rows in each stripe. -\item[\rcitem{web}{style}] Controls the template Mercurial uses to - display the web interface. Mercurial ships with two web templates, - named \texttt{default} and \texttt{gitweb} (the latter is much more - visually attractive). You can also specify a custom template of - your own; see chapter~\ref{chap:template} for details. Here, you - can see how to enable the \texttt{gitweb} style. - \begin{codesample4} - [web] - style = gitweb - \end{codesample4} -\item[\rcitem{web}{templates}] Path. The directory in which to search - for template files. By default, Mercurial searches in the directory - in which it was installed. -\end{itemize} -If you are using \sfilename{hgwebdir.cgi}, you can place a few -configuration items in a \rcsection{web} section of the -\sfilename{hgweb.config} file instead of a \hgrc\ file, for -convenience. These items are \rcitem{web}{motd} and -\rcitem{web}{style}. - -\subsubsection{Options specific to an individual repository} - -A few \rcsection{web} configuration items ought to be placed in a -repository's local \sfilename{.hg/hgrc}, rather than a user's or -global \hgrc. -\begin{itemize} -\item[\rcitem{web}{description}] String. A free-form (but preferably - brief) string that describes the contents or purpose of the - repository. -\item[\rcitem{web}{name}] String. The name to use for the repository - in the web interface. This overrides the default name, which is the - last component of the repository's path. -\end{itemize} - -\subsubsection{Options specific to the \hgcmd{serve} command} - -Some of the items in the \rcsection{web} section of a \hgrc\ file are -only for use with the \hgcmd{serve} command. -\begin{itemize} -\item[\rcitem{web}{accesslog}] Path. The name of a file into which to - write an access log. By default, the \hgcmd{serve} command writes - this information to standard output, not to a file. Log entries are - written in the standard ``combined'' file format used by almost all - web servers. -\item[\rcitem{web}{address}] String. The local address on which the - server should listen for incoming connections. By default, the - server listens on all addresses. -\item[\rcitem{web}{errorlog}] Path. The name of a file into which to - write an error log. By default, the \hgcmd{serve} command writes this - information to standard error, not to a file. -\item[\rcitem{web}{ipv6}] Boolean. Whether to use the IPv6 protocol. - By default, IPv6 is not used. -\item[\rcitem{web}{port}] Integer. The TCP~port number on which the - server should listen. The default port number used is~8000. -\end{itemize} - -\subsubsection{Choosing the right \hgrc\ file to add \rcsection{web} - items to} - -It is important to remember that a web server like Apache or -\texttt{lighttpd} will run under a user~ID that is different to yours. -CGI scripts run by your server, such as \sfilename{hgweb.cgi}, will -usually also run under that user~ID. - -If you add \rcsection{web} items to your own personal \hgrc\ file, CGI -scripts won't read that \hgrc\ file. Those settings will thus only -affect the behaviour of the \hgcmd{serve} command when you run it. To -cause CGI scripts to see your settings, either create a \hgrc\ file in -the home directory of the user ID that runs your web server, or add -those settings to a system-wide \hgrc\ file. - - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "00book" -%%% End:
--- a/en/concepts.tex Wed Jan 21 14:16:38 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,577 +0,0 @@ -\chapter{Behind the scenes} -\label{chap:concepts} - -Unlike many revision control systems, the concepts upon which -Mercurial is built are simple enough that it's easy to understand how -the software really works. Knowing this certainly isn't necessary, -but I find it useful to have a ``mental model'' of what's going on. - -This understanding gives me confidence that Mercurial has been -carefully designed to be both \emph{safe} and \emph{efficient}. And -just as importantly, if it's easy for me to retain a good idea of what -the software is doing when I perform a revision control task, I'm less -likely to be surprised by its behaviour. - -In this chapter, we'll initially cover the core concepts behind -Mercurial's design, then continue to discuss some of the interesting -details of its implementation. - -\section{Mercurial's historical record} - -\subsection{Tracking the history of a single file} - -When Mercurial tracks modifications to a file, it stores the history -of that file in a metadata object called a \emph{filelog}. Each entry -in the filelog contains enough information to reconstruct one revision -of the file that is being tracked. Filelogs are stored as files in -the \sdirname{.hg/store/data} directory. A filelog contains two kinds -of information: revision data, and an index to help Mercurial to find -a revision efficiently. - -A file that is large, or has a lot of history, has its filelog stored -in separate data (``\texttt{.d}'' suffix) and index (``\texttt{.i}'' -suffix) files. For small files without much history, the revision -data and index are combined in a single ``\texttt{.i}'' file. The -correspondence between a file in the working directory and the filelog -that tracks its history in the repository is illustrated in -figure~\ref{fig:concepts:filelog}. - -\begin{figure}[ht] - \centering - \grafix{filelog} - \caption{Relationships between files in working directory and - filelogs in repository} - \label{fig:concepts:filelog} -\end{figure} - -\subsection{Managing tracked files} - -Mercurial uses a structure called a \emph{manifest} to collect -together information about the files that it tracks. Each entry in -the manifest contains information about the files present in a single -changeset. An entry records which files are present in the changeset, -the revision of each file, and a few other pieces of file metadata. - -\subsection{Recording changeset information} - -The \emph{changelog} contains information about each changeset. Each -revision records who committed a change, the changeset comment, other -pieces of changeset-related information, and the revision of the -manifest to use. - -\subsection{Relationships between revisions} - -Within a changelog, a manifest, or a filelog, each revision stores a -pointer to its immediate parent (or to its two parents, if it's a -merge revision). As I mentioned above, there are also relationships -between revisions \emph{across} these structures, and they are -hierarchical in nature. - -For every changeset in a repository, there is exactly one revision -stored in the changelog. Each revision of the changelog contains a -pointer to a single revision of the manifest. A revision of the -manifest stores a pointer to a single revision of each filelog tracked -when that changeset was created. These relationships are illustrated -in figure~\ref{fig:concepts:metadata}. - -\begin{figure}[ht] - \centering - \grafix{metadata} - \caption{Metadata relationships} - \label{fig:concepts:metadata} -\end{figure} - -As the illustration shows, there is \emph{not} a ``one to one'' -relationship between revisions in the changelog, manifest, or filelog. -If the manifest hasn't changed between two changesets, the changelog -entries for those changesets will point to the same revision of the -manifest. If a file that Mercurial tracks hasn't changed between two -changesets, the entry for that file in the two revisions of the -manifest will point to the same revision of its filelog. - -\section{Safe, efficient storage} - -The underpinnings of changelogs, manifests, and filelogs are provided -by a single structure called the \emph{revlog}. - -\subsection{Efficient storage} - -The revlog provides efficient storage of revisions using a -\emph{delta} mechanism. Instead of storing a complete copy of a file -for each revision, it stores the changes needed to transform an older -revision into the new revision. For many kinds of file data, these -deltas are typically a fraction of a percent of the size of a full -copy of a file. - -Some obsolete revision control systems can only work with deltas of -text files. They must either store binary files as complete snapshots -or encoded into a text representation, both of which are wasteful -approaches. Mercurial can efficiently handle deltas of files with -arbitrary binary contents; it doesn't need to treat text as special. - -\subsection{Safe operation} -\label{sec:concepts:txn} - -Mercurial only ever \emph{appends} data to the end of a revlog file. -It never modifies a section of a file after it has written it. This -is both more robust and efficient than schemes that need to modify or -rewrite data. - -In addition, Mercurial treats every write as part of a -\emph{transaction} that can span a number of files. A transaction is -\emph{atomic}: either the entire transaction succeeds and its effects -are all visible to readers in one go, or the whole thing is undone. -This guarantee of atomicity means that if you're running two copies of -Mercurial, where one is reading data and one is writing it, the reader -will never see a partially written result that might confuse it. - -The fact that Mercurial only appends to files makes it easier to -provide this transactional guarantee. The easier it is to do stuff -like this, the more confident you should be that it's done correctly. - -\subsection{Fast retrieval} - -Mercurial cleverly avoids a pitfall common to all earlier -revision control systems: the problem of \emph{inefficient retrieval}. -Most revision control systems store the contents of a revision as an -incremental series of modifications against a ``snapshot''. To -reconstruct a specific revision, you must first read the snapshot, and -then every one of the revisions between the snapshot and your target -revision. The more history that a file accumulates, the more -revisions you must read, hence the longer it takes to reconstruct a -particular revision. - -\begin{figure}[ht] - \centering - \grafix{snapshot} - \caption{Snapshot of a revlog, with incremental deltas} - \label{fig:concepts:snapshot} -\end{figure} - -The innovation that Mercurial applies to this problem is simple but -effective. Once the cumulative amount of delta information stored -since the last snapshot exceeds a fixed threshold, it stores a new -snapshot (compressed, of course), instead of another delta. This -makes it possible to reconstruct \emph{any} revision of a file -quickly. This approach works so well that it has since been copied by -several other revision control systems. - -Figure~\ref{fig:concepts:snapshot} illustrates the idea. In an entry -in a revlog's index file, Mercurial stores the range of entries from -the data file that it must read to reconstruct a particular revision. - -\subsubsection{Aside: the influence of video compression} - -If you're familiar with video compression or have ever watched a TV -feed through a digital cable or satellite service, you may know that -most video compression schemes store each frame of video as a delta -against its predecessor frame. In addition, these schemes use -``lossy'' compression techniques to increase the compression ratio, so -visual errors accumulate over the course of a number of inter-frame -deltas. - -Because it's possible for a video stream to ``drop out'' occasionally -due to signal glitches, and to limit the accumulation of artefacts -introduced by the lossy compression process, video encoders -periodically insert a complete frame (called a ``key frame'') into the -video stream; the next delta is generated against that frame. This -means that if the video signal gets interrupted, it will resume once -the next key frame is received. Also, the accumulation of encoding -errors restarts anew with each key frame. - -\subsection{Identification and strong integrity} - -Along with delta or snapshot information, a revlog entry contains a -cryptographic hash of the data that it represents. This makes it -difficult to forge the contents of a revision, and easy to detect -accidental corruption. - -Hashes provide more than a mere check against corruption; they are -used as the identifiers for revisions. The changeset identification -hashes that you see as an end user are from revisions of the -changelog. Although filelogs and the manifest also use hashes, -Mercurial only uses these behind the scenes. - -Mercurial verifies that hashes are correct when it retrieves file -revisions and when it pulls changes from another repository. If it -encounters an integrity problem, it will complain and stop whatever -it's doing. - -In addition to the effect it has on retrieval efficiency, Mercurial's -use of periodic snapshots makes it more robust against partial data -corruption. If a revlog becomes partly corrupted due to a hardware -error or system bug, it's often possible to reconstruct some or most -revisions from the uncorrupted sections of the revlog, both before and -after the corrupted section. This would not be possible with a -delta-only storage model. - -\section{Revision history, branching, - and merging} - -Every entry in a Mercurial revlog knows the identity of its immediate -ancestor revision, usually referred to as its \emph{parent}. In fact, -a revision contains room for not one parent, but two. Mercurial uses -a special hash, called the ``null ID'', to represent the idea ``there -is no parent here''. This hash is simply a string of zeroes. - -In figure~\ref{fig:concepts:revlog}, you can see an example of the -conceptual structure of a revlog. Filelogs, manifests, and changelogs -all have this same structure; they differ only in the kind of data -stored in each delta or snapshot. - -The first revision in a revlog (at the bottom of the image) has the -null ID in both of its parent slots. For a ``normal'' revision, its -first parent slot contains the ID of its parent revision, and its -second contains the null ID, indicating that the revision has only one -real parent. Any two revisions that have the same parent ID are -branches. A revision that represents a merge between branches has two -normal revision IDs in its parent slots. - -\begin{figure}[ht] - \centering - \grafix{revlog} - \caption{} - \label{fig:concepts:revlog} -\end{figure} - -\section{The working directory} - -In the working directory, Mercurial stores a snapshot of the files -from the repository as of a particular changeset. - -The working directory ``knows'' which changeset it contains. When you -update the working directory to contain a particular changeset, -Mercurial looks up the appropriate revision of the manifest to find -out which files it was tracking at the time that changeset was -committed, and which revision of each file was then current. It then -recreates a copy of each of those files, with the same contents it had -when the changeset was committed. - -The \emph{dirstate} contains Mercurial's knowledge of the working -directory. This details which changeset the working directory is -updated to, and all of the files that Mercurial is tracking in the -working directory. - -Just as a revision of a revlog has room for two parents, so that it -can represent either a normal revision (with one parent) or a merge of -two earlier revisions, the dirstate has slots for two parents. When -you use the \hgcmd{update} command, the changeset that you update to -is stored in the ``first parent'' slot, and the null ID in the second. -When you \hgcmd{merge} with another changeset, the first parent -remains unchanged, and the second parent is filled in with the -changeset you're merging with. The \hgcmd{parents} command tells you -what the parents of the dirstate are. - -\subsection{What happens when you commit} - -The dirstate stores parent information for more than just book-keeping -purposes. Mercurial uses the parents of the dirstate as \emph{the - parents of a new changeset} when you perform a commit. - -\begin{figure}[ht] - \centering - \grafix{wdir} - \caption{The working directory can have two parents} - \label{fig:concepts:wdir} -\end{figure} - -Figure~\ref{fig:concepts:wdir} shows the normal state of the working -directory, where it has a single changeset as parent. That changeset -is the \emph{tip}, the newest changeset in the repository that has no -children. - -\begin{figure}[ht] - \centering - \grafix{wdir-after-commit} - \caption{The working directory gains new parents after a commit} - \label{fig:concepts:wdir-after-commit} -\end{figure} - -It's useful to think of the working directory as ``the changeset I'm -about to commit''. Any files that you tell Mercurial that you've -added, removed, renamed, or copied will be reflected in that -changeset, as will modifications to any files that Mercurial is -already tracking; the new changeset will have the parents of the -working directory as its parents. - -After a commit, Mercurial will update the parents of the working -directory, so that the first parent is the ID of the new changeset, -and the second is the null ID. This is shown in -figure~\ref{fig:concepts:wdir-after-commit}. Mercurial doesn't touch -any of the files in the working directory when you commit; it just -modifies the dirstate to note its new parents. - -\subsection{Creating a new head} - -It's perfectly normal to update the working directory to a changeset -other than the current tip. For example, you might want to know what -your project looked like last Tuesday, or you could be looking through -changesets to see which one introduced a bug. In cases like this, the -natural thing to do is update the working directory to the changeset -you're interested in, and then examine the files in the working -directory directly to see their contents as they were when you -committed that changeset. The effect of this is shown in -figure~\ref{fig:concepts:wdir-pre-branch}. - -\begin{figure}[ht] - \centering - \grafix{wdir-pre-branch} - \caption{The working directory, updated to an older changeset} - \label{fig:concepts:wdir-pre-branch} -\end{figure} - -Having updated the working directory to an older changeset, what -happens if you make some changes, and then commit? Mercurial behaves -in the same way as I outlined above. The parents of the working -directory become the parents of the new changeset. This new changeset -has no children, so it becomes the new tip. And the repository now -contains two changesets that have no children; we call these -\emph{heads}. You can see the structure that this creates in -figure~\ref{fig:concepts:wdir-branch}. - -\begin{figure}[ht] - \centering - \grafix{wdir-branch} - \caption{After a commit made while synced to an older changeset} - \label{fig:concepts:wdir-branch} -\end{figure} - -\begin{note} - If you're new to Mercurial, you should keep in mind a common - ``error'', which is to use the \hgcmd{pull} command without any - options. By default, the \hgcmd{pull} command \emph{does not} - update the working directory, so you'll bring new changesets into - your repository, but the working directory will stay synced at the - same changeset as before the pull. If you make some changes and - commit afterwards, you'll thus create a new head, because your - working directory isn't synced to whatever the current tip is. - - I put the word ``error'' in quotes because all that you need to do - to rectify this situation is \hgcmd{merge}, then \hgcmd{commit}. In - other words, this almost never has negative consequences; it just - surprises people. I'll discuss other ways to avoid this behaviour, - and why Mercurial behaves in this initially surprising way, later - on. -\end{note} - -\subsection{Merging heads} - -When you run the \hgcmd{merge} command, Mercurial leaves the first -parent of the working directory unchanged, and sets the second parent -to the changeset you're merging with, as shown in -figure~\ref{fig:concepts:wdir-merge}. - -\begin{figure}[ht] - \centering - \grafix{wdir-merge} - \caption{Merging two heads} - \label{fig:concepts:wdir-merge} -\end{figure} - -Mercurial also has to modify the working directory, to merge the files -managed in the two changesets. Simplified a little, the merging -process goes like this, for every file in the manifests of both -changesets. -\begin{itemize} -\item If neither changeset has modified a file, do nothing with that - file. -\item If one changeset has modified a file, and the other hasn't, - create the modified copy of the file in the working directory. -\item If one changeset has removed a file, and the other hasn't (or - has also deleted it), delete the file from the working directory. -\item If one changeset has removed a file, but the other has modified - the file, ask the user what to do: keep the modified file, or remove - it? -\item If both changesets have modified a file, invoke an external - merge program to choose the new contents for the merged file. This - may require input from the user. -\item If one changeset has modified a file, and the other has renamed - or copied the file, make sure that the changes follow the new name - of the file. -\end{itemize} -There are more details---merging has plenty of corner cases---but -these are the most common choices that are involved in a merge. As -you can see, most cases are completely automatic, and indeed most -merges finish automatically, without requiring your input to resolve -any conflicts. - -When you're thinking about what happens when you commit after a merge, -once again the working directory is ``the changeset I'm about to -commit''. After the \hgcmd{merge} command completes, the working -directory has two parents; these will become the parents of the new -changeset. - -Mercurial lets you perform multiple merges, but you must commit the -results of each individual merge as you go. This is necessary because -Mercurial only tracks two parents for both revisions and the working -directory. While it would be technically possible to merge multiple -changesets at once, the prospect of user confusion and making a -terrible mess of a merge immediately becomes overwhelming. - -\section{Other interesting design features} - -In the sections above, I've tried to highlight some of the most -important aspects of Mercurial's design, to illustrate that it pays -careful attention to reliability and performance. However, the -attention to detail doesn't stop there. There are a number of other -aspects of Mercurial's construction that I personally find -interesting. I'll detail a few of them here, separate from the ``big -ticket'' items above, so that if you're interested, you can gain a -better idea of the amount of thinking that goes into a well-designed -system. - -\subsection{Clever compression} - -When appropriate, Mercurial will store both snapshots and deltas in -compressed form. It does this by always \emph{trying to} compress a -snapshot or delta, but only storing the compressed version if it's -smaller than the uncompressed version. - -This means that Mercurial does ``the right thing'' when storing a file -whose native form is compressed, such as a \texttt{zip} archive or a -JPEG image. When these types of files are compressed a second time, -the resulting file is usually bigger than the once-compressed form, -and so Mercurial will store the plain \texttt{zip} or JPEG. - -Deltas between revisions of a compressed file are usually larger than -snapshots of the file, and Mercurial again does ``the right thing'' in -these cases. It finds that such a delta exceeds the threshold at -which it should store a complete snapshot of the file, so it stores -the snapshot, again saving space compared to a naive delta-only -approach. - -\subsubsection{Network recompression} - -When storing revisions on disk, Mercurial uses the ``deflate'' -compression algorithm (the same one used by the popular \texttt{zip} -archive format), which balances good speed with a respectable -compression ratio. However, when transmitting revision data over a -network connection, Mercurial uncompresses the compressed revision -data. - -If the connection is over HTTP, Mercurial recompresses the entire -stream of data using a compression algorithm that gives a better -compression ratio (the Burrows-Wheeler algorithm from the widely used -\texttt{bzip2} compression package). This combination of algorithm -and compression of the entire stream (instead of a revision at a time) -substantially reduces the number of bytes to be transferred, yielding -better network performance over almost all kinds of network. - -(If the connection is over \command{ssh}, Mercurial \emph{doesn't} -recompress the stream, because \command{ssh} can already do this -itself.) - -\subsection{Read/write ordering and atomicity} - -Appending to files isn't the whole story when it comes to guaranteeing -that a reader won't see a partial write. If you recall -figure~\ref{fig:concepts:metadata}, revisions in the changelog point to -revisions in the manifest, and revisions in the manifest point to -revisions in filelogs. This hierarchy is deliberate. - -A writer starts a transaction by writing filelog and manifest data, -and doesn't write any changelog data until those are finished. A -reader starts by reading changelog data, then manifest data, followed -by filelog data. - -Since the writer has always finished writing filelog and manifest data -before it writes to the changelog, a reader will never read a pointer -to a partially written manifest revision from the changelog, and it will -never read a pointer to a partially written filelog revision from the -manifest. - -\subsection{Concurrent access} - -The read/write ordering and atomicity guarantees mean that Mercurial -never needs to \emph{lock} a repository when it's reading data, even -if the repository is being written to while the read is occurring. -This has a big effect on scalability; you can have an arbitrary number -of Mercurial processes safely reading data from a repository safely -all at once, no matter whether it's being written to or not. - -The lockless nature of reading means that if you're sharing a -repository on a multi-user system, you don't need to grant other local -users permission to \emph{write} to your repository in order for them -to be able to clone it or pull changes from it; they only need -\emph{read} permission. (This is \emph{not} a common feature among -revision control systems, so don't take it for granted! Most require -readers to be able to lock a repository to access it safely, and this -requires write permission on at least one directory, which of course -makes for all kinds of nasty and annoying security and administrative -problems.) - -Mercurial uses locks to ensure that only one process can write to a -repository at a time (the locking mechanism is safe even over -filesystems that are notoriously hostile to locking, such as NFS). If -a repository is locked, a writer will wait for a while to retry if the -repository becomes unlocked, but if the repository remains locked for -too long, the process attempting to write will time out after a while. -This means that your daily automated scripts won't get stuck forever -and pile up if a system crashes unnoticed, for example. (Yes, the -timeout is configurable, from zero to infinity.) - -\subsubsection{Safe dirstate access} - -As with revision data, Mercurial doesn't take a lock to read the -dirstate file; it does acquire a lock to write it. To avoid the -possibility of reading a partially written copy of the dirstate file, -Mercurial writes to a file with a unique name in the same directory as -the dirstate file, then renames the temporary file atomically to -\filename{dirstate}. The file named \filename{dirstate} is thus -guaranteed to be complete, not partially written. - -\subsection{Avoiding seeks} - -Critical to Mercurial's performance is the avoidance of seeks of the -disk head, since any seek is far more expensive than even a -comparatively large read operation. - -This is why, for example, the dirstate is stored in a single file. If -there were a dirstate file per directory that Mercurial tracked, the -disk would seek once per directory. Instead, Mercurial reads the -entire single dirstate file in one step. - -Mercurial also uses a ``copy on write'' scheme when cloning a -repository on local storage. Instead of copying every revlog file -from the old repository into the new repository, it makes a ``hard -link'', which is a shorthand way to say ``these two names point to the -same file''. When Mercurial is about to write to one of a revlog's -files, it checks to see if the number of names pointing at the file is -greater than one. If it is, more than one repository is using the -file, so Mercurial makes a new copy of the file that is private to -this repository. - -A few revision control developers have pointed out that this idea of -making a complete private copy of a file is not very efficient in its -use of storage. While this is true, storage is cheap, and this method -gives the highest performance while deferring most book-keeping to the -operating system. An alternative scheme would most likely reduce -performance and increase the complexity of the software, each of which -is much more important to the ``feel'' of day-to-day use. - -\subsection{Other contents of the dirstate} - -Because Mercurial doesn't force you to tell it when you're modifying a -file, it uses the dirstate to store some extra information so it can -determine efficiently whether you have modified a file. For each file -in the working directory, it stores the time that it last modified the -file itself, and the size of the file at that time. - -When you explicitly \hgcmd{add}, \hgcmd{remove}, \hgcmd{rename} or -\hgcmd{copy} files, Mercurial updates the dirstate so that it knows -what to do with those files when you commit. - -When Mercurial is checking the states of files in the working -directory, it first checks a file's modification time. If that has -not changed, the file must not have been modified. If the file's size -has changed, the file must have been modified. If the modification -time has changed, but the size has not, only then does Mercurial need -to read the actual contents of the file to see if they've changed. -Storing these few extra pieces of information dramatically reduces the -amount of data that Mercurial needs to read, which yields large -performance improvements compared to other revision control systems. - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "00book" -%%% End:
--- a/en/daily.tex Wed Jan 21 14:16:38 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,381 +0,0 @@ -\chapter{Mercurial in daily use} -\label{chap:daily} - -\section{Telling Mercurial which files to track} - -Mercurial does not work with files in your repository unless you tell -it to manage them. The \hgcmd{status} command will tell you which -files Mercurial doesn't know about; it uses a ``\texttt{?}'' to -display such files. - -To tell Mercurial to track a file, use the \hgcmd{add} command. Once -you have added a file, the entry in the output of \hgcmd{status} for -that file changes from ``\texttt{?}'' to ``\texttt{A}''. -\interaction{daily.files.add} - -After you run a \hgcmd{commit}, the files that you added before the -commit will no longer be listed in the output of \hgcmd{status}. The -reason for this is that \hgcmd{status} only tells you about -``interesting'' files---those that you have modified or told Mercurial -to do something with---by default. If you have a repository that -contains thousands of files, you will rarely want to know about files -that Mercurial is tracking, but that have not changed. (You can still -get this information; we'll return to this later.) - -Once you add a file, Mercurial doesn't do anything with it -immediately. Instead, it will take a snapshot of the file's state the -next time you perform a commit. It will then continue to track the -changes you make to the file every time you commit, until you remove -the file. - -\subsection{Explicit versus implicit file naming} - -A useful behaviour that Mercurial has is that if you pass the name of -a directory to a command, every Mercurial command will treat this as -``I want to operate on every file in this directory and its -subdirectories''. -\interaction{daily.files.add-dir} -Notice in this example that Mercurial printed the names of the files -it added, whereas it didn't do so when we added the file named -\filename{a} in the earlier example. - -What's going on is that in the former case, we explicitly named the -file to add on the command line, so the assumption that Mercurial -makes in such cases is that you know what you were doing, and it -doesn't print any output. - -However, when we \emph{imply} the names of files by giving the name of -a directory, Mercurial takes the extra step of printing the name of -each file that it does something with. This makes it more clear what -is happening, and reduces the likelihood of a silent and nasty -surprise. This behaviour is common to most Mercurial commands. - -\subsection{Aside: Mercurial tracks files, not directories} - -Mercurial does not track directory information. Instead, it tracks -the path to a file. Before creating a file, it first creates any -missing directory components of the path. After it deletes a file, it -then deletes any empty directories that were in the deleted file's -path. This sounds like a trivial distinction, but it has one minor -practical consequence: it is not possible to represent a completely -empty directory in Mercurial. - -Empty directories are rarely useful, and there are unintrusive -workarounds that you can use to achieve an appropriate effect. The -developers of Mercurial thus felt that the complexity that would be -required to manage empty directories was not worth the limited benefit -this feature would bring. - -If you need an empty directory in your repository, there are a few -ways to achieve this. One is to create a directory, then \hgcmd{add} a -``hidden'' file to that directory. On Unix-like systems, any file -name that begins with a period (``\texttt{.}'') is treated as hidden -by most commands and GUI tools. This approach is illustrated in -figure~\ref{ex:daily:hidden}. - -\begin{figure}[ht] - \interaction{daily.files.hidden} - \caption{Simulating an empty directory using a hidden file} - \label{ex:daily:hidden} -\end{figure} - -Another way to tackle a need for an empty directory is to simply -create one in your automated build scripts before they will need it. - -\section{How to stop tracking a file} - -Once you decide that a file no longer belongs in your repository, use -the \hgcmd{remove} command; this deletes the file, and tells Mercurial -to stop tracking it. A removed file is represented in the output of -\hgcmd{status} with a ``\texttt{R}''. -\interaction{daily.files.remove} - -After you \hgcmd{remove} a file, Mercurial will no longer track -changes to that file, even if you recreate a file with the same name -in your working directory. If you do recreate a file with the same -name and want Mercurial to track the new file, simply \hgcmd{add} it. -Mercurial will know that the newly added file is not related to the -old file of the same name. - -\subsection{Removing a file does not affect its history} - -It is important to understand that removing a file has only two -effects. -\begin{itemize} -\item It removes the current version of the file from the working - directory. -\item It stops Mercurial from tracking changes to the file, from the - time of the next commit. -\end{itemize} -Removing a file \emph{does not} in any way alter the \emph{history} of -the file. - -If you update the working directory to a changeset in which a file -that you have removed was still tracked, it will reappear in the -working directory, with the contents it had when you committed that -changeset. If you then update the working directory to a later -changeset, in which the file had been removed, Mercurial will once -again remove the file from the working directory. - -\subsection{Missing files} - -Mercurial considers a file that you have deleted, but not used -\hgcmd{remove} to delete, to be \emph{missing}. A missing file is -represented with ``\texttt{!}'' in the output of \hgcmd{status}. -Mercurial commands will not generally do anything with missing files. -\interaction{daily.files.missing} - -If your repository contains a file that \hgcmd{status} reports as -missing, and you want the file to stay gone, you can run -\hgcmdargs{remove}{\hgopt{remove}{--after}} at any time later on, to -tell Mercurial that you really did mean to remove the file. -\interaction{daily.files.remove-after} - -On the other hand, if you deleted the missing file by accident, use -\hgcmdargs{revert}{\emph{filename}} to recover the file. It will -reappear, in unmodified form. -\interaction{daily.files.recover-missing} - -\subsection{Aside: why tell Mercurial explicitly to - remove a file?} - -You might wonder why Mercurial requires you to explicitly tell it that -you are deleting a file. Early during the development of Mercurial, -it let you delete a file however you pleased; Mercurial would notice -the absence of the file automatically when you next ran a -\hgcmd{commit}, and stop tracking the file. In practice, this made it -too easy to accidentally remove a file without noticing. - -\subsection{Useful shorthand---adding and removing files - in one step} - -Mercurial offers a combination command, \hgcmd{addremove}, that adds -untracked files and marks missing files as removed. -\interaction{daily.files.addremove} -The \hgcmd{commit} command also provides a \hgopt{commit}{-A} option -that performs this same add-and-remove, immediately followed by a -commit. -\interaction{daily.files.commit-addremove} - -\section{Copying files} - -Mercurial provides a \hgcmd{copy} command that lets you make a new -copy of a file. When you copy a file using this command, Mercurial -makes a record of the fact that the new file is a copy of the original -file. It treats these copied files specially when you merge your work -with someone else's. - -\subsection{The results of copying during a merge} - -What happens during a merge is that changes ``follow'' a copy. To -best illustrate what this means, let's create an example. We'll start -with the usual tiny repository that contains a single file. -\interaction{daily.copy.init} -We need to do some work in parallel, so that we'll have something to -merge. So let's clone our repository. -\interaction{daily.copy.clone} -Back in our initial repository, let's use the \hgcmd{copy} command to -make a copy of the first file we created. -\interaction{daily.copy.copy} - -If we look at the output of the \hgcmd{status} command afterwards, the -copied file looks just like a normal added file. -\interaction{daily.copy.status} -But if we pass the \hgopt{status}{-C} option to \hgcmd{status}, it -prints another line of output: this is the file that our newly-added -file was copied \emph{from}. -\interaction{daily.copy.status-copy} - -Now, back in the repository we cloned, let's make a change in -parallel. We'll add a line of content to the original file that we -created. -\interaction{daily.copy.other} -Now we have a modified \filename{file} in this repository. When we -pull the changes from the first repository, and merge the two heads, -Mercurial will propagate the changes that we made locally to -\filename{file} into its copy, \filename{new-file}. -\interaction{daily.copy.merge} - -\subsection{Why should changes follow copies?} -\label{sec:daily:why-copy} - -This behaviour, of changes to a file propagating out to copies of the -file, might seem esoteric, but in most cases it's highly desirable. - -First of all, remember that this propagation \emph{only} happens when -you merge. So if you \hgcmd{copy} a file, and subsequently modify the -original file during the normal course of your work, nothing will -happen. - -The second thing to know is that modifications will only propagate -across a copy as long as the repository that you're pulling changes -from \emph{doesn't know} about the copy. - -The reason that Mercurial does this is as follows. Let's say I make -an important bug fix in a source file, and commit my changes. -Meanwhile, you've decided to \hgcmd{copy} the file in your repository, -without knowing about the bug or having seen the fix, and you have -started hacking on your copy of the file. - -If you pulled and merged my changes, and Mercurial \emph{didn't} -propagate changes across copies, your source file would now contain -the bug, and unless you remembered to propagate the bug fix by hand, -the bug would \emph{remain} in your copy of the file. - -By automatically propagating the change that fixed the bug from the -original file to the copy, Mercurial prevents this class of problem. -To my knowledge, Mercurial is the \emph{only} revision control system -that propagates changes across copies like this. - -Once your change history has a record that the copy and subsequent -merge occurred, there's usually no further need to propagate changes -from the original file to the copied file, and that's why Mercurial -only propagates changes across copies until this point, and no -further. - -\subsection{How to make changes \emph{not} follow a copy} - -If, for some reason, you decide that this business of automatically -propagating changes across copies is not for you, simply use your -system's normal file copy command (on Unix-like systems, that's -\command{cp}) to make a copy of a file, then \hgcmd{add} the new copy -by hand. Before you do so, though, please do reread -section~\ref{sec:daily:why-copy}, and make an informed decision that -this behaviour is not appropriate to your specific case. - -\subsection{Behaviour of the \hgcmd{copy} command} - -When you use the \hgcmd{copy} command, Mercurial makes a copy of each -source file as it currently stands in the working directory. This -means that if you make some modifications to a file, then \hgcmd{copy} -it without first having committed those changes, the new copy will -also contain the modifications you have made up until that point. (I -find this behaviour a little counterintuitive, which is why I mention -it here.) - -The \hgcmd{copy} command acts similarly to the Unix \command{cp} -command (you can use the \hgcmd{cp} alias if you prefer). The last -argument is the \emph{destination}, and all prior arguments are -\emph{sources}. If you pass it a single file as the source, and the -destination does not exist, it creates a new file with that name. -\interaction{daily.copy.simple} -If the destination is a directory, Mercurial copies its sources into -that directory. -\interaction{daily.copy.dir-dest} -Copying a directory is recursive, and preserves the directory -structure of the source. -\interaction{daily.copy.dir-src} -If the source and destination are both directories, the source tree is -recreated in the destination directory. -\interaction{daily.copy.dir-src-dest} - -As with the \hgcmd{rename} command, if you copy a file manually and -then want Mercurial to know that you've copied the file, simply use -the \hgopt{copy}{--after} option to \hgcmd{copy}. -\interaction{daily.copy.after} - -\section{Renaming files} - -It's rather more common to need to rename a file than to make a copy -of it. The reason I discussed the \hgcmd{copy} command before talking -about renaming files is that Mercurial treats a rename in essentially -the same way as a copy. Therefore, knowing what Mercurial does when -you copy a file tells you what to expect when you rename a file. - -When you use the \hgcmd{rename} command, Mercurial makes a copy of -each source file, then deletes it and marks the file as removed. -\interaction{daily.rename.rename} -The \hgcmd{status} command shows the newly copied file as added, and -the copied-from file as removed. -\interaction{daily.rename.status} -As with the results of a \hgcmd{copy}, we must use the -\hgopt{status}{-C} option to \hgcmd{status} to see that the added file -is really being tracked by Mercurial as a copy of the original, now -removed, file. -\interaction{daily.rename.status-copy} - -As with \hgcmd{remove} and \hgcmd{copy}, you can tell Mercurial about -a rename after the fact using the \hgopt{rename}{--after} option. In -most other respects, the behaviour of the \hgcmd{rename} command, and -the options it accepts, are similar to the \hgcmd{copy} command. - -\subsection{Renaming files and merging changes} - -Since Mercurial's rename is implemented as copy-and-remove, the same -propagation of changes happens when you merge after a rename as after -a copy. - -If I modify a file, and you rename it to a new name, and then we merge -our respective changes, my modifications to the file under its -original name will be propagated into the file under its new name. -(This is something you might expect to ``simply work,'' but not all -revision control systems actually do this.) - -Whereas having changes follow a copy is a feature where you can -perhaps nod and say ``yes, that might be useful,'' it should be clear -that having them follow a rename is definitely important. Without -this facility, it would simply be too easy for changes to become -orphaned when files are renamed. - -\subsection{Divergent renames and merging} - -The case of diverging names occurs when two developers start with a -file---let's call it \filename{foo}---in their respective -repositories. - -\interaction{rename.divergent.clone} -Anne renames the file to \filename{bar}. -\interaction{rename.divergent.rename.anne} -Meanwhile, Bob renames it to \filename{quux}. -\interaction{rename.divergent.rename.bob} - -I like to think of this as a conflict because each developer has -expressed different intentions about what the file ought to be named. - -What do you think should happen when they merge their work? -Mercurial's actual behaviour is that it always preserves \emph{both} -names when it merges changesets that contain divergent renames. -\interaction{rename.divergent.merge} - -Notice that Mercurial does warn about the divergent renames, but it -leaves it up to you to do something about the divergence after the merge. - -\subsection{Convergent renames and merging} - -Another kind of rename conflict occurs when two people choose to -rename different \emph{source} files to the same \emph{destination}. -In this case, Mercurial runs its normal merge machinery, and lets you -guide it to a suitable resolution. - -\subsection{Other name-related corner cases} - -Mercurial has a longstanding bug in which it fails to handle a merge -where one side has a file with a given name, while another has a -directory with the same name. This is documented as~\bug{29}. -\interaction{issue29.go} - -\section{Recovering from mistakes} - -Mercurial has some useful commands that will help you to recover from -some common mistakes. - -The \hgcmd{revert} command lets you undo changes that you have made to -your working directory. For example, if you \hgcmd{add} a file by -accident, just run \hgcmd{revert} with the name of the file you added, -and while the file won't be touched in any way, it won't be tracked -for adding by Mercurial any longer, either. You can also use -\hgcmd{revert} to get rid of erroneous changes to a file. - -It's useful to remember that the \hgcmd{revert} command is useful for -changes that you have not yet committed. Once you've committed a -change, if you decide it was a mistake, you can still do something -about it, though your options may be more limited. - -For more information about the \hgcmd{revert} command, and details -about how to deal with changes you have already committed, see -chapter~\ref{chap:undo}. - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "00book" -%%% End:
--- a/en/examples/backout.manual.log.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/backout.manual.log.out Mon Feb 09 22:59:50 2009 -0800 @@ -1,13 +1,13 @@ $ \textbf{hg log --style compact} -3[tip]:1 +3[tip]:1 2009-01-30 06:32 +0000 bos back out second change -2 +2 2009-01-30 06:32 +0000 bos third change -1 +1 2009-01-30 06:32 +0000 bos second change -0 +0 2009-01-30 06:32 +0000 bos first change
--- a/en/examples/backout.simple.log.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/backout.simple.log.out Mon Feb 09 22:59:50 2009 -0800 @@ -1,10 +1,10 @@ $ \textbf{hg log --style compact} -2[tip] +2[tip] 2009-01-30 06:32 +0000 bos back out second change -1 +1 2009-01-30 06:32 +0000 bos second change -0 +0 2009-01-30 06:32 +0000 bos first change
--- a/en/examples/bisect Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/bisect Mon Feb 09 22:59:50 2009 -0800 @@ -1,8 +1,11 @@ #!/bin/bash -# Don't add the bisect extension. That's not needed anymore. -#echo '[extensions]' >> $HGRC -#echo 'hbisect =' >> $HGRC +if hg -v | head -1 | grep -e "version 0.*" +then +#On mercurial 1.0 and later bisect is a builtin +echo '[extensions]' >> $HGRC +echo 'hbisect =' >> $HGRC +fi # XXX There's some kind of horrible nondeterminism in the execution of # bisect at the moment. Ugh. @@ -34,15 +37,15 @@ #$ name: search.init -hg bisect --init +hg bisect init #$ name: search.bad-init -hg bisect --bad +hg bisect bad #$ name: search.good-init -hg bisect --good 10 +hg bisect good 10 #$ name: search.step1 @@ -67,7 +70,7 @@ fi echo this revision is $result - hg bisect --$result + hg bisect $result } #$ name: search.step2 @@ -82,7 +85,7 @@ #$ name: search.reset -hg bisect --reset +hg bisect reset #$ name:
--- a/en/examples/bisect.commits.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/bisect.commits.out Mon Feb 09 22:59:50 2009 -0800 @@ -1,16 +1,3 @@ - - - - - - - - - - - - - @@ -21,25 +8,3 @@ - - - - - - - - - - - - - - - - - - - - - -
--- a/en/examples/bisect.help.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/bisect.help.out Mon Feb 09 22:59:50 2009 -0800 @@ -19,3 +19,15 @@ + + + + + + + + + + + +
--- a/en/examples/bisect.init.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/bisect.init.out Mon Feb 09 22:59:50 2009 -0800 @@ -1,3 +1,2 @@ -
--- a/en/examples/bisect.search.bad-init.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/bisect.search.bad-init.out Mon Feb 09 22:59:50 2009 -0800 @@ -1,22 +1,2 @@ - - - - - - - - - - - - - - - - - - - -
--- a/en/examples/bisect.search.good-init.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/bisect.search.good-init.out Mon Feb 09 22:59:50 2009 -0800 @@ -1,22 +1,4 @@ - - - - - - - - - - - - - - - - - -
--- a/en/examples/bisect.search.init.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/bisect.search.init.out Mon Feb 09 22:59:50 2009 -0800 @@ -1,22 +1,2 @@ - - - - - - - - - - - - - - - - - - - -
--- a/en/examples/bisect.search.reset.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/bisect.search.reset.out Mon Feb 09 22:59:50 2009 -0800 @@ -1,22 +1,2 @@ - - - - - - - - - - - - - - - - - - - -
--- a/en/examples/bisect.search.rest.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/bisect.search.rest.out Mon Feb 09 22:59:50 2009 -0800 @@ -1,19 +1,3 @@ - - - - - - - - - - - - - - - - @@ -33,37 +17,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--- a/en/examples/bisect.search.step1.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/bisect.search.step1.out Mon Feb 09 22:59:50 2009 -0800 @@ -1,7 +1,3 @@ - - - - @@ -13,18 +9,3 @@ - - - - - - - - - - - - - - -
--- a/en/examples/bisect.search.step2.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/bisect.search.step2.out Mon Feb 09 22:59:50 2009 -0800 @@ -1,23 +1,5 @@ - - - - - - - - - - - - - - - - - -
--- a/en/examples/branch-repo.bugfix.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/branch-repo.bugfix.out Mon Feb 09 22:59:50 2009 -0800 @@ -5,7 +5,7 @@ $ \textbf{echo 'I fixed a bug using only echo!' >> myfile} $ \textbf{hg commit -m 'Important fix for 1.0.1'} $ \textbf{hg push} -pushing to /tmp/branch-repo4rF-PL/myproject-1.0.1 +pushing to searching for changes adding changesets adding manifests
--- a/en/examples/branching.stable.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/branching.stable.out Mon Feb 09 22:59:50 2009 -0800 @@ -5,7 +5,7 @@ $ \textbf{echo 'This is a fix to a boring feature.' > myfile} $ \textbf{hg commit -m 'Fix a bug'} $ \textbf{hg push} -pushing to /tmp/branchingfJgZac/stable +pushing to searching for changes adding changesets adding manifests
--- a/en/examples/cmdref.diff-p.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/cmdref.diff-p.out Mon Feb 09 22:59:50 2009 -0800 @@ -14,7 +14,7 @@ diff -r myfile.c -@@ -1,4 +1,4 @@ int myfunc() +@@ -1,4 +1,4 @@ int myfunc() \{ - return 1;
--- a/en/examples/mq.guards.qselect.foo.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/mq.guards.qselect.foo.out Mon Feb 09 22:59:50 2009 -0800 @@ -1,5 +1,5 @@ $ \textbf{hg qpop -a} -Patch queue now empty +patch queue now empty $ \textbf{hg qselect} no active guards $ \textbf{hg qselect foo}
--- a/en/examples/mq.guards.qselect.foobar.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/mq.guards.qselect.foobar.out Mon Feb 09 22:59:50 2009 -0800 @@ -5,4 +5,4 @@ $ \textbf{hg qpush -a} applying hello.patch applying goodbye.patch -Now at: goodbye.patch +now at: goodbye.patch
--- a/en/examples/mq.guards.qselect.qpush.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/mq.guards.qselect.qpush.out Mon Feb 09 22:59:50 2009 -0800 @@ -1,4 +1,4 @@ $ \textbf{hg qpush -a} applying hello.patch applying goodbye.patch -Now at: goodbye.patch +now at: goodbye.patch
--- a/en/examples/mq.guards.qselect.quux.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/mq.guards.qselect.quux.out Mon Feb 09 22:59:50 2009 -0800 @@ -1,6 +1,6 @@ $ \textbf{hg qselect quux} number of guarded, applied patches has changed from 0 to 2 $ \textbf{hg qpop -a} -Patch queue now empty +patch queue now empty $ \textbf{hg qpush -a} patch series already fully applied
--- a/en/examples/mq.tarball.newsource.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/mq.tarball.newsource.out Mon Feb 09 22:59:50 2009 -0800 @@ -1,5 +1,5 @@ $ \textbf{hg qpop -a} -Patch queue now empty +patch queue now empty $ \textbf{cd ..} $ \textbf{download netplug-1.2.8.tar.bz2} $ \textbf{hg clone netplug-1.2.5 netplug-1.2.8}
--- a/en/examples/mq.tarball.repush.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/mq.tarball.repush.out Mon Feb 09 22:59:50 2009 -0800 @@ -10,4 +10,4 @@ $ \textbf{hg qpush -a} (working directory not at tip) applying build-fix.patch -Now at: build-fix.patch +now at: build-fix.patch
--- a/en/examples/mq.tutorial.qnew2.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/mq.tutorial.qnew2.out Mon Feb 09 22:59:50 2009 -0800 @@ -1,15 +1,15 @@ $ \textbf{hg qnew second.patch} $ \textbf{hg log --style=compact --limit=2} -2[qtip,second.patch,tip] +2[qtip,second.patch,tip] 2009-01-30 06:34 +0000 bos [mq]: second.patch -1[first.patch,qbase] +1[first.patch,qbase] 2009-01-30 06:34 +0000 bos [mq]: first.patch $ \textbf{echo 'line 4' >> file1} $ \textbf{hg qrefresh} $ \textbf{hg tip --style=compact --patch} -2[qtip,second.patch,tip] +2[qtip,second.patch,tip] 2009-01-30 06:34 +0000 bos [mq]: second.patch diff -r -r file1
--- a/en/examples/mq.tutorial.qpop.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/mq.tutorial.qpop.out Mon Feb 09 22:59:50 2009 -0800 @@ -2,7 +2,7 @@ first.patch second.patch $ \textbf{hg qpop} -Now at: first.patch +now at: first.patch $ \textbf{hg qseries} first.patch second.patch
--- a/en/examples/mq.tutorial.qpush-a.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/mq.tutorial.qpush-a.out Mon Feb 09 22:59:50 2009 -0800 @@ -1,6 +1,6 @@ $ \textbf{hg qpush -a} applying second.patch -Now at: second.patch +now at: second.patch $ \textbf{cat file1} line 1 line 2
--- a/en/examples/mq.tutorial.qrefresh.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/mq.tutorial.qrefresh.out Mon Feb 09 22:59:50 2009 -0800 @@ -9,7 +9,7 @@ $ \textbf{hg qrefresh} $ \textbf{hg diff} $ \textbf{hg tip --style=compact --patch} -1[qtip,first.patch,tip,qbase] +1[qtip,first.patch,tip,qbase] 2009-01-30 06:34 +0000 bos [mq]: first.patch diff -r -r file1
--- a/en/examples/mq.tutorial.qrefresh2.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/mq.tutorial.qrefresh2.out Mon Feb 09 22:59:50 2009 -0800 @@ -3,7 +3,7 @@ M file1 $ \textbf{hg qrefresh} $ \textbf{hg tip --style=compact --patch} -1[qtip,first.patch,tip,qbase] +1[qtip,first.patch,tip,qbase] 2009-01-30 06:34 +0000 bos [mq]: first.patch diff -r -r file1
--- a/en/examples/run-example Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/run-example Mon Feb 09 22:59:50 2009 -0800 @@ -61,9 +61,10 @@ timeout = 10 - def __init__(self, name, verbose): + def __init__(self, name, verbose, keep_change): self.name = name self.verbose = verbose + self.keep_change = keep_change self.poll = select.poll() def parse(self): @@ -320,7 +321,11 @@ return False else: print >> sys.stderr, '\nOutput of %s has changed!' % base - os.system('diff -u %s %s 1>&2' % (oldname, errname)) + if self.keep_change: + os.rename(errname, oldname) + return False + else: + os.system('diff -u %s %s 1>&2' % (oldname, errname)) return True def print_help(exit, msg=None): @@ -330,19 +335,23 @@ print >> sys.stderr, 'Options:' print >> sys.stderr, ' -a --all run all tests in this directory' print >> sys.stderr, ' -h --help print this help message' + print >> sys.stderr, ' --help keep new output as desired output' print >> sys.stderr, ' -v --verbose display extra debug output' sys.exit(exit) def main(path='.'): opts, args = getopt.getopt(sys.argv[1:], '?ahv', - ['all', 'help', 'verbose']) + ['all', 'help', 'keep', 'verbose']) verbose = False run_all = False + keep_change = False for o, a in opts: if o in ('-h', '-?', '--help'): print_help(0) if o in ('-a', '--all'): run_all = True + if o in ('--keep',): + keep_change = True if o in ('-v', '--verbose'): verbose = True errs = 0 @@ -355,7 +364,7 @@ errs += 1 continue if stat.S_ISREG(st.st_mode) and st.st_mode & 0111: - if example(a, verbose).run(): + if example(a, verbose, keep_change).run(): errs += 1 else: print >> sys.stderr, '%s: not a file, or not executable' % a @@ -376,7 +385,7 @@ raise continue if stat.S_ISREG(st.st_mode) and st.st_mode & 0111: - if example(pathname, verbose).run(): + if example(pathname, verbose, keep_change).run(): errs += 1 print >> open(os.path.join(path, '.run'), 'w'), time.asctime() else:
--- a/en/examples/template.svnstyle Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/template.svnstyle Mon Feb 09 22:59:50 2009 -0800 @@ -34,6 +34,7 @@ hg log -r0 --template '{node}' #$ name: simplest +#$ ignore: \d+-\d+-\d+ \d+:\d+ \+.* cat svn.style hg log -r1 --style svn.style
--- a/en/examples/template.svnstyle.result.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/template.svnstyle.result.out Mon Feb 09 22:59:50 2009 -0800 @@ -1,7 +1,7 @@ $ \textbf{hg log -r1 --style svn.style} ------------------------------------------------------------------------ -r1 | bos +r1 | bos | 2009-01-30 06:40 +0000 (Fri, 30 Jan 2009 06:40:17 +0000) added line to end of <<hello>> file.
--- a/en/examples/tour-merge-conflict Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/tour-merge-conflict Mon Feb 09 22:59:50 2009 -0800 @@ -56,6 +56,7 @@ #$ ignore: [<>]{7} /tmp/.* export HGMERGE=merge +echo 'XXX this is broken and must be fixed' hg merge cat letter.txt
--- a/en/examples/tour-merge-conflict.merge.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/tour-merge-conflict.merge.out Mon Feb 09 22:59:50 2009 -0800 @@ -1,4 +1,6 @@ $ \textbf{export HGMERGE=merge} +$ \textbf{echo 'XXX this is broken and must be fixed'} +XXX this is broken and must be fixed $ \textbf{hg merge} merging letter.txt merge: warning: conflicts during merge
--- a/en/examples/tour.help.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/tour.help.out Mon Feb 09 22:59:50 2009 -0800 @@ -9,8 +9,7 @@ If no directory is given, the current directory is used. It is possible to specify an ssh:// URL as the destination. - Look at the help text for the pull command for important details - about ssh:// URLs. + See 'hg help urls' for more information. options:
--- a/en/examples/tour.ls.out Wed Jan 21 14:16:38 2009 +0100 +++ b/en/examples/tour.ls.out Mon Feb 09 22:59:50 2009 -0800 @@ -1,5 +1,5 @@ $ \textbf{ls -l} -total 4 + $ \textbf{ls hello} Makefile hello.c
--- a/en/filenames.tex Wed Jan 21 14:16:38 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,306 +0,0 @@ -\chapter{File names and pattern matching} -\label{chap:names} - -Mercurial provides mechanisms that let you work with file names in a -consistent and expressive way. - -\section{Simple file naming} - -Mercurial uses a unified piece of machinery ``under the hood'' to -handle file names. Every command behaves uniformly with respect to -file names. The way in which commands work with file names is as -follows. - -If you explicitly name real files on the command line, Mercurial works -with exactly those files, as you would expect. -\interaction{filenames.files} - -When you provide a directory name, Mercurial will interpret this as -``operate on every file in this directory and its subdirectories''. -Mercurial traverses the files and subdirectories in a directory in -alphabetical order. When it encounters a subdirectory, it will -traverse that subdirectory before continuing with the current -directory. -\interaction{filenames.dirs} - -\section{Running commands without any file names} - -Mercurial's commands that work with file names have useful default -behaviours when you invoke them without providing any file names or -patterns. What kind of behaviour you should expect depends on what -the command does. Here are a few rules of thumb you can use to -predict what a command is likely to do if you don't give it any names -to work with. -\begin{itemize} -\item Most commands will operate on the entire working directory. - This is what the \hgcmd{add} command does, for example. -\item If the command has effects that are difficult or impossible to - reverse, it will force you to explicitly provide at least one name - or pattern (see below). This protects you from accidentally - deleting files by running \hgcmd{remove} with no arguments, for - example. -\end{itemize} - -It's easy to work around these default behaviours if they don't suit -you. If a command normally operates on the whole working directory, -you can invoke it on just the current directory and its subdirectories -by giving it the name ``\dirname{.}''. -\interaction{filenames.wdir-subdir} - -Along the same lines, some commands normally print file names relative -to the root of the repository, even if you're invoking them from a -subdirectory. Such a command will print file names relative to your -subdirectory if you give it explicit names. Here, we're going to run -\hgcmd{status} from a subdirectory, and get it to operate on the -entire working directory while printing file names relative to our -subdirectory, by passing it the output of the \hgcmd{root} command. -\interaction{filenames.wdir-relname} - -\section{Telling you what's going on} - -The \hgcmd{add} example in the preceding section illustrates something -else that's helpful about Mercurial commands. If a command operates -on a file that you didn't name explicitly on the command line, it will -usually print the name of the file, so that you will not be surprised -what's going on. - -The principle here is of \emph{least surprise}. If you've exactly -named a file on the command line, there's no point in repeating it -back at you. If Mercurial is acting on a file \emph{implicitly}, -because you provided no names, or a directory, or a pattern (see -below), it's safest to tell you what it's doing. - -For commands that behave this way, you can silence them using the -\hggopt{-q} option. You can also get them to print the name of every -file, even those you've named explicitly, using the \hggopt{-v} -option. - -\section{Using patterns to identify files} - -In addition to working with file and directory names, Mercurial lets -you use \emph{patterns} to identify files. Mercurial's pattern -handling is expressive. - -On Unix-like systems (Linux, MacOS, etc.), the job of matching file -names to patterns normally falls to the shell. On these systems, you -must explicitly tell Mercurial that a name is a pattern. On Windows, -the shell does not expand patterns, so Mercurial will automatically -identify names that are patterns, and expand them for you. - -To provide a pattern in place of a regular name on the command line, -the mechanism is simple: -\begin{codesample2} - syntax:patternbody -\end{codesample2} -That is, a pattern is identified by a short text string that says what -kind of pattern this is, followed by a colon, followed by the actual -pattern. - -Mercurial supports two kinds of pattern syntax. The most frequently -used is called \texttt{glob}; this is the same kind of pattern -matching used by the Unix shell, and should be familiar to Windows -command prompt users, too. - -When Mercurial does automatic pattern matching on Windows, it uses -\texttt{glob} syntax. You can thus omit the ``\texttt{glob:}'' prefix -on Windows, but it's safe to use it, too. - -The \texttt{re} syntax is more powerful; it lets you specify patterns -using regular expressions, also known as regexps. - -By the way, in the examples that follow, notice that I'm careful to -wrap all of my patterns in quote characters, so that they won't get -expanded by the shell before Mercurial sees them. - -\subsection{Shell-style \texttt{glob} patterns} - -This is an overview of the kinds of patterns you can use when you're -matching on glob patterns. - -The ``\texttt{*}'' character matches any string, within a single -directory. -\interaction{filenames.glob.star} - -The ``\texttt{**}'' pattern matches any string, and crosses directory -boundaries. It's not a standard Unix glob token, but it's accepted by -several popular Unix shells, and is very useful. -\interaction{filenames.glob.starstar} - -The ``\texttt{?}'' pattern matches any single character. -\interaction{filenames.glob.question} - -The ``\texttt{[}'' character begins a \emph{character class}. This -matches any single character within the class. The class ends with a -``\texttt{]}'' character. A class may contain multiple \emph{range}s -of the form ``\texttt{a-f}'', which is shorthand for -``\texttt{abcdef}''. -\interaction{filenames.glob.range} -If the first character after the ``\texttt{[}'' in a character class -is a ``\texttt{!}'', it \emph{negates} the class, making it match any -single character not in the class. - -A ``\texttt{\{}'' begins a group of subpatterns, where the whole group -matches if any subpattern in the group matches. The ``\texttt{,}'' -character separates subpatterns, and ``\texttt{\}}'' ends the group. -\interaction{filenames.glob.group} - -\subsubsection{Watch out!} - -Don't forget that if you want to match a pattern in any directory, you -should not be using the ``\texttt{*}'' match-any token, as this will -only match within one directory. Instead, use the ``\texttt{**}'' -token. This small example illustrates the difference between the two. -\interaction{filenames.glob.star-starstar} - -\subsection{Regular expression matching with \texttt{re} patterns} - -Mercurial accepts the same regular expression syntax as the Python -programming language (it uses Python's regexp engine internally). -This is based on the Perl language's regexp syntax, which is the most -popular dialect in use (it's also used in Java, for example). - -I won't discuss Mercurial's regexp dialect in any detail here, as -regexps are not often used. Perl-style regexps are in any case -already exhaustively documented on a multitude of web sites, and in -many books. Instead, I will focus here on a few things you should -know if you find yourself needing to use regexps with Mercurial. - -A regexp is matched against an entire file name, relative to the root -of the repository. In other words, even if you're already in -subbdirectory \dirname{foo}, if you want to match files under this -directory, your pattern must start with ``\texttt{foo/}''. - -One thing to note, if you're familiar with Perl-style regexps, is that -Mercurial's are \emph{rooted}. That is, a regexp starts matching -against the beginning of a string; it doesn't look for a match -anywhere within the string. To match anywhere in a string, start -your pattern with ``\texttt{.*}''. - -\section{Filtering files} - -Not only does Mercurial give you a variety of ways to specify files; -it lets you further winnow those files using \emph{filters}. Commands -that work with file names accept two filtering options. -\begin{itemize} -\item \hggopt{-I}, or \hggopt{--include}, lets you specify a pattern - that file names must match in order to be processed. -\item \hggopt{-X}, or \hggopt{--exclude}, gives you a way to - \emph{avoid} processing files, if they match this pattern. -\end{itemize} -You can provide multiple \hggopt{-I} and \hggopt{-X} options on the -command line, and intermix them as you please. Mercurial interprets -the patterns you provide using glob syntax by default (but you can use -regexps if you need to). - -You can read a \hggopt{-I} filter as ``process only the files that -match this filter''. -\interaction{filenames.filter.include} -The \hggopt{-X} filter is best read as ``process only the files that -don't match this pattern''. -\interaction{filenames.filter.exclude} - -\section{Ignoring unwanted files and directories} - -XXX. - -\section{Case sensitivity} -\label{sec:names:case} - -If you're working in a mixed development environment that contains -both Linux (or other Unix) systems and Macs or Windows systems, you -should keep in the back of your mind the knowledge that they treat the -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 -\emph{case} of characters in file and directory names. There are -three common ways to handle case in names. -\begin{itemize} -\item Completely case insensitive. Uppercase and lowercase versions - of a letter are treated as identical, both when creating a file and - during subsequent accesses. This is common on older DOS-based - systems. -\item Case preserving, but insensitive. When a file or directory is - created, the case of its name is stored, and can be retrieved and - displayed by the operating system. When an existing file is being - looked up, its case is ignored. This is the standard arrangement on - Windows and MacOS. The names \filename{foo} and \filename{FoO} - identify the same file. This treatment of uppercase and lowercase - letters as interchangeable is also referred to as \emph{case - folding}. -\item Case sensitive. The case of a name is significant at all times. - The names \filename{foo} and {FoO} identify different files. This - is the way Linux and Unix systems normally work. -\end{itemize} - -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. - -\subsection{Safe, portable repository storage} - -Mercurial's repository storage mechanism is \emph{case safe}. It -translates file names so that they can be safely stored on both case -sensitive and case insensitive filesystems. This means that you can -use normal file copying tools to transfer a Mercurial repository onto, -for example, a USB thumb drive, and safely move that drive and -repository back and forth between a Mac, a PC running Windows, and a -Linux box. - -\subsection{Detecting case conflicts} - -When operating in the working directory, Mercurial honours the naming -policy of the filesystem where the working directory is located. If -the filesystem is case preserving, but insensitive, Mercurial will -treat names that differ only in case as the same. - -An important aspect of this approach is that it is possible to commit -a changeset on a case sensitive (typically Linux or Unix) filesystem -that will cause trouble for users on case insensitive (usually Windows -and MacOS) users. If a Linux user commits changes to two files, one -named \filename{myfile.c} and the other named \filename{MyFile.C}, -they will be stored correctly in the repository. And in the working -directories of other Linux users, they will be correctly represented -as separate files. - -If a Windows or Mac user pulls this change, they will not initially -have a problem, because Mercurial's repository storage mechanism is -case safe. However, once they try to \hgcmd{update} the working -directory to that changeset, or \hgcmd{merge} with that changeset, -Mercurial will spot the conflict between the two file names that the -filesystem would treat as the same, and forbid the update or merge -from occurring. - -\subsection{Fixing a case conflict} - -If you are using Windows or a Mac in a mixed environment where some of -your collaborators are using Linux or Unix, and Mercurial reports a -case folding conflict when you try to \hgcmd{update} or \hgcmd{merge}, -the procedure to fix the problem is simple. - -Just find a nearby Linux or Unix box, clone the problem repository -onto it, and use Mercurial's \hgcmd{rename} command to change the -names of any offending files or directories so that they will no -longer cause case folding conflicts. Commit this change, \hgcmd{pull} -or \hgcmd{push} it across to your Windows or MacOS system, and -\hgcmd{update} to the revision with the non-conflicting names. - -The changeset with case-conflicting names will remain in your -project's history, and you still won't be able to \hgcmd{update} your -working directory to that changeset on a Windows or MacOS system, but -you can continue development unimpeded. - -\begin{note} - Prior to version~0.9.3, Mercurial did not use a case safe repository - storage mechanism, and did not detect case folding conflicts. If - you are using an older version of Mercurial on Windows or MacOS, I - strongly recommend that you upgrade. -\end{note} - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "00book" -%%% End:
--- a/en/hgext.tex Wed Jan 21 14:16:38 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,429 +0,0 @@ -\chapter{Adding functionality with extensions} -\label{chap:hgext} - -While the core of Mercurial is quite complete from a functionality -standpoint, it's deliberately shorn of fancy features. This approach -of preserving simplicity keeps the software easy to deal with for both -maintainers and users. - -However, Mercurial doesn't box you in with an inflexible command set: -you can add features to it as \emph{extensions} (sometimes known as -\emph{plugins}). We've already discussed a few of these extensions in -earlier chapters. -\begin{itemize} -\item Section~\ref{sec:tour-merge:fetch} covers the \hgext{fetch} - extension; this combines pulling new changes and merging them with - local changes into a single command, \hgxcmd{fetch}{fetch}. -\item In chapter~\ref{chap:hook}, we covered several extensions that - are useful for hook-related functionality: \hgext{acl} adds access - control lists; \hgext{bugzilla} adds integration with the Bugzilla - bug tracking system; and \hgext{notify} sends notification emails on - new changes. -\item The Mercurial Queues patch management extension is so invaluable - that it merits two chapters and an appendix all to itself. - Chapter~\ref{chap:mq} covers the basics; - chapter~\ref{chap:mq-collab} discusses advanced topics; and - appendix~\ref{chap:mqref} goes into detail on each command. -\end{itemize} - -In this chapter, we'll cover some of the other extensions that are -available for Mercurial, and briefly touch on some of the machinery -you'll need to know about if you want to write an extension of your -own. -\begin{itemize} -\item In section~\ref{sec:hgext:inotify}, we'll discuss the - possibility of \emph{huge} performance improvements using the - \hgext{inotify} extension. -\end{itemize} - -\section{Improve performance with the \hgext{inotify} extension} -\label{sec:hgext:inotify} - -Are you interested in having some of the most common Mercurial -operations run as much as a hundred times faster? Read on! - -Mercurial has great performance under normal circumstances. For -example, when you run the \hgcmd{status} command, Mercurial has to -scan almost every directory and file in your repository so that it can -display file status. Many other Mercurial commands need to do the -same work behind the scenes; for example, the \hgcmd{diff} command -uses the status machinery to avoid doing an expensive comparison -operation on files that obviously haven't changed. - -Because obtaining file status is crucial to good performance, the -authors of Mercurial have optimised this code to within an inch of its -life. However, there's no avoiding the fact that when you run -\hgcmd{status}, Mercurial is going to have to perform at least one -expensive system call for each managed file to determine whether it's -changed since the last time Mercurial checked. For a sufficiently -large repository, this can take a long time. - -To put a number on the magnitude of this effect, I created a -repository containing 150,000 managed files. I timed \hgcmd{status} -as taking ten seconds to run, even when \emph{none} of those files had -been modified. - -Many modern operating systems contain a file notification facility. -If a program signs up to an appropriate service, the operating system -will notify it every time a file of interest is created, modified, or -deleted. On Linux systems, the kernel component that does this is -called \texttt{inotify}. - -Mercurial's \hgext{inotify} extension talks to the kernel's -\texttt{inotify} component to optimise \hgcmd{status} commands. The -extension has two components. A daemon sits in the background and -receives notifications from the \texttt{inotify} subsystem. It also -listens for connections from a regular Mercurial command. The -extension modifies Mercurial's behaviour so that instead of scanning -the filesystem, it queries the daemon. Since the daemon has perfect -information about the state of the repository, it can respond with a -result instantaneously, avoiding the need to scan every directory and -file in the repository. - -Recall the ten seconds that I measured plain Mercurial as taking to -run \hgcmd{status} on a 150,000 file repository. With the -\hgext{inotify} extension enabled, the time dropped to 0.1~seconds, a -factor of \emph{one hundred} faster. - -Before we continue, please pay attention to some caveats. -\begin{itemize} -\item The \hgext{inotify} extension is Linux-specific. Because it - interfaces directly to the Linux kernel's \texttt{inotify} - subsystem, it does not work on other operating systems. -\item It should work on any Linux distribution that was released after - early~2005. Older distributions are likely to have a kernel that - lacks \texttt{inotify}, or a version of \texttt{glibc} that does not - have the necessary interfacing support. -\item Not all filesystems are suitable for use with the - \hgext{inotify} extension. Network filesystems such as NFS are a - non-starter, for example, particularly if you're running Mercurial - on several systems, all mounting the same network filesystem. The - kernel's \texttt{inotify} system has no way of knowing about changes - made on another system. Most local filesystems (e.g.~ext3, XFS, - ReiserFS) should work fine. -\end{itemize} - -The \hgext{inotify} extension is not yet shipped with Mercurial as of -May~2007, so it's a little more involved to set up than other -extensions. But the performance improvement is worth it! - -The extension currently comes in two parts: a set of patches to the -Mercurial source code, and a library of Python bindings to the -\texttt{inotify} subsystem. -\begin{note} - There are \emph{two} Python \texttt{inotify} binding libraries. One - of them is called \texttt{pyinotify}, and is packaged by some Linux - distributions as \texttt{python-inotify}. This is \emph{not} the - one you'll need, as it is too buggy and inefficient to be practical. -\end{note} -To get going, it's best to already have a functioning copy of -Mercurial installed. -\begin{note} - If you follow the instructions below, you'll be \emph{replacing} and - overwriting any existing installation of Mercurial that you might - already have, using the latest ``bleeding edge'' Mercurial code. - Don't say you weren't warned! -\end{note} -\begin{enumerate} -\item Clone the Python \texttt{inotify} binding repository. Build and - install it. - \begin{codesample4} - hg clone http://hg.kublai.com/python/inotify - cd inotify - python setup.py build --force - sudo python setup.py install --skip-build - \end{codesample4} -\item Clone the \dirname{crew} Mercurial repository. Clone the - \hgext{inotify} patch repository so that Mercurial Queues will be - able to apply patches to your cope of the \dirname{crew} repository. - \begin{codesample4} - hg clone http://hg.intevation.org/mercurial/crew - hg clone crew inotify - hg clone http://hg.kublai.com/mercurial/patches/inotify inotify/.hg/patches - \end{codesample4} -\item Make sure that you have the Mercurial Queues extension, - \hgext{mq}, enabled. If you've never used MQ, read - section~\ref{sec:mq:start} to get started quickly. -\item Go into the \dirname{inotify} repo, and apply all of the - \hgext{inotify} patches using the \hgxopt{mq}{qpush}{-a} option to - the \hgxcmd{mq}{qpush} command. - \begin{codesample4} - cd inotify - hg qpush -a - \end{codesample4} - If you get an error message from \hgxcmd{mq}{qpush}, you should not - continue. Instead, ask for help. -\item Build and install the patched version of Mercurial. - \begin{codesample4} - python setup.py build --force - sudo python setup.py install --skip-build - \end{codesample4} -\end{enumerate} -Once you've build a suitably patched version of Mercurial, all you -need to do to enable the \hgext{inotify} extension is add an entry to -your \hgrc. -\begin{codesample2} - [extensions] - inotify = -\end{codesample2} -When the \hgext{inotify} extension is enabled, Mercurial will -automatically and transparently start the status daemon the first time -you run a command that needs status in a repository. It runs one -status daemon per repository. - -The status daemon is started silently, and runs in the background. If -you look at a list of running processes after you've enabled the -\hgext{inotify} extension and run a few commands in different -repositories, you'll thus see a few \texttt{hg} processes sitting -around, waiting for updates from the kernel and queries from -Mercurial. - -The first time you run a Mercurial command in a repository when you -have the \hgext{inotify} extension enabled, it will run with about the -same performance as a normal Mercurial command. This is because the -status daemon needs to perform a normal status scan so that it has a -baseline against which to apply later updates from the kernel. -However, \emph{every} subsequent command that does any kind of status -check should be noticeably faster on repositories of even fairly -modest size. Better yet, the bigger your repository is, the greater a -performance advantage you'll see. The \hgext{inotify} daemon makes -status operations almost instantaneous on repositories of all sizes! - -If you like, you can manually start a status daemon using the -\hgxcmd{inotify}{inserve} command. This gives you slightly finer -control over how the daemon ought to run. This command will of course -only be available when the \hgext{inotify} extension is enabled. - -When you're using the \hgext{inotify} extension, you should notice -\emph{no difference at all} in Mercurial's behaviour, with the sole -exception of status-related commands running a whole lot faster than -they used to. You should specifically expect that commands will not -print different output; neither should they give different results. -If either of these situations occurs, please report a bug. - -\section{Flexible diff support with the \hgext{extdiff} extension} -\label{sec:hgext:extdiff} - -Mercurial's built-in \hgcmd{diff} command outputs plaintext unified -diffs. -\interaction{extdiff.diff} -If you would like to use an external tool to display modifications, -you'll want to use the \hgext{extdiff} extension. This will let you -use, for example, a graphical diff tool. - -The \hgext{extdiff} extension is bundled with Mercurial, so it's easy -to set up. In the \rcsection{extensions} section of your \hgrc, -simply add a one-line entry to enable the extension. -\begin{codesample2} - [extensions] - extdiff = -\end{codesample2} -This introduces a command named \hgxcmd{extdiff}{extdiff}, which by -default uses your system's \command{diff} command to generate a -unified diff in the same form as the built-in \hgcmd{diff} command. -\interaction{extdiff.extdiff} -The result won't be exactly the same as with the built-in \hgcmd{diff} -variations, because the output of \command{diff} varies from one -system to another, even when passed the same options. - -As the ``\texttt{making snapshot}'' lines of output above imply, the -\hgxcmd{extdiff}{extdiff} command works by creating two snapshots of -your source tree. The first snapshot is of the source revision; the -second, of the target revision or working directory. The -\hgxcmd{extdiff}{extdiff} command generates these snapshots in a -temporary directory, passes the name of each directory to an external -diff viewer, then deletes the temporary directory. For efficiency, it -only snapshots the directories and files that have changed between the -two revisions. - -Snapshot directory names have the same base name as your repository. -If your repository path is \dirname{/quux/bar/foo}, then \dirname{foo} -will be the name of each snapshot directory. Each snapshot directory -name has its changeset ID appended, if appropriate. If a snapshot is -of revision \texttt{a631aca1083f}, the directory will be named -\dirname{foo.a631aca1083f}. A snapshot of the working directory won't -have a changeset ID appended, so it would just be \dirname{foo} in -this example. To see what this looks like in practice, look again at -the \hgxcmd{extdiff}{extdiff} example above. Notice that the diff has -the snapshot directory names embedded in its header. - -The \hgxcmd{extdiff}{extdiff} command accepts two important options. -The \hgxopt{extdiff}{extdiff}{-p} option lets you choose a program to -view differences with, instead of \command{diff}. With the -\hgxopt{extdiff}{extdiff}{-o} option, you can change the options that -\hgxcmd{extdiff}{extdiff} passes to the program (by default, these -options are ``\texttt{-Npru}'', which only make sense if you're -running \command{diff}). In other respects, the -\hgxcmd{extdiff}{extdiff} command acts similarly to the built-in -\hgcmd{diff} command: you use the same option names, syntax, and -arguments to specify the revisions you want, the files you want, and -so on. - -As an example, here's how to run the normal system \command{diff} -command, getting it to generate context diffs (using the -\cmdopt{diff}{-c} option) instead of unified diffs, and five lines of -context instead of the default three (passing \texttt{5} as the -argument to the \cmdopt{diff}{-C} option). -\interaction{extdiff.extdiff-ctx} - -Launching a visual diff tool is just as easy. Here's how to launch -the \command{kdiff3} viewer. -\begin{codesample2} - hg extdiff -p kdiff3 -o '' -\end{codesample2} - -If your diff viewing command can't deal with directories, you can -easily work around this with a little scripting. For an example of -such scripting in action with the \hgext{mq} extension and the -\command{interdiff} command, see -section~\ref{mq-collab:tips:interdiff}. - -\subsection{Defining command aliases} - -It can be cumbersome to remember the options to both the -\hgxcmd{extdiff}{extdiff} command and the diff viewer you want to use, -so the \hgext{extdiff} extension lets you define \emph{new} commands -that will invoke your diff viewer with exactly the right options. - -All you need to do is edit your \hgrc, and add a section named -\rcsection{extdiff}. Inside this section, you can define multiple -commands. Here's how to add a \texttt{kdiff3} command. Once you've -defined this, you can type ``\texttt{hg kdiff3}'' and the -\hgext{extdiff} extension will run \command{kdiff3} for you. -\begin{codesample2} - [extdiff] - cmd.kdiff3 = -\end{codesample2} -If you leave the right hand side of the definition empty, as above, -the \hgext{extdiff} extension uses the name of the command you defined -as the name of the external program to run. But these names don't -have to be the same. Here, we define a command named ``\texttt{hg - wibble}'', which runs \command{kdiff3}. -\begin{codesample2} - [extdiff] - cmd.wibble = kdiff3 -\end{codesample2} - -You can also specify the default options that you want to invoke your -diff viewing program with. The prefix to use is ``\texttt{opts.}'', -followed by the name of the command to which the options apply. This -example defines a ``\texttt{hg vimdiff}'' command that runs the -\command{vim} editor's \texttt{DirDiff} extension. -\begin{codesample2} - [extdiff] - cmd.vimdiff = vim - opts.vimdiff = -f '+next' '+execute "DirDiff" argv(0) argv(1)' -\end{codesample2} - -\section{Cherrypicking changes with the \hgext{transplant} extension} -\label{sec:hgext:transplant} - -Need to have a long chat with Brendan about this. - -\section{Send changes via email with the \hgext{patchbomb} extension} -\label{sec:hgext:patchbomb} - -Many projects have a culture of ``change review'', in which people -send their modifications to a mailing list for others to read and -comment on before they commit the final version to a shared -repository. Some projects have people who act as gatekeepers; they -apply changes from other people to a repository to which those others -don't have access. - -Mercurial makes it easy to send changes over email for review or -application, via its \hgext{patchbomb} extension. The extension is so -namd because changes are formatted as patches, and it's usual to send -one changeset per email message. Sending a long series of changes by -email is thus much like ``bombing'' the recipient's inbox, hence -``patchbomb''. - -As usual, the basic configuration of the \hgext{patchbomb} extension -takes just one or two lines in your \hgrc. -\begin{codesample2} - [extensions] - patchbomb = -\end{codesample2} -Once you've enabled the extension, you will have a new command -available, named \hgxcmd{patchbomb}{email}. - -The safest and best way to invoke the \hgxcmd{patchbomb}{email} -command is to \emph{always} run it first with the -\hgxopt{patchbomb}{email}{-n} option. This will show you what the -command \emph{would} send, without actually sending anything. Once -you've had a quick glance over the changes and verified that you are -sending the right ones, you can rerun the same command, with the -\hgxopt{patchbomb}{email}{-n} option removed. - -The \hgxcmd{patchbomb}{email} command accepts the same kind of -revision syntax as every other Mercurial command. For example, this -command will send every revision between 7 and \texttt{tip}, -inclusive. -\begin{codesample2} - hg email -n 7:tip -\end{codesample2} -You can also specify a \emph{repository} to compare with. If you -provide a repository but no revisions, the \hgxcmd{patchbomb}{email} -command will send all revisions in the local repository that are not -present in the remote repository. If you additionally specify -revisions or a branch name (the latter using the -\hgxopt{patchbomb}{email}{-b} option), this will constrain the -revisions sent. - -It's perfectly safe to run the \hgxcmd{patchbomb}{email} command -without the names of the people you want to send to: if you do this, -it will just prompt you for those values interactively. (If you're -using a Linux or Unix-like system, you should have enhanced -\texttt{readline}-style editing capabilities when entering those -headers, too, which is useful.) - -When you are sending just one revision, the \hgxcmd{patchbomb}{email} -command will by default use the first line of the changeset -description as the subject of the single email message it sends. - -If you send multiple revisions, the \hgxcmd{patchbomb}{email} command -will usually send one message per changeset. It will preface the -series with an introductory message, in which you should describe the -purpose of the series of changes you're sending. - -\subsection{Changing the behaviour of patchbombs} - -Not every project has exactly the same conventions for sending changes -in email; the \hgext{patchbomb} extension tries to accommodate a -number of variations through command line options. -\begin{itemize} -\item You can write a subject for the introductory message on the - command line using the \hgxopt{patchbomb}{email}{-s} option. This - takes one argument, the text of the subject to use. -\item To change the email address from which the messages originate, - use the \hgxopt{patchbomb}{email}{-f} option. This takes one - argument, the email address to use. -\item The default behaviour is to send unified diffs (see - section~\ref{sec:mq:patch} for a description of the format), one per - message. You can send a binary bundle instead with the - \hgxopt{patchbomb}{email}{-b} option. -\item Unified diffs are normally prefaced with a metadata header. You - can omit this, and send unadorned diffs, with the - \hgxopt{patchbomb}{email}{--plain} option. -\item Diffs are normally sent ``inline'', in the same body part as the - description of a patch. This makes it easiest for the largest - number of readers to quote and respond to parts of a diff, as some - mail clients will only quote the first MIME body part in a message. - If you'd prefer to send the description and the diff in separate - body parts, use the \hgxopt{patchbomb}{email}{-a} option. -\item Instead of sending mail messages, you can write them to an - \texttt{mbox}-format mail folder using the - \hgxopt{patchbomb}{email}{-m} option. That option takes one - argument, the name of the file to write to. -\item If you would like to add a \command{diffstat}-format summary to - each patch, and one to the introductory message, use the - \hgxopt{patchbomb}{email}{-d} option. The \command{diffstat} - command displays a table containing the name of each file patched, - the number of lines affected, and a histogram showing how much each - file is modified. This gives readers a qualitative glance at how - complex a patch is. -\end{itemize} - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "00book" -%%% End:
--- a/en/hook.tex Wed Jan 21 14:16:38 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1413 +0,0 @@ -\chapter{Handling repository events with hooks} -\label{chap:hook} - -Mercurial offers a powerful mechanism to let you perform automated -actions in response to events that occur in a repository. In some -cases, you can even control Mercurial's response to those events. - -The name Mercurial uses for one of these actions is a \emph{hook}. -Hooks are called ``triggers'' in some revision control systems, but -the two names refer to the same idea. - -\section{An overview of hooks in Mercurial} - -Here is a brief list of the hooks that Mercurial supports. We will -revisit each of these hooks in more detail later, in -section~\ref{sec:hook:ref}. - -\begin{itemize} -\item[\small\hook{changegroup}] This is run after a group of - changesets has been brought into the repository from elsewhere. -\item[\small\hook{commit}] This is run after a new changeset has been - created in the local repository. -\item[\small\hook{incoming}] This is run once for each new changeset - that is brought into the repository from elsewhere. Notice the - difference from \hook{changegroup}, which is run once per - \emph{group} of changesets brought in. -\item[\small\hook{outgoing}] This is run after a group of changesets - has been transmitted from this repository. -\item[\small\hook{prechangegroup}] This is run before starting to - bring a group of changesets into the repository. -\item[\small\hook{precommit}] Controlling. This is run before starting - a commit. -\item[\small\hook{preoutgoing}] Controlling. This is run before - starting to transmit a group of changesets from this repository. -\item[\small\hook{pretag}] Controlling. This is run before creating a tag. -\item[\small\hook{pretxnchangegroup}] Controlling. This is run after a - group of changesets has been brought into the local repository from - another, but before the transaction completes that will make the - changes permanent in the repository. -\item[\small\hook{pretxncommit}] Controlling. This is run after a new - changeset has been created in the local repository, but before the - transaction completes that will make it permanent. -\item[\small\hook{preupdate}] Controlling. This is run before starting - an update or merge of the working directory. -\item[\small\hook{tag}] This is run after a tag is created. -\item[\small\hook{update}] This is run after an update or merge of the - working directory has finished. -\end{itemize} -Each of the hooks whose description begins with the word -``Controlling'' has the ability to determine whether an activity can -proceed. If the hook succeeds, the activity may proceed; if it fails, -the activity is either not permitted or undone, depending on the hook. - -\section{Hooks and security} - -\subsection{Hooks are run with your privileges} - -When you run a Mercurial command in a repository, and the command -causes a hook to run, that hook runs on \emph{your} system, under -\emph{your} user account, with \emph{your} privilege level. Since -hooks are arbitrary pieces of executable code, you should treat them -with an appropriate level of suspicion. Do not install a hook unless -you are confident that you know who created it and what it does. - -In some cases, you may be exposed to hooks that you did not install -yourself. If you work with Mercurial on an unfamiliar system, -Mercurial will run hooks defined in that system's global \hgrc\ file. - -If you are working with a repository owned by another user, Mercurial -can run hooks defined in that user's repository, but it will still run -them as ``you''. For example, if you \hgcmd{pull} from that -repository, and its \sfilename{.hg/hgrc} defines a local -\hook{outgoing} hook, that hook will run under your user account, even -though you don't own that repository. - -\begin{note} - This only applies if you are pulling from a repository on a local or - network filesystem. If you're pulling over http or ssh, any - \hook{outgoing} hook will run under whatever account is executing - the server process, on the server. -\end{note} - -XXX To see what hooks are defined in a repository, use the -\hgcmdargs{config}{hooks} command. If you are working in one -repository, but talking to another that you do not own (e.g.~using -\hgcmd{pull} or \hgcmd{incoming}), remember that it is the other -repository's hooks you should be checking, not your own. - -\subsection{Hooks do not propagate} - -In Mercurial, hooks are not revision controlled, and do not propagate -when you clone, or pull from, a repository. The reason for this is -simple: a hook is a completely arbitrary piece of executable code. It -runs under your user identity, with your privilege level, on your -machine. - -It would be extremely reckless for any distributed revision control -system to implement revision-controlled hooks, as this would offer an -easily exploitable way to subvert the accounts of users of the -revision control system. - -Since Mercurial does not propagate hooks, if you are collaborating -with other people on a common project, you should not assume that they -are using the same Mercurial hooks as you are, or that theirs are -correctly configured. You should document the hooks you expect people -to use. - -In a corporate intranet, this is somewhat easier to control, as you -can for example provide a ``standard'' installation of Mercurial on an -NFS filesystem, and use a site-wide \hgrc\ file to define hooks that -all users will see. However, this too has its limits; see below. - -\subsection{Hooks can be overridden} - -Mercurial allows you to override a hook definition by redefining the -hook. You can disable it by setting its value to the empty string, or -change its behaviour as you wish. - -If you deploy a system-~or site-wide \hgrc\ file that defines some -hooks, you should thus understand that your users can disable or -override those hooks. - -\subsection{Ensuring that critical hooks are run} - -Sometimes you may want to enforce a policy that you do not want others -to be able to work around. For example, you may have a requirement -that every changeset must pass a rigorous set of tests. Defining this -requirement via a hook in a site-wide \hgrc\ won't work for remote -users on laptops, and of course local users can subvert it at will by -overriding the hook. - -Instead, you can set up your policies for use of Mercurial so that -people are expected to propagate changes through a well-known -``canonical'' server that you have locked down and configured -appropriately. - -One way to do this is via a combination of social engineering and -technology. Set up a restricted-access account; users can push -changes over the network to repositories managed by this account, but -they cannot log into the account and run normal shell commands. In -this scenario, a user can commit a changeset that contains any old -garbage they want. - -When someone pushes a changeset to the server that everyone pulls -from, the server will test the changeset before it accepts it as -permanent, and reject it if it fails to pass the test suite. If -people only pull changes from this filtering server, it will serve to -ensure that all changes that people pull have been automatically -vetted. - -\section{Care with \texttt{pretxn} hooks in a shared-access repository} - -If you want to use hooks to do some automated work in a repository -that a number of people have shared access to, you need to be careful -in how you do this. - -Mercurial only locks a repository when it is writing to the -repository, and only the parts of Mercurial that write to the -repository pay attention to locks. Write locks are necessary to -prevent multiple simultaneous writers from scribbling on each other's -work, corrupting the repository. - -Because Mercurial is careful with the order in which it reads and -writes data, it does not need to acquire a lock when it wants to read -data from the repository. The parts of Mercurial that read from the -repository never pay attention to locks. This lockless reading scheme -greatly increases performance and concurrency. - -With great performance comes a trade-off, though, one which has the -potential to cause you trouble unless you're aware of it. To describe -this requires a little detail about how Mercurial adds changesets to a -repository and reads those changes. - -When Mercurial \emph{writes} metadata, it writes it straight into the -destination file. It writes file data first, then manifest data -(which contains pointers to the new file data), then changelog data -(which contains pointers to the new manifest data). Before the first -write to each file, it stores a record of where the end of the file -was in its transaction log. If the transaction must be rolled back, -Mercurial simply truncates each file back to the size it was before the -transaction began. - -When Mercurial \emph{reads} metadata, it reads the changelog first, -then everything else. Since a reader will only access parts of the -manifest or file metadata that it can see in the changelog, it can -never see partially written data. - -Some controlling hooks (\hook{pretxncommit} and -\hook{pretxnchangegroup}) run when a transaction is almost complete. -All of the metadata has been written, but Mercurial can still roll the -transaction back and cause the newly-written data to disappear. - -If one of these hooks runs for long, it opens a window of time during -which a reader can see the metadata for changesets that are not yet -permanent, and should not be thought of as ``really there''. The -longer the hook runs, the longer that window is open. - -\subsection{The problem illustrated} - -In principle, a good use for the \hook{pretxnchangegroup} hook would -be to automatically build and test incoming changes before they are -accepted into a central repository. This could let you guarantee that -nobody can push changes to this repository that ``break the build''. -But if a client can pull changes while they're being tested, the -usefulness of the test is zero; an unsuspecting someone can pull -untested changes, potentially breaking their build. - -The safest technological answer to this challenge is to set up such a -``gatekeeper'' repository as \emph{unidirectional}. Let it take -changes pushed in from the outside, but do not allow anyone to pull -changes from it (use the \hook{preoutgoing} hook to lock it down). -Configure a \hook{changegroup} hook so that if a build or test -succeeds, the hook will push the new changes out to another repository -that people \emph{can} pull from. - -In practice, putting a centralised bottleneck like this in place is -not often a good idea, and transaction visibility has nothing to do -with the problem. As the size of a project---and the time it takes to -build and test---grows, you rapidly run into a wall with this ``try -before you buy'' approach, where you have more changesets to test than -time in which to deal with them. The inevitable result is frustration -on the part of all involved. - -An approach that scales better is to get people to build and test -before they push, then run automated builds and tests centrally -\emph{after} a push, to be sure all is well. The advantage of this -approach is that it does not impose a limit on the rate at which the -repository can accept changes. - -\section{A short tutorial on using hooks} -\label{sec:hook:simple} - -It is easy to write a Mercurial hook. Let's start with a hook that -runs when you finish a \hgcmd{commit}, and simply prints the hash of -the changeset you just created. The hook is called \hook{commit}. - -\begin{figure}[ht] - \interaction{hook.simple.init} - \caption{A simple hook that runs when a changeset is committed} - \label{ex:hook:init} -\end{figure} - -All hooks follow the pattern in example~\ref{ex:hook:init}. You add -an entry to the \rcsection{hooks} section of your \hgrc. On the left -is the name of the event to trigger on; on the right is the action to -take. As you can see, you can run an arbitrary shell command in a -hook. Mercurial passes extra information to the hook using -environment variables (look for \envar{HG\_NODE} in the example). - -\subsection{Performing multiple actions per event} - -Quite often, you will want to define more than one hook for a -particular kind of event, as shown in example~\ref{ex:hook:ext}. -Mercurial lets you do this by adding an \emph{extension} to the end of -a hook's name. You extend a hook's name by giving the name of the -hook, followed by a full stop (the ``\texttt{.}'' character), followed -by some more text of your choosing. For example, Mercurial will run -both \texttt{commit.foo} and \texttt{commit.bar} when the -\texttt{commit} event occurs. - -\begin{figure}[ht] - \interaction{hook.simple.ext} - \caption{Defining a second \hook{commit} hook} - \label{ex:hook:ext} -\end{figure} - -To give a well-defined order of execution when there are multiple -hooks defined for an event, Mercurial sorts hooks by extension, and -executes the hook commands in this sorted order. In the above -example, it will execute \texttt{commit.bar} before -\texttt{commit.foo}, and \texttt{commit} before both. - -It is a good idea to use a somewhat descriptive extension when you -define a new hook. This will help you to remember what the hook was -for. If the hook fails, you'll get an error message that contains the -hook name and extension, so using a descriptive extension could give -you an immediate hint as to why the hook failed (see -section~\ref{sec:hook:perm} for an example). - -\subsection{Controlling whether an activity can proceed} -\label{sec:hook:perm} - -In our earlier examples, we used the \hook{commit} hook, which is -run after a commit has completed. This is one of several Mercurial -hooks that run after an activity finishes. Such hooks have no way of -influencing the activity itself. - -Mercurial defines a number of events that occur before an activity -starts; or after it starts, but before it finishes. Hooks that -trigger on these events have the added ability to choose whether the -activity can continue, or will abort. - -The \hook{pretxncommit} hook runs after a commit has all but -completed. In other words, the metadata representing the changeset -has been written out to disk, but the transaction has not yet been -allowed to complete. The \hook{pretxncommit} hook has the ability to -decide whether the transaction can complete, or must be rolled back. - -If the \hook{pretxncommit} hook exits with a status code of zero, the -transaction is allowed to complete; the commit finishes; and the -\hook{commit} hook is run. If the \hook{pretxncommit} hook exits with -a non-zero status code, the transaction is rolled back; the metadata -representing the changeset is erased; and the \hook{commit} hook is -not run. - -\begin{figure}[ht] - \interaction{hook.simple.pretxncommit} - \caption{Using the \hook{pretxncommit} hook to control commits} - \label{ex:hook:pretxncommit} -\end{figure} - -The hook in example~\ref{ex:hook:pretxncommit} checks that a commit -comment contains a bug ID. If it does, the commit can complete. If -not, the commit is rolled back. - -\section{Writing your own hooks} - -When you are writing a hook, you might find it useful to run Mercurial -either with the \hggopt{-v} option, or the \rcitem{ui}{verbose} config -item set to ``true''. When you do so, Mercurial will print a message -before it calls each hook. - -\subsection{Choosing how your hook should run} -\label{sec:hook:lang} - -You can write a hook either as a normal program---typically a shell -script---or as a Python function that is executed within the Mercurial -process. - -Writing a hook as an external program has the advantage that it -requires no knowledge of Mercurial's internals. You can call normal -Mercurial commands to get any added information you need. The -trade-off is that external hooks are slower than in-process hooks. - -An in-process Python hook has complete access to the Mercurial API, -and does not ``shell out'' to another process, so it is inherently -faster than an external hook. It is also easier to obtain much of the -information that a hook requires by using the Mercurial API than by -running Mercurial commands. - -If you are comfortable with Python, or require high performance, -writing your hooks in Python may be a good choice. However, when you -have a straightforward hook to write and you don't need to care about -performance (probably the majority of hooks), a shell script is -perfectly fine. - -\subsection{Hook parameters} -\label{sec:hook:param} - -Mercurial calls each hook with a set of well-defined parameters. In -Python, a parameter is passed as a keyword argument to your hook -function. For an external program, a parameter is passed as an -environment variable. - -Whether your hook is written in Python or as a shell script, the -hook-specific parameter names and values will be the same. A boolean -parameter will be represented as a boolean value in Python, but as the -number 1 (for ``true'') or 0 (for ``false'') as an environment -variable for an external hook. If a hook parameter is named -\texttt{foo}, the keyword argument for a Python hook will also be -named \texttt{foo}, while the environment variable for an external -hook will be named \texttt{HG\_FOO}. - -\subsection{Hook return values and activity control} - -A hook that executes successfully must exit with a status of zero if -external, or return boolean ``false'' if in-process. Failure is -indicated with a non-zero exit status from an external hook, or an -in-process hook returning boolean ``true''. If an in-process hook -raises an exception, the hook is considered to have failed. - -For a hook that controls whether an activity can proceed, zero/false -means ``allow'', while non-zero/true/exception means ``deny''. - -\subsection{Writing an external hook} - -When you define an external hook in your \hgrc\ and the hook is run, -its value is passed to your shell, which interprets it. This means -that you can use normal shell constructs in the body of the hook. - -An executable hook is always run with its current directory set to a -repository's root directory. - -Each hook parameter is passed in as an environment variable; the name -is upper-cased, and prefixed with the string ``\texttt{HG\_}''. - -With the exception of hook parameters, Mercurial does not set or -modify any environment variables when running a hook. This is useful -to remember if you are writing a site-wide hook that may be run by a -number of different users with differing environment variables set. -In multi-user situations, you should not rely on environment variables -being set to the values you have in your environment when testing the -hook. - -\subsection{Telling Mercurial to use an in-process hook} - -The \hgrc\ syntax for defining an in-process hook is slightly -different than for an executable hook. The value of the hook must -start with the text ``\texttt{python:}'', and continue with the -fully-qualified name of a callable object to use as the hook's value. - -The module in which a hook lives is automatically imported when a hook -is run. So long as you have the module name and \envar{PYTHONPATH} -right, it should ``just work''. - -The following \hgrc\ example snippet illustrates the syntax and -meaning of the notions we just described. -\begin{codesample2} - [hooks] - commit.example = python:mymodule.submodule.myhook -\end{codesample2} -When Mercurial runs the \texttt{commit.example} hook, it imports -\texttt{mymodule.submodule}, looks for the callable object named -\texttt{myhook}, and calls it. - -\subsection{Writing an in-process hook} - -The simplest in-process hook does nothing, but illustrates the basic -shape of the hook API: -\begin{codesample2} - def myhook(ui, repo, **kwargs): - pass -\end{codesample2} -The first argument to a Python hook is always a -\pymodclass{mercurial.ui}{ui} object. The second is a repository object; -at the moment, it is always an instance of -\pymodclass{mercurial.localrepo}{localrepository}. Following these two -arguments are other keyword arguments. Which ones are passed in -depends on the hook being called, but a hook can ignore arguments it -doesn't care about by dropping them into a keyword argument dict, as -with \texttt{**kwargs} above. - -\section{Some hook examples} - -\subsection{Writing meaningful commit messages} - -It's hard to imagine a useful commit message being very short. The -simple \hook{pretxncommit} hook of figure~\ref{ex:hook:msglen.go} -will prevent you from committing a changeset with a message that is -less than ten bytes long. - -\begin{figure}[ht] - \interaction{hook.msglen.go} - \caption{A hook that forbids overly short commit messages} - \label{ex:hook:msglen.go} -\end{figure} - -\subsection{Checking for trailing whitespace} - -An interesting use of a commit-related hook is to help you to write -cleaner code. A simple example of ``cleaner code'' is the dictum that -a change should not add any new lines of text that contain ``trailing -whitespace''. Trailing whitespace is a series of space and tab -characters at the end of a line of text. In most cases, trailing -whitespace is unnecessary, invisible noise, but it is occasionally -problematic, and people often prefer to get rid of it. - -You can use either the \hook{precommit} or \hook{pretxncommit} hook to -tell whether you have a trailing whitespace problem. If you use the -\hook{precommit} hook, the hook will not know which files you are -committing, so it will have to check every modified file in the -repository for trailing white space. If you want to commit a change -to just the file \filename{foo}, but the file \filename{bar} contains -trailing whitespace, doing a check in the \hook{precommit} hook will -prevent you from committing \filename{foo} due to the problem with -\filename{bar}. This doesn't seem right. - -Should you choose the \hook{pretxncommit} hook, the check won't occur -until just before the transaction for the commit completes. This will -allow you to check for problems only the exact files that are being -committed. However, if you entered the commit message interactively -and the hook fails, the transaction will roll back; you'll have to -re-enter the commit message after you fix the trailing whitespace and -run \hgcmd{commit} again. - -\begin{figure}[ht] - \interaction{hook.ws.simple} - \caption{A simple hook that checks for trailing whitespace} - \label{ex:hook:ws.simple} -\end{figure} - -Figure~\ref{ex:hook:ws.simple} introduces a simple \hook{pretxncommit} -hook that checks for trailing whitespace. This hook is short, but not -very helpful. It exits with an error status if a change adds a line -with trailing whitespace to any file, but does not print any -information that might help us to identify the offending file or -line. It also has the nice property of not paying attention to -unmodified lines; only lines that introduce new trailing whitespace -cause problems. - -\begin{figure}[ht] - \interaction{hook.ws.better} - \caption{A better trailing whitespace hook} - \label{ex:hook:ws.better} -\end{figure} - -The example of figure~\ref{ex:hook:ws.better} is much more complex, -but also more useful. It parses a unified diff to see if any lines -add trailing whitespace, and prints the name of the file and the line -number of each such occurrence. Even better, if the change adds -trailing whitespace, this hook saves the commit comment and prints the -name of the save file before exiting and telling Mercurial to roll the -transaction back, so you can use -\hgcmdargs{commit}{\hgopt{commit}{-l}~\emph{filename}} to reuse the -saved commit message once you've corrected the problem. - -As a final aside, note in figure~\ref{ex:hook:ws.better} the use of -\command{perl}'s in-place editing feature to get rid of trailing -whitespace from a file. This is concise and useful enough that I will -reproduce it here. -\begin{codesample2} - perl -pi -e 's,[ \textbackslash{}t]+\$,,' filename -\end{codesample2} - -\section{Bundled hooks} - -Mercurial ships with several bundled hooks. You can find them in the -\dirname{hgext} directory of a Mercurial source tree. If you are -using a Mercurial binary package, the hooks will be located in the -\dirname{hgext} directory of wherever your package installer put -Mercurial. - -\subsection{\hgext{acl}---access control for parts of a repository} - -The \hgext{acl} extension lets you control which remote users are -allowed to push changesets to a networked server. You can protect any -portion of a repository (including the entire repo), so that a -specific remote user can push changes that do not affect the protected -portion. - -This extension implements access control based on the identity of the -user performing a push, \emph{not} on who committed the changesets -they're pushing. It makes sense to use this hook only if you have a -locked-down server environment that authenticates remote users, and -you want to be sure that only specific users are allowed to push -changes to that server. - -\subsubsection{Configuring the \hook{acl} hook} - -In order to manage incoming changesets, the \hgext{acl} hook must be -used as a \hook{pretxnchangegroup} hook. This lets it see which files -are modified by each incoming changeset, and roll back a group of -changesets if they modify ``forbidden'' files. Example: -\begin{codesample2} - [hooks] - pretxnchangegroup.acl = python:hgext.acl.hook -\end{codesample2} - -The \hgext{acl} extension is configured using three sections. - -The \rcsection{acl} section has only one entry, \rcitem{acl}{sources}, -which lists the sources of incoming changesets that the hook should -pay attention to. You don't normally need to configure this section. -\begin{itemize} -\item[\rcitem{acl}{serve}] Control incoming changesets that are arriving - from a remote repository over http or ssh. This is the default - value of \rcitem{acl}{sources}, and usually the only setting you'll - need for this configuration item. -\item[\rcitem{acl}{pull}] Control incoming changesets that are - arriving via a pull from a local repository. -\item[\rcitem{acl}{push}] Control incoming changesets that are - arriving via a push from a local repository. -\item[\rcitem{acl}{bundle}] Control incoming changesets that are - arriving from another repository via a bundle. -\end{itemize} - -The \rcsection{acl.allow} section controls the users that are allowed to -add changesets to the repository. If this section is not present, all -users that are not explicitly denied are allowed. If this section is -present, all users that are not explicitly allowed are denied (so an -empty section means that all users are denied). - -The \rcsection{acl.deny} section determines which users are denied -from adding changesets to the repository. If this section is not -present or is empty, no users are denied. - -The syntaxes for the \rcsection{acl.allow} and \rcsection{acl.deny} -sections are identical. On the left of each entry is a glob pattern -that matches files or directories, relative to the root of the -repository; on the right, a user name. - -In the following example, the user \texttt{docwriter} can only push -changes to the \dirname{docs} subtree of the repository, while -\texttt{intern} can push changes to any file or directory except -\dirname{source/sensitive}. -\begin{codesample2} - [acl.allow] - docs/** = docwriter - - [acl.deny] - source/sensitive/** = intern -\end{codesample2} - -\subsubsection{Testing and troubleshooting} - -If you want to test the \hgext{acl} hook, run it with Mercurial's -debugging output enabled. Since you'll probably be running it on a -server where it's not convenient (or sometimes possible) to pass in -the \hggopt{--debug} option, don't forget that you can enable -debugging output in your \hgrc: -\begin{codesample2} - [ui] - debug = true -\end{codesample2} -With this enabled, the \hgext{acl} hook will print enough information -to let you figure out why it is allowing or forbidding pushes from -specific users. - -\subsection{\hgext{bugzilla}---integration with Bugzilla} - -The \hgext{bugzilla} extension adds a comment to a Bugzilla bug -whenever it finds a reference to that bug ID in a commit comment. You -can install this hook on a shared server, so that any time a remote -user pushes changes to this server, the hook gets run. - -It adds a comment to the bug that looks like this (you can configure -the contents of the comment---see below): -\begin{codesample2} - Changeset aad8b264143a, made by Joe User <joe.user@domain.com> in - the frobnitz repository, refers to this bug. - - For complete details, see - http://hg.domain.com/frobnitz?cmd=changeset;node=aad8b264143a - - Changeset description: - Fix bug 10483 by guarding against some NULL pointers -\end{codesample2} -The value of this hook is that it automates the process of updating a -bug any time a changeset refers to it. If you configure the hook -properly, it makes it easy for people to browse straight from a -Bugzilla bug to a changeset that refers to that bug. - -You can use the code in this hook as a starting point for some more -exotic Bugzilla integration recipes. Here are a few possibilities: -\begin{itemize} -\item Require that every changeset pushed to the server have a valid - bug~ID in its commit comment. In this case, you'd want to configure - the hook as a \hook{pretxncommit} hook. This would allow the hook - to reject changes that didn't contain bug IDs. -\item Allow incoming changesets to automatically modify the - \emph{state} of a bug, as well as simply adding a comment. For - example, the hook could recognise the string ``fixed bug 31337'' as - indicating that it should update the state of bug 31337 to - ``requires testing''. -\end{itemize} - -\subsubsection{Configuring the \hook{bugzilla} hook} -\label{sec:hook:bugzilla:config} - -You should configure this hook in your server's \hgrc\ as an -\hook{incoming} hook, for example as follows: -\begin{codesample2} - [hooks] - incoming.bugzilla = python:hgext.bugzilla.hook -\end{codesample2} - -Because of the specialised nature of this hook, and because Bugzilla -was not written with this kind of integration in mind, configuring -this hook is a somewhat involved process. - -Before you begin, you must install the MySQL bindings for Python on -the host(s) where you'll be running the hook. If this is not -available as a binary package for your system, you can download it -from~\cite{web:mysql-python}. - -Configuration information for this hook lives in the -\rcsection{bugzilla} section of your \hgrc. -\begin{itemize} -\item[\rcitem{bugzilla}{version}] The version of Bugzilla installed on - the server. The database schema that Bugzilla uses changes - occasionally, so this hook has to know exactly which schema to use. - At the moment, the only version supported is \texttt{2.16}. -\item[\rcitem{bugzilla}{host}] The hostname of the MySQL server that - stores your Bugzilla data. The database must be configured to allow - connections from whatever host you are running the \hook{bugzilla} - hook on. -\item[\rcitem{bugzilla}{user}] The username with which to connect to - the MySQL server. The database must be configured to allow this - user to connect from whatever host you are running the - \hook{bugzilla} hook on. This user must be able to access and - modify Bugzilla tables. The default value of this item is - \texttt{bugs}, which is the standard name of the Bugzilla user in a - MySQL database. -\item[\rcitem{bugzilla}{password}] The MySQL password for the user you - configured above. This is stored as plain text, so you should make - sure that unauthorised users cannot read the \hgrc\ file where you - store this information. -\item[\rcitem{bugzilla}{db}] The name of the Bugzilla database on the - MySQL server. The default value of this item is \texttt{bugs}, - which is the standard name of the MySQL database where Bugzilla - stores its data. -\item[\rcitem{bugzilla}{notify}] If you want Bugzilla to send out a - notification email to subscribers after this hook has added a - comment to a bug, you will need this hook to run a command whenever - it updates the database. The command to run depends on where you - have installed Bugzilla, but it will typically look something like - this, if you have Bugzilla installed in - \dirname{/var/www/html/bugzilla}: - \begin{codesample4} - cd /var/www/html/bugzilla && ./processmail %s nobody@nowhere.com - \end{codesample4} - The Bugzilla \texttt{processmail} program expects to be given a - bug~ID (the hook replaces ``\texttt{\%s}'' with the bug~ID) and an - email address. It also expects to be able to write to some files in - the directory that it runs in. If Bugzilla and this hook are not - installed on the same machine, you will need to find a way to run - \texttt{processmail} on the server where Bugzilla is installed. -\end{itemize} - -\subsubsection{Mapping committer names to Bugzilla user names} - -By default, the \hgext{bugzilla} hook tries to use the email address -of a changeset's committer as the Bugzilla user name with which to -update a bug. If this does not suit your needs, you can map committer -email addresses to Bugzilla user names using a \rcsection{usermap} -section. - -Each item in the \rcsection{usermap} section contains an email address -on the left, and a Bugzilla user name on the right. -\begin{codesample2} - [usermap] - jane.user@example.com = jane -\end{codesample2} -You can either keep the \rcsection{usermap} data in a normal \hgrc, or -tell the \hgext{bugzilla} hook to read the information from an -external \filename{usermap} file. In the latter case, you can store -\filename{usermap} data by itself in (for example) a user-modifiable -repository. This makes it possible to let your users maintain their -own \rcitem{bugzilla}{usermap} entries. The main \hgrc\ file might -look like this: -\begin{codesample2} - # regular hgrc file refers to external usermap file - [bugzilla] - usermap = /home/hg/repos/userdata/bugzilla-usermap.conf -\end{codesample2} -While the \filename{usermap} file that it refers to might look like -this: -\begin{codesample2} - # bugzilla-usermap.conf - inside a hg repository - [usermap] - stephanie@example.com = steph -\end{codesample2} - -\subsubsection{Configuring the text that gets added to a bug} - -You can configure the text that this hook adds as a comment; you -specify it in the form of a Mercurial template. Several \hgrc\ -entries (still in the \rcsection{bugzilla} section) control this -behaviour. -\begin{itemize} -\item[\texttt{strip}] The number of leading path elements to strip - from a repository's path name to construct a partial path for a URL. - For example, if the repositories on your server live under - \dirname{/home/hg/repos}, and you have a repository whose path is - \dirname{/home/hg/repos/app/tests}, then setting \texttt{strip} to - \texttt{4} will give a partial path of \dirname{app/tests}. The - hook will make this partial path available when expanding a - template, as \texttt{webroot}. -\item[\texttt{template}] The text of the template to use. In addition - to the usual changeset-related variables, this template can use - \texttt{hgweb} (the value of the \texttt{hgweb} configuration item - above) and \texttt{webroot} (the path constructed using - \texttt{strip} above). -\end{itemize} - -In addition, you can add a \rcitem{web}{baseurl} item to the -\rcsection{web} section of your \hgrc. The \hgext{bugzilla} hook will -make this available when expanding a template, as the base string to -use when constructing a URL that will let users browse from a Bugzilla -comment to view a changeset. Example: -\begin{codesample2} - [web] - baseurl = http://hg.domain.com/ -\end{codesample2} - -Here is an example set of \hgext{bugzilla} hook config information. -\begin{codesample2} - [bugzilla] - host = bugzilla.example.com - password = mypassword - version = 2.16 - # server-side repos live in /home/hg/repos, so strip 4 leading - # separators - strip = 4 - hgweb = http://hg.example.com/ - usermap = /home/hg/repos/notify/bugzilla.conf - template = Changeset \{node|short\}, made by \{author\} in the \{webroot\} - repo, refers to this bug.\\nFor complete details, see - \{hgweb\}\{webroot\}?cmd=changeset;node=\{node|short\}\\nChangeset - description:\\n\\t\{desc|tabindent\} -\end{codesample2} - -\subsubsection{Testing and troubleshooting} - -The most common problems with configuring the \hgext{bugzilla} hook -relate to running Bugzilla's \filename{processmail} script and mapping -committer names to user names. - -Recall from section~\ref{sec:hook:bugzilla:config} above that the user -that runs the Mercurial process on the server is also the one that -will run the \filename{processmail} script. The -\filename{processmail} script sometimes causes Bugzilla to write to -files in its configuration directory, and Bugzilla's configuration -files are usually owned by the user that your web server runs under. - -You can cause \filename{processmail} to be run with the suitable -user's identity using the \command{sudo} command. Here is an example -entry for a \filename{sudoers} file. -\begin{codesample2} - hg_user = (httpd_user) NOPASSWD: /var/www/html/bugzilla/processmail-wrapper %s -\end{codesample2} -This allows the \texttt{hg\_user} user to run a -\filename{processmail-wrapper} program under the identity of -\texttt{httpd\_user}. - -This indirection through a wrapper script is necessary, because -\filename{processmail} expects to be run with its current directory -set to wherever you installed Bugzilla; you can't specify that kind of -constraint in a \filename{sudoers} file. The contents of the wrapper -script are simple: -\begin{codesample2} - #!/bin/sh - cd `dirname $0` && ./processmail "$1" nobody@example.com -\end{codesample2} -It doesn't seem to matter what email address you pass to -\filename{processmail}. - -If your \rcsection{usermap} is not set up correctly, users will see an -error message from the \hgext{bugzilla} hook when they push changes -to the server. The error message will look like this: -\begin{codesample2} - cannot find bugzilla user id for john.q.public@example.com -\end{codesample2} -What this means is that the committer's address, -\texttt{john.q.public@example.com}, is not a valid Bugzilla user name, -nor does it have an entry in your \rcsection{usermap} that maps it to -a valid Bugzilla user name. - -\subsection{\hgext{notify}---send email notifications} - -Although Mercurial's built-in web server provides RSS feeds of changes -in every repository, many people prefer to receive change -notifications via email. The \hgext{notify} hook lets you send out -notifications to a set of email addresses whenever changesets arrive -that those subscribers are interested in. - -As with the \hgext{bugzilla} hook, the \hgext{notify} hook is -template-driven, so you can customise the contents of the notification -messages that it sends. - -By default, the \hgext{notify} hook includes a diff of every changeset -that it sends out; you can limit the size of the diff, or turn this -feature off entirely. It is useful for letting subscribers review -changes immediately, rather than clicking to follow a URL. - -\subsubsection{Configuring the \hgext{notify} hook} - -You can set up the \hgext{notify} hook to send one email message per -incoming changeset, or one per incoming group of changesets (all those -that arrived in a single pull or push). -\begin{codesample2} - [hooks] - # send one email per group of changes - changegroup.notify = python:hgext.notify.hook - # send one email per change - incoming.notify = python:hgext.notify.hook -\end{codesample2} - -Configuration information for this hook lives in the -\rcsection{notify} section of a \hgrc\ file. -\begin{itemize} -\item[\rcitem{notify}{test}] By default, this hook does not send out - email at all; instead, it prints the message that it \emph{would} - send. Set this item to \texttt{false} to allow email to be sent. - The reason that sending of email is turned off by default is that it - takes several tries to configure this extension exactly as you would - like, and it would be bad form to spam subscribers with a number of - ``broken'' notifications while you debug your configuration. -\item[\rcitem{notify}{config}] The path to a configuration file that - contains subscription information. This is kept separate from the - main \hgrc\ so that you can maintain it in a repository of its own. - People can then clone that repository, update their subscriptions, - and push the changes back to your server. -\item[\rcitem{notify}{strip}] The number of leading path separator - characters to strip from a repository's path, when deciding whether - a repository has subscribers. For example, if the repositories on - your server live in \dirname{/home/hg/repos}, and \hgext{notify} is - considering a repository named \dirname{/home/hg/repos/shared/test}, - setting \rcitem{notify}{strip} to \texttt{4} will cause - \hgext{notify} to trim the path it considers down to - \dirname{shared/test}, and it will match subscribers against that. -\item[\rcitem{notify}{template}] The template text to use when sending - messages. This specifies both the contents of the message header - and its body. -\item[\rcitem{notify}{maxdiff}] The maximum number of lines of diff - data to append to the end of a message. If a diff is longer than - this, it is truncated. By default, this is set to 300. Set this to - \texttt{0} to omit diffs from notification emails. -\item[\rcitem{notify}{sources}] A list of sources of changesets to - consider. This lets you limit \hgext{notify} to only sending out - email about changes that remote users pushed into this repository - via a server, for example. See section~\ref{sec:hook:sources} for - the sources you can specify here. -\end{itemize} - -If you set the \rcitem{web}{baseurl} item in the \rcsection{web} -section, you can use it in a template; it will be available as -\texttt{webroot}. - -Here is an example set of \hgext{notify} configuration information. -\begin{codesample2} - [notify] - # really send email - test = false - # subscriber data lives in the notify repo - config = /home/hg/repos/notify/notify.conf - # repos live in /home/hg/repos on server, so strip 4 "/" chars - strip = 4 - template = X-Hg-Repo: \{webroot\} - Subject: \{webroot\}: \{desc|firstline|strip\} - From: \{author\} - - changeset \{node|short\} in \{root\} - details: \{baseurl\}\{webroot\}?cmd=changeset;node=\{node|short\} - description: - \{desc|tabindent|strip\} - - [web] - baseurl = http://hg.example.com/ -\end{codesample2} - -This will produce a message that looks like the following: -\begin{codesample2} - X-Hg-Repo: tests/slave - Subject: tests/slave: Handle error case when slave has no buffers - Date: Wed, 2 Aug 2006 15:25:46 -0700 (PDT) - - changeset 3cba9bfe74b5 in /home/hg/repos/tests/slave - details: http://hg.example.com/tests/slave?cmd=changeset;node=3cba9bfe74b5 - description: - Handle error case when slave has no buffers - diffs (54 lines): - - diff -r 9d95df7cf2ad -r 3cba9bfe74b5 include/tests.h - --- a/include/tests.h Wed Aug 02 15:19:52 2006 -0700 - +++ b/include/tests.h Wed Aug 02 15:25:26 2006 -0700 - @@ -212,6 +212,15 @@ static __inline__ void test_headers(void *h) - [...snip...] -\end{codesample2} - -\subsubsection{Testing and troubleshooting} - -Do not forget that by default, the \hgext{notify} extension \emph{will - not send any mail} until you explicitly configure it to do so, by -setting \rcitem{notify}{test} to \texttt{false}. Until you do that, -it simply prints the message it \emph{would} send. - -\section{Information for writers of hooks} -\label{sec:hook:ref} - -\subsection{In-process hook execution} - -An in-process hook is called with arguments of the following form: -\begin{codesample2} - def myhook(ui, repo, **kwargs): - pass -\end{codesample2} -The \texttt{ui} parameter is a \pymodclass{mercurial.ui}{ui} object. -The \texttt{repo} parameter is a -\pymodclass{mercurial.localrepo}{localrepository} object. The -names and values of the \texttt{**kwargs} parameters depend on the -hook being invoked, with the following common features: -\begin{itemize} -\item If a parameter is named \texttt{node} or - \texttt{parent\emph{N}}, it will contain a hexadecimal changeset ID. - The empty string is used to represent ``null changeset ID'' instead - of a string of zeroes. -\item If a parameter is named \texttt{url}, it will contain the URL of - a remote repository, if that can be determined. -\item Boolean-valued parameters are represented as Python - \texttt{bool} objects. -\end{itemize} - -An in-process hook is called without a change to the process's working -directory (unlike external hooks, which are run in the root of the -repository). It must not change the process's working directory, or -it will cause any calls it makes into the Mercurial API to fail. - -If a hook returns a boolean ``false'' value, it is considered to have -succeeded. If it returns a boolean ``true'' value or raises an -exception, it is considered to have failed. A useful way to think of -the calling convention is ``tell me if you fail''. - -Note that changeset IDs are passed into Python hooks as hexadecimal -strings, not the binary hashes that Mercurial's APIs normally use. To -convert a hash from hex to binary, use the -\pymodfunc{mercurial.node}{bin} function. - -\subsection{External hook execution} - -An external hook is passed to the shell of the user running Mercurial. -Features of that shell, such as variable substitution and command -redirection, are available. The hook is run in the root directory of -the repository (unlike in-process hooks, which are run in the same -directory that Mercurial was run in). - -Hook parameters are passed to the hook as environment variables. Each -environment variable's name is converted in upper case and prefixed -with the string ``\texttt{HG\_}''. For example, if the name of a -parameter is ``\texttt{node}'', the name of the environment variable -representing that parameter will be ``\texttt{HG\_NODE}''. - -A boolean parameter is represented as the string ``\texttt{1}'' for -``true'', ``\texttt{0}'' for ``false''. If an environment variable is -named \envar{HG\_NODE}, \envar{HG\_PARENT1} or \envar{HG\_PARENT2}, it -contains a changeset ID represented as a hexadecimal string. The -empty string is used to represent ``null changeset ID'' instead of a -string of zeroes. If an environment variable is named -\envar{HG\_URL}, it will contain the URL of a remote repository, if -that can be determined. - -If a hook exits with a status of zero, it is considered to have -succeeded. If it exits with a non-zero status, it is considered to -have failed. - -\subsection{Finding out where changesets come from} - -A hook that involves the transfer of changesets between a local -repository and another may be able to find out information about the -``far side''. Mercurial knows \emph{how} changes are being -transferred, and in many cases \emph{where} they are being transferred -to or from. - -\subsubsection{Sources of changesets} -\label{sec:hook:sources} - -Mercurial will tell a hook what means are, or were, used to transfer -changesets between repositories. This is provided by Mercurial in a -Python parameter named \texttt{source}, or an environment variable named -\envar{HG\_SOURCE}. - -\begin{itemize} -\item[\texttt{serve}] Changesets are transferred to or from a remote - repository over http or ssh. -\item[\texttt{pull}] Changesets are being transferred via a pull from - one repository into another. -\item[\texttt{push}] Changesets are being transferred via a push from - one repository into another. -\item[\texttt{bundle}] Changesets are being transferred to or from a - bundle. -\end{itemize} - -\subsubsection{Where changes are going---remote repository URLs} -\label{sec:hook:url} - -When possible, Mercurial will tell a hook the location of the ``far -side'' of an activity that transfers changeset data between -repositories. This is provided by Mercurial in a Python parameter -named \texttt{url}, or an environment variable named \envar{HG\_URL}. - -This information is not always known. If a hook is invoked in a -repository that is being served via http or ssh, Mercurial cannot tell -where the remote repository is, but it may know where the client is -connecting from. In such cases, the URL will take one of the -following forms: -\begin{itemize} -\item \texttt{remote:ssh:\emph{ip-address}}---remote ssh client, at - the given IP address. -\item \texttt{remote:http:\emph{ip-address}}---remote http client, at - the given IP address. If the client is using SSL, this will be of - the form \texttt{remote:https:\emph{ip-address}}. -\item Empty---no information could be discovered about the remote - client. -\end{itemize} - -\section{Hook reference} - -\subsection{\hook{changegroup}---after remote changesets added} -\label{sec:hook:changegroup} - -This hook is run after a group of pre-existing changesets has been -added to the repository, for example via a \hgcmd{pull} or -\hgcmd{unbundle}. This hook is run once per operation that added one -or more changesets. This is in contrast to the \hook{incoming} hook, -which is run once per changeset, regardless of whether the changesets -arrive in a group. - -Some possible uses for this hook include kicking off an automated -build or test of the added changesets, updating a bug database, or -notifying subscribers that a repository contains new changes. - -Parameters to this hook: -\begin{itemize} -\item[\texttt{node}] A changeset ID. The changeset ID of the first - changeset in the group that was added. All changesets between this - and \index{tags!\texttt{tip}}\texttt{tip}, inclusive, were added by - a single \hgcmd{pull}, \hgcmd{push} or \hgcmd{unbundle}. -\item[\texttt{source}] A string. The source of these changes. See - section~\ref{sec:hook:sources} for details. -\item[\texttt{url}] A URL. The location of the remote repository, if - known. See section~\ref{sec:hook:url} for more information. -\end{itemize} - -See also: \hook{incoming} (section~\ref{sec:hook:incoming}), -\hook{prechangegroup} (section~\ref{sec:hook:prechangegroup}), -\hook{pretxnchangegroup} (section~\ref{sec:hook:pretxnchangegroup}) - -\subsection{\hook{commit}---after a new changeset is created} -\label{sec:hook:commit} - -This hook is run after a new changeset has been created. - -Parameters to this hook: -\begin{itemize} -\item[\texttt{node}] A changeset ID. The changeset ID of the newly - committed changeset. -\item[\texttt{parent1}] A changeset ID. The changeset ID of the first - parent of the newly committed changeset. -\item[\texttt{parent2}] A changeset ID. The changeset ID of the second - parent of the newly committed changeset. -\end{itemize} - -See also: \hook{precommit} (section~\ref{sec:hook:precommit}), -\hook{pretxncommit} (section~\ref{sec:hook:pretxncommit}) - -\subsection{\hook{incoming}---after one remote changeset is added} -\label{sec:hook:incoming} - -This hook is run after a pre-existing changeset has been added to the -repository, for example via a \hgcmd{push}. If a group of changesets -was added in a single operation, this hook is called once for each -added changeset. - -You can use this hook for the same purposes as the \hook{changegroup} -hook (section~\ref{sec:hook:changegroup}); it's simply more convenient -sometimes to run a hook once per group of changesets, while other -times it's handier once per changeset. - -Parameters to this hook: -\begin{itemize} -\item[\texttt{node}] A changeset ID. The ID of the newly added - changeset. -\item[\texttt{source}] A string. The source of these changes. See - section~\ref{sec:hook:sources} for details. -\item[\texttt{url}] A URL. The location of the remote repository, if - known. See section~\ref{sec:hook:url} for more information. -\end{itemize} - -See also: \hook{changegroup} (section~\ref{sec:hook:changegroup}) \hook{prechangegroup} (section~\ref{sec:hook:prechangegroup}), \hook{pretxnchangegroup} (section~\ref{sec:hook:pretxnchangegroup}) - -\subsection{\hook{outgoing}---after changesets are propagated} -\label{sec:hook:outgoing} - -This hook is run after a group of changesets has been propagated out -of this repository, for example by a \hgcmd{push} or \hgcmd{bundle} -command. - -One possible use for this hook is to notify administrators that -changes have been pulled. - -Parameters to this hook: -\begin{itemize} -\item[\texttt{node}] A changeset ID. The changeset ID of the first - changeset of the group that was sent. -\item[\texttt{source}] A string. The source of the of the operation - (see section~\ref{sec:hook:sources}). If a remote client pulled - changes from this repository, \texttt{source} will be - \texttt{serve}. If the client that obtained changes from this - repository was local, \texttt{source} will be \texttt{bundle}, - \texttt{pull}, or \texttt{push}, depending on the operation the - client performed. -\item[\texttt{url}] A URL. The location of the remote repository, if - known. See section~\ref{sec:hook:url} for more information. -\end{itemize} - -See also: \hook{preoutgoing} (section~\ref{sec:hook:preoutgoing}) - -\subsection{\hook{prechangegroup}---before starting to add remote changesets} -\label{sec:hook:prechangegroup} - -This controlling hook is run before Mercurial begins to add a group of -changesets from another repository. - -This hook does not have any information about the changesets to be -added, because it is run before transmission of those changesets is -allowed to begin. If this hook fails, the changesets will not be -transmitted. - -One use for this hook is to prevent external changes from being added -to a repository. For example, you could use this to ``freeze'' a -server-hosted branch temporarily or permanently so that users cannot -push to it, while still allowing a local administrator to modify the -repository. - -Parameters to this hook: -\begin{itemize} -\item[\texttt{source}] A string. The source of these changes. See - section~\ref{sec:hook:sources} for details. -\item[\texttt{url}] A URL. The location of the remote repository, if - known. See section~\ref{sec:hook:url} for more information. -\end{itemize} - -See also: \hook{changegroup} (section~\ref{sec:hook:changegroup}), -\hook{incoming} (section~\ref{sec:hook:incoming}), , -\hook{pretxnchangegroup} (section~\ref{sec:hook:pretxnchangegroup}) - -\subsection{\hook{precommit}---before starting to commit a changeset} -\label{sec:hook:precommit} - -This hook is run before Mercurial begins to commit a new changeset. -It is run before Mercurial has any of the metadata for the commit, -such as the files to be committed, the commit message, or the commit -date. - -One use for this hook is to disable the ability to commit new -changesets, while still allowing incoming changesets. Another is to -run a build or test, and only allow the commit to begin if the build -or test succeeds. - -Parameters to this hook: -\begin{itemize} -\item[\texttt{parent1}] A changeset ID. The changeset ID of the first - parent of the working directory. -\item[\texttt{parent2}] A changeset ID. The changeset ID of the second - parent of the working directory. -\end{itemize} -If the commit proceeds, the parents of the working directory will -become the parents of the new changeset. - -See also: \hook{commit} (section~\ref{sec:hook:commit}), -\hook{pretxncommit} (section~\ref{sec:hook:pretxncommit}) - -\subsection{\hook{preoutgoing}---before starting to propagate changesets} -\label{sec:hook:preoutgoing} - -This hook is invoked before Mercurial knows the identities of the -changesets to be transmitted. - -One use for this hook is to prevent changes from being transmitted to -another repository. - -Parameters to this hook: -\begin{itemize} -\item[\texttt{source}] A string. The source of the operation that is - attempting to obtain changes from this repository (see - section~\ref{sec:hook:sources}). See the documentation for the - \texttt{source} parameter to the \hook{outgoing} hook, in - section~\ref{sec:hook:outgoing}, for possible values of this - parameter. -\item[\texttt{url}] A URL. The location of the remote repository, if - known. See section~\ref{sec:hook:url} for more information. -\end{itemize} - -See also: \hook{outgoing} (section~\ref{sec:hook:outgoing}) - -\subsection{\hook{pretag}---before tagging a changeset} -\label{sec:hook:pretag} - -This controlling hook is run before a tag is created. If the hook -succeeds, creation of the tag proceeds. If the hook fails, the tag is -not created. - -Parameters to this hook: -\begin{itemize} -\item[\texttt{local}] A boolean. Whether the tag is local to this - repository instance (i.e.~stored in \sfilename{.hg/localtags}) or - managed by Mercurial (stored in \sfilename{.hgtags}). -\item[\texttt{node}] A changeset ID. The ID of the changeset to be tagged. -\item[\texttt{tag}] A string. The name of the tag to be created. -\end{itemize} - -If the tag to be created is revision-controlled, the \hook{precommit} -and \hook{pretxncommit} hooks (sections~\ref{sec:hook:commit} -and~\ref{sec:hook:pretxncommit}) will also be run. - -See also: \hook{tag} (section~\ref{sec:hook:tag}) - -\subsection{\hook{pretxnchangegroup}---before completing addition of - remote changesets} -\label{sec:hook:pretxnchangegroup} - -This controlling hook is run before a transaction---that manages the -addition of a group of new changesets from outside the -repository---completes. If the hook succeeds, the transaction -completes, and all of the changesets become permanent within this -repository. If the hook fails, the transaction is rolled back, and -the data for the changesets is erased. - -This hook can access the metadata associated with the almost-added -changesets, but it should not do anything permanent with this data. -It must also not modify the working directory. - -While this hook is running, if other Mercurial processes access this -repository, they will be able to see the almost-added changesets as if -they are permanent. This may lead to race conditions if you do not -take steps to avoid them. - -This hook can be used to automatically vet a group of changesets. If -the hook fails, all of the changesets are ``rejected'' when the -transaction rolls back. - -Parameters to this hook: -\begin{itemize} -\item[\texttt{node}] A changeset ID. The changeset ID of the first - changeset in the group that was added. All changesets between this - and \index{tags!\texttt{tip}}\texttt{tip}, inclusive, were added by - a single \hgcmd{pull}, \hgcmd{push} or \hgcmd{unbundle}. -\item[\texttt{source}] A string. The source of these changes. See - section~\ref{sec:hook:sources} for details. -\item[\texttt{url}] A URL. The location of the remote repository, if - known. See section~\ref{sec:hook:url} for more information. -\end{itemize} - -See also: \hook{changegroup} (section~\ref{sec:hook:changegroup}), -\hook{incoming} (section~\ref{sec:hook:incoming}), -\hook{prechangegroup} (section~\ref{sec:hook:prechangegroup}) - -\subsection{\hook{pretxncommit}---before completing commit of new changeset} -\label{sec:hook:pretxncommit} - -This controlling hook is run before a transaction---that manages a new -commit---completes. If the hook succeeds, the transaction completes -and the changeset becomes permanent within this repository. If the -hook fails, the transaction is rolled back, and the commit data is -erased. - -This hook can access the metadata associated with the almost-new -changeset, but it should not do anything permanent with this data. It -must also not modify the working directory. - -While this hook is running, if other Mercurial processes access this -repository, they will be able to see the almost-new changeset as if it -is permanent. This may lead to race conditions if you do not take -steps to avoid them. - -Parameters to this hook: -\begin{itemize} -\item[\texttt{node}] A changeset ID. The changeset ID of the newly - committed changeset. -\item[\texttt{parent1}] A changeset ID. The changeset ID of the first - parent of the newly committed changeset. -\item[\texttt{parent2}] A changeset ID. The changeset ID of the second - parent of the newly committed changeset. -\end{itemize} - -See also: \hook{precommit} (section~\ref{sec:hook:precommit}) - -\subsection{\hook{preupdate}---before updating or merging working directory} -\label{sec:hook:preupdate} - -This controlling hook is run before an update or merge of the working -directory begins. It is run only if Mercurial's normal pre-update -checks determine that the update or merge can proceed. If the hook -succeeds, the update or merge may proceed; if it fails, the update or -merge does not start. - -Parameters to this hook: -\begin{itemize} -\item[\texttt{parent1}] A changeset ID. The ID of the parent that the - working directory is to be updated to. If the working directory is - being merged, it will not change this parent. -\item[\texttt{parent2}] A changeset ID. Only set if the working - directory is being merged. The ID of the revision that the working - directory is being merged with. -\end{itemize} - -See also: \hook{update} (section~\ref{sec:hook:update}) - -\subsection{\hook{tag}---after tagging a changeset} -\label{sec:hook:tag} - -This hook is run after a tag has been created. - -Parameters to this hook: -\begin{itemize} -\item[\texttt{local}] A boolean. Whether the new tag is local to this - repository instance (i.e.~stored in \sfilename{.hg/localtags}) or - managed by Mercurial (stored in \sfilename{.hgtags}). -\item[\texttt{node}] A changeset ID. The ID of the changeset that was - tagged. -\item[\texttt{tag}] A string. The name of the tag that was created. -\end{itemize} - -If the created tag is revision-controlled, the \hook{commit} hook -(section~\ref{sec:hook:commit}) is run before this hook. - -See also: \hook{pretag} (section~\ref{sec:hook:pretag}) - -\subsection{\hook{update}---after updating or merging working directory} -\label{sec:hook:update} - -This hook is run after an update or merge of the working directory -completes. Since a merge can fail (if the external \command{hgmerge} -command fails to resolve conflicts in a file), this hook communicates -whether the update or merge completed cleanly. - -\begin{itemize} -\item[\texttt{error}] A boolean. Indicates whether the update or - merge completed successfully. -\item[\texttt{parent1}] A changeset ID. The ID of the parent that the - working directory was updated to. If the working directory was - merged, it will not have changed this parent. -\item[\texttt{parent2}] A changeset ID. Only set if the working - directory was merged. The ID of the revision that the working - directory was merged with. -\end{itemize} - -See also: \hook{preupdate} (section~\ref{sec:hook:preupdate}) - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "00book" -%%% End:
--- a/en/intro.tex Wed Jan 21 14:16:38 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,561 +0,0 @@ -\chapter{Introduction} -\label{chap:intro} - -\section{About revision control} - -Revision control is the process of managing multiple versions of a -piece of information. In its simplest form, this is something that -many people do by hand: every time you modify a file, save it under a -new name that contains a number, each one higher than the number of -the preceding version. - -Manually managing multiple versions of even a single file is an -error-prone task, though, so software tools to help automate this -process have long been available. The earliest automated revision -control tools were intended to help a single user to manage revisions -of a single file. Over the past few decades, the scope of revision -control tools has expanded greatly; they now manage multiple files, -and help multiple people to work together. The best modern revision -control tools have no problem coping with thousands of people working -together on projects that consist of hundreds of thousands of files. - -\subsection{Why use revision control?} - -There are a number of reasons why you or your team might want to use -an automated revision control tool for a project. -\begin{itemize} -\item It will track the history and evolution of your project, so you - don't have to. For every change, you'll have a log of \emph{who} - made it; \emph{why} they made it; \emph{when} they made it; and - \emph{what} the change was. -\item When you're working with other people, revision control software - makes it easier for you to collaborate. For example, when people - more or less simultaneously make potentially incompatible changes, - the software will help you to identify and resolve those conflicts. -\item It can help you to recover from mistakes. If you make a change - that later turns out to be in error, you can revert to an earlier - version of one or more files. In fact, a \emph{really} good - revision control tool will even help you to efficiently figure out - exactly when a problem was introduced (see - section~\ref{sec:undo:bisect} for details). -\item It will help you to work simultaneously on, and manage the drift - between, multiple versions of your project. -\end{itemize} -Most of these reasons are equally valid---at least in theory---whether -you're working on a project by yourself, or with a hundred other -people. - -A key question about the practicality of revision control at these two -different scales (``lone hacker'' and ``huge team'') is how its -\emph{benefits} compare to its \emph{costs}. A revision control tool -that's difficult to understand or use is going to impose a high cost. - -A five-hundred-person project is likely to collapse under its own -weight almost immediately without a revision control tool and process. -In this case, the cost of using revision control might hardly seem -worth considering, since \emph{without} it, failure is almost -guaranteed. - -On the other hand, a one-person ``quick hack'' might seem like a poor -place to use a revision control tool, because surely the cost of using -one must be close to the overall cost of the project. Right? - -Mercurial uniquely supports \emph{both} of these scales of -development. You can learn the basics in just a few minutes, and due -to its low overhead, you can apply revision control to the smallest of -projects with ease. Its simplicity means you won't have a lot of -abstruse concepts or command sequences competing for mental space with -whatever you're \emph{really} trying to do. At the same time, -Mercurial's high performance and peer-to-peer nature let you scale -painlessly to handle large projects. - -No revision control tool can rescue a poorly run project, but a good -choice of tools can make a huge difference to the fluidity with which -you can work on a project. - -\subsection{The many names of revision control} - -Revision control is a diverse field, so much so that it doesn't -actually have a single name or acronym. Here are a few of the more -common names and acronyms you'll encounter: -\begin{itemize} -\item Revision control (RCS) -\item Software configuration management (SCM), or configuration management -\item Source code management -\item Source code control, or source control -\item Version control (VCS) -\end{itemize} -Some people claim that these terms actually have different meanings, -but in practice they overlap so much that there's no agreed or even -useful way to tease them apart. - -\section{A short history of revision control} - -The best known of the old-time revision control tools is SCCS (Source -Code Control System), which Marc Rochkind wrote at Bell Labs, in the -early 1970s. SCCS operated on individual files, and required every -person working on a project to have access to a shared workspace on a -single system. Only one person could modify a file at any time; -arbitration for access to files was via locks. It was common for -people to lock files, and later forget to unlock them, preventing -anyone else from modifying those files without the help of an -administrator. - -Walter Tichy developed a free alternative to SCCS in the early 1980s; -he called his program RCS (Revison Control System). Like SCCS, RCS -required developers to work in a single shared workspace, and to lock -files to prevent multiple people from modifying them simultaneously. - -Later in the 1980s, Dick Grune used RCS as a building block for a set -of shell scripts he initially called cmt, but then renamed to CVS -(Concurrent Versions System). The big innovation of CVS was that it -let developers work simultaneously and somewhat independently in their -own personal workspaces. The personal workspaces prevented developers -from stepping on each other's toes all the time, as was common with -SCCS and RCS. Each developer had a copy of every project file, and -could modify their copies independently. They had to merge their -edits prior to committing changes to the central repository. - -Brian Berliner took Grune's original scripts and rewrote them in~C, -releasing in 1989 the code that has since developed into the modern -version of CVS. CVS subsequently acquired the ability to operate over -a network connection, giving it a client/server architecture. CVS's -architecture is centralised; only the server has a copy of the history -of the project. Client workspaces just contain copies of recent -versions of the project's files, and a little metadata to tell them -where the server is. CVS has been enormously successful; it is -probably the world's most widely used revision control system. - -In the early 1990s, Sun Microsystems developed an early distributed -revision control system, called TeamWare. A TeamWare workspace -contains a complete copy of the project's history. TeamWare has no -notion of a central repository. (CVS relied upon RCS for its history -storage; TeamWare used SCCS.) - -As the 1990s progressed, awareness grew of a number of problems with -CVS. It records simultaneous changes to multiple files individually, -instead of grouping them together as a single logically atomic -operation. It does not manage its file hierarchy well; it is easy to -make a mess of a repository by renaming files and directories. Worse, -its source code is difficult to read and maintain, which made the -``pain level'' of fixing these architectural problems prohibitive. - -In 2001, Jim Blandy and Karl Fogel, two developers who had worked on -CVS, started a project to replace it with a tool that would have a -better architecture and cleaner code. The result, Subversion, does -not stray from CVS's centralised client/server model, but it adds -multi-file atomic commits, better namespace management, and a number -of other features that make it a generally better tool than CVS. -Since its initial release, it has rapidly grown in popularity. - -More or less simultaneously, Graydon Hoare began working on an -ambitious distributed revision control system that he named Monotone. -While Monotone addresses many of CVS's design flaws and has a -peer-to-peer architecture, it goes beyond earlier (and subsequent) -revision control tools in a number of innovative ways. It uses -cryptographic hashes as identifiers, and has an integral notion of -``trust'' for code from different sources. - -Mercurial began life in 2005. While a few aspects of its design are -influenced by Monotone, Mercurial focuses on ease of use, high -performance, and scalability to very large projects. - -\section{Trends in revision control} - -There has been an unmistakable trend in the development and use of -revision control tools over the past four decades, as people have -become familiar with the capabilities of their tools and constrained -by their limitations. - -The first generation began by managing single files on individual -computers. Although these tools represented a huge advance over -ad-hoc manual revision control, their locking model and reliance on a -single computer limited them to small, tightly-knit teams. - -The second generation loosened these constraints by moving to -network-centered architectures, and managing entire projects at a -time. As projects grew larger, they ran into new problems. With -clients needing to talk to servers very frequently, server scaling -became an issue for large projects. An unreliable network connection -could prevent remote users from being able to talk to the server at -all. As open source projects started making read-only access -available anonymously to anyone, people without commit privileges -found that they could not use the tools to interact with a project in -a natural way, as they could not record their changes. - -The current generation of revision control tools is peer-to-peer in -nature. All of these systems have dropped the dependency on a single -central server, and allow people to distribute their revision control -data to where it's actually needed. Collaboration over the Internet -has moved from constrained by technology to a matter of choice and -consensus. Modern tools can operate offline indefinitely and -autonomously, with a network connection only needed when syncing -changes with another repository. - -\section{A few of the advantages of distributed revision control} - -Even though distributed revision control tools have for several years -been as robust and usable as their previous-generation counterparts, -people using older tools have not yet necessarily woken up to their -advantages. There are a number of ways in which distributed tools -shine relative to centralised ones. - -For an individual developer, distributed tools are almost always much -faster than centralised tools. This is for a simple reason: a -centralised tool needs to talk over the network for many common -operations, because most metadata is stored in a single copy on the -central server. A distributed tool stores all of its metadata -locally. All else being equal, talking over the network adds overhead -to a centralised tool. Don't underestimate the value of a snappy, -responsive tool: you're going to spend a lot of time interacting with -your revision control software. - -Distributed tools are indifferent to the vagaries of your server -infrastructure, again because they replicate metadata to so many -locations. If you use a centralised system and your server catches -fire, you'd better hope that your backup media are reliable, and that -your last backup was recent and actually worked. With a distributed -tool, you have many backups available on every contributor's computer. - -The reliability of your network will affect distributed tools far less -than it will centralised tools. You can't even use a centralised tool -without a network connection, except for a few highly constrained -commands. With a distributed tool, if your network connection goes -down while you're working, you may not even notice. The only thing -you won't be able to do is talk to repositories on other computers, -something that is relatively rare compared with local operations. If -you have a far-flung team of collaborators, this may be significant. - -\subsection{Advantages for open source projects} - -If you take a shine to an open source project and decide that you -would like to start hacking on it, and that project uses a distributed -revision control tool, you are at once a peer with the people who -consider themselves the ``core'' of that project. If they publish -their repositories, you can immediately copy their project history, -start making changes, and record your work, using the same tools in -the same ways as insiders. By contrast, with a centralised tool, you -must use the software in a ``read only'' mode unless someone grants -you permission to commit changes to their central server. Until then, -you won't be able to record changes, and your local modifications will -be at risk of corruption any time you try to update your client's view -of the repository. - -\subsubsection{The forking non-problem} - -It has been suggested that distributed revision control tools pose -some sort of risk to open source projects because they make it easy to -``fork'' the development of a project. A fork happens when there are -differences in opinion or attitude between groups of developers that -cause them to decide that they can't work together any longer. Each -side takes a more or less complete copy of the project's source code, -and goes off in its own direction. - -Sometimes the camps in a fork decide to reconcile their differences. -With a centralised revision control system, the \emph{technical} -process of reconciliation is painful, and has to be performed largely -by hand. You have to decide whose revision history is going to -``win'', and graft the other team's changes into the tree somehow. -This usually loses some or all of one side's revision history. - -What distributed tools do with respect to forking is they make forking -the \emph{only} way to develop a project. Every single change that -you make is potentially a fork point. The great strength of this -approach is that a distributed revision control tool has to be really -good at \emph{merging} forks, because forks are absolutely -fundamental: they happen all the time. - -If every piece of work that everybody does, all the time, is framed in -terms of forking and merging, then what the open source world refers -to as a ``fork'' becomes \emph{purely} a social issue. If anything, -distributed tools \emph{lower} the likelihood of a fork: -\begin{itemize} -\item They eliminate the social distinction that centralised tools - impose: that between insiders (people with commit access) and - outsiders (people without). -\item They make it easier to reconcile after a social fork, because - all that's involved from the perspective of the revision control - software is just another merge. -\end{itemize} - -Some people resist distributed tools because they want to retain tight -control over their projects, and they believe that centralised tools -give them this control. However, if you're of this belief, and you -publish your CVS or Subversion repositories publically, there are -plenty of tools available that can pull out your entire project's -history (albeit slowly) and recreate it somewhere that you don't -control. So while your control in this case is illusory, you are -forgoing the ability to fluidly collaborate with whatever people feel -compelled to mirror and fork your history. - -\subsection{Advantages for commercial projects} - -Many commercial projects are undertaken by teams that are scattered -across the globe. Contributors who are far from a central server will -see slower command execution and perhaps less reliability. Commercial -revision control systems attempt to ameliorate these problems with -remote-site replication add-ons that are typically expensive to buy -and cantankerous to administer. A distributed system doesn't suffer -from these problems in the first place. Better yet, you can easily -set up multiple authoritative servers, say one per site, so that -there's no redundant communication between repositories over expensive -long-haul network links. - -Centralised revision control systems tend to have relatively low -scalability. It's not unusual for an expensive centralised system to -fall over under the combined load of just a few dozen concurrent -users. Once again, the typical response tends to be an expensive and -clunky replication facility. Since the load on a central server---if -you have one at all---is many times lower with a distributed -tool (because all of the data is replicated everywhere), a single -cheap server can handle the needs of a much larger team, and -replication to balance load becomes a simple matter of scripting. - -If you have an employee in the field, troubleshooting a problem at a -customer's site, they'll benefit from distributed revision control. -The tool will let them generate custom builds, try different fixes in -isolation from each other, and search efficiently through history for -the sources of bugs and regressions in the customer's environment, all -without needing to connect to your company's network. - -\section{Why choose Mercurial?} - -Mercurial has a unique set of properties that make it a particularly -good choice as a revision control system. -\begin{itemize} -\item It is easy to learn and use. -\item It is lightweight. -\item It scales excellently. -\item It is easy to customise. -\end{itemize} - -If you are at all familiar with revision control systems, you should -be able to get up and running with Mercurial in less than five -minutes. Even if not, it will take no more than a few minutes -longer. Mercurial's command and feature sets are generally uniform -and consistent, so you can keep track of a few general rules instead -of a host of exceptions. - -On a small project, you can start working with Mercurial in moments. -Creating new changes and branches; transferring changes around -(whether locally or over a network); and history and status operations -are all fast. Mercurial attempts to stay nimble and largely out of -your way by combining low cognitive overhead with blazingly fast -operations. - -The usefulness of Mercurial is not limited to small projects: it is -used by projects with hundreds to thousands of contributors, each -containing tens of thousands of files and hundreds of megabytes of -source code. - -If the core functionality of Mercurial is not enough for you, it's -easy to build on. Mercurial is well suited to scripting tasks, and -its clean internals and implementation in Python make it easy to add -features in the form of extensions. There are a number of popular and -useful extensions already available, ranging from helping to identify -bugs to improving performance. - -\section{Mercurial compared with other tools} - -Before you read on, please understand that this section necessarily -reflects my own experiences, interests, and (dare I say it) biases. I -have used every one of the revision control tools listed below, in -most cases for several years at a time. - - -\subsection{Subversion} - -Subversion is a popular revision control tool, developed to replace -CVS. It has a centralised client/server architecture. - -Subversion and Mercurial have similarly named commands for performing -the same operations, so if you're familiar with one, it is easy to -learn to use the other. Both tools are portable to all popular -operating systems. - -Prior to version 1.5, Subversion had no useful support for merges. -At the time of writing, its merge tracking capability is new, and known to be -\href{http://svnbook.red-bean.com/nightly/en/svn.branchmerge.advanced.html#svn.branchmerge.advanced.finalword}{complicated - and buggy}. - -Mercurial has a substantial performance advantage over Subversion on -every revision control operation I have benchmarked. I have measured -its advantage as ranging from a factor of two to a factor of six when -compared with Subversion~1.4.3's \emph{ra\_local} file store, which is -the fastest access method available. In more realistic deployments -involving a network-based store, Subversion will be at a substantially -larger disadvantage. Because many Subversion commands must talk to -the server and Subversion does not have useful replication facilities, -server capacity and network bandwidth become bottlenecks for modestly -large projects. - -Additionally, Subversion incurs substantial storage overhead to avoid -network transactions for a few common operations, such as finding -modified files (\texttt{status}) and displaying modifications against -the current revision (\texttt{diff}). As a result, a Subversion -working copy is often the same size as, or larger than, a Mercurial -repository and working directory, even though the Mercurial repository -contains a complete history of the project. - -Subversion is widely supported by third party tools. Mercurial -currently lags considerably in this area. This gap is closing, -however, and indeed some of Mercurial's GUI tools now outshine their -Subversion equivalents. Like Mercurial, Subversion has an excellent -user manual. - -Because Subversion doesn't store revision history on the client, it is -well suited to managing projects that deal with lots of large, opaque -binary files. If you check in fifty revisions to an incompressible -10MB file, Subversion's client-side space usage stays constant The -space used by any distributed SCM will grow rapidly in proportion to -the number of revisions, because the differences between each revision -are large. - -In addition, it's often difficult or, more usually, impossible to -merge different versions of a binary file. Subversion's ability to -let a user lock a file, so that they temporarily have the exclusive -right to commit changes to it, can be a significant advantage to a -project where binary files are widely used. - -Mercurial can import revision history from a Subversion repository. -It can also export revision history to a Subversion repository. This -makes it easy to ``test the waters'' and use Mercurial and Subversion -in parallel before deciding to switch. History conversion is -incremental, so you can perform an initial conversion, then small -additional conversions afterwards to bring in new changes. - - -\subsection{Git} - -Git is a distributed revision control tool that was developed for -managing the Linux kernel source tree. Like Mercurial, its early -design was somewhat influenced by Monotone. - -Git has a very large command set, with version~1.5.0 providing~139 -individual commands. It has something of a reputation for being -difficult to learn. Compared to Git, Mercurial has a strong focus on -simplicity. - -In terms of performance, Git is extremely fast. In several cases, it -is faster than Mercurial, at least on Linux, while Mercurial performs -better on other operations. However, on Windows, the performance and -general level of support that Git provides is, at the time of writing, -far behind that of Mercurial. - -While a Mercurial repository needs no maintenance, a Git repository -requires frequent manual ``repacks'' of its metadata. Without these, -performance degrades, while space usage grows rapidly. A server that -contains many Git repositories that are not rigorously and frequently -repacked will become heavily disk-bound during backups, and there have -been instances of daily backups taking far longer than~24 hours as a -result. A freshly packed Git repository is slightly smaller than a -Mercurial repository, but an unpacked repository is several orders of -magnitude larger. - -The core of Git is written in C. Many Git commands are implemented as -shell or Perl scripts, and the quality of these scripts varies widely. -I have encountered several instances where scripts charged along -blindly in the presence of errors that should have been fatal. - -Mercurial can import revision history from a Git repository. - - -\subsection{CVS} - -CVS is probably the most widely used revision control tool in the -world. Due to its age and internal untidiness, it has been only -lightly maintained for many years. - -It has a centralised client/server architecture. It does not group -related file changes into atomic commits, making it easy for people to -``break the build'': one person can successfully commit part of a -change and then be blocked by the need for a merge, causing other -people to see only a portion of the work they intended to do. This -also affects how you work with project history. If you want to see -all of the modifications someone made as part of a task, you will need -to manually inspect the descriptions and timestamps of the changes -made to each file involved (if you even know what those files were). - -CVS has a muddled notion of tags and branches that I will not attempt -to even describe. It does not support renaming of files or -directories well, making it easy to corrupt a repository. It has -almost no internal consistency checking capabilities, so it is usually -not even possible to tell whether or how a repository is corrupt. I -would not recommend CVS for any project, existing or new. - -Mercurial can import CVS revision history. However, there are a few -caveats that apply; these are true of every other revision control -tool's CVS importer, too. Due to CVS's lack of atomic changes and -unversioned filesystem hierarchy, it is not possible to reconstruct -CVS history completely accurately; some guesswork is involved, and -renames will usually not show up. Because a lot of advanced CVS -administration has to be done by hand and is hence error-prone, it's -common for CVS importers to run into multiple problems with corrupted -repositories (completely bogus revision timestamps and files that have -remained locked for over a decade are just two of the less interesting -problems I can recall from personal experience). - -Mercurial can import revision history from a CVS repository. - - -\subsection{Commercial tools} - -Perforce has a centralised client/server architecture, with no -client-side caching of any data. Unlike modern revision control -tools, Perforce requires that a user run a command to inform the -server about every file they intend to edit. - -The performance of Perforce is quite good for small teams, but it -falls off rapidly as the number of users grows beyond a few dozen. -Modestly large Perforce installations require the deployment of -proxies to cope with the load their users generate. - - -\subsection{Choosing a revision control tool} - -With the exception of CVS, all of the tools listed above have unique -strengths that suit them to particular styles of work. There is no -single revision control tool that is best in all situations. - -As an example, Subversion is a good choice for working with frequently -edited binary files, due to its centralised nature and support for -file locking. - -I personally find Mercurial's properties of simplicity, performance, -and good merge support to be a compelling combination that has served -me well for several years. - - -\section{Switching from another tool to Mercurial} - -Mercurial is bundled with an extension named \hgext{convert}, which -can incrementally import revision history from several other revision -control tools. By ``incremental'', I mean that you can convert all of -a project's history to date in one go, then rerun the conversion later -to obtain new changes that happened after the initial conversion. - -The revision control tools supported by \hgext{convert} are as -follows: -\begin{itemize} -\item Subversion -\item CVS -\item Git -\item Darcs -\end{itemize} - -In addition, \hgext{convert} can export changes from Mercurial to -Subversion. This makes it possible to try Subversion and Mercurial in -parallel before committing to a switchover, without risking the loss -of any work. - -The \hgxcmd{conver}{convert} command is easy to use. Simply point it -at the path or URL of the source repository, optionally give it the -name of the destination repository, and it will start working. After -the initial conversion, just run the same command again to import new -changes. - - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "00book" -%%% End:
--- a/en/license.tex Wed Jan 21 14:16:38 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,138 +0,0 @@ -\chapter{Open Publication License} -\label{cha:opl} - -Version 1.0, 8 June 1999 - -\section{Requirements on both unmodified and modified versions} - -The Open Publication works may be reproduced and distributed in whole -or in part, in any medium physical or electronic, provided that the -terms of this license are adhered to, and that this license or an -incorporation of it by reference (with any options elected by the -author(s) and/or publisher) is displayed in the reproduction. - -Proper form for an incorporation by reference is as follows: - -\begin{quote} - Copyright (c) \emph{year} by \emph{author's name or designee}. This - material may be distributed only subject to the terms and conditions - set forth in the Open Publication License, v\emph{x.y} or later (the - latest version is presently available at - \url{http://www.opencontent.org/openpub/}). -\end{quote} - -The reference must be immediately followed with any options elected by -the author(s) and/or publisher of the document (see -section~\ref{sec:opl:options}). - -Commercial redistribution of Open Publication-licensed material is -permitted. - -Any publication in standard (paper) book form shall require the -citation of the original publisher and author. The publisher and -author's names shall appear on all outer surfaces of the book. On all -outer surfaces of the book the original publisher's name shall be as -large as the title of the work and cited as possessive with respect to -the title. - -\section{Copyright} - -The copyright to each Open Publication is owned by its author(s) or -designee. - -\section{Scope of license} - -The following license terms apply to all Open Publication works, -unless otherwise explicitly stated in the document. - -Mere aggregation of Open Publication works or a portion of an Open -Publication work with other works or programs on the same media shall -not cause this license to apply to those other works. The aggregate -work shall contain a notice specifying the inclusion of the Open -Publication material and appropriate copyright notice. - -\textbf{Severability}. If any part of this license is found to be -unenforceable in any jurisdiction, the remaining portions of the -license remain in force. - -\textbf{No warranty}. Open Publication works are licensed and provided -``as is'' without warranty of any kind, express or implied, including, -but not limited to, the implied warranties of merchantability and -fitness for a particular purpose or a warranty of non-infringement. - -\section{Requirements on modified works} - -All modified versions of documents covered by this license, including -translations, anthologies, compilations and partial documents, must -meet the following requirements: - -\begin{enumerate} -\item The modified version must be labeled as such. -\item The person making the modifications must be identified and the - modifications dated. -\item Acknowledgement of the original author and publisher if - applicable must be retained according to normal academic citation - practices. -\item The location of the original unmodified document must be - identified. -\item The original author's (or authors') name(s) may not be used to - assert or imply endorsement of the resulting document without the - original author's (or authors') permission. -\end{enumerate} - -\section{Good-practice recommendations} - -In addition to the requirements of this license, it is requested from -and strongly recommended of redistributors that: - -\begin{enumerate} -\item If you are distributing Open Publication works on hardcopy or - CD-ROM, you provide email notification to the authors of your intent - to redistribute at least thirty days before your manuscript or media - freeze, to give the authors time to provide updated documents. This - notification should describe modifications, if any, made to the - document. -\item All substantive modifications (including deletions) be either - clearly marked up in the document or else described in an attachment - to the document. -\item Finally, while it is not mandatory under this license, it is - considered good form to offer a free copy of any hardcopy and CD-ROM - expression of an Open Publication-licensed work to its author(s). -\end{enumerate} - -\section{License options} -\label{sec:opl:options} - -The author(s) and/or publisher of an Open Publication-licensed -document may elect certain options by appending language to the -reference to or copy of the license. These options are considered part -of the license instance and must be included with the license (or its -incorporation by reference) in derived works. - -\begin{enumerate}[A] -\item To prohibit distribution of substantively modified versions - without the explicit permission of the author(s). ``Substantive - modification'' is defined as a change to the semantic content of the - document, and excludes mere changes in format or typographical - corrections. - - To accomplish this, add the phrase ``Distribution of substantively - modified versions of this document is prohibited without the - explicit permission of the copyright holder.'' to the license - reference or copy. - -\item To prohibit any publication of this work or derivative works in - whole or in part in standard (paper) book form for commercial - purposes is prohibited unless prior permission is obtained from the - copyright holder. - - To accomplish this, add the phrase ``Distribution of the work or - derivative of the work in any standard (paper) book form is - prohibited unless prior permission is obtained from the copyright - holder.'' to the license reference or copy. -\end{enumerate} - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "00book" -%%% End:
--- a/en/mq-collab.tex Wed Jan 21 14:16:38 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,393 +0,0 @@ -\chapter{Advanced uses of Mercurial Queues} -\label{chap:mq-collab} - -While it's easy to pick up straightforward uses of Mercurial Queues, -use of a little discipline and some of MQ's less frequently used -capabilities makes it possible to work in complicated development -environments. - -In this chapter, I will use as an example a technique I have used to -manage the development of an Infiniband device driver for the Linux -kernel. The driver in question is large (at least as drivers go), -with 25,000 lines of code spread across 35 source files. It is -maintained by a small team of developers. - -While much of the material in this chapter is specific to Linux, the -same principles apply to any code base for which you're not the -primary owner, and upon which you need to do a lot of development. - -\section{The problem of many targets} - -The Linux kernel changes rapidly, and has never been internally -stable; developers frequently make drastic changes between releases. -This means that a version of the driver that works well with a -particular released version of the kernel will not even \emph{compile} -correctly against, typically, any other version. - -To maintain a driver, we have to keep a number of distinct versions of -Linux in mind. -\begin{itemize} -\item One target is the main Linux kernel development tree. - Maintenance of the code is in this case partly shared by other - developers in the kernel community, who make ``drive-by'' - modifications to the driver as they develop and refine kernel - subsystems. -\item We also maintain a number of ``backports'' to older versions of - the Linux kernel, to support the needs of customers who are running - older Linux distributions that do not incorporate our drivers. (To - \emph{backport} a piece of code is to modify it to work in an older - version of its target environment than the version it was developed - for.) -\item Finally, we make software releases on a schedule that is - necessarily not aligned with those used by Linux distributors and - kernel developers, so that we can deliver new features to customers - without forcing them to upgrade their entire kernels or - distributions. -\end{itemize} - -\subsection{Tempting approaches that don't work well} - -There are two ``standard'' ways to maintain a piece of software that -has to target many different environments. - -The first is to maintain a number of branches, each intended for a -single target. The trouble with this approach is that you must -maintain iron discipline in the flow of changes between repositories. -A new feature or bug fix must start life in a ``pristine'' repository, -then percolate out to every backport repository. Backport changes are -more limited in the branches they should propagate to; a backport -change that is applied to a branch where it doesn't belong will -probably stop the driver from compiling. - -The second is to maintain a single source tree filled with conditional -statements that turn chunks of code on or off depending on the -intended target. Because these ``ifdefs'' are not allowed in the -Linux kernel tree, a manual or automatic process must be followed to -strip them out and yield a clean tree. A code base maintained in this -fashion rapidly becomes a rat's nest of conditional blocks that are -difficult to understand and maintain. - -Neither of these approaches is well suited to a situation where you -don't ``own'' the canonical copy of a source tree. In the case of a -Linux driver that is distributed with the standard kernel, Linus's -tree contains the copy of the code that will be treated by the world -as canonical. The upstream version of ``my'' driver can be modified -by people I don't know, without me even finding out about it until -after the changes show up in Linus's tree. - -These approaches have the added weakness of making it difficult to -generate well-formed patches to submit upstream. - -In principle, Mercurial Queues seems like a good candidate to manage a -development scenario such as the above. While this is indeed the -case, MQ contains a few added features that make the job more -pleasant. - -\section{Conditionally applying patches with - guards} - -Perhaps the best way to maintain sanity with so many targets is to be -able to choose specific patches to apply for a given situation. MQ -provides a feature called ``guards'' (which originates with quilt's -\texttt{guards} command) that does just this. To start off, let's -create a simple repository for experimenting in. -\interaction{mq.guards.init} -This gives us a tiny repository that contains two patches that don't -have any dependencies on each other, because they touch different files. - -The idea behind conditional application is that you can ``tag'' a -patch with a \emph{guard}, which is simply a text string of your -choosing, then tell MQ to select specific guards to use when applying -patches. MQ will then either apply, or skip over, a guarded patch, -depending on the guards that you have selected. - -A patch can have an arbitrary number of guards; -each one is \emph{positive} (``apply this patch if this guard is -selected'') or \emph{negative} (``skip this patch if this guard is -selected''). A patch with no guards is always applied. - -\section{Controlling the guards on a patch} - -The \hgxcmd{mq}{qguard} command lets you determine which guards should -apply to a patch, or display the guards that are already in effect. -Without any arguments, it displays the guards on the current topmost -patch. -\interaction{mq.guards.qguard} -To set a positive guard on a patch, prefix the name of the guard with -a ``\texttt{+}''. -\interaction{mq.guards.qguard.pos} -To set a negative guard on a patch, prefix the name of the guard with -a ``\texttt{-}''. -\interaction{mq.guards.qguard.neg} - -\begin{note} - The \hgxcmd{mq}{qguard} command \emph{sets} the guards on a patch; it - doesn't \emph{modify} them. What this means is that if you run - \hgcmdargs{qguard}{+a +b} on a patch, then \hgcmdargs{qguard}{+c} on - the same patch, the \emph{only} guard that will be set on it - afterwards is \texttt{+c}. -\end{note} - -Mercurial stores guards in the \sfilename{series} file; the form in -which they are stored is easy both to understand and to edit by hand. -(In other words, you don't have to use the \hgxcmd{mq}{qguard} command if -you don't want to; it's okay to simply edit the \sfilename{series} -file.) -\interaction{mq.guards.series} - -\section{Selecting the guards to use} - -The \hgxcmd{mq}{qselect} command determines which guards are active at a -given time. The effect of this is to determine which patches MQ will -apply the next time you run \hgxcmd{mq}{qpush}. It has no other effect; in -particular, it doesn't do anything to patches that are already -applied. - -With no arguments, the \hgxcmd{mq}{qselect} command lists the guards -currently in effect, one per line of output. Each argument is treated -as the name of a guard to apply. -\interaction{mq.guards.qselect.foo} -In case you're interested, the currently selected guards are stored in -the \sfilename{guards} file. -\interaction{mq.guards.qselect.cat} -We can see the effect the selected guards have when we run -\hgxcmd{mq}{qpush}. -\interaction{mq.guards.qselect.qpush} - -A guard cannot start with a ``\texttt{+}'' or ``\texttt{-}'' -character. The name of a guard must not contain white space, but most -other characters are acceptable. If you try to use a guard with an -invalid name, MQ will complain: -\interaction{mq.guards.qselect.error} -Changing the selected guards changes the patches that are applied. -\interaction{mq.guards.qselect.quux} -You can see in the example below that negative guards take precedence -over positive guards. -\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 -determining 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:
--- a/en/mq-ref.tex Wed Jan 21 14:16:38 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,349 +0,0 @@ -\chapter{Mercurial Queues reference} -\label{chap:mqref} - -\section{MQ command reference} -\label{sec:mqref:cmdref} - -For an overview of the commands provided by MQ, use the command -\hgcmdargs{help}{mq}. - -\subsection{\hgxcmd{mq}{qapplied}---print applied patches} - -The \hgxcmd{mq}{qapplied} command prints the current stack of applied -patches. Patches are printed in oldest-to-newest order, so the last -patch in the list is the ``top'' patch. - -\subsection{\hgxcmd{mq}{qcommit}---commit changes in the queue repository} - -The \hgxcmd{mq}{qcommit} command commits any outstanding changes in the -\sdirname{.hg/patches} repository. This command only works if the -\sdirname{.hg/patches} directory is a repository, i.e.~you created the -directory using \hgcmdargs{qinit}{\hgxopt{mq}{qinit}{-c}} or ran -\hgcmd{init} in the directory after running \hgxcmd{mq}{qinit}. - -This command is shorthand for \hgcmdargs{commit}{--cwd .hg/patches}. - -\subsection{\hgxcmd{mq}{qdelete}---delete a patch from the - \sfilename{series} file} - -The \hgxcmd{mq}{qdelete} command removes the entry for a patch from the -\sfilename{series} file in the \sdirname{.hg/patches} directory. It -does not pop the patch if the patch is already applied. By default, -it does not delete the patch file; use the \hgxopt{mq}{qdel}{-f} option to -do that. - -Options: -\begin{itemize} -\item[\hgxopt{mq}{qdel}{-f}] Delete the patch file. -\end{itemize} - -\subsection{\hgxcmd{mq}{qdiff}---print a diff of the topmost applied patch} - -The \hgxcmd{mq}{qdiff} command prints a diff of the topmost applied patch. -It is equivalent to \hgcmdargs{diff}{-r-2:-1}. - -\subsection{\hgxcmd{mq}{qfold}---merge (``fold'') several patches into one} - -The \hgxcmd{mq}{qfold} command merges multiple patches into the topmost -applied patch, so that the topmost applied patch makes the union of -all of the changes in the patches in question. - -The patches to fold must not be applied; \hgxcmd{mq}{qfold} will exit with -an error if any is. The order in which patches are folded is -significant; \hgcmdargs{qfold}{a b} means ``apply the current topmost -patch, followed by \texttt{a}, followed by \texttt{b}''. - -The comments from the folded patches are appended to the comments of -the destination patch, with each block of comments separated by three -asterisk (``\texttt{*}'') characters. Use the \hgxopt{mq}{qfold}{-e} -option to edit the commit message for the combined patch/changeset -after the folding has completed. - -Options: -\begin{itemize} -\item[\hgxopt{mq}{qfold}{-e}] Edit the commit message and patch description - for the newly folded patch. -\item[\hgxopt{mq}{qfold}{-l}] Use the contents of the given file as the new - commit message and patch description for the folded patch. -\item[\hgxopt{mq}{qfold}{-m}] Use the given text as the new commit message - and patch description for the folded patch. -\end{itemize} - -\subsection{\hgxcmd{mq}{qheader}---display the header/description of a patch} - -The \hgxcmd{mq}{qheader} command prints the header, or description, of a -patch. By default, it prints the header of the topmost applied patch. -Given an argument, it prints the header of the named patch. - -\subsection{\hgxcmd{mq}{qimport}---import a third-party patch into the queue} - -The \hgxcmd{mq}{qimport} command adds an entry for an external patch to the -\sfilename{series} file, and copies the patch into the -\sdirname{.hg/patches} directory. It adds the entry immediately after -the topmost applied patch, but does not push the patch. - -If the \sdirname{.hg/patches} directory is a repository, -\hgxcmd{mq}{qimport} automatically does an \hgcmd{add} of the imported -patch. - -\subsection{\hgxcmd{mq}{qinit}---prepare a repository to work with MQ} - -The \hgxcmd{mq}{qinit} command prepares a repository to work with MQ. It -creates a directory called \sdirname{.hg/patches}. - -Options: -\begin{itemize} -\item[\hgxopt{mq}{qinit}{-c}] Create \sdirname{.hg/patches} as a repository - in its own right. Also creates a \sfilename{.hgignore} file that - will ignore the \sfilename{status} file. -\end{itemize} - -When the \sdirname{.hg/patches} directory is a repository, the -\hgxcmd{mq}{qimport} and \hgxcmd{mq}{qnew} commands automatically \hgcmd{add} -new patches. - -\subsection{\hgxcmd{mq}{qnew}---create a new patch} - -The \hgxcmd{mq}{qnew} command creates a new patch. It takes one mandatory -argument, the name to use for the patch file. The newly created patch -is created empty by default. It is added to the \sfilename{series} -file after the current topmost applied patch, and is immediately -pushed on top of that patch. - -If \hgxcmd{mq}{qnew} finds modified files in the working directory, it will -refuse to create a new patch unless the \hgxopt{mq}{qnew}{-f} option is -used (see below). This behaviour allows you to \hgxcmd{mq}{qrefresh} your -topmost applied patch before you apply a new patch on top of it. - -Options: -\begin{itemize} -\item[\hgxopt{mq}{qnew}{-f}] Create a new patch if the contents of the - working directory are modified. Any outstanding modifications are - added to the newly created patch, so after this command completes, - the working directory will no longer be modified. -\item[\hgxopt{mq}{qnew}{-m}] Use the given text as the commit message. - This text will be stored at the beginning of the patch file, before - the patch data. -\end{itemize} - -\subsection{\hgxcmd{mq}{qnext}---print the name of the next patch} - -The \hgxcmd{mq}{qnext} command prints the name name of the next patch in -the \sfilename{series} file after the topmost applied patch. This -patch will become the topmost applied patch if you run \hgxcmd{mq}{qpush}. - -\subsection{\hgxcmd{mq}{qpop}---pop patches off the stack} - -The \hgxcmd{mq}{qpop} command removes applied patches from the top of the -stack of applied patches. By default, it removes only one patch. - -This command removes the changesets that represent the popped patches -from the repository, and updates the working directory to undo the -effects of the patches. - -This command takes an optional argument, which it uses as the name or -index of the patch to pop to. If given a name, it will pop patches -until the named patch is the topmost applied patch. If given a -number, \hgxcmd{mq}{qpop} treats the number as an index into the entries in -the series file, counting from zero (empty lines and lines containing -only comments do not count). It pops patches until the patch -identified by the given index is the topmost applied patch. - -The \hgxcmd{mq}{qpop} command does not read or write patches or the -\sfilename{series} file. It is thus safe to \hgxcmd{mq}{qpop} a patch that -you have removed from the \sfilename{series} file, or a patch that you -have renamed or deleted entirely. In the latter two cases, use the -name of the patch as it was when you applied it. - -By default, the \hgxcmd{mq}{qpop} command will not pop any patches if the -working directory has been modified. You can override this behaviour -using the \hgxopt{mq}{qpop}{-f} option, which reverts all modifications in -the working directory. - -Options: -\begin{itemize} -\item[\hgxopt{mq}{qpop}{-a}] Pop all applied patches. This returns the - repository to its state before you applied any patches. -\item[\hgxopt{mq}{qpop}{-f}] Forcibly revert any modifications to the - working directory when popping. -\item[\hgxopt{mq}{qpop}{-n}] Pop a patch from the named queue. -\end{itemize} - -The \hgxcmd{mq}{qpop} command removes one line from the end of the -\sfilename{status} file for each patch that it pops. - -\subsection{\hgxcmd{mq}{qprev}---print the name of the previous patch} - -The \hgxcmd{mq}{qprev} command prints the name of the patch in the -\sfilename{series} file that comes before the topmost applied patch. -This will become the topmost applied patch if you run \hgxcmd{mq}{qpop}. - -\subsection{\hgxcmd{mq}{qpush}---push patches onto the stack} -\label{sec:mqref:cmd:qpush} - -The \hgxcmd{mq}{qpush} command adds patches onto the applied stack. By -default, it adds only one patch. - -This command creates a new changeset to represent each applied patch, -and updates the working directory to apply the effects of the patches. - -The default data used when creating a changeset are as follows: -\begin{itemize} -\item The commit date and time zone are the current date and time - zone. Because these data are used to compute the identity of a - changeset, this means that if you \hgxcmd{mq}{qpop} a patch and - \hgxcmd{mq}{qpush} it again, the changeset that you push will have a - different identity than the changeset you popped. -\item The author is the same as the default used by the \hgcmd{commit} - command. -\item The commit message is any text from the patch file that comes - before the first diff header. If there is no such text, a default - commit message is used that identifies the name of the patch. -\end{itemize} -If a patch contains a Mercurial patch header (XXX add link), the -information in the patch header overrides these defaults. - -Options: -\begin{itemize} -\item[\hgxopt{mq}{qpush}{-a}] Push all unapplied patches from the - \sfilename{series} file until there are none left to push. -\item[\hgxopt{mq}{qpush}{-l}] Add the name of the patch to the end - of the commit message. -\item[\hgxopt{mq}{qpush}{-m}] If a patch fails to apply cleanly, use the - entry for the patch in another saved queue to compute the parameters - for a three-way merge, and perform a three-way merge using the - normal Mercurial merge machinery. Use the resolution of the merge - as the new patch content. -\item[\hgxopt{mq}{qpush}{-n}] Use the named queue if merging while pushing. -\end{itemize} - -The \hgxcmd{mq}{qpush} command reads, but does not modify, the -\sfilename{series} file. It appends one line to the \hgcmd{status} -file for each patch that it pushes. - -\subsection{\hgxcmd{mq}{qrefresh}---update the topmost applied patch} - -The \hgxcmd{mq}{qrefresh} command updates the topmost applied patch. It -modifies the patch, removes the old changeset that represented the -patch, and creates a new changeset to represent the modified patch. - -The \hgxcmd{mq}{qrefresh} command looks for the following modifications: -\begin{itemize} -\item Changes to the commit message, i.e.~the text before the first - diff header in the patch file, are reflected in the new changeset - that represents the patch. -\item Modifications to tracked files in the working directory are - added to the patch. -\item Changes to the files tracked using \hgcmd{add}, \hgcmd{copy}, - \hgcmd{remove}, or \hgcmd{rename}. Added files and copy and rename - destinations are added to the patch, while removed files and rename - sources are removed. -\end{itemize} - -Even if \hgxcmd{mq}{qrefresh} detects no changes, it still recreates the -changeset that represents the patch. This causes the identity of the -changeset to differ from the previous changeset that identified the -patch. - -Options: -\begin{itemize} -\item[\hgxopt{mq}{qrefresh}{-e}] Modify the commit and patch description, - using the preferred text editor. -\item[\hgxopt{mq}{qrefresh}{-m}] Modify the commit message and patch - description, using the given text. -\item[\hgxopt{mq}{qrefresh}{-l}] Modify the commit message and patch - description, using text from the given file. -\end{itemize} - -\subsection{\hgxcmd{mq}{qrename}---rename a patch} - -The \hgxcmd{mq}{qrename} command renames a patch, and changes the entry for -the patch in the \sfilename{series} file. - -With a single argument, \hgxcmd{mq}{qrename} renames the topmost applied -patch. With two arguments, it renames its first argument to its -second. - -\subsection{\hgxcmd{mq}{qrestore}---restore saved queue state} - -XXX No idea what this does. - -\subsection{\hgxcmd{mq}{qsave}---save current queue state} - -XXX Likewise. - -\subsection{\hgxcmd{mq}{qseries}---print the entire patch series} - -The \hgxcmd{mq}{qseries} command prints the entire patch series from the -\sfilename{series} file. It prints only patch names, not empty lines -or comments. It prints in order from first to be applied to last. - -\subsection{\hgxcmd{mq}{qtop}---print the name of the current patch} - -The \hgxcmd{mq}{qtop} prints the name of the topmost currently applied -patch. - -\subsection{\hgxcmd{mq}{qunapplied}---print patches not yet applied} - -The \hgxcmd{mq}{qunapplied} command prints the names of patches from the -\sfilename{series} file that are not yet applied. It prints them in -order from the next patch that will be pushed to the last. - -\subsection{\hgcmd{strip}---remove a revision and descendants} - -The \hgcmd{strip} command removes a revision, and all of its -descendants, from the repository. It undoes the effects of the -removed revisions from the repository, and updates the working -directory to the first parent of the removed revision. - -The \hgcmd{strip} command saves a backup of the removed changesets in -a bundle, so that they can be reapplied if removed in error. - -Options: -\begin{itemize} -\item[\hgopt{strip}{-b}] Save unrelated changesets that are intermixed - with the stripped changesets in the backup bundle. -\item[\hgopt{strip}{-f}] If a branch has multiple heads, remove all - heads. XXX This should be renamed, and use \texttt{-f} to strip revs - when there are pending changes. -\item[\hgopt{strip}{-n}] Do not save a backup bundle. -\end{itemize} - -\section{MQ file reference} - -\subsection{The \sfilename{series} file} - -The \sfilename{series} file contains a list of the names of all -patches that MQ can apply. It is represented as a list of names, with -one name saved per line. Leading and trailing white space in each -line are ignored. - -Lines may contain comments. A comment begins with the ``\texttt{\#}'' -character, and extends to the end of the line. Empty lines, and lines -that contain only comments, are ignored. - -You will often need to edit the \sfilename{series} file by hand, hence -the support for comments and empty lines noted above. For example, -you can comment out a patch temporarily, and \hgxcmd{mq}{qpush} will skip -over that patch when applying patches. You can also change the order -in which patches are applied by reordering their entries in the -\sfilename{series} file. - -Placing the \sfilename{series} file under revision control is also -supported; it is a good idea to place all of the patches that it -refers to under revision control, as well. If you create a patch -directory using the \hgxopt{mq}{qinit}{-c} option to \hgxcmd{mq}{qinit}, this -will be done for you automatically. - -\subsection{The \sfilename{status} file} - -The \sfilename{status} file contains the names and changeset hashes of -all patches that MQ currently has applied. Unlike the -\sfilename{series} file, this file is not intended for editing. You -should not place this file under revision control, or modify it in any -way. It is used by MQ strictly for internal book-keeping. - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "00book" -%%% End:
--- a/en/mq.tex Wed Jan 21 14:16:38 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1043 +0,0 @@ -\chapter{Managing change with Mercurial Queues} -\label{chap:mq} - -\section{The patch management problem} -\label{sec:mq:patch-mgmt} - -Here is a common scenario: you need to install a software package from -source, but you find a bug that you must fix in the source before you -can start using the package. You make your changes, forget about the -package for a while, and a few months later you need to upgrade to a -newer version of the package. If the newer version of the package -still has the bug, you must extract your fix from the older source -tree and apply it against the newer version. This is a tedious task, -and it's easy to make mistakes. - -This is a simple case of the ``patch management'' problem. You have -an ``upstream'' source tree that you can't change; you need to make -some local changes on top of the upstream tree; and you'd like to be -able to keep those changes separate, so that you can apply them to -newer versions of the upstream source. - -The patch management problem arises in many situations. Probably the -most visible is that a user of an open source software project will -contribute a bug fix or new feature to the project's maintainers in the -form of a patch. - -Distributors of operating systems that include open source software -often need to make changes to the packages they distribute so that -they will build properly in their environments. - -When you have few changes to maintain, it is easy to manage a single -patch using the standard \command{diff} and \command{patch} programs -(see section~\ref{sec:mq:patch} for a discussion of these tools). -Once the number of changes grows, it starts to make sense to maintain -patches as discrete ``chunks of work,'' so that for example a single -patch will contain only one bug fix (the patch might modify several -files, but it's doing ``only one thing''), and you may have a number -of such patches for different bugs you need fixed and local changes -you require. In this situation, if you submit a bug fix patch to the -upstream maintainers of a package and they include your fix in a -subsequent release, you can simply drop that single patch when you're -updating to the newer release. - -Maintaining a single patch against an upstream tree is a little -tedious and error-prone, but not difficult. However, the complexity -of the problem grows rapidly as the number of patches you have to -maintain increases. With more than a tiny number of patches in hand, -understanding which ones you have applied and maintaining them moves -from messy to overwhelming. - -Fortunately, Mercurial includes a powerful extension, Mercurial Queues -(or simply ``MQ''), that massively simplifies the patch management -problem. - -\section{The prehistory of Mercurial Queues} -\label{sec:mq:history} - -During the late 1990s, several Linux kernel developers started to -maintain ``patch series'' that modified the behaviour of the Linux -kernel. Some of these series were focused on stability, some on -feature coverage, and others were more speculative. - -The sizes of these patch series grew rapidly. In 2002, Andrew Morton -published some shell scripts he had been using to automate the task of -managing his patch queues. Andrew was successfully using these -scripts to manage hundreds (sometimes thousands) of patches on top of -the Linux kernel. - -\subsection{A patchwork quilt} -\label{sec:mq:quilt} - -In early 2003, Andreas Gruenbacher and Martin Quinson borrowed the -approach of Andrew's scripts and published a tool called ``patchwork -quilt''~\cite{web:quilt}, or simply ``quilt'' -(see~\cite{gruenbacher:2005} for a paper describing it). Because -quilt substantially automated patch management, it rapidly gained a -large following among open source software developers. - -Quilt manages a \emph{stack of patches} on top of a directory tree. -To begin, you tell quilt to manage a directory tree, and tell it which -files you want to manage; it stores away the names and contents of -those files. To fix a bug, you create a new patch (using a single -command), edit the files you need to fix, then ``refresh'' the patch. - -The refresh step causes quilt to scan the directory tree; it updates -the patch with all of the changes you have made. You can create -another patch on top of the first, which will track the changes -required to modify the tree from ``tree with one patch applied'' to -``tree with two patches applied''. - -You can \emph{change} which patches are applied to the tree. If you -``pop'' a patch, the changes made by that patch will vanish from the -directory tree. Quilt remembers which patches you have popped, -though, so you can ``push'' a popped patch again, and the directory -tree will be restored to contain the modifications in the patch. Most -importantly, you can run the ``refresh'' command at any time, and the -topmost applied patch will be updated. This means that you can, at -any time, change both which patches are applied and what -modifications those patches make. - -Quilt knows nothing about revision control tools, so it works equally -well on top of an unpacked tarball or a Subversion working copy. - -\subsection{From patchwork quilt to Mercurial Queues} -\label{sec:mq:quilt-mq} - -In mid-2005, Chris Mason took the features of quilt and wrote an -extension that he called Mercurial Queues, which added quilt-like -behaviour to Mercurial. - -The key difference between quilt and MQ is that quilt knows nothing -about revision control systems, while MQ is \emph{integrated} into -Mercurial. Each patch that you push is represented as a Mercurial -changeset. Pop a patch, and the changeset goes away. - -Because quilt does not care about revision control tools, it is still -a tremendously useful piece of software to know about for situations -where you cannot use Mercurial and MQ. - -\section{The huge advantage of MQ} - -I cannot overstate the value that MQ offers through the unification of -patches and revision control. - -A major reason that patches have persisted in the free software and -open source world---in spite of the availability of increasingly -capable revision control tools over the years---is the \emph{agility} -they offer. - -Traditional revision control tools make a permanent, irreversible -record of everything that you do. While this has great value, it's -also somewhat stifling. If you want to perform a wild-eyed -experiment, you have to be careful in how you go about it, or you risk -leaving unneeded---or worse, misleading or destabilising---traces of -your missteps and errors in the permanent revision record. - -By contrast, MQ's marriage of distributed revision control with -patches makes it much easier to isolate your work. Your patches live -on top of normal revision history, and you can make them disappear or -reappear at will. If you don't like a patch, you can drop it. If a -patch isn't quite as you want it to be, simply fix it---as many times -as you need to, until you have refined it into the form you desire. - -As an example, the integration of patches with revision control makes -understanding patches and debugging their effects---and their -interplay with the code they're based on---\emph{enormously} easier. -Since every applied patch has an associated changeset, you can use -\hgcmdargs{log}{\emph{filename}} to see which changesets and patches -affected a file. You can use the \hgext{bisect} command to -binary-search through all changesets and applied patches to see where -a bug got introduced or fixed. You can use the \hgcmd{annotate} -command to see which changeset or patch modified a particular line of -a source file. And so on. - -\section{Understanding patches} -\label{sec:mq:patch} - -Because MQ doesn't hide its patch-oriented nature, it is helpful to -understand what patches are, and a little about the tools that work -with them. - -The traditional Unix \command{diff} command compares two files, and -prints a list of differences between them. The \command{patch} command -understands these differences as \emph{modifications} to make to a -file. Take a look at figure~\ref{ex:mq:diff} for a simple example of -these commands in action. - -\begin{figure}[ht] - \interaction{mq.dodiff.diff} - \caption{Simple uses of the \command{diff} and \command{patch} commands} - \label{ex:mq:diff} -\end{figure} - -The type of file that \command{diff} generates (and \command{patch} -takes as input) is called a ``patch'' or a ``diff''; there is no -difference between a patch and a diff. (We'll use the term ``patch'', -since it's more commonly used.) - -A patch file can start with arbitrary text; the \command{patch} -command ignores this text, but MQ uses it as the commit message when -creating changesets. To find the beginning of the patch content, -\command{patch} searches for the first line that starts with the -string ``\texttt{diff~-}''. - -MQ works with \emph{unified} diffs (\command{patch} can accept several -other diff formats, but MQ doesn't). A unified diff contains two -kinds of header. The \emph{file header} describes the file being -modified; it contains the name of the file to modify. When -\command{patch} sees a new file header, it looks for a file with that -name to start modifying. - -After the file header comes a series of \emph{hunks}. Each hunk -starts with a header; this identifies the range of line numbers within -the file that the hunk should modify. Following the header, a hunk -starts and ends with a few (usually three) lines of text from the -unmodified file; these are called the \emph{context} for the hunk. If -there's only a small amount of context between successive hunks, -\command{diff} doesn't print a new hunk header; it just runs the hunks -together, with a few lines of context between modifications. - -Each line of context begins with a space character. Within the hunk, -a line that begins with ``\texttt{-}'' means ``remove this line,'' -while a line that begins with ``\texttt{+}'' means ``insert this -line.'' For example, a line that is modified is represented by one -deletion and one insertion. - -We will return to some of the more subtle aspects of patches later (in -section~\ref{sec:mq:adv-patch}), but you should have enough information -now to use MQ. - -\section{Getting started with Mercurial Queues} -\label{sec:mq:start} - -Because MQ is implemented as an extension, you must explicitly enable -before you can use it. (You don't need to download anything; MQ ships -with the standard Mercurial distribution.) To enable MQ, edit your -\tildefile{.hgrc} file, and add the lines in figure~\ref{ex:mq:config}. - -\begin{figure}[ht] - \begin{codesample4} - [extensions] - hgext.mq = - \end{codesample4} - \label{ex:mq:config} - \caption{Contents to add to \tildefile{.hgrc} to enable the MQ extension} -\end{figure} - -Once the extension is enabled, it will make a number of new commands -available. To verify that the extension is working, you can use -\hgcmd{help} to see if the \hgxcmd{mq}{qinit} command is now available; see -the example in figure~\ref{ex:mq:enabled}. - -\begin{figure}[ht] - \interaction{mq.qinit-help.help} - \caption{How to verify that MQ is enabled} - \label{ex:mq:enabled} -\end{figure} - -You can use MQ with \emph{any} Mercurial repository, and its commands -only operate within that repository. To get started, simply prepare -the repository using the \hgxcmd{mq}{qinit} command (see -figure~\ref{ex:mq:qinit}). This command creates an empty directory -called \sdirname{.hg/patches}, where MQ will keep its metadata. As -with many Mercurial commands, the \hgxcmd{mq}{qinit} command prints nothing -if it succeeds. - -\begin{figure}[ht] - \interaction{mq.tutorial.qinit} - \caption{Preparing a repository for use with MQ} - \label{ex:mq:qinit} -\end{figure} - -\begin{figure}[ht] - \interaction{mq.tutorial.qnew} - \caption{Creating a new patch} - \label{ex:mq:qnew} -\end{figure} - -\subsection{Creating a new patch} - -To begin work on a new patch, use the \hgxcmd{mq}{qnew} command. This -command takes one argument, the name of the patch to create. MQ will -use this as the name of an actual file in the \sdirname{.hg/patches} -directory, as you can see in figure~\ref{ex:mq:qnew}. - -Also newly present in the \sdirname{.hg/patches} directory are two -other files, \sfilename{series} and \sfilename{status}. The -\sfilename{series} file lists all of the patches that MQ knows about -for this repository, with one patch per line. Mercurial uses the -\sfilename{status} file for internal book-keeping; it tracks all of the -patches that MQ has \emph{applied} in this repository. - -\begin{note} - You may sometimes want to edit the \sfilename{series} file by hand; - for example, to change the sequence in which some patches are - applied. However, manually editing the \sfilename{status} file is - almost always a bad idea, as it's easy to corrupt MQ's idea of what - is happening. -\end{note} - -Once you have created your new patch, you can edit files in the -working directory as you usually would. All of the normal Mercurial -commands, such as \hgcmd{diff} and \hgcmd{annotate}, work exactly as -they did before. - -\subsection{Refreshing a patch} - -When you reach a point where you want to save your work, use the -\hgxcmd{mq}{qrefresh} command (figure~\ref{ex:mq:qnew}) to update the patch -you are working on. This command folds the changes you have made in -the working directory into your patch, and updates its corresponding -changeset to contain those changes. - -\begin{figure}[ht] - \interaction{mq.tutorial.qrefresh} - \caption{Refreshing a patch} - \label{ex:mq:qrefresh} -\end{figure} - -You can run \hgxcmd{mq}{qrefresh} as often as you like, so it's a good way -to ``checkpoint'' your work. Refresh your patch at an opportune -time; try an experiment; and if the experiment doesn't work out, -\hgcmd{revert} your modifications back to the last time you refreshed. - -\begin{figure}[ht] - \interaction{mq.tutorial.qrefresh2} - \caption{Refresh a patch many times to accumulate changes} - \label{ex:mq:qrefresh2} -\end{figure} - -\subsection{Stacking and tracking patches} - -Once you have finished working on a patch, or need to work on another, -you can use the \hgxcmd{mq}{qnew} command again to create a new patch. -Mercurial will apply this patch on top of your existing patch. See -figure~\ref{ex:mq:qnew2} for an example. Notice that the patch -contains the changes in our prior patch as part of its context (you -can see this more clearly in the output of \hgcmd{annotate}). - -\begin{figure}[ht] - \interaction{mq.tutorial.qnew2} - \caption{Stacking a second patch on top of the first} - \label{ex:mq:qnew2} -\end{figure} - -So far, with the exception of \hgxcmd{mq}{qnew} and \hgxcmd{mq}{qrefresh}, we've -been careful to only use regular Mercurial commands. However, MQ -provides many commands that are easier to use when you are thinking -about patches, as illustrated in figure~\ref{ex:mq:qseries}: - -\begin{itemize} -\item The \hgxcmd{mq}{qseries} command lists every patch that MQ knows - about in this repository, from oldest to newest (most recently - \emph{created}). -\item The \hgxcmd{mq}{qapplied} command lists every patch that MQ has - \emph{applied} in this repository, again from oldest to newest (most - recently applied). -\end{itemize} - -\begin{figure}[ht] - \interaction{mq.tutorial.qseries} - \caption{Understanding the patch stack with \hgxcmd{mq}{qseries} and - \hgxcmd{mq}{qapplied}} - \label{ex:mq:qseries} -\end{figure} - -\subsection{Manipulating the patch stack} - -The previous discussion implied that there must be a difference -between ``known'' and ``applied'' patches, and there is. MQ can -manage a patch without it being applied in the repository. - -An \emph{applied} patch has a corresponding changeset in the -repository, and the effects of the patch and changeset are visible in -the working directory. You can undo the application of a patch using -the \hgxcmd{mq}{qpop} command. MQ still \emph{knows about}, or manages, a -popped patch, but the patch no longer has a corresponding changeset in -the repository, and the working directory does not contain the changes -made by the patch. Figure~\ref{fig:mq:stack} illustrates the -difference between applied and tracked patches. - -\begin{figure}[ht] - \centering - \grafix{mq-stack} - \caption{Applied and unapplied patches in the MQ patch stack} - \label{fig:mq:stack} -\end{figure} - -You can reapply an unapplied, or popped, patch using the \hgxcmd{mq}{qpush} -command. This creates a new changeset to correspond to the patch, and -the patch's changes once again become present in the working -directory. See figure~\ref{ex:mq:qpop} for examples of \hgxcmd{mq}{qpop} -and \hgxcmd{mq}{qpush} in action. Notice that once we have popped a patch -or two patches, the output of \hgxcmd{mq}{qseries} remains the same, while -that of \hgxcmd{mq}{qapplied} has changed. - -\begin{figure}[ht] - \interaction{mq.tutorial.qpop} - \caption{Modifying the stack of applied patches} - \label{ex:mq:qpop} -\end{figure} - -\subsection{Pushing and popping many patches} - -While \hgxcmd{mq}{qpush} and \hgxcmd{mq}{qpop} each operate on a single patch at -a time by default, you can push and pop many patches in one go. The -\hgxopt{mq}{qpush}{-a} option to \hgxcmd{mq}{qpush} causes it to push all -unapplied patches, while the \hgxopt{mq}{qpop}{-a} option to \hgxcmd{mq}{qpop} -causes it to pop all applied patches. (For some more ways to push and -pop many patches, see section~\ref{sec:mq:perf} below.) - -\begin{figure}[ht] - \interaction{mq.tutorial.qpush-a} - \caption{Pushing all unapplied patches} - \label{ex:mq:qpush-a} -\end{figure} - -\subsection{Safety checks, and overriding them} - -Several MQ commands check the working directory before they do -anything, and fail if they find any modifications. They do this to -ensure that you won't lose any changes that you have made, but not yet -incorporated into a patch. Figure~\ref{ex:mq:add} illustrates this; -the \hgxcmd{mq}{qnew} command will not create a new patch if there are -outstanding changes, caused in this case by the \hgcmd{add} of -\filename{file3}. - -\begin{figure}[ht] - \interaction{mq.tutorial.add} - \caption{Forcibly creating a patch} - \label{ex:mq:add} -\end{figure} - -Commands that check the working directory all take an ``I know what -I'm doing'' option, which is always named \option{-f}. The exact -meaning of \option{-f} depends on the command. For example, -\hgcmdargs{qnew}{\hgxopt{mq}{qnew}{-f}} will incorporate any outstanding -changes into the new patch it creates, but -\hgcmdargs{qpop}{\hgxopt{mq}{qpop}{-f}} will revert modifications to any -files affected by the patch that it is popping. Be sure to read the -documentation for a command's \option{-f} option before you use it! - -\subsection{Working on several patches at once} - -The \hgxcmd{mq}{qrefresh} command always refreshes the \emph{topmost} -applied patch. This means that you can suspend work on one patch (by -refreshing it), pop or push to make a different patch the top, and -work on \emph{that} patch for a while. - -Here's an example that illustrates how you can use this ability. -Let's say you're developing a new feature as two patches. The first -is a change to the core of your software, and the second---layered on -top of the first---changes the user interface to use the code you just -added to the core. If you notice a bug in the core while you're -working on the UI patch, it's easy to fix the core. Simply -\hgxcmd{mq}{qrefresh} the UI patch to save your in-progress changes, and -\hgxcmd{mq}{qpop} down to the core patch. Fix the core bug, -\hgxcmd{mq}{qrefresh} the core patch, and \hgxcmd{mq}{qpush} back to the UI -patch to continue where you left off. - -\section{More about patches} -\label{sec:mq:adv-patch} - -MQ uses the GNU \command{patch} command to apply patches, so it's -helpful to know a few more detailed aspects of how \command{patch} -works, and about patches themselves. - -\subsection{The strip count} - -If you look at the file headers in a patch, you will notice that the -pathnames usually have an extra component on the front that isn't -present in the actual path name. This is a holdover from the way that -people used to generate patches (people still do this, but it's -somewhat rare with modern revision control tools). - -Alice would unpack a tarball, edit her files, then decide that she -wanted to create a patch. So she'd rename her working directory, -unpack the tarball again (hence the need for the rename), and use the -\cmdopt{diff}{-r} and \cmdopt{diff}{-N} options to \command{diff} to -recursively generate a patch between the unmodified directory and the -modified one. The result would be that the name of the unmodified -directory would be at the front of the left-hand path in every file -header, and the name of the modified directory would be at the front -of the right-hand path. - -Since someone receiving a patch from the Alices of the net would be -unlikely to have unmodified and modified directories with exactly the -same names, the \command{patch} command has a \cmdopt{patch}{-p} -option that indicates the number of leading path name components to -strip when trying to apply a patch. This number is called the -\emph{strip count}. - -An option of ``\texttt{-p1}'' means ``use a strip count of one''. If -\command{patch} sees a file name \filename{foo/bar/baz} in a file -header, it will strip \filename{foo} and try to patch a file named -\filename{bar/baz}. (Strictly speaking, the strip count refers to the -number of \emph{path separators} (and the components that go with them -) to strip. A strip count of one will turn \filename{foo/bar} into -\filename{bar}, but \filename{/foo/bar} (notice the extra leading -slash) into \filename{foo/bar}.) - -The ``standard'' strip count for patches is one; almost all patches -contain one leading path name component that needs to be stripped. -Mercurial's \hgcmd{diff} command generates path names in this form, -and the \hgcmd{import} command and MQ expect patches to have a strip -count of one. - -If you receive a patch from someone that you want to add to your patch -queue, and the patch needs a strip count other than one, you cannot -just \hgxcmd{mq}{qimport} the patch, because \hgxcmd{mq}{qimport} does not yet -have a \texttt{-p} option (see~\bug{311}). Your best bet is to -\hgxcmd{mq}{qnew} a patch of your own, then use \cmdargs{patch}{-p\emph{N}} -to apply their patch, followed by \hgcmd{addremove} to pick up any -files added or removed by the patch, followed by \hgxcmd{mq}{qrefresh}. -This complexity may become unnecessary; see~\bug{311} for details. -\subsection{Strategies for applying a patch} - -When \command{patch} applies a hunk, it tries a handful of -successively less accurate strategies to try to make the hunk apply. -This falling-back technique often makes it possible to take a patch -that was generated against an old version of a file, and apply it -against a newer version of that file. - -First, \command{patch} tries an exact match, where the line numbers, -the context, and the text to be modified must apply exactly. If it -cannot make an exact match, it tries to find an exact match for the -context, without honouring the line numbering information. If this -succeeds, it prints a line of output saying that the hunk was applied, -but at some \emph{offset} from the original line number. - -If a context-only match fails, \command{patch} removes the first and -last lines of the context, and tries a \emph{reduced} context-only -match. If the hunk with reduced context succeeds, it prints a message -saying that it applied the hunk with a \emph{fuzz factor} (the number -after the fuzz factor indicates how many lines of context -\command{patch} had to trim before the patch applied). - -When neither of these techniques works, \command{patch} prints a -message saying that the hunk in question was rejected. It saves -rejected hunks (also simply called ``rejects'') to a file with the -same name, and an added \sfilename{.rej} extension. It also saves an -unmodified copy of the file with a \sfilename{.orig} extension; the -copy of the file without any extensions will contain any changes made -by hunks that \emph{did} apply cleanly. If you have a patch that -modifies \filename{foo} with six hunks, and one of them fails to -apply, you will have: an unmodified \filename{foo.orig}, a -\filename{foo.rej} containing one hunk, and \filename{foo}, containing -the changes made by the five successful hunks. - -\subsection{Some quirks of patch representation} - -There are a few useful things to know about how \command{patch} works -with files. -\begin{itemize} -\item This should already be obvious, but \command{patch} cannot - handle binary files. -\item Neither does it care about the executable bit; it creates new - files as readable, but not executable. -\item \command{patch} treats the removal of a file as a diff between - the file to be removed and the empty file. So your idea of ``I - deleted this file'' looks like ``every line of this file was - deleted'' in a patch. -\item It treats the addition of a file as a diff between the empty - file and the file to be added. So in a patch, your idea of ``I - added this file'' looks like ``every line of this file was added''. -\item It treats a renamed file as the removal of the old name, and the - addition of the new name. This means that renamed files have a big - footprint in patches. (Note also that Mercurial does not currently - try to infer when files have been renamed or copied in a patch.) -\item \command{patch} cannot represent empty files, so you cannot use - a patch to represent the notion ``I added this empty file to the - tree''. -\end{itemize} -\subsection{Beware the fuzz} - -While applying a hunk at an offset, or with a fuzz factor, will often -be completely successful, these inexact techniques naturally leave -open the possibility of corrupting the patched file. The most common -cases typically involve applying a patch twice, or at an incorrect -location in the file. If \command{patch} or \hgxcmd{mq}{qpush} ever -mentions an offset or fuzz factor, you should make sure that the -modified files are correct afterwards. - -It's often a good idea to refresh a patch that has applied with an -offset or fuzz factor; refreshing the patch generates new context -information that will make it apply cleanly. I say ``often,'' not -``always,'' because sometimes refreshing a patch will make it fail to -apply against a different revision of the underlying files. In some -cases, such as when you're maintaining a patch that must sit on top of -multiple versions of a source tree, it's acceptable to have a patch -apply with some fuzz, provided you've verified the results of the -patching process in such cases. - -\subsection{Handling rejection} - -If \hgxcmd{mq}{qpush} fails to apply a patch, it will print an error -message and exit. If it has left \sfilename{.rej} files behind, it is -usually best to fix up the rejected hunks before you push more patches -or do any further work. - -If your patch \emph{used to} apply cleanly, and no longer does because -you've changed the underlying code that your patches are based on, -Mercurial Queues can help; see section~\ref{sec:mq:merge} for details. - -Unfortunately, there aren't any great techniques for dealing with -rejected hunks. Most often, you'll need to view the \sfilename{.rej} -file and edit the target file, applying the rejected hunks by hand. - -If you're feeling adventurous, Neil Brown, a Linux kernel hacker, -wrote a tool called \command{wiggle}~\cite{web:wiggle}, which is more -vigorous than \command{patch} in its attempts to make a patch apply. - -Another Linux kernel hacker, Chris Mason (the author of Mercurial -Queues), wrote a similar tool called -\command{mpatch}~\cite{web:mpatch}, which takes a simple approach to -automating the application of hunks rejected by \command{patch}. The -\command{mpatch} command can help with four common reasons that a hunk -may be rejected: - -\begin{itemize} -\item The context in the middle of a hunk has changed. -\item A hunk is missing some context at the beginning or end. -\item A large hunk might apply better---either entirely or in - part---if it was broken up into smaller hunks. -\item A hunk removes lines with slightly different content than those - currently present in the file. -\end{itemize} - -If you use \command{wiggle} or \command{mpatch}, you should be doubly -careful to check your results when you're done. In fact, -\command{mpatch} enforces this method of double-checking the tool's -output, by automatically dropping you into a merge program when it has -done its job, so that you can verify its work and finish off any -remaining merges. - -\section{Getting the best performance out of MQ} -\label{sec:mq:perf} - -MQ is very efficient at handling a large number of patches. I ran -some performance experiments in mid-2006 for a talk that I gave at the -2006 EuroPython conference~\cite{web:europython}. I used as my data -set the Linux 2.6.17-mm1 patch series, which consists of 1,738 -patches. I applied these on top of a Linux kernel repository -containing all 27,472 revisions between Linux 2.6.12-rc2 and Linux -2.6.17. - -On my old, slow laptop, I was able to -\hgcmdargs{qpush}{\hgxopt{mq}{qpush}{-a}} all 1,738 patches in 3.5 minutes, -and \hgcmdargs{qpop}{\hgxopt{mq}{qpop}{-a}} them all in 30 seconds. (On a -newer laptop, the time to push all patches dropped to two minutes.) I -could \hgxcmd{mq}{qrefresh} one of the biggest patches (which made 22,779 -lines of changes to 287 files) in 6.6 seconds. - -Clearly, MQ is well suited to working in large trees, but there are a -few tricks you can use to get the best performance of it. - -First of all, try to ``batch'' operations together. Every time you -run \hgxcmd{mq}{qpush} or \hgxcmd{mq}{qpop}, these commands scan the working -directory once to make sure you haven't made some changes and then -forgotten to run \hgxcmd{mq}{qrefresh}. On a small tree, the time that -this scan takes is unnoticeable. However, on a medium-sized tree -(containing tens of thousands of files), it can take a second or more. - -The \hgxcmd{mq}{qpush} and \hgxcmd{mq}{qpop} commands allow you to push and pop -multiple patches at a time. You can identify the ``destination -patch'' that you want to end up at. When you \hgxcmd{mq}{qpush} with a -destination specified, it will push patches until that patch is at the -top of the applied stack. When you \hgxcmd{mq}{qpop} to a destination, MQ -will pop patches until the destination patch is at the top. - -You can identify a destination patch using either the name of the -patch, or by number. If you use numeric addressing, patches are -counted from zero; this means that the first patch is zero, the second -is one, and so on. - -\section{Updating your patches when the underlying code changes} -\label{sec:mq:merge} - -It's common to have a stack of patches on top of an underlying -repository that you don't modify directly. If you're working on -changes to third-party code, or on a feature that is taking longer to -develop than the rate of change of the code beneath, you will often -need to sync up with the underlying code, and fix up any hunks in your -patches that no longer apply. This is called \emph{rebasing} your -patch series. - -The simplest way to do this is to \hgcmdargs{qpop}{\hgxopt{mq}{qpop}{-a}} -your patches, then \hgcmd{pull} changes into the underlying -repository, and finally \hgcmdargs{qpush}{\hgxopt{mq}{qpop}{-a}} your -patches again. MQ will stop pushing any time it runs across a patch -that fails to apply during conflicts, allowing you to fix your -conflicts, \hgxcmd{mq}{qrefresh} the affected patch, and continue pushing -until you have fixed your entire stack. - -This approach is easy to use and works well if you don't expect -changes to the underlying code to affect how well your patches apply. -If your patch stack touches code that is modified frequently or -invasively in the underlying repository, however, fixing up rejected -hunks by hand quickly becomes tiresome. - -It's possible to partially automate the rebasing process. If your -patches apply cleanly against some revision of the underlying repo, MQ -can use this information to help you to resolve conflicts between your -patches and a different revision. - -The process is a little involved. -\begin{enumerate} -\item To begin, \hgcmdargs{qpush}{-a} all of your patches on top of - the revision where you know that they apply cleanly. -\item Save a backup copy of your patch directory using - \hgcmdargs{qsave}{\hgxopt{mq}{qsave}{-e} \hgxopt{mq}{qsave}{-c}}. This prints - the name of the directory that it has saved the patches in. It will - save the patches to a directory called - \sdirname{.hg/patches.\emph{N}}, where \texttt{\emph{N}} is a small - integer. It also commits a ``save changeset'' on top of your - applied patches; this is for internal book-keeping, and records the - states of the \sfilename{series} and \sfilename{status} files. -\item Use \hgcmd{pull} to bring new changes into the underlying - repository. (Don't run \hgcmdargs{pull}{-u}; see below for why.) -\item Update to the new tip revision, using - \hgcmdargs{update}{\hgopt{update}{-C}} to override the patches you - have pushed. -\item Merge all patches using \hgcmdargs{qpush}{\hgxopt{mq}{qpush}{-m} - \hgxopt{mq}{qpush}{-a}}. The \hgxopt{mq}{qpush}{-m} option to \hgxcmd{mq}{qpush} - tells MQ to perform a three-way merge if the patch fails to apply. -\end{enumerate} - -During the \hgcmdargs{qpush}{\hgxopt{mq}{qpush}{-m}}, each patch in the -\sfilename{series} file is applied normally. If a patch applies with -fuzz or rejects, MQ looks at the queue you \hgxcmd{mq}{qsave}d, and -performs a three-way merge with the corresponding changeset. This -merge uses Mercurial's normal merge machinery, so it may pop up a GUI -merge tool to help you to resolve problems. - -When you finish resolving the effects of a patch, MQ refreshes your -patch based on the result of the merge. - -At the end of this process, your repository will have one extra head -from the old patch queue, and a copy of the old patch queue will be in -\sdirname{.hg/patches.\emph{N}}. You can remove the extra head using -\hgcmdargs{qpop}{\hgxopt{mq}{qpop}{-a} \hgxopt{mq}{qpop}{-n} patches.\emph{N}} -or \hgcmd{strip}. You can delete \sdirname{.hg/patches.\emph{N}} once -you are sure that you no longer need it as a backup. - -\section{Identifying patches} - -MQ commands that work with patches let you refer to a patch either by -using its name or by a number. By name is obvious enough; pass the -name \filename{foo.patch} to \hgxcmd{mq}{qpush}, for example, and it will -push patches until \filename{foo.patch} is applied. - -As a shortcut, you can refer to a patch using both a name and a -numeric offset; \texttt{foo.patch-2} means ``two patches before -\texttt{foo.patch}'', while \texttt{bar.patch+4} means ``four patches -after \texttt{bar.patch}''. - -Referring to a patch by index isn't much different. The first patch -printed in the output of \hgxcmd{mq}{qseries} is patch zero (yes, it's one -of those start-at-zero counting systems); the second is patch one; and -so on. - -MQ also makes it easy to work with patches when you are using normal -Mercurial commands. Every command that accepts a changeset ID will -also accept the name of an applied patch. MQ augments the tags -normally in the repository with an eponymous one for each applied -patch. In addition, the special tags \index{tags!special tag - names!\texttt{qbase}}\texttt{qbase} and \index{tags!special tag - names!\texttt{qtip}}\texttt{qtip} identify the ``bottom-most'' and -topmost applied patches, respectively. - -These additions to Mercurial's normal tagging capabilities make -dealing with patches even more of a breeze. -\begin{itemize} -\item Want to patchbomb a mailing list with your latest series of - changes? - \begin{codesample4} - hg email qbase:qtip - \end{codesample4} - (Don't know what ``patchbombing'' is? See - section~\ref{sec:hgext:patchbomb}.) -\item Need to see all of the patches since \texttt{foo.patch} that - have touched files in a subdirectory of your tree? - \begin{codesample4} - hg log -r foo.patch:qtip \emph{subdir} - \end{codesample4} -\end{itemize} - -Because MQ makes the names of patches available to the rest of -Mercurial through its normal internal tag machinery, you don't need to -type in the entire name of a patch when you want to identify it by -name. - -\begin{figure}[ht] - \interaction{mq.id.output} - \caption{Using MQ's tag features to work with patches} - \label{ex:mq:id} -\end{figure} - -Another nice consequence of representing patch names as tags is that -when you run the \hgcmd{log} command, it will display a patch's name -as a tag, simply as part of its normal output. This makes it easy to -visually distinguish applied patches from underlying ``normal'' -revisions. Figure~\ref{ex:mq:id} shows a few normal Mercurial -commands in use with applied patches. - -\section{Useful things to know about} - -There are a number of aspects of MQ usage that don't fit tidily into -sections of their own, but that are good to know. Here they are, in -one place. - -\begin{itemize} -\item Normally, when you \hgxcmd{mq}{qpop} a patch and \hgxcmd{mq}{qpush} it - again, the changeset that represents the patch after the pop/push - will have a \emph{different identity} than the changeset that - represented the hash beforehand. See - section~\ref{sec:mqref:cmd:qpush} for information as to why this is. -\item It's not a good idea to \hgcmd{merge} changes from another - branch with a patch changeset, at least if you want to maintain the - ``patchiness'' of that changeset and changesets below it on the - patch stack. If you try to do this, it will appear to succeed, but - MQ will become confused. -\end{itemize} - -\section{Managing patches in a repository} -\label{sec:mq:repo} - -Because MQ's \sdirname{.hg/patches} directory resides outside a -Mercurial repository's working directory, the ``underlying'' Mercurial -repository knows nothing about the management or presence of patches. - -This presents the interesting possibility of managing the contents of -the patch directory as a Mercurial repository in its own right. This -can be a useful way to work. For example, you can work on a patch for -a while, \hgxcmd{mq}{qrefresh} it, then \hgcmd{commit} the current state of -the patch. This lets you ``roll back'' to that version of the patch -later on. - -You can then share different versions of the same patch stack among -multiple underlying repositories. I use this when I am developing a -Linux kernel feature. I have a pristine copy of my kernel sources for -each of several CPU architectures, and a cloned repository under each -that contains the patches I am working on. When I want to test a -change on a different architecture, I push my current patches to the -patch repository associated with that kernel tree, pop and push all of -my patches, and build and test that kernel. - -Managing patches in a repository makes it possible for multiple -developers to work on the same patch series without colliding with -each other, all on top of an underlying source base that they may or -may not control. - -\subsection{MQ support for patch repositories} - -MQ helps you to work with the \sdirname{.hg/patches} directory as a -repository; when you prepare a repository for working with patches -using \hgxcmd{mq}{qinit}, you can pass the \hgxopt{mq}{qinit}{-c} option to -create the \sdirname{.hg/patches} directory as a Mercurial repository. - -\begin{note} - If you forget to use the \hgxopt{mq}{qinit}{-c} option, you can simply go - into the \sdirname{.hg/patches} directory at any time and run - \hgcmd{init}. Don't forget to add an entry for the - \sfilename{status} file to the \sfilename{.hgignore} file, though - - (\hgcmdargs{qinit}{\hgxopt{mq}{qinit}{-c}} does this for you - automatically); you \emph{really} don't want to manage the - \sfilename{status} file. -\end{note} - -As a convenience, if MQ notices that the \dirname{.hg/patches} -directory is a repository, it will automatically \hgcmd{add} every -patch that you create and import. - -MQ provides a shortcut command, \hgxcmd{mq}{qcommit}, that runs -\hgcmd{commit} in the \sdirname{.hg/patches} directory. This saves -some bothersome typing. - -Finally, as a convenience to manage the patch directory, you can -define the alias \command{mq} on Unix systems. For example, on Linux -systems using the \command{bash} shell, you can include the following -snippet in your \tildefile{.bashrc}. - -\begin{codesample2} - alias mq=`hg -R \$(hg root)/.hg/patches' -\end{codesample2} - -You can then issue commands of the form \cmdargs{mq}{pull} from -the main repository. - -\subsection{A few things to watch out for} - -MQ's support for working with a repository full of patches is limited -in a few small respects. - -MQ cannot automatically detect changes that you make to the patch -directory. If you \hgcmd{pull}, manually edit, or \hgcmd{update} -changes to patches or the \sfilename{series} file, you will have to -\hgcmdargs{qpop}{\hgxopt{mq}{qpop}{-a}} and then -\hgcmdargs{qpush}{\hgxopt{mq}{qpush}{-a}} in the underlying repository to -see those changes show up there. If you forget to do this, you can -confuse MQ's idea of which patches are applied. - -\section{Third party tools for working with patches} -\label{sec:mq:tools} - -Once you've been working with patches for a while, you'll find -yourself hungry for tools that will help you to understand and -manipulate the patches you're dealing with. - -The \command{diffstat} command~\cite{web:diffstat} generates a -histogram of the modifications made to each file in a patch. It -provides a good way to ``get a sense of'' a patch---which files it -affects, and how much change it introduces to each file and as a -whole. (I find that it's a good idea to use \command{diffstat}'s -\cmdopt{diffstat}{-p} option as a matter of course, as otherwise it -will try to do clever things with prefixes of file names that -inevitably confuse at least me.) - -\begin{figure}[ht] - \interaction{mq.tools.tools} - \caption{The \command{diffstat}, \command{filterdiff}, and \command{lsdiff} commands} - \label{ex:mq:tools} -\end{figure} - -The \package{patchutils} package~\cite{web:patchutils} is invaluable. -It provides a set of small utilities that follow the ``Unix -philosophy;'' each does one useful thing with a patch. The -\package{patchutils} command I use most is \command{filterdiff}, which -extracts subsets from a patch file. For example, given a patch that -modifies hundreds of files across dozens of directories, a single -invocation of \command{filterdiff} can generate a smaller patch that -only touches files whose names match a particular glob pattern. See -section~\ref{mq-collab:tips:interdiff} for another example. - -\section{Good ways to work with patches} - -Whether you are working on a patch series to submit to a free software -or open source project, or a series that you intend to treat as a -sequence of regular changesets when you're done, you can use some -simple techniques to keep your work well organised. - -Give your patches descriptive names. A good name for a patch might be -\filename{rework-device-alloc.patch}, because it will immediately give -you a hint what the purpose of the patch is. Long names shouldn't be -a problem; you won't be typing the names often, but you \emph{will} be -running commands like \hgxcmd{mq}{qapplied} and \hgxcmd{mq}{qtop} over and over. -Good naming becomes especially important when you have a number of -patches to work with, or if you are juggling a number of different -tasks and your patches only get a fraction of your attention. - -Be aware of what patch you're working on. Use the \hgxcmd{mq}{qtop} -command and skim over the text of your patches frequently---for -example, using \hgcmdargs{tip}{\hgopt{tip}{-p}})---to be sure of where -you stand. I have several times worked on and \hgxcmd{mq}{qrefresh}ed a -patch other than the one I intended, and it's often tricky to migrate -changes into the right patch after making them in the wrong one. - -For this reason, it is very much worth investing a little time to -learn how to use some of the third-party tools I described in -section~\ref{sec:mq:tools}, particularly \command{diffstat} and -\command{filterdiff}. The former will give you a quick idea of what -changes your patch is making, while the latter makes it easy to splice -hunks selectively out of one patch and into another. - -\section{MQ cookbook} - -\subsection{Manage ``trivial'' patches} - -Because the overhead of dropping files into a new Mercurial repository -is so low, it makes a lot of sense to manage patches this way even if -you simply want to make a few changes to a source tarball that you -downloaded. - -Begin by downloading and unpacking the source tarball, -and turning it into a Mercurial repository. -\interaction{mq.tarball.download} - -Continue by creating a patch stack and making your changes. -\interaction{mq.tarball.qinit} - -Let's say a few weeks or months pass, and your package author releases -a new version. First, bring their changes into the repository. -\interaction{mq.tarball.newsource} -The pipeline starting with \hgcmd{locate} above deletes all files in -the working directory, so that \hgcmd{commit}'s -\hgopt{commit}{--addremove} option can actually tell which files have -really been removed in the newer version of the source. - -Finally, you can apply your patches on top of the new tree. -\interaction{mq.tarball.repush} - -\subsection{Combining entire patches} -\label{sec:mq:combine} - -MQ provides a command, \hgxcmd{mq}{qfold} that lets you combine entire -patches. This ``folds'' the patches you name, in the order you name -them, into the topmost applied patch, and concatenates their -descriptions onto the end of its description. The patches that you -fold must be unapplied before you fold them. - -The order in which you fold patches matters. If your topmost applied -patch is \texttt{foo}, and you \hgxcmd{mq}{qfold} \texttt{bar} and -\texttt{quux} into it, you will end up with a patch that has the same -effect as if you applied first \texttt{foo}, then \texttt{bar}, -followed by \texttt{quux}. - -\subsection{Merging part of one patch into another} - -Merging \emph{part} of one patch into another is more difficult than -combining entire patches. - -If you want to move changes to entire files, you can use -\command{filterdiff}'s \cmdopt{filterdiff}{-i} and -\cmdopt{filterdiff}{-x} options to choose the modifications to snip -out of one patch, concatenating its output onto the end of the patch -you want to merge into. You usually won't need to modify the patch -you've merged the changes from. Instead, MQ will report some rejected -hunks when you \hgxcmd{mq}{qpush} it (from the hunks you moved into the -other patch), and you can simply \hgxcmd{mq}{qrefresh} the patch to drop -the duplicate hunks. - -If you have a patch that has multiple hunks modifying a file, and you -only want to move a few of those hunks, the job becomes more messy, -but you can still partly automate it. Use \cmdargs{lsdiff}{-nvv} to -print some metadata about the patch. -\interaction{mq.tools.lsdiff} - -This command prints three different kinds of number: -\begin{itemize} -\item (in the first column) a \emph{file number} to identify each file - modified in the patch; -\item (on the next line, indented) the line number within a modified - file where a hunk starts; and -\item (on the same line) a \emph{hunk number} to identify that hunk. -\end{itemize} - -You'll have to use some visual inspection, and reading of the patch, -to identify the file and hunk numbers you'll want, but you can then -pass them to to \command{filterdiff}'s \cmdopt{filterdiff}{--files} -and \cmdopt{filterdiff}{--hunks} options, to select exactly the file -and hunk you want to extract. - -Once you have this hunk, you can concatenate it onto the end of your -destination patch and continue with the remainder of -section~\ref{sec:mq:combine}. - -\section{Differences between quilt and MQ} - -If you are already familiar with quilt, MQ provides a similar command -set. There are a few differences in the way that it works. - -You will already have noticed that most quilt commands have MQ -counterparts that simply begin with a ``\texttt{q}''. The exceptions -are quilt's \texttt{add} and \texttt{remove} commands, the -counterparts for which are the normal Mercurial \hgcmd{add} and -\hgcmd{remove} commands. There is no MQ equivalent of the quilt -\texttt{edit} command. - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "00book" -%%% End:
--- a/en/preface.tex Wed Jan 21 14:16:38 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -\chapter*{Preface} -\addcontentsline{toc}{chapter}{Preface} -\label{chap:preface} - -Distributed revision control is a relatively new territory, and has -thus far grown due to people's willingness to strike out into -ill-charted territory. - -I am writing a book about distributed revision control because I -believe that it is an important subject that deserves a field guide. -I chose to write about Mercurial because it is the easiest tool to -learn the terrain with, and yet it scales to the demands of real, -challenging environments where many other revision control tools fail. - -\section{This book is a work in progress} - -I am releasing this book while I am still writing it, in the hope that -it will prove useful to others. I also hope that readers will -contribute as they see fit. - -\section{About the examples in this book} - -This book takes an unusual approach to code samples. Every example is -``live''---each one is actually the result of a shell script that -executes the Mercurial commands you see. Every time an image of the -book is built from its sources, all the example scripts are -automatically run, and their current results compared against their -expected results. - -The advantage of this approach is that the examples are always -accurate; they describe \emph{exactly} the behaviour of the version of -Mercurial that's mentioned at the front of the book. If I update the -version of Mercurial that I'm documenting, and the output of some -command changes, the build fails. - -There is a small disadvantage to this approach, which is that the -dates and times you'll see in examples tend to be ``squashed'' -together in a way that they wouldn't be if the same commands were -being typed by a human. Where a human can issue no more than one -command every few seconds, with any resulting timestamps -correspondingly spread out, my automated example scripts run many -commands in one second. - -As an instance of this, several consecutive commits in an example can -show up as having occurred during the same second. You can see this -occur in the \hgext{bisect} example in section~\ref{sec:undo:bisect}, -for instance. - -So when you're reading examples, don't place too much weight on the -dates or times you see in the output of commands. But \emph{do} be -confident that the behaviour you're seeing is consistent and -reproducible. - -\section{Colophon---this book is Free} - -This book is licensed under the Open Publication License, and is -produced entirely using Free Software tools. It is typeset with -\LaTeX{}; illustrations are drawn and rendered with -\href{http://www.inkscape.org/}{Inkscape}. - -The complete source code for this book is published as a Mercurial -repository, at \url{http://hg.serpentine.com/mercurial/book}. - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "00book" -%%% End:
--- a/en/srcinstall.tex Wed Jan 21 14:16:38 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -\chapter{Installing Mercurial from source} -\label{chap:srcinstall} - -\section{On a Unix-like system} -\label{sec:srcinstall:unixlike} - -If you are using a Unix-like system that has a sufficiently recent -version of Python (2.3~or newer) available, it is easy to install -Mercurial from source. -\begin{enumerate} -\item Download a recent source tarball from - \url{http://www.selenic.com/mercurial/download}. -\item Unpack the tarball: - \begin{codesample4} - gzip -dc mercurial-\emph{version}.tar.gz | tar xf - - \end{codesample4} -\item Go into the source directory and run the installer script. This - will build Mercurial and install it in your home directory. - \begin{codesample4} - cd mercurial-\emph{version} - python setup.py install --force --home=\$HOME - \end{codesample4} -\end{enumerate} -Once the install finishes, Mercurial will be in the \texttt{bin} -subdirectory of your home directory. Don't forget to make sure that -this directory is present in your shell's search path. - -You will probably need to set the \envar{PYTHONPATH} environment -variable so that the Mercurial executable can find the rest of the -Mercurial packages. For example, on my laptop, I have set it to -\texttt{/home/bos/lib/python}. The exact path that you will need to -use depends on how Python was built for your system, but should be -easy to figure out. If you're uncertain, look through the output of -the installer script above, and see where the contents of the -\texttt{mercurial} directory were installed to. - -\section{On Windows} - -Building and installing Mercurial on Windows requires a variety of -tools, a fair amount of technical knowledge, and considerable -patience. I very much \emph{do not recommend} this route if you are a -``casual user''. Unless you intend to hack on Mercurial, I strongly -suggest that you use a binary package instead. - -If you are intent on building Mercurial from source on Windows, follow -the ``hard way'' directions on the Mercurial wiki at -\url{http://www.selenic.com/mercurial/wiki/index.cgi/WindowsInstall}, -and expect the process to involve a lot of fiddly work. - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "00book" -%%% End:
--- a/en/template.tex Wed Jan 21 14:16:38 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,475 +0,0 @@ -\chapter{Customising the output of Mercurial} -\label{chap:template} - -Mercurial provides a powerful mechanism to let you control how it -displays information. The mechanism is based on templates. You can -use templates to generate specific output for a single command, or to -customise the entire appearance of the built-in web interface. - -\section{Using precanned output styles} -\label{sec:style} - -Packaged with Mercurial are some output styles that you can use -immediately. A style is simply a precanned template that someone -wrote and installed somewhere that Mercurial can find. - -Before we take a look at Mercurial's bundled styles, let's review its -normal output. - -\interaction{template.simple.normal} - -This is somewhat informative, but it takes up a lot of space---five -lines of output per changeset. The \texttt{compact} style reduces -this to three lines, presented in a sparse manner. - -\interaction{template.simple.compact} - -The \texttt{changelog} style hints at the expressive power of -Mercurial's templating engine. This style attempts to follow the GNU -Project's changelog guidelines\cite{web:changelog}. - -\interaction{template.simple.changelog} - -You will not be shocked to learn that Mercurial's default output style -is named \texttt{default}. - -\subsection{Setting a default style} - -You can modify the output style that Mercurial will use for every -command by editing your \hgrc\ file, naming the style you would -prefer to use. - -\begin{codesample2} - [ui] - style = compact -\end{codesample2} - -If you write a style of your own, you can use it by either providing -the path to your style file, or copying your style file into a -location where Mercurial can find it (typically the \texttt{templates} -subdirectory of your Mercurial install directory). - -\section{Commands that support styles and templates} - -All of Mercurial's ``\texttt{log}-like'' commands let you use styles -and templates: \hgcmd{incoming}, \hgcmd{log}, \hgcmd{outgoing}, and -\hgcmd{tip}. - -As I write this manual, these are so far the only commands that -support styles and templates. Since these are the most important -commands that need customisable output, there has been little pressure -from the Mercurial user community to add style and template support to -other commands. - -\section{The basics of templating} - -At its simplest, a Mercurial template is a piece of text. Some of the -text never changes, while other parts are \emph{expanded}, or replaced -with new text, when necessary. - -Before we continue, let's look again at a simple example of -Mercurial's normal output. - -\interaction{template.simple.normal} - -Now, let's run the same command, but using a template to change its -output. - -\interaction{template.simple.simplest} - -The example above illustrates the simplest possible template; it's -just a piece of static text, printed once for each changeset. The -\hgopt{log}{--template} option to the \hgcmd{log} command tells -Mercurial to use the given text as the template when printing each -changeset. - -Notice that the template string above ends with the text -``\Verb+\n+''. This is an \emph{escape sequence}, telling Mercurial -to print a newline at the end of each template item. If you omit this -newline, Mercurial will run each piece of output together. See -section~\ref{sec:template:escape} for more details of escape sequences. - -A template that prints a fixed string of text all the time isn't very -useful; let's try something a bit more complex. - -\interaction{template.simple.simplesub} - -As you can see, the string ``\Verb+{desc}+'' in the template has been -replaced in the output with the description of each changeset. Every -time Mercurial finds text enclosed in curly braces (``\texttt{\{}'' -and ``\texttt{\}}''), it will try to replace the braces and text with -the expansion of whatever is inside. To print a literal curly brace, -you must escape it, as described in section~\ref{sec:template:escape}. - -\section{Common template keywords} -\label{sec:template:keyword} - -You can start writing simple templates immediately using the keywords -below. - -\begin{itemize} -\item[\tplkword{author}] String. The unmodified author of the changeset. -\item[\tplkword{branches}] String. The name of the branch on which - the changeset was committed. Will be empty if the branch name was - \texttt{default}. -\item[\tplkword{date}] Date information. The date when the changeset - was committed. This is \emph{not} human-readable; you must pass it - through a filter that will render it appropriately. See - section~\ref{sec:template:filter} for more information on filters. - The date is expressed as a pair of numbers. The first number is a - Unix UTC timestamp (seconds since January 1, 1970); the second is - the offset of the committer's timezone from UTC, in seconds. -\item[\tplkword{desc}] String. The text of the changeset description. -\item[\tplkword{files}] List of strings. All files modified, added, or - removed by this changeset. -\item[\tplkword{file\_adds}] List of strings. Files added by this - changeset. -\item[\tplkword{file\_dels}] List of strings. Files removed by this - changeset. -\item[\tplkword{node}] String. The changeset identification hash, as a - 40-character hexadecimal string. -\item[\tplkword{parents}] List of strings. The parents of the - changeset. -\item[\tplkword{rev}] Integer. The repository-local changeset revision - number. -\item[\tplkword{tags}] List of strings. Any tags associated with the - changeset. -\end{itemize} - -A few simple experiments will show us what to expect when we use these -keywords; you can see the results in -figure~\ref{fig:template:keywords}. - -\begin{figure} - \interaction{template.simple.keywords} - \caption{Template keywords in use} - \label{fig:template:keywords} -\end{figure} - -As we noted above, the date keyword does not produce human-readable -output, so we must treat it specially. This involves using a -\emph{filter}, about which more in section~\ref{sec:template:filter}. - -\interaction{template.simple.datekeyword} - -\section{Escape sequences} -\label{sec:template:escape} - -Mercurial's templating engine recognises the most commonly used escape -sequences in strings. When it sees a backslash (``\Verb+\+'') -character, it looks at the following character and substitutes the two -characters with a single replacement, as described below. - -\begin{itemize} -\item[\Verb+\textbackslash\textbackslash+] Backslash, ``\Verb+\+'', - ASCII~134. -\item[\Verb+\textbackslash n+] Newline, ASCII~12. -\item[\Verb+\textbackslash r+] Carriage return, ASCII~15. -\item[\Verb+\textbackslash t+] Tab, ASCII~11. -\item[\Verb+\textbackslash v+] Vertical tab, ASCII~13. -\item[\Verb+\textbackslash \{+] Open curly brace, ``\Verb+{+'', ASCII~173. -\item[\Verb+\textbackslash \}+] Close curly brace, ``\Verb+}+'', ASCII~175. -\end{itemize} - -As indicated above, if you want the expansion of a template to contain -a literal ``\Verb+\+'', ``\Verb+{+'', or ``\Verb+{+'' character, you -must escape it. - -\section{Filtering keywords to change their results} -\label{sec:template:filter} - -Some of the results of template expansion are not immediately easy to -use. Mercurial lets you specify an optional chain of \emph{filters} -to modify the result of expanding a keyword. You have already seen a -common filter, \tplkwfilt{date}{isodate}, in action above, to make a -date readable. - -Below is a list of the most commonly used filters that Mercurial -supports. While some filters can be applied to any text, others can -only be used in specific circumstances. The name of each filter is -followed first by an indication of where it can be used, then a -description of its effect. - -\begin{itemize} -\item[\tplfilter{addbreaks}] Any text. Add an XHTML ``\Verb+<br/>+'' - tag before the end of every line except the last. For example, - ``\Verb+foo\nbar+'' becomes ``\Verb+foo<br/>\nbar+''. -\item[\tplkwfilt{date}{age}] \tplkword{date} keyword. Render the - age of the date, relative to the current time. Yields a string like - ``\Verb+10 minutes+''. -\item[\tplfilter{basename}] Any text, but most useful for the - \tplkword{files} keyword and its relatives. Treat the text as a - path, and return the basename. For example, ``\Verb+foo/bar/baz+'' - becomes ``\Verb+baz+''. -\item[\tplkwfilt{date}{date}] \tplkword{date} keyword. Render a date - in a similar format to the Unix \tplkword{date} command, but with - timezone included. Yields a string like - ``\Verb+Mon Sep 04 15:13:13 2006 -0700+''. -\item[\tplkwfilt{author}{domain}] Any text, but most useful for the - \tplkword{author} keyword. Finds the first string that looks like - an email address, and extract just the domain component. For - example, ``\Verb+Bryan O'Sullivan <bos@serpentine.com>+'' becomes - ``\Verb+serpentine.com+''. -\item[\tplkwfilt{author}{email}] Any text, but most useful for the - \tplkword{author} keyword. Extract the first string that looks like - an email address. For example, - ``\Verb+Bryan O'Sullivan <bos@serpentine.com>+'' becomes - ``\Verb+bos@serpentine.com+''. -\item[\tplfilter{escape}] Any text. Replace the special XML/XHTML - characters ``\Verb+&+'', ``\Verb+<+'' and ``\Verb+>+'' with - XML entities. -\item[\tplfilter{fill68}] Any text. Wrap the text to fit in 68 - columns. This is useful before you pass text through the - \tplfilter{tabindent} filter, and still want it to fit in an - 80-column fixed-font window. -\item[\tplfilter{fill76}] Any text. Wrap the text to fit in 76 - columns. -\item[\tplfilter{firstline}] Any text. Yield the first line of text, - without any trailing newlines. -\item[\tplkwfilt{date}{hgdate}] \tplkword{date} keyword. Render the - date as a pair of readable numbers. Yields a string like - ``\Verb+1157407993 25200+''. -\item[\tplkwfilt{date}{isodate}] \tplkword{date} keyword. Render the - date as a text string in ISO~8601 format. Yields a string like - ``\Verb+2006-09-04 15:13:13 -0700+''. -\item[\tplfilter{obfuscate}] Any text, but most useful for the - \tplkword{author} keyword. Yield the input text rendered as a - sequence of XML entities. This helps to defeat some particularly - stupid screen-scraping email harvesting spambots. -\item[\tplkwfilt{author}{person}] Any text, but most useful for the - \tplkword{author} keyword. Yield the text before an email address. - For example, ``\Verb+Bryan O'Sullivan <bos@serpentine.com>+'' - becomes ``\Verb+Bryan O'Sullivan+''. -\item[\tplkwfilt{date}{rfc822date}] \tplkword{date} keyword. Render a - date using the same format used in email headers. Yields a string - like ``\Verb+Mon, 04 Sep 2006 15:13:13 -0700+''. -\item[\tplkwfilt{node}{short}] Changeset hash. Yield the short form - of a changeset hash, i.e.~a 12-character hexadecimal string. -\item[\tplkwfilt{date}{shortdate}] \tplkword{date} keyword. Render - the year, month, and day of the date. Yields a string like - ``\Verb+2006-09-04+''. -\item[\tplfilter{strip}] Any text. Strip all leading and trailing - whitespace from the string. -\item[\tplfilter{tabindent}] Any text. Yield the text, with every line - except the first starting with a tab character. -\item[\tplfilter{urlescape}] Any text. Escape all characters that are - considered ``special'' by URL parsers. For example, \Verb+foo bar+ - becomes \Verb+foo%20bar+. -\item[\tplkwfilt{author}{user}] Any text, but most useful for the - \tplkword{author} keyword. Return the ``user'' portion of an email - address. For example, - ``\Verb+Bryan O'Sullivan <bos@serpentine.com>+'' becomes - ``\Verb+bos+''. -\end{itemize} - -\begin{figure} - \interaction{template.simple.manyfilters} - \caption{Template filters in action} - \label{fig:template:filters} -\end{figure} - -\begin{note} - If you try to apply a filter to a piece of data that it cannot - process, Mercurial will fail and print a Python exception. For - example, trying to run the output of the \tplkword{desc} keyword - into the \tplkwfilt{date}{isodate} filter is not a good idea. -\end{note} - -\subsection{Combining filters} - -It is easy to combine filters to yield output in the form you would -like. The following chain of filters tidies up a description, then -makes sure that it fits cleanly into 68 columns, then indents it by a -further 8~characters (at least on Unix-like systems, where a tab is -conventionally 8~characters wide). - -\interaction{template.simple.combine} - -Note the use of ``\Verb+\t+'' (a tab character) in the template to -force the first line to be indented; this is necessary since -\tplkword{tabindent} indents all lines \emph{except} the first. - -Keep in mind that the order of filters in a chain is significant. The -first filter is applied to the result of the keyword; the second to -the result of the first filter; and so on. For example, using -\Verb+fill68|tabindent+ gives very different results from -\Verb+tabindent|fill68+. - - -\section{From templates to styles} - -A command line template provides a quick and simple way to format some -output. Templates can become verbose, though, and it's useful to be -able to give a template a name. A style file is a template with a -name, stored in a file. - -More than that, using a style file unlocks the power of Mercurial's -templating engine in ways that are not possible using the command line -\hgopt{log}{--template} option. - -\subsection{The simplest of style files} - -Our simple style file contains just one line: - -\interaction{template.simple.rev} - -This tells Mercurial, ``if you're printing a changeset, use the text -on the right as the template''. - -\subsection{Style file syntax} - -The syntax rules for a style file are simple. - -\begin{itemize} -\item The file is processed one line at a time. - -\item Leading and trailing white space are ignored. - -\item Empty lines are skipped. - -\item If a line starts with either of the characters ``\texttt{\#}'' or - ``\texttt{;}'', the entire line is treated as a comment, and skipped - as if empty. - -\item A line starts with a keyword. This must start with an - alphabetic character or underscore, and can subsequently contain any - alphanumeric character or underscore. (In regexp notation, a - keyword must match \Verb+[A-Za-z_][A-Za-z0-9_]*+.) - -\item The next element must be an ``\texttt{=}'' character, which can - be preceded or followed by an arbitrary amount of white space. - -\item If the rest of the line starts and ends with matching quote - characters (either single or double quote), it is treated as a - template body. - -\item If the rest of the line \emph{does not} start with a quote - character, it is treated as the name of a file; the contents of this - file will be read and used as a template body. -\end{itemize} - -\section{Style files by example} - -To illustrate how to write a style file, we will construct a few by -example. Rather than provide a complete style file and walk through -it, we'll mirror the usual process of developing a style file by -starting with something very simple, and walking through a series of -successively more complete examples. - -\subsection{Identifying mistakes in style files} - -If Mercurial encounters a problem in a style file you are working on, -it prints a terse error message that, once you figure out what it -means, is actually quite useful. - -\interaction{template.svnstyle.syntax.input} - -Notice that \filename{broken.style} attempts to define a -\texttt{changeset} keyword, but forgets to give any content for it. -When instructed to use this style file, Mercurial promptly complains. - -\interaction{template.svnstyle.syntax.error} - -This error message looks intimidating, but it is not too hard to -follow. - -\begin{itemize} -\item The first component is simply Mercurial's way of saying ``I am - giving up''. - \begin{codesample4} - \textbf{abort:} broken.style:1: parse error - \end{codesample4} - -\item Next comes the name of the style file that contains the error. - \begin{codesample4} - abort: \textbf{broken.style}:1: parse error - \end{codesample4} - -\item Following the file name is the line number where the error was - encountered. - \begin{codesample4} - abort: broken.style:\textbf{1}: parse error - \end{codesample4} - -\item Finally, a description of what went wrong. - \begin{codesample4} - abort: broken.style:1: \textbf{parse error} - \end{codesample4} - The description of the problem is not always clear (as in this - case), but even when it is cryptic, it is almost always trivial to - visually inspect the offending line in the style file and see what - is wrong. -\end{itemize} - -\subsection{Uniquely identifying a repository} - -If you would like to be able to identify a Mercurial repository -``fairly uniquely'' using a short string as an identifier, you can -use the first revision in the repository. -\interaction{template.svnstyle.id} -This is not guaranteed to be unique, but it is nevertheless useful in -many cases. -\begin{itemize} -\item It will not work in a completely empty repository, because such - a repository does not have a revision~zero. -\item Neither will it work in the (extremely rare) case where a - repository is a merge of two or more formerly independent - repositories, and you still have those repositories around. -\end{itemize} -Here are some uses to which you could put this identifier: -\begin{itemize} -\item As a key into a table for a database that manages repositories - on a server. -\item As half of a \{\emph{repository~ID}, \emph{revision~ID}\} tuple. - Save this information away when you run an automated build or other - activity, so that you can ``replay'' the build later if necessary. -\end{itemize} - -\subsection{Mimicking Subversion's output} - -Let's try to emulate the default output format used by another -revision control tool, Subversion. -\interaction{template.svnstyle.short} - -Since Subversion's output style is fairly simple, it is easy to -copy-and-paste a hunk of its output into a file, and replace the text -produced above by Subversion with the template values we'd like to see -expanded. -\interaction{template.svnstyle.template} - -There are a few small ways in which this template deviates from the -output produced by Subversion. -\begin{itemize} -\item Subversion prints a ``readable'' date (the ``\texttt{Wed, 27 Sep - 2006}'' in the example output above) in parentheses. Mercurial's - templating engine does not provide a way to display a date in this - format without also printing the time and time zone. -\item We emulate Subversion's printing of ``separator'' lines full of - ``\texttt{-}'' characters by ending the template with such a line. - We use the templating engine's \tplkword{header} keyword to print a - separator line as the first line of output (see below), thus - achieving similar output to Subversion. -\item Subversion's output includes a count in the header of the number - of lines in the commit message. We cannot replicate this in - Mercurial; the templating engine does not currently provide a filter - that counts the number of lines the template generates. -\end{itemize} -It took me no more than a minute or two of work to replace literal -text from an example of Subversion's output with some keywords and -filters to give the template above. The style file simply refers to -the template. -\interaction{template.svnstyle.style} - -We could have included the text of the template file directly in the -style file by enclosing it in quotes and replacing the newlines with -``\verb!\n!'' sequences, but it would have made the style file too -difficult to read. Readability is a good guide when you're trying to -decide whether some text belongs in a style file, or in a template -file that the style file points to. If the style file will look too -big or cluttered if you insert a literal piece of text, drop it into a -template instead. - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "00book" -%%% End:
--- a/en/tour-basic.tex Wed Jan 21 14:16:38 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,624 +0,0 @@ -\chapter{A tour of Mercurial: the basics} -\label{chap:tour-basic} - -\section{Installing Mercurial on your system} -\label{sec:tour:install} - -Prebuilt binary packages of Mercurial are available for every popular -operating system. These make it easy to start using Mercurial on your -computer immediately. - -\subsection{Linux} - -Because each Linux distribution has its own packaging tools, policies, -and rate of development, it's difficult to give a comprehensive set of -instructions on how to install Mercurial binaries. The version of -Mercurial that you will end up with can vary depending on how active -the person is who maintains the package for your distribution. - -To keep things simple, I will focus on installing Mercurial from the -command line under the most popular Linux distributions. Most of -these distributions provide graphical package managers that will let -you install Mercurial with a single click; the package name to look -for is \texttt{mercurial}. - -\begin{itemize} -\item[Debian] - \begin{codesample4} - apt-get install mercurial - \end{codesample4} - -\item[Fedora Core] - \begin{codesample4} - yum install mercurial - \end{codesample4} - -\item[Gentoo] - \begin{codesample4} - emerge mercurial - \end{codesample4} - -\item[OpenSUSE] - \begin{codesample4} - yum install mercurial - \end{codesample4} - -\item[Ubuntu] Ubuntu's Mercurial package is based on Debian's. To - install it, run the following command. - \begin{codesample4} - apt-get install mercurial - \end{codesample4} - The Ubuntu package for Mercurial tends to lag behind the Debian - version by a considerable time margin (at the time of writing, seven - months), which in some cases will mean that on Ubuntu, you may run - into problems that have since been fixed in the Debian package. -\end{itemize} - -\subsection{Solaris} - -SunFreeWare, at \url{http://www.sunfreeware.com}, is a good source for a -large number of pre-built Solaris packages for 32 and 64 bit Intel and -Sparc architectures, including current versions of Mercurial. - -\subsection{Mac OS X} - -Lee Cantey publishes an installer of Mercurial for Mac OS~X at -\url{http://mercurial.berkwood.com}. This package works on both -Intel-~and Power-based Macs. Before you can use it, you must install -a compatible version of Universal MacPython~\cite{web:macpython}. This -is easy to do; simply follow the instructions on Lee's site. - -It's also possible to install Mercurial using Fink or MacPorts, -two popular free package managers for Mac OS X. If you have Fink, -use \command{sudo apt-get install mercurial-py25}. If MacPorts, -\command{sudo port install mercurial}. - -\subsection{Windows} - -Lee Cantey publishes an installer of Mercurial for Windows at -\url{http://mercurial.berkwood.com}. This package has no external -dependencies; it ``just works''. - -\begin{note} - The Windows version of Mercurial does not automatically convert line - endings between Windows and Unix styles. If you want to share work - with Unix users, you must do a little additional configuration - work. XXX Flesh this out. -\end{note} - -\section{Getting started} - -To begin, we'll use the \hgcmd{version} command to find out whether -Mercurial is actually installed properly. The actual version -information that it prints isn't so important; it's whether it prints -anything at all that we care about. -\interaction{tour.version} - -\subsection{Built-in help} - -Mercurial provides a built-in help system. This is invaluable for those -times when you find yourself stuck trying to remember how to run a -command. If you are completely stuck, simply run \hgcmd{help}; it -will print a brief list of commands, along with a description of what -each does. If you ask for help on a specific command (as below), it -prints more detailed information. -\interaction{tour.help} -For a more impressive level of detail (which you won't usually need) -run \hgcmdargs{help}{\hggopt{-v}}. The \hggopt{-v} option is short -for \hggopt{--verbose}, and tells Mercurial to print more information -than it usually would. - -\section{Working with a repository} - -In Mercurial, everything happens inside a \emph{repository}. The -repository for a project contains all of the files that ``belong to'' -that project, along with a historical record of the project's files. - -There's nothing particularly magical about a repository; it is simply -a directory tree in your filesystem that Mercurial treats as special. -You can rename or delete a repository any time you like, using either the -command line or your file browser. - -\subsection{Making a local copy of a repository} - -\emph{Copying} a repository is just a little bit special. While you -could use a normal file copying command to make a copy of a -repository, it's best to use a built-in command that Mercurial -provides. This command is called \hgcmd{clone}, because it creates an -identical copy of an existing repository. -\interaction{tour.clone} -If our clone succeeded, we should now have a local directory called -\dirname{hello}. This directory will contain some files. -\interaction{tour.ls} -These files have the same contents and history in our repository as -they do in the repository we cloned. - -Every Mercurial repository is complete, self-contained, and -independent. It contains its own private copy of a project's files -and history. A cloned repository remembers the location of the -repository it was cloned from, but it does not communicate with that -repository, or any other, unless you tell it to. - -What this means for now is that we're free to experiment with our -repository, safe in the knowledge that it's a private ``sandbox'' that -won't affect anyone else. - -\subsection{What's in a repository?} - -When we take a more detailed look inside a repository, we can see that -it contains a directory named \dirname{.hg}. This is where Mercurial -keeps all of its metadata for the repository. -\interaction{tour.ls-a} - -The contents of the \dirname{.hg} directory and its subdirectories are -private to Mercurial. Every other file and directory in the -repository is yours to do with as you please. - -To introduce a little terminology, the \dirname{.hg} directory is the -``real'' repository, and all of the files and directories that coexist -with it are said to live in the \emph{working directory}. An easy way -to remember the distinction is that the \emph{repository} contains the -\emph{history} of your project, while the \emph{working directory} -contains a \emph{snapshot} of your project at a particular point in -history. - -\section{A tour through history} - -One of the first things we might want to do with a new, unfamiliar -repository is understand its history. The \hgcmd{log} command gives -us a view of history. -\interaction{tour.log} -By default, this command prints a brief paragraph of output for each -change to the project that was recorded. In Mercurial terminology, we -call each of these recorded events a \emph{changeset}, because it can -contain a record of changes to several files. - -The fields in a record of output from \hgcmd{log} are as follows. -\begin{itemize} -\item[\texttt{changeset}] This field has the format of a number, - followed by a colon, followed by a hexadecimal string. These are - \emph{identifiers} for the changeset. There are two identifiers - because the number is shorter and easier to type than the hex - string. -\item[\texttt{user}] The identity of the person who created the - changeset. This is a free-form field, but it most often contains a - person's name and email address. -\item[\texttt{date}] The date and time on which the changeset was - created, and the timezone in which it was created. (The date and - time are local to that timezone; they display what time and date it - was for the person who created the changeset.) -\item[\texttt{summary}] The first line of the text message that the - creator of the changeset entered to describe the changeset. -\end{itemize} -The default output printed by \hgcmd{log} is purely a summary; it is -missing a lot of detail. - -Figure~\ref{fig:tour-basic:history} provides a graphical representation of -the history of the \dirname{hello} repository, to make it a little -easier to see which direction history is ``flowing'' in. We'll be -returning to this figure several times in this chapter and the chapter -that follows. - -\begin{figure}[ht] - \centering - \grafix{tour-history} - \caption{Graphical history of the \dirname{hello} repository} - \label{fig:tour-basic:history} -\end{figure} - -\subsection{Changesets, revisions, and talking to other - people} - -As English is a notoriously sloppy language, and computer science has -a hallowed history of terminological confusion (why use one term when -four will do?), revision control has a variety of words and phrases -that mean the same thing. If you are talking about Mercurial history -with other people, you will find that the word ``changeset'' is often -compressed to ``change'' or (when written) ``cset'', and sometimes a -changeset is referred to as a ``revision'' or a ``rev''. - -While it doesn't matter what \emph{word} you use to refer to the -concept of ``a~changeset'', the \emph{identifier} that you use to -refer to ``a~\emph{specific} changeset'' is of great importance. -Recall that the \texttt{changeset} field in the output from -\hgcmd{log} identifies a changeset using both a number and a -hexadecimal string. -\begin{itemize} -\item The revision number is \emph{only valid in that repository}, -\item while the hex string is the \emph{permanent, unchanging - identifier} that will always identify that exact changeset in - \emph{every} copy of the repository. -\end{itemize} -This distinction is important. If you send someone an email talking -about ``revision~33'', there's a high likelihood that their -revision~33 will \emph{not be the same} as yours. The reason for this -is that a revision number depends on the order in which changes -arrived in a repository, and there is no guarantee that the same -changes will happen in the same order in different repositories. -Three changes $a,b,c$ can easily appear in one repository as $0,1,2$, -while in another as $1,0,2$. - -Mercurial uses revision numbers purely as a convenient shorthand. If -you need to discuss a changeset with someone, or make a record of a -changeset for some other reason (for example, in a bug report), use -the hexadecimal identifier. - -\subsection{Viewing specific revisions} - -To narrow the output of \hgcmd{log} down to a single revision, use the -\hgopt{log}{-r} (or \hgopt{log}{--rev}) option. You can use either a -revision number or a long-form changeset identifier, and you can -provide as many revisions as you want. \interaction{tour.log-r} - -If you want to see the history of several revisions without having to -list each one, you can use \emph{range notation}; this lets you -express the idea ``I want all revisions between $a$ and $b$, -inclusive''. -\interaction{tour.log.range} -Mercurial also honours the order in which you specify revisions, so -\hgcmdargs{log}{-r 2:4} prints $2,3,4$ while \hgcmdargs{log}{-r 4:2} -prints $4,3,2$. - -\subsection{More detailed information} - -While the summary information printed by \hgcmd{log} is useful if you -already know what you're looking for, you may need to see a complete -description of the change, or a list of the files changed, if you're -trying to decide whether a changeset is the one you're looking for. -The \hgcmd{log} command's \hggopt{-v} (or \hggopt{--verbose}) -option gives you this extra detail. -\interaction{tour.log-v} - -If you want to see both the description and content of a change, add -the \hgopt{log}{-p} (or \hgopt{log}{--patch}) option. This displays -the content of a change as a \emph{unified diff} (if you've never seen -a unified diff before, see section~\ref{sec:mq:patch} for an overview). -\interaction{tour.log-vp} - -\section{All about command options} - -Let's take a brief break from exploring Mercurial commands to discuss -a pattern in the way that they work; you may find this useful to keep -in mind as we continue our tour. - -Mercurial has a consistent and straightforward approach to dealing -with the options that you can pass to commands. It follows the -conventions for options that are common to modern Linux and Unix -systems. -\begin{itemize} -\item Every option has a long name. For example, as we've already - seen, the \hgcmd{log} command accepts a \hgopt{log}{--rev} option. -\item Most options have short names, too. Instead of - \hgopt{log}{--rev}, we can use \hgopt{log}{-r}. (The reason that - some options don't have short names is that the options in question - are rarely used.) -\item Long options start with two dashes (e.g.~\hgopt{log}{--rev}), - while short options start with one (e.g.~\hgopt{log}{-r}). -\item Option naming and usage is consistent across commands. For - example, every command that lets you specify a changeset~ID or - revision number accepts both \hgopt{log}{-r} and \hgopt{log}{--rev} - arguments. -\end{itemize} -In the examples throughout this book, I use short options instead of -long. This just reflects my own preference, so don't read anything -significant into it. - -Most commands that print output of some kind will print more output -when passed a \hggopt{-v} (or \hggopt{--verbose}) option, and less -when passed \hggopt{-q} (or \hggopt{--quiet}). - -\section{Making and reviewing changes} - -Now that we have a grasp of viewing history in Mercurial, let's take a -look at making some changes and examining them. - -The first thing we'll do is isolate our experiment in a repository of -its own. We use the \hgcmd{clone} command, but we don't need to -clone a copy of the remote repository. Since we already have a copy -of it locally, we can just clone that instead. This is much faster -than cloning over the network, and cloning a local repository uses -less disk space in most cases, too. -\interaction{tour.reclone} -As an aside, it's often good practice to keep a ``pristine'' copy of a -remote repository around, which you can then make temporary clones of -to create sandboxes for each task you want to work on. This lets you -work on multiple tasks in parallel, each isolated from the others -until it's complete and you're ready to integrate it back. Because -local clones are so cheap, there's almost no overhead to cloning and -destroying repositories whenever you want. - -In our \dirname{my-hello} repository, we have a file -\filename{hello.c} that contains the classic ``hello, world'' program. -Let's use the ancient and venerable \command{sed} command to edit this -file so that it prints a second line of output. (I'm only using -\command{sed} to do this because it's easy to write a scripted example -this way. Since you're not under the same constraint, you probably -won't want to use \command{sed}; simply use your preferred text editor to -do the same thing.) -\interaction{tour.sed} - -Mercurial's \hgcmd{status} command will tell us what Mercurial knows -about the files in the repository. -\interaction{tour.status} -The \hgcmd{status} command prints no output for some files, but a line -starting with ``\texttt{M}'' for \filename{hello.c}. Unless you tell -it to, \hgcmd{status} will not print any output for files that have -not been modified. - -The ``\texttt{M}'' indicates that Mercurial has noticed that we -modified \filename{hello.c}. We didn't need to \emph{inform} -Mercurial that we were going to modify the file before we started, or -that we had modified the file after we were done; it was able to -figure this out itself. - -It's a little bit helpful to know that we've modified -\filename{hello.c}, but we might prefer to know exactly \emph{what} -changes we've made to it. To do this, we use the \hgcmd{diff} -command. -\interaction{tour.diff} - -\section{Recording changes in a new changeset} - -We can modify files, build and test our changes, and use -\hgcmd{status} and \hgcmd{diff} to review our changes, until we're -satisfied with what we've done and arrive at a natural stopping point -where we want to record our work in a new changeset. - -The \hgcmd{commit} command lets us create a new changeset; we'll -usually refer to this as ``making a commit'' or ``committing''. - -\subsection{Setting up a username} - -When you try to run \hgcmd{commit} for the first time, it is not -guaranteed to succeed. Mercurial records your name and address with -each change that you commit, so that you and others will later be able -to tell who made each change. Mercurial tries to automatically figure -out a sensible username to commit the change with. It will attempt -each of the following methods, in order: -\begin{enumerate} -\item If you specify a \hgopt{commit}{-u} option to the \hgcmd{commit} - command on the command line, followed by a username, this is always - given the highest precedence. -\item If you have set the \envar{HGUSER} environment variable, this is - checked next. -\item If you create a file in your home directory called - \sfilename{.hgrc}, with a \rcitem{ui}{username} entry, that will be - used next. To see what the contents of this file should look like, - refer to section~\ref{sec:tour-basic:username} below. -\item If you have set the \envar{EMAIL} environment variable, this - will be used next. -\item Mercurial will query your system to find out your local user - name and host name, and construct a username from these components. - Since this often results in a username that is not very useful, it - will print a warning if it has to do this. -\end{enumerate} -If all of these mechanisms fail, Mercurial will fail, printing an -error message. In this case, it will not let you commit until you set -up a username. - -You should think of the \envar{HGUSER} environment variable and the -\hgopt{commit}{-u} option to the \hgcmd{commit} command as ways to -\emph{override} Mercurial's default selection of username. For normal -use, the simplest and most robust way to set a username for yourself -is by creating a \sfilename{.hgrc} file; see below for details. - -\subsubsection{Creating a Mercurial configuration file} -\label{sec:tour-basic:username} - -To set a user name, use your favourite editor to create a file called -\sfilename{.hgrc} in your home directory. Mercurial will use this -file to look up your personalised configuration settings. The initial -contents of your \sfilename{.hgrc} should look like this. -\begin{codesample2} - # This is a Mercurial configuration file. - [ui] - username = Firstname Lastname <email.address@domain.net> -\end{codesample2} -The ``\texttt{[ui]}'' line begins a \emph{section} of the config file, -so you can read the ``\texttt{username = ...}'' line as meaning ``set -the value of the \texttt{username} item in the \texttt{ui} section''. -A section continues until a new section begins, or the end of the -file. Mercurial ignores empty lines and treats any text from -``\texttt{\#}'' to the end of a line as a comment. - -\subsubsection{Choosing a user name} - -You can use any text you like as the value of the \texttt{username} -config item, since this information is for reading by other people, -but for interpreting by Mercurial. The convention that most people -follow is to use their name and email address, as in the example -above. - -\begin{note} - Mercurial's built-in web server obfuscates email addresses, to make - it more difficult for the email harvesting tools that spammers use. - This reduces the likelihood that you'll start receiving more junk - email if you publish a Mercurial repository on the web. -\end{note} - -\subsection{Writing a commit message} - -When we commit a change, Mercurial drops us into a text editor, to -enter a message that will describe the modifications we've made in -this changeset. This is called the \emph{commit message}. It will be -a record for readers of what we did and why, and it will be printed by -\hgcmd{log} after we've finished committing. -\interaction{tour.commit} - -The editor that the \hgcmd{commit} command drops us into will contain -an empty line, followed by a number of lines starting with -``\texttt{HG:}''. -\begin{codesample2} - \emph{empty line} - HG: changed hello.c -\end{codesample2} -Mercurial ignores the lines that start with ``\texttt{HG:}''; it uses -them only to tell us which files it's recording changes to. Modifying -or deleting these lines has no effect. - -\subsection{Writing a good commit message} - -Since \hgcmd{log} only prints the first line of a commit message by -default, it's best to write a commit message whose first line stands -alone. Here's a real example of a commit message that \emph{doesn't} -follow this guideline, and hence has a summary that is not readable. -\begin{codesample2} - changeset: 73:584af0e231be - user: Censored Person <censored.person@example.org> - date: Tue Sep 26 21:37:07 2006 -0700 - summary: include buildmeister/commondefs. Add an exports and install -\end{codesample2} - -As far as the remainder of the contents of the commit message are -concerned, there are no hard-and-fast rules. Mercurial itself doesn't -interpret or care about the contents of the commit message, though -your project may have policies that dictate a certain kind of -formatting. - -My personal preference is for short, but informative, commit messages -that tell me something that I can't figure out with a quick glance at -the output of \hgcmdargs{log}{--patch}. - -\subsection{Aborting a commit} - -If you decide that you don't want to commit while in the middle of -editing a commit message, simply exit from your editor without saving -the file that it's editing. This will cause nothing to happen to -either the repository or the working directory. - -If we run the \hgcmd{commit} command without any arguments, it records -all of the changes we've made, as reported by \hgcmd{status} and -\hgcmd{diff}. - -\subsection{Admiring our new handiwork} - -Once we've finished the commit, we can use the \hgcmd{tip} command to -display the changeset we just created. This command produces output -that is identical to \hgcmd{log}, but it only displays the newest -revision in the repository. -\interaction{tour.tip} -We refer to the newest revision in the repository as the tip revision, -or simply the tip. - -\section{Sharing changes} - -We mentioned earlier that repositories in Mercurial are -self-contained. This means that the changeset we just created exists -only in our \dirname{my-hello} repository. Let's look at a few ways -that we can propagate this change into other repositories. - -\subsection{Pulling changes from another repository} -\label{sec:tour:pull} - -To get started, let's clone our original \dirname{hello} repository, -which does not contain the change we just committed. We'll call our -temporary repository \dirname{hello-pull}. -\interaction{tour.clone-pull} - -We'll use the \hgcmd{pull} command to bring changes from -\dirname{my-hello} into \dirname{hello-pull}. However, blindly -pulling unknown changes into a repository is a somewhat scary -prospect. Mercurial provides the \hgcmd{incoming} command to tell us -what changes the \hgcmd{pull} command \emph{would} pull into the -repository, without actually pulling the changes in. -\interaction{tour.incoming} -(Of course, someone could cause more changesets to appear in the -repository that we ran \hgcmd{incoming} in, before we get a chance to -\hgcmd{pull} the changes, so that we could end up pulling changes that we -didn't expect.) - -Bringing changes into a repository is a simple matter of running the -\hgcmd{pull} command, and telling it which repository to pull from. -\interaction{tour.pull} -As you can see from the before-and-after output of \hgcmd{tip}, we -have successfully pulled changes into our repository. There remains -one step before we can see these changes in the working directory. - -\subsection{Updating the working directory} - -We have so far glossed over the relationship between a repository and -its working directory. The \hgcmd{pull} command that we ran in -section~\ref{sec:tour:pull} brought changes into the repository, but -if we check, there's no sign of those changes in the working -directory. This is because \hgcmd{pull} does not (by default) touch -the working directory. Instead, we use the \hgcmd{update} command to -do this. -\interaction{tour.update} - -It might seem a bit strange that \hgcmd{pull} doesn't update the -working directory automatically. There's actually a good reason for -this: you can use \hgcmd{update} to update the working directory to -the state it was in at \emph{any revision} in the history of the -repository. If you had the working directory updated to an old -revision---to hunt down the origin of a bug, say---and ran a -\hgcmd{pull} which automatically updated the working directory to a -new revision, you might not be terribly happy. - -However, since pull-then-update is such a common thing to do, -Mercurial lets you combine the two by passing the \hgopt{pull}{-u} -option to \hgcmd{pull}. -\begin{codesample2} - hg pull -u -\end{codesample2} -If you look back at the output of \hgcmd{pull} in -section~\ref{sec:tour:pull} when we ran it without \hgopt{pull}{-u}, -you can see that it printed a helpful reminder that we'd have to take -an explicit step to update the working directory: -\begin{codesample2} - (run 'hg update' to get a working copy) -\end{codesample2} - -To find out what revision the working directory is at, use the -\hgcmd{parents} command. -\interaction{tour.parents} -If you look back at figure~\ref{fig:tour-basic:history}, you'll see -arrows connecting each changeset. The node that the arrow leads -\emph{from} in each case is a parent, and the node that the arrow -leads \emph{to} is its child. The working directory has a parent in -just the same way; this is the changeset that the working directory -currently contains. - -To update the working directory to a particular revision, give a -revision number or changeset~ID to the \hgcmd{update} command. -\interaction{tour.older} -If you omit an explicit revision, \hgcmd{update} will update to the -tip revision, as shown by the second call to \hgcmd{update} in the -example above. - -\subsection{Pushing changes to another repository} - -Mercurial lets us push changes to another repository, from the -repository we're currently visiting. As with the example of -\hgcmd{pull} above, we'll create a temporary repository to push our -changes into. -\interaction{tour.clone-push} -The \hgcmd{outgoing} command tells us what changes would be pushed -into another repository. -\interaction{tour.outgoing} -And the \hgcmd{push} command does the actual push. -\interaction{tour.push} -As with \hgcmd{pull}, the \hgcmd{push} command does not update the -working directory in the repository that it's pushing changes into. -(Unlike \hgcmd{pull}, \hgcmd{push} does not provide a \texttt{-u} -option that updates the other repository's working directory.) - -What happens if we try to pull or push changes and the receiving -repository already has those changes? Nothing too exciting. -\interaction{tour.push.nothing} - -\subsection{Sharing changes over a network} - -The commands we have covered in the previous few sections are not -limited to working with local repositories. Each works in exactly the -same fashion over a network connection; simply pass in a URL instead -of a local path. -\interaction{tour.outgoing.net} -In this example, we can see what changes we could push to the remote -repository, but the repository is understandably not set up to let -anonymous users push to it. -\interaction{tour.push.net} - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "00book" -%%% End:
--- a/en/tour-merge.tex Wed Jan 21 14:16:38 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,283 +0,0 @@ -\chapter{A tour of Mercurial: merging work} -\label{chap:tour-merge} - -We've now covered cloning a repository, making changes in a -repository, and pulling or pushing changes from one repository into -another. Our next step is \emph{merging} changes from separate -repositories. - -\section{Merging streams of work} - -Merging is a fundamental part of working with a distributed revision -control tool. -\begin{itemize} -\item Alice and Bob each have a personal copy of a repository for a - project they're collaborating on. Alice fixes a bug in her - repository; Bob adds a new feature in his. They want the shared - repository to contain both the bug fix and the new feature. -\item I frequently work on several different tasks for a single - project at once, each safely isolated in its own repository. - Working this way means that I often need to merge one piece of my - own work with another. -\end{itemize} - -Because merging is such a common thing to need to do, Mercurial makes -it easy. Let's walk through the process. We'll begin by cloning yet -another repository (see how often they spring up?) and making a change -in it. -\interaction{tour.merge.clone} -We should now have two copies of \filename{hello.c} with different -contents. The histories of the two repositories have also diverged, -as illustrated in figure~\ref{fig:tour-merge:sep-repos}. -\interaction{tour.merge.cat} - -\begin{figure}[ht] - \centering - \grafix{tour-merge-sep-repos} - \caption{Divergent recent histories of the \dirname{my-hello} and - \dirname{my-new-hello} repositories} - \label{fig:tour-merge:sep-repos} -\end{figure} - -We already know that pulling changes from our \dirname{my-hello} -repository will have no effect on the working directory. -\interaction{tour.merge.pull} -However, the \hgcmd{pull} command says something about ``heads''. - -\subsection{Head changesets} - -A head is a change that has no descendants, or children, as they're -also known. The tip revision is thus a head, because the newest -revision in a repository doesn't have any children, but a repository -can contain more than one head. - -\begin{figure}[ht] - \centering - \grafix{tour-merge-pull} - \caption{Repository contents after pulling from \dirname{my-hello} into - \dirname{my-new-hello}} - \label{fig:tour-merge:pull} -\end{figure} - -In figure~\ref{fig:tour-merge:pull}, you can see the effect of the -pull from \dirname{my-hello} into \dirname{my-new-hello}. The history -that was already present in \dirname{my-new-hello} is untouched, but a -new revision has been added. By referring to -figure~\ref{fig:tour-merge:sep-repos}, we can see that the -\emph{changeset ID} remains the same in the new repository, but the -\emph{revision number} has changed. (This, incidentally, is a fine -example of why it's not safe to use revision numbers when discussing -changesets.) We can view the heads in a repository using the -\hgcmd{heads} command. -\interaction{tour.merge.heads} - -\subsection{Performing the merge} - -What happens if we try to use the normal \hgcmd{update} command to -update to the new tip? -\interaction{tour.merge.update} -Mercurial is telling us that the \hgcmd{update} command won't do a -merge; it won't update the working directory when it thinks we might -be wanting to do a merge, unless we force it to do so. Instead, we -use the \hgcmd{merge} command to merge the two heads. -\interaction{tour.merge.merge} - -\begin{figure}[ht] - \centering - \grafix{tour-merge-merge} - \caption{Working directory and repository during merge, and - following commit} - \label{fig:tour-merge:merge} -\end{figure} - -This updates the working directory so that it contains changes from -\emph{both} heads, which is reflected in both the output of -\hgcmd{parents} and the contents of \filename{hello.c}. -\interaction{tour.merge.parents} - -\subsection{Committing the results of the merge} - -Whenever we've done a merge, \hgcmd{parents} will display two parents -until we \hgcmd{commit} the results of the merge. -\interaction{tour.merge.commit} -We now have a new tip revision; notice that it has \emph{both} of -our former heads as its parents. These are the same revisions that -were previously displayed by \hgcmd{parents}. -\interaction{tour.merge.tip} -In figure~\ref{fig:tour-merge:merge}, you can see a representation of -what happens to the working directory during the merge, and how this -affects the repository when the commit happens. During the merge, the -working directory has two parent changesets, and these become the -parents of the new changeset. - -\section{Merging conflicting changes} - -Most merges are simple affairs, but sometimes you'll find yourself -merging changes where each modifies the same portions of the same -files. Unless both modifications are identical, this results in a -\emph{conflict}, where you have to decide how to reconcile the -different changes into something coherent. - -\begin{figure}[ht] - \centering - \grafix{tour-merge-conflict} - \caption{Conflicting changes to a document} - \label{fig:tour-merge:conflict} -\end{figure} - -Figure~\ref{fig:tour-merge:conflict} illustrates an instance of two -conflicting changes to a document. We started with a single version -of the file; then we made some changes; while someone else made -different changes to the same text. Our task in resolving the -conflicting changes is to decide what the file should look like. - -Mercurial doesn't have a built-in facility for handling conflicts. -Instead, it runs an external program called \command{hgmerge}. This -is a shell script that is bundled with Mercurial; you can change it to -behave however you please. What it does by default is try to find one -of several different merging tools that are likely to be installed on -your system. It first tries a few fully automatic merging tools; if -these don't succeed (because the resolution process requires human -guidance) or aren't present, the script tries a few different -graphical merging tools. - -It's also possible to get Mercurial to run another program or script -instead of \command{hgmerge}, by setting the \envar{HGMERGE} -environment variable to the name of your preferred program. - -\subsection{Using a graphical merge tool} - -My preferred graphical merge tool is \command{kdiff3}, which I'll use -to describe the features that are common to graphical file merging -tools. You can see a screenshot of \command{kdiff3} in action in -figure~\ref{fig:tour-merge:kdiff3}. The kind of merge it is -performing is called a \emph{three-way merge}, because there are three -different versions of the file of interest to us. The tool thus -splits the upper portion of the window into three panes: -\begin{itemize} -\item At the left is the \emph{base} version of the file, i.e.~the - most recent version from which the two versions we're trying to - merge are descended. -\item In the middle is ``our'' version of the file, with the contents - that we modified. -\item On the right is ``their'' version of the file, the one that - from the changeset that we're trying to merge with. -\end{itemize} -In the pane below these is the current \emph{result} of the merge. -Our task is to replace all of the red text, which indicates unresolved -conflicts, with some sensible merger of the ``ours'' and ``theirs'' -versions of the file. - -All four of these panes are \emph{locked together}; if we scroll -vertically or horizontally in any of them, the others are updated to -display the corresponding sections of their respective files. - -\begin{figure}[ht] - \centering - \grafix{kdiff3} - \caption{Using \command{kdiff3} to merge versions of a file} - \label{fig:tour-merge:kdiff3} -\end{figure} - -For each conflicting portion of the file, we can choose to resolve -the conflict using some combination of text from the base version, -ours, or theirs. We can also manually edit the merged file at any -time, in case we need to make further modifications. - -There are \emph{many} file merging tools available, too many to cover -here. They vary in which platforms they are available for, and in -their particular strengths and weaknesses. Most are tuned for merging -files containing plain text, while a few are aimed at specialised file -formats (generally XML). - -\subsection{A worked example} - -In this example, we will reproduce the file modification history of -figure~\ref{fig:tour-merge:conflict} above. Let's begin by creating a -repository with a base version of our document. -\interaction{tour-merge-conflict.wife} -We'll clone the repository and make a change to the file. -\interaction{tour-merge-conflict.cousin} -And another clone, to simulate someone else making a change to the -file. (This hints at the idea that it's not all that unusual to merge -with yourself when you isolate tasks in separate repositories, and -indeed to find and resolve conflicts while doing so.) -\interaction{tour-merge-conflict.son} -Having created two different versions of the file, we'll set up an -environment suitable for running our merge. -\interaction{tour-merge-conflict.pull} - -In this example, I won't use Mercurial's normal \command{hgmerge} -program to do the merge, because it would drop my nice automated -example-running tool into a graphical user interface. Instead, I'll -set \envar{HGMERGE} to tell Mercurial to use the non-interactive -\command{merge} command. This is bundled with many Unix-like systems. -If you're following this example on your computer, don't bother -setting \envar{HGMERGE}. -\interaction{tour-merge-conflict.merge} -Because \command{merge} can't resolve the conflicting changes, it -leaves \emph{merge markers} inside the file that has conflicts, -indicating which lines have conflicts, and whether they came from our -version of the file or theirs. - -Mercurial can tell from the way \command{merge} exits that it wasn't -able to merge successfully, so it tells us what commands we'll need to -run if we want to redo the merging operation. This could be useful -if, for example, we were running a graphical merge tool and quit -because we were confused or realised we had made a mistake. - -If automatic or manual merges fail, there's nothing to prevent us from -``fixing up'' the affected files ourselves, and committing the results -of our merge: -\interaction{tour-merge-conflict.commit} - -\section{Simplifying the pull-merge-commit sequence} -\label{sec:tour-merge:fetch} - -The process of merging changes as outlined above is straightforward, -but requires running three commands in sequence. -\begin{codesample2} - hg pull - hg merge - hg commit -m 'Merged remote changes' -\end{codesample2} -In the case of the final commit, you also need to enter a commit -message, which is almost always going to be a piece of uninteresting -``boilerplate'' text. - -It would be nice to reduce the number of steps needed, if this were -possible. Indeed, Mercurial is distributed with an extension called -\hgext{fetch} that does just this. - -Mercurial provides a flexible extension mechanism that lets people -extend its functionality, while keeping the core of Mercurial small -and easy to deal with. Some extensions add new commands that you can -use from the command line, while others work ``behind the scenes,'' -for example adding capabilities to the server. - -The \hgext{fetch} extension adds a new command called, not -surprisingly, \hgcmd{fetch}. This extension acts as a combination of -\hgcmd{pull}, \hgcmd{update} and \hgcmd{merge}. It begins by pulling -changes from another repository into the current repository. If it -finds that the changes added a new head to the repository, it begins a -merge, then commits the result of the merge with an -automatically-generated commit message. If no new heads were added, -it updates the working directory to the new tip changeset. - -Enabling the \hgext{fetch} extension is easy. Edit your -\sfilename{.hgrc}, and either go to the \rcsection{extensions} section -or create an \rcsection{extensions} section. Then add a line that -simply reads ``\Verb+fetch +''. -\begin{codesample2} - [extensions] - fetch = -\end{codesample2} -(Normally, on the right-hand side of the ``\texttt{=}'' would appear -the location of the extension, but since the \hgext{fetch} extension -is in the standard distribution, Mercurial knows where to search for -it.) - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "00book" -%%% End:
--- a/en/undo.tex Wed Jan 21 14:16:38 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,766 +0,0 @@ -\chapter{Finding and fixing your mistakes} -\label{chap:undo} - -To err might be human, but to really handle the consequences well -takes a top-notch revision control system. In this chapter, we'll -discuss some of the techniques you can use when you find that a -problem has crept into your project. Mercurial has some highly -capable features that will help you to isolate the sources of -problems, and to handle them appropriately. - -\section{Erasing local history} - -\subsection{The accidental commit} - -I have the occasional but persistent problem of typing rather more -quickly than I can think, which sometimes results in me committing a -changeset that is either incomplete or plain wrong. In my case, the -usual kind of incomplete changeset is one in which I've created a new -source file, but forgotten to \hgcmd{add} it. A ``plain wrong'' -changeset is not as common, but no less annoying. - -\subsection{Rolling back a transaction} -\label{sec:undo:rollback} - -In section~\ref{sec:concepts:txn}, I mentioned that Mercurial treats -each modification of a repository as a \emph{transaction}. Every time -you commit a changeset or pull changes from another repository, -Mercurial remembers what you did. You can undo, or \emph{roll back}, -exactly one of these actions using the \hgcmd{rollback} command. (See -section~\ref{sec:undo:rollback-after-push} for an important caveat -about the use of this command.) - -Here's a mistake that I often find myself making: committing a change -in which I've created a new file, but forgotten to \hgcmd{add} it. -\interaction{rollback.commit} -Looking at the output of \hgcmd{status} after the commit immediately -confirms the error. -\interaction{rollback.status} -The commit captured the changes to the file \filename{a}, but not the -new file \filename{b}. If I were to push this changeset to a -repository that I shared with a colleague, the chances are high that -something in \filename{a} would refer to \filename{b}, which would not -be present in their repository when they pulled my changes. I would -thus become the object of some indignation. - -However, luck is with me---I've caught my error before I pushed the -changeset. I use the \hgcmd{rollback} command, and Mercurial makes -that last changeset vanish. -\interaction{rollback.rollback} -Notice that the changeset is no longer present in the repository's -history, and the working directory once again thinks that the file -\filename{a} is modified. The commit and rollback have left the -working directory exactly as it was prior to the commit; the changeset -has been completely erased. I can now safely \hgcmd{add} the file -\filename{b}, and rerun my commit. -\interaction{rollback.add} - -\subsection{The erroneous pull} - -It's common practice with Mercurial to maintain separate development -branches of a project in different repositories. Your development -team might have one shared repository for your project's ``0.9'' -release, and another, containing different changes, for the ``1.0'' -release. - -Given this, you can imagine that the consequences could be messy if -you had a local ``0.9'' repository, and accidentally pulled changes -from the shared ``1.0'' repository into it. At worst, you could be -paying insufficient attention, and push those changes into the shared -``0.9'' tree, confusing your entire team (but don't worry, we'll -return to this horror scenario later). However, it's more likely that -you'll notice immediately, because Mercurial will display the URL it's -pulling from, or you will see it pull a suspiciously large number of -changes into the repository. - -The \hgcmd{rollback} command will work nicely to expunge all of the -changesets that you just pulled. Mercurial groups all changes from -one \hgcmd{pull} into a single transaction, so one \hgcmd{rollback} is -all you need to undo this mistake. - -\subsection{Rolling back is useless once you've pushed} -\label{sec:undo:rollback-after-push} - -The value of the \hgcmd{rollback} command drops to zero once you've -pushed your changes to another repository. Rolling back a change -makes it disappear entirely, but \emph{only} in the repository in -which you perform the \hgcmd{rollback}. Because a rollback eliminates -history, there's no way for the disappearance of a change to propagate -between repositories. - -If you've pushed a change to another repository---particularly if it's -a shared repository---it has essentially ``escaped into the wild,'' -and you'll have to recover from your mistake in a different way. What -will happen if you push a changeset somewhere, then roll it back, then -pull from the repository you pushed to, is that the changeset will -reappear in your repository. - -(If you absolutely know for sure that the change you want to roll back -is the most recent change in the repository that you pushed to, -\emph{and} you know that nobody else could have pulled it from that -repository, you can roll back the changeset there, too, but you really -should really not rely on this working reliably. If you do this, -sooner or later a change really will make it into a repository that -you don't directly control (or have forgotten about), and come back to -bite you.) - -\subsection{You can only roll back once} - -Mercurial stores exactly one transaction in its transaction log; that -transaction is the most recent one that occurred in the repository. -This means that you can only roll back one transaction. If you expect -to be able to roll back one transaction, then its predecessor, this is -not the behaviour you will get. -\interaction{rollback.twice} -Once you've rolled back one transaction in a repository, you can't -roll back again in that repository until you perform another commit or -pull. - -\section{Reverting the mistaken change} - -If you make a modification to a file, and decide that you really -didn't want to change the file at all, and you haven't yet committed -your changes, the \hgcmd{revert} command is the one you'll need. It -looks at the changeset that's the parent of the working directory, and -restores the contents of the file to their state as of that changeset. -(That's a long-winded way of saying that, in the normal case, it -undoes your modifications.) - -Let's illustrate how the \hgcmd{revert} command works with yet another -small example. We'll begin by modifying a file that Mercurial is -already tracking. -\interaction{daily.revert.modify} -If we don't want that change, we can simply \hgcmd{revert} the file. -\interaction{daily.revert.unmodify} -The \hgcmd{revert} command provides us with an extra degree of safety -by saving our modified file with a \filename{.orig} extension. -\interaction{daily.revert.status} - -Here is a summary of the cases that the \hgcmd{revert} command can -deal with. We will describe each of these in more detail in the -section that follows. -\begin{itemize} -\item If you modify a file, it will restore the file to its unmodified - state. -\item If you \hgcmd{add} a file, it will undo the ``added'' state of - the file, but leave the file itself untouched. -\item If you delete a file without telling Mercurial, it will restore - the file to its unmodified contents. -\item If you use the \hgcmd{remove} command to remove a file, it will - undo the ``removed'' state of the file, and restore the file to its - unmodified contents. -\end{itemize} - -\subsection{File management errors} -\label{sec:undo:mgmt} - -The \hgcmd{revert} command is useful for more than just modified -files. It lets you reverse the results of all of Mercurial's file -management commands---\hgcmd{add}, \hgcmd{remove}, and so on. - -If you \hgcmd{add} a file, then decide that in fact you don't want -Mercurial to track it, use \hgcmd{revert} to undo the add. Don't -worry; Mercurial will not modify the file in any way. It will just -``unmark'' the file. -\interaction{daily.revert.add} - -Similarly, if you ask Mercurial to \hgcmd{remove} a file, you can use -\hgcmd{revert} to restore it to the contents it had as of the parent -of the working directory. -\interaction{daily.revert.remove} -This works just as well for a file that you deleted by hand, without -telling Mercurial (recall that in Mercurial terminology, this kind of -file is called ``missing''). -\interaction{daily.revert.missing} - -If you revert a \hgcmd{copy}, the copied-to file remains in your -working directory afterwards, untracked. Since a copy doesn't affect -the copied-from file in any way, Mercurial doesn't do anything with -the copied-from file. -\interaction{daily.revert.copy} - -\subsubsection{A slightly special case: reverting a rename} - -If you \hgcmd{rename} a file, there is one small detail that -you should remember. When you \hgcmd{revert} a rename, it's not -enough to provide the name of the renamed-to file, as you can see -here. -\interaction{daily.revert.rename} -As you can see from the output of \hgcmd{status}, the renamed-to file -is no longer identified as added, but the renamed-\emph{from} file is -still removed! This is counter-intuitive (at least to me), but at -least it's easy to deal with. -\interaction{daily.revert.rename-orig} -So remember, to revert a \hgcmd{rename}, you must provide \emph{both} -the source and destination names. - -% TODO: the output doesn't look like it will be removed! - -(By the way, if you rename a file, then modify the renamed-to file, -then revert both components of the rename, when Mercurial restores the -file that was removed as part of the rename, it will be unmodified. -If you need the modifications in the renamed-to file to show up in the -renamed-from file, don't forget to copy them over.) - -These fiddly aspects of reverting a rename arguably constitute a small -bug in Mercurial. - -\section{Dealing with committed changes} - -Consider a case where you have committed a change $a$, and another -change $b$ on top of it; you then realise that change $a$ was -incorrect. Mercurial lets you ``back out'' an entire changeset -automatically, and building blocks that let you reverse part of a -changeset by hand. - -Before you read this section, here's something to keep in mind: the -\hgcmd{backout} command undoes changes by \emph{adding} history, not -by modifying or erasing it. It's the right tool to use if you're -fixing bugs, but not if you're trying to undo some change that has -catastrophic consequences. To deal with those, see -section~\ref{sec:undo:aaaiiieee}. - -\subsection{Backing out a changeset} - -The \hgcmd{backout} command lets you ``undo'' the effects of an entire -changeset in an automated fashion. Because Mercurial's history is -immutable, this command \emph{does not} get rid of the changeset you -want to undo. Instead, it creates a new changeset that -\emph{reverses} the effect of the to-be-undone changeset. - -The operation of the \hgcmd{backout} command is a little intricate, so -let's illustrate it with some examples. First, we'll create a -repository with some simple changes. -\interaction{backout.init} - -The \hgcmd{backout} command takes a single changeset ID as its -argument; this is the changeset to back out. Normally, -\hgcmd{backout} will drop you into a text editor to write a commit -message, so you can record why you're backing the change out. In this -example, we provide a commit message on the command line using the -\hgopt{backout}{-m} option. - -\subsection{Backing out the tip changeset} - -We're going to start by backing out the last changeset we committed. -\interaction{backout.simple} -You can see that the second line from \filename{myfile} is no longer -present. Taking a look at the output of \hgcmd{log} gives us an idea -of what the \hgcmd{backout} command has done. -\interaction{backout.simple.log} -Notice that the new changeset that \hgcmd{backout} has created is a -child of the changeset we backed out. It's easier to see this in -figure~\ref{fig:undo:backout}, which presents a graphical view of the -change history. As you can see, the history is nice and linear. - -\begin{figure}[htb] - \centering - \grafix{undo-simple} - \caption{Backing out a change using the \hgcmd{backout} command} - \label{fig:undo:backout} -\end{figure} - -\subsection{Backing out a non-tip change} - -If you want to back out a change other than the last one you -committed, pass the \hgopt{backout}{--merge} option to the -\hgcmd{backout} command. -\interaction{backout.non-tip.clone} -This makes backing out any changeset a ``one-shot'' operation that's -usually simple and fast. -\interaction{backout.non-tip.backout} - -If you take a look at the contents of \filename{myfile} after the -backout finishes, you'll see that the first and third changes are -present, but not the second. -\interaction{backout.non-tip.cat} - -As the graphical history in figure~\ref{fig:undo:backout-non-tip} -illustrates, Mercurial actually commits \emph{two} changes in this -kind of situation (the box-shaped nodes are the ones that Mercurial -commits automatically). Before Mercurial begins the backout process, -it first remembers what the current parent of the working directory -is. It then backs out the target changeset, and commits that as a -changeset. Finally, it merges back to the previous parent of the -working directory, and commits the result of the merge. - -% TODO: to me it looks like mercurial doesn't commit the second merge automatically! - -\begin{figure}[htb] - \centering - \grafix{undo-non-tip} - \caption{Automated backout of a non-tip change using the \hgcmd{backout} command} - \label{fig:undo:backout-non-tip} -\end{figure} - -The result is that you end up ``back where you were'', only with some -extra history that undoes the effect of the changeset you wanted to -back out. - -\subsubsection{Always use the \hgopt{backout}{--merge} option} - -In fact, since the \hgopt{backout}{--merge} option will do the ``right -thing'' whether or not the changeset you're backing out is the tip -(i.e.~it won't try to merge if it's backing out the tip, since there's -no need), you should \emph{always} use this option when you run the -\hgcmd{backout} command. - -\subsection{Gaining more control of the backout process} - -While I've recommended that you always use the -\hgopt{backout}{--merge} option when backing out a change, the -\hgcmd{backout} command lets you decide how to merge a backout -changeset. Taking control of the backout process by hand is something -you will rarely need to do, but it can be useful to understand what -the \hgcmd{backout} command is doing for you automatically. To -illustrate this, let's clone our first repository, but omit the -backout change that it contains. - -\interaction{backout.manual.clone} -As with our earlier example, We'll commit a third changeset, then back -out its parent, and see what happens. -\interaction{backout.manual.backout} -Our new changeset is again a descendant of the changeset we backout -out; it's thus a new head, \emph{not} a descendant of the changeset -that was the tip. The \hgcmd{backout} command was quite explicit in -telling us this. -\interaction{backout.manual.log} - -Again, it's easier to see what has happened by looking at a graph of -the revision history, in figure~\ref{fig:undo:backout-manual}. This -makes it clear that when we use \hgcmd{backout} to back out a change -other than the tip, Mercurial adds a new head to the repository (the -change it committed is box-shaped). - -\begin{figure}[htb] - \centering - \grafix{undo-manual} - \caption{Backing out a change using the \hgcmd{backout} command} - \label{fig:undo:backout-manual} -\end{figure} - -After the \hgcmd{backout} command has completed, it leaves the new -``backout'' changeset as the parent of the working directory. -\interaction{backout.manual.parents} -Now we have two isolated sets of changes. -\interaction{backout.manual.heads} - -Let's think about what we expect to see as the contents of -\filename{myfile} now. The first change should be present, because -we've never backed it out. The second change should be missing, as -that's the change we backed out. Since the history graph shows the -third change as a separate head, we \emph{don't} expect to see the -third change present in \filename{myfile}. -\interaction{backout.manual.cat} -To get the third change back into the file, we just do a normal merge -of our two heads. -\interaction{backout.manual.merge} -Afterwards, the graphical history of our repository looks like -figure~\ref{fig:undo:backout-manual-merge}. - -\begin{figure}[htb] - \centering - \grafix{undo-manual-merge} - \caption{Manually merging a backout change} - \label{fig:undo:backout-manual-merge} -\end{figure} - -\subsection{Why \hgcmd{backout} works as it does} - -Here's a brief description of how the \hgcmd{backout} command works. -\begin{enumerate} -\item It ensures that the working directory is ``clean'', i.e.~that - the output of \hgcmd{status} would be empty. -\item It remembers the current parent of the working directory. Let's - call this changeset \texttt{orig} -\item It does the equivalent of a \hgcmd{update} to sync the working - directory to the changeset you want to back out. Let's call this - changeset \texttt{backout} -\item It finds the parent of that changeset. Let's call that - changeset \texttt{parent}. -\item For each file that the \texttt{backout} changeset affected, it - does the equivalent of a \hgcmdargs{revert}{-r parent} on that file, - to restore it to the contents it had before that changeset was - committed. -\item It commits the result as a new changeset. This changeset has - \texttt{backout} as its parent. -\item If you specify \hgopt{backout}{--merge} on the command line, it - merges with \texttt{orig}, and commits the result of the merge. -\end{enumerate} - -An alternative way to implement the \hgcmd{backout} command would be -to \hgcmd{export} the to-be-backed-out changeset as a diff, then use -the \cmdopt{patch}{--reverse} option to the \command{patch} command to -reverse the effect of the change without fiddling with the working -directory. This sounds much simpler, but it would not work nearly as -well. - -The reason that \hgcmd{backout} does an update, a commit, a merge, and -another commit is to give the merge machinery the best chance to do a -good job when dealing with all the changes \emph{between} the change -you're backing out and the current tip. - -If you're backing out a changeset that's~100 revisions back in your -project's history, the chances that the \command{patch} command will -be able to apply a reverse diff cleanly are not good, because -intervening changes are likely to have ``broken the context'' that -\command{patch} uses to determine whether it can apply a patch (if -this sounds like gibberish, see \ref{sec:mq:patch} for a -discussion of the \command{patch} command). Also, Mercurial's merge -machinery will handle files and directories being renamed, permission -changes, and modifications to binary files, none of which -\command{patch} can deal with. - -\section{Changes that should never have been} -\label{sec:undo:aaaiiieee} - -Most of the time, the \hgcmd{backout} command is exactly what you need -if you want to undo the effects of a change. It leaves a permanent -record of exactly what you did, both when committing the original -changeset and when you cleaned up after it. - -On rare occasions, though, you may find that you've committed a change -that really should not be present in the repository at all. For -example, it would be very unusual, and usually considered a mistake, -to commit a software project's object files as well as its source -files. Object files have almost no intrinsic value, and they're -\emph{big}, so they increase the size of the repository and the amount -of time it takes to clone or pull changes. - -Before I discuss the options that you have if you commit a ``brown -paper bag'' change (the kind that's so bad that you want to pull a -brown paper bag over your head), let me first discuss some approaches -that probably won't work. - -Since Mercurial treats history as accumulative---every change builds -on top of all changes that preceded it---you generally can't just make -disastrous changes disappear. The one exception is when you've just -committed a change, and it hasn't been pushed or pulled into another -repository. That's when you can safely use the \hgcmd{rollback} -command, as I detailed in section~\ref{sec:undo:rollback}. - -After you've pushed a bad change to another repository, you -\emph{could} still use \hgcmd{rollback} to make your local copy of the -change disappear, but it won't have the consequences you want. The -change will still be present in the remote repository, so it will -reappear in your local repository the next time you pull. - -If a situation like this arises, and you know which repositories your -bad change has propagated into, you can \emph{try} to get rid of the -changeefrom \emph{every} one of those repositories. This is, of -course, not a satisfactory solution: if you miss even a single -repository while you're expunging, the change is still ``in the -wild'', and could propagate further. - -If you've committed one or more changes \emph{after} the change that -you'd like to see disappear, your options are further reduced. -Mercurial doesn't provide a way to ``punch a hole'' in history, -leaving changesets intact. - -XXX This needs filling out. The \texttt{hg-replay} script in the -\texttt{examples} directory works, but doesn't handle merge -changesets. Kind of an important omission. - -\subsection{Protect yourself from ``escaped'' changes} - -If you've committed some changes to your local repository and they've -been pushed or pulled somewhere else, this isn't necessarily a -disaster. You can protect yourself ahead of time against some classes -of bad changeset. This is particularly easy if your team usually -pulls changes from a central repository. - -By configuring some hooks on that repository to validate incoming -changesets (see chapter~\ref{chap:hook}), you can automatically -prevent some kinds of bad changeset from being pushed to the central -repository at all. With such a configuration in place, some kinds of -bad changeset will naturally tend to ``die out'' because they can't -propagate into the central repository. Better yet, this happens -without any need for explicit intervention. - -For instance, an incoming change hook that verifies that a changeset -will actually compile can prevent people from inadvertantly ``breaking -the build''. - -\section{Finding the source of a bug} -\label{sec:undo:bisect} - -While it's all very well to be able to back out a changeset that -introduced a bug, this requires that you know which changeset to back -out. Mercurial provides an invaluable command, called -\hgcmd{bisect}, that helps you to automate this process and accomplish -it very efficiently. - -The idea behind the \hgcmd{bisect} command is that a changeset has -introduced some change of behaviour that you can identify with a -simple binary test. You don't know which piece of code introduced the -change, but you know how to test for the presence of the bug. The -\hgcmd{bisect} command uses your test to direct its search for the -changeset that introduced the code that caused the bug. - -Here are a few scenarios to help you understand how you might apply -this command. -\begin{itemize} -\item The most recent version of your software has a bug that you - remember wasn't present a few weeks ago, but you don't know when it - was introduced. Here, your binary test checks for the presence of - that bug. -\item You fixed a bug in a rush, and now it's time to close the entry - in your team's bug database. The bug database requires a changeset - ID when you close an entry, but you don't remember which changeset - you fixed the bug in. Once again, your binary test checks for the - presence of the bug. -\item Your software works correctly, but runs~15\% slower than the - last time you measured it. You want to know which changeset - introduced the performance regression. In this case, your binary - test measures the performance of your software, to see whether it's - ``fast'' or ``slow''. -\item The sizes of the components of your project that you ship - exploded recently, and you suspect that something changed in the way - you build your project. -\end{itemize} - -From these examples, it should be clear that the \hgcmd{bisect} -command is not useful only for finding the sources of bugs. You can -use it to find any ``emergent property'' of a repository (anything -that you can't find from a simple text search of the files in the -tree) for which you can write a binary test. - -We'll introduce a little bit of terminology here, just to make it -clear which parts of the search process are your responsibility, and -which are Mercurial's. A \emph{test} is something that \emph{you} run -when \hgcmd{bisect} chooses a changeset. A \emph{probe} is what -\hgcmd{bisect} runs to tell whether a revision is good. Finally, -we'll use the word ``bisect'', as both a noun and a verb, to stand in -for the phrase ``search using the \hgcmd{bisect} command. - -One simple way to automate the searching process would be simply to -probe every changeset. However, this scales poorly. If it took ten -minutes to test a single changeset, and you had 10,000 changesets in -your repository, the exhaustive approach would take on average~35 -\emph{days} to find the changeset that introduced a bug. Even if you -knew that the bug was introduced by one of the last 500 changesets, -and limited your search to those, you'd still be looking at over 40 -hours to find the changeset that introduced your bug. - -What the \hgcmd{bisect} command does is use its knowledge of the -``shape'' of your project's revision history to perform a search in -time proportional to the \emph{logarithm} of the number of changesets -to check (the kind of search it performs is called a dichotomic -search). With this approach, searching through 10,000 changesets will -take less than three hours, even at ten minutes per test (the search -will require about 14 tests). Limit your search to the last hundred -changesets, and it will take only about an hour (roughly seven tests). - -The \hgcmd{bisect} command is aware of the ``branchy'' nature of a -Mercurial project's revision history, so it has no problems dealing -with branches, merges, or multiple heads in a repoository. It can -prune entire branches of history with a single probe, which is how it -operates so efficiently. - -\subsection{Using the \hgcmd{bisect} command} - -Here's an example of \hgcmd{bisect} in action. - -\begin{note} - In versions 0.9.5 and earlier of Mercurial, \hgcmd{bisect} was not a - core command: it was distributed with Mercurial as an extension. - This section describes the built-in command, not the old extension. -\end{note} - -Now let's create a repository, so that we can try out the -\hgcmd{bisect} command in isolation. -We'll simulate a project that has a bug in it in a simple-minded way: -create trivial changes in a loop, and nominate one specific change -that will have the ``bug''. This loop creates 35 changesets, each -adding a single file to the repository. We'll represent our ``bug'' -with a file that contains the text ``i have a gub''. -\interaction{bisect.commits} - -The next thing that we'd like to do is figure out how to use the -\hgcmd{bisect} command. We can use Mercurial's normal built-in help -mechanism for this. -\interaction{bisect.help} - -The \hgcmd{bisect} command works in steps. Each step proceeds as follows. -\begin{enumerate} -\item You run your binary test. - \begin{itemize} - \item If the test succeeded, you tell \hgcmd{bisect} by running the - \hgcmdargs{bisect}{good} command. - \item If it failed, run the \hgcmdargs{bisect}{--bad} command. - \end{itemize} -\item The command uses your information to decide which changeset to - test next. -\item It updates the working directory to that changeset, and the - process begins again. -\end{enumerate} -The process ends when \hgcmd{bisect} identifies a unique changeset -that marks the point where your test transitioned from ``succeeding'' -to ``failing''. - -To start the search, we must run the \hgcmdargs{bisect}{--reset} command. -\interaction{bisect.search.init} - -In our case, the binary test we use is simple: we check to see if any -file in the repository contains the string ``i have a gub''. If it -does, this changeset contains the change that ``caused the bug''. By -convention, a changeset that has the property we're searching for is -``bad'', while one that doesn't is ``good''. - -Most of the time, the revision to which the working directory is -synced (usually the tip) already exhibits the problem introduced by -the buggy change, so we'll mark it as ``bad''. -\interaction{bisect.search.bad-init} - -Our next task is to nominate a changeset that we know \emph{doesn't} -have the bug; the \hgcmd{bisect} command will ``bracket'' its search -between the first pair of good and bad changesets. In our case, we -know that revision~10 didn't have the bug. (I'll have more words -about choosing the first ``good'' changeset later.) -\interaction{bisect.search.good-init} - -Notice that this command printed some output. -\begin{itemize} -\item It told us how many changesets it must consider before it can - identify the one that introduced the bug, and how many tests that - will require. -\item It updated the working directory to the next changeset to test, - and told us which changeset it's testing. -\end{itemize} - -We now run our test in the working directory. We use the -\command{grep} command to see if our ``bad'' file is present in the -working directory. If it is, this revision is bad; if not, this -revision is good. -\interaction{bisect.search.step1} - -This test looks like a perfect candidate for automation, so let's turn -it into a shell function. -\interaction{bisect.search.mytest} -We can now run an entire test step with a single command, -\texttt{mytest}. -\interaction{bisect.search.step2} -A few more invocations of our canned test step command, and we're -done. -\interaction{bisect.search.rest} - -Even though we had~40 changesets to search through, the \hgcmd{bisect} -command let us find the changeset that introduced our ``bug'' with -only five tests. Because the number of tests that the \hgcmd{bisect} -command performs grows logarithmically with the number of changesets to -search, the advantage that it has over the ``brute force'' search -approach increases with every changeset you add. - -\subsection{Cleaning up after your search} - -When you're finished using the \hgcmd{bisect} command in a -repository, you can use the \hgcmdargs{bisect}{reset} command to drop -the information it was using to drive your search. The command -doesn't use much space, so it doesn't matter if you forget to run this -command. However, \hgcmd{bisect} won't let you start a new search in -that repository until you do a \hgcmdargs{bisect}{reset}. -\interaction{bisect.search.reset} - -\section{Tips for finding bugs effectively} - -\subsection{Give consistent input} - -The \hgcmd{bisect} command requires that you correctly report the -result of every test you perform. If you tell it that a test failed -when it really succeeded, it \emph{might} be able to detect the -inconsistency. If it can identify an inconsistency in your reports, -it will tell you that a particular changeset is both good and bad. -However, it can't do this perfectly; it's about as likely to report -the wrong changeset as the source of the bug. - -\subsection{Automate as much as possible} - -When I started using the \hgcmd{bisect} command, I tried a few times -to run my tests by hand, on the command line. This is an approach -that I, at least, am not suited to. After a few tries, I found that I -was making enough mistakes that I was having to restart my searches -several times before finally getting correct results. - -My initial problems with driving the \hgcmd{bisect} command by hand -occurred even with simple searches on small repositories; if the -problem you're looking for is more subtle, or the number of tests that -\hgcmd{bisect} must perform increases, the likelihood of operator -error ruining the search is much higher. Once I started automating my -tests, I had much better results. - -The key to automated testing is twofold: -\begin{itemize} -\item always test for the same symptom, and -\item always feed consistent input to the \hgcmd{bisect} command. -\end{itemize} -In my tutorial example above, the \command{grep} command tests for the -symptom, and the \texttt{if} statement takes the result of this check -and ensures that we always feed the same input to the \hgcmd{bisect} -command. The \texttt{mytest} function marries these together in a -reproducible way, so that every test is uniform and consistent. - -\subsection{Check your results} - -Because the output of a \hgcmd{bisect} search is only as good as the -input you give it, don't take the changeset it reports as the -absolute truth. A simple way to cross-check its report is to manually -run your test at each of the following changesets: -\begin{itemize} -\item The changeset that it reports as the first bad revision. Your - test should still report this as bad. -\item The parent of that changeset (either parent, if it's a merge). - Your test should report this changeset as good. -\item A child of that changeset. Your test should report this - changeset as bad. -\end{itemize} - -\subsection{Beware interference between bugs} - -It's possible that your search for one bug could be disrupted by the -presence of another. For example, let's say your software crashes at -revision 100, and worked correctly at revision 50. Unknown to you, -someone else introduced a different crashing bug at revision 60, and -fixed it at revision 80. This could distort your results in one of -several ways. - -It is possible that this other bug completely ``masks'' yours, which -is to say that it occurs before your bug has a chance to manifest -itself. If you can't avoid that other bug (for example, it prevents -your project from building), and so can't tell whether your bug is -present in a particular changeset, the \hgcmd{bisect} command cannot -help you directly. Instead, you can mark a changeset as untested by -running \hgcmdargs{bisect}{--skip}. - -A different problem could arise if your test for a bug's presence is -not specific enough. If you check for ``my program crashes'', then -both your crashing bug and an unrelated crashing bug that masks it -will look like the same thing, and mislead \hgcmd{bisect}. - -Another useful situation in which to use \hgcmdargs{bisect}{--skip} is -if you can't test a revision because your project was in a broken and -hence untestable state at that revision, perhaps because someone -checked in a change that prevented the project from building. - -\subsection{Bracket your search lazily} - -Choosing the first ``good'' and ``bad'' changesets that will mark the -end points of your search is often easy, but it bears a little -discussion nevertheless. From the perspective of \hgcmd{bisect}, the -``newest'' changeset is conventionally ``bad'', and the older -changeset is ``good''. - -If you're having trouble remembering when a suitable ``good'' change -was, so that you can tell \hgcmd{bisect}, you could do worse than -testing changesets at random. Just remember to eliminate contenders -that can't possibly exhibit the bug (perhaps because the feature with -the bug isn't present yet) and those where another problem masks the -bug (as I discussed above). - -Even if you end up ``early'' by thousands of changesets or months of -history, you will only add a handful of tests to the total number that -\hgcmd{bisect} must perform, thanks to its logarithmic behaviour. - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "00book" -%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/00book.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,81 @@ +% The use of oneside here is a temporary hack; \marginpar entries +% don't show up on odd pages of PDF output without it. Sigh. +\documentclass[oneside]{book} +\usepackage[spanish]{babel} +\usepackage{enumerate} +\usepackage{fullpage} +\usepackage{makeidx} +\usepackage{ifpdf} +\usepackage{graphicx} +\usepackage{pslatex} +\usepackage{fancyvrb} +\usepackage[utf8]{inputenc} %accents in spanish +% leave hyperref until last +\ifpdf +\usepackage[colorlinks=true,bookmarks=true,pdftitle={Distributed + revision control with Mercurial},pdfsubject={Revision + control},pdfkeywords={Mercurial, Revision control, Distributed + revision control},pdfauthor={Bryan O'Sullivan}]{hyperref} +\fi + +\include{99defs} + +\title{Control Distribuido de Revisiones con Mercurial} \author{Bryan + O'Sullivan} +\date{Copyright \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~\ref{cha:opl} para encontrar el + texto de la licencia.\\ + Este libro fue preparado a partir de + \href{http://mercurial.intuxication.org/hg/mercurial_book_es}{rev~\input{build_id}} + usando Mercurial \href{http://www.selenic.com/hg/}{rev~\input{hg_id}}.} + +\makeindex + +\begin{document} +\spanishdeactivate{<>"~} +\maketitle + +\addcontentsline{toc}{chapter}{Índice general} +\pagenumbering{roman} +\tableofcontents +\listoffigures +%\listoftables + +\pagenumbering{arabic} + +\include{preface} +\include{intro} +\include{tour-basic} +\include{tour-merge} +\include{concepts} +\include{daily} +\include{collab} +\include{filenames} +\include{branch} +\include{undo} +\include{hook} +\include{template} +\include{mq} +\include{mq-collab} +\include{hgext} + +\appendix +\include{cmdref} +\include{mq-ref} +\include{srcinstall} +\include{license} +\addcontentsline{toc}{chapter}{Bibliografía} +\bibliographystyle{alpha} +\bibliography{99book} + +\addcontentsline{toc}{chapter}{Índice alfabético} +\printindex + +\end{document} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: t +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/99book.bib Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,1 @@ +../en/99book.bib \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/99defs.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,146 @@ +% Bug ID. +\newcommand{\bug}[1]{\index{Base de datos de fallos de Mercurial + !\href{http://www.selenic.com/mercurial/bts/issue#1}{fallo + ~#1}}\href{http://www.selenic.com/mercurial/bts/issue#1}{Fallo de + Mercurial No.~#1}} + +% File name in the user's home directory. +\newcommand{\tildefile}[1]{\texttt{\~{}/#1}} + +% File name. +\newcommand{\filename}[1]{\texttt{#1}} + +% Directory name. +\newcommand{\dirname}[1]{\texttt{#1}} + +% File name, with index entry. +% The ``s'' prefix comes from ``special''. +\newcommand{\sfilename}[1]{\index{\texttt{#1}, fichero}\texttt{#1}} + +% Directory name, with index entry. +\newcommand{\sdirname}[1]{\index{\texttt{#1}, directorio}\texttt{#1}} + +% Mercurial extension. +\newcommand{\hgext}[1]{\index{\texttt{#1}, extensi\'on}\texttt{#1}} + +% Command provided by a Mercurial extension. +\newcommand{\hgxcmd}[2]{\index{\texttt{#2}, comando (extensi\'on +\texttt{#1})}\index{\texttt{#1}, extensi\'on!comando \texttt{#2}}``\texttt{hg #2}''} + +% Mercurial command. +\newcommand{\hgcmd}[1]{\index{\texttt{#1}, comando}``\texttt{hg #1}''} + +% Mercurial command, with arguments. +\newcommand{\hgcmdargs}[2]{\index{\texttt{#1}, comando}``\texttt{hg #1 #2}''} + +\newcommand{\tplkword}[1]{\index{\texttt{#1}, palabra clave de +plantilla}\index{palabras clave de plantilla!\texttt{#1}}\texttt{#1}} + +\newcommand{\tplkwfilt}[2]{\index{\texttt{#1}, palabra clave de plantilla!filtro +\texttt{#2}}\index{filtros de plantilla!\texttt{#2}}\index{\texttt{#2}, filtro +de plantilla}\texttt{#2}} + +\newcommand{\tplfilter}[1]{\index{filtros de +plantilla!\texttt{#1}}\index{\texttt{#1}, filtro de plantilla}\texttt{#1}} + +% Shell/system command. +\newcommand{\command}[1]{\index{\texttt{#1}, comando de sistema}\texttt{#1}} + +% Shell/system command, with arguments. +\newcommand{\cmdargs}[2]{\index{\texttt{#1} comando de sistema}``\texttt{#1 #2}''} + +% Mercurial command option. +\newcommand{\hgopt}[2]{\index{\texttt{#1}, comando!opción \texttt{#2}}\texttt{#2}} + +% Mercurial command option, provided by an extension command. +\newcommand{\hgxopt}[3]{\index{\texttt{#2}, comando (extensión +\texttt{#1})!opción \texttt{#3}}\index{\texttt{#1}, extensión!comando +\texttt{#2}!opción\texttt{#3}}\texttt{#3}} + +% Mercurial global option. +\newcommand{\hggopt}[1]{\index{opciones globales!opción \texttt{#1}}\texttt{#1}} + +% Shell/system command option. +\newcommand{\cmdopt}[2]{\index{\texttt{#1}, comando!opción \texttt{#2}}\texttt{#2}} + +% Command option. +\newcommand{\option}[1]{\texttt{#1}} + +% Software package. +\newcommand{\package}[1]{\index{\texttt{#1}, paquete}\texttt{#1}} + +% Section name from a hgrc file. +\newcommand{\rcsection}[1]{\index{\texttt{hgrc}, fichero!sección \texttt{#1}}\texttt{[#1]}} + +% Named item in a hgrc file section. +\newcommand{\rcitem}[2]{\index{\texttt{hgrc}, fichero!sección +\texttt{#1}!entrada \texttt{#2}}\texttt{#2}} + +% hgrc file. +\newcommand{\hgrc}{\index{fichero de configuración!\texttt{hgrc} + (Linux/Unix)}\index{\texttt{hgrc}, fichero de configuración}\texttt{hgrc}} + +% Mercurial.ini file. +\newcommand{\hgini}{\index{fichero de configuración!\texttt{Mercurial.ini} + (Windows)}\index{\texttt{Mercurial.ini}, fichero de configuración}\texttt{Mercurial.ini}} + +% Hook name. +\newcommand{\hook}[1]{\index{\texttt{#1}, gancho}\index{ganchos!\texttt{#1}}\texttt{#1}} + +% Environment variable. +\newcommand{\envar}[1]{\index{\texttt{#1}, variable de entorno}\index{variables +de entorno!\texttt{#1}}\texttt{#1}} + +% Python module. +\newcommand{\pymod}[1]{\index{\texttt{#1}, módulo}\texttt{#1}} + +% Python class in a module. +\newcommand{\pymodclass}[2]{\index{\texttt{#1}, módulo!clase \texttt{#2}}\texttt{#1.#2}} + +% Python function in a module. +\newcommand{\pymodfunc}[2]{\index{\texttt{#1}, módulo!función \texttt{#2}}\texttt{#1.#2}} + +% Note: blah blah. +\newsavebox{\notebox} +\newenvironment{note}% + {\begin{lrbox}{\notebox}\begin{minipage}{0.7\textwidth}\textbf{Nota:}\space}% + {\end{minipage}\end{lrbox}\fbox{\usebox{\notebox}}} +\newenvironment{caution}% + {\begin{lrbox}{\notebox}\begin{minipage}{0.7\textwidth}\textbf{Precaución:}\space}% + {\end{minipage}\end{lrbox}\fbox{\usebox{\notebox}}} + +% Code sample, eating 4 characters of leading space. +\DefineVerbatimEnvironment{codesample4}{Verbatim}{frame=single,gobble=4,numbers=left,commandchars=\\\{\}} + +% Code sample, eating 2 characters of leading space. +\DefineVerbatimEnvironment{codesample2}{Verbatim}{frame=single,gobble=2,numbers=left,commandchars=\\\{\}} + +% Interaction from the examples directory. +\newcommand{\interaction}[1]{\VerbatimInput[frame=single,numbers=left,commandchars=\\\{\}]{examples/#1.lxo}} +% Example code from the examples directory. +\newcommand{\excode}[1]{\VerbatimInput[frame=single,numbers=left,commandchars=\\\{\}]{../examples/#1}} + +% Graphics inclusion. +\ifpdf + \newcommand{\grafix}[2][]{\includegraphics[#1]{#2}} +\else + \newcommand{\grafix}[1]{\includegraphics{#1.png}} +\fi + +% Reference entry for a command. +\newcommand{\cmdref}[2]{\section{\hgcmd{#1}---#2}\label{cmdref:#1}\index{\texttt{#1}, comando}} + +% Reference entry for a command option with long and short forms. +\newcommand{\optref}[3]{\subsubsection{\hgopt{#1}{--#3}, también \hgopt{#1}{-#2}}} + +% Reference entry for a command option with only long form. +\newcommand{\loptref}[2]{\subsubsection{opción \hgopt{#1}{--#2}}} + +% command to generate a footnote to be used as a translator's note +\newcommand{\ndt}[1]{\footnote{\textbf{N. del T.} #1}} + + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/Leame.1st Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,508 @@ += Parámetros de Organización = + + * Se mantienen los nombres de los archivos + * Se traduce solamente el contenido + * Copie los archivos de en a es y tradúzcalos + * Las gráficas son tan importantes como los archivos + de texto, ya han sido traducidas + * Encoding UTF-8 para las tildes, eñes y demás + * Ancho de línea de 70 caracteres + += ¿Cómo contribuir? = +Obtenga la copia : +hg clone http://mercurial.intuxication.org/hg/mercurial_book_es/ + +Esto le ofrecerá un clon del repositorio en el directorio recién +creado '''mercurial_book_es''': + +mercurial_book_es +| +|-- en +|-- es +|-- examples +|-- html +`-- sillybench + +El directorio de trabajo es '''es'''. + + +Una vez que haya traducido o aplicado correcciones a los archivos de +su copia local, haga un commit + + hg commit -m "comentario descriptivo de lo que hizo" + +Siempre mantenga actualizado su repositorio local + hg pull + hg update + +Hay dos formas de hacer la contribución, primero envíe un correo a +igor@tamarapatino.org indicando lo que desea hacer, se le puede +otorgar permiso de escritura en el repositorio, o si lo prefiere, +puede enviar un parche (patch). Describimos a continuación los dos +procedimientos : Repositorio Público y Parches. Es preferible el +repositorio público frente a los parches, puesto que estos segundos +pueden tardar en propagarse más. + +== Repositorio Público == +Este sería el método preferido para que los cambios que usted haga +automáticamente queden en el repositorio y todos los traductores +podamos contar con la información rápidamente. + +Una vez que usted haya recibido la información necesaria, habiendo +elegido su usuario y su clave podrá "publicar" (push). + +Como este es un sistema distribuido, después de hacer la +consignación (commit), deberá publicarlo. + + hg push + +Se le solicitará su usuario y clave. + +== Parches == +Este método exige que alguien reciba el parche y haga manualmente la +aplicación del mismo, ese alguien es igor@tamarapatino.org por ahora, +después de haber hecho commit en su repositorio local, revise su log. + + hg log | head + +Esta última orden le permitirá establecer la última revisión que se +consignó en su repositorio local, su identificador de revisión tendrá +el formato número:hash. Generaría el archivo +/tmp/patchparahgbook.patch con la orden + + hg -o /tmp/patchparahgbook.patch REV + +donde REV es el identificador de revisión que debió haber encontrado. + += Traducción/Revisión = + +En esta sección indicamos quienes están traduciendo +y quienes revisando lo traducido. Coloque su nombre +para que los demás colaboradores sepan en dónde +enfocar sus esfuerzos. + +Indique qué archivos está traduciendo y/o revisando en +la lista siguiente. Cada archivo debe ser traducido y +revisado antes de poder considerarlo terminado. El revisor +no puede ser la misma persona que hizo la traducción. + +Cada traductor puede traducir o revisar el archivo que +desee, teniendo siempre en cuenta los archivos que ya tengan +alguien a cargo y escogiendo en la medida de lo posible +otros que no lo tengan. Los arreglos de 'typos' y problemas +de ortografía son siempre bienvenidos. + +== Archivos en proceso de traducción == +||'''archivo''' ||'''traductor'''||'''Estado'''||'''Inicio'''|| '''Fin''' || +|| 00book.tex || Igor Támara || 100% || 16/10/2008 || 16/10/2008 || +|| preface.tex || Javier Rojas || 100% || 18/10/2008 || 19/10/2008 || +|| intro.tex || Igor Támara || 100% || 08/11/2008 || 09/11/2008 || +|| tour-basic.tex || Javier Rojas || 100% || 19/10/2008 || 27/10/2008 || +|| tour-merge.tex || Javier Rojas || 100% || 28/10/2008 || 03/11/2008 || +|| concepts.tex || Javier Rojas || 100% || 03/11/2008 || 23/11/2008 || +|| daily.tex || Igor Támara || 100% || 19/10/2008 || 26/10/2008 || +|| collab.tex || Igor Támara || 100% || 10/11/2008 || 06/12/2008 || +|| filenames.tex || Javier Rojas || 100% || 27/11/2008 || 12/01/2008 || +|| branch.tex || Igor Támara || 100% || 16/10/2008 || 19/10/2008 || +|| undo.tex || Igor Támara || 100% || 26/10/2008 || 07/11/2008 || +|| hook.tex || Javier Rojas || 100% || 01/12/2008 || 04/01/2009 || +|| template.tex || Igor Támara || 100% || 27/12/2008 || 01/01/2009 || +|| mq.tex || Igor Támara || 100% || 06/12/2008 || 13/12/2008 || +|| mq-collab.tex || Javier Rojas || 100% || 04/01/2009 || 08/01/2009 || +|| hgext.tex || Igor Támara || 100% || 13/12/2008 || 16/12/2008 || +|| cmdref.tex || Igor Támara || 100% || 01/01/2009 || 01/01/2009 || +|| mq-ref.tex || Igor Támara || 100% || 06/01/2009 || 10/01/2009 || +|| srcinstall.tex || Igor Támara || 100% || 01/01/2009 || 01/01/2009 || +|| license.tex || Igor Támara || 100% || 16/12/2008 || 16/12/2008 || + +== Archivos en proceso de revisión == +||'''archivo''' || '''revisor''' ||'''Estado'''||'''Inicio'''|| '''Fin''' || +|| 00book.tex || Javier Rojas || 100% || 18/01/2009 || 18/01/2009 || +|| branch.tex || Javier Rojas || 100% || 25/01/2009 || 25/01/2009 || +|| preface.tex || || || || || +|| daily.tex || Javier Rojas || 100% || 25/01/2009 || 29/01/2009 || +|| tour-basic.tex || || || || || +|| undo.tex || Javier Rojas || || || || +|| tour-merge.tex || || || || || +|| concepts.tex || || || || || +|| intro.tex || Javier Rojas || 100% || 12/01/2009 || 12/01/2009 || +|| collab.tex || Javier Rojas || 0% || 29/01/2009 || || +|| mq.tex || || || || || +|| hgext.tex || || || || || +|| template.tex || || || || || +|| mq-collab.tex || || || || || +|| mq-ref.tex || || || || || +|| cmdref.tex || || || || || +|| license.tex || || || || || +|| srcinstall.tex || || || || || + +== Archivos terminados == +||'''archivo''' ||'''Inicio'''|| '''Fin''' || +|| 00book.tex || 16/10/2008 || 18/01/2009 || +|| intro.tex || 08/11/2008 || 12/01/2009 || +|| branch.tex || 16/10/2008 || 25/01/2009 || +|| daily.tex || 19/10/2008 || 29/01/2009 || + += Unificación de Términos de Traducción = +Por favor mantenga esta lista en orden alfabético + +La mayor parte del texto a continuación fue tomado del glosario de la +traducción del libro de subversion al idioma español. + +Pequeño glosario de términos traducidos. Aquí deben ponerse esos +"bonitos palabros" que tanto nos ha costado traducir para evitar +inconsistencias en la traducción por varias personas. Normalmente +son técnicos, pero puede incluirse cualquier "giro" o expresión que +consideremos útil mantener y repetir a lo largo de la traducción. +En el libro final posiblemente se añada una versión de este fichero +como apéndice. + +Para incluir algo, hay que especificar la expresión en inglés, su +versión traducida, y una pequeña explicación como justificación. + + Alice: Alicia + Anne: Ana + Back out: Retroceder + Binary test: Prueba binaria + Bob : Roberto + Branch: Rama + Bug: Fallo + Build Script: Guión de construcción + Builtin: integrada/o + Bundle: Agrupamiento + Bundled: Incluído o agrupado + Changelog: Bitácora de Cambios + Changeset: Conjunto de Cambios + Command: Orden + Commit: Consignar + Core: alma + Directory: Directorio + Escape Sequence: Secuencia de control + File: fichero + Filelog: fichero de registro + Fold: Integrar + Fork: Bifurcación + Hash: No se traduce + Head: Principal. En el contexto de revisiones HEAD se sugiere usar "frente" + Hook: Gancho + Merge: Fusión + Milestone: Etapa + Mistake: Equivocación, cometida por un humano + Output: salida o despliegue + Patch: Parche + Path: Ruta de archivo + Pointer: apuntador + Pop: Sustraer, la contraparte push, será publicar + Probe: Sondeo + Pull: Jalar + Push: Publicar. En el contexto de parches introducir. + Queue: Cola + Release: Versión o liberación de versión + Revlog: Bitácora de revisiones + Roll back: NO se traduce Ver más abajo + Snapshot: instantánea + Snippet: Recorte de código + Stack: pila + Stripped: + Sprint: sprint + Tarball: paquete de cambios + Timestamp : marca de tiempo + Tip: punta + Update: actualización + Upstream: principal, mantenedor principal. De acuerdo al contexto. + +abort -> cancelar + +ancestry -> ascendencia + La traducción literal concuerda con el significado que se le + da al mismo término en la jerga de Subversion. + +API, GUI -> no se traduce + La primera vez que aparecen, poner nota del traductor indicando + qué significan las siglas y su traducción al castellano. De + hecho, verificar la aparición de las notas de traductor en el + sitio correcto debería ser una entrada del fichero TODO... + +back-end -> ??? + Se refiere al término opuesto de front-end. En el octavo + capítulo se usa al menos tres veces para referirse al "motor" + que hay detrás de la capa de base de datos. Hmmm... pero motor + suena raro... + +backup -> copia de seguridad + Obtenido del glosario ORCA. + +Blanket Access Control -> Control de acceso simple + Por ahora no se me ocurre mejor traducción. Aquí blanket se + refiere a un control muy genérico, con poca granularidad. + +branching and merging -> crear ramas y fusionarlas + Aunque branch está bien traducido como rama o rama de desarrollo + (en su versión "verbose"), no hay forma de hacer de un sustantivo + un verbo. Por lo tanto, se crean, borran y fusionan ramas. + +browse -> navegar + Usado con frecuencia para navegar por directorios o repositorios. + +build -> comodín + No es que se traduzca como comodín, sino que en función del + contexto es una de esas palabras que significan muchas cosas y + no es posible traducirla literalmente. Por ejemplo, cuando se + usa como verbo se suele referir a compilar código fuente. En + cambio, cuando se usa como sustantivo se suele referir a una + versión particular del fichero binario ejecutable de un software, + resultado de una compilación previa. Cuidado con esto. Anotar + aquí traducciones realizadas para comparar. + + ...using and building Subversion... + ...usando y compilando Subversion... + +changeset -> ??? + Aparentemente conjunto de cambios. No puede ser traducido + como parche, el libro inglés indica que un "changeset" es un + parche con nombre único. Por ahora dejar en "changeset", ya se + buscará algo en el futuro. Para ver la descripción del libro, + buscar en ch04.xml la frase "Subversion y los changesets". + +cheap copy -> copia ligera + Otras posibilidades barajadas son copia barata o liviana. + Veremos si en el futuro éstas suenan mejor. + +click -> haga clic, pulse + Traducción obtenida del glosario. Parece ser que click sólo se + deja tal cual cuando se refiere a un sonido mecánico. Cuando + se refiere a pulsar el botón del ratón se traduce como clic. + +CVS -> No se traduce + +DAV share -> recurso DAV compartido + No tengo ni idea de lo que es un DAV share, así que cuando me + entere (o alguien lo haga), que cambie esta entrada del glosario. + +directory -> directorio + Entre carpeta y directorio se prefiere directorio. + +email -> correo electrónico + Entre las posibilidades de dejar la palabra tal cual, añadir un + guión (e-mail) y poner la traducción, se prefiere la traducción + completa. + +FAQ -> FAQ + Se deja tal cual pues se explica en el primer párrafo del prólogo + como una nota del traductor, y porque es muy frecuente ver su + uso en español. PyRF, PUFs o PFs son realmente desconcertantes. + +file -> fichero + Entre archivo y fichero se prefiere fichero. + +file path -> ruta del fichero + Cuando se ve path a secas, la forma más común de traducirlo + es decir ruta a secas igualmente. Bueno, el glosario de ORCA + también da como válidos camino y trayectoria. A ser posible + usar ruta si el contexto de la frase así lo favorece, en caso + contrario probar con camino o trayectoria. + +hash table -> tabla hash + Sugerido por Miguel Pérez Ibars. También encontrado en el + glosario ORCA. + +history -> Depende del contexto. Cuando se use en el contexto del repositorio + (the history of the repository), debe usarse el término historial. En otro + caso, historia. Valga anotar que ambas traducciones son aceptadas (al menos + en wordreference.com). + +hook, to hook -> gancho, enganchar + Usado en terminología de programación para indicar que el usuario + tiene un mecanismo estándar para modificar el comportamiento de + un dispositivo existente. Un ejemplo a nivel de programación + son las funciones "callback" que se pasan como parámetros, + o a nivel de sistema, scripts que existen en un directorio + concreto y que se ejecutan por el servidor de Subversion para + realizar tareas personalizadas por el administrador. + +ignore pattern -> ignorar patrones, pero patrones de exclusión + Subversion permite que ciertas opciones almacenen patrones + con los que se ignoran ficheros no versionados. Cuando la + frase original usa el verbo, se puede traducir como ignorar + directamente, pero cuando se usa a modo de sustantivo, + es mejor traducirlo como "patrón de exclusión" en lugar de + "patrón a ignorar". + +language -> lenguaje o idioma + Precisamente para diferenciar si nos estamos refiriendo a un + lenguaje de programación o a un lenguaje hablado por humanos, + se usará idioma en este último caso. + +language binding, SWIG binding, wrapper -> interfaz, enlace, ligadura, envoltorio... + Dependiendo del contexto, y desde un punto de vista personal, + se puede traducir esta palabra de muchas maneras. Una manera + genérica es decir "Interfaz con el lenguaje X", o "Ligadura con + el lenguaje X". Habitualmente, cuando se habla de una interfaz, + se está hablando de un binding ligero, o que únicamente permite + a hacer llamadas a funciones del lenguaje de programación A + desde el lenguaje de programación B. + + En cambio, se suele hablar de wrapper cuando el código entre + el lenguaje A y B es algo más complejo, o adapta el estilo de + programación del lenguaje A al del lenguaje B. Un ejemplo de esto + último sería una librería en C cuyo binding en Perl/Ruby/Python + fuese orientado a objetos. + + Aparte, wrapper también se usa cuando se habla de una función + o librería que engloba o asimila otra menor, con el propósito + de extender su funcionalidad o hacerla más fácil de usar + de cara al usuario, sin necesidad de cruzar ninguna barrera + "intra-lenguaje". + + Por lo tanto, hay que decidir con cuidado el contexto de la + palabra binding o wrapper, y escoger una. En el capítulo octavo + hay varios usos, aunque como son relativos a SWIG, se habla de + interfaces o envoltorios simplificados, puesto que se generan + de manera automática y no hay ninguna "conversión" en el estilo + de uso. + +lazy copy -> copia vaga + Horrible traducción literal. ¿Sugerencias? + copia perezosa + +location (repository) -> ubicación (del repositorio) + Cuidado, no traducir como localización, que es la acción y + efecto de localizar. + +log, log message -> informe de cambios, mensaje del informe de cambios + Traducción extraída del manual de CVS en español. La traducción + de "log message" es muy larga, pero únicamente porque no se + tiene contexto alguno. Con contexto suele ser posible omitir + la palabra informe si se considera apropiado. + +memory pool -> área de memoria + Traducción temporal hasta que se revise algún libro en castellano + de programación que use el mismo término. + +merge -> fusionar cambios, fusión + Obtenida referencia del manual de CVS en castellano, la otra + alternativa obvia para traducir merge es mezclar. No obstante, + mezclar tiene una connotación de azar al realizar la mezcla. Se + mezclan líquidos, ingredientes, etc. En cambio, un "merge" es + cualquier cosa menos un proceso realizado al azar (especialmente + si ha habido conflictos). En caso de traducir como sustantivo, + fusión vuelve a "sonar mejor" que mezcla, que por alguna razón me + suena a un combustible especial usado en vehículos de transporte. + +namespace -> espacio de nombrado, + Tecnicismo del C++, obtenido de la traducción del libro + "Pensar en C++", en concreto la sección 3.2 disponible en + http://arco.inf-cr.uclm.es/~dvilla/pensar_en_C++/ch02s03.html#id2589039. + +open source -> código fuente abierto + Referencia: http://es.tldp.org/ORCA/glosario.html#O + +plugins -> módulos + El término fue extraído del glosario de ORCA. + +repository -> repositorio + No hay mucha alternativa, ¿verdad? + +roll back -> No se traduce + El significado igual que en los ambientes + de sistemas manejadores de bases de datos se refiere a la atomicidad + e integridad al devolver un conjunto de acciones que permitan dejar + el repositorio en un estado consistente previo. + +repository layou t-> estructura del repositorio En referencia a cómo + están organizados los directorios. + +schedule -> programa o planifica + Parece más correcta la opción programa, en el sentido de pensar en + hacer algo. + schedule foo to be added -> programa la adición de foo + +switch -> cambiar + Únicamente cuando se habla del comando svn switch, no como la + palabra switch aislada, que significa parámetro o interruptor. + En el contexto de svn switch, traducirlo como cambiar. Quizás + traducir como reubicar una vez haya suficiente material traducido + y una lectura final sugiera una u otra traducción. + +tag, tagging -> etiqueta, etiquetar + Expresión ya común en español. + +three-way differencing program -> programa de diferenciación a tres bandas + Una diferenciación "normal" es generar las diferencias entre + dos ficheros. Una diferenciación a tres bandas es comparar las + diferencias entre tres ficheros, y se usa cuando se intentan + fusionar los cambios de una copia local junto con los cambios + recibidos del repositorio (por ejemplo, otra persona ha cambiado + el fichero sobre el que trabajábamos). + +timestamp, datestamp -> marca de tiempo, fecha y hora + Esa traducción viene del ORCA. No obstante en muchos casos es + más claro traducirla como "fecha de fichero" o "fecha" a secas. + Ejemplo: ...will show the modification timestamp. -> mostrará + la fecha de modificación. Decir "mostrará la marca de tiempo de + la modificación" o algo así es una traducción demasiado literal, + así que ojo con el contexto. + +track -> seguir, monitorear + este término suele ser usado cuando se habla de archivos, y de llevar la + pista o monitorear los cambios en un archivo. + +trunk -> tronco + Se refiere al nombre que recibe la línea de desarrollo + principal del repositorio que no está asociada a ninguna rama + en particular. Traducción obtenida del manual de CVS en español. + +Unix-like systems -> sistemas tipo unix + +URL -> No se traduce + +working copy -> copia (de trabajo) local/activa + Traducción similar a la de "commit data" (razonamiento + cliente-servidor). + += Términos a no-usar = +Evite el uso de estos términos en la traducción aún si son de uso +común para usted; el consenso general de la comunidad hispana puede +ser diferente del que usted tenga ;) + + * "archivo". Use "fichero" en su lugar. + * "carpeta". Use "directorio". + * "la historia". Use "el historial" (por supuesto, cuando se refiera al + historial del repositorio) + += Diferencias de redacción = +Hay varias expresiones que pueden terminar siendo traducidas de formas +diferentes por los traductores, y que aún siendo ambas correctas, ponen en +evidencia que el trabajo fue llevado a cabo por varias personas y que el estilo +que ellas usan difiere. Una vez terminada la traducción habrá que buscar cuáles +son éstos términos y decidirse por uno solo de ellos. A continuación se presenta +una lista de los términos o expresiones encontrados hasta ahora + + * comando - orden. Ambos son la traducción para "command". Parece que la + traducción más adecuada es "comando" + (http://www.wordreference.com/es/translation.asp?tranword=command) + * kernel - núcleo. + * Colas de Mercurial - colas de Mercurial. Creo que lo mejor es revisar qué + usó el autor (y rogar que él no haya sido inconsistente :P) + * armar - compilar - construir. Build, compile. Más que todo "build" + * daemonio - demonio. daemon + * kernel - núcleo. + * la URL - el URL + += Notas del traductor = +Por favor use el comando \ndt para insertar notas del traductor. Este +comando requiere un argumento. Por ejemplo: \ndt{Del inglés del original.} + += Para compilar = +He aquí algunas dependencias para Debian: + +apt-get install texlive-latex-extra tex4ht diffstat patchutils \ + inkscape graphviz texlive-pdfetex + += Traductores = +Por favor mantenga esta lista en orden alfabético de acuerdo al +apellido. + + * Javier Rojas <jerojasro@devnull.li> + * Igor Támara <igor@tamarapatino.org> + * Su nombre <su@e.mail>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/Makefile Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,223 @@ +# This makefile requires GNU make. + +sources := \ + 00book.tex \ + 99book.bib \ + 99defs.tex \ + build_id.tex \ + branch.tex \ + cmdref.tex \ + collab.tex \ + concepts.tex \ + daily.tex \ + filenames.tex \ + hg_id.tex \ + hgext.tex \ + hook.tex \ + intro.tex \ + mq.tex \ + mq-collab.tex \ + mq-ref.tex \ + preface.tex \ + srcinstall.tex \ + template.tex \ + tour-basic.tex \ + tour-merge.tex \ + undo.tex + +image-sources := \ + feature-branches.dot \ + filelog.svg \ + kdiff3.png \ + metadata.svg \ + mq-stack.svg \ + note.png \ + revlog.svg \ + snapshot.svg \ + tour-history.svg \ + tour-merge-conflict.svg \ + tour-merge-merge.svg \ + tour-merge-pull.svg \ + tour-merge-sep-repos.svg \ + undo-manual.dot \ + undo-manual-merge.dot \ + undo-non-tip.dot \ + undo-simple.dot \ + wdir.svg \ + wdir-after-commit.svg \ + wdir-branch.svg \ + wdir-merge.svg \ + wdir-pre-branch.svg + +image-dot := $(filter %.dot,$(image-sources)) +image-svg := $(filter %.svg,$(image-sources)) +image-png := $(filter %.png,$(image-sources)) + +image-pdf := $(image-dot:%.dot=%.pdf) $(image-svg:%.svg=%.pdf) $(image-png) +image-html := $(image-dot:%.dot=%.png) $(image-svg:%.svg=%.png) $(image-png) + +example-sources := \ + backout \ + bisect \ + branching \ + branch-named \ + branch-repo \ + cmdref \ + daily.copy \ + daily.files \ + daily.rename \ + daily.revert \ + extdiff \ + filenames \ + hook.msglen \ + hook.simple \ + hook.ws \ + issue29 \ + mq.guards \ + mq.qinit-help \ + mq.dodiff \ + mq.id \ + mq.tarball \ + mq.tools \ + mq.tutorial \ + rename.divergent \ + rollback \ + tag \ + template.simple \ + template.svnstyle \ + tour \ + tour-merge-conflict + +example-prereqs := \ + /usr/bin/merge + +dist-sources := \ + ../html/hgicon.png \ + ../html/index.html.var \ + ../html/index.en.html \ + ../html/index.es.html + +latex-options = \ + -interaction batchmode \ + -output-directory $(dir $(1)) \ + -jobname $(basename $(notdir $(1))) + +hg = $(shell which hg) + +hg-id = $(shell hg parents --template '{node|short}, fechado {date|isodate},\n') + +hg-version = $(shell hg version -q | \ + sed 's,.*(versión \(unknown\|[a-f0-9+]*\)),\1,') + +all: pdf html + +pdf: pdf/hgbook.pdf + +define pdf + mkdir -p $(dir $@) + TEXINPUTS=$(dir $<): pdflatex $(call latex-options,$@) $< || (rm -f $@; exit 1) + cp 99book.bib $(dir $@) + cd $(dir $@) && bibtex $(basename $(notdir $@)) + cd $(dir $@) && makeindex $(basename $(notdir $@)) + TEXINPUTS=$(dir $<): pdflatex $(call latex-options,$@) $< || (rm -f $@; exit 1) + TEXINPUTS=$(dir $<): pdflatex $(call latex-options,$@) $< || (rm -f $@; exit 1) + if grep 'Reference.*undefined' $(@:.pdf=.log); then exit 1; fi +endef + +pdf/hgbook.pdf: $(sources) examples $(image-pdf) + $(call pdf) + +html: onepage split + +onepage: $(htlatex) html/onepage/hgbook.html html/onepage/hgbook.css $(image-html:%=html/onepage/%) + +html/onepage/%: % + cp $< $@ + +split: $(htlatex) html/split/hgbook.html html/split/hgbook.css $(image-html:%=html/split/%) + +html/split/%: % + cp $< $@ + +# This is a horrible hack to work around the fact that the htlatex +# command in tex4ht is itself a horrible hack. I really don't want to +# include verbatim the big wad of TeX that is repeated in that script, +# but I've given up and run a hacked copy as htlatex.book here. + +define htlatex + mkdir -p $(dir $(1)) + cp 99book.bib $(dir $(1)) + TEXINPUTS=$(dir $(2)): ./htlatex.book $(2) "bookhtml,html4-uni,$(3)" " -cunihtf -utf8" "$(dir $(1))" "$(call latex-options,$(1))" || (rm -f $(1); exit 1) + cd $(dir $(1)) && tex4ht -f/$(basename $(notdir $(1))) -cvalidate -cunihtf + cd $(dir $(1)) && t4ht -f/$(basename $(notdir $(1))) + ./fixhtml.py $(dir $(1))/*.html + rm $(dir $(1))/hgbook.css +endef + +html/onepage/hgbook.html: $(sources) examples $(image-html) bookhtml.cfg + $(call htlatex,$@,$<) + +html/split/hgbook.html: $(sources) examples bookhtml.cfg + $(call htlatex,$@,$<,2) + +# Produce 90dpi PNGs for the web. + +%.png: %.svg fixsvg + ./fixsvg $< + inkscape -D -e $@ $<-tmp.svg + rm $<-tmp.svg + +%.svg: %.dot + dot -Tsvg -o $@ $< + +# Produce eps & pdf for the pdf + +%.pdf: %.eps + epstopdf $< + +%.eps: %.svg + ./fixsvg $< + inkscape -E $@ $<-tmp.svg + rm $<-tmp.svg + +%.eps: %.dot + dot -Tps -o $@ $< + +examples: $(example-prereqs) examples/.run + +examples/.run: $(example-sources:%=examples/%.run) + touch examples/.run + +examples/%.run: examples/% examples/run-example + cd examples && ./run-example $(notdir $<) + +changelog := $(wildcard ../.hg/store/00changelog.[id]) +ifeq ($(changelog),) +changelog := $(wildcard ../.hg/00changelog.[id]) +endif + +build_id.tex: $(changelog) + echo -n '$(hg-id)' > build_id.tex + +hg_id.tex: $(hg) + echo -n '$(hg-version)' > hg_id.tex + +clean: + rm -rf dist html pdf \ + $(image-dot:%.dot=%.pdf) \ + $(image-dot:%.dot=%.png) \ + $(image-svg:%.svg=%.pdf) \ + $(image-svg:%.svg=%.png) \ + examples/*.{lxo,run} examples/.run build_id.tex hg_id.tex + +install: pdf split $(dist-sources) + rm -rf dist + mkdir -p dist + cp pdf/hgbook.pdf dist + cp html/split/*.{css,html,png} dist + cp html/onepage/hgbook.html dist/onepage.html + ln -s index.es.html dist/index.html + cp $(dist-sources) dist + +rsync: install + rsync -avz --delete dist/ ikks@sulaco.devnull.li:public_html/hgbook/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/bookhtml.cfg Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,1 @@ +../en/bookhtml.cfg \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/branch.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,412 @@ +%% vim: tw=70 encoding=utf8 +\chapter{Administración de versiones y desarrollo ramificado} +\label{chap:branch} + +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. + +\section{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). +\interaction{tag.init} + +Mercurial le permite dar un nombre permanente a cualquier revisión +usando la orden \hgcmd{tag}. Sin causa de sorpresa, esos nombres se llaman +``tags'' (etiquetas). +\interaction{tag.tag} + +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: +\begin{itemize} +\item Dos puntos (ASCII 58, ``\texttt{:}'') +\item Retorno de carro (return) (ASCII 13, ``\Verb+\r+'') +\item Nueva línea (ASCII 10, ``\Verb+\n+'') +\end{itemize} + +Puede usar la orden \hgcmd{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. +\interaction{tag.tags} +Note que \texttt{tip} aparece en en listado generado por \hgcmd{tags}. La etiqueta +\texttt{tip} es una etiqueta ``flotante'' especial, que identifica siempre +la revisión más reciente en el repositorio. + +Al desplegar la orden \hgcmd{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 \texttt{tip} siempre aparecerá como primera +etiqueta listada al desplegar la orden \hgcmd{tags}. + +Cuando usted ejecuta \hgcmd{log}, si se muestra una revisión que tenga +etiquetas asociadas a ella, se imprimirán tales etiquetas. +\interaction{tag.log} + +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á. +\interaction{tag.log.v1.0} + +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 +(\textexclamdown 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 +\hgcmdargs{tag}{--remove}. +\interaction{tag.remove} +También puede modificar una etiqueta en cualquier momento, para que +identifique una revisión distinta, simplemente usando una nueva orden +\hgcmd{tag}. Deberá usar la opción \hgopt{tag}{-f} para indicarle a +Mercurial que \emph{realmente} desea actualizar la etiqueta. +\interaction{tag.replace} +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 \sfilename{.hgtags}. Cuando invoca la orden \hgcmd{tag}, +Mercurial modifica este fichero, y hace la consignación del cambio al +mismo automáticamente. Esto significa que cada vez que ejecuta +\hgcmd{tag}, verá un conjunto de cambios correspondiente en la salida +de \hgcmd{log}. +\interaction{tag.tip} + +\subsection{Manejo de conflictos entre etiquetas durante una fusión} + +Usualmente no tendrá que preocuparse por el fichero \sfilename{.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 \sfilename{.hgtags} +durante una fusión, hay un detalle para tener en cuenta al modificar +el fichero \sfilename{.hgtags}: +cuando Mercurial procesa las etiquetas en el repositorio, \emph{nunca} +lee la copia de trabajo del fichero \sfilename{.hgtags}. En cambio, +lee la versión \emph{consignada más reciente} del fichero. + +Una consecuencia desafortunada de este diseño es que usted no puede +verificar que su fichero \sfilename{.hgtags} fusionado sea correcto hasta +\emph{después} de haber consignado un cambio. Así que si se +encuentra resolviendo un conflicto en \sfilename{.hgtags} durante una +fusión, asegúrese de ejecutar la orden \hgcmd{tags} después de +consignar. Si encuentra un error en el fichero \sfilename{.hgtags}, +la orden reportará el lugar del error, que podrá arreglar y después +consignar. Posteriormente ejecute de nuevo la orden \hgcmd{tags} para +asegurarse de que su arreglo fue aplicado correctamente . + +\subsection{Etiquetas y clonado} + +Puede haber notado que la orden \hgcmd{clone} tiene la opción +\hgopt{clone}{-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 +\sfilename{.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 +\hgcmdargs{clone}{-r foo} para clonar un repositorio hasta la etiqueta +\texttt{foo}, el nuevo clon \emph{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, \emph{no} la etiqueta que podría haber esperado. + +\subsection{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 \texttt{4237e45506ee} es realmente \texttt{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 +\emph{locales}. Puede crear una etiqueta local con la opción~\hgopt{tag}{-l} +de la orden \hgcmd{tag}. Esto guardará la etiqueta en un fichero llamado +\sfilename{.hg/localtags}. A diferencia de \sfilename{.hgtags}, +\sfilename{.hg/localtags} no está controlado por revisiones. +Cualquier etiqueta que usted cree usando \hgopt{tag}{-l} se mantendrá +local al repositorio en el que esté trabajando en ese momento. + +\section{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 \emph{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. +\begin{itemize} +\item ``El gran cuadro'' Las ramas representan un barrido de la + evolución del proyecto; la gente les da nombres y hablan acerca de + ellas en sus conversaciones. +\item ``El cuadro pequeño'' Las ramas son artefactos de las + actividades diarias de desarrollar y fusionar cambios. Exponen la + narrativa de cómo se desarrolló el código. +\end{itemize} + +\section{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 +\texttt{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. +\interaction{branch-repo.tag} +Ahora puede clonar un repositorio compartido nuevo +\texttt{myproject-1.0.1} con tal etiqueta. +\interaction{branch-repo.clone} + +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 \texttt{myproject-1.0.1}, +harían sus cambios y los empujarían de vuelta. +\interaction{branch-repo.bugfix} +Mientras tanto, el desarrollo para la siguiente versión mayor puede +continuar aislado e incólume, en el repositorio \texttt{myproject}. +\interaction{branch-repo.new} + +\section{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. +\interaction{branch-repo.pull} +A continuación deberá mezclar las cabezas de las dos ramas, y empujar +de nuevo a la rama principal. +\interaction{branch-repo.merge} + +\section{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'' (\emph{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), \emph{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 \emph{nombre} persistente a una rama. Siempre +existe una rama llamada \texttt{default}. Incluso antes de que +empiece a nombrar ramas por su cuenta, puede encontrar indicios de la +rama \texttt{default} si los busca. + +Por ejemplo, cuando invoca la orden \hgcmd{commit}, y se lanza su +editor para introducir el mensaje de la consignación, busque la línea +que contiene el texto ``\texttt{HG: branch default}'' al final. Le +está indicando que su consignación ocurrirá en la rama llamada +\texttt{default}. + +Use la orden \hgcmd{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. +\interaction{branch-named.branches} +Dado que todavía no ha creado ramas nombradas, la única que verá será +\texttt{default}. + +Para hallar cuál es la rama ``actual'', invoque la orden +\hgcmd{branch}, sin argumento alguno. Le informará en qué rama se +encuentra el padre del conjunto de cambios actual. +\interaction{branch-named.branch} + +Para crear una nueva rama, invoque la orden \hgcmd{branch} de +nuevo. En esta oportunidad, ofrezca un argumento: el nombre de la rama +que desea crear. +\interaction{branch-named.create} + +Después de crear la rama, usted podría desear ver el efecto que tuvo +la orden \hgcmd{branch}. ¿Qué reportan las ordenes \hgcmd{status} y +\hgcmd{tip}? +\interaction{branch-named.status} +Nada cambia en el directorio actual, y no se ha añadido nada al +historial. Esto sugiere que al ejecutar la orden \hgcmd{branch} no hay +un efecto permanente; solamente le indica a que nombre de rama usará +la \emph{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 \texttt{default} +y haya consignado, verá que el nombre de la nueva rama se mostrará +cuando use la orden \hgcmd{log}, \hgcmd{tip}, y otras órdenes que +desplieguen la misma clase de información. +\interaction{branch-named.commit} +Las órdenes del tipo \hgcmd{log} imprimirán el nombre de la rama de +cualquier conjunto de cambios que no esté en la rama +\texttt{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 \hgcmd{branch}. +\interaction{branch-named.rebranch} +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.) + +\section{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 \hgcmd{update} o \hgcmdargs{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 \hgopt{update}{-C} de \hgcmd{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. +\interaction{branch-named.parents} +Estamos en la rama \texttt{bar}, pero existe otra rama más antigua +llamada \hgcmd{foo}. + +Podemos hacer \hgcmd{update} entre los tipos de las ramas \texttt{foo} +y \texttt{bar} sin necesidad de usar la opción \hgopt{update}{-C}, +puesto que esto solamente implica ir linealmente hacia adelante y +atrás en nuestro historial de cambios. +\interaction{branch-named.update-switchy} + +Si volvemos a la rama \texttt{foo} e invocamos la orden \hgcmd{update}, +nos mantendrá en \texttt{foo}, sin movernos a la punta de \texttt{bar}. +\interaction{branch-named.update-nothing} + +Al consignar un cambio a la rama \texttt{foo} se introducirá una nueva +cabeza. +\interaction{branch-named.foo-commit} + +\section{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 +\hgcmd{update} a 17 y aplico \hgcmd{merge} a 23, Mercurial almacena 17 +como el primer padre de la fusión, y 23 como el segundo. Mientras que +si hago \hgcmd{update} a 23 y después aplico \hgcmd{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 \texttt{foo}, y fusiona con +\texttt{bar}, el nombre de la rama continuará siendo \texttt{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 +\texttt{foo}, y usted también. Consignamos cambios distintos; yo jalo +sus cambios; Ahora tengo dos cabezas, cada una afirmando estar en la +rama \texttt{foo}. El resultado de una fusión será una única cabeza +en la rama \texttt{foo} como usted esperaría. + +Pero si estoy trabajando en la rama \texttt{bar}, y fusiono el trabajo +de la rama \texttt{foo}, el resultado permanecerá en la rama +\texttt{bar}. +\interaction{branch-named.merge} + +En un ejemplo más concreto, si yo estoy trabajando en la rama +\texttt{bleeding-edge}, y deseo traer los arreglos más recientes de la +rama \texttt{estable}, Mercurial elegirá el nombre de rama ``correcto'' +(\texttt{bleeding-edge}) cuando yo jale una fusión desde \texttt{estable}. + +\section{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 +\hook{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. +\begin{codesample2} + [hooks] + pretxnchangegroup.branch = hg heads --template '{branches} ' | grep mybranch +\end{codesample2} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/cmdref.py Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,1 @@ +../en/cmdref.py \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/cmdref.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,186 @@ +\chapter{Referencia de Órdenes} +\label{cmdref} + +\cmdref{add}{Añade ficheros en la próxima consignación} +\optref{add}{I}{include} +\optref{add}{X}{exclude} +\optref{add}{n}{dry-run} + +\cmdref{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~\ref{sec:mq:patch}. + +De forma predeterminada, esta orden no imprime las diferencias para +los ficheros binarios que Mercurial esté siguiendo. Para controlar +este comportamiento, vea las opciones \hgopt{diff}{-a} y +\hgopt{diff}{--git}. + +\subsection{Options} + +\loptref{diff}{nodates} + +Omite la fecha y hora cuando se muestran los encabezados de las +diferencias. + +\optref{diff}{B}{ignore-blank-lines} + +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. + +\optref{diff}{I}{include} + +Incluye ficheros y directorios cuyos nombres coinciden con los +patrones elegidos. + +\optref{diff}{X}{exclude} + +Excluye los ficheros y directorios cuyos nombres coinciden con los +patrones elegidos. + +\optref{diff}{a}{text} + +Si no especifica esta opción, \hgcmd{diff} no mostrará las diferencias +de los ficheros que detecte como binarios. Al especificar \hgopt{diff}{-a} +se forza a \hgcmd{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. + +\optref{diff}{b}{ignore-space-change} + +No imprime si el único cambio que en la línea es la cantidad de +espacio en blanco. + +\optref{diff}{g}{git} + +Mostrar diferencias compatibles con \command{git}. XXX reference a format +description. + +\optref{diff}{p}{show-function} + +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 \hgopt{diff}{-p} +no tiene efectos a menos que cambie el valor de +\rcitem{diff}{showfunc} en la configuración, como en el ejemplo +siguiente. +\interaction{cmdref.diff-p} + +\optref{diff}{r}{rev} + +Especifique una o más revisiones para comparar. La orden \hgcmd{diff} +acepta hasta dos opciones \hgopt{diff}{-r} para especificar las +revisiones a comparar. + +\begin{enumerate} +\setcounter{enumi}{0} +\item Despliega las diferencias entre la revisión padre y del directorio + de trabajo. +\item Despliega las diferencias entre el conjunto de cambios + especificados y el directorio de trabajo. +\item Despliega las diferencias entre dos conjuntos de cambios + especificados. +\end{enumerate} + +Puede especificar dos revisiones usando o bien sea las opciones +\hgopt{diff}{-r} o la notación de rango. Por ejemplo, las dos +especificaciones de revisiones a continuación son equivalentes: +\begin{codesample2} + hg diff -r 10 -r 20 + hg diff -r10:20 +\end{codesample2} + +Cuando especifica dos revisiones, esto tiene significado para +Mercurial. Esto significa que \hgcmdargs{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 +\hgcmdargs{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. + +\optref{diff}{w}{ignore-all-space} + +\cmdref{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: +\begin{itemize} +\item La cadena ``\texttt{unknown}''. Esta versión de Mercurial no fue + construida en un repositorio de Mercurial, y no puede determinar su + propia versión. +\item Una cadena numérica corta, tal como ``\texttt{1.1}''. Esta es + una construcción de una versión de Mercurial que se identifica con + una etiqueta específica en el repositorio en el cual fue + armada (Esto no significa necesariamente que está ejecutando una + versión oficial; alguien pudo haber añadido tal etiqueta a cualquier + versión del repositorio en el cual armaron Mercurial). +\item Una cadena hexadecimal, tal como ``\texttt{875489e31abe}''. + Esta es una construcción de una revisión dada de Mercurial. +\item Una cadena hexadecimal seguida por una fecha, tal como + ``\texttt{875489e31abe+20070205}''. Esta construcción de la + revisión de Mercurial fue la construcción de un repositorio que tuvo + cambios locales que no han sido consignados. +\end{itemize} + +\subsection{Consejos y trucos} + +\subsubsection{¿Por qué difieren los resultados de \hgcmd{diff} y + \hgcmd{status}?} +\label{cmdref:diff-vs-status} + +Cuando ejecuta la orden \hgcmd{status}, verá una lista de ficheros +para los cuales Mercurial almacenará cambios la próxima vez que +consigne. Si ejecuta la orden \hgcmd{diff}, verá que imprime +diferencias solamente para un \emph{subconjunto} de los ficheros que +\hgcmd{status} liste. Hay dos posibles razones para este comportamiento: + +La primera es que \hgcmd{status} imprime cierta clase de +modificaciones que \hgcmd{diff} no despliega normalmente. La orden +\hgcmd{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 \hgopt{diff}{--git} de \hgcmd{diff}, mostrará +diferencias compatibles con \command{git} que \emph{pueden} desplegar +esta información adicional. + +La segunda razón posible para que \hgcmd{diff} esté imprimiendo +diferencias para un subconjunto de ficheros de lo que muestra +\hgcmd{status} es que si usted le invoca sin argumento alguno, +\hgcmd{diff} imprime diferencias frente al primer padre del directorio +de trabajo. Si ha ejecutado \hgcmd{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 \hgcmd{parents} para +verlos). Mientras que \hgcmd{status} imprime modificaciones relativas +a \emph{ambos} padres después de una fusión que no se ha consignado, +\hgcmd{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 \hgopt{diff}{-r}. No hay forma +de hacer que imprima las diferencias relativas a los dos padres. + +\subsubsection{Generar diferencias seguras en binarios} + +Si usa la opción \hgopt{diff}{-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 \hgcmd{import} de Mercurial o a +la orden \command{patch} del sistema. + +Si desea generar una diferencia de un fichero binario que es seguro +para usarlo como entrada a la orden \hgcmd{import}, use la opción +\hgcmd{diff}{--git} cuando genere el parche. La orden \command{patch} +del sistema no puede tratar con parches binarios. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/collab.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,1059 @@ +\chapter{Colaborar con otros} +\label{cha:collab} + +Debido a su naturaleza descentralizada, Mercurial no impone política +alguna de cómo deben trabajar los grupos de personas. Sin embargo, si +usted es nuevo al control distribuido de versiones, es bueno tener +herramientas y ejemplos a la mano al pensar en posibles modelos de +flujo de trabajo. + +\section{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 de los +repositorios. Que 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 clonar repositorios a los usuarios +remotos, jalar cambios, y (cuando el servidor está configurado para +permitirlo) publicar cambios en el mismo. El protocolo de entunelamiento +de Mercurial comprime datos agresivamente, de forma que trabaja +eficientemente incluso con conexiones de red con poco ancho de banda. + +La forma más sencilla de iniciarse con la interfaz web es usar su +navegador para visitar un repositorio existente, como por ejemplo el +repositorio principal de Mercurial \url{http://www.selenic.com/repo/hg?style=gitweb}. + +Si está interesado en proveer una interfaz web a sus propios +repositorios, Mercurial provee dos formas de hacerlo. La primera es +usando la orden \hgcmd{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~\ref{sec:collab:serve} más adelante. Si +tiene un repositorio que desea hacer permanente, Mercurial tiene +soporte embebido del \command{ssh} para publicar cambios con seguridad +al repositorio central, como se documenta en la +sección~\ref{sec:collab:ssh}. Es muy usual que se publique una copia +de sólo lectura en el repositorio que está corriendo sobre HTTP usando +CGI, como en la sección~\ref{sec:collab:cgi}. Publicar sobre HTTP +satisface las necesidades de la gente que no tiene permisos de +publicación y de aquellos que quieren usar navegadores web para +visualizar el historial del repositorio. + +\subsection{Trabajo con muchas ramas} + +Los proyectos de cierta talla tienden naturalmente 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, pero no nuevas características. En +paralelo con las versiones de mantenimiento puede haber una o muchas +versiones futuras pueden estar en desarrollo. La gente usa normalmente +la palabra ``rama'' para referirse a una de las direcciones +ligeramente distintas en las cuales procede 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 puede +mezclar los cambios de una a otra de acuerdo con las necesidades. Dado +que los repositorios son independientes, uno del otro, los cambios +inestables de una rama de desarrollo nunca afectarán una rama estable +a menos que alguien explícitamente mezcle los cambios. + +A continuación un ejemplo de cómo podría hacerse esto en la +práctica. Digamos que tiene una ``rama principal'' en un servidor +central. +\interaction{branching.init} +Alguien lo clona, hace cambios locales, los prueba, y los publica allí +mismo. + +Una vez que la rama principal alcanza una estado de versión se puede +usar la orden \hgcmd{tag} para dar un nombre permanente a la revisión. +\interaction{branching.tag} +Digamos que en la rama principal ocurre más desarrollo. +\interaction{branching.main} +Cuando se usa la etiqueta con que se identificó la versión, la gente +puede clonar el repositorio en cualquier momento en el futuro +empleando \hgcmd{update} para obtener una copia del directorio de +trabajo exacta como cuando se creó la etiqueta de la revisión que se +consignó. +\interaction{branching.update} + +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. +\interaction{branching.clone} + +Alguien que requiera hacer un cambio en la rama estable puede clonar +\emph{ese} repositorio, hacer sus cambios, consignar y publicarlos +posteriormente al inicial. +\interaction{branching.stable} +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 \emph{aisladas} la una de la otra. +Los cambios que haga en la rama principal no ``se filtran'' a la rama +estable o vice versa. + +Es usual que los arreglos de fallos de la rama estable deban hacerse +aparecer en la rama principal también. En lugar de reescribir el +arreglo del fallo en la rama principal, puede jalar y mezclar los +cambios de la rama estable a la principal, Mercurial traerá tales +arreglos por usted. +\interaction{branching.merge} +La rama principal contendrá aún 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. + +\subsection{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 típicamente +están aislados de los desarrollos de otras ramas. + +\begin{figure}[ht] + \centering + \grafix{feature-branches} + \caption{Ramas de Características} + \label{fig:collab:feature-branches} +\end{figure} + +Cuando una rama particular alcanza un estado deseado, alguien del +equipo de características jala y fusiona de la rama principal hacia +la rama de características y publica posteriormente a la rama principal. + +\subsection{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 momento ``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. + +\subsection{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 desarrolladores usan \command{git}, una herramienta distribuida +de control de versiones con capacidades similares a Mercurial, resulta +de utilidad describir la forma en que el trabajo fluye en tal +ambiente; si le gustan las ideas, la aproximación se traduce bien +entre Git y 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 acerca de los árboles de los cuales jala. + +Linus tiene varios ``lugartenientes confiables''. 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 +tenga en cuenta su cambio. Si el mantenedor revisa los cambios y está +de acuerdo en tomarlos, estos pasarán al árbol de Linus de acuerdo a +lo expuesto. + +Cada lugarteniente tiene su forma particular de revisar, aceptar y +publicar los cambios; y para decidir cuando hacerlos presentes a +Linus. Adicionalmente existen varias ramas conocidas que mucha gente +usa para propósitos distintos. Por ejemplo, pocas personas mantienen +repositorios ``estables'' de versiones anteriores del kernel, a los +cuales aplican arreglos de fallos críticos necesarios. Algunos +mantenedores publican varios árboles: uno para cambios +experimentales; uno para cambios que van a ofrecer al mantenedor +principal; y así sucesivamente. Otros publican un solo árbol. + +Este modelo tiene dos características notables. La primera es que son +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 otra persona controla. + +El segundo está basado en reputación y meritocracia. Si usted es un +desconocido, Linus probablemente ignorará sus cambios, sin siquiera +responderle. Pero un 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 es transversal entre +``personas'' de diferentes subsistemas. Si usted es respetado pero es +un hacker 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 desean; y la fase de desarrollo es +alucinante. A pesar de eso Linux es una pieza de software exitosa y +bien reconocida. + +\subsection{Solamente jalar frente a colaboración pública} + +Una fuente perpetua de discusiones en la comunidad de código abierto +yace en 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 +aplicar una aproximación artificial (tal como aplicar parches a mano). + +Una buena herramienta distribuida de control de versiones, 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 depender de las peripecias que la +herramienta les obligue a hacer. + +\subsection{Cuando la colaboración encuentra la administración ramificada} + +Una vez que usted y su equipo configurar algunos repositorios +compartidos y comienzan a propagar cambios entre sus repositorios +locales y compartidos, 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, es lo suficientemente denso para ameritar un +tratamiento en el capítulo~\ref{chap:branch}. + +\section{Aspectos técnicos de la colaboración} + +Lo que resta del capítulo lo dedicamos a las cuestiones de servir +datos a sus colaboradores. + +\section{Compartir informalmente con \hgcmd{serve}} +\label{sec:collab:serve} + +La orden \hgcmd{serve} de Mercurial satisface de forma espectacular +las necesidades de un grupo pequeño, acoplado y de corto +tiempo. Se constituye en una demostración de cómo se siente usar los +comandos usando la red. + +Ejecute \hgcmd{serve} dentro de un repositorio, y en pocos segundos +iniciará un servidor HTTP especializado; aceptará conexiones desde +cualquier cliente y servirá datos de este repositorio mientrs lo +mantenga funcionando. Todo el que sepa el URL del servidor que ha +iniciado, y que puede comunicarse con su computador por la red, puede +usar un navegador web o Mercurial para leer datos del repositorio. Un +URL para una instancia de \hgcmd{serve} ejecutándose en un portátil +debería lucir algo \Verb|http://my-laptop.local:8000/|. + +La orden \hgcmd{serve} \emph{no} es un servidor web de propósito +general. Solamente puede hacer dos cosas: +\begin{itemize} +\item Permitir que se pueda visualizar el historial del repositorio que + está sirviendo desde navegadores web. +\item Hablar el protocolo de conexión de Mercurial para que puedan hacer + \hgcmd{clone} o \hgcmd{pull} (jalar) cambios de tal repositorio. +\end{itemize} +En particular, \hgcmd{serve} no permitirá que los usuarios remotos +puedan \emph{modificar} su repositorio. Es de tipo solo lectura. + +Si está comenzando con Mercurial, no hay nada que le impida usar +\hgcmd{serve} para servir un repositorio en su propio computador, y +usar posteriormente órdenes como \hgcmd{clone}, \hgcmd{incoming}, para +comunicarse con el servidor como si el repositorio estuviera alojado +remotamente. Lo que además puede ayudarle a adecuarse rápidamente para +usar comandos en repositorios alojados en la red. + +\subsection{Cuestiones adicionales para tener en cuenta} + +Dado que permite lectura sin autenticación a todos sus clientes, +debería usar \hgcmd{serve} exclusivamente en ambientes en los cuáles +no tenga problema en que otros vean, o en los cuales tenga control +completo acerca de quien puede acceder a su red y jalar cambios de su +repositorio. + +La orden \hgcmd{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 \hgcmd{serve}, lo siguiente que debería hacer +(\emph{después} de asegurarse que tienen el URL correcto) es verificar +su configuración de cortafuegos. + +De forma predeterminada, \hgcmd{serve} escucha conexiones entrantes en +el puerto~8000. Si otro proceso está escuchando en tal puerto, usted +podrá especificar un puerto distinto para escuchar con la opción +\hgopt{serve}{-p}. + +Normalmente, cuando se inicia \hgcmd{serve}, no imprime nada, lo cual +puede ser desconcertante. Si desea confirmar que en efecto está +ejecutándose correctamente, y darse cuenta qué URL debería enviar a +sus colaboradores, inícielo con la opción \hggopt{-v}. + +\section{Uso del protocolo Secure Shell (ssh)} +\label{sec:collab:ssh} + +Usted puede publicar y jalar cambios en la red de forma segura usando +el protocolo Secure Shell (\texttt{ssh}). Para usarlo satisfactoriamente, +tendrá que hacer algo de configuración a nivel de cliente o el +servidor. + +Si no está familiarizado con ssh, es un protocolo de red que le permite +comunicarse con seguridad con otro computador. Para usarlo con +Mercurial, estará estableciendo 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 \emph{es} familiar, encontrará probablemente elemental una +porción del material a continuación.) + +\subsection{Cómo leer y escribir URLs de ssh} + +Los URLs de ssh tienden a lucir de la siguiente forma: +\begin{codesample2} + ssh://bos@hg.serpentine.com:22/hg/hgbook +\end{codesample2} +\begin{enumerate} +\item La parte ``\texttt{ssh://}'' indica a Mercurial que use el + protocolo ssh. +\item El componente ``\texttt{bos@}'' indica el nombre del usuario que + está entrando al servidor. Puede omitirlo si el usuario remoto + coincide con el usuario local. +\item ``\texttt{hg.serpentine.com}'' es el nombre del servidor al cual + se desea entrar. +\item 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 \emph{no} está usando el + puerto~22. +\item La última porción del URL es la ruta local al repositorio en el + servidor. +\end{enumerate} + +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 relativo al +directorio personal del usuario remoto. Por ejemplo, si el usuario +\texttt{foo} en el servidor tiene el directorio casa +\dirname{/home/foo}, +entonces un URL ssh que contenga en su ruta a \dirname{bar} +\emph{realmente} se refiere al directorio \dirname{/home/foo/bar}. + +Si desea especificar una ruta relativa a otro directorio de usuario, +puede usar una ruta que comience con un caracter tildado, seguido del +nombre del usuario (llamémosle \texttt{otrousuario}, así +\begin{codesample2} + ssh://server/~otrousuario/hg/repo +\end{codesample2} + +Y si realmente desea especifica una ruta \emph{absoluta} en el +servidor, comience con el componente de la ruta con dos barras como +en el siguiente ejemplo: +\begin{codesample2} + ssh://server//absolute/path +\end{codesample2} + +\subsection{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 \Verb|which ssh| para +identificar dónde está instalada la orden \command{ssh} (usualmente +estará en \dirname{/usr/bin}). Si por casualidad no está presente, +vea la documentación de sus sistema para lograr instalarlo. + +En Windows, tendrá que escoger primero un cliente adecuado para +descargarlo. Hay dos alternativas: +\begin{itemize} +\item El excelente paquete PuTTY~\cite{web:putty} de Simon Tatham, que + ofrece un suite completo de órdenes de cliente ssh. +\item Si tiene alta tolerancia al dolor, puede usar el porte de Cygwin + para OpenSSH. +\end{itemize} +En cualquier caso, tendrá que editar su fichero \hgini\ para indicarle +a Mercurial dónde encontrar la orden real del cliente. Por ejemplo, si +está usando PuTTY, tendrá que usar la orden \command{plink} como un +cliente de línea de órdenes. +\begin{codesample2} + [ui] + ssh = C:/ruta/a/plink.exe -ssh -i "C:/ruta/a/mi/llave/privada" +\end{codesample2} + +\begin{note} + La ruta a \command{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 + \dirname{C:\\Program Files} +\end{note} + +\subsection{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 \command{ssh-keygen} también se +comportará bien. En Windows, si está usando PuTTY, la orden +\command{puttygen} es la que necesitará. + +Cuando genera un par de llaves, se aconseja \emph{comedidamente} +protegerlas con una frase de clave. (La única oportunidad en la cual +usted querría identificarse una única vez, 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 una llave +pública al conjunto de llaves autorizadas para todos los usuarios +remotos que se vayan a autenticar. Para aquellos servidores que usen +OpenSSH (la gran mayoría), significará añadir la llave pública a la +lista en el fichero llamado \sfilename{authorized\_keys} en su +directorio \sdirname{.ssh}. + +En sistemas tipo Unix, su llave pública tendrá la extensión +\filename{.pub}. Si usa \command{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 +\sfilename{authorized\_keys}. + +\subsection{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 y vuelve a entrar). Un cliente +ssh notará si está corriendo, y 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 sea muy alternante. +Es su decisión si es un riesgo aceptable. Lo que si es seguro es que +evita reteclear. + +En sistemas tipo Unix, el agente se llama \command{ssh-agent}, y +usualmente se ejecuta automáticamente cuando usted entra. Tendrá que +usar la orden \command{ssh-add} para añadir frases claves al agente. En +Windows, si está usando PuTTY, la orden \command{pageant} actúa como +el agente. Añade un icono a su barra del sistema que le permitirá +almacenar frases clave. + +\subsection{Configurar el lado del servidor apropiadamente} + +Dado que puede ser dispendioso configurar ssh si usted es nuevo, hay +una variedad de cosas que podrían ir mal. Añada piense primero en +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 continuará trabajando indefinidamente. + +Antes de intentar que Mercurial hable con un servidor ssh, es mejor +asegurarse que puede usar la orden normal \command{ssh} o \command{putty} +para comunicarse con el servidor primero. Si tiene problemas usando +estas órdenes directamente, de seguro Mercurial no funcionará. Pero aún, +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, \emph{antes} +de preocuparse por si existe un problema con Mercurial. + +Lo primero para asegurar en el lado del servidor es que puede entrar +desde otra máquina. Si no puede entrar con \command{ssh} o +\command{putty}, el mensaje de error que obtenga le puede dar pistas +de qué ha ido mal. Los problemas más comunes son los siguientes: +\begin{itemize} +\item Si obtiene un error de ``conexión rehusada'', es posible que no + haya un demonio SSH corriendo en el servidor o que no pueda accederse + a él por configuraciones de cortafuegos. +\item Si obtiene un error de ``no hay ruta hasta el servidor'', puede + tener la dirección del servidor incorrecta o un cortafuegos con + bloqueo agresivo que no permitirá su existencia. +\item Si obtiene un mensaje de ``permiso denegado'', puede que haya + tecleado mal el usuario en el servidor, o que haya tecleado + incorrectamente la frase clave o la clave del usuario remoto. +\end{itemize} +En resumen, si tiene problemas al comunicarse con el demonio ssh del +servidor, primero asegúrese de que está corriendo. En muchos sistemas +estará instalado, pero deshabilitado de forma predeterminada. Una vez +que haya hecho este paso tendrá que revisar si el cortafuegos del +servidor está configurado para recibir conexiones entrantes en el +puerto en el cual el demonio de ssh está escuchando (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 solicite frases claves o +contraseñas. Si se le pregunta alguna, a continuación algunas +posibilidades: +\begin{itemize} +\item Puede haber olvidado usar \command{ssh-add} o + \command{pageant} para guardar la frase clave. +\item Puede haber almacenado una frase clave errónea para la llave. +\end{itemize} +Si se le solicita la clave del usuario remoto, hay otras posibilidades +que deben revisarse: +\begin{itemize} +\item O bien el directorio del usuario o su directorio \sdirname{.ssh} + tiene permisos excesivamente abiertos. Como resultado el daemonio + ssh no creerá o leerá su fichero \sfilename{authorized\_keys}. + Por ejemplo, un directorio casa o \sdirname{.ssh} causará aveces + este síntoma. +\item El fichero de usuario \sfilename{authorized\_keys} puede tener + un problema. Si alguien distinto al usuario es dueño o puede + escribir el fichero, el demonio ssh no confiará o lo leerá. +\end{itemize} + +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. +\begin{codesample2} + ssh miservidor fecha +\end{codesample2} + +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 \command{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 +\Verb|tty -s|.) + +Cuando verifique que el venerado ssh funciona en su servidor, el +paso siguiente es asegurar que Mercurial corre en el servidor. La +orden siguiente debería ejecutarse satisfactoriamente: +\begin{codesample2} + ssh miservidor hg version +\end{codesample2} +Si ve un mensaje de error en lugar de la salida usual de +\hgcmd{version}, será porque no ha instalado Mercurial en +\dirname{/usr/bin}. No se preocupe si este es el caso; no necesita +hacerlo. Pero debería revisar los posibles problemas presentados a +continuación: +\begin{itemize} +\item Está instalado Mercurial en el servidor? Se que suena trivial + pero es mejor revisar! +\item Tal vez la ruta de búsqueda de la interfaz de órdenes + (normalmente vía la variable de ambiente \envar{PATH}) simplemente + está mal configurada. +\item Puede ser que su variable de ambiente \envar{PATH} soalamente + apunte al lugar en el cual está el ejecutable \command{hg} si la + sesión de entrada es interactiva. Puede suceder si establece la + ruta en el guión de línea de comandos de entrada incorrecto. Consulte la + documentación de su línea de órdenes. +\item La variable de ambiente \envar{PYTHONPATH} puede requerir la + ruta a los módulos de Mercurial en Python. Puede que ni siquiera + está establecida; podría estar incorrecta; o puede ser que se + establezca únicamente cuando hay entradas interactivas. +\end{itemize} + +Si puede ejecutar \hgcmd{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 +\hggopt{--debug} para tener información más clara de lo que está +sucediendo. + +\subsection{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 \emph{no} +solicitar 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 la compresión reduciendo la cantidad +de tiempo requerido para clonar un repositorio particularmente +grande de~51 minutos a~17 minutos. + +Tanto \command{ssh} como \command{plink} aceptan la opción +\cmdopt{ssh}{-C} que activa la compresión. Puede editar fácilmente +su \hgrc\ para habilitar la compresión para todos los usos de +Mercurial sobre el protocolo ssh. +\begin{codesample2} + [ui] + ssh = ssh -C +\end{codesample2} + +Si usa \command{ssh}, puede reconfigurarlo para que siempre use +compresión cuando se comunique con su servidor. Para hacerlo, +edite su fichero \sfilename{.ssh/config} (que puede no existir +aún), de la siguiente forma: +\begin{codesample2} + Host hg + Compression yes + HostName hg.ejemplo.com +\end{codesample2} +Que define un alias, \texttt{hg}. Cuando lo usa con la orden +\command{ssh} o con una URL de Mercurial con protocolo\texttt{ssh}, +logrará que \command{ssh} se conecte a \texttt{hg.ejemplo.com} +con compresión. Que le dará un nombre más corto para teclear y +compresión, los cuales por derecho propio son buenos. + +\section{Uso de CGI a través de HTTP} +\label{sec:collab: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 su configuración del servidor web. + +\begin{note} + 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 secciones siguientes. + Esté preparado para cometer muchas equivocaciones, y emplear + bastante tiempo leyendo sus bitácoras de error del servidor. +\end{note} + +\subsection{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: + +\begin{enumerate} +\item ¿Tiene un servidor web? Mac OS X viene con Apache, pero otros + sistemas pueden no tener un servidor web instalado. +\item 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. +\item ¿u servidor está configurado para permitir ejecutar programas + CGI en el directorio donde planea hacerlo? Casi todos los + servidores de forma predeterminada explícitamente inhiben la + habilidad de ejecutar programas CGI. +\end{enumerate} + +Si no tiene un servidor web instalado, y no tiene cierta experiencia +configurando Apache, debería considerar usar el servidor web +\texttt{lighttpd} en lugar de Apache. Apache tiene una reputación +bien ganada por su configuración barroca y confusa. +A pesar de que \texttt{lighttpd} tiene menos características que +Apache en ciertas áreas, las mismas no son relevantes para servir +repositorios de Mercurial. Definitivamente es mucho más sencillo +comenzar con \texttt{lighttpd} que con Apache. + +\subsection{Configuración básica de CGI} + +En sistemas tipo Unix es común que los usuarios tengan un subdirectorio +con un nombre como \dirname{public\_html} en su directorio personal, +desde el cual pueden servir páginas web. Un fichero llamado \filename{foo} +en este directorio será visible en una URL de la forma +\texttt{http://www.example.com/\~username/foo}. + +Para comenzar, encuentre el guión \sfilename{hgweb.cgi} que debería +estar presente en su instalación de Mercurial. Si no puede +encontrarlo rápidamente una copia local en su sistema, puede +descargarlo del repositorio principal de Mercurial en +\url{http://www.selenic.com/repo/hg/raw-file/tip/hgweb.cgi}. + +Tendrá que copiar este guión en su directorio \dirname{public\_html}, +y asegurarse que sea ejecutable. +\begin{codesample2} + cp .../hgweb.cgi ~/public_html + chmod 755 ~/public_html/hgweb.cgi +\end{codesample2} +El argumento \texttt{755} de la orden \command{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'' \emph{no} tengan +permiso de escritura. Si dejara los permisos de escritura abiertos, +, el subsistema \texttt{suexec} de Apache probablemente se negaría +a ejecutar el guión. De hecho, \texttt{suexec} también insiste en que +el \emph{directorio} en el cual reside el guión no tenga permiso de +escritura para otros. +\begin{codesample2} + chmod 755 ~/public_html +\end{codesample2} + +\subsubsection{¿Qué \emph{podría} resultar mal?} +\label{sec:collab:wtf} + +Cuando haya ubicado el CGI en el sitio correspondiente con un navegador +intente visitar el URL \url{http://myhostname/~myuser/hgweb.cgi}, +\emph{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 \texttt{UserDir}. Si no está presente en sitio alguno, los +directorios por usuario están deshabilitados. Si la hay, pero su +valor es \texttt{disabled}, los directorios por usuario estarán +deshabilitados. La directiva \texttt{UserDir} en caso contrario tendrá +el nombre del subdirectorio bajo el cual Apache mirará en el +directorio de cada usuario, por ejemplo \dirname{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 \dirname{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. +\begin{codesample2} + chmod 755 ~ + find ~/public_html -type d -print0 | xargs -0r chmod 755 + find ~/public_html -type f -print0 | xargs -0r chmod 644 +\end{codesample2} + +Otra posibilidad con los permisos es que obtenga una ventana +completamente en blanco cuando trata de cargar el guión. En cuyo +caso, es posible que los permisos que tiene son \emph{demasiado + permisivos}. El subsistema \texttt{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. + +\begin{codesample2} + <Directory /home/*/public_html> + AllowOverride FileInfo AuthConfig Limit + Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec + <Limit GET POST OPTIONS> + Order allow,deny + Allow from all + </Limit> + <LimitExcept GET POST OPTIONS> + Order deny,allow + Deny from all + </LimitExcept> + </Directory> +\end{codesample2} +Si encuentra un grupo de instrucciones de \texttt{Directory} similares +en su configuración de Apache, la directiva a revisar es \texttt{Options}. +Adicione \texttt{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: +\begin{codesample2} + AddHandler cgi-script .cgi +\end{codesample2} + +Otra posibilidad es que observe una traza de Python en colores +informando que no puede importar un módulo relacionado con +\texttt{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 ambiente de las +cuales usted si dispone en una sesión interactiva. Si este error le +ocurre, edite su copia de \sfilename{hgweb.cgi} y siga las indicaciones +dentro del mismo para establecer de forma adecuada su variable de +ambiente \envar{PYTHONPATH}. + +Finalmente, si encuentra \emph{otra} traza a todo color de Python al visitar +el URL: Esta seguramente se referirá a que no puede encontrar +\dirname{/path/to/repository}. Edite su script \sfilename{hgweb.cgi} +y reemplace la cadena \dirname{/path/to/repository} con la ruta +completa al repositorio que desea servir. + +En este punto, cuando trate de recargar la página, deberá visualizar +una linda vista HTML del historial de su repositorio. Uff! + +\subsubsection{Configuración de lighttpd} + +En mi intención de ser exhaustivo, intenté configurar +\texttt{lighttpd}, un servidor web con creciente aceptación, para +servir los repositorios de la misma forma como lo describí +anteriormente con Apache. Después de superar 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 \sfilename{hgweb.cgi} también lo era. + +Dado que ya Apache estaba en ejecución correctamente, lograr que +\texttt{lighttpd} sirviera mi repositorio fue rápido (en otras +palabras, si está tratando de usar \texttt{lighttpd}, debe leer la +sección de Apache). Primero tuve que editar la sección +\texttt{mod\_access} para habilitar \texttt{mod\_cgi} y +\texttt{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. +\begin{codesample2} + userdir.path = "public_html" + cgi.assign = ( ".cgi" => "" ) +\end{codesample2} +Hecho esto, \texttt{lighttpd} funcionó inmediatamente para +mí. Configuré \texttt{lighttpd} antes que Apache, tuve casi los mismos +reparos a nivel de configuración del sistema que con Apache. De todas +maneras, considero que \texttt{lighttpd} es bastante más sencillo de +configurar que Apache, a pesar de haber usado Apache por lo menos por +una década, y esta fue mi primera experiencia con \texttt{lighttpd}. + +\subsection{Compartir varios repositorios con un guión CGI} + +El guión \sfilename{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 +\sfilename{hgwebdir.cgi}. + +El procedimiento para configurar \sfilename{hgwebdir.cgi} tiene una +porción adicional frente al trabajo requerido con +\sfilename{hgweb.cgi}. Primero se debe obtener una copia del +guión. Si no tiene una a mano, puede descargar una copia del ftp +principal del repositorio de Mercurial en +\url{http://www.selenic.com/repo/hg/raw-file/tip/hgwebdir.cgi}. + +Necesitará una copia del guión en su directorio \dirname{public\_html}, +y asegurarse de que sea ejecutable. +\begin{codesample2} + cp .../hgwebdir.cgi ~/public_html + chmod 755 ~/public_html ~/public_html/hgwebdir.cgi +\end{codesample2} +Con la configuración básica, intente visitar en su navegador +\url{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~\ref{sec:collab:wtf}. + +El guión \sfilename{hgwebdir.cgi} se apoya en un fichero externo de +configuración. En principio, busca un fichero llamado +\sfilename{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 interpretar el módulo +\texttt{ConfigParser}~\cite{web:configparser} de Python. + +La forma más sencilla de configurar \sfilename{hgwebdir.cgi} es con +una sección llamada \texttt{collections}. Esta publicará automáticamente +\emph{todos} los repositorios en los directorios que usted +especifique. La sección debería lucir así: +\begin{codesample2} + [collections] + /mi/ruta = /mi/ruta +\end{codesample2} +Mercurial lo interpreta buscando el nombre del directorio que esté a la +\emph{derecha} del símbolo ``\texttt{=}''; encontrando repositorios en +la jerarquía de directorios; y usando el texto a la \emph{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 +\dirname{/mi/ruta/este/repo}, el guión CGI eliminará la porción inicial +\dirname{/mi/ruta} del nombre y publicará el repositorio con una ruta +virtual \dirname{este/repo}. Si el URL base de nuestro guión CGI es +\url{http://myhostname/~myuser/hgwebdir.cgi}, el URL completo al +repositorio será +\url{http://myhostname/~myuser/hgwebdir.cgi/this/repo}. + +Si reemplazamos \dirname{/mi/ruta} en el lado izquierdo de este +ejemplo con \dirname{/mi}, \sfilename{hgwebdir.cgi} eliminará solamente +\dirname{/mi} del nombre del repositorio, y nos ofrecerá la ruta +virtual \dirname{ruta/este/repo} en lugar de \dirname{este/repo}. + +El guión \sfilename{hgwebdir.cgi} buscará recursivamente en cada +directorio listado en la sección \texttt{collections} de su fichero de +configuración, pero \texttt{no} hará el recorrido recursivo dentro de +los repositorios que encuentre. + +El mecanismo de \texttt{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 \sfilename{hgwebdir.cgi} que +mirase. + +\subsubsection{Especificación explícita de los repositorios a publicar} + +Además del mecanismo \texttt{collections}, el guión +\sfilename{hgwebdir.cgi} le permite publicar una lista específica de +repositorios. Para hacerlo, cree una sección \texttt{paths}, con los +contenidos de la siguiente forma: +\begin{codesample2} + [paths] + repo1 = /mi/ruta/a/un/repo + repo2 = /ruta/a/otro/repo +\end{codesample2} +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 los dos mecanismos \texttt{collections} y +\texttt{paths} simultáneamente en un sólo fichero de configuración. + +\begin{note} + Si varios repositorios tienen la misma ruta virtual, + \sfilename{hgwebdir.cgi} no reportará error. Pero se comportará + impredeciblemente. +\end{note} + +\subsection{Descarga de ficheros fuente} + +La interfaz web de Mercurial permite a los usuarios descargar +un conjunto de cualquier revisión. Este fichero 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. + +De forma predeterminada esta característica no está habilitada. Para +lograrlo adicione un \rcitem{web}{allow\_archive} a la sección \rcsection{web} +de su fichero \hgrc. + +\subsection{Opciones de configuración en Web} + +Las interfaces web de Mercurial (la orden \hgcmd{serve}, y los guiones +\sfilename{hgweb.cgi} y \sfilename{hgwebdir.cgi}) tienen varias +opciones de configuración para establecer. Todas ellas en la sección +\rcsection{web}. +\begin{itemize} +\item[\rcitem{web}{allow\_archive}] Determina cuáles tipos de ficheros + de descarga soportará Mercurial. Si habilita esta característica, + los usuarios de la interfaz web podrán descargar una copia de la + revisión del repositorio que estén viendo. Para activar la + característica de descarga de fichero, el valor tendrá una secuencia + de palabras extraídas de la lista de abajo. + \begin{itemize} + \item[\texttt{bz2}] Un fichero \command{tar} con el método de + compresión \texttt{bzip2}. Tiene la mejor tasa de compresión, + pero usa más tiempo de procesamiento en el servidor. + \item[\texttt{gz}] Un fichero \command{tar}, comprimido con + \texttt{gzip}. + \item[\texttt{zip}] Un fichero \command{zip}, comprimido con LZW. + Este formato posee la peor tasa de compresión, pero es muy usado en + el mundo Windows. + \end{itemize} + Si da una lista vacía o no tiene la entrada + \rcitem{web}{allow\_archive}, esta característica se deshabilitará. + A continuación un ejemplo de cómo habilitar los tres formatos soportados. + \begin{codesample4} + [web] + allow_archive = bz2 gz zip + \end{codesample4} +\item[\rcitem{web}{allowpull}] Booleano. Determina si la interfaz web + permite a los usuarios remotos emplear \hgcmd{pull} y \hgcmd{clone} + sobre el repositorio~HTTP. Si se coloca \texttt{no} o + \texttt{false}, solamente la porción de los procesos + ``orientados-a-humanos'' se habilita de la interfaz web. +\item[\rcitem{web}{contact}] Cadena. Una cadena en forma libre (pero + preferiblemente corta) que identifica a la persona o grupo a cargo + del repositorio. Usualmente contiene el nombre y la dirección de + correo electrónico de una persona o de una lista de correo. Aveces + tiene sentido colocar esta opción en el fichero \sfilename{.hg/hgrc} + del repositorio, pero en otras oportunidades en el \hgrc\ global si + todos los repositorios tienen un único mantenedor. +\item[\rcitem{web}{maxchanges}] Entero. La cantidad máxima de + conjuntos de cambios a mostrar de forma predeterminada en cada página. +\item[\rcitem{web}{maxfiles}] Entero. La cantidad máxima + predeterminada de ficheros modificados a desplegar en una página. +\item[\rcitem{web}{stripes}] Entero. Si la interfaz web despliega + ``franjas'' para facilitar la visualización alineada de filas cuando + se ve una tabla, este valor controla la cantidad de filas en cada + franja. +\item[\rcitem{web}{style}] Controla la plantilla que Mercurial usa para + desplegar la interfaz web. Mercurial viene con dos plantillas web, + llamadas \texttt{default} y \texttt{gitweb} (La primera es + visualmente más atractiva). Puede especificar una plantilla propia; + consulte el capítulo~\ref{chap:template}. A continuación mostramos + cómo habilitar el estilo \texttt{gitweb}. + \begin{codesample4} + [web] + style = gitweb + \end{codesample4} +\item[\rcitem{web}{templates}] Ruta. Directorio en el que se buscarán + los ficheros plantilla. De forma predeterminada, busca en el + directorio en el cual fue instalado. +\end{itemize} +Si usa \sfilename{hgwebdir.cgi}, puede añadir otras opciones de +configuración en una sección \section{web} del fichero +\sfilename{hgweb.config} en lugar del fichero \hgrc\, si lo considera +más conveniente. Estas opciones son \rcitem{web}{motd} y +\rcitem{web}{style}. + +\subsubsection{Opciones específicas para repositorios individuales} + +Ciertas opciones de configuración de \rcsection{web} deben estar +ubicadas en el \sfilename{.hg/hgrc} de un repositorio en lugar del +fichero del usuario o el \hgrc global. +\begin{itemize} +\item[\rcitem{web}{description}] Cadena. Una cadena de forma + libre (preferiblemente corta) que describa los contenidos o el + propósito del repositorio. +\item[\rcitem{web}{name}] Cadena. El nombre para visualizar en la + interfaz web del repositorio. Sustituye el nombre predeterminado, el + cual es el último componente de la ruta del repositorio. +\end{itemize} + +\subsubsection{Opciones específicas a la orden \hgcmd{serve}} + +Algunas opciones en la sección \rcsection{web} de un fichero \hgrc\ +son de uso exclusivo para la orden \hgcmd{serve}. +\begin{itemize} +\item[\rcitem{web}{accesslog}] Ruta. El nombre del fichero en el cual + se escribe la bitácora de acceso. En principio, la orden + \hgcmd{serve} escribe esta información a la salida estándar, no a un + fichero. Las líneas de la bitácora se escriben en un formato de + fichero ``combinado'' estándar, usado por casi todos los servidores + web. +\item[\rcitem{web}{address}] Cadena. La dirección local en la cual el + servidor debe escuchar peticiones entrantes. En principio, el + servidor escucha en todas las direcciones. +\item[\rcitem{web}{errorlog}] Ruta. El nombre de un fichero en el + cual escribir la bitácora de error. En principio, la orden + \hgcmd{serve} escribe esta información en la salida de error + estándar, no a un fichero. +\item[\rcitem{web}{ipv6}] Booleano. Si se usa o no el protocolo + IPv6. En principio, IPv6 no se usa. +\item[\rcitem{web}{port}] Entero. El número del puerto~TCP en el cuál + el servidor escuchará. El puerto predeterminado es el~8000. +\end{itemize} + +\subsubsection{Elegir el fichero \hgrc\ correcto para las + configuraciones de \rcsection{web}} + +Es importante recordar que un servidor web como Apache o +\texttt{lighttpd} se ejecutarán bajo el usuario~ID que generalmente no +es el suyo Los guiones CGI ejecutados por su servidor, tales como +\sfilename{hgweb.cgi}, se ejecutarán también con el usuario~ID. + +Si añade opciones \rcsection{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 \hgcmd{serve} cuando +usted lo ejecuta. Para logar que los guiones CGI vean sus +configuraciones, o bien cree un fichero \hgrc\ en el directorio hogar +del usuario ID que ejecuta su servidor web, o añada tales +configuraciones al fichero global \hgrc. + + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/concepts.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,650 @@ +\chapter{Tras bambalinas} +\label{chap:concepts} + +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 \emph{seguro} como +\emph{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. + +\section{Registro del historial de Mercurial} + +\subsection{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 +\emph{filelog}\ndt{Fichero de registro}. 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 +\sdirname{.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 +``\texttt{.d}'') y para el índice (sufijo ``\texttt{.i}''). Para +ficheros pequeños con un historial pequeño, los datos de revisiones y +el índice son combinados en un único fichero ``\texttt{.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~\ref{fig:concepts:filelog}. + +\begin{figure}[ht] + \centering + \grafix{filelog} + \caption{Relación entre ficheros en el directorio de trabajo y + ficheros de registro en el repositorio} + \label{fig:concepts:filelog} +\end{figure} + +\subsection{Administración de ficheros monitoreados} + +Mercurial usa una estructura llamada \emph{manifiesto} para +% TODO collect together => centralizar +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. + +\subsection{Registro de información del conjunto de cambios} + +La \emph{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. + +\subsection{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 \emph{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~\ref{fig:concepts:metadata}. + +\begin{figure}[ht] + \centering + \grafix{metadata} + \caption{Relaciones entre metadatos} + \label{fig:concepts:metadata} +\end{figure} + +Como lo muestra la figura, \emph{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. + +\section{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 +\emph{revlog}\ndt{Contracción de \emph{revision log}, registro de +revisión.}. + +\subsection{Almacenamiento eficiente} + +El revlog provee almacenamiento eficiente de revisiones por medio del +mecanismo de \emph{deltas}\ndt{Diferencias.}. 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. + +\subsection{Operación segura} +\label{sec:concepts:txn} + +Mercurial sólo \emph{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 +\emph{transacción}, que puede cubrir varios ficheros. Una transacción es +\emph{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. +% TODO atomicidad no existe de acuerdo a DRAE, reemplazar +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. + +\subsection{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 +\emph{recuperación\ndt{\emph{Retrieval}. Recuperación en el sentido de traer los +datos, o reconstruirlos a partir de otros datos, pero no debido a una falla o +calamidad, sino a la operación normal del sistema.} 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. + +\begin{figure}[ht] + \centering + \grafix{snapshot} + \caption{Instantánea de un revlog, con deltas incrementales} + \label{fig:concepts:snapshot} +\end{figure} + +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 \emph{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~\ref{fig:concepts:snapshot} 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. + +\subsubsection{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. + +\subsection{Identificación e integridad fuerte} + +Además de la información de deltas e instantáneas, una entrada en un +% TODO de pronto aclarar qué diablos es un hash? +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. +% TODO no entendí completamente la frase a continuación +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. + +\section{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 +\emph{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~\ref{fig:concepts:revlog} 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. + +\begin{figure}[ht] + \centering + \grafix{revlog} + \caption{} + \label{fig:concepts:revlog} +\end{figure} + +\section{El directorio de trabajo} + +% TODO revisar párrafo, no me convence la traducción +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 \emph{estado de directorio}\ndt{dirstate, en inglés en el +original.} 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 \hgcmd{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 (\hgcmd{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 +\hgcmd{parents} le indica cuáles son los padres del estado de +directorio. + +\subsection{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 \emph{los padres de un nuevo conjunto de cambios} +cuando usted hace una consignación. + +\begin{figure}[ht] + \centering + \grafix{wdir} + \caption{El directorio de trabajo puede tener dos padres} + \label{fig:concepts:wdir} +\end{figure} + +La figura~\ref{fig:concepts:wdir} muestra el estado normal del +directorio de trabajo, que tiene un único conjunto de cambios como +padre. Dicho conjunto de cambios es la \emph{punta}, el conjunto de +cambios más reciente en el repositorio que no tiene hijos. + +\begin{figure}[ht] + \centering + \grafix{wdir-after-commit} + \caption{El directorio de trabajo obtiene nuevos padres luego de una + consignación} + \label{fig:concepts:wdir-after-commit} +\end{figure} + +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~\ref{fig:concepts:wdir-after-commit}. 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. + +\subsection{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~\ref{fig:concepts:wdir-pre-branch}. + +\begin{figure}[ht] + \centering + \grafix{wdir-pre-branch} + \caption{El directorio de trabajo, actualizado a un conjunto de + cambios anterior} + \label{fig:concepts:wdir-pre-branch} +\end{figure} + +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 \emph{frentes}. Usted puede apreciar la +estructura que esto crea en la figura~\ref{fig:concepts:wdir-branch}. + +\begin{figure}[ht] + \centering + \grafix{wdir-branch} + \caption{Después de una consignación hecha mientras se usaba un + conjunto de cambios anterior} + \label{fig:concepts:wdir-branch} +\end{figure} + +\begin{note} + Si usted es nuevo en Mercurial, debería tener en mente un + ``error'' común, que es usar el comando \hgcmd{pull} sin ninguna + opción. Por defecto, el comando \hgcmd{pull} \emph{no} 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 + (\hgcmd{merge}), y luego una consignación (\hgcmd{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. +\end{note} + +\subsection{Fusión de frentes} + +Cuando usted ejecuta el comando \hgcmd{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~\ref{fig:concepts:wdir-merge}. + +\begin{figure}[ht] + \centering + \grafix{wdir-merge} + \caption{Fusión de dos frentes} + \label{fig:concepts:wdir-merge} +\end{figure} + +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. +\begin{itemize} +\item Si ningún conjunto de cambios ha modificado un fichero, no se + hace nada con el mismo. +\item Si un conjunto de cambios ha modificado un fichero, y el otro no + lo ha hecho, se crea una copia del fichero con las modificaciones + pertinentes en el directorio de trabajo. +\item Si un conjunto de cambios borra un fichero, y el otro no lo ha + hecho (o también lo borró), se borra dicho fichero del directorio + de trabajo. +\item Si un conjunto de cambios ha borrado un fichero, pero el otro lo ha + modificado, se le pregunta al usuario qué hacer: conservar el + fichero modificado, o borrarlo? +\item Si ambos conjuntos de cambios han modificado un fichero, se + invoca el programa externo de fusión para definir el nuevo + contenido del fichero fusionado. Esto puede requerir interacción + directa de parte del usuario. +\item Si un conjunto de cambios ha modificado un fichero, y el otro ha + renombrado o copiado el mismo, asegurarse de que los cambios sigan + al nuevo nombre de fichero. +\end{itemize} +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 \hgcmd{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. + +\section{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á +% TODO the amount of thinking => (la cantidad de) esfuerzo mental +interesado, pueda obetener una idea mejor de la cantidad de esfuerzo +mental invertido en el diseño de un sistema bien diseñado. + + +\subsection{Compresión ingeniosa} + +Cuando es adecuado, Mercurial almacenará tanto las instantáneas como +los deltas en formato comprimido. Lo hace \emph{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 +\texttt{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 \texttt{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. + +\subsubsection{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 \texttt{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 \texttt{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 \command{ssh}, Mercurial \emph{no} +recomprmime el flujo, porque \command{ssh} puede hacer esto por sí +mismo.) + +\subsection{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~\ref{fig:concepts:metadata}, 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. + +%TODO revisar párrafo completo, no me gusta el resultado +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. + +\subsection{Acceso concurrente} + +El reordenado de lectura/escritura y la atomicidad garantizan que +Mercurial nunca necesita \emph{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 \emph{escritura} a su +repositorio para que ellos puedan clonarlo o jalar cambios; sólo +necesitan permisos de \emph{lectura}. (Esta \emph{no} es una +%TODO signo de admiración de apertura +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). + + +\subsubsection{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 \filename{dirstate}\ndt{Estado de +directorio.}. Así se garantiza que el fichero llamado +\filename{dirstate} esté completo, y no parcialmente escrito. + +\subsection{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. + +\subsection{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 (\hgcmd{add}), remueve (\hgcmd{remove}), renombra +(\hgcmd{rename}) o copia (\hgcmd{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. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/daily.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,404 @@ +\chapter{Mercurial día a día} +\label{chap:daily} + +\section{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 \hgcmd{status} le mostrará +cuáles ficheros son desconocidos para Mercurial; se emplea un +``\texttt{?}'' para mostrar tales ficheros. + +Para indicarle a Mercurial que tenga en cuenta un fichero, emplee la +orden \hgcmd{add}. Una vez que haya adicionado el fichero, la línea +referente al fichero al aplicar la orden \hgcmd{status} para tal +fichero cambia de ``\texttt{?}'' a ``\texttt{A}''. +\interaction{daily.files.add} + +Después de invocar \hgcmd{commit}, los ficheros que haya adicionado +antes de consignar no se listarán en la salida de \hgcmd{status}. La +razón para esto es que \hgcmd{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. + +\subsection{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''. +\interaction{daily.files.add-dir} +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 \filename{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 \emph{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. + +\subsection{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 \hgcmd{add} a +un fichero ``oculto'' dentro de ese directorio. En sistemas tipo +Unix, cualquier fichero cuyo nombre comience con un punto +(``\texttt{.}'') es tratado como oculto por la mayoría de +comandos y herramientas GUI. Esta aproximación se ilustra en la figura~\ref{ex:daily:hidden}. + +\begin{figure}[ht] + \interaction{daily.files.hidden} + \caption{Simular un directorio vacío con un fichero oculto} + \label{ex:daily:hidden} +\end{figure} + +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. + +\section{Cómo dejar de hacer seguimiento a un fichero} + +Si decide que un fichero no pertenece a su repositorio, use la orden +\hgcmd{remove}; se borrará el fichero y le indicará a Mercurial que +deje de hacerle seguimiento. Los ficheros eliminados se representan +con ``\texttt{R}'' al usar \hgcmd{status}. +\interaction{daily.files.remove} + +Después de hacer \hgcmd{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 \hgcmd{add}. Mercurial sabrá que el fichero recientemente +adicionado no está relacionado con el fichero anterior que tenía el +mismo nombre. + +\subsection{Al eliminar un fichero no se afecta su historial} + +Es preciso tener en cuenta que eliminar un fichero tiene sólo dos +efectos. +\begin{itemize} +\item Se elimina la versión actual del fichero del directorio de +trabajo. +\item Mercurial deja de hacer seguimiento a los cambios del fichero + desde la próxima consignación. +\end{itemize} +Al eliminar un fichero \emph{no} se altera de ninguna manera el +\emph{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. + +\subsection{Ficheros perdidos} + +Mercurial considera como \emph{perdido} un fichero que usted borró, +pero para el que no se usó \hgcmd{remove}. Los ficheros perdidos se +representan con ``\texttt{!}'' al visualizar \hgcmd{status}. +Las órdenes de Mercurial generalmente no harán nada con los ficheros +perdidos. +\interaction{daily.files.missing} + +Si su repositorio contiene un fichero que \hgcmd{status} reporta como +perdido, y desea que el mismo se vaya, se puede usar +\hgcmdargs{remove}{\hgopt{remove}{--after}} posteriormente para +indicarle a Mercurial que usted deseaba borrar tal fichero. +\interaction{daily.files.remove-after} + +Por otro lado, si borró un fichero perdido por accidente, puede usar +\hgcmdargs{revert}{\emph{nombre de fichero}} para recuperar el +fichero. Reaparecerá, sin modificaciones. +\interaction{daily.files.recover-missing} + +\subsection{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 \hgcmd{commit}, y dejaría de +hacer seguimiento al fichero. En la práctica, resultaba muy sencillo +borrar un fichero accidentalmente sin darse cuenta. + +\subsection{Atajo útil---agregar y eliminar ficheros en un solo paso} + +Mercurial ofrece una orden combinada, \hgcmd{addremove}, que agrega +los ficheros que no tienen seguimiento y marca los ficheros faltantes +como eliminados. +\interaction{daily.files.addremove} +La orden \hgcmd{commit} se puede usar con la opción \hgopt{commit}{-A} +que aplica el mismo agregar-eliminar, seguido inmediatamente de una +consignación. +\interaction{daily.files.commit-addremove} + +\section{Copiar ficheros} + +Mercurial ofrece la orden \hgcmd{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. + +\subsection{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 +\interaction{daily.copy.init} +Debemos hacer algo de trabajo en paralelo, de forma que tengamos algo para +fusionar. Aquí clonamos el repositorio. +\interaction{daily.copy.clone} +De vuelta en el repositorio inicial, usemos la orden \hgcmd{copy} para hacer +una copia del primer fichero que creamos. +\interaction{daily.copy.copy} + +Si vemos la salida de la orden \hgcmd{status}, el fichero copiado luce +tal como un fichero que se ha añadido normalmente. +\interaction{daily.copy.status} +Pero si usamos la opción \hgopt{status}{-C} de la orden +\hgcmd{status}, se imprimirá otra línea: el fichero \emph{desde} el +cual fue copiado nuestro fichero recién añadido. +\interaction{daily.copy.status-copy} + +Ahora, en el repositorio que clonamos, hagamos un cambio en +paralelo. Adicionaremos una línea de contenido al fichero original que +creamos. +\interaction{daily.copy.other} +Hemos modificado el fichero \filename{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 \filename{file} a su copia, \filename{new-file}. +\interaction{daily.copy.merge} + +\subsection{¿Por qué los cambios se reflejan en las copias?} +\label{sec:daily:why-copy} + +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 \emph{solamente} sucede +cuando fusionamos. Por lo tanto si sobre un fichero se usa +\hgcmd{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 \emph{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 +\hgcmd{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 \emph{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 \emph{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 \emph{ú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á. + + +\subsection{Cómo hacer que los cambios \emph{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 \command{cp}), y posteriormente use \hgcmd{add} sobre la nueva +copia hecha a mano. Antes de hacerlo, de todas maneras, relea la +sección~\ref{sec:daily:why-copy}, y tome una decisión asegurándose que +este comportamiento no es el apropiado para su caso específico. + +\subsection{Comportamiento de la orden \hgcmd{copy}} + +Cuando usa la orden \hgcmd{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 \hgcmd{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 \hgcmd{copy} actúa de forma parecida a la orden \command{cp} +de Unix (puede usar el alias \hgcmd{cp} si le es más cómodo). El +último argumento es el \emph{destino}, y todos los argumentos previos +son las \emph{fuentes}. Si solamente indica un fichero como la +fuente, y el destino no existe, se crea un fichero nuevo con ese nombre. +\interaction{daily.copy.simple} +Si el destino es un directorio, Mercurial copia las fuentes en éste. +\interaction{daily.copy.dir-dest} +La copia de un directorio es recursiva, y preserva la estructura del +directorio fuente. +\interaction{daily.copy.dir-src} +Si tanto la fuente como el destino son directorios, la estructura de +la fuente se recrea en el directorio destino. +\interaction{daily.copy.dir-src-dest} + +De la misma forma que la orden \hgcmd{rename}, si copia un fichero +manualmente y desea que Mercurial sepa que ha copiado un fichero, +basta con aplicar la opción \hgopt{copy}{--after} a la orden +\hgcmd{copy}. +\interaction{daily.copy.after} + +\section{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 \hgcmd{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 \hgcmd{rename}, Mercurial hace una copia de cada +fichero fuente, lo borra y lo marca como fichero eliminado. +\interaction{daily.rename.rename} +La orden \hgcmd{status} muestra la nueva copia del fichero como +añadida y el fichero inicial de la copia, como eliminado. +\interaction{daily.rename.status} +De la misma forma en que se usa la orden \hgcmd{copy}, debemos usar la +opción \hgopt{status}{-C} de la orden \hgcmd{status} para verificar +que el fichero añadido realmente comienza a ser seguido por Mercurial +como una copia del fichero original, ahora eliminado. +\interaction{daily.rename.status-copy} + +Igual que con \hgcmd{remove} y \hgcmd{copy}, puede indicársele a +Mercurial acerca de un renombramiento inmediato con la opción +\hgopt{rename}{--after}. El comportamiento de la orden +\hgcmd{rename} y las opciones que acepta, son similares a la orden +\hgcmd{copy} en casi todo. + +\subsection{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. + +\subsection{Cambios de nombre divergentes y fusión} + +El caso de renombramiento con nombres divergentes ocurre cuando dos +desarrolladores comienzan con un fichero---llamémoslo +\filename{foo}---en sus repositorios respectivos. + +\interaction{rename.divergent.clone} +%TODO we must either change the example's names, and these names, or +%use the original names. as of now, we keep the old names +Anne renombra el fichero a \filename{bar}. +\interaction{rename.divergent.rename.anne} +Mientras que Bob lo renombra como \filename{quux}. +\interaction{rename.divergent.rename.bob} + +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 \emph{ambos} +nombres cuando fusiona los conjuntos de cambios que contienen nombres +divergentes. +%TODO traducir texto interaccion +\interaction{rename.divergent.merge} + +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. + +\subsection{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 \emph{fuente} al mismo +\emph{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. + +\subsection{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~\bug{29}. +\interaction{issue29.go} + +\section{Recuperarse de equivocaciones} + +Mercurial tiene unas órdenes poderosas que le ayudarán a recuperarse +de equivocaciones comunes. + +La orden \hgcmd{revert} le permite deshacer cambios que haya hecho a +su directorio de trabajo. Por ejemplo, si aplicó \hgcmd{add} a un +fichero por accidente, ejecute \hgcmd{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 \hgcmd{revert} para deshacerse de cambios erróneos a un +fichero. + +Tenga en cuenta que la orden \hgcmd{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 \hgcmd{revert} y detalles +de cómo tratar con cambios consignados, vea el capítulo~\ref{chap:undo}. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/backout Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,83 @@ +#!/bin/bash + +# We have to fake the merges here, because they cause conflicts with +# three-way command-line merge, and kdiff3 may not be available. + +export HGMERGE=$(mktemp) +echo '#!/bin/sh' >> $HGMERGE +echo 'echo first change > "$1"' >> $HGMERGE +echo 'echo third change >> "$1"' >> $HGMERGE +chmod 700 $HGMERGE + +#$ name: init + +hg init myrepo +cd myrepo +echo first change >> myfile +hg add myfile +hg commit -m 'first change' +echo second change >> myfile +hg commit -m 'second change' + +#$ name: simple + +hg backout -m 'back out second change' tip +cat myfile + +#$ name: simple.log +#$ ignore: \s+200[78]-.* + +hg log --style compact + +#$ name: non-tip.clone + +cd .. +hg clone -r1 myrepo non-tip-repo +cd non-tip-repo + +#$ name: non-tip.backout + +echo third change >> myfile +hg commit -m 'third change' +hg backout --merge -m 'back out second change' 1 + +#$ name: non-tip.cat +cat myfile + +#$ name: manual.clone + +cd .. +hg clone -r1 myrepo newrepo +cd newrepo + +#$ name: manual.backout + +echo third change >> myfile +hg commit -m 'third change' +hg backout -m 'back out second change' 1 + +#$ name: manual.log + +hg log --style compact + +#$ name: manual.parents + +hg parents + +#$ name: manual.heads + +hg heads + +#$ name: manual.cat + +cat myfile + +#$ name: manual.merge + +hg merge +hg commit -m 'merged backout with previous tip' +cat myfile + +#$ name: + +rm $HGMERGE
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/bisect Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,96 @@ +#!/bin/bash + +if hg -v | head -1 | grep -e "version 0.*" +then +#On mercurial 1.0 and later bisect is a builtin +echo '[extensions]' >> $HGRC +echo 'hbisect =' >> $HGRC +fi + +# XXX There's some kind of horrible nondeterminism in the execution of +# bisect at the moment. Ugh. + +#$ ignore: .* + +#$ name: init + +hg init mybug +cd mybug + +#$ name: commits + +buggy_change=22 + +for (( i = 0; i < 35; i++ )); do + if [[ $i = $buggy_change ]]; then + echo 'i have a gub' > myfile$i + hg commit -q -A -m 'buggy changeset' + else + echo 'nothing to see here, move along' > myfile$i + hg commit -q -A -m 'normal changeset' + fi +done + +#$ name: help + +hg help bisect + +#$ name: search.init + +if hg -v | head -1 | grep -e "version 0.*" +then +#On mercurial 1.0 --init disappeared +hg bisect --init +fi + +#$ name: search.bad-init + +hg bisect --bad + +#$ name: search.good-init + +hg bisect --good 10 + +#$ name: search.step1 + +if grep -q 'i have a gub' * +then + result=bad +else + result=good +fi + +echo this revision is $result +hg bisect --$result + +#$ name: search.mytest + +mytest() { + if grep -q 'i have a gub' * + then + result=bad + else + result=good + fi + + echo this revision is $result + hg bisect --$result +} + +#$ name: search.step2 + +mytest + +#$ name: search.rest + +mytest +mytest +mytest + +#$ name: search.reset + +hg bisect --reset + +#$ name: + +exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/branch-named Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,73 @@ +#!/bin/bash + +hg init a +cd a +echo hello > myfile +hg commit -A -m 'Initial commit' + +#$ name: branches + +hg tip +hg branches + +#$ name: branch + +hg branch + +#$ name: create + +hg branch foo +hg branch + +#$ name: status + +hg status +hg tip + +#$ name: commit + +echo 'hello again' >> myfile +hg commit -m 'Second commit' +hg tip + +#$ name: rebranch + +hg branch +hg branch bar +echo new file > newfile +hg commit -A -m 'Third commit' +hg tip + +#$ name: parents + +hg parents +hg branches + +#$ name: update-switchy + +hg update foo +hg parents +hg update bar +hg parents + +#$ name: update-nothing + +hg update foo +hg update + +#$ name: foo-commit + +echo something > somefile +hg commit -A -m 'New file' +hg heads + +#$ name: update-bar + +hg update bar + +#$ name: merge + +hg branch +hg merge foo +hg commit -m 'Merge' +hg tip
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/branch-repo Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,48 @@ +#!/bin/bash + +hg init myproject +cd myproject +echo hello > myfile +hg commit -A -m 'Initial commit' +cd .. + +#$ name: tag + +cd myproject +hg tag v1.0 + +#$ name: clone + +cd .. +hg clone myproject myproject-1.0.1 + +#$ name: bugfix + +hg clone myproject-1.0.1 my-1.0.1-bugfix +cd my-1.0.1-bugfix +echo 'I fixed a bug using only echo!' >> myfile +hg commit -m 'Important fix for 1.0.1' +#$ ignore: /tmp/branch-repo.* +hg push + +#$ name: new + +cd .. +hg clone myproject my-feature +cd my-feature +echo 'This sure is an exciting new feature!' > mynewfile +hg commit -A -m 'New feature' +hg push + +#$ name: pull + +cd .. +hg clone myproject myproject-merge +cd myproject-merge +hg pull ../myproject-1.0.1 + +#$ name: merge + +hg merge +hg commit -m 'Merge bugfix from 1.0.1 branch' +hg push
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/branching Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,63 @@ +#!/bin/bash + +#$ name: init + +hg init main +cd main +echo 'This is a boring feature.' > myfile +hg commit -A -m 'We have reached an important milestone!' + +#$ name: tag + +hg tag v1.0 +hg tip +hg tags + +#$ name: main + +cd ../main +echo 'This is exciting and new!' >> myfile +hg commit -m 'Add a new feature' +cat myfile + +#$ name: update + +cd .. +hg clone -U main main-old +cd main-old +hg update v1.0 +cat myfile + +#$ name: clone + +cd .. +hg clone -rv1.0 main stable + +#$ name: stable + +hg clone stable stable-fix +cd stable-fix +echo 'This is a fix to a boring feature.' > myfile +hg commit -m 'Fix a bug' +#$ ignore: /tmp/branching.* +hg push + +#$ name: + +export HGMERGE=$(mktemp) +echo '#!/bin/sh' > $HGMERGE +echo 'echo "This is a fix to a boring feature." > "$1"' >> $HGMERGE +echo 'echo "This is exciting and new!" >> "$1"' >> $HGMERGE +chmod 700 $HGMERGE + +#$ name: merge + +cd ../main +hg pull ../stable +hg merge +hg commit -m 'Bring in bugfix from stable branch' +cat myfile + +#$ name: + +rm $HGMERGE
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/cmdref Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,22 @@ +#!/bin/bash + +hg init diff +cd diff +cat > myfile.c <<EOF +int myfunc() +{ + return 1; +} +EOF +hg ci -Ama + +sed -ie 's/return 1/return 10/' myfile.c + +#$ name: diff-p + +echo '[diff]' >> $HGRC +echo 'showfunc = False' >> $HGRC + +hg diff + +hg diff -p
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/daily.copy Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,82 @@ +#!/bin/bash + +#$ name: init + +hg init my-copy +cd my-copy +echo line > file +hg add file +hg commit -m 'Added a file' + +#$ name: clone + +cd .. +hg clone my-copy your-copy + +#$ name: copy + +cd my-copy +hg copy file new-file + +#$ name: status + +hg status + +#$ name: status-copy + +hg status -C +hg commit -m 'Copied file' + +#$ name: other + +cd ../your-copy +echo 'new contents' >> file +hg commit -m 'Changed file' + +#$ name: cat + +cat file +cat ../my-copy/new-file + +#$ name: merge + +hg pull ../my-copy +hg merge +cat new-file + +#$ name: + +cd .. +hg init copy-example +cd copy-example +echo a > a +echo b > b +mkdir c +mkdir c/a +echo c > c/a/c +hg ci -Ama + +#$ name: simple + +mkdir k +hg copy a k +ls k + +#$ name: dir-dest + +mkdir d +hg copy a b d +ls d + +#$ name: dir-src + +hg copy c e + +#$ name: dir-src-dest + +hg copy c d + +#$ name: after + +cp a z +hg copy --after a z
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/daily.files Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,93 @@ +#!/bin/bash + +#$ name: add + +hg init add-example +cd add-example +echo a > a +hg status +hg add a +hg status +hg commit -m 'Added one file' +hg status + +#$ name: add-dir + +mkdir b +echo b > b/b +echo c > b/c +mkdir b/d +echo d > b/d/d +hg add b +hg commit -m 'Added all files in subdirectory' + +#$ name: + +cd .. + +#$ name: hidden + +hg init hidden-example +cd hidden-example +mkdir empty +touch empty/.hidden +hg add empty/.hidden +hg commit -m 'Manage an empty-looking directory' +ls empty +cd .. +hg clone hidden-example tmp +ls tmp +ls tmp/empty + +#$ name: remove + +hg init remove-example +cd remove-example +echo a > a +mkdir b +echo b > b/b +hg add a b +hg commit -m 'Small example for file removal' +hg remove a +hg status +hg remove b + +#$ name: + +cd .. + +#$ name: missing +hg init missing-example +cd missing-example +echo a > a +hg add a +hg commit -m 'File about to be missing' +rm a +hg status + +#$ name: remove-after + +hg remove --after a +hg status + +#$ name: recover-missing +hg revert a +cat a +hg status + +#$ name: + +cd .. + +#$ name: addremove + +hg init addremove-example +cd addremove-example +echo a > a +echo b > b +hg addremove + +#$ name: commit-addremove + +echo c > c +hg commit -A -m 'Commit with addremove'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/daily.rename Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,18 @@ +#!/bin/bash + +hg init a +cd a +echo a > a +hg ci -Ama + +#$ name: rename + +hg rename a b + +#$ name: status + +hg status + +#$ name: status-copy + +hg status -C
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/daily.revert Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,74 @@ +#!/bin/bash + +hg init a +cd a +echo 'original content' > file +hg ci -Ama + +#$ name: modify + +cat file +echo unwanted change >> file +hg diff file + +#$ name: unmodify + +hg status +hg revert file +cat file + +#$ name: status + +hg status +cat file.orig + +#$ name: + +rm file.orig + +#$ name: add + +echo oops > oops +hg add oops +hg status oops +hg revert oops +hg status + +#$ name: + +rm oops + +#$ name: remove + +hg remove file +hg status +hg revert file +hg status +ls file + +#$ name: missing + +rm file +hg status +hg revert file +ls file + +#$ name: copy + +hg copy file new-file +hg revert new-file +hg status + +#$ name: + +rm new-file + +#$ name: rename + +hg rename file new-file +hg revert new-file +hg status + +#$ name: rename-orig +hg revert file +hg status
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/data/check_whitespace.py Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,44 @@ +#!/usr/bin/python + +import re + +def trailing_whitespace(difflines): + added, linenum, header = [], 0, False + + for line in difflines: + if header: + # remember the name of the file that this diff affects + m = re.match(r'(?:---|\+\+\+) ([^\t]+)', line) + if m and m.group(1) != '/dev/null': + filename = m.group(1).split('/', 1)[-1] + if line.startswith('+++ '): + header = False + continue + if line.startswith('diff '): + header = True + continue + # hunk header - save the line number + m = re.match(r'@@ -\d+,\d+ \+(\d+),', line) + if m: + linenum = int(m.group(1)) + continue + # hunk body - check for an added line with trailing whitespace + m = re.match(r'\+.*\s$', line) + if m: + added.append((filename, linenum)) + if line and line[0] in ' +': + linenum += 1 + return added + +if __name__ == '__main__': + import os, sys + + added = trailing_whitespace(os.popen('hg export tip')) + if added: + for filename, linenum in added: + print >> sys.stderr, ('%s, line %d: trailing whitespace added' % + (filename, linenum)) + # save the commit message so we don't need to retype it + os.system('hg tip --template "{desc}" > .hg/commit.save') + print >> sys.stderr, 'commit message saved to .hg/commit.save' + sys.exit(1)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/data/remove-redundant-null-checks.patch Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,190 @@ + +From: Jesper Juhl <jesper.juhl@gmail.com> + +Remove redundant NULL chck before kfree + tiny CodingStyle cleanup for +drivers/ + +Signed-off-by: Jesper Juhl <jesper.juhl@gmail.com> +Signed-off-by: Andrew Morton <akpm@osdl.org> +--- + + drivers/char/agp/sgi-agp.c | 5 ++--- + drivers/char/hvcs.c | 11 +++++------ + drivers/message/fusion/mptfc.c | 6 ++---- + drivers/message/fusion/mptsas.c | 3 +-- + drivers/net/fs_enet/fs_enet-mii.c | 3 +-- + drivers/net/wireless/ipw2200.c | 22 ++++++---------------- + drivers/scsi/libata-scsi.c | 4 +--- + drivers/video/au1100fb.c | 3 +-- + 8 files changed, 19 insertions(+), 38 deletions(-) + +diff -puN drivers/char/agp/sgi-agp.c~remove-redundant-null-checks-before-free-in-drivers drivers/char/agp/sgi-agp.c +--- a/drivers/char/agp/sgi-agp.c~remove-redundant-null-checks-before-free-in-drivers ++++ a/drivers/char/agp/sgi-agp.c +@@ -329,9 +329,8 @@ static int __devinit agp_sgi_init(void) + + static void __devexit agp_sgi_cleanup(void) + { +- if (sgi_tioca_agp_bridges) +- kfree(sgi_tioca_agp_bridges); +- sgi_tioca_agp_bridges=NULL; ++ kfree(sgi_tioca_agp_bridges); ++ sgi_tioca_agp_bridges = NULL; + } + + module_init(agp_sgi_init); +diff -puN drivers/char/hvcs.c~remove-redundant-null-checks-before-free-in-drivers drivers/char/hvcs.c +--- a/drivers/char/hvcs.c~remove-redundant-null-checks-before-free-in-drivers ++++ a/drivers/char/hvcs.c +@@ -1320,11 +1320,12 @@ static struct tty_operations hvcs_ops = + static int hvcs_alloc_index_list(int n) + { + int i; ++ + hvcs_index_list = kmalloc(n * sizeof(hvcs_index_count),GFP_KERNEL); + if (!hvcs_index_list) + return -ENOMEM; + hvcs_index_count = n; +- for(i = 0; i < hvcs_index_count; i++) ++ for (i = 0; i < hvcs_index_count; i++) + hvcs_index_list[i] = -1; + return 0; + } +@@ -1332,11 +1333,9 @@ static int hvcs_alloc_index_list(int n) + static void hvcs_free_index_list(void) + { + /* Paranoia check to be thorough. */ +- if (hvcs_index_list) { +- kfree(hvcs_index_list); +- hvcs_index_list = NULL; +- hvcs_index_count = 0; +- } ++ kfree(hvcs_index_list); ++ hvcs_index_list = NULL; ++ hvcs_index_count = 0; + } + + static int __init hvcs_module_init(void) +diff -puN drivers/message/fusion/mptfc.c~remove-redundant-null-checks-before-free-in-drivers drivers/message/fusion/mptfc.c +--- a/drivers/message/fusion/mptfc.c~remove-redundant-null-checks-before-free-in-drivers ++++ a/drivers/message/fusion/mptfc.c +@@ -305,10 +305,8 @@ mptfc_GetFcDevPage0(MPT_ADAPTER *ioc, in + } + + out: +- if (pp0_array) +- kfree(pp0_array); +- if (p0_array) +- kfree(p0_array); ++ kfree(pp0_array); ++ kfree(p0_array); + return rc; + } + +diff -puN drivers/message/fusion/mptsas.c~remove-redundant-null-checks-before-free-in-drivers drivers/message/fusion/mptsas.c +--- a/drivers/message/fusion/mptsas.c~remove-redundant-null-checks-before-free-in-drivers ++++ a/drivers/message/fusion/mptsas.c +@@ -1378,8 +1378,7 @@ mptsas_probe_hba_phys(MPT_ADAPTER *ioc) + return 0; + + out_free_port_info: +- if (hba) +- kfree(hba); ++ kfree(hba); + out: + return error; + } +diff -puN drivers/net/fs_enet/fs_enet-mii.c~remove-redundant-null-checks-before-free-in-drivers drivers/net/fs_enet/fs_enet-mii.c +--- a/drivers/net/fs_enet/fs_enet-mii.c~remove-redundant-null-checks-before-free-in-drivers ++++ a/drivers/net/fs_enet/fs_enet-mii.c +@@ -431,8 +431,7 @@ static struct fs_enet_mii_bus *create_bu + return bus; + + err: +- if (bus) +- kfree(bus); ++ kfree(bus); + return ERR_PTR(ret); + } + +diff -puN drivers/net/wireless/ipw2200.c~remove-redundant-null-checks-before-free-in-drivers drivers/net/wireless/ipw2200.c +--- a/drivers/net/wireless/ipw2200.c~remove-redundant-null-checks-before-free-in-drivers ++++ a/drivers/net/wireless/ipw2200.c +@@ -1229,12 +1229,6 @@ static struct ipw_fw_error *ipw_alloc_er + return error; + } + +-static void ipw_free_error_log(struct ipw_fw_error *error) +-{ +- if (error) +- kfree(error); +-} +- + static ssize_t show_event_log(struct device *d, + struct device_attribute *attr, char *buf) + { +@@ -1296,10 +1290,9 @@ static ssize_t clear_error(struct device + const char *buf, size_t count) + { + struct ipw_priv *priv = dev_get_drvdata(d); +- if (priv->error) { +- ipw_free_error_log(priv->error); +- priv->error = NULL; +- } ++ ++ kfree(priv->error); ++ priv->error = NULL; + return count; + } + +@@ -1970,8 +1963,7 @@ static void ipw_irq_tasklet(struct ipw_p + struct ipw_fw_error *error = + ipw_alloc_error_log(priv); + ipw_dump_error_log(priv, error); +- if (error) +- ipw_free_error_log(error); ++ kfree(error); + } + #endif + } else { +@@ -11693,10 +11685,8 @@ static void ipw_pci_remove(struct pci_de + } + } + +- if (priv->error) { +- ipw_free_error_log(priv->error); +- priv->error = NULL; +- } ++ kfree(priv->error); ++ priv->error = NULL; + + #ifdef CONFIG_IPW2200_PROMISCUOUS + ipw_prom_free(priv); +diff -puN drivers/scsi/libata-scsi.c~remove-redundant-null-checks-before-free-in-drivers drivers/scsi/libata-scsi.c +--- a/drivers/scsi/libata-scsi.c~remove-redundant-null-checks-before-free-in-drivers ++++ a/drivers/scsi/libata-scsi.c +@@ -222,9 +222,7 @@ int ata_cmd_ioctl(struct scsi_device *sc + && copy_to_user(arg + sizeof(args), argbuf, argsize)) + rc = -EFAULT; + error: +- if (argbuf) +- kfree(argbuf); +- ++ kfree(argbuf); + return rc; + } + +diff -puN drivers/video/au1100fb.c~remove-redundant-null-checks-before-free-in-drivers drivers/video/au1100fb.c +--- a/drivers/video/au1100fb.c~remove-redundant-null-checks-before-free-in-drivers ++++ a/drivers/video/au1100fb.c +@@ -743,8 +743,7 @@ void __exit au1100fb_cleanup(void) + { + driver_unregister(&au1100fb_driver); + +- if (drv_info.opt_mode) +- kfree(drv_info.opt_mode); ++ kfree(drv_info.opt_mode); + } + + module_init(au1100fb_init); +_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/extdiff Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,28 @@ +#!/bin/bash + +echo '[extensions]' >> $HGRC +echo 'extdiff =' >> $HGRC + +hg init a +cd a +echo 'The first line.' > myfile +hg ci -Ama +echo 'The second line.' >> myfile + +#$ name: diff + +hg diff + +#$ name: extdiff + +hg extdiff + +#$ name: extdiff-ctx + +#$ ignore: ^\*\*\* a.* + +hg extdiff -o -NprcC5 + +#$ name: + +exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/filenames Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,61 @@ +#!/bin/bash + +hg init a +cd a +mkdir -p examples src/watcher +touch COPYING MANIFEST.in README setup.py +touch examples/performant.py examples/simple.py +touch src/main.py src/watcher/_watcher.c src/watcher/watcher.py src/xyzzy.txt + +#$ name: files + +hg add COPYING README examples/simple.py + +#$ name: dirs + +hg status src + +#$ name: wdir-subdir + +cd src +hg add -n +hg add -n . + +#$ name: wdir-relname + +hg status +hg status `hg root` + +#$ name: glob.star + +hg add 'glob:*.py' + +#$ name: glob.starstar + +cd .. +hg status 'glob:**.py' + +#$ name: glob.star-starstar + +hg status 'glob:*.py' +hg status 'glob:**.py' + +#$ name: glob.question + +hg status 'glob:**.?' + +#$ name: glob.range + +hg status 'glob:**[nr-t]' + +#$ name: glob.group + +hg status 'glob:*.{in,py}' + +#$ name: filter.include + +hg status -I '*.in' + +#$ name: filter.exclude + +hg status -X '**.py' src
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/hook.msglen Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,14 @@ +#!/bin/sh + +hg init a +cd a +echo '[hooks]' > .hg/hgrc +echo 'pretxncommit.msglen = test `hg tip --template {desc} | wc -c` -ge 10' >> .hg/hgrc + +#$ name: go + +cat .hg/hgrc +echo a > a +hg add a +hg commit -A -m 'too short' +hg commit -A -m 'long enough'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/hook.simple Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,37 @@ +#!/bin/bash + +#$ name: init + +hg init hook-test +cd hook-test +echo '[hooks]' >> .hg/hgrc +echo 'commit = echo committed $HG_NODE' >> .hg/hgrc +cat .hg/hgrc +echo a > a +hg add a +hg commit -m 'testing commit hook' + +#$ name: ext +#$ ignore: ^date of commit.* + +echo 'commit.when = echo -n "date of commit: "; date' >> .hg/hgrc +echo a >> a +hg commit -m 'i have two hooks' + +#$ name: + +echo '#!/bin/sh' >> check_bug_id +echo '# check that a commit comment mentions a numeric bug id' >> check_bug_id +echo 'hg log -r $1 --template {desc} | grep -q "\<bug *[0-9]"' >> check_bug_id +chmod +x check_bug_id + +#$ name: pretxncommit + +cat check_bug_id + +echo 'pretxncommit.bug_id_required = ./check_bug_id $HG_NODE' >> .hg/hgrc + +echo a >> a +hg commit -m 'i am not mentioning a bug id' + +hg commit -m 'i refer you to bug 666'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/hook.ws Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,31 @@ +#!/bin/bash + +hg init a +cd a +echo '[hooks]' > .hg/hgrc +echo "pretxncommit.whitespace = hg export tip | (! egrep -q '^\\+.*[ \\t]$')" >> .hg/hgrc + +#$ name: simple + +cat .hg/hgrc +echo 'a ' > a +hg commit -A -m 'test with trailing whitespace' +echo 'a' > a +hg commit -A -m 'drop trailing whitespace and try again' + +#$ name: + +echo '[hooks]' > .hg/hgrc +echo "pretxncommit.whitespace = .hg/check_whitespace.py" >> .hg/hgrc +cp $EXAMPLE_DIR/data/check_whitespace.py .hg + +#$ name: better + +cat .hg/hgrc +echo 'a ' >> a +hg commit -A -m 'add new line with trailing whitespace' +sed -i 's, *$,,' a +hg commit -A -m 'trimmed trailing whitespace' + +#$ name: +exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/issue29 Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,22 @@ +#!/bin/bash + +#$ name: go + +hg init issue29 +cd issue29 +echo a > a +hg ci -Ama +echo b > b +hg ci -Amb +hg up 0 +mkdir b +echo b > b/b +hg ci -Amc + +#$ ignore: abort: Is a directory: .* +hg merge + +#$ name: +# This error is expected from the failed merge. + +exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/mq.dodiff Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,14 @@ +#!/bin/bash + +#$ name: diff + +echo 'this is my original thought' > oldfile +echo 'i have changed my mind' > newfile + +diff -u oldfile newfile > tiny.patch + +cat tiny.patch + +patch < tiny.patch + +cat oldfile
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/mq.guards Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,67 @@ +#!/bin/bash + +echo '[extensions]' >> $HGRC +echo 'hgext.mq =' >> $HGRC + +hg init a +cd a + +#$ name: init + +hg qinit +hg qnew hello.patch +echo hello > hello +hg add hello +hg qrefresh +hg qnew goodbye.patch +echo goodbye > goodbye +hg add goodbye +hg qrefresh + +#$ name: qguard + +hg qguard + +#$ name: qguard.pos + +hg qguard +foo +hg qguard + +#$ name: qguard.neg + +hg qguard hello.patch -quux +hg qguard hello.patch + +#$ name: series + +cat .hg/patches/series + +#$ name: qselect.foo + +hg qpop -a +hg qselect +hg qselect foo +hg qselect + +#$ name: qselect.cat + +cat .hg/patches/guards + +#$ name: qselect.qpush +hg qpush -a + +#$ name: qselect.error + +hg qselect +foo + +#$ name: qselect.quux + +hg qselect quux +hg qpop -a +hg qpush -a + +#$ name: qselect.foobar + +hg qselect foo bar +hg qpop -a +hg qpush -a
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/mq.id Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,28 @@ +#!/bin/sh + +echo '[extensions]' >> $HGRC +echo 'hgext.mq =' >> $HGRC + +hg init a +cd a +hg qinit +echo 'int x;' > test.c +hg ci -Ama + +hg qnew first.patch +echo 'float c;' >> test.c +hg qrefresh + +hg qnew second.patch +echo 'double u;' > other.c +hg add other.c +hg qrefresh + +#$ name: output + +hg qapplied +hg log -r qbase:qtip +hg export second.patch + +#$ name: +exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/mq.qinit-help Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,7 @@ +#!/bin/bash + +echo '[extensions]' >> $HGRC +echo 'hgext.mq =' >> $HGRC + +#$ name: help +hg help qinit
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/mq.tarball Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,51 @@ +#!/bin/bash + +cp $EXAMPLE_DIR/data/netplug-*.tar.bz2 . +ln -s /bin/true download +export PATH=`pwd`:$PATH + +#$ name: download + +download netplug-1.2.5.tar.bz2 +tar jxf netplug-1.2.5.tar.bz2 +cd netplug-1.2.5 +hg init +hg commit -q --addremove --message netplug-1.2.5 +cd .. +hg clone netplug-1.2.5 netplug + +#$ name: + +cd netplug +echo '[extensions]' >> $HGRC +echo 'hgext.mq =' >> $HGRC +cd .. + +#$ name: qinit + +cd netplug +hg qinit +hg qnew -m 'fix build problem with gcc 4' build-fix.patch +perl -pi -e 's/int addr_len/socklen_t addr_len/' netlink.c +hg qrefresh +hg tip -p + +#$ name: newsource + +hg qpop -a +cd .. +download netplug-1.2.8.tar.bz2 +hg clone netplug-1.2.5 netplug-1.2.8 +cd netplug-1.2.8 +hg locate -0 | xargs -0 rm +cd .. +tar jxf netplug-1.2.8.tar.bz2 +cd netplug-1.2.8 +hg commit --addremove --message netplug-1.2.8 + +#$ name: repush + +cd ../netplug +hg pull ../netplug-1.2.8 +hg qpush -a +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/mq.tools Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,11 @@ +#!/bin/bash + +cp $EXAMPLE_DIR/data/remove-redundant-null-checks.patch . + +#$ name: tools +diffstat -p1 remove-redundant-null-checks.patch + +filterdiff -i '*/video/*' remove-redundant-null-checks.patch + +#$ name: lsdiff +lsdiff -nvv remove-redundant-null-checks.patch
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/mq.tutorial Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,74 @@ +#!/bin/bash + +echo '[extensions]' >> $HGRC +echo 'hgext.mq =' >> $HGRC + +#$ name: qinit + +hg init mq-sandbox +cd mq-sandbox +echo 'line 1' > file1 +echo 'another line 1' > file2 +hg add file1 file2 +hg commit -m'first change' + +hg qinit + +#$ name: qnew + +hg tip +hg qnew first.patch +hg tip +ls .hg/patches + +#$ name: qrefresh +#$ ignore: \s+200[78]-.* + +echo 'line 2' >> file1 +hg diff +hg qrefresh +hg diff +hg tip --style=compact --patch + +#$ name: qrefresh2 + +echo 'line 3' >> file1 +hg status +hg qrefresh +hg tip --style=compact --patch + +#$ name: qnew2 + +hg qnew second.patch +hg log --style=compact --limit=2 +echo 'line 4' >> file1 +hg qrefresh +hg tip --style=compact --patch +hg annotate file1 + +#$ name: qseries + +hg qseries +hg qapplied + +#$ name: qpop + +hg qapplied +hg qpop +hg qseries +hg qapplied +cat file1 + +#$ name: qpush-a + +hg qpush -a +cat file1 + +#$ name: add + +echo 'file 3, line 1' >> file3 +hg qnew add-file3.patch +hg qnew -f add-file3.patch + +#$ name: +exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/rename.divergent Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,33 @@ +#!/bin/bash + +hg init orig +cd orig +echo foo > foo +hg ci -A -m 'First commit' +cd .. + +#$ name: clone + +hg clone orig anne +hg clone orig bob + +#$ name: rename.anne + +cd anne +hg mv foo bar +hg ci -m 'Rename foo to bar' + +#$ name: rename.bob + +cd ../bob +hg mv foo quux +hg ci -m 'Rename foo to quux' + +#$ name: merge +# See http://www.selenic.com/mercurial/bts/issue455 + +cd ../orig +hg pull -u ../anne +hg pull ../bob +hg merge +ls
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/rollback Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,37 @@ +#!/bin/bash + +hg init a +cd a +echo a > a +hg ci -A -m 'First commit' + +echo a >> a + +#$ name: tip + +#$ name: commit + +hg status +echo b > b +hg commit -m 'Add file b' + +#$ name: status + +hg status +hg tip + +#$ name: rollback + +hg rollback +hg tip +hg status + +#$ name: add + +hg add b +hg commit -m 'Add file b, this time for real' + +#$ name: twice + +hg rollback +hg rollback
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/run-example Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,391 @@ +#!/usr/bin/env python +# +# This program takes something that resembles a shell script and runs +# it, spitting input (commands from the script) and output into text +# files, for use in examples. + +import cStringIO +import errno +import getopt +import os +import pty +import re +import select +import shutil +import signal +import stat +import sys +import tempfile +import time + +tex_subs = { + '\\': '\\textbackslash{}', + '{': '\\{', + '}': '\\}', + } + +def gensubs(s): + start = 0 + for i, c in enumerate(s): + sub = tex_subs.get(c) + if sub: + yield s[start:i] + start = i + 1 + yield sub + yield s[start:] + +def tex_escape(s): + return ''.join(gensubs(s)) + +def maybe_unlink(name): + try: + os.unlink(name) + return True + except OSError, err: + if err.errno != errno.ENOENT: + raise + return False + +def find_path_to(program): + for p in os.environ.get('PATH', os.defpath).split(os.pathsep): + name = os.path.join(p, program) + if os.access(name, os.X_OK): + return p + return None + +class example: + shell = '/usr/bin/env bash' + ps1 = '__run_example_ps1__ ' + ps2 = '__run_example_ps2__ ' + pi_re = re.compile(r'#\$\s*(name|ignore):\s*(.*)$') + + timeout = 10 + + def __init__(self, name, verbose): + self.name = name + self.verbose = verbose + self.poll = select.poll() + + def parse(self): + '''yield each hunk of input from the file.''' + fp = open(self.name) + cfp = cStringIO.StringIO() + for line in fp: + cfp.write(line) + if not line.rstrip().endswith('\\'): + yield cfp.getvalue() + cfp.seek(0) + cfp.truncate() + + def status(self, s): + sys.stdout.write(s) + if not s.endswith('\n'): + sys.stdout.flush() + + def send(self, s): + if self.verbose: + print >> sys.stderr, '>', self.debugrepr(s) + while s: + count = os.write(self.cfd, s) + s = s[count:] + + def debugrepr(self, s): + rs = repr(s) + limit = 60 + if len(rs) > limit: + return ('%s%s ... [%d bytes]' % (rs[:limit], rs[0], len(s))) + else: + return rs + + timeout = 5 + + def read(self, hint): + events = self.poll.poll(self.timeout * 1000) + if not events: + print >> sys.stderr, ('[%stimed out after %d seconds]' % + (hint, self.timeout)) + os.kill(self.pid, signal.SIGHUP) + return '' + return os.read(self.cfd, 1024) + + def receive(self, hint): + out = cStringIO.StringIO() + while True: + try: + if self.verbose: + sys.stderr.write('< ') + s = self.read(hint) + except OSError, err: + if err.errno == errno.EIO: + return '', '' + raise + if self.verbose: + print >> sys.stderr, self.debugrepr(s) + out.write(s) + s = out.getvalue() + if s.endswith(self.ps1): + return self.ps1, s.replace('\r\n', '\n')[:-len(self.ps1)] + if s.endswith(self.ps2): + return self.ps2, s.replace('\r\n', '\n')[:-len(self.ps2)] + + def sendreceive(self, s, hint): + self.send(s) + ps, r = self.receive(hint) + if r.startswith(s): + r = r[len(s):] + return ps, r + + def run(self): + ofp = None + basename = os.path.basename(self.name) + self.status('running %s ' % basename) + tmpdir = tempfile.mkdtemp(prefix=basename) + + # remove the marker file that we tell make to use to see if + # this run succeeded + maybe_unlink(self.name + '.run') + + rcfile = os.path.join(tmpdir, '.hgrc') + rcfp = open(rcfile, 'w') + print >> rcfp, '[ui]' + print >> rcfp, "username = Bryan O'Sullivan <bos@serpentine.com>" + + rcfile = os.path.join(tmpdir, '.bashrc') + rcfp = open(rcfile, 'w') + print >> rcfp, 'PS1="%s"' % self.ps1 + print >> rcfp, 'PS2="%s"' % self.ps2 + print >> rcfp, 'unset HISTFILE' + path = ['/usr/bin', '/bin'] + hg = find_path_to('hg') + if hg and hg not in path: + path.append(hg) + def re_export(envar): + v = os.getenv(envar) + if v is not None: + print >> rcfp, 'export ' + envar + '=' + v + print >> rcfp, 'export PATH=' + ':'.join(path) + re_export('PYTHONPATH') + print >> rcfp, 'export EXAMPLE_DIR="%s"' % os.getcwd() + print >> rcfp, 'export HGMERGE=merge' + print >> rcfp, 'export LANG=C' + print >> rcfp, 'export LC_ALL=C' + print >> rcfp, 'export TZ=GMT' + print >> rcfp, 'export HGRC="%s/.hgrc"' % tmpdir + print >> rcfp, 'export HGRCPATH=$HGRC' + print >> rcfp, 'cd %s' % tmpdir + rcfp.close() + sys.stdout.flush() + sys.stderr.flush() + self.pid, self.cfd = pty.fork() + if self.pid == 0: + cmdline = ['/usr/bin/env', '-i', 'bash', '--noediting', + '--noprofile', '--norc'] + try: + os.execv(cmdline[0], cmdline) + except OSError, err: + print >> sys.stderr, '%s: %s' % (cmdline[0], err.strerror) + sys.stderr.flush() + os._exit(0) + self.poll.register(self.cfd, select.POLLIN | select.POLLERR | + select.POLLHUP) + + prompts = { + '': '', + self.ps1: '$', + self.ps2: '>', + } + + ignore = [ + r'\d+:[0-9a-f]{12}', # changeset number:hash + r'[0-9a-f]{40}', # long changeset hash + r'[0-9a-f]{12}', # short changeset hash + r'^(?:---|\+\+\+) .*', # diff header with dates + r'^date:.*', # date + #r'^diff -r.*', # "diff -r" is followed by hash + r'^# Date \d+ \d+', # hg patch header + ] + + err = False + read_hint = '' + + try: + try: + # eat first prompt string from shell + self.read(read_hint) + # setup env and prompt + ps, output = self.sendreceive('source %s\n' % rcfile, + read_hint) + for hunk in self.parse(): + # is this line a processing instruction? + m = self.pi_re.match(hunk) + if m: + pi, rest = m.groups() + if pi == 'name': + self.status('.') + out = rest + if out in ('err', 'lxo', 'out', 'run', 'tmp'): + print >> sys.stderr, ('%s: illegal section ' + 'name %r' % + (self.name, out)) + return 1 + assert os.sep not in out + if ofp is not None: + ofp.close() + err |= self.rename_output(ofp_basename, ignore) + if out: + ofp_basename = '%s.%s' % (self.name, out) + read_hint = ofp_basename + ' ' + ofp = open(ofp_basename + '.tmp', 'w') + else: + ofp = None + elif pi == 'ignore': + ignore.append(rest) + elif hunk.strip(): + # it's something we should execute + newps, output = self.sendreceive(hunk, read_hint) + if not ofp: + continue + # first, print the command we ran + if not hunk.startswith('#'): + nl = hunk.endswith('\n') + hunk = ('%s \\textbf{%s}' % + (prompts[ps], + tex_escape(hunk.rstrip('\n')))) + if nl: hunk += '\n' + ofp.write(hunk) + # then its output + ofp.write(tex_escape(output)) + ps = newps + self.status('\n') + except: + print >> sys.stderr, '(killed)' + os.kill(self.pid, signal.SIGKILL) + pid, rc = os.wait() + raise + else: + try: + ps, output = self.sendreceive('exit\n', read_hint) + if ofp is not None: + ofp.write(output) + ofp.close() + err |= self.rename_output(ofp_basename, ignore) + os.close(self.cfd) + except IOError: + pass + os.kill(self.pid, signal.SIGTERM) + pid, rc = os.wait() + err = err or rc + if err: + if os.WIFEXITED(rc): + print >> sys.stderr, '(exit %s)' % os.WEXITSTATUS(rc) + elif os.WIFSIGNALED(rc): + print >> sys.stderr, '(signal %s)' % os.WTERMSIG(rc) + else: + open(self.name + '.run', 'w') + return err + finally: + shutil.rmtree(tmpdir) + + def rename_output(self, base, ignore): + mangle_re = re.compile('(?:' + '|'.join(ignore) + ')') + def mangle(s): + return mangle_re.sub('', s) + def matchfp(fp1, fp2): + while True: + s1 = mangle(fp1.readline()) + s2 = mangle(fp2.readline()) + if cmp(s1, s2): + break + if not s1: + return True + return False + + oldname = base + '.out' + tmpname = base + '.tmp' + errname = base + '.err' + errfp = open(errname, 'w+') + for line in open(tmpname): + errfp.write(mangle_re.sub('', line)) + os.rename(tmpname, base + '.lxo') + errfp.seek(0) + try: + oldfp = open(oldname) + except IOError, err: + if err.errno != errno.ENOENT: + raise + os.rename(errname, oldname) + return False + if matchfp(oldfp, errfp): + os.unlink(errname) + return False + else: + print >> sys.stderr, '\nOutput of %s has changed!' % base + os.system('diff -u %s %s 1>&2' % (oldname, errname)) + return True + +def print_help(exit, msg=None): + if msg: + print >> sys.stderr, 'Error:', msg + print >> sys.stderr, 'Usage: run-example [options] [test...]' + print >> sys.stderr, 'Options:' + print >> sys.stderr, ' -a --all run all tests in this directory' + print >> sys.stderr, ' -h --help print this help message' + print >> sys.stderr, ' -v --verbose display extra debug output' + sys.exit(exit) + +def main(path='.'): + opts, args = getopt.getopt(sys.argv[1:], '?ahv', + ['all', 'help', 'verbose']) + verbose = False + run_all = False + for o, a in opts: + if o in ('-h', '-?', '--help'): + print_help(0) + if o in ('-a', '--all'): + run_all = True + if o in ('-v', '--verbose'): + verbose = True + errs = 0 + if args: + for a in args: + try: + st = os.lstat(a) + except OSError, err: + print >> sys.stderr, '%s: %s' % (a, err.strerror) + errs += 1 + continue + if stat.S_ISREG(st.st_mode) and st.st_mode & 0111: + if example(a, verbose).run(): + errs += 1 + else: + print >> sys.stderr, '%s: not a file, or not executable' % a + errs += 1 + elif run_all: + names = os.listdir(path) + names.sort() + for name in names: + if name == 'run-example' or name.startswith('.'): continue + if name.endswith('.out') or name.endswith('~'): continue + if name.endswith('.run'): continue + pathname = os.path.join(path, name) + try: + st = os.lstat(pathname) + except OSError, err: + # could be an output file that was removed while we ran + if err.errno != errno.ENOENT: + raise + continue + if stat.S_ISREG(st.st_mode) and st.st_mode & 0111: + if example(pathname, verbose).run(): + errs += 1 + print >> open(os.path.join(path, '.run'), 'w'), time.asctime() + else: + print_help(1, msg='no test names given, and --all not provided') + return errs + +if __name__ == '__main__': + try: + sys.exit(main()) + except KeyboardInterrupt: + print >> sys.stderr, 'interrupted!' + sys.exit(1)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/svn-long.txt Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,11 @@ +------------------------------------------------------------------------ +r9653 | sean.hefty | 2006-09-27 14:39:55 -0700 (Wed, 27 Sep 2006) | 5 lines +Changed paths: + M /gen2/trunk/src/linux-kernel/infiniband/core/cma.c + +On reporting a route error, also include the status for the error, +rather than indicating a status of 0 when an error has occurred. + +Signed-off-by: Sean Hefty <sean.hefty@intel.com> + +------------------------------------------------------------------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/svn-short.txt Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,9 @@ +------------------------------------------------------------------------ +r9653 | sean.hefty | 2006-09-27 14:39:55 -0700 (Wed, 27 Sep 2006) | 5 lines + +On reporting a route error, also include the status for the error, +rather than indicating a status of 0 when an error has occurred. + +Signed-off-by: Sean Hefty <sean.hefty@intel.com> + +------------------------------------------------------------------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/svn.style Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,2 @@ +header = '------------------------------------------------------------------------\n\n' +changeset = svn.template
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/svn.template Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,5 @@ +r{rev} | {author|user} | {date|isodate} ({date|rfc822date}) + +{desc|strip|fill76} + +------------------------------------------------------------------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/tag Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,44 @@ +#!/bin/bash + +#$ name: init + +hg init mytag +cd mytag + +echo hello > myfile +hg commit -A -m 'Initial commit' + +#$ name: tag + +hg tag v1.0 + +#$ name: tags + +hg tags + +#$ name: log + +hg log + +#$ name: log.v1.0 + +echo goodbye > myfile2 +hg commit -A -m 'Second commit' +hg log -r v1.0 + +#$ name: remove + +hg tag --remove v1.0 +hg tags + +#$ name: replace + +hg tag -r 1 v1.1 +hg tags +hg tag -r 2 v1.1 +hg tag -f -r 2 v1.1 +hg tags + +#$ name: tip + +hg tip
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/template.simple Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,96 @@ +#!/bin/bash + +# So many different bits of random output, it would be a nightmare to +# ignore each individually. +#$ ignore: .* + +hg init myrepo +cd myrepo +echo hello > hello +hg commit -Am'added hello' + +echo hello >> hello +echo goodbye > goodbye +echo ' added line to end of <<hello>> file.' > ../msg +echo '' >> ../msg +echo 'in addition, added a file with the helpful name (at least i hope that some might consider it so) of goodbye.' >> ../msg + +hg commit -Al../msg + +hg tag mytag +hg tag v0.1 + +#$ name: normal + +hg log -r1 + +#$ name: compact + +hg log --style compact + +#$ name: changelog + +hg log --style changelog + +#$ name: simplest + +hg log -r1 --template 'i saw a changeset\n' + +#$ name: simplesub + +hg log --template 'i saw a changeset: {desc}\n' + +#$ name: keywords + +hg log -r1 --template 'author: {author}\n' +hg log -r1 --template 'desc:\n{desc}\n' +hg log -r1 --template 'files: {files}\n' +hg log -r1 --template 'file_adds: {file_adds}\n' +hg log -r1 --template 'file_dels: {file_dels}\n' +hg log -r1 --template 'node: {node}\n' +hg log -r1 --template 'parents: {parents}\n' +hg log -r1 --template 'rev: {rev}\n' +hg log -r1 --template 'tags: {tags}\n' + +#$ name: datekeyword + +hg log -r1 --template 'date: {date}\n' +hg log -r1 --template 'date: {date|isodate}\n' + +#$ name: manyfilters + +hg log -r1 --template '{author}\n' +hg log -r1 --template '{author|domain}\n' +hg log -r1 --template '{author|email}\n' +hg log -r1 --template '{author|obfuscate}\n' | cut -c-76 +hg log -r1 --template '{author|person}\n' +hg log -r1 --template '{author|user}\n' + +hg log -r1 --template 'looks almost right, but actually garbage: {date}\n' +hg log -r1 --template '{date|age}\n' +hg log -r1 --template '{date|date}\n' +hg log -r1 --template '{date|hgdate}\n' +hg log -r1 --template '{date|isodate}\n' +hg log -r1 --template '{date|rfc822date}\n' +hg log -r1 --template '{date|shortdate}\n' + +hg log -r1 --template '{desc}\n' | cut -c-76 +hg log -r1 --template '{desc|addbreaks}\n' | cut -c-76 +hg log -r1 --template '{desc|escape}\n' | cut -c-76 +hg log -r1 --template '{desc|fill68}\n' +hg log -r1 --template '{desc|fill76}\n' +hg log -r1 --template '{desc|firstline}\n' +hg log -r1 --template '{desc|strip}\n' | cut -c-76 +hg log -r1 --template '{desc|tabindent}\n' | expand | cut -c-76 + +hg log -r1 --template '{node}\n' +hg log -r1 --template '{node|short}\n' + +#$ name: combine + +hg log -r1 --template 'description:\n\t{desc|strip|fill68|tabindent}\n' + +#$ name: rev + +echo 'changeset = "rev: {rev}\n"' > rev +hg log -l1 --style ./rev
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/template.svnstyle Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,70 @@ +#!/bin/bash + +svn() { + cat $EXAMPLE_DIR/svn-short.txt +} + +#$ name: short + +svn log -r9653 + +#$ name: + +hg init myrepo +cd myrepo + +echo hello > hello +hg commit -Am'added hello' + +echo hello >> hello +echo goodbye > goodbye +echo ' added line to end of <<hello>> file.' > ../msg +echo '' >> ../msg +echo 'in addition, added a file with the helpful name (at least i hope that some might consider it so) of goodbye.' >> ../msg + +hg commit -Al../msg + +hg tag mytag +hg tag v0.1 + +echo 'changeset = "{node|short}\n"' > svn.style + +#$ name: id + +hg log -r0 --template '{node}' + +#$ name: simplest + +cat svn.style +hg log -r1 --style svn.style + +#$ name: + +echo 'changeset =' > broken.style + +#$ name: syntax.input + +cat broken.style + +#$ name: syntax.error + +hg log -r1 --style broken.style + +#$ name: + +cp $EXAMPLE_DIR/svn.style . +cp $EXAMPLE_DIR/svn.template . + +#$ name: template + +cat svn.template + +#$ name: style + +cat svn.style + +#$ name: result +#$ ignore: \| 200[78].* + +hg log -r1 --style svn.style +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/tour Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,194 @@ +#!/bin/bash + +#$ name: version + +hg version + +#$ name: help + +hg help init + +#$ name: clone + +hg clone http://hg.serpentine.com/tutorial/hello + +#$ name: ls +#$ ignore: ^drwx.* +#$ ignore: ^total \d+ + +ls -l +ls hello + +#$ name: ls-a + +cd hello +ls -a + +#$ name: log + +hg log + +#$ name: log-r + +hg log -r 3 +hg log -r 0272e0d5a517 +hg log -r 1 -r 4 + +#$ name: log.range + +hg log -r 2:4 + +#$ name: log-v + +hg log -v -r 3 + +#$ name: log-vp + +hg log -v -p -r 2 + +#$ name: reclone + +cd .. +hg clone hello my-hello +cd my-hello + +#$ name: sed + +sed -i '/printf/a\\tprintf("hello again!\\n");' hello.c + +#$ name: status + +ls +hg status + +#$ name: diff + +hg diff + +#$ name: + +export HGEDITOR='echo Added an extra line of output >' + +#$ name: commit + +hg commit + +#$ name: merge.dummy1 + +hg log -r 5 | grep changeset | cut -c 16-19 2>/dev/null > /tmp/REV5.my-hello + +#$ name: tip + +hg tip -vp + +#$ name: clone-pull + +cd .. +hg clone hello hello-pull + +#$ name: incoming + +cd hello-pull +hg incoming ../my-hello + +#$ name: pull + +hg tip +hg pull ../my-hello +hg tip + +#$ name: update + +grep printf hello.c +hg update tip +grep printf hello.c + +#$ name: parents + +hg parents + +#$ name: older + +hg update 2 +hg parents +hg update + +#$ name: clone-push + +cd .. +hg clone hello hello-push + +#$ name: outgoing + +cd my-hello +hg outgoing ../hello-push + +#$ name: push + +hg push ../hello-push + +#$ name: push.nothing + +hg push ../hello-push + +#$ name: outgoing.net + +hg outgoing http://hg.serpentine.com/tutorial/hello + +#$ name: push.net + +hg push http://hg.serpentine.com/tutorial/hello + +#$ name: merge.clone + +cd .. +hg clone hello my-new-hello +cd my-new-hello +sed -i '/printf/i\\tprintf("once more, hello.\\n");' hello.c +hg commit -m 'A new hello for a new day.' + +#$ name: merge.dummy2 + +hg log -r 5 | grep changeset | cut -c 16-19 2>/dev/null > /tmp/REV5.my-new-hello + +#$ name: merge.cat + +cat hello.c +cat ../my-hello/hello.c + +#$ name: merge.pull + +hg pull ../my-hello + +#$ name: merge.dummy3 + +hg log -r 6 | grep changeset | cut -c 16-19 2>/dev/null > /tmp/REV6.my-new-hello + +#$ name: merge.heads + +hg heads + +#$ name: merge.update + +hg update + +#$ name: merge.merge + +hg merge + +#$ name: merge.parents + +hg parents +cat hello.c + +#$ name: merge.commit + +hg commit -m 'Merged changes' + +#$ name: merge.dummy4 + +hg log -r 7 | grep changeset | cut -c 16-19 2>/dev/null > /tmp/REV7.my-new-hello + +#$ name: merge.tip + +hg tip
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/examples/tour-merge-conflict Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,73 @@ +#!/bin/bash + +hg init scam +cd scam + +#$ name: wife + +cat > letter.txt <<EOF +Greetings! + +I am Mariam Abacha, the wife of former +Nigerian dictator Sani Abacha. +EOF + +hg add letter.txt +hg commit -m '419 scam, first draft' + +#$ name: cousin + +cd .. +hg clone scam scam-cousin +cd scam-cousin + +cat > letter.txt <<EOF +Greetings! + +I am Shehu Musa Abacha, cousin to the former +Nigerian dictator Sani Abacha. +EOF + +hg commit -m '419 scam, with cousin' + +#$ name: son + +cd .. +hg clone scam scam-son +cd scam-son + +cat > letter.txt <<EOF +Greetings! + +I am Alhaji Abba Abacha, son of the former +Nigerian dictator Sani Abacha. +EOF + +hg commit -m '419 scam, with son' + +#$ name: pull + +cd .. +hg clone scam-cousin scam-merge +cd scam-merge +hg pull -u ../scam-son + +#$ name: merge +#$ ignore: [<>]{7} /tmp/.* + +export HGMERGE=merge +hg merge +cat letter.txt + +#$ name: commit + +cat > letter.txt <<EOF +Greetings! + +I am Bryan O'Sullivan, no relation of the former +Nigerian dictator Sani Abacha. +EOF + +hg resolve -m letter.txt +hg commit -m 'Send me your money' +hg tip
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/fblinks Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,1 @@ +../en/fblinks \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/feature-branches.dot Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,8 @@ +digraph feature_branches { + maestro -> cripto; + maestro -> sistemadearchivos; + maestro -> ipc; + maestro -> memoria; + maestro -> red; + maestro -> seguridad; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/filelog.svg Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,381 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="filelog.svg" + sodipodi:docbase="/home/arun/hgbook/en" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs4"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective57" /> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mend" + style="overflow:visible;"> + <path + id="path3128" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;" + transform="scale(0.4) rotate(180) translate(10,0)" /> + </marker> + <linearGradient + id="linearGradient2887"> + <stop + style="stop-color:#91cfcf;stop-opacity:1;" + offset="0" + id="stop2889" /> + <stop + style="stop-color:aqua;stop-opacity:0;" + offset="1" + id="stop2891" /> + </linearGradient> + <linearGradient + id="linearGradient2795"> + <stop + style="stop-color:#ccc;stop-opacity:1;" + offset="0" + id="stop2797" /> + <stop + style="stop-color:#ccc;stop-opacity:0;" + offset="1" + id="stop2799" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2795" + id="linearGradient3170" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(121.2183,94.95434)" + x1="81.322357" + y1="404.34424" + x2="201.52036" + y2="373.03967" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2887" + id="linearGradient3172" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(0,12)" + x1="62.634491" + y1="503.3392" + x2="248.49242" + y2="462.94327" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2795" + id="linearGradient3174" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.001035,0,0,0.653159,236.7075,153.0415)" + x1="81.322357" + y1="404.34424" + x2="201.52036" + y2="373.03967" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2887" + id="linearGradient3176" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(0,12)" + x1="62.634491" + y1="503.3392" + x2="248.49242" + y2="462.94327" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2795" + id="linearGradient3208" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.001035,0,0,0.653159,236.7075,153.0415)" + x1="81.322357" + y1="404.34424" + x2="201.52036" + y2="373.03967" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2887" + id="linearGradient3210" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(0,12)" + x1="62.634491" + y1="503.3392" + x2="248.49242" + y2="462.94327" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2795" + id="linearGradient3212" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(121.2183,94.95434)" + x1="81.322357" + y1="404.34424" + x2="201.52036" + y2="373.03967" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2887" + id="linearGradient3214" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(0,12)" + x1="62.634491" + y1="503.3392" + x2="248.49242" + y2="462.94327" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2795" + id="linearGradient3256" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.2343775,0,0,0.9981848,103.25588,95.681888)" + x1="74.301666" + y1="431.67441" + x2="260.05884" + y2="369.95322" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2887" + id="linearGradient3258" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.228929,0,0,0.9972824,-62.037003,13.312997)" + x1="62.634491" + y1="503.3392" + x2="248.49242" + y2="462.94327" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2795" + id="linearGradient3260" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.2300738,0,0,0.6517275,219.97511,153.61527)" + x1="74.387527" + y1="431.80576" + x2="259.97339" + y2="369.82224" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2887" + id="linearGradient3262" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.2289272,0,0,0.9972824,-62.036756,13.312985)" + x1="62.634491" + y1="503.3392" + x2="248.49242" + y2="462.94327" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.4" + inkscape:cx="455.8122" + inkscape:cy="520" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="1280" + inkscape:window-height="800" + inkscape:window-x="0" + inkscape:window-y="0" + showgrid="false" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <rect + style="opacity:1;fill:#abadf8;fill-opacity:1;stroke:#595959;stroke-width:0.93760371;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3180" + width="273.81375" + height="199.06245" + x="369.1796" + y="351.79019" /> + <rect + style="opacity:1;fill:#a2f69c;fill-opacity:1;stroke:#595959;stroke-width:0.93760341;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3178" + width="273.81339" + height="199.06233" + x="72.699799" + y="351.78983" /> + <g + id="g3144" + transform="translate(80.467048,0.71578)"> + <g + id="g2940"> + <rect + style="fill:url(#linearGradient3260);fill-opacity:1;stroke:#000000;stroke-width:0.89536202;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect2914" + width="227.38896" + height="39.500999" + x="311.92496" + y="395.08627" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="323.72824" + y="416.7626" + id="text2918"><tspan + sodipodi:role="line" + id="tspan2920" + x="323.72824" + y="416.7626" + style="font-family:Courier">.hg/store/data/README.i</tspan></text> + </g> + <g + transform="translate(3.79093e-5,-80.1853)" + id="g2945"> + <g + id="g2955"> + <rect + y="475.4968" + x="15.550935" + height="39.500999" + width="227.17694" + id="rect2947" + style="fill:url(#linearGradient3262);fill-opacity:1;stroke:#000000;stroke-width:1.10706258;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text2949" + y="498.35123" + x="31.230644" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="498.35123" + x="31.230644" + id="tspan2951" + sodipodi:role="line">README</tspan></text> + </g> + </g> + <path + inkscape:connector-type="polyline" + id="path2960" + d="M 242.94685,414.91115 C 242.94685,414.91115 293.61127,415.26754 310.16269,415.38633" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1.02046943px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + sodipodi:nodetypes="cz" /> + </g> + <g + id="g3156" + transform="translate(80.467048,0.71578)"> + <g + transform="translate(116,0)" + id="g2831"> + <rect + style="fill:url(#linearGradient3256);fill-opacity:1;stroke:#000000;stroke-width:1.11001658;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect1906" + width="228.18446" + height="60.499123" + x="195.52719" + y="465.51859" /> + <g + id="g2803" + transform="translate(-0.893671,1.833581)"> + <text + id="text1884" + y="483.92801" + x="208.95944" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="483.92801" + x="208.95944" + id="tspan1886" + sodipodi:role="line">.hg/store/data/src/hello.c.d</tspan></text> + <text + id="text1888" + y="507.79309" + x="208.95944" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="507.79309" + x="208.95944" + id="tspan1890" + sodipodi:role="line">.hg/store/data/src/hello.c.i</tspan></text> + </g> + </g> + <g + id="g2907"> + <rect + style="fill:url(#linearGradient3258);fill-opacity:1;stroke:#000000;stroke-width:1.10706329;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect2843" + width="227.17728" + height="39.500999" + x="15.550805" + y="475.4968" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="31.230644" + y="498.35123" + id="text2847"><tspan + sodipodi:role="line" + id="tspan2849" + x="31.230644" + y="498.35123" + style="font-family:Courier">src/hello.c</tspan></text> + </g> + <path + inkscape:connection-end="#g2831" + inkscape:connection-start="#g2907" + inkscape:connector-type="polyline" + id="path2962" + d="M 242.4315,495.88043 C 242.4315,495.88043 292.8861,495.99942 310.04102,496.03909" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + sodipodi:nodetypes="cs" /> + </g> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="98.496666" + y="373.96353" + id="text3216"><tspan + sodipodi:role="line" + id="tspan3218" + x="98.496666" + y="373.96353">Directorio de trabajo</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="391.39197" + y="373.96353" + id="text3228"><tspan + sodipodi:role="line" + id="tspan3230" + x="391.39197" + y="373.96353">Repositorio</tspan></text> + </g> +</svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/filenames.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,344 @@ +\chapter{Nombres de ficheros y asociación de patrones} +\label{chap:names} + +Mercurial provee mecanismos que le permiten trabajar con nombres de +ficheros en una manera consistente y expresiva. + +\section{Nombrado de ficheros simple} + +% TODO traducción literal de "under the hood". revisar +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. +\interaction{filenames.files} + +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. +\interaction{filenames.dirs} + +\section{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. +\begin{itemize} +\item Muchos comandos operarán sobre el directorio de trabajo + completo. Por ejemplo, esto es lo que hace el comando + \hgcmd{add}, +\item Si el comando tiene efectos difíciles o incluso imposibles de + revertir, se le obligará a usted a proveer explícitamente al menos + % TODO revisar ese "lo proteje a usted" + un nombre o patrón (ver más abajo). Esto lo proteje a usted de, + por ejemplo, borrar ficheros accidentalmente al ejecutar + \hgcmd{remove} sin ningún argumento. +\end{itemize} + + +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 ``\dirname{.}''. +\interaction{filenames.wdir-subdir} + +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 +\hgcmd{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 \hgcmd{root}. +\interaction{filenames.wdir-relname} + +\section{Reportar que está pasando} + +El ejemplo con el comando \hgcmd{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 \emph{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 \emph{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 \hggopt{-q}. También puede hacer que impriman el +nombre de cada fichero, aún aquellos que usted indicó explícitamente, +usando la opción \hggopt{-v}. + +\section{Uso de patrones para identificar ficheros} + +Además de trabajar con nombres de ficheros y directorios, Mercurial le +permite usar \emph{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: +\begin{codesample2} + syntax:patternbody +\end{codesample2} +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 \texttt{glob}\ndt{Grupo, colección, +aglomeración.}; 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 \texttt{glob}. Por esto, usted puede omitir el +prefijo ``\texttt{glob:}'' en Windows, pero también es seguro usarlo. + +La sintaxis \texttt{re}\ndt{Expresiones regulares.} 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. + +\subsection{Patrones \texttt{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 ``\texttt{*}'' se asocia con cualquier cadena, dentro de +un único directorio. +\interaction{filenames.glob.star} + +La secuencia ``\texttt{**}'' se asocia con cualquier cadena, y cruza los +% TODO token +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. +\interaction{filenames.glob.starstar} + +La secuencia ``\texttt{?}'' se asocia con cualquier caracter sencillo. +\interaction{filenames.glob.question} + +El caracter ``\texttt{[}'' marca el inicio de una \emph{clase de +caracteres}. Ella se asocia con cualquier caracter sencillo dentro de +la clase. La clase se finaliza con un caracter ``\texttt{]}''. Una +clase puede contener múltiples \emph{rango}s de la forma +``\texttt{a-f}'', que en este caso es una abreviación para +``\texttt{abcdef}''. +\interaction{filenames.glob.range} +Si el primer caracter en aparecer después de ``\texttt{[}'' en la +clase de caracteres es un ``\texttt{!}'', se \emph{niega} la clase, +haciendo que se asocie con cualquier caracter sencillo que no se +encuentre en la clase. + +Un ``\texttt{\{}'' 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 ``\texttt{,}'' separa los subpatrones, +y el ``\texttt{\}}'' finaliza el grupo. +\interaction{filenames.glob.group} + +\subsubsection{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 ``\texttt{*}'', ya que éste sólo generará asociaciones dentro +de un solo directorio. En vez de eso, use el caracter para asociar con +cualquier cadena ``\texttt{**}''. Este pequeño ejemplo ilustra la +diferencia entre los dos. +\interaction{filenames.glob.star-starstar} + +\subsection{Asociación con patrones de expresiones regulares \texttt{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 \dirname{foo}, si desea +asociar ficheros en este directorio, su patrón debe empezar con +``\texttt{foo/}''. + +Un detalle a tener en cuenta es que, si le son familiares las +expresiones regulares al estilo Perl, las de Mercurial están +\emph{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 ``\texttt{.*}''. + +\section{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 \emph{filtros}. Los comandos que operan con nombres de fichero +aceptan dos opciones de filtrado. +\begin{itemize} +\item \hggopt{-I}, o \hggopt{--include}, le permite especificar un + patrón con el que deben coincidir los ficheros para ser + procesados. +\item \hggopt{-X}, o \hggopt{--exclude}, le brinda una manera de + \emph{evitar} procesar ficheros, si coinciden con este patrón. +\end{itemize} +Usted puede pasar múltiples veces las opciones \hggopt{-I} y +\hggopt{-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 \hggopt{-I} puede verse como un ``procese todos los ficheros +que coincidan con este filtro''. +\interaction{filenames.filter.include} +El filtro \hggopt{-X} puede verse como ``procese únicamente los +ficheros que no coincidan con este patrón''. +\interaction{filenames.filter.exclude} + +\section{Ignorar ficheros y directorios no deseados} + +XXX. + +\section{Sensibilidad a mayúsculas} +\label{sec:names:case} + +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 +%TODO FIXME seguir desde aqui, no tengo idea de como traducir case +%sensitivity +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 +\emph{case} of characters in file and directory names. There are +three common ways to handle case in names. +\begin{itemize} +\item Completely case insensitive. Uppercase and lowercase versions + of a letter are treated as identical, both when creating a file and + during subsequent accesses. This is common on older DOS-based + systems. +\item Case preserving, but insensitive. When a file or directory is + created, the case of its name is stored, and can be retrieved and + displayed by the operating system. When an existing file is being + looked up, its case is ignored. This is the standard arrangement on + Windows and MacOS. The names \filename{foo} and \filename{FoO} + identify the same file. This treatment of uppercase and lowercase + letters as interchangeable is also referred to as \emph{case + folding}. +\item Case sensitive. The case of a name is significant at all times. + The names \filename{foo} and {FoO} identify different files. This + is the way Linux and Unix systems normally work. +\end{itemize} + +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. + +\subsection{Almacenamiento portable y seguro de repositorios} + +El mecanismo de almacenamiento de los repositorios en Mercurial es +\emph{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 + +\subsection{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 \filename{myfile.c} y el otro llamado \filename{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 (\hgcmd{update}) el directorio +de trabajo con ese conjunto de cambios, o hagan fusión (\hgcmd{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. + +\subsection{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 +(\hgcmd{update}) o fusionar (\hgcmd{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 \hgcmd{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 (\hgcmd{pull}) o empújelo (\hgcmd{push}) a su sistema Windows +o MacOS, y actualícelo (\hgcmd{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 (\hgcmd{update}) su directorio de trabajo a +dicho conjunto de cambios en un sistema Windows o MacOS, pero puede +continuar el desarrollo sin impedimentos. + +\begin{note} + 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. +\end{note} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/fixhtml.py Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,1 @@ +../en/fixhtml.py \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/fixsvg Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,1 @@ +../en/fixsvg \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/hgbook.css Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,1 @@ +../en/hgbook.css \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/hgext.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,441 @@ +\chapter{Añadir funcionalidad con extensiones} +\label{chap:hgext} + +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 +\emph{extensiones} (aveces llamadas \emph{añadidos}\ndt{plugins}). Ya +hemos discutido algunas de estas extensiones en capítulos anteriores: +\begin{itemize} +\item La sección~\ref{sec:tour-merge:fetch} cubre la extensión + \hgext{fetch}; que combina jalar cambios y fusionarlos con los + cambios locales en una sola orden: \hgxcmd{fetch}{fetch}. +\item En el capítulo~\ref{chap:hook}, cubrimos muchas extensiones que + son útiles en funcionalidades relacionadas con ganchos: Los + \hgext{acl} añaden listas de control de acceso; \hgext{bugzilla} + añade integración con el sistema de seguimiento de fallos Bugzilla; y + \hgext{notify} envía notificaciones por correo de nuevos cambios. +\item La extensión de administración de parches MQ es tan invaluable + que amerita dos capítulos y un apéndice por sí misma. + El capítulo~\ref{chap:mq} cubre lo básico; el + capítulo~\ref{chap:mq-collab} discute temas avanzados; y el + apéndice~\ref{chap:mqref} muestra en detalle cada orden. +\end{itemize} + +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. +\begin{itemize} +\item En la sección~\ref{sec:hgext:inotify}, discutiremos la + posibilidad de mejorar el desempeño \emph{en gran medida} con la extensión + \hgext{inotify}. +\end{itemize} + +\section{Mejorar el desempeño con la extensión \hgext{inotify}} +\label{sec:hgext: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 \hgcmd{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 \hgcmd{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 \hgcmd{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 \hgcmd{status}, a pesar de que \emph{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 \texttt{inotify}. + +La extensión \hgext{inotify} habla con el componente \texttt{inotify} +del núcleo para optimizar las órdenes de \hgcmd{status}. La extensión +tiene dos componentes. Un daemonio está en el fondo recibiendo +notificaciones del subsistema \texttt{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 +\hgcmd{status} de Mercurial sobre un repositorio de 150.000 +ficheros. Con la extensión \hgext{inotify} habilitada, el tiempo se +disipó a 0.1~seconds, un factor \emph{cien veces} más rápido. + +Antes de continuar, tenga en cuenta algunos detalles: +\begin{itemize} +\item La extensión \hgext{inotify} es específica de Linux. Porque se + enlaza directamente con el subsistema \texttt{inotify} del núcleo + Linux, no funciona en otros sistemas operativos. +\item Debería funcionar en cualquier distribución Linux a partir de + comienzos del 2005. Las distribuciones más antiguas deben tener un + kernel sin \texttt{inotify}, o una versión de \texttt{glibc} que no + tiene necesariamente el soporte para la interfaz. +\item No todos los sistemas de ficheros pueden usarse con la extensión + \hgext{inotify}. Los sistemas de ficheros tales como NFS no lo + soportan, por ejemplo, si está corriendo Mercurial en vaios + sistemas, montados todos sobre el mismo sistema de ficheros en red. + El sistema \texttt{inotify} del kernel no tiene forma de saber + acerca de los cambios hechos en otro sistema. La mayoría de + sistemas de ficheros locales (p.e.~ext3, XFS, ReiserFS) deberían + funcionar bien. +\end{itemize} + +Hacia mayo de 2007 la extensión \hgext{inotify} no venía de forma +predeterminada en Mercurial\ndt{Desde el 2008 para kernels 2.6 viene + en Mercurial, pero no está activada de forma predeterminada}, 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 \texttt{inotify}. +\begin{note} + Hay \emph{dos} librerías de enlace de Python hacia \texttt{inotify}. + Una de ellas se llama \texttt{pyinotify}, y en algunas + distribuciones de Linux se encuentra como \texttt{python-inotify}. + Esta es la que \emph{no} necesita, puesto que tiene muchos fallos, + y es ineficiente para ser práctica. +\end{note} +Para comenzar, es mejor tener una copia de Mercurial funcional +instalada: +\begin{note} + Si sigue las instrucciones a continuación, estará + \emph{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! +\end{note} +\begin{enumerate} +\item Clone el repositorio de interfaz entre Python e + \texttt{inotify}. Ármelo e instálelo: + \begin{codesample4} + hg clone http://hg.kublai.com/python/inotify + cd inotify + python setup.py build --force + sudo python setup.py install --skip-build + \end{codesample4} +\item Clone el repositorio \dirname{crew} de Mercurial. Clone el + repositorio de parches de \hgext{inotify} de forma tal que las colas + de Mercurial puedan aplicar los parches sobre el repositorio \dirname{crew}. + \begin{codesample4} + hg clone http://hg.intevation.org/mercurial/crew + hg clone crew inotify + hg clone http://hg.kublai.com/mercurial/patches/inotify inotify/.hg/patches + \end{codesample4} +\item Asegúrese de instalar la extensión Colas de Mercurial + \hgext{mq} y que estén habilitadas. Si nunca ha usado MQ, lea la + sección~\ref{sec:mq:start} para poder comenzar rápidamente. +\item Vaya al repositorio de \dirname{inotify} y aplique todos los + parches de \hgext{inotify} con la opción \hgxopt{mq}{qpush}{-a} de + la orden \hgxcmd{mq}{qpush}. + \begin{codesample4} + cd inotify + hg qpush -a + \end{codesample4} + Si obtiene un mensaje de error de \hgxcmd{mq}{qpush}, no debería + continuar. Mejor pida ayuda. +\item Arme e instale la versión parchada de Mercurial. + \begin{codesample4} + python setup.py build --force + sudo python setup.py install --skip-build + \end{codesample4} +\end{enumerate} +Una vez que haya armado una versión funcional parchada de Mercurial, +todo lo que necesita es habilitar la extensión \hgext{inotify} +colocando una entrada en su \hgrc. +\begin{codesample2} + [extensions] + inotify = +\end{codesample2} +Cuando la extensión \hgext{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 \hgext{inotify} y ejecuta unos pocos comandos +en diferentes repositorios, verá que hay algunos procesos de +\texttt{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 \hgext{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, +\emph{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 \hgext{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 +\hgxcmd{inotify}{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 \hgext{inotify}. + +Cuando esté usando la extensión \hgext{inotify}, +\emph{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. + +\section{Soporte flexible de diff con la extensión \hgext{extdiff}} +\label{sec:hgext:extdiff} + +La orden predeterminada \hgcmd{diff} de Mercurial despliega diffs en +texto plano unificadas. +\interaction{extdiff.diff} +Si dese emplear una herramienta externa para desplegar las +modificaciones, querrá usar la extensión \hgext{extdiff}. Esta le +permitirá usar por ejemplo una herramienta gráfica de diff. + +La extensión \hgext{extdiff} viene con Mercurial, y es fácil +configurar. En la sección \rcsection{extensions} de su \hgrc, +basta con añadir una entrada de una línea para habilitar la extensión. +\begin{codesample2} + [extensions] + extdiff = +\end{codesample2} +Esto introduce una orden llamada \hgxcmd{extdiff}{extdiff}, que de +forma predeterminada usa su orden del sistema \command{diff} para +generar un diff unificado de la misma forma que lo hace el comando +predeterminado \hgcmd{diff}. +\interaction{extdiff.extdiff} +El resultado no será exactamente el mismo que con la orden interna +\hgcmd{diff}, puesto que la salida de \command{diff} varía de un +sistema a otro, incluso pasando las mismas opciones. + +Como lo indican las líneas``\texttt{making snapshot}'', la orden +\hgxcmd{extdiff}{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 +\hgxcmd{extdiff}{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 +\dirname{/quux/bar/foo}, \dirname{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 +\texttt{a631aca1083f}, el directorio se llamará +\dirname{foo.a631aca1083f}. Una instantánea del directorio de trabajo +no tendrá el identificador del conjunto de cambios, y por lo tanto +será solamente \dirname{foo} en este ejemplo. Para ver cómo luce en +la práctica, veamos de nuevo el ejemplo \hgxcmd{extdiff}{extdiff} +antes mencionado. Tenga en cuenta que los diffs tienen los nombres de +las instantáneas de directorio dentro de su encabezado. + +La orden \hgxcmd{extdiff}{extdiff} acepta dos opciones importantes. +La opción \hgxopt{extdiff}{extdiff}{-p} le permite elegir un programa +para ver las diferencias, en lugar de \command{diff}. Con la opción +\hgxopt{extdiff}{extdiff}{-o} puede cambiar las opciones que +\hgxcmd{extdiff}{extdiff} pasa a tal programa (de forma predeterminada +las opciones son``\texttt{-Npru}'', que tienen sentido únicamente si +está usando \command{diff}). En otros aspectos, la orden +\hgxcmd{extdiff}{extdiff} actúa de forma similar a como lo hace la +orden \hgcmd{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 \command{diff}, +para lograr que se generen diferencias de contexto (con la opción +\cmdopt{diff}{-c}) en lugar de diferencias unificadas, y cinco líneas +de contexto en lugar de las tres predeterminadas (pasando \texttt{5} +como argumento a la opción \cmdopt{diff}{-C}). +\interaction{extdiff.extdiff-ctx} + +Es sencillo lanzar unas herramienta usual de diferencias. Para lanzar +el visor \command{kdiff3}: +\begin{codesample2} + hg extdiff -p kdiff3 -o '' +\end{codesample2} + +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 \hgext{mq} junto con la orden +\command{interdiff} está en la sección~\ref{mq-collab:tips:interdiff}. + +\subsection{Definición de alias de comandos} + +Acordarse de todas las opciones de las órdenes +\hgxcmd{extdiff}{extdiff} y el visor de diferencias de su preferencia +puede ser dispendioso, y por lo tanto la extensión \hgext{extdiff} le +permite definir \emph{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 +\rcsection{extdiff}. Dentro de esta sección puede definir varias +órdenes. Mostraremos como añadir la orden \texttt{kdiff3}. Después de +definido, puede teclear ``\texttt{hg kdiff3}'' y la extensión a +\hgext{extdiff} ejecutará la orden \command{kdiff3}. +\begin{codesample2} + [extdiff] + cmd.kdiff3 = +\end{codesample2} +Si deja vacía la porción derecha de la definición, como en el ejemplo, +la extensión \hgext{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 + ``\texttt{hg wibble}'', que ejecuta \command{kdiff3}. +\begin{codesample2} + [extdiff] + cmd.wibble = kdiff3 +\end{codesample2} + +También puede especificar las opciones predeterminadas con las cuales +desea invocar el visor de diferencias. Se usa el prefijo ``\texttt{opts.}'', +seguido por el nombre de la orden a la cual se aplican las opciones. +En este ejemplos se define la orden ``\texttt{hg vimdiff}'' que +ejecuta la extensión \texttt{DirDiff} del editor \command{vim}. +\begin{codesample2} + [extdiff] + cmd.vimdiff = vim + opts.vimdiff = -f '+next' '+execute "DirDiff" argv(0) argv(1)' +\end{codesample2} + +\section{Uso de la extensión \hgext{transplant} para seleccionar} +\label{sec:hgext:transplant} + +Need to have a long chat with Brendan about this. + +\section{Enviar cambios vía correo electrónico con la extensión \hgext{patchbomb}} +\label{sec:hgext: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 \hgext{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 +\hgext{patchbomb} consta de una o dos líneas en su \hgrc. +\begin{codesample2} + [extensions] + patchbomb = +\end{codesample2} +Cuando haya habilitado la extensión, dispondrá de una nueva orden, +llamada \hgxcmd{patchbomb}{email}. + +La forma mejor y más segura para invocar la orden +\hgxcmd{patchbomb}{email} es ejecutarla \emph{siempre} con la opción +\hgxopt{patchbomb}{email}{-n}; que le mostrará lo que la orden +\emph{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 \hgxopt{patchbomb}{email}{-n}. + +La orden \hgxcmd{patchbomb}{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 \texttt{punta}, inclusive. +\begin{codesample2} + hg email -n 7:tip +\end{codesample2} +También puede especificar un \emph{repositorio} para comparar. Si +indica un repositoro sin revisiones, la orden \hgxcmd{patchbomb}{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 +\hgxopt{patchbomb}{email}{-b}), respetará las revisiones enviadas. + +Ejecutar la orden \hgxcmd{patchbomb}{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--\texttt{readline} aumentadas cuando ingrese tales encabezados, +lo cual es sumamente útil.) + +Cuando envíe una sola revisión, la orden \hgxcmd{patchbomb}{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 \hgxcmd{patchbomb}{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. + +\subsection{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 \hgext{patchbomb} intenta acomodarse +a diferentes variaciones gracias a las opciones de la línea de órdenes: +\begin{itemize} +\item Puede escribir un tema para el mensaje introductorio en la línea + de órdenes con la opciń \hgxopt{patchbomb}{email}{-s}. Toma un + argumento: el tema del mensaje a usar. +\item Para cambiar el correo electrónico del campo del cual se + origina, use la opción \hgxopt{patchbomb}{email}{-f}. Toma un + argumento, el correo electrónico a usar. +\item El comportamiento predeterminado es enviar diferencias + unificadas (consulte la sección~\ref{sec:mq:patch} si desea una + descripción del formato), una por mensaje. Puede enviar un conjunto + binario\ndt{binary bundle} con la opción \hgxopt{patchbomb}{email}{-b}. +\item Las diferencias unificadas están precedidas por un encabezado de + metadatos. Puede omitirlo, y enviar diferencias sin adornos con la + opción \hgxopt{patchbomb}{email}{--plain}. +\item Las diferencias usualmente se envían ``en línea'', como parte + del cuerpo del mensaje con la descripción del parche. Que facilita a + a la mayor cantidad de lectores citar y responder partes de un diff, + dado que algunos clientes de correo solamente citarán la primera + parte MIME del cuerpo de un mensaje. Si prefiere enviar la + descripción y el diff en partes separadas del cuerpo, use la opción + \hgxopt{patchbomb}{email}{-a}. +\item En lugar de enviar mensajes de correo puede escribirlos a un + fichero con formato-\texttt{mbox}- con la opción + \hgxopt{patchbomb}{email}{-m}. La opción recibe un argumento, el + nombre del fichero en el cual escribir. +\item Si desea añadir un resumen con formato-\command{diffstat} en + cada parche, y uno como mensaje introductorio, use la opción + \hgxopt{patchbomb}{email}{-d}. La orden \command{diffstat} + despliega una tabla que contiene el nombre de cada fichero parchado, + el número de líneas afectadas, y un historgrama mostrando cuánto ha + sido modificado cada fichero. Lo cual ofrece a los lectores una + mirada cuantitativa de cuan complejo es el parche. +\end{itemize} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/hook.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,1568 @@ +\chapter{Manejo de eventos en repositorios mediante ganchos} +\label{chap:hook} + +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 \emph{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. + +\section{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~\ref{sec:hook:ref}. + +\begin{itemize} +\item[\small\hook{changegroup}] Es ejecutado luego de que un grupo de + conjuntos de cambios ha sido traído al repositorio desde algún + otro sitio. +\item[\small\hook{commit}] Es ejecutado después de la creación de + un conjunto de cambios en el repositorio local. +\item[\small\hook{incoming}] Es ejecutado una vez por cada conjunto de + cambios traído al repositorio desde otra ubicación. Note la + diferencia respecto al gancho \hook{changegroup}, que es ejecutado + una vez por cada \emph{grupo} de conjuntos de cambios que se + traiga. +\item[\small\hook{outgoing}] Es ejecutado luego de que un grupo de + conjuntos de cambios ha sido transmitido desde el repositorio. +\item[\small\hook{prechangegroup}] Es ejecutado antes de iniciar la + recepción de un grupo de conjuntos de cambios en el repositorio. +\item[\small\hook{precommit}] De control. Es ejecutado antes de + iniciar una consignación. +\item[\small\hook{preoutgoing}] De control. Es ejecutado antes de + iniciar la transmisión de un grupo de conjuntos de cambios desde + el repositorio. +\item[\small\hook{pretag}] De control. Es ejecutado antes de crear una + etiqueta. +\item[\small\hook{pretxnchangegroup}] De control. Es ejecutado después + de haber recibido un grupo de conjuntos de cambios en el + repositorio local, pero antes de que la transacción se complete y + los cambios sean permanentes dentro del repositorio. +\item[\small\hook{pretxncommit}] De control. Es ejecutado luego de la + creación de un conjunto de cambios en el repositorio local, pero + antes de que la transacción que hace permanente el cambio sea + completada. +\item[\small\hook{preupdate}] De control. Es ejecutado antes de + iniciar una actualización o fusión en el directorio de trabajo. +\item[\small\hook{tag}] Es ejecutado después de la creación de una + etiqueta. +\item[\small\hook{update}] Es ejecutado después de que termina una + actualización o una fusión. +\end{itemize} +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. + +\section{Ganchos y seguridad} + +\subsection{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 +\emph{su} sistema, en \emph{su} cuenta de usuario, con \emph{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 +%TODO acá introduzco algo de texto por mi cuenta, por claridad +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 (\hgcmd{pull}) desde ese repositorio, y el +\sfilename{.hg/hgrc} define un gancho saliente (\hook{outgoing}), +dicho gancho se ejecuta bajo su cuenta de usuario, aun cuando usted no +es el propietario del repositorio. + +\begin{note} + 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 (\hook{outgoing}) se ejecutará bajo + la cuenta que está ejecutando el proceso servidor, en el servidor. +\end{note} + +XXX Para ver qué ganchos han sido definidos en un repositorio, use el +comando \hgcmdargs{config}{hooks}. Si usted está trabajando en un +repositorio, pero comunicándose con otro que no le pertenece +(por ejemplo, usando \hgcmd{pull} o \hgcmd{incoming}), recuerde que +los ganchos que debe considerar son los del otro repositorio, no los +del suyo. + +\subsection{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 +%TODO subvertir +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. + +\subsection{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. + +\subsection{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 + +\section{Precauciones con ganchos \texttt{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 \emph{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 \emph{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 (\hook{pretxncommit} y +\hook{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. + +\subsection{Ilustración del problema} + +En principio, un buen uso del gancho \hook{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 \emph{unidireccional}. Permita que +reciba cambios desde el exterior, pero no permita que nadie jale +cambios de él (use el gancho \hook{preoutgoing} para bloquear esto). +Configure un gancho \hook{changegroup} para que si el ensamblaje o +prueba tiene éxito, el gancho empuje los nuevos cambios a otro +repositorio del que la gente \emph{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 \emph{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. + +\section{Tutorial corto de uso de ganchos} +\label{sec:hook:simple} + +Escribir un gancho para Mercurial es fácil. Empecemos con un gancho +que se ejecute cuando usted termine un \hgcmd{commit}, y simplemente +muestre el hash del conjunto de cambios que usted acaba de crear. El +gancho se llamará \hook{commit}. + +\begin{figure}[ht] + \interaction{hook.simple.init} + \caption{Un gancho simple que se ejecuta al hacer la consignación de + un conjunto de cambios} + \label{ex:hook:init} +\end{figure} + +Todos los ganchos siguen el patrón del ejemplo~\ref{ex:hook:init}. +Usted puede añadir una entrada a la sección \rcsection{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 \envar{HG\_NODE} en el ejemplo). + +\subsection{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~\ref{ex:hook:ext}. +Mercurial le permite hacer esto añadiendo una \emph{extensión} al +final del nombre de un gancho. Usted extiende el nombre del gancho +%TODO Yuk, no me gusta ese "parada completa" +poniendo el nombre del gancho, seguido por una parada completa (el +caracter ``\texttt{.}''), seguido de algo más de texto de su elección. +Por ejemplo, Mercurial ejecutará tanto \texttt{commit.foo} como +\texttt{commit.bar} cuando ocurra el evento \texttt{commit}. + +\begin{figure}[ht] + \interaction{hook.simple.ext} + \caption{Definición de un segundo gancho \hook{commit}} + \label{ex:hook:ext} +\end{figure} + +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 \texttt{commit.bar} se ejecutará antes que +\texttt{commit.foo}, y \texttt{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~\ref{sec:hook:perm}). + +\subsection{Controlar cuándo puede llevarse a cabo una actividad} +\label{sec:hook:perm} + +En los ejemplos anteriores, usamos el gancho \hook{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 \hook{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 \hook{pretxncommit} tiene la +capacidad de decidir si una transacción se completa, o debe +deshacerse. + +Si el gancho \hook{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 \hook{commit} es ejecutado. Si el gancho +\hook{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 \hook{commit} no es ejecutado. + +\begin{figure}[ht] + \interaction{hook.simple.pretxncommit} + \caption{Uso del gancho \hook{pretxncommit} para controlar consignaciones} + \label{ex:hook:pretxncommit} +\end{figure} + +El gancho en el ejemplo~\ref{ex:hook:pretxncommit} 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. + +\section{Escribir sus propios ganchos} + +Cuando usted escriba un gancho, puede encontrar útil el ejecutar +Mercurial o bien pasándole la opción \hggopt{-v}, o con el valor de +configuración \rcitem{ui}{verbose} fijado en ``true'' (verdadero). +Cuando lo haga, Mercurial imprimirá un mensaje antes de llamar cada +gancho. + +\subsection{Escoger cómo debe ejecutarse su gancho} +\label{sec:hook:lang} + +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. + +\subsection{Parámetros para ganchos} +\label{sec:hook:param} + +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 \texttt{foo}, el argumento de +palabra clave para un gancho en Python también se llamará +\texttt{foo}, mientras que la variable de entorno para un gancho +externo se llamará \texttt{HG\_FOO}. + +\subsection{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 +% TODO me suena mejor "no permitir" que "denegar" +no-cero/verdadero/excepción quiere decir ``no permitir''. + +\subsection{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 +``\texttt{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. + +\subsection{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 ``\texttt{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 \envar{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. +\begin{codesample2} + [hooks] + commit.example = python:mymodule.submodule.myhook +\end{codesample2} +Cuando Mercurial ejecuta el gancho \texttt{commit.example}, importa +\texttt{mymodule.submodule}, busca el objeto invocable llamado +\texttt{myhook}, y lo invoca (llama). + +\subsection{Escribir un gancho interno} + +El gancho interno más sencillo no hace nada, pero ilustra la +estructura básica de la API\ndt{\emph{Application Progamming +Interface}, Interfaz para Programación de Aplicaciones} para ganchos: +\begin{codesample2} + def myhook(ui, repo, **kwargs): + pass +\end{codesample2} +El primer argumento para un gancho Python siempre es un objeto +\pymodclass{mercurial.ui}{ui}. El segundo es un objeto repositorio; +de momento, siempre es una instancia de +\pymodclass{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 \texttt{**kwargs}. + +\section{Ejemplos de ganchos} + +\subsection{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 \hook{pretxncommit} de la +figura~\ref{ex:hook:msglen.go} evitará que usted consigne un conjunto +de cambios con un mensaje de menos de 10 bytes de longitud. + +\begin{figure}[ht] + \interaction{hook.msglen.go} + \caption{Un gancho que prohíbe mensajes de consignación demasiado + cortos} + \label{ex:hook:msglen.go} +\end{figure} + +\subsection{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 +%TODO dictum => regla +``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 \hook{precommit} o +\hook{pretxncommit} para revisar si tiene el problema de los espacios +en blanco finales. Si usa el gancho \hook{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 \filename{foo}, y el fichero \filename{bar} contiene +espacios en blanco finales, hacer la revisión en el gancho +\hook{precommit} evitará que usted haga la consignación de +\filename{foo} debido al problem en \filename{bar}. Este no parece el +enfoque adeucado. + +Si usted escogiera el gancho \hook{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 +%TODO roll-back +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 \hgcmd{commit} de nuevo. + +\begin{figure}[ht] + \interaction{hook.ws.simple} + \caption{Un gancho simple que revisa si hay espacios en blanco + finales} + \label{ex:hook:ws.simple} +\end{figure} + +La figura~\ref{ex:hook:ws.simple} presenta un gancho +\hook{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. + +\begin{figure}[ht] + \interaction{hook.ws.better} + \caption{Un mejor gancho para espacios en blanco finales} + \label{ex:hook:ws.better} +\end{figure} + +El ejemplo de la figura~\ref{ex:hook:ws.better} 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 +\hgcmdargs{commit}{\hgopt{commit}{-l}~\emph{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~\ref{ex:hook:ws.better} el +%TODO on-site => in-situ ? +uso de la característica de edición \emph{in-situ} de \command{perl} +para eliminar los espacios en blanco finales en un fichero. Esto es +lo suficientemente conciso y poderoso para que lo presente aquí. +% TODO corregí el backslash, y comprobé por mi cuenta un archivo +% aparte, y el comando hace lo que debe hacer. Favor copiar del pdf el +% comando perl y comprobar con un archivo con espacios en blanco +% finales, y si todo está bien (que debería), borrar esta nota +\begin{codesample2} + perl -pi -e 's,\textbackslash{}s+\$,,' nombre\_fichero +\end{codesample2} + +\section{Ganchos adicionales} + +Mercurial se instala con varios ganchos adicionales. Usted puede +encontrarlos en el directorio \dirname{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 +\dirname{hgext} en donde su instalador de paquetes haya puesto a +Mercurial. + +\subsection{\hgext{acl}---control de acceso a partes de un repositorio} + +La extensión \hgext{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, \emph{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. + +\subsubsection{Configuración del gancho \hook{acl}} + +Para administrar los conjuntos de cambios entrantes, se debe usar el +gancho \hgext{acl} como un gancho de tipo \hook{pretxnchangegroup}. +Esto le permite ver qué ficheros son modificados por cada conjunto de +%TODO rollback => "deshacer el efecto" +cambios entrante, y deshacer el efecto de un grupo de conjuntos de +cambios si alguno de ellos modifica algún fichero ``prohibido''. +Ejemplo: +\begin{codesample2} + [hooks] + pretxnchangegroup.acl = python:hgext.acl.hook +\end{codesample2} + +La extensión \hgext{acl} es configurada mediante tres secciones. + +La sección \rcsection{acl} sólo tiene una entrada, +\rcitem{acl}{sources}\ndt{Fuentes.}, 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. +\begin{itemize} + \item[\rcitem{acl}{serve}] Controlar conjuntos de + cambios entrantes que están llegando desde un repositorio a + través de http o ssh. Este es el valor por defecto de + \rcitem{acl}{sources}, y usualmente es el único valor de + configuración que necesitará para este ítem. +\item[\rcitem{acl}{pull}] Controlar conjuntos de cambios entrantes que + lleguen vía un pull (jalado) desde un repositorio local. +\item[\rcitem{acl}{push}] Controlar conjuntos de cambios entrantes que + lleguen vía un push (empuje) desde un repositorio local. +\item[\rcitem{acl}{bundle}] Controlar conjuntos de cambios entrantes + %TODO bundle + que lleguen desde otro repositorio a través de un paquete. +\end{itemize} + +La sección \rcsection{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 \rcsection{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 \rcsection{acl.allow} y +\rcsection{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 \texttt{escritordoc} sólo puede +empujar cambios al directorio \dirname{docs} del repositorio, mientras +que \texttt{practicante} puede enviar cambios a cualquier fichero o +directorio excepto \dirname{fuentes/sensitivo}. +\begin{codesample2} + [acl.allow] + docs/** = escritordoc + + [acl.deny] + fuentes/sensitivo/** = practicante +\end{codesample2} + +\subsubsection{Pruebas y resolución de problemas} + +Si usted desea probar el gancho \hgext{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 \hggopt{--debug}, no olvide que usted puede +habilitar la salida de depuración en su \hgrc: +\begin{codesample2} + [ui] + debug = true +\end{codesample2} +Con esto habilitado, el gancho \hgext{acl} imprimirá suficiente +información para permitirle saber porqué está permitiendo o denegando +la operación de empujar a usuarios específicos. + +\subsection{\hgext{bugzilla}---integración con Bugzilla} + +La extensión \hgext{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): +%TODO traducir? +\begin{codesample2} + Changeset aad8b264143a, made by Joe User <joe.user@domain.com> in + the frobnitz repository, refers to this bug. + + For complete details, see + http://hg.domain.com/frobnitz?cmd=changeset;node=aad8b264143a + + Changeset description: + Fix bug 10483 by guarding against some NULL pointers +\end{codesample2} +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: +\begin{itemize} +\item Requerir que cada conjunto de cambios tenga un ID de fallo en su + mensaje de consignación. En este caso, usted querrá configurar el + gancho como uno de tipo \hook{pretxncommit}. Esto le permitirá al + gancho rechazar cambios que no contiene IDs de fallos. +\item Permitir a los conjuntos de cambios entrantes modificar + automáticamente el \emph{estado} de un fallo, así como simplemente + añadir un comentario. Por ejemplo, el gancho podría reconocer la + cadena ``corregido fallo 31337'' como la señal de que debería + actualizar el estado del fallo 31337 a ``requiere pruebas''. +\end{itemize} + +\subsubsection{Configuración del gancho \hook{bugzilla}} +\label{sec:hook:bugzilla:config} + +Usted debería configurar este gancho en el \hgrc\ de su servidor como +un gancho \hook{incoming}\ndt{Entrante.}, por ejemplo como sigue: +\begin{codesample2} + [hooks] + incoming.bugzilla = python:hgext.bugzilla.hook +\end{codesample2} + +Debido a la naturaleza especializada de este gancho, y porque Bugzilla +no fue escrito con este tipo de integración en mente, configurar este +% TODO involved => complejo ? no intarwebs here :( +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~\cite{web:mysql-python}. + +La información para configurar este gancho se ubica en la sección +\rcsection{bugzilla} de su \hgrc. +\begin{itemize} +\item[\rcitem{bugzilla}{version}] La versión de Bugzilla instalada en + el servidor. El esquema de base de datos que Bugzilla usa cambia + ocasionalmente, así que este gancho debe saber exactamente qué + esquema usar. A la fecha, la única versión soportada es la + \texttt{2.16}. +\item[\rcitem{bugzilla}{host}] El nombre de máquina (\emph{hostname}) + del servidor MySQL que almacena sus datos Bugzilla. La base de datos + debe ser configurada para permitir conexiones desde las máquinas en + las que usted ejecute el gancho \hook{bugzilla}. +\item[\rcitem{bugzilla}{user}] El nombre de usuario que se usará para + conectarse al servidor MySQL. La base de datos debe ser configurada + para permitir a dicho usuario conectarse desde cualquiera de las + máquinas en las que se ejecute el gancho \hook{bugzilla}. Este + usuario debe tener acceso y poder modificar las tablas de Bugzilla. + El valor por defecto para este ítem es \texttt{bugs}, que es el + nombre estándar del usuario para Bugzilla en una base de datos + MySQL. +\item[\rcitem{bugzilla}{password}] La contraseña MySQL para el usuario + configurado anteriormente. Ésta es almacenada como texto plano, así + que usted deberá asegurarse de que los usuarios no autorizados no + puedan leer el fichero \hgrc\ en donde usted guarda esta + información. +\item[\rcitem{bugzilla}{db}] El nombre de la base de datos Bugzilla en + el servidor MySQL. El nombre por defecto para este ítem es + \texttt{bugs}, que es el nombre estándar de la base de datos MySQL + en donde Bugzilla almacena sus datos. +\item[\rcitem{bugzilla}{notify}] Si usted desea que Bugzilla envíe un + %TODO suBscriptores? + correo de notificación a los suscriptores después de que el gancho + haya añadido un comentario a un fallo, necesitará que este gancho + ejecute un comando siempre que actualice la base de datos. El + comando que se ejecute depende de en dónde haya sido instalado + Bugzilla, pero típicamente se verá así, si usted ha instalado + Bugzilla en \dirname{/var/www/html/bugzilla}: + \begin{codesample4} + cd /var/www/html/bugzilla && ./processmail %s nobody@nowhere.com + \end{codesample4} + El programa \texttt{processmail} de Bugzilla espera recibir un ID de + fallo (el gancho reemplaza ``\texttt{\%s}'' por el ID del fallo) y + una dirección de correo. También espera poder escribir a ciertos + ficheros en el directorio en que se ejecuta. Si Bugzilla y éste + gancho no están instalados en la misma máquina, usted deberá + encontrar una manera de ejecutar \texttt{processmail} en el servidor + donde está instalado Bugzilla. +\end{itemize} + +\subsubsection{Asociar nombres de consignadores a nombres de usuario +Bugzilla} + +Por defecto, el gancho \hgext{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 \rcsection{usermap}. + +Cada ítem en la sección \rcsection{usermap} contiene una dirección de +correo electrónico a la izquierda, y un nombre de usuario Bugzilla a +la derecha. +\begin{codesample2} + [usermap] + jane.user@example.com = jane +\end{codesample2} +Usted puede mantener los datos de \rcsection{usermap} en un fichero +\hgrc, o decirle al gancho \hgext{bugzilla} que lea la información +desde un fichero \filename{usermap} externo. En este caso, usted +puede almacenar los datos de \filename{usermap} en (por ejemplo) un +repositorio modificable por los usuarios. Esto hace posible para sus +usuarios mantener sus propias entradas \rcitem{bugzilla}{usermap}. El +fichero \hgrc\ principal se vería así: +\begin{codesample2} + # fichero hgrc normal se refiere a un fichero usermap externo + [bugzilla] + usermap = /home/hg/repos/userdata/bugzilla-usermap.conf +\end{codesample2} +Mientras que el fichero \filename{usermap} al que se hace referencia +se vería así: +\begin{codesample2} + # bugzilla-usermap.conf - dentro de un repositorio hg + [usermap] + stephanie@example.com = steph +\end{codesample2} + +\subsubsection{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 \rcsection{bugzilla}) controlan este +comportamiento. +\begin{itemize} +\item[\texttt{strip}] La cantidad de elementos iniciales de ruta a + remover de un nombre de ruta del repositorio para construir una ruta + parcial para una URL. Por ejemplo, si los repositorios en su + servidor se ubican en \dirname{/home/hg/repos}, y usted tiene un + repositorio cuya ruta es \dirname{/home/hg/repos/app/tests}, + entonces fijar \texttt{strip} a \texttt{4} resultará en una ruta + parcial de \dirname{app/tests}. El gancho hará disponible esta ruta + parcial cuando expanda una plantilla, como \texttt{webroot}. +\item[\texttt{template}] El texto de la plantilla a usar. En adición a + las variables usuales relacionadas con conjuntos de cambios, esta + plantilla puede usar \texttt{hgweb} (el valor del ítem de + configuración \texttt{hgweb} de arriba) y \texttt{webroot} (la ruta + construida usando \texttt{strip} arriba). +\end{itemize} + +Adicionalmente, usted puede añadir un ítem \rcitem{web}{baseurl} a la +sección \rcsection{web} de su \hgrc. El gancho \hgext{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: +\begin{codesample2} + [web] + baseurl = http://hg.domain.com/ +\end{codesample2} + +A continuación se presenta un ejemplo completo de configuración para +el gancho \hgext{bugzilla}. +%TODO traducir? +\begin{codesample2} + [bugzilla] + host = bugzilla.example.com + password = mypassword + version = 2.16 + # server-side repos live in /home/hg/repos, so strip 4 leading + # separators + strip = 4 + hgweb = http://hg.example.com/ + usermap = /home/hg/repos/notify/bugzilla.conf + template = Changeset \{node|short\}, made by \{author\} in the \{webroot\} + repo, refers to this bug.\\nFor complete details, see + \{hgweb\}\{webroot\}?cmd=changeset;node=\{node|short\}\\nChangeset + description:\\n\\t\{desc|tabindent\} +\end{codesample2} + +\subsubsection{Pruebas y resolución de problemas} + +Los problemas más comunes que aparecen en la configuración del gancho +\hgext{bugzilla} suelen estar relacionados con la ejecución del guión +de Bugzilla \filename{processmail} y la asociación de nombres de +consignadores a nombres de usuario. + +Recuerde que en la sección~\ref{sec:hook:bugzilla:config} arriba el +usuario que ejecuta el proceso Mercurial en el servidor es también +el usuario que ejecutará el guión \filename{processmail}. El guión +\filename{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 \filename{processmail} sea ejecutado con la +identidad del usuario adecuado usando el comando \command{sudo}. A +continuación se presenta una entrada de ejemplo para un fichero +\filename{sudoers}. +\begin{codesample2} + hg_user = (httpd_user) NOPASSWD: /var/www/html/bugzilla/processmail-wrapper %s +\end{codesample2} +Esto permite que el usuario \texttt{hg\_user} ejecute el programa +\filename{processmail-wrapper} con la identidad del usuario +\texttt{httpd\_user}. + +Esta indirección a través de un guión envoltorio es necesaria, porque +\filename{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 \filename{sudoers}. +Los contenidos del giuón envoltorio son simples: +\begin{codesample2} + #!/bin/sh + cd `dirname $0` && ./processmail "$1" nobody@example.com +\end{codesample2} +No parece importar qué dirección de correo se le pase a +\filename{processmail}. + +Si su \rcsection{usermap} no es configurada correctamente, los +usuarios verán un mensaje de error del gancho \hgext{bugzilla} cuando +empujen cambios al servidor. El mensaje de error se verá así: +\begin{codesample2} + cannot find bugzilla user id for john.q.public@example.com +\end{codesample2} +Lo que esto quiere decir es que la dirección del consignador, +\texttt{john.q.public@example.com}, no es un nombre de usuario +Bugzilla válido, ni tiene una entrada en su \rcsection{usermap} que lo +asocie con un nombre de usuario válido Bugzilla. + +\subsection{\hgext{notify}---enviar notificaciones de correo +electrónico} + +%TODO feeds => notificaciones: lo más fácil es mirar en wikipedia +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 +\hgext{notify}\ndt{Notificación.} 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 \hgext{bugzilla}, el gancho +\hgext{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 \hgext{notify} incluye un diff de cada conjunto +%TODO que se envía? revisar, pienso que es ``que se recibe'' +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. + +\subsubsection{Configuración del gancho \hgext{notify}} + +Usted puede configurar el gancho \hgext{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). +\begin{codesample2} + [hooks] + # enviar un correo por grupo de cambios + changegroup.notify = python:hgext.notify.hook + # enviar un correo por cambio + incoming.notify = python:hgext.notify.hook +\end{codesample2} + +La información para configurar este gancho se ubica en la sección +\rcsection{notify} de un fichero \hgrc. +\begin{itemize} +\item[\rcitem{notify}{test}] Por defecto, este gancho no envía correos + en absoluto; en vez de eso, imprime el mensaje que se + \emph{enviaría}. Fije este ítem en \texttt{false} para permitir el + envío de correos. El motivo por el que el envío de correos está + desactivado es que hacen falta varios intentos para configurar esta + extensión exactamente como usted desea, y sería maleducado enviar a + los subscriptores una cantidad de notificaciones ``rotas'' mientras + usted depura su configuración. +\item[\rcitem{notify}{config}] La ruta a un fichero de configuración + que contiene información de subscripción. Esto se mantiene separado + del \hgrc\ principal para que usted pueda mantenerlo en un + repositorio. La gente puede clonar ese repositorio, actualizar sus + subscripciones, y empujar los cambios de vuelta a su servidor. +\item[\rcitem{notify}{strip}] La cantidad de caracteres iniciales de + separación de ruta a remover de la ruta del repositorio, al decidir + si un repositorio tiene subscriptores. Por ejemplo, si los + repositorios en su servidor están en \dirname{/home/hg/repos}, y + \hgext{notify} está trabajando con un repositorio llamado + \dirname{/home/hg/repos/shared/test}, fijar \rcitem{notify}{strip} a + \texttt{4} hará que \hgext{notify} elimine las partes iniciales de + la ruta hasta \dirname{shared/test}, y asociará los subscriptores + frente a dicha ruta. +\item[\rcitem{notify}{template}] El texto de plantilla a usar cuando + se envíen mensajes. Especifica los contenidos de la cabecera del + mensaje y el cuerpo del mismo. +\item[\rcitem{notify}{maxdiff}] El número máximo de líneas de datos de + diff a añadir al final de un mensaje. Si la longitud de un diff es + mayor a eso, se trunca. Por defecto, está fijado en 300. Fije esto a + \texttt{0} para omitir los diffs en los correos de notificación. +\item[\rcitem{notify}{sources}] Una lista de fuentes de conjuntos de + cambios a considerar. Esto le permite a usted indicar a + \hgext{notify} para que sólo envíe correos acerca de cambios que + usuarios remotos hayan empujado al repositorio vía un servidor, por + ejemplo. Vea la sección~\ref{sec:hook:sources} para las fuentes que + usted puede especificar aquí. +\end{itemize} + +Si usted fija el ítem \rcitem{web}{baseurl} en la sección +\rcsection{web}, usted lo puede usar en una plantilla; estará +disponible como \texttt{webroot}. + +A continuación se presenta un ejemplo completo de configuración para +el gancho \hgext{notify}. +\begin{codesample2} + [notify] + # enviar correo + test = false + # datos de subscriptores están en el repositorio notify + config = /home/hg/repos/notify/notify.conf + # repos están en /home/hg/repos on server, así que elimine 4 + # caracteres"/" + strip = 4 + template = X-Hg-Repo: \{webroot\} + Subject: \{webroot\}: \{desc|firstline|strip\} + From: \{author\} + + changeset \{node|short\} in \{root\} + details: \{baseurl\}\{webroot\}?cmd=changeset;node=\{node|short\} + description: + \{desc|tabindent|strip\} + + [web] + baseurl = http://hg.example.com/ +\end{codesample2} + +Esto producirá un mensaje que se verá como el siguiente: +\begin{codesample2} + X-Hg-Repo: tests/slave + Subject: tests/slave: Handle error case when slave has no buffers + Date: Wed, 2 Aug 2006 15:25:46 -0700 (PDT) + + changeset 3cba9bfe74b5 in /home/hg/repos/tests/slave + details: http://hg.example.com/tests/slave?cmd=changeset;node=3cba9bfe74b5 + description: + Handle error case when slave has no buffers + diffs (54 lines): + + diff -r 9d95df7cf2ad -r 3cba9bfe74b5 include/tests.h + --- a/include/tests.h Wed Aug 02 15:19:52 2006 -0700 + +++ b/include/tests.h Wed Aug 02 15:25:26 2006 -0700 + @@ -212,6 +212,15 @@ static __inline__ void test_headers(void *h) + [...snip...] +\end{codesample2} + +\subsubsection{Pruebas y resolución de problemas} + +No olvide que por defecto, la extensión \hgext{notify} \emph{no +enviará ningún correo electrónico} hasta que usted la configure +explícitamente para hacerlo, fijando el valor de \rcitem{notify}{test} +a \texttt{false}. Hasta que usted haga eso, simplemente se imprimirá +el mensaje que se \emph{enviaría}. + +\section{Información para escritores de ganchos} +\label{sec:hook:ref} + +\subsection{Ejecución de ganchos internos} + +Un gancho interno es llamado con argumentos de la siguiente forma: +\begin{codesample2} + def myhook(ui, repo, **kwargs): + pass +\end{codesample2} +El parámetro \texttt{ui} es un objeto \pymodclass{mercurial.ui}{ui}. +El parámetro \texttt{repo} es un objeto +\pymodclass{mercurial.localrepo}{localrepository}. Los nombres y +valores de los parámetros en \texttt{**kwargs} dependen del gancho que +se invoque, con las siguientes características en común: +\begin{itemize} +\item Si hay un parámetro llamado \texttt{node} o + \texttt{parent\emph{N}}, contendrá un ID hexadecimal de un conjunto + de cambios. La cadena vacía es usada para representar un + ``ID de conjunto de cambios nulo'' en vez de una cadena de ceros. +\item Si hay un parámetro llamado \texttt{url}, contendrá la URL de un + repositorio remoto, si puede ser determinada. +\item Los parámetros booleanos son representados como objetos + \texttt{bool} de Python. +\end{itemize} + +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''\ndt{Falso.}, se +considera que éste tuvo éxito. Si retorna +``true''\ndt{Verdadero.} 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 \pymodfunc{mercurial.node}{bin}. + +\subsection{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 ``\texttt{HG\_}''. Por ejemplo, si el nombre de un +parámetro es ``\texttt{node}'', el nombre de la variable de entorno +que almacena el parámetro se llamará ``\texttt{HG\_NODE}''. + +Un parámetro booleano se representa con la cadena ``\texttt{1}'' para +``true'', ``\texttt{0}'' para ``false''. Si una variable se llama +\envar{HG\_NODE}, \envar{HG\_PARENT1} o \envar{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 \envar{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ó. + +\subsection{Averiguar de dónde vienen los conjuntos de cambios} +%TODO los trae la cigüeña. De París. Y quedan debajo de una col. + +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 \emph{cómo} son +transferidos los conjuntos de cambios, y en muchos casos también desde +o hacia donde están siendo transferidos. + +\subsubsection{Fuentes de conjuntos de cambios} +\label{sec:hook:sources} + +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 \texttt{source}\ndt{Fuente.}, o una variable de entorno +llamada \envar{HG\_SOURCE}. + +\begin{itemize} +\item[\texttt{serve}] Los conjuntos de cambios son transferidos desde + o hacia un repositorio remoto a través de http o ssh. +\item[\texttt{pull}] Los conjuntos de cambios son transferidos vía una + operación de jalado de un repositorio a otro. +\item[\texttt{push}] Los conjuntos de cambios son transferidos vía un + empuje de un repositorio a otro. +\item[\texttt{bundle}] Los conjuntos de cambios son transferidos desde + %TODO bundle + o hacia un paquete. +\end{itemize} + +\subsubsection{A dónde van los cambios---URLs de repositorios remotos} +\label{sec:hook:url} +%TODO al cielo? no, ésos son los perros + +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 \texttt{url}, o en una variable de entorno +llamada \envar{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: +\begin{itemize} +\item \texttt{remote:ssh:\emph{ip-address}}---cliente ssh remoto, en + la dirección IP dada. +\item \texttt{remote:http:\emph{ip-address}}---cliente remoto http, en + la dirección IP dada. Si el cliente está usando SSL, tendrá la forma + \texttt{remote:https:\emph{ip-address}}. +\item Vacío---no se pudo descubrir información acerca del cliente + remoto. +\end{itemize} + +\section{Referencia de ganchos} + +\subsection{\hook{changegroup}---luego de añadir conjuntos de cambios +remotos} +\label{sec:hook:changegroup} + +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 +\hgcmd{pull} o \hgcmd{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 \hook{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: +\begin{itemize} +\item[\texttt{node}] Un ID de conjunto de cambios. El ID del primer conjunto + de cambios que fue añadido en el grupo. Todos los conjuntos de + cambios entre éste y la punta + %TODO mirar qué hacer con el índice + \index{tags!\texttt{tip}}(\texttt{tip}), inclusive, fueron añadidos + %TODO unbundle + por un único jalado (\hgcmd{pull}), empuje (\hgcmd{push}) o \hgcmd{unbundle}. +\item[\texttt{source}] Una cadena. La fuente de estos cambios. Vea la + sección~\ref{sec:hook:sources} para más detalles. +\item[\texttt{url}] Una URL. La ubicación del repositorio remoto, si + es conocida. Vea la sección~\ref{sec:hook:url} para más información. +\end{itemize} + +Veta también: \hook{incoming} (sección~\ref{sec:hook:incoming}), +\hook{prechangegroup} (sección~\ref{sec:hook:prechangegroup}), +\hook{pretxnchangegroup} (sección~\ref{sec:hook:pretxnchangegroup}) + +\subsection{\hook{commit}---luego de la creación de un nuevo conjunto +de cambios} +\label{sec:hook:commit} + +Este gancho es ejecutado luego de la creación de un nuevo conjunto de +cambios. + +Parámetros para este gancho: +\begin{itemize} +\item[\texttt{node}] Un ID de conjunto de cambios. El ID de conjunto + de cambios del conjunto de cambios que acabó de ser consignado. +\item[\texttt{parent1}] Un ID de conjunto de cambios. El ID de + conjunto de cambios del primer padre del conjunto de cambios que + acaba de ser consignado. +\item[\texttt{parent2}] Un ID de conjunto de cambios. El ID de + conjunto de cambios del segundo padre del conjunto de cambios que + acaba de ser consignado. +\end{itemize} + +Vea también: \hook{precommit} (sección~\ref{sec:hook:precommit}), +\hook{pretxncommit} (sección~\ref{sec:hook:pretxncommit}) + +\subsection{\hook{incoming}---luego de que un conjunto de cambios +remoto es añadido} +\label{sec:hook:incoming} + +Este gancho es ejecutado luego de que un conjunto de cambios +preexistente ha sido añadido al repositorio, por ejemplo, vía un +\hgcmd{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 +\hook{changegroup} (sección~\ref{sec:hook:changegroup}); 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: +\begin{itemize} +\item[\texttt{node}] Un ID de conjunto de cambios. El ID del conjunto + de cambios recién añadido. +\item[\texttt{source}] Una cadena. La fuente de estos cambios. Vea la + sección~\ref{sec:hook:sources} para más detalles. +\item[\texttt{url}] Una URL. La ubicación del repositorio remoto, si + es conocida. Vea la sección~\ref{sec:hook:url} para más información. +\end{itemize} + +Vea también: \hook{changegroup} (sección~\ref{sec:hook:changegroup}) +\hook{prechangegroup} (sección~\ref{sec:hook:prechangegroup}), +\hook{pretxnchangegroup} (sección~\ref{sec:hook:pretxnchangegroup}) + +\subsection{\hook{outgoing}---luego de la propagación de los conjuntos +de cambios} +\label{sec:hook:outgoing} + +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 \hgcmd{push} o \hgcmd{bundle}. + +Un uso posible para este gancho es notificar a los administradores que +los cambios han sido jalados. + +Parámetros para este gancho: +\begin{itemize} +\item[\texttt{node}] Un ID de conjunto de cambios. El ID del primer conjunto + de cambios del grupo que fue enviado. +\item[\texttt{source}] Una cadena. La fuente de la operación (vea la + sección~\ref{sec:hook:sources}). Si un cliente remoto jaló cambios + de este repositorio, \texttt{source} será \texttt{serve}. Si el + cliente que obtuvo los cambios desde este repositorio era local, + \texttt{source} será \texttt{bundle}, \texttt{pull}, o + \texttt{push}, dependiendo de la operación que llevó a cabo el + cliente. +\item[\texttt{url}] Una URL. La ubicación del repositorio remoto, si + es conocida. Vea la sección~\ref{sec:hook:url} para más información. +\end{itemize} + +Vea también: \hook{preoutgoing} (sección~\ref{sec:hook:preoutgoing}) + +\subsection{\hook{prechangegroup}---antes de empezar la adición de +conjuntos de cambios remotos} +\label{sec:hook:prechangegroup} + +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: +\begin{itemize} +\item[\texttt{source}] Una cadena. La fuente de estos cambios. Vea la + sección~\ref{sec:hook:sources} para más detalles. +\item[\texttt{url}] Una URL. La ubicación del repositorio remoto, si + es conocida. Vea la sección~\ref{sec:hook:url} para más información. +\end{itemize} + +Vea también: \hook{changegroup} (sección~\ref{sec:hook:changegroup}), +\hook{incoming} (sección~\ref{sec:hook:incoming}), , +\hook{pretxnchangegroup} (sección~\ref{sec:hook:pretxnchangegroup}) + +\subsection{\hook{precommit}---antes de iniciar la consignación de un +conjunto de cambios} +\label{sec:hook:precommit} + +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: +\begin{itemize} + \item[\texttt{parent1}] Un ID de conjunto de cambios. El ID de + conjunto de cambios del primer padre del directorio de trabajo. +\item[\texttt{parent2}] Un ID de conjunto de cambios. El ID de + conjunto de cambios del segundo padre del directorio de trabajo. +\end{itemize} +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: \hook{commit} (sección~\ref{sec:hook:commit}), +\hook{pretxncommit} (sección~\ref{sec:hook:pretxncommit}) + +\subsection{\hook{preoutgoing}---antes de empezar la propagación de +conjuntos de cambios} +\label{sec:hook:preoutgoing} + +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: +\begin{itemize} + \item[\texttt{source}] Una cadena. La fuente la operación que está + tratando de obtener cambios de éste repositorio (vea + la sección~\ref{sec:hook:sources}). Revise la documentación para + el parámetro \texttt{source} del gancho \hook{outgoing}, en la + sección~\ref{sec:hook:outgoing}, para ver los posibles valores de + este parámetro. +\item[\texttt{url}] Una URL. La ubicación del repositorio remoto, si + es conocida. Vea la sección~\ref{sec:hook:url} para más información. +\end{itemize} + +Vea también: \hook{outgoing} (sección~\ref{sec:hook:outgoing}) + +\subsection{\hook{pretag}---antes de etiquetar un conjunto de cambios} +\label{sec:hook:pretag} + +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: +\begin{itemize} +\item[\texttt{local}] Un booleano. Indica si la etiqueta es local a + ésta instancia del repositorio (p.e.~almacenado en + \sfilename{.hg/localtags}) o administrado por Mercurial (almacenado + en \sfilename{.hgtags}). +\item[\texttt{node}] Un ID de conjunto de cambios. El ID del conjunto + de cambios a etiquetar. +\item[\texttt{tag}] Una cadena. El nombre de la etiqueta por crear. +\end{itemize} + +Si la etiqueta que se va a crear se encuentra bajo control de +revisiones, los ganchos \hook{precommit} y \hook{pretxncommit} +(secciones~\ref{sec:hook:commit} y~\ref{sec:hook:pretxncommit}) +también serán ejecutados. + +Vea también: \hook{tag} (sección~\ref{sec:hook:tag}) + +\subsection{\hook{pretxnchangegroup}---antes de completar la adición +de conjuntos de cambios remotos} +\label{sec:hook:pretxnchangegroup} + +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: +\begin{itemize} + \item[\texttt{node}] Un ID de conjunto de cambios. El ID del primer + conjunto de cambios que fue añadido en el grupo. Todos los + conjuntos de cambios entre éste y el + \index{tags!\texttt{tip}}\texttt{tip}, inclusive, fueron añadidos + por un único \hgcmd{pull}, \hgcmd{push} o \hgcmd{unbundle}. +\item[\texttt{source}] Una cadena. La fuente de estos cambios. Vea la + sección~\ref{sec:hook:sources} para más detalles. +\item[\texttt{url}] Una URL. La ubicación del repositorio remoto, si + es conocida. Vea la sección~\ref{sec:hook:url} para más información. +\end{itemize} + +Vea también: \hook{changegroup} (sección~\ref{sec:hook:changegroup}), +\hook{incoming} (sección~\ref{sec:hook:incoming}), +\hook{prechangegroup} (sección~\ref{sec:hook:prechangegroup}) + +\subsection{\hook{pretxncommit}---antes de completar la consignación +de un nuevo conjunto de cambios} +\label{sec:hook:pretxncommit} + +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: +\begin{itemize} + \item[\texttt{node}] Un ID de conjunto de cambios. El ID del + conjunto de cambios recién consignado. +\item[\texttt{parent1}] Un ID de conjunto de cambios. El ID de + conjunto de cambios del primer padre del conjunto de cambios que + acaba de ser consignado. +\item[\texttt{parent2}] Un ID de conjunto de cambios. El ID de + conjunto de cambios del segundo padre del conjunto de cambios que + acaba de ser consignado. +\end{itemize} + +Vea también: \hook{precommit} (sección~\ref{sec:hook:precommit}) + +\subsection{\hook{preupdate}---antes de actualizar o fusionar el +directorio de trabajo} +\label{sec:hook:preupdate} + +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: +\begin{itemize} +\item[\texttt{parent1}] Un ID de conjunto de cambios. El ID del + padre al que el directorio de trabajo será actualizado. Si se está + fusionando el directorio de trabajo, no cambiará este padre. +\item[\texttt{parent2}] Un ID de conjunto de cambios. Sólo está + definido si se está fusionando el directorio de trabajo. El ID de la + revisión con la cual está siendo fusionado el directorio de trabajo. +\end{itemize} + +Vea también: \hook{update} (sección~\ref{sec:hook:update}) + +\subsection{\hook{tag}---luego de etiquetar un conjunto de cambios} +\label{sec:hook:tag} + +Este gancho es ejecutado luego de la creación de una etiqueta. + +Parámetros para este gancho: +\begin{itemize} +\item[\texttt{local}] Un booleano. Indica si la etiqueta es local a + ésta instancia del repositorio (p.e.~almacenado en + \sfilename{.hg/localtags}) o administrado por Mercurial (almacenado + en \sfilename{.hgtags}). +\item[\texttt{node}] Un ID de conjunto de cambios. El ID del + conjunto de cambios que fue etiquetado. +\item[\texttt{tag}] Una cadena. El nombre de la etiqueta que fue + creada. +\end{itemize} + +Si la etiqueta creada está bajo control de revisiones, el gancho +\hook{commit} (sección~\ref{sec:hook:commit}) es ejecutado antes de +este gancho. + +Vea también: \hook{pretag} (sección~\ref{sec:hook:pretag}) + +\subsection{\hook{update}---luego de actualizar o fusionar el +directorio de trabajo} +\label{sec:hook:update} + +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 \command{hgmerge} no puede resolver los conflictos en un +fichero), este gancho indica si la actualización o fusión fueron +completados adecuadamente. + +\begin{itemize} +\item[\texttt{error}] Un booleano. Indica si la actualización o fusión + fue completada exitosamente. +\item[\texttt{parent1}] Un ID de conjunto de cambios. El ID del padre + al cual fue actualizado el directorio de trabajo. Si se fusionó el + directorio de trabajo, no se habrá cambiado este padre. +\item[\texttt{parent2}] Un ID de conjunto de cambios. Sólo está + definido si se fusionó el directorio de trabajo. El ID de la + revisión con la que fue fusionado el directorio de trabajo. +\end{itemize} + +Vea también: \hook{preupdate} (sección~\ref{sec:hook:preupdate}) + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/htlatex.book Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,1 @@ +../en/htlatex.book \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/intro.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,621 @@ +\chapter{Introducción} +\label{chap:intro} + +\section{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. + +\subsection{¿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. +\begin{itemize} +\item Llevar registro del historial y la evolución de su proyecto, para + evitar hacer la tarea manualmente. Por cada cambio, tendrá una + bitácora de \emph{quién} lo hizo; \emph{por qué} se hizo; + \emph{cuándo} se hizo; y de \emph{qué} se trataba el cambio. +\item Cuando trabaja con más personas, los programas de control de + revisiones facilitan la colaboración. Por ejemplo, cuando varias + personas hacen cambios potencialmente incompatibles de forma casi + simultánea, el programa le ayudará a identificar y resolver tales + conflictos. +\item Puede ayudarle a recuperarse de equivocaciones. Si aplica un + cambio que posteriormente se evidencia como un error, puede + revertirlo a una versión previa a uno o muchos ficheros. De hecho, + una herramienta \emph{realmente} buena, incluso puede ayudarle + efectivamente a darse cuenta exactamente cuándo se introdujo el + error (para más detalles ver la sección~\ref{sec:undo:bisect}). +\item Le ayudará a trabajar simultáneamente, y a manejar las diferencias + entre múltiples versiones de su proyecto. +\end{itemize} +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 \emph{beneficios} con los +\emph{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 \emph{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 \emph{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 \emph{realmente} esté tratando +de hacer. Al mismo tiempo, Mercurial tiene alto desempeño y su +%TODO distribuida? en vez de p2p +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. + +\subsection{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: +\begin{itemize} +\item Control de revisiones (RCS) +\item Manejo de Configuraciones de Programas (SCM), o administracón de + configuraciones +\item Administración de código fuente +\item Control de Código Fuente, o Control de Fuentes +\item Control de Versiones (VCS) +\end{itemize} +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. + +\section{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. + +\section{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. + +\section{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. + +\subsection{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. + +\subsubsection{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''\ndt{fork.} 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 \emph{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 \emph{ú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 \emph{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 \emph{puramente} en una cuestión social. Lo que hacen las +herramientas distribuidas es \emph{disminuir} la posibilidad de una +bifurcación porque: +\begin{itemize} +\item Eliminan la distinción social que imponen las herramientas + centralizadas: aquélla entre miembros (personas con permiso de + consignar) y forasteros (los que no tienen el permiso). +\item Facilitan la reconciliación después de una bifurcación social, + porque todo lo que concierne al programa de control de revisiones es + una fusión. +\end{itemize} + +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. + +\subsection{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. + +\section{¿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: +\begin{itemize} +\item Es fácil de aprender y usar. +\item Es liviano. +\item Escala de forma excelente. +\item Es fácil de acondicionar. +\end{itemize} + +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. + +\section{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. + + +\subsection{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, +\href{http://svnbook.red-bean.com/nightly/en/svn.branchmerge.advanced.html#svn.branchmerge.advanced.finalword}{complicadas + y poco estables\ndt{buggy}}. + +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 \emph{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 (\texttt{status}) y desplegar +información frente a la revisión actual (\texttt{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 GUI\ndt{Interfaz de + Usuario Gráfica}, 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. + +\subsection{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. + +\subsection{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. + +\subsection{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. + +\subsection{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. + + +\section{Migrar de otra herramienta hacia Mercurial} + +Mercurial viene con una extensión llamada \hgext{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 \hgext{convert}: +\begin{itemize} +\item Subversion +\item CVS +\item Git +\item Darcs +\end{itemize} + +Adicionalmente, \hgext{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 \hgxcmd{conver}{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. + + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/license.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,142 @@ +\chapter{Licencia de Publicación Abierta} +\label{cha:opl} + +Versión 1.0, 8 Junio de 1999 + +\section{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: + +\begin{quote} + Copyright (c) \emph{año} por \emph{nombre del autor o designado}. + Este material puede distribuirse solamente bajo los términos y + condiciones especificados por la Licencia de Publicación Abierta, + v\emph{x.y} o posterior (la última versión disponible está en + \url{http://www.opencontent.org/openpub/}). +\end{quote} + +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~\ref{sec:opl:options}). + +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. + +\section{Derechos de reproducción} + +El derecho de reproducción de cada Publicación Abierta pertenece +al(os) autor(es) o designados. + +\section{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. + +\textbf{Separabilidad}. Si cualquier porción de esta licencia no es +aplicable en alguna jurisdicción, las porciones restantes se +mantienen. + +\textbf{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. + +\section{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: + +\begin{enumerate} +\item La versión modificada debe estar etiquetada como tal. +\item La persona que hace la modificación debe estar identificada y la + modificación con fecha. +\item El dar crédito al autor original y al editor si se requiere de + acuerdo a las prácticas académicas de citas. +\item Debe identificarse el lugar del documento original sin + modificación. +\item 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). +\end{enumerate} + +\section{Recomendaciones de buenas prácticas} + +Adicional a los requerimientos de esta licencia, se solicita a los +redistribuidores y se recomienda en gran medida que: + +\begin{enumerate} +\item 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. +\item Todas las modificaciones sustanciales (incluyendo eliminaciones) + deben estar marcadas claramente en el documento o si no descritas en + un adjunto del documento. +\item 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). +\end{enumerate} + +\section{Opciones de licencia} +\label{sec:opl:options} + +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. + +\begin{enumerate}[A] +\item 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. + +\item 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. +\end{enumerate} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/metadata.svg Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,337 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="metadata.svg" + sodipodi:docbase="/home/bos/hg/hgbook/en" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs4"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective2479" /> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mend" + style="overflow:visible;"> + <path + id="path2944" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;" + transform="scale(0.4) rotate(180) translate(10,0)" /> + </marker> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.98994949" + inkscape:cx="232.14286" + inkscape:cy="519.03485" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="906" + inkscape:window-height="659" + inkscape:window-x="181" + inkscape:window-y="58" + showgrid="false" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#a7a7a7;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:4.5, 1.5;stroke-dashoffset:0;stroke-opacity:1;display:inline" + d="M 326.94646,467.18359 L 326.94646,510.98123" + id="path1910" + inkscape:connector-type="polyline" + inkscape:connection-end="#rect2962" + inkscape:connection-start="#rect2764" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#a7a7a7;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:4.5, 1.5;stroke-dashoffset:0;stroke-opacity:1;display:inline" + d="M 326.94646,531.98123 L 326.94646,591.77887" + id="path1912" + inkscape:connector-type="polyline" + inkscape:connection-start="#rect2962" + inkscape:connection-end="#rect3000" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#a7a7a7;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:4.5, 1.5;stroke-dashoffset:0;stroke-opacity:1;display:inline" + d="M 316.1622,531.98123 L 192.30212,652.57648" + id="path1916" + inkscape:connector-type="polyline" + inkscape:connection-end="#rect3038" + inkscape:connection-start="#rect2962" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#484848;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:4.5, 1.5;stroke-dashoffset:0;stroke-opacity:1" + d="M 254.23217,467.18359 L 254.23216,510.98123" + id="path3088" + inkscape:connector-type="polyline" + inkscape:connection-start="#rect1872" + inkscape:connection-end="#rect2960" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#484848;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:4.5, 1.5;stroke-dashoffset:0;stroke-opacity:1" + d="M 254.23215,531.98123 L 254.23215,591.77887" + id="path3090" + inkscape:connector-type="polyline" + inkscape:connection-start="#rect2960" + inkscape:connection-end="#rect2998" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#484848;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:4.5, 1.5;stroke-dashoffset:0;stroke-opacity:1" + d="M 248.84002,531.98123 L 186.90999,652.57648" + id="path3092" + inkscape:connector-type="polyline" + inkscape:connection-start="#rect2960" + inkscape:connection-end="#rect3038" /> + <rect + style="fill:#7b7df5;fill-opacity:1;stroke:#595959;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect1872" + width="51.42857" + height="20" + x="228.51788" + y="446.68359" /> + <rect + style="fill:#cacbfb;fill-opacity:1;stroke:#595959;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect2764" + width="51.42857" + height="20" + x="301.23218" + y="446.68359" /> + <rect + style="fill:#cacbfb;fill-opacity:1;stroke:#595959;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect2766" + width="51.42857" + height="20" + x="155.80359" + y="446.68359" /> + <rect + style="fill:#cacbfb;fill-opacity:1;stroke:#595959;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect2768" + width="51.42857" + height="20" + x="83.089294" + y="446.68359" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#747474;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 135.01786,456.68359 L 155.30359,456.68359" + id="path2770" + inkscape:connector-type="polyline" + inkscape:connection-start="#rect2768" + inkscape:connection-end="#rect2766" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#747474;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 207.73216,456.68359 L 228.01788,456.68359" + id="path2772" + inkscape:connector-type="polyline" + inkscape:connection-start="#rect2766" + inkscape:connection-end="#rect1872" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#747474;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 280.44645,456.68359 L 300.73218,456.68359" + id="path2774" + inkscape:connector-type="polyline" + inkscape:connection-start="#rect1872" + inkscape:connection-end="#rect2764" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#747474;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:3, 3;stroke-dashoffset:0;stroke-opacity:1" + d="M 62.303571,456.68359 L 82.589294,456.68359" + id="path2778" + inkscape:connector-type="polyline" + inkscape:connection-end="#rect2768" /> + <rect + style="fill:#84f57b;fill-opacity:1;stroke:#595959;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect2960" + width="51.42857" + height="20" + x="228.51787" + y="511.48123" /> + <rect + style="fill:#cefbca;fill-opacity:1;stroke:#595959;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect2962" + width="51.42857" + height="20" + x="301.23218" + y="511.48123" /> + <rect + style="fill:#cefbca;fill-opacity:1;stroke:#595959;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect2964" + width="51.42857" + height="20" + x="155.80357" + y="511.48123" /> + <rect + style="fill:#cefbca;fill-opacity:1;stroke:#595959;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect2966" + width="51.42857" + height="20" + x="83.089287" + y="511.48123" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#747474;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 135.01786,521.48121 L 155.30359,521.48121" + id="path2968" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#747474;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 207.73216,521.48121 L 228.01788,521.48121" + id="path2970" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#747474;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 280.44645,521.48121 L 300.73218,521.48121" + id="path2972" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#747474;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:3, 3;stroke-dashoffset:0;stroke-opacity:1" + d="M 62.30358,521.48121 L 82.5893,521.48121" + id="path2974" + inkscape:connector-type="polyline" /> + <rect + style="fill:#f57b8f;fill-opacity:1;stroke:#595959;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect2998" + width="51.42857" + height="20" + x="228.51787" + y="592.27887" /> + <rect + style="fill:#fbcad2;fill-opacity:1;stroke:#595959;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3000" + width="51.42857" + height="20" + x="301.23218" + y="592.27887" /> + <rect + style="fill:#fbcad2;fill-opacity:1;stroke:#595959;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3002" + width="51.42857" + height="20" + x="155.80357" + y="592.27887" /> + <rect + style="fill:#fbcad2;fill-opacity:1;stroke:#595959;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3004" + width="51.42857" + height="20" + x="83.089287" + y="592.27887" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#747474;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 135.01786,602.27884 L 155.30359,602.27884" + id="path3006" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#747474;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 207.73216,602.27884 L 228.01788,602.27884" + id="path3008" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#747474;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 280.44645,602.27884 L 300.73218,602.27884" + id="path3010" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#747474;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:3, 3;stroke-dashoffset:0;stroke-opacity:1" + d="M 62.30358,602.27884 L 82.5893,602.27884" + id="path3012" + inkscape:connector-type="polyline" /> + <rect + style="fill:#ffced6;fill-opacity:1;stroke:#595959;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3034" + width="51.42857" + height="20" + x="228.51787" + y="653.07648" /> + <rect + style="fill:#f57b8f;fill-opacity:1;stroke:#595959;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3038" + width="51.42857" + height="20" + x="155.80357" + y="653.07648" /> + <rect + style="fill:#fbcad2;fill-opacity:1;stroke:#595959;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3040" + width="51.42857" + height="20" + x="83.089287" + y="653.07648" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#747474;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 135.01786,663.07646 L 155.30359,663.07646" + id="path3042" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#747474;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 207.73216,663.07646 L 228.01788,663.07646" + id="path3044" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#747474;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:3, 3;stroke-dashoffset:0;stroke-opacity:1" + d="M 62.30358,663.07646 L 82.5893,663.07646" + id="path3048" + inkscape:connector-type="polyline" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="82.072548" + y="432.64789" + id="text3094"><tspan + sodipodi:role="line" + id="tspan3096" + x="82.072548" + y="432.64789">Bitácora de cambios</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="82.306923" + y="498.97327" + id="text3098"><tspan + sodipodi:role="line" + id="tspan3100" + x="82.306923" + y="498.97327">Manifiesto</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="82.14286" + y="580.08569" + id="text3102"><tspan + sodipodi:role="line" + id="tspan3104" + x="82.14286" + y="580.08569">Bitácora de archivos</tspan></text> + </g> +</svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/mq-collab.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,428 @@ +\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{Reglas de MQ para aplicar parches} + +Las reglas que MQ usa para decidir si debe aplicar un parche son las +siguientes. +\begin{itemize} +\item Un parche sin guardias es aplicado siempre. +\item Si el parche tiene algún guardia negativo que corresponda con + cualquiera de los guardias seleccionados, se salta el parche. +\item Si el parche tiene algún guardia positivo que corresponda con + cualquiera de los guardias seleccionados, se aplica el parche. +\item Si el parche tiene guardias positivos o negativos, pero ninguno + corresponde con cualquiera de los guardias seleccionados, se salta + el parche. +\end{itemize} + +\section{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. + +\section{Dividir el fichero \sfilename{series}} + +Yo categorizo los parches en el fichero \sfilename{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. +\begin{itemize} +\item El grupo ``aceptado''. Son parches que el equipo de desarrollo + ha enviado al mantenedor del subsistema Infiniband, y que él ha + aceptado, pero que no están presentes en la instantánea en la cual + está basada el repositorio pequeño. Estos son parches de + ``sólo lectura'', presentes únicamente para transformar el árbol en + un estado similar al del repositorio del mantenedor oficial. +\item El grupo ``revisar''. Parches que yo he enviado, pero sobre los + que que el mantenedor oficial ha solicitado modificaciones antes de + aceptarlos. +\item El grupo ``pendiente''. Parches que no he enviado al mantenedor + oficial, pero que ya están terminados. Estos parches serán de + ``sólo lectura'' por un buen tiempo. Si el mantenedor oficial los + acepta cuando los envíe, los moveré al final del grupo ``aceptado''. + Si él solicita que modificaciones en alguno de ellos, los moveré al + principio del grupo ``revisar''. +\item El grupo ``en proceso''. Parches que están siendo activamente + desarrollados, y no deberían ser enviados a ninguna parte aún. +\item El grupo ``backport''. Parches que adaptan el árbol de fuentes a + versiones antiguas del árbol del kernel. +\item El grupo ``no enviar''. Parches que por alguna razón nunca deben + ser enviados al mantenedor oficial del kernel. Por ejemplo, alguno + de esos parches podría cambiar las cadenas de identificación + embebidas del controlador para hacer más fácil la distinción, en + pruebas de campo, entre una versión del controlador de + salida-del-árbol y una versión entregada por un vendedor de alguna + distribución. +\end{itemize} + +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 \sfilename{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 +\sfilename{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. + +\section{Mantener la serie de parches} + +En mi trabajo, uso varios guardias para controlar qué parches deben +ser aplicados. + +\begin{itemize} +\item Los parches ``aceptados'' son vigilados con + \texttt{accepted}. Yo habilito este guardia la mayoría de las veces. + Cuando aplico los parches sobre un árbol donde los parches ya están + %TODO no será ``desactivar este guardia''? si sí, corregir versión + %en inglés también + presentes, puedo desactivar este parche, y los parches que lo siguen + se aplicarán sin problemas. +\item Los parches que están ``terminados'', pero no han sido enviados, + no tienen guardias. Si estoy aplicando la pila de parches a una + copia del árbol oficial, no necesito habilitar ningún guardia para + obtener un árbol de fuentes razonablemente seguro. +\item Los parches que necesitan revisión antes de ser reenviados + tienen el guardia \texttt{rework}. +\item Para aquellos parches que aún están bajo desarrollo, uso + \texttt{devel}. +\item Un parche de backport puede tener varios guardias, uno para cada + versión del kernel a la que aplica. Por ejemplo, un parche que hace + backport de un segmento de código a~2.6.9 tendrá un guardia~\texttt{2.6.9}. +\end{itemize} +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. + +\subsection{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 \texttt{\#ifdef}s (secciones de código fuente +que sólo son usados condicionalmente) en su código, no introduzca +\texttt{\#ifdef}s 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 \hgext{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 \emph{sin} que el parche de +backport sea aplicado antes. + +\section{Consejos útiles para hacer desarrollo con MQ} + +\subsection{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. + +\subsection{Ver el historial de un parche} +\label{mq-collab:tips:interdiff} + +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~\ref{sec:mq:repo}. Si lo hace, notará +rápidamente que usar el comando \hgcmd{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 \hgext{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 +\package{patchutils}~\cite{web:patchutils}. Éste paquete provee un +comando llamado \command{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 \hgext{extdiff} de la manera usual, +añadiendo una línea a la sección \rcsection{extensions} de su \hgrc. +\begin{codesample2} + [extensions] + extdiff = +\end{codesample2} +El comando \command{interdiff} espera recibir los nombres de dos +ficheros, pero la extensión \hgext{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 \command{interdiff} en cada par de +ficheros de estos dos directorios. Este programa está disponible como +\sfilename{hg-interdiff} en el directorio \dirname{examples} del +repositorio de código fuente que acompaña a este libro. +\excode{hg-interdiff} + +Con el programa \sfilename{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: +\begin{codesample2} + hg extdiff -p hg-interdiff -r A:B my-change.patch +\end{codesample2} +Ya que usted seguramente querrá usar este comando tan largo a menudo, +puede hacer que \hgext{hgext} lo haga disponible como un comando +normal de Mercurial, editando de nuevo su \hgrc. +\begin{codesample2} + [extdiff] + cmd.interdiff = hg-interdiff +\end{codesample2} +Esto le indica a \hgext{hgext} que ponga a disposición un comando +\texttt{interdiff}, con lo que usted puede abreviar la invocación +anterior de \hgxcmd{extdiff}{extdiff} a algo un poco más manejable. +\begin{codesample2} + hg interdiff -r A:B my-change.patch +\end{codesample2} + +\begin{note} + %TODO revisar redacción + El comando \command{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, \command{interdiff} podría + no producir ningún resultado útil. +\end{note} + +La extensión \hgext{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~\ref{sec:hgext:extdiff}. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/mq-ref.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,378 @@ +\chapter{Referencia de las Colas de Mercurial} +\label{chap:mqref} + +\section{Referencia de órdenes MQ} +\label{sec:mqref:cmdref} + +Si desea dar un vistazo a las órdenes que ofrece MQ, use la orden +\hgcmdargs{help}{mq}. + +\subsection{\hgxcmd{mq}{qapplied}---imprimir los parches aplicados} + +La orden \hgxcmd{mq}{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''. + +\subsection{\hgxcmd{mq}{qcommit}---consignar cambios en la cola del repositorio} + +La orden \hgxcmd{mq}{qcommit} consigna cualquier cambio sobresaliente +en el repositorio \sdirname{.hg/patches}. Esta orden solamente +funciona si el directorio \sdirname{.hg/patches} es un repositorio, +p.e.~usted creó el directorio con +\hgcmdargs{qinit}{\hgxopt{mq}{qinit}{-c}} o ejecutó +\hgcmd{init} en el directorio después de correr \hgxcmd{mq}{qinit}. + +Esta orden es un atajo para \hgcmdargs{commit}{--cwd .hg/patches}. + +\subsection{\hgxcmd{mq}{qdelete}---eliminar un parche del fichero + \sfilename{series}} + +La orden \hgxcmd{mq}{qdelete} elimina la entrada del fichero +\sfilename{series} para el parche en el directorio +\sdirname{.hg/patches}. No sca el parche si ha sido aplicado. De +forma predeterminada no borra el fichero del parche; use la opción +\hgxopt{mq}{qdel}{-f} para hacerlo. + +Opciones: +\begin{itemize} +\item[\hgxopt{mq}{qdel}{-f}] Elimina el fichero del parche. +\end{itemize} + +\subsection{\hgxcmd{mq}{qdiff}---imprimir la diferencia del último + parche aplicado} + +La orden \hgxcmd{mq}{qdiff} imprime un diff del parche más +recientemente aplicado. Es equivalente a \hgcmdargs{diff}{-r-2:-1}. + +\subsection{\hgxcmd{mq}{qfold}---fusionar (``integrar'') varios parches en + uno solo} + +La orden \hgxcmd{mq}{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; +\hgxcmd{mq}{qfold} saldrá indicando un error si alguno ha sido +aplicado. El orden en el cual los parches se pliegan es +significativo; \hgcmdargs{qfold}{a b} significa ``aplique el parche +más reciente, seguido de \texttt{a}, y seguido de \texttt{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 (``\texttt{*}''). Se usa la opción +\hgxopt{mq}{qfold}{-e} para editar el mensaje de consignación para el +conjunto de cambios/parches después de completarse el pliegue. + +Opciones: +\begin{itemize} +\item[\hgxopt{mq}{qfold}{-e}] Edita el mensaje de consignación y la + descripción del parche del parche que se ha integrado. +\item[\hgxopt{mq}{qfold}{-l}] Usa los contenidos del fichero dado como + el nuevo mensaje de consignación y descripción del parche para el + parche a integrar. +\item[\hgxopt{mq}{qfold}{-m}] Usa el texto dado como el mensaje de + consignación y descripción del parche para el parche integrado. +\end{itemize} + +\subsection{\hgxcmd{mq}{qheader}---desplegar el encabezado/descripción + de un parche} + +La orden \hgxcmd{mq}{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. + +\subsection{\hgxcmd{mq}{qimport}---importar el parche de un tercero en + la cola} + +La orden \hgxcmd{mq}{qimport} añade una entrada de un parche externo +al fichero \sfilename{series} y copia el parche en el directorio +\sdirname{.hg/patches}. Añade la entrada inmediatamente después del +último parche aplicado, pero no introduce el parche. + +Si el directorio \sdirname{.hg/patches} es un repositorio, +\hgxcmd{mq}{qimport} automáticamente hace un \hgcmd{add} del parche +importado. + +\subsection{\hgxcmd{mq}{qinit}---preparar un repositorio para trabajar + con MQ} + +La orden \hgxcmd{mq}{qinit} prepara un repositorio para trabajar con +MQ. Crea un directorio llamado \sdirname{.hg/patches}. + +Opciones: +\begin{itemize} +\item[\hgxopt{mq}{qinit}{-c}] Crea \sdirname{.hg/patches} como un + repositorio por sí mismo. También crea un fichero + \sfilename{.hgignore} que ignorará el fichero \sfilename{status}. +\end{itemize} + +Cuando el directorio \sdirname{.hg/patches} es un repositorio, las órdenes +\hgxcmd{mq}{qimport} y \hgxcmd{mq}{qnew} hacen \hgcmd{add} +automáticamente a los parches nuevos. + +\subsection{\hgxcmd{mq}{qnew}---crear un parche nuevo} + +La orden \hgxcmd{mq}{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 \sfilename{series} después +del último parche aplicado, y se introduce en el tope de ese parche. + +Si \hgxcmd{mq}{qnew} encuentra ficheros modificados en el directorio +de trabajo, rehusará crear un parche nuevo a meos que se emplee +\hgxopt{mq}{qnew}{-f} la opción (ver más adelante). Este +comportamiento le permite hacer \hgxcmd{mq}{qrefresh} al último parche +aplicado antes de aplicar un parche nuevo encima de este. + +Opciones: +\begin{itemize} +\item[\hgxopt{mq}{qnew}{-f}] Crea un parche nuevo si los contenidos + del directorio actual han sido modificados. Cualquier modificación + significativa se añade al parche recientemente creado, de tal forma + que al finalizar la orden, el directorio de trabajo no lucirá + modificado. +\item[\hgxopt{mq}{qnew}{-m}] Usa el texto dado como el mensaje de + consignación. Este texto se almacenará al principio del fichero del + parche, antes de los datos del parche. +\end{itemize} + +\subsection{\hgxcmd{mq}{qnext}---imprimir el nombre del próximo parche} + +La orden \hgxcmd{mq}{qnext} imprime el nombre del siguiente parche en +el fichero \sfilename{series} a continuación del último parche +aplicado. Este parche sería el próximo parche a aplicar si se +ejecutara la orden \hgxcmd{mq}{qpush}. + +\subsection{\hgxcmd{mq}{qpop}---sustraer parches de la pila} + +La orden \hgxcmd{mq}{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, \hgxcmd{mq}{qpop} lo trata como un +índice dentro del fichero \sfilename{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 \hgxcmd{mq}{qpop} no lee o escribe parches en el fichero +\sfilename{series}. \hgxcmd{mq}{qpop} se constituye por tanto en una +forma segura de sustraer un parche del fichero \sfilename{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 \hgxcmd{mq}{qpop} no sustraerá +parche alguno si el directorio de trabajo ha sido modificado. Puede +modificar este comportamiento con la opción \hgxopt{mq}{qpop}{-f}, que +revierte todas las modificaciones del directorio de trabajo. + +Opciones: +\begin{itemize} +\item[\hgxopt{mq}{qpop}{-a}] Sustrae todos los parches aplicados. + Restaura el repositorio al estado antes de haber aplicado parche alguno. +\item[\hgxopt{mq}{qpop}{-f}] Revertir forzadamente cualquier + modificación del directorio de trabajo cuando se hace sustracciones. +\item[\hgxopt{mq}{qpop}{-n}] Sustraer un parche de la cola dado un nombre. +\end{itemize} + +La orden \hgxcmd{mq}{qpop} elimina una línea del final del fichero +\sfilename{status} por cada parche que se sustrae. + +\subsection{\hgxcmd{mq}{qprev}---imprimir el nombre del parche anterior} + +La orden \hgxcmd{mq}{qprev} imprime el nombre del parche en el fichero +\sfilename{series} que está antes del último parche aplicado. Este +se volverá el último parche aplicado si ejecuta \hgxcmd{mq}{qpop}. + +\subsection{\hgxcmd{mq}{qpush}---introducir parches a la pila} +\label{sec:mqref:cmd:qpush} + +La orden \hgxcmd{mq}{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: +\begin{itemize} +\item La fecha de consignación y zona horaria corresponden a la hora + actual de la zona. Dado que tales datos se usan para computar la + identidad de un conjunto de cambios, significa que si hace + \hgxcmd{mq}{qpop} a un parche y \hgxcmd{mq}{qpush} de nuevo, el + conjunto de cambios que introduzca tendrá una identidad distinta a + la del conjunto de cambios que sustrajo. +\item El autor es el mismo que el predeterminado usado por la orden + \hgcmd{commit}. +\item El mensaje de consignación es cualquier texto del fichero del + parche que viene antes del primer encabezado del diff. Si no hay + tal texto, un mensaje predeterminado se sua para identificar el + nombre del parche. +\end{itemize} +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: +\begin{itemize} +\item[\hgxopt{mq}{qpush}{-a}] Introduce todos los parches que no han + sido aplicados del fichero \sfilename{series} hasta que no haya nada + más para introducir. +\item[\hgxopt{mq}{qpush}{-l}] Añade el nombre del parche al final del + mensaje de consignación +\item[\hgxopt{mq}{qpush}{-m}] Si un parche no se aplica limpiamente, + usa la entrada para un parche en otra cola almacenada para computar + los parámetros en una fusión de tres, y aplica una fusión de tres + fuentes usando la maquinaria usual de Mercurial. Usa la resolución + de la fusión como el contenido del parche nuevo. +\item[\hgxopt{mq}{qpush}{-n}] Usa la cola mencionada si se está + fusionando en la introducción. +\end{itemize} + +La orden \hgxcmd{mq}{qpush} lee, pero no modifica el fichero +\sfilename{series}. Añade al final del fichero \hgcmd{status} una +línea por cada parche que se introduce. + +\subsection{\hgxcmd{mq}{qrefresh}---actualiza el último parche aplicado} + +La orden \hgxcmd{mq}{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 \hgxcmd{mq}{qrefresh} busca las siguientes modificaciones: +\begin{itemize} +\item Los cambios al mensaje de consignación, p.e.~el texto antes del + primer encabezado de diff en el fichero del parche, se replejan en + el nuevo conjunto de cambios que representa el parche. +\item Las modificaciones a los ficheros a los que se les da + seguimiento en el directorio de trabajo se añade al parche. +\item Los cambios a los ficheros a los que se les da seguimiento con + \hgcmd{add}, \hgcmd{copy}, \hgcmd{remove}, o \hgcmd{rename}. Se + añaden al parche los ficheros añadidos, copiados y renombrados, + mientras que los ficheros eliminados y las fuentes renombradas se + eliminan. +\end{itemize} + +Incluso si \hgxcmd{mq}{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: +\begin{itemize} +\item[\hgxopt{mq}{qrefresh}{-e}] Modificar la descripción de la + consignación y el parche con el editor de texto preferido. +\item[\hgxopt{mq}{qrefresh}{-m}] Modificar el mensaje de consignación + y la descripción del parche con el texto dado. +\item[\hgxopt{mq}{qrefresh}{-l}] Modificar el mensaje de consignación + y la descripción del parche con el texto del fichero dado. +\end{itemize} + +\subsection{\hgxcmd{mq}{qrename}---renombrar un parche} + +La orden \hgxcmd{mq}{qrename} renombra un parche y cambia la entrada +del parche en el fichero \sfilename{series}. + +Con un argumento sencillo, \hgxcmd{mq}{qrename} renombra el último +parche aplicado. Con dos argumentos, renombra el primer argumento con +el segundo. + +\subsection{\hgxcmd{mq}{qrestore}---restaurar el estado almacenado de + la cola} + +XXX No idea what this does. + +\subsection{\hgxcmd{mq}{qsave}---almacena el estado actual de la cola} + +XXX Likewise. + +\subsection{\hgxcmd{mq}{qseries}---imprime la serie completa de parches} + +La orden \hgxcmd{mq}{qseries} imprime la serie completa de parches del +fichero \sfilename{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. + +\subsection{\hgxcmd{mq}{qtop}---imprime el nombre del parche actual} + +\hgxcmd{mq}{qtop} imprime el nombre del último parche aplicado. + +\subsection{\hgxcmd{mq}{qunapplied}---imprimir los parches que aún no + se han aplicado} + +La orden \hgxcmd{mq}{qunapplied} imprime los nombres de los parches +del fichero \sfilename{series} que todavía no han sido aplicados. Los +imprime de acuerdo al orden en el cual serían introducidos. + +\subsection{\hgcmd{strip}---remover una revisión y sus descendientes} + +La orden \hgcmd{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 \hgcmd{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: +\begin{itemize} +\item[\hgopt{strip}{-b}] Almacenar conjuntos de cambios no + relacionados que se han mezclado con los conjuntos de cambios que + están en franjas con el agrupamiento de copia de seguridad. +\item[\hgopt{strip}{-f}] Si una rama tiene varias ramas principales + remueve todos los frentes. XXX This should be renamed, y usa + \texttt{-f} para desagrupar revisiones cuando hay cambios pendientes. +\item[\hgopt{strip}{-n}] No almacene la copia de seguridad agrupada. +\end{itemize} + +\section{Referencia de ficheros de MQ} + +\subsection{El fichero \sfilename{series}} + +El fichero \sfilename{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 ``\texttt{\#}'', 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 \sfilename{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 \hgxcmd{mq}{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 \sfilename{series}. + +También es posible colocar el fichero \sfilename{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 \hgxopt{mq}{qinit}{-c} de \hgxcmd{mq}{qinit}, esto se hará +automáticamente. + +\subsection{El fichero \sfilename{status}} + +El fichero \sfilename{status} contiene los nombres y los hashes de los +conjuntos de cambios de todos los parches que MQ ha aplicado. A +diferencia del fichero \sfilename{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. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/mq-stack.svg Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,280 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="mq-stack.svg" + sodipodi:docbase="/home/bos/hg/hgbook/en" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs4"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective2525" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.4142136" + inkscape:cx="299.33323" + inkscape:cy="815.646" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="1014" + inkscape:window-height="689" + inkscape:window-x="89" + inkscape:window-y="25" + showgrid="false" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <rect + style="fill:#0000ff;fill-opacity:0.75;fill-rule:evenodd;stroke:#3c3c3c;stroke-width:1.05063355px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + id="rect1307" + width="202.93683" + height="24.243662" + x="230.01944" + y="221.70146" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" + x="237.89606" + y="237.13383" + id="text1309"><tspan + sodipodi:role="line" + id="tspan1311" + x="237.89606" + y="237.13383">prevent-compiler-reorder.patch</tspan></text> + <rect + style="fill:#7979ff;fill-opacity:0.875;fill-rule:evenodd;stroke:#3c3c3c;stroke-width:1.05063355px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + id="rect1320" + width="202.93683" + height="24.243662" + x="230.01936" + y="251.34325" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" + x="237.89598" + y="266.77563" + id="text1322"><tspan + sodipodi:role="line" + id="tspan1324" + x="237.89598" + y="266.77563">namespace-cleanup.patch</tspan></text> + <rect + style="fill:#7979ff;fill-opacity:0.875;fill-rule:evenodd;stroke:#3c3c3c;stroke-width:1.05063355px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + id="rect2217" + width="202.93683" + height="24.243662" + x="230.01936" + y="280.98505" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" + x="237.89598" + y="296.41742" + id="text2219"><tspan + sodipodi:role="line" + id="tspan2221" + x="237.89598" + y="296.41742">powerpc-port-fixes.patch</tspan></text> + <rect + style="fill:#7979ff;fill-opacity:0.875;fill-rule:evenodd;stroke:#3c3c3c;stroke-width:1.05063355px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + id="rect3114" + width="202.93683" + height="24.243662" + x="230.01936" + y="310.6268" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" + x="237.89598" + y="326.05917" + id="text3116"><tspan + sodipodi:role="line" + id="tspan3118" + x="237.89598" + y="326.05917">report-devinfo-correctly.patch</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" + x="200.01021" + y="191.68094" + id="text3170" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3172" + x="200.01021" + y="191.68094" + style="font-size:48px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:Bitstream Vera Sans">{</tspan></text> + <text + xml:space="preserve" + style="font-size:15.25329685px;font-style:normal;font-weight:normal;line-height:125%;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" + x="255.26627" + y="248.79449" + id="text3190" + sodipodi:linespacing="125%" + transform="scale(0.786716,1.271107)"><tspan + sodipodi:role="line" + id="tspan3192" + x="255.26627" + y="248.79449" + style="font-size:61.01318741px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:Bitstream Vera Sans">{</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" + x="195.86807" + y="173.17117" + id="text4085" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4087" + x="195.86807" + y="173.17117" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:end;line-height:125%;writing-mode:lr-tb;text-anchor:end;font-family:Bitstream Vera Sans">presente en la serie,</tspan><tspan + sodipodi:role="line" + x="195.86807" + y="188.17117" + id="tspan4089" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:end;line-height:125%;writing-mode:lr-tb;text-anchor:end;font-family:Bitstream Vera Sans">pero no aplicado</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" + x="195.0712" + y="288.91745" + id="text4091" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4093" + x="195.0712" + y="288.91745" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:end;line-height:125%;writing-mode:lr-tb;text-anchor:end;font-family:Bitstream Vera Sans">parches aplicados,</tspan><tspan + sodipodi:role="line" + x="195.0712" + y="303.91745" + id="tspan4111" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:end;line-height:125%;writing-mode:lr-tb;text-anchor:end;font-family:Bitstream Vera Sans">Conjuntos de cambios presentes</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" + x="195.0712" + y="229.28813" + id="text4095" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4097" + x="195.0712" + y="229.28813" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:end;line-height:125%;writing-mode:lr-tb;text-anchor:end;font-family:Bitstream Vera Sans">parche aplicado</tspan><tspan + sodipodi:role="line" + x="195.0712" + y="244.28813" + id="tspan4109" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:end;line-height:125%;writing-mode:lr-tb;text-anchor:end;font-family:Bitstream Vera Sans">más recientemente</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;opacity:1;fill:#666666;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" + x="450.4975" + y="238.29692" + id="text4137"><tspan + sodipodi:role="line" + id="tspan4139" + x="450.4975" + y="238.29692">201ad3209902</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;opacity:1;fill:#989898;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" + x="450.05804" + y="267.93872" + id="text4141"><tspan + sodipodi:role="line" + id="tspan4143" + x="450.05804" + y="267.93872">126b84e593ae</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;opacity:1;fill:#989898;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" + x="450.6557" + y="297.58051" + id="text4145"><tspan + sodipodi:role="line" + id="tspan4147" + x="450.6557" + y="297.58051">a655daf15409</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;opacity:1;fill:#989898;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" + x="450.71429" + y="327.22226" + id="text4149"><tspan + sodipodi:role="line" + id="tspan4151" + x="450.71429" + y="327.22226">e50d59aaea3a</tspan></text> + <rect + style="fill:#d7d7ff;fill-opacity:0.875;fill-rule:evenodd;stroke:#a6a6a6;stroke-width:1.05063355px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + id="rect3106" + width="202.93683" + height="24.243662" + x="230.01936" + y="150.41792" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#808080;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" + x="237.89598" + y="165.8503" + id="text3108"><tspan + sodipodi:role="line" + id="tspan3110" + x="237.89598" + y="165.8503">forbid-illegal-params.patch</tspan></text> + <rect + style="fill:#d7d7ff;fill-opacity:0.875;fill-rule:evenodd;stroke:#a6a6a6;stroke-width:1.05063355px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + id="rect2241" + width="202.93683" + height="24.243662" + x="230.16466" + y="180.05968" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#808080;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" + x="238.04128" + y="195.49205" + id="text2243"><tspan + sodipodi:role="line" + id="tspan2245" + x="238.04128" + y="195.49205">fix-memory-leak.patch</tspan></text> + </g> +</svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/mq.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,1103 @@ +\chapter{Administración de cambios con Colas de Mercurial} +\label{chap:mq} + +\section{El problema de la administración de parches} +\label{sec:mq:patch-mgmt} + +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 \command{diff} y +\command{patch} (ver la sección~\ref{sec:mq:patch} 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. + +\section{La prehistoria de las Colas de Mercurial} +\label{sec:mq:history} + +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. + +\subsection{Trabajar parches con quilt} +\label{sec:mq: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''~\cite{web:quilt}, o simplemente ``quilt'' +(ver~\cite{gruenbacher:2005} 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 \emph{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 \emph{elegir} qué cambios desea aplicar al árbol. Si +``pop''\ndt{saca} un parche, los cambios hechos por tal parchve +desapareceŕan del árbol de directorios. Quilt recuerda qué parches ha +sacado, para que pueda ``introducirlos''\ndt{push} 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. + +\subsection{Pasar de trabajo con parches con Quilt hacia Colas de Mercurial} +\label{sec:mq:quilt-mq} + +A mediados de 2005, Chris Mason tomó las características de quilt y +escribió una extensión que llamó Colas de Mercurial\ndt{Mercurial +Queues}, 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á +\emph{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.\ndt{introduce originalmente es +push y pop es sustraer en este contexto, usaremos el original en inglés +cuando encontremos que facilita la comprensión} + +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. + +\section{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 \emph{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 +\emph{mucho} más sencillo. Dado que todo parche que se aplique tiene +un conjunto de cambios asociado, puede usar +\hgcmdargs{log}{\emph{filename}} para ver qué conjuntos de cambios y +parches afectaron un fichero. Puede usar la orden \hgext{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 \hgcmd{annotate} para ver qué +conjuntos de cambios o parches modificaron una línea particular de un +fichero fuente. Y mucho más. + +\section{Entender los parches} +\label{sec:mq:patch} + +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 \command{diff} compara dos ficheros, e +imprime una lista de diferencias de sus líneas. La orden +\command{patch} entiende esas diferencias como \emph{modificaciones} +para construir un fichero. Vea en la figura~\ref{ex:mq:diff} un +ejemplo sencillo de tales órdenes en acción. + +\begin{figure}[ht] + \interaction{mq.dodiff.diff} + \caption{Uso sencillo de las órdenes \command{diff} y \command{patch}} + \label{ex:mq:diff} +\end{figure} + +El tipo de fichero que \command{diff} genera (y que \command{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 \command{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 \command{patch} busca la primera +línea que comience con la cadena ``\texttt{diff~-}''. + +MQ trabaja con diffs \emph{unificados} (\command{patch} acepta varios +formatos de diff adicionales, pero MQ no). Un diff unificado contiene +dos clases de encabezados. El \emph{encabezado de fichero} describe +el fichero que se está modificando; contiene el nombre del fichero a +modificar. Cuando \command{patch} ve un nuevo encabezado de fichero, +busca un fichero con ese nombre para modificarlo. + +Después del encabezaado vienen varios \emph{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 +\emph{contexto} del trozo. Si solamente hay una pequeña cantidad de +contexto entre trozos sucesivos, \command{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 ``\texttt{-}'' significa ``elimine +esta línea'', si la línea comienza con un ``\texttt{+}'' 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~\ref{sec:mq:adv-patch}), pero en el momento usted ya +debería tener suficiente información para usar MQ. + +\section{Comenzar a usar Colas de Mercurial} +\label{sec:mq:start} + +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 \tildefile{.hgrc}, y añada las líneas +de la figura~\ref{ex:mq:config}. + +\begin{figure}[ht] + \begin{codesample4} + [extensions] + hgext.mq = + \end{codesample4} + \label{ex:mq:config} + \caption{Líneas a añadir en \tildefile{.hgrc} para habilitar la extensión MQ} +\end{figure} + +Cuando la extensión esté habilitada, aparecerán varios comandos. Para +verificar que la extensión está trabajando, puede usar \hgcmd{help} +para ver si la orden \hgxcmd{mq}{qinit} está disponible; vea un +ejemplo en la figura~\ref{ex:mq:enabled}. + +\begin{figure}[ht] + \interaction{mq.qinit-help.help} + \caption{Cómo verificar que MQ está habilitado} + \label{ex:mq:enabled} +\end{figure} + +Puede usar MQ en \emph{cualquier} repositorio de Mercurial, y sus +comandos solamente operarán con tal repositorio. Para comenzar, basta +con preparar el repositorio con la orden \hgxcmd{mq}{qinit} (ver la +figura~\ref{ex:mq:qinit}). Esta orden crea un directorio vacío +llamado \sdirname{.hg/patches}, donde MQ mantendrá sus metadatos. Como +otras ordenes de Mercurial, la orden \hgxcmd{mq}{qinit} no imprime +nada cuando es exitosa. + +\begin{figure}[ht] + \interaction{mq.tutorial.qinit} + \caption{Preparar un repositorio para usar MQ} + \label{ex:mq:qinit} +\end{figure} + +\begin{figure}[ht] + \interaction{mq.tutorial.qnew} + \caption{Crear un nuevo parche} + \label{ex:mq:qnew} +\end{figure} + +\subsection{Crear un nuevo parche} + +Para comenzar a trabajar en un nuevo parche use la orden +\hgxcmd{mq}{qnew}. Esta orden recibe un argumento, el nombre del +parche a crear. MQ lo usará como el nombre del fichero en el +directorio \sdirname{.hg/patches}, como puede apreciarlo en la +figura~\ref{ex:mq:qnew}. + +También hay otros dos nuevos ficheros en el directorio +\sdirname{.hg/patches}: \sfilename{series} y \sfilename{status}. El +fichero \sfilename{series} lista todos los parches de los cuales MQ +tiene noticia para este repositorio, con un parche por línea. +Mercurial usa el fichero \sfilename{status} para mantener registros +interns; da seguimiento a todos los parches que MQ ha \emph{aplicado} +en el repositorio. + +\begin{note} + En ciertas ocasiones usted querrá editar el fichero + \sfilename{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 \sfilename{status}, dado que es fácil + desorientar a MQ acerca de lo que está pasando. +\end{note} + +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 \hgcmd{diff} y \hgcmd{annotate}, +trabajarán de la misma forma como lo han hecho antes. + +\subsection{Refrescar un parche} + +Cuando usted llega a un punto en el cual desea guardar su trabajo, use +la orden \hgxcmd{mq}{qrefresh} (figura~\ref{ex:mq:qnew}) 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. + +\begin{figure}[ht] + \interaction{mq.tutorial.qrefresh} + \caption{Refrescar un parche} + \label{ex:mq:qrefresh} +\end{figure} + +Puede ejecutar la orden \hgxcmd{mq}{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 \hgcmd{revert} sobre sus modificaciones +para volver al refresco anterior. + +\begin{figure}[ht] + \interaction{mq.tutorial.qrefresh2} + \caption{Refrescar un parche muchas veces para acumular cambios} + \label{ex:mq:qrefresh2} +\end{figure} + +\subsection{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 \hgxcmd{mq}{qnew} para crear un nuevo +parche. Mercurial aplicará este parche sobre su parche anterior. +Para un ejemplo, ver la figura~\ref{ex:mq:qnew2}. 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 \hgcmd{annotate}). + +\begin{figure}[ht] + \interaction{mq.tutorial.qnew2} + \caption{Aplicar un parche después del primero} + \label{ex:mq:qnew2} +\end{figure} + +Hasta ahora, con excepción de \hgxcmd{mq}{qnew} y +\hgxcmd{mq}{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~\ref{ex:mq:qseries}: + +\begin{itemize} +\item La orden \hgxcmd{mq}{qseries} lista cada parche del cual MQ + tiene noticia en este repositorio, desde el más antiguo hasta el más + nuevo (El último \emph{creado}). +\item La orden \hgxcmd{mq}{qapplied} lista cada parche que MQ haya + \emph{aplicado} en este repositorio, de nuevo, desde el más antiguo + hasta el más nuevo (El aplicado más recientemente). +\end{itemize} + +\begin{figure}[ht] + \interaction{mq.tutorial.qseries} + \caption{Entender la pila de parches con \hgxcmd{mq}{qseries} y + \hgxcmd{mq}{qapplied}} + \label{ex:mq:qseries} +\end{figure} + +\subsection{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 \emph{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 \hgxcmd{mq}{qpop}. MQ +\emph{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~\ref{fig:mq:stack} ilustra la diferencia entre +parches aplicados y seguidos. + +\begin{figure}[ht] + \centering + \grafix{mq-stack} + \caption{Parches aplicados y no aplicados en la pila de parches de MQ} + \label{fig:mq:stack} +\end{figure} + +Puede reaplicar un parche no aplicado o sustraído con la orden +\hgxcmd{mq}{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 +\hgxcmd{mq}{qpop} y \hgxcmd{mq}{qpush} en acción en la +figura~\ref{ex:mq:qpop}. Vea que hemos sustraído uno o dos parches, +la salida de\hgxcmd{mq}{qseries} continúa igual, mientras que +\hgxcmd{mq}{qapplied} ha cambiado. + +\begin{figure}[ht] + \interaction{mq.tutorial.qpop} + \caption{Modificar la pila de parches aplicados} + \label{ex:mq:qpop} +\end{figure} + +\subsection{Introducir y sustraer muchos parches} + +Mientras que \hgxcmd{mq}{qpush} y \hgxcmd{mq}{qpop} operan sobre un +único parche cada vez, puede introducir y sustraer varios parches de +una vez. La opción \hgxopt{mq}{qpush}{-a} de \hgxcmd{mq}{qpush} +introduce todos los cambios que no hayan sido aplicados, mientras que +la opción \hgxopt{mq}{qpop}{-a} de \hgxcmd{mq}{qpop} sustrae todos los +cambios aplicados. (Vea la sección~\ref{sec:mq:perf} más adelante +en la cual se explican otras formas de de introducir y sustraer varios +cambios.) + +\begin{figure}[ht] + \interaction{mq.tutorial.qpush-a} + \caption{Pushing all unapplied patches} + \label{ex:mq:qpush-a} +\end{figure} + +\subsection{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~\ref{ex:mq:add} ilusta esto; la orden \hgxcmd{mq}{qnew} no +creará un nuevo parche si hay cambios notorios, causados en este caso +por aplicado la orden \hgcmd{add} a \filename{file3}. + +\begin{figure}[ht] + \interaction{mq.tutorial.add} + \caption{Crear un parche a la fuerza} + \label{ex:mq:add} +\end{figure} + +Las órdenes que revisan el directorio actual cuentan con una opción +``Se lo que estoy haciendo'', que siempre está nombrada como +\option{-f}. El significado exacto de \option{-f} depende de la +orden. Por ejemplo, \hgcmdargs{qnew}{\hgxopt{mq}{qnew}{-f}} +incorporarán cualquier cambio notorio en el nuevo parche que crea pero +\hgcmdargs{qpop}{\hgxopt{mq}{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 \option{-f} +de cada comando antes de usarla! + +\subsection{Trabajar con varios parches a la vez} + +La orden \hgxcmd{mq}{qrefresh} siempre refresca el \emph{ú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 \emph{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 UI\ndt{Interfaz de Usuario, User Interface en inglés}, es fácil arreglar la parte fundamental. +Simplemente use \hgxcmd{mq}{qrefresh} sobre el parche de la UI para +guardar los cambios de su trabajo en progreso, y use \hgxcmd{mq}{qpop} +para sacar sustraer el parche de la parte fundamental. Arregla el +fallo sobre la parte fundamental, aplique \hgxcmd{mq}{qrefresh} sobre +el parche fundamental, y aplique \hgxcmd{mq}{qpush} sobre el parche de +UI para continuar donde había quedado. + +\section{Más acerca de parches} +\label{sec:mq:adv-patch} + +MQ usa la orden GNU \command{patch} para aplicar los parches, por lo +tanto es útil conocer ciertos detalles de cómo trabaja +\command{patch}, y también acerca de los parches. + +\subsection{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 \cmdopt{diff}{-r} y +\cmdopt{diff}{-N} de \command{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 \command{patch} tiene la opción +\cmdopt{patch}{-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 \emph{cantidad de eliminaciones}. + +La opción con ``\texttt{-p1}'' significa ``elimine uno''. Si +\command{patch} ve un nombre de fichero \filename{foo/bar/baz} en el +encabezado del fichero, eliminará \filename{foo} y tratará de parchar +un fichero llamado \filename{bar/baz}. (Hablando estrictamente, la +cantidad de eliminaciones se refiere a la cantidad de \emph{separadores de + ruta} (y los componentes que vayan con ellos) a eliminar. Si el +contador es uno volverá \filename{foo/bar} en \filename{bar}, pero +\filename{/foo/bar} (note la barra extra) en \filename{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 \hgcmd{diff} de Mercurial genera nombres de ruta +de esta forma, y la orden \hgcmd{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 \hgxcmd{mq}{qimport} en primera medida, +porque \hgxcmd{mq}{qimport} no tiene todavía una opción \texttt{-p} +option (ver~\bug{311}). Lo mejor que puede hacer es aplicar +\hgxcmd{mq}{qnew} por su cuenta, y después usar \cmdargs{patch}{-p\emph{N}} +para aplicar tal parche, seguido de \hgcmd{addremove} para tener en +cuenta cualquier fichero adicionado o eliminado por el parche, seguido +de \hgxcmd{mq}{qrefresh}. Esta complejidad puede ser innecesaria; +consulte~\bug{311} para más información. + +\subsection{Estrategias para aplicar parches} + +Cuando \command{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 \command{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 \emph{corrimiento} del número de línea original. + +Si falla la correspondencia por contexto, \command{patch} elimina la +primera y la última línea del contexto, e intenta una correspondencia +\emph{reducida} del contexto. Si el trozo con contexto reducido es +exitoso, imprime un mensaje indicando que aplicó el trozo con un +\emph{factor difuso} (el número después del factor difuso indica +cuántas líneas de contexto \command{patch} tuvo que eliminar antes de +aplicar el parche). + +Cuando ninguna de estas técnicas funciona, \command{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 \sfilename{.rej} añadida. También +almacena una copia igual al fichero original con la extensión +\sfilename{.orig}; la copia del fichero sin extensión contendrá +cualquier cambio hecho por los trozos que \emph{sí} se aplicaron sin +problema. Si usted tiene un parche que modifica \filename{foo} con +seis trozos, y uno de ellos falla al aplicarse, tendrá : un fichero +original \filename{foo.orig}, un fichero \filename{foo.rej} que +contiene el trozo, y \filename{foo}, que contiene los cambios que se +aplicaron por los cinco trozos exitosos. + +\subsection{Algunos detalles de la representación de parches} + +Hay ciertas cosas útiles por saber acerca de cómo trabaja +\command{patch} con los ficheros: +\begin{itemize} +\item Debería ser obvio que \command{patch} no puede manipular + ficheros binarios. +\item No se preocupa por el bit ejecutable; crea ficheros nuevos en + modo lectura, pero no ejecutable. +\item \command{patch} intenta eliminar un fichero como una diferencia + entre el fichero a eliminar y un fichero vacío. Y por lo tanto su + idea de ``Borré este fichero'' debería pensarse como ``toda línea de + este fichero fue eliminada'' en un parche. +\item Trata la adición de un fichero como un diff entre un fichero + vacío y el fichero a ser adicionado. Por lo tanto en un parche su + idea de ``Añadí este fichero'' se vería como ``toda línea de este + fichero fue añadida''. +\item Trata el renombramiento de un fichero como la eliminación del + nombre anterior y la adición del nuevo nombre. Esto significa que + los ficheros renombrados dejan un rastro grande en los parches. + (Tenga en cuenta que Mercurial no trata de inferir cuando los + ficheros han sido renombrados o copiados en un parche en este + momento.) +\item \command{patch} no puede representar ficheros vacíos, por lo + tanto no puede usar un parche para representar la noción ``Añadí + este fichero vacío al árbol''. +\end{itemize} +\subsection{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 \command{patch} o \hgxcmd{mq}{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. + +\subsection{Manejo de descartes} + +Si \hgxcmd{mq}{qpush} falla al aplicar un parche, mostrará un texto de +error y saldrá. Si ha dejado ficheros \sfilename{.rej}, es mejor +arreglar los trozos descartados antes de introducir parches +adicionales o hacer cualquier otra cosa. + +Si su parche \emph{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~\ref{sec:mq:merge}. + +Desafortunadamente, no hay grandes técnicas para tratar los trozos +descartados. Casi siempre deberá consultar el fichero +\sfilename{.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 \command{wiggle}~\cite{web:wiggle}, que es más +vigorosa que \command{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 +\command{mpatch}~\cite{web:mpatch}, que sigue una aproximación +sencilla para automatizar la aplicación de trozos descartados por +\command{patch}. La orden \command{mpatch} puede ayudar con cuatro +razones comunes por las cuales un parche ha sido descartado: + +\begin{itemize} +\item El contexto en la mitad de un trozo ha cambiado. +\item Un trozo ha perdido cierto contexto al principio o al final. +\item Un trozo largo podría aplicarse mejor---por completo o una + parte---si estaba cortado en trozos más pequeños. +\item Un trozo remueve líneas con contenido ligeramente diferente que + aquellas que están presentes en el fichero. +\end{itemize} + +Si usted usa \command{wiggle} o \command{mpatch}, debería ser +doblemente cuidadoso al revisar sus resultados cuando haya terminado. +De hecho, \command{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. + +\section{maximizar el rendimiento de MQ} +\label{sec:mq:perf} + +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~\cite{web:europython}. 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 +\hgcmdargs{qpush}{\hgxopt{mq}{qpush}{-a}} a los 1.738 parches en 3.5 +minutos, y \hgcmdargs{qpop}{\hgxopt{mq}{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é +\hgxcmd{mq}{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 \hgxcmd{mq}{qpush} o \hgxcmd{mq}{qpop}, tales órdenes +revisan el directorio de trabajo para asegurarse de que usted no ha +hecho cambios y ha olvidado ejecutar \hgxcmd{mq}{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 \hgxcmd{mq}{qpush} y \hgxcmd{mq}{qpop} le permiten +introducir o sustraer varios parches en una operación. Puede +identificar el ``parche destino'' que desee. Cuando aplique +\hgxcmd{mq}{qpush} con un destino, introducirá tantos parches como sea +necesario hasta que el especificado esté en el tope de la pila. +Cuando emplee \hgxcmd{mq}{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. + +\section{Actualiar los parches cuando el código cambia} +\label{sec:mq:merge} + +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 \emph{rebase} a su serie de parches. + +La vía más sencilla de hacerlo es con \hgcmdargs{qpop}{\hgxopt{mq}{qpop}{-a}} +sobre sus parches, después hacer \hgcmd{pull} de los cambios en el +repositorio, y finalmente hacer +\hgcmdargs{qpush}{\hgxopt{mq}{qpop}{-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 \hgxcmd{mq}{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: +\begin{enumerate} +\item Para comenzar, haga \hgcmdargs{qpush}{-a} sobre todos los + parches que usted sepa se aplican limpiamente. +\item Guarde una copia de seguridad de su directorio de parches con + \hgcmdargs{qsave}{\hgxopt{mq}{qsave}{-e} \hgxopt{mq}{qsave}{-c}}. + Esto imprime el nombre del directorio en el cual se han guardado los + parches. Guardará los parches en un directorio llamado + \sdirname{.hg/patches.\emph{N}}, donde \texttt{\emph{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 \sfilename{series} + y \sfilename{status}. +\item Use \hgcmd{pull} para traer los nuevos cambios en el repositorio + subyacente. (No ejecute \hgcmdargs{pull}{-u}; vea más adelante por qué.) +\item Actualice a la nueva revisión punta con + \hgcmdargs{update}{\hgopt{update}{-C}} para sobreescribir los + parches que haya introducido. +\item Fusione todos los parches con \hgcmdargs{qpush}{\hgxopt{mq}{qpush}{-m} + \hgxopt{mq}{qpush}{-a}}. La opción \hgxopt{mq}{qpush}{-m} de \hgxcmd{mq}{qpush} + le indica a MQ que haga una fusión que involucra tres fuentes si el + parche falla al aplicarse. +\end{enumerate} + +Durante el \hgcmdargs{qpush}{\hgxopt{mq}{qpush}{-m}}, cada parche en +el fichero \sfilename{series} se aplica normalmente. Si un parche se +aplica difusamente o se niea a aplicarse, MQ consulta la cola que +usted guardó con \hgxcmd{mq}{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 \sdirname{.hg/patches.\emph{N}}. Puede eliminar la cabeza +extra con \hgcmdargs{qpop}{\hgxopt{mq}{qpop}{-a} \hgxopt{mq}{qpop}{-n} patches.\emph{N}} +o \hgcmd{strip}. Puede eliminar \sdirname{.hg/patches.\emph{N}} una +vez que esté seguro de que no lo necesita más como copia de seguridad. + +\section{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 \filename{foo.patch} a \hgxcmd{mq}{qpush}, que +introducirá los parches hasta que \filename{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, \texttt{foo.patch-2} significa +``dos parches antes de \texttt{foo.patch}'', mientras que +\texttt{bar.patch+4} significa ``cuatro parches después de \texttt{bar.patch}''. + +Referirse a un parche por su índice no es muy diferente. El primer +parche que se imprime en la salida de \hgxcmd{mq}{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 \index{tags!special tag + names!\texttt{qbase}}\texttt{qbase} y \index{tags!special tag + names!\texttt{qtip}}\texttt{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. +\begin{itemize} +\item ¿Desea enviar una bomba de parches a una lista de correo con los + últimos cambios que ha hecho? + \begin{codesample4} + hg email qbase:qtip + \end{codesample4} + (¿No sabe qué es una ``bomba de parches''? Consulte la + sección~\ref{sec:hgext:patchbomb}.) +\item ¿Desea ver todos los parches desde que se aplicó + \texttt{foo.patch} sobre los ficheros de un subdirectorio en su + árbol? + \begin{codesample4} + hg log -r foo.patch:qtip \emph{subdir} + \end{codesample4} +\end{itemize} + +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. + +\begin{figure}[ht] + \interaction{mq.id.output} + \caption{Uso de las características de etiquetamiento al trabajar + con MQ} + \label{ex:mq:id} +\end{figure} + +Otra consecuencia deseable al representar los nombres de parches como +etiquetas es que cuando ejecute la orden \hgcmd{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~\ref{ex:mq:id} muestra algunos +comandos usuales de Mercurial al trabajar con parches. + +\section{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í: + +\begin{itemize} +\item Usualmente cuando hace \hgxcmd{mq}{qpop} a un parche y vuelve a + hacerle \hgxcmd{mq}{qpush}, el conjunto de cambios que representa el + parche después de introducir/sustraer tendrá una \emph{identidad + distinta} que aquella que representaba el conjunto de cambios + anteriormente. Consulte la secctión~\ref{sec:mqref:cmd:qpush} para + obtener información del por qué de esto. +\item No es una buena idea aplicar \hgcmd{merge} de cambios de otra + rama con un conjunto de cambios de parches, por lo menos si desea + mantener la ``información de parches'' de ese conjunto de cambios y + los conjuntos de cambios que se encuentran por debajo en la pila de + parches. Si intenta hacerlo, parecerá que ha sido exitoso, pero MQ + se confundirá. +\end{itemize} + +\section{Administrar parches en un repositorio} +\label{sec:mq:repo} + +Dado que el directorio \sdirname{.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 \hgxcmd{mq}{qrefresh} y +después hacer \hgcmd{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. + +\subsection{Soporte de MQ para repositorios de parches} + +MQ le ayuda a trabajar con el directorio \sdirname{.hg/patches} como +un repositorio; cuando usted prepara un repositorio para trabajar con +parches usando \hgxcmd{mq}{qinit}, puede pasarle la opción +\hgxopt{mq}{qinit}{-c} para que se cree el directorio +\sdirname{.hg/patches} como un repositorio de Mercurial. + +\begin{note} + Si olvida usar la opción \hgxopt{mq}{qinit}{-c} option, puede ir al + directorio \sdirname{.hg/patches} en cualquier momento y ejecutar + \hgcmd{init}. No olvide añadir una entrada en el fichero + \sfilename{status} del fichero \sfilename{.hgignore}, a pesar de que + (\hgcmdargs{qinit}{\hgxopt{mq}{qinit}{-c}} hace estodo de forma + automática para usted); usted \emph{seguro} no quiere administrar el + fichero \sfilename{status}. +\end{note} + +MQ nota convenientemente que el directorio \dirname{.hg/patches} +es un repositorio, hará \hgcmd{add} automáticamente a cada parche que +usted cree e importe. + +MQ provee una orden corta, \hgxcmd{mq}{qcommit}, que ejecuta +\hgcmd{commit} en el directorio \sdirname{.hg/patches}. Lo que ahorra +tecleo recurrente. + +Finalmente, para administrar convenientemente el directorio de +parches, puede definir el alias \command{mq} en sistemas Unix. Por +ejemplo, en sistemas Linux con el intérprete \command{bash}, puede +incluir el siguiente recorte de código\ndt{snippet} en su fichero +\tildefile{.bashrc}. + +\begin{codesample2} + alias mq=`hg -R \$(hg root)/.hg/patches' +\end{codesample2} + +Puede aplicar las órdenes de la forma \cmdargs{mq}{pull} al +repositorio principal. + +\subsection{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 \hgcmd{pull}, edita manualmente, o +hace \hgcmd{update} a los parches o el fichero \sfilename{series}, +tendrá que aplicar \hgcmdargs{qpop}{\hgxopt{mq}{qpop}{-a}} y después +\hgcmdargs{qpush}{\hgxopt{mq}{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. + +\section{Otras herramientas para trabajar con parches} +\label{sec:mq:tools} + +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 \command{diffstat}~\cite{web:diffstat} 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 \cmdopt{diffstat}{-p} de +\command{diffstat}, puesto que de otra forma intentará hacer cosas +inteligentes con prefijos de ficheros que terminan confundiéndome.) + +\begin{figure}[ht] + \interaction{mq.tools.tools} + \caption{Las órdenes \command{diffstat}, \command{filterdiff}, y \command{lsdiff}} + \label{ex:mq:tools} +\end{figure} + +El paquete \package{patchutils}~\cite{web:patchutils} 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 \package{patchutils} que más uso es +\command{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 +\command{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~\ref{mq-collab:tips:interdiff}. + +\section{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 \filename{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 +\emph{estará} ejecutando regularmente órdenes como +\hgxcmd{mq}{qapplied} y \hgxcmd{mq}{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 \hgxcmd{mq}{qtop} +para dar un vistazo al texto de sus parches frecuentemente---por +ejemplo, use \hgcmdargs{tip}{\hgopt{tip}{-p}})---para asegurarse en +dónde está ubicado. En distintas oportunidades me sucedió que apliqué +\hgxcmd{mq}{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~\ref{sec:mq:tools}, particularmente \command{diffstat} y +\command{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. + +\section{Recetas de MQ} + +\subsection{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: +\interaction{mq.tarball.download} + +Continue creando una pila de parches y haga sus cambios. +\interaction{mq.tarball.qinit} + +Digamos que pasan unas semanas o meses, y el autor del paquete libera +una nueva versión. Primero se traen sus cambios al repositorio. +\interaction{mq.tarball.newsource} +La porción que comienza con \hgcmd{locate} mostrada más arriba, borra +todos los ficheros en el directorio de trabajo, así que la opción +\hgopt{commit}{--addremove} de \hgcmd{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 +\interaction{mq.tarball.repush} + +\subsection{Combinar parches completos} +\label{sec:mq:combine} + +MQ provee la orden \hgxcmd{mq}{qfold} que le permite combinar parches +enteros. Se ``integran''\ndt{fold} 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 \texttt{foo}, e integra \hgxcmd{mq}{qfold} \texttt{bar} y +\texttt{quux} en él, terminará con un parche que tiene el mismo efecto +que si hubiera aplicado primero \texttt{foo}, y después \texttt{bar}, +seguido de \texttt{quux}. + +\subsection{Fusionar una porción de un parche dentro de otro} + +Fusionar \emph{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 +\command{filterdiff}'s \cmdopt{filterdiff}{-i} y +\cmdopt{filterdiff}{-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 \hgxcmd{mq}{qpush} (de los +trozos que haya movido al otro parche), y puede sencillamente aplicar +\hgxcmd{mq}{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 +\cmdargs{lsdiff}{-nvv} para imprimir algunos metadatos del parche. +\interaction{mq.tools.lsdiff} + +Esta orden imprime tres clases diferentes de números: +\begin{itemize} +\item (en la primera columna) un \emph{número de fichero} para + identificar cada fichero modificado en el parche; +\item (En la siguiente línea, indentado) el número de línea dentro de + un fichero modificado donde comienza el trozo; y +\item (en la misma línea) un \emph{número de trozo} que identifica el + trozo. +\end{itemize} + +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 \cmdopt{filterdiff}{--files} y +\cmdopt{filterdiff}{--hunks} de \command{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~\ref{sec:mq:combine}. + +\section{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 ``\texttt{q}''. Las +excepciones son las órdenes \texttt{add} y \texttt{remove} de quilt, +que realmente son las órdenes \hgcmd{add} y \hgcmd{remove} de +Mercurial. No hay un equivalente en MQ para la orden +\texttt{edit} de quilt. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/preface.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,74 @@ +\chapter*{Prefacio} +\addcontentsline{toc}{chapter}{Prefacio} +\label{chap:preface} + +El control distribuido de revisiones es un territorio relativamente +nuevo, y ha crecido hasta ahora +% TODO el original dice "due to", que sería "debido", pero creo que "gracias +% a" queda mejor +gracias a a la voluntad que tiene la gente de salir y explorar +territorios desconocidos. +% TODO revisar la frase anterior. me tomé muchas licencias para +% traducirla + +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 +%TODO puse explorar en vez de aprender, you be the judge dear reviewer ;) +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. + +\section{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. + +\section{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 +% TODO shell script +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, +% TODO scripts +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 \emph{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. + +% TODO commit +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 \hgext{bisect} en la +sección~\ref{sec:undo:bisect}, 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 +\emph{tenga} confianza en que el comportamiento que está viendo es +consistente y reproducible. + +\section{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 \href{http://www.inkscape.org/}{Inkscape}. + +El código fuente completo para este libro es publicado como un +repositorio Mercurial, en \url{http://hg.serpentine.com/mercurial/book}. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/revlog.svg Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,1164 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docbase="/home/bos/hg/hgbook/en" + sodipodi:docname="revlog.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs4"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective2726" /> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mend" + style="overflow:visible;"> + <path + id="path4852" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;" + transform="scale(0.4) rotate(180) translate(10,0)" /> + </marker> + <linearGradient + id="linearGradient3092"> + <stop + style="stop-color:#44436f;stop-opacity:1;" + offset="0" + id="stop3094" /> + <stop + style="stop-color:#abade5;stop-opacity:1;" + offset="1" + id="stop3096" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3092" + id="linearGradient3118" + gradientUnits="userSpaceOnUse" + x1="176.16635" + y1="405.21934" + x2="417.11935" + y2="405.21934" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3092" + id="linearGradient3120" + gradientUnits="userSpaceOnUse" + x1="176.16635" + y1="405.21934" + x2="417.11935" + y2="405.21934" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3092" + id="linearGradient3129" + gradientUnits="userSpaceOnUse" + x1="176.16635" + y1="405.21934" + x2="417.11935" + y2="405.21934" + gradientTransform="translate(-0.928574,-1.428574)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3092" + id="linearGradient3133" + gradientUnits="userSpaceOnUse" + x1="176.16635" + y1="405.21934" + x2="417.11935" + y2="405.21934" + gradientTransform="translate(-0.928574,-1.428574)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3092" + id="linearGradient3708" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.423343,0,0,0.423343,138.874,-67.01732)" + x1="175.23776" + y1="509.98154" + x2="416.29077" + y2="297.49997" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3092" + id="linearGradient5164" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.423343,0,0,0.423343,198.249,247.4358)" + x1="175.23776" + y1="509.98154" + x2="416.29077" + y2="297.49997" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3092" + id="linearGradient5584" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.423343,0,0,0.423343,143.9081,371.2915)" + x1="175.23776" + y1="509.98154" + x2="416.29077" + y2="297.49997" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3092" + id="linearGradient5784" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.423343,0,0,0.423343,76.37397,152.137)" + x1="175.23776" + y1="509.98154" + x2="416.29077" + y2="297.49997" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3092" + id="linearGradient5786" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.423343,0,0,0.423343,198.249,152.137)" + x1="175.23776" + y1="509.98154" + x2="416.29077" + y2="297.49997" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3092" + id="linearGradient5895" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.423343,0,0,0.423343,198.0215,261.7142)" + x1="175.23776" + y1="509.98154" + x2="416.29077" + y2="297.49997" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3092" + id="linearGradient5958" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.423343,0,0,0.423343,137.1978,42.55987)" + x1="175.23776" + y1="509.98154" + x2="416.29077" + y2="297.49997" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.8101934" + inkscape:cx="199.78816" + inkscape:cy="863.27363" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="906" + inkscape:window-height="659" + inkscape:window-x="29" + inkscape:window-y="79" + inkscape:connector-spacing="11" + showgrid="false" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <rect + y="168.74846" + x="211.58516" + height="89.506805" + width="101.60232" + id="rect3068" + style="fill:url(#linearGradient5958);fill-opacity:1;stroke:black;stroke-width:0.48811448;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + id="g3215" + transform="matrix(0.423343,0,0,0.423343,137.1977,42.55985)"> + <rect + y="447.71451" + x="299.67859" + height="48.571426" + width="103.14286" + id="rect2899" + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text2903" + y="464.8139" + x="308.89639" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + y="464.8139" + x="308.89639" + sodipodi:role="line" + id="tspan2905">Segundo padre</tspan></text> + <text + id="text2907" + y="485.50256" + x="308.20175" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="485.50256" + x="308.20175" + id="tspan2909" + sodipodi:role="line">32bf9a5f22c0</tspan></text> + </g> + <g + id="g3250" + transform="matrix(0.423343,0,0,0.423343,137.1977,42.55986)"> + <rect + y="311.28598" + x="188.6071" + height="48.571426" + width="103.14286" + id="rect2936" + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text2940" + y="328.38538" + x="197.82495" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + y="328.38538" + x="197.82495" + sodipodi:role="line" + id="tspan2942">Hash de revisión</tspan></text> + <text + id="text2944" + y="349.07404" + x="197.13031" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="349.07404" + x="197.13031" + id="tspan2946" + sodipodi:role="line">34b8b7a15ea1</tspan></text> + </g> + <g + id="g3243" + transform="matrix(0.423343,0,0,0.423343,137.6664,43.91853)"> + <rect + y="363.07654" + x="187.5" + height="75" + width="213.85715" + id="rect2950" + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text2958" + y="400.86459" + x="196.02321" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="fill:black;fill-opacity:1;font-family:Courier" + y="400.86459" + x="196.02321" + id="tspan2960" + sodipodi:role="line">...</tspan></text> + <text + id="text2954" + y="380.17593" + x="196.71785" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + y="380.17593" + x="196.71785" + sodipodi:role="line" + id="tspan2956" + style="fill:#000000;fill-opacity:1">Datos de Revisión (delta o snapshot)</tspan></text> + </g> + <g + id="g5529" + transform="translate(-6.710312,-8.165836e-6)"> + <rect + style="fill:url(#linearGradient5584);fill-opacity:1;stroke:black;stroke-width:0.48811448;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3509" + width="101.60232" + height="89.506805" + x="218.29547" + y="497.4801" /> + <g + transform="matrix(0.423343,0,0,0.423343,143.908,371.2915)" + id="g3513"> + <g + id="g3515"> + <rect + y="447.72418" + x="188.6071" + height="48.571426" + width="103.14286" + id="rect3517" + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text3519" + y="464.82358" + x="197.82495" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + y="464.82358" + x="197.82495" + sodipodi:role="line" + id="tspan3521">Primer padre</tspan></text> + <text + id="text3523" + y="485.51224" + x="197.13031" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="485.51224" + x="197.13031" + id="tspan3525" + sodipodi:role="line">000000000000</tspan></text> + </g> + <g + id="g3527"> + <rect + y="447.71451" + x="299.67859" + height="48.571426" + width="103.14286" + id="rect3529" + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text3531" + y="464.8139" + x="308.89639" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + y="464.8139" + x="308.89639" + sodipodi:role="line" + id="tspan3533">Segundo padre</tspan></text> + <text + id="text3535" + y="485.50256" + x="308.20175" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="485.50256" + x="308.20175" + id="tspan3537" + sodipodi:role="line">000000000000</tspan></text> + </g> + </g> + <g + transform="matrix(0.423343,0,0,0.423343,143.908,371.2915)" + id="g3539"> + <rect + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3541" + width="103.14286" + height="48.571426" + x="188.6071" + y="311.28598" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="197.82495" + y="328.38538" + id="text3543"><tspan + id="tspan3545" + sodipodi:role="line" + x="197.82495" + y="328.38538">Hash de revisión</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="197.13031" + y="349.07404" + id="text3547"><tspan + sodipodi:role="line" + id="tspan3549" + x="197.13031" + y="349.07404" + style="font-family:Courier">ff9dc8bc2a8b</tspan></text> + </g> + <g + transform="matrix(0.423343,0,0,0.423343,144.3767,372.6502)" + id="g3551"> + <rect + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3553" + width="213.85715" + height="75" + x="187.5" + y="363.07654" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="196.02321" + y="400.86459" + id="text3555"><tspan + sodipodi:role="line" + id="tspan3557" + x="196.02321" + y="400.86459" + style="fill:black;fill-opacity:1;font-family:Courier">...</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="196.71785" + y="380.17593" + id="text3559"><tspan + style="fill:#000000;fill-opacity:1" + id="tspan3561" + sodipodi:role="line" + x="196.71785" + y="380.17593">Datos de revisión (delta o snapshot)</tspan></text> + </g> + </g> + <g + id="g4868" + transform="translate(-1.676208,-2.342463e-5)"> + <rect + style="fill:url(#linearGradient3708);fill-opacity:1;stroke:black;stroke-width:0.48811448;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3567" + width="101.60232" + height="89.506805" + x="213.26137" + y="59.171272" /> + <g + transform="matrix(0.423343,0,0,0.423343,138.8739,-67.01734)" + id="g3573"> + <rect + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3575" + width="103.14286" + height="48.571426" + x="188.6071" + y="447.72418" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="197.82495" + y="464.82358" + id="text3577"><tspan + id="tspan3579" + sodipodi:role="line" + x="197.82495" + y="464.82358">Primer padre</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="197.13031" + y="485.51224" + id="text3581"><tspan + sodipodi:role="line" + id="tspan3583" + x="197.13031" + y="485.51224" + style="font-family:Courier">34b8b7a15ea1</tspan></text> + </g> + <g + transform="matrix(0.423343,0,0,0.423343,138.8739,-67.01734)" + id="g3585"> + <rect + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3587" + width="103.14286" + height="48.571426" + x="299.67859" + y="447.71451" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="308.89639" + y="464.8139" + id="text3589"><tspan + id="tspan3591" + sodipodi:role="line" + x="308.89639" + y="464.8139">Segundo padre</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="308.20175" + y="485.50256" + id="text3593"><tspan + sodipodi:role="line" + id="tspan3595" + x="308.20175" + y="485.50256" + style="font-family:Courier">000000000000</tspan></text> + </g> + <g + transform="matrix(0.423343,0,0,0.423343,138.8739,-67.01733)" + id="g3597"> + <rect + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3599" + width="103.14286" + height="48.571426" + x="188.6071" + y="311.28598" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="197.82495" + y="328.38538" + id="text3601"><tspan + id="tspan3603" + sodipodi:role="line" + x="197.82495" + y="328.38538">Hash de revisión</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="197.13031" + y="349.07404" + id="text3605"><tspan + sodipodi:role="line" + id="tspan3607" + x="197.13031" + y="349.07404" + style="font-family:Courier">1b67dc96f27a</tspan></text> + </g> + <g + transform="matrix(0.423343,0,0,0.423343,139.3426,-65.65866)" + id="g3609"> + <rect + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3611" + width="213.85715" + height="75" + x="187.5" + y="363.07654" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="196.02321" + y="400.86459" + id="text3613"><tspan + sodipodi:role="line" + id="tspan3615" + x="196.02321" + y="400.86459" + style="fill:black;fill-opacity:1;font-family:Courier">...</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="196.71785" + y="380.17593" + id="text3617"><tspan + style="fill:#000000;fill-opacity:1" + id="tspan3619" + sodipodi:role="line" + x="196.71785" + y="380.17593">Datos de revisión (delta o snapshot)</tspan></text> + </g> + </g> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:none;marker-end:url(#Arrow1Mend)" + d="M 240.78255,143.08593 L 241.42595,171.75349" + id="path3801" + inkscape:connector-type="polyline" + inkscape:connection-start="#g3573" + inkscape:connection-end="#g3250" /> + <g + id="g5677"> + <rect + style="fill:url(#linearGradient5784);fill-opacity:1;stroke:black;stroke-width:0.48811448;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3393" + width="101.60232" + height="89.506805" + x="150.76137" + y="278.32565" /> + <g + transform="matrix(0.423343,0,0,0.423343,76.37397,152.137)" + id="g3399"> + <rect + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3401" + width="103.14286" + height="48.571426" + x="188.6071" + y="447.72418" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="197.82495" + y="464.82358" + id="text3403"><tspan + id="tspan3405" + sodipodi:role="line" + x="197.82495" + y="464.82358">Primer padre</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="197.13031" + y="485.51224" + id="text3407"><tspan + sodipodi:role="line" + id="tspan3409" + x="197.13031" + y="485.51224" + style="font-family:Courier">ff9dc8bc2a8b</tspan></text> + </g> + <g + transform="matrix(0.423343,0,0,0.423343,76.37397,152.137)" + id="g3411"> + <rect + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3413" + width="103.14286" + height="48.571426" + x="299.67859" + y="447.71451" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="308.89639" + y="464.8139" + id="text3415"><tspan + id="tspan3417" + sodipodi:role="line" + x="308.89639" + y="464.8139">Segundo padre</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="308.20175" + y="485.50256" + id="text3419"><tspan + sodipodi:role="line" + id="tspan3421" + x="308.20175" + y="485.50256" + style="font-family:Courier">000000000000</tspan></text> + </g> + <g + transform="matrix(0.423343,0,0,0.423343,76.37397,152.137)" + id="g3423"> + <rect + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3425" + width="103.14286" + height="48.571426" + x="188.6071" + y="311.28598" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="197.82495" + y="328.38538" + id="text3427"><tspan + id="tspan3429" + sodipodi:role="line" + x="197.82495" + y="328.38538">Hash de revisión</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="197.13031" + y="349.07404" + id="text3431"><tspan + sodipodi:role="line" + id="tspan3433" + x="197.13031" + y="349.07404" + style="font-family:Courier">5b80c922ebdd</tspan></text> + </g> + <g + transform="matrix(0.423343,0,0,0.423343,76.84265,153.4957)" + id="g3435"> + <rect + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3437" + width="213.85715" + height="75" + x="187.5" + y="363.07654" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="196.02321" + y="400.86459" + id="text3439"><tspan + sodipodi:role="line" + id="tspan3441" + x="196.02321" + y="400.86459" + style="fill:black;fill-opacity:1;font-family:Courier">...</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="196.71785" + y="380.17593" + id="text3443"><tspan + style="fill:#000000;fill-opacity:1" + id="tspan3445" + sodipodi:role="line" + x="196.71785" + y="380.17593">Datos de revisión (delta o snapshot)</tspan></text> + </g> + </g> + <g + id="g5646" + transform="translate(-0.227432,0)"> + <rect + style="fill:url(#linearGradient5786);fill-opacity:1;stroke:black;stroke-width:0.48811448;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3451" + width="101.60232" + height="89.506805" + x="272.63638" + y="278.32565" /> + <g + transform="matrix(0.423343,0,0,0.423343,198.2489,152.137)" + id="g3457"> + <rect + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3459" + width="103.14286" + height="48.571426" + x="188.6071" + y="447.72418" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="197.82495" + y="464.82358" + id="text3461"><tspan + id="tspan3463" + sodipodi:role="line" + x="197.82495" + y="464.82358">Primer padre</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="197.13031" + y="485.51224" + id="text3465"><tspan + sodipodi:role="line" + id="tspan3467" + x="197.13031" + y="485.51224" + style="font-family:Courier">ecacb6b4c9fd</tspan></text> + </g> + <g + transform="matrix(0.423343,0,0,0.423343,198.2489,152.137)" + id="g3469"> + <rect + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3471" + width="103.14286" + height="48.571426" + x="299.67859" + y="447.71451" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="308.89639" + y="464.8139" + id="text3473"><tspan + id="tspan3475" + sodipodi:role="line" + x="308.89639" + y="464.8139">Segundo padre</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="308.20175" + y="485.50256" + id="text3477"><tspan + sodipodi:role="line" + id="tspan3479" + x="308.20175" + y="485.50256" + style="font-family:Courier">000000000000</tspan></text> + </g> + <g + transform="matrix(0.423343,0,0,0.423343,198.2489,152.137)" + id="g3481"> + <rect + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3483" + width="103.14286" + height="48.571426" + x="188.6071" + y="311.28598" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="197.82495" + y="328.38538" + id="text3485"><tspan + id="tspan3487" + sodipodi:role="line" + x="197.82495" + y="328.38538">Hash de revisión</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="197.13031" + y="349.07404" + id="text3489"><tspan + sodipodi:role="line" + id="tspan3491" + x="197.13031" + y="349.07404" + style="font-family:Courier">32bf9a5f22c0</tspan></text> + </g> + <g + transform="matrix(0.423343,0,0,0.423343,198.7176,153.4957)" + id="g3493"> + <rect + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3495" + width="213.85715" + height="75" + x="187.5" + y="363.07654" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="196.02321" + y="400.86459" + id="text3497"><tspan + sodipodi:role="line" + id="tspan3499" + x="196.02321" + y="400.86459" + style="fill:black;fill-opacity:1;font-family:Courier">...</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="196.71785" + y="380.17593" + id="text3501"><tspan + style="fill:#000000;fill-opacity:1" + id="tspan3503" + sodipodi:role="line" + x="196.71785" + y="380.17593">Datos de revisión (delta o snapshot)</tspan></text> + </g> + </g> + <rect + y="387.90286" + x="272.40894" + height="89.506805" + width="101.60232" + id="rect5081" + style="fill:url(#linearGradient5895);fill-opacity:1;stroke:black;stroke-width:0.48811448;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + id="g5087" + transform="matrix(0.423343,0,0,0.423343,198.0214,261.7142)"> + <rect + y="447.72418" + x="188.6071" + height="48.571426" + width="103.14286" + id="rect5089" + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text5091" + y="464.82358" + x="197.82495" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + y="464.82358" + x="197.82495" + sodipodi:role="line" + id="tspan5093">Primer padre</tspan></text> + <text + id="text5095" + y="485.51224" + x="197.13031" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="485.51224" + x="197.13031" + id="tspan5097" + sodipodi:role="line">ff9dc8bc2a8b</tspan></text> + </g> + <g + id="g5099" + transform="matrix(0.423343,0,0,0.423343,198.0214,261.7142)"> + <rect + y="447.71451" + x="299.67859" + height="48.571426" + width="103.14286" + id="rect5101" + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text5103" + y="464.8139" + x="308.89639" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + y="464.8139" + x="308.89639" + sodipodi:role="line" + id="tspan5105">Segundo padre</tspan></text> + <text + id="text5107" + y="485.50256" + x="308.20175" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="485.50256" + x="308.20175" + id="tspan5109" + sodipodi:role="line">000000000000</tspan></text> + </g> + <g + id="g5111" + transform="matrix(0.423343,0,0,0.423343,198.0214,261.7142)"> + <rect + y="311.28598" + x="188.6071" + height="48.571426" + width="103.14286" + id="rect5113" + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text5115" + y="328.38538" + x="197.82495" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + y="328.38538" + x="197.82495" + sodipodi:role="line" + id="tspan5117">Hash de revisión</tspan></text> + <text + id="text5119" + y="349.07404" + x="197.13031" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="349.07404" + x="197.13031" + id="tspan5121" + sodipodi:role="line">ecacb6b4c9fd</tspan></text> + </g> + <g + id="g5123" + transform="matrix(0.423343,0,0,0.423343,198.4901,263.0729)"> + <rect + y="363.07654" + x="187.5" + height="75" + width="213.85715" + id="rect5125" + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text5127" + y="400.86459" + x="196.02321" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="fill:black;fill-opacity:1;font-family:Courier" + y="400.86459" + x="196.02321" + id="tspan5129" + sodipodi:role="line">...</tspan></text> + <text + id="text5131" + y="380.17593" + x="196.71785" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + y="380.17593" + x="196.71785" + sodipodi:role="line" + id="tspan5133" + style="fill:#000000;fill-opacity:1">Datos de revisión (delta o snapshot)</tspan></text> + </g> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1;display:inline" + d="M 299.69935,362.24027 L 299.69931,393.49494" + id="path5203" + inkscape:connector-type="polyline" + inkscape:connection-start="#g3457" + inkscape:connection-end="#g5111" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 182.35357,362.22647 L 241.2842,503.07224" + id="path5271" + inkscape:connector-type="polyline" + inkscape:connection-start="#g3399" + inkscape:connection-end="#g3539" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1;display:inline" + d="M 287.63109,471.81747 L 250.9438,503.07223" + id="path5285" + inkscape:connector-type="polyline" + inkscape:connection-start="#g5087" + inkscape:connection-end="#g3539" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow1Mend)" + d="M 290.80419,250.07192 L 297.80065,283.90394" + id="path5077" + inkscape:connector-type="polyline" + inkscape:connection-start="#g3215" + inkscape:connection-end="#g3481" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow1Mend)" + d="M 229.63373,250.07601 L 190.07484,283.90394" + id="path5075" + inkscape:connector-type="polyline" + inkscape:connection-end="#g3423" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="131.5625" + y="100.79968" + id="text5897"><tspan + sodipodi:role="line" + id="tspan5899" + x="131.5625" + y="100.79968" + style="text-align:end;text-anchor:end">Revisión principal</tspan><tspan + sodipodi:role="line" + x="131.5625" + y="115.79968" + id="tspan5901" + style="text-align:end;text-anchor:end">(sin hijos)</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="131.5625" + y="207.04968" + id="text5903"><tspan + sodipodi:role="line" + id="tspan5905" + x="131.5625" + y="207.04968" + style="text-align:end;text-anchor:end">Revisión de fusión</tspan><tspan + sodipodi:role="line" + x="131.5625" + y="222.04968" + id="tspan5907" + style="text-align:end;text-anchor:end">(dos padres)</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="131.92578" + y="451.58093" + id="text5909"><tspan + sodipodi:role="line" + id="tspan5911" + x="131.92578" + y="451.58093" + style="text-align:end;text-anchor:end">Ramas</tspan><tspan + sodipodi:role="line" + x="131.92578" + y="466.58093" + id="tspan5913" + style="text-align:end;text-anchor:end">(dos revisiones,</tspan><tspan + sodipodi:role="line" + x="131.92578" + y="481.58093" + id="tspan5915" + style="text-align:end;text-anchor:end">mismo padre)</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:2, 1;stroke-dashoffset:0;stroke-opacity:1;display:inline" + d="M 111.71875,433.61218 L 154.7268,368.52294" + id="path5917" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:2, 1;stroke-dashoffset:0;stroke-opacity:1;display:inline" + d="M 134.375,464.86218 L 277.86691,440.37816" + id="path5919" + inkscape:connector-type="polyline" + inkscape:connection-end="#g5123" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;text-align:end;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="131.5625" + y="536.73718" + id="text5927"><tspan + sodipodi:role="line" + id="tspan5929" + x="131.5625" + y="536.73718">Primera revisión</tspan><tspan + sodipodi:role="line" + x="131.5625" + y="551.73718" + id="tspan5931">(ambos padres nulos)</tspan></text> + <rect + style="fill:#bbb4ff;fill-opacity:1;stroke:none;stroke-width:0.95291203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect2830" + width="43.664806" + height="20.562374" + x="217.0432" + y="232.10075" /> + <text + xml:space="preserve" + style="font-size:5.0801158px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="220.94551" + y="239.33966" + id="text2832"><tspan + id="tspan2836" + sodipodi:role="line" + x="220.94551" + y="239.33966">Primer padre</tspan></text> + <text + xml:space="preserve" + style="font-size:5.0801158px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="220.65144" + y="248.09805" + id="text2879"><tspan + sodipodi:role="line" + id="tspan2881" + x="220.65144" + y="248.09805" + style="font-family:Courier">5b80c922ebdd</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:2, 1;stroke-dashoffset:0;stroke-opacity:1;display:inline" + d="M 139.84375,107.83093 L 210.15625,107.83093" + id="path5965" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:2, 1;stroke-dashoffset:0;stroke-opacity:1;display:inline" + d="M 137.5,213.29968 L 210.49036,214.09055" + id="path5967" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:2, 1;stroke-dashoffset:0;stroke-opacity:1;display:inline" + d="M 136.34375,544.54968 L 206.65625,544.54968" + id="path5969" + inkscape:connector-type="polyline" + inkscape:transform-center-y="-171.09375" + inkscape:transform-center-x="53.90625" /> + </g> +</svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/snapshot.svg Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,212 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg2807" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docbase="/home/bos/hg/hgbook/en" + sodipodi:docname="snapshot.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs2809"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective2759" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.4" + inkscape:cx="252.04111" + inkscape:cy="605.75448" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="906" + inkscape:window-height="721" + inkscape:window-x="141" + inkscape:window-y="57" + showgrid="false" /> + <metadata + id="metadata2812"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <rect + style="opacity:1;fill:#d3ceff;fill-opacity:1;stroke:#a7a7a7;stroke-width:1.88795626;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect2817" + width="118.18347" + height="245.32632" + x="243.05112" + y="315.4133" + inkscape:transform-center-x="136.84403" + inkscape:transform-center-y="-66.529183" /> + <rect + y="315.04153" + x="46.965065" + height="97.803009" + width="108.92702" + id="rect2815" + style="fill:#ffced6;fill-opacity:1;stroke:#a7a7a7;stroke-width:1.14441991;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + id="g3814"> + <rect + y="348.94302" + x="59.285713" + height="30" + width="84.285713" + id="rect2819" + style="fill:#ff6e86;fill-opacity:1;stroke:#a7a7a7;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + ry="0" /> + <text + id="text2821" + y="368.02701" + x="72.717636" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + y="368.02701" + x="72.717636" + id="tspan2823" + sodipodi:role="line">Índice, rev 7</tspan></text> + </g> + <text + id="text3722" + y="303.43359" + x="22.61635" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + y="303.43359" + x="22.61635" + id="tspan3724" + sodipodi:role="line">Índice de bitácora de revisiones (archivo .i)</tspan></text> + <text + id="text3726" + y="301.29074" + x="241.90207" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + y="301.29074" + x="241.90207" + id="tspan3728" + sodipodi:role="line">Datos de Bitacora de revisiones (archivo .d)</tspan></text> + <path + style="fill:#c695ff;fill-opacity:0.60109288;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 143.57143,348.07647 L 255,368.07646 L 255.71429,544.50504 L 142.85714,379.50504 L 143.57143,348.07647 z " + id="path3839" + sodipodi:nodetypes="ccccc" /> + <rect + style="fill:#4733ff;fill-opacity:1;stroke:#a7a7a7;stroke-width:2.35124183;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3752" + width="92.720184" + height="67.005905" + x="255.42564" + y="368.64264" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="264.45859" + y="387.30099" + id="text3754"><tspan + sodipodi:role="line" + id="tspan3756" + x="264.45859" + y="387.30099">Snapshot, rev 4</tspan></text> + <rect + style="fill:#7c6eff;fill-opacity:1;stroke:#a7a7a7;stroke-width:1.57776296;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3761" + width="93.49366" + height="29.922237" + x="255.03891" + y="442.04395" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="263.2662" + y="460.17206" + id="text3763"><tspan + sodipodi:role="line" + id="tspan3765" + x="263.2662" + y="460.17206">Delta, rev 4 a 5</tspan></text> + <rect + style="fill:#7c6eff;fill-opacity:1;stroke:#a7a7a7;stroke-width:1.57776296;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3774" + width="93.49366" + height="29.922237" + x="255.03891" + y="477.97485" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="263.2662" + y="496.10297" + id="text3776"><tspan + sodipodi:role="line" + id="tspan3778" + x="263.2662" + y="496.10297">Delta, rev 5 a 6</tspan></text> + <rect + style="fill:#7c6eff;fill-opacity:1;stroke:#a7a7a7;stroke-width:1.57776296;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3782" + width="93.49366" + height="29.922237" + x="255.03891" + y="513.90576" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="263.2662" + y="532.03387" + id="text3784"><tspan + sodipodi:role="line" + id="tspan3786" + x="263.2662" + y="532.03387">Delta, rev 6 a 7</tspan></text> + <rect + style="fill:#7c6eff;fill-opacity:1;stroke:#a7a7a7;stroke-width:1.57776296;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3889" + width="93.49366" + height="29.922237" + x="255.03891" + y="332.32489" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="263.2662" + y="350.453" + id="text3891"><tspan + sodipodi:role="line" + id="tspan3893" + x="263.2662" + y="350.453">Delta, rev 2 a 3</tspan></text> + </g> +</svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/srcinstall.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,55 @@ +\chapter{Instalar Mercurial desde las fuentes} +\label{chap:srcinstall} + +\section{En un sistema tipo Unix} +\label{sec:srcinstall:unixlike} + +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. +\begin{enumerate} +\item Descargue un paquete fuente reciente de + \url{http://www.selenic.com/mercurial/download}. +\item Descomprímalo: + \begin{codesample4} + gzip -dc mercurial-\emph{version}.tar.gz | tar xf - + \end{codesample4} +\item Vaya al directorio fuente y ejecute el guión de instalación. + Esto armará Mercurial y lo instalará en su directorio casa: + \begin{codesample4} + cd mercurial-\emph{version} + python setup.py install --force --home=\$HOME + \end{codesample4} +\end{enumerate} +Cuando termine la instalación, Mercurial estará en el subdirectorio +\texttt{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 +\envar{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 \texttt{/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 \texttt{mercurial} se +instalaron. + +\section{En Windows} + +Armar e instalar Mercurial en Windows requiere una variedad de +herramientas, cierta suficiencia técnica y paciencia considerable. +Personalmente, \emph{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 +\url{http://www.selenic.com/mercurial/wiki/index.cgi/WindowsInstall}, +y espere que el proceso sea realmente un trabajo duro. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/template.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,497 @@ +\chapter{Personalizar los mensajes de Mercurial} +\label{chap:template} + +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. + +\section{Usar estilos que vienen con Mercurial} +\label{sec:style} + +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. + +\interaction{template.simple.normal} + +Es en cierta medida informativa, pero ocupa mucho espacio---cinco +líneas de salida por cada conjunto de cambios. El estilo +\texttt{compact} lo reduce a tres líneas, presentadas de forma +suscinta. + +\interaction{template.simple.compact} + +El estilo de la \texttt{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\cite{web:changelog}. + +\interaction{template.simple.changelog} + +No es una sorpresa que el estilo predeterminado de Mercurial se llame +\texttt{default}\ndt{predeterminado}. + +\subsection{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. + +\begin{codesample2} + [ui] + style = compact +\end{codesample2} + +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 +\texttt{templates} de su directorio de instalación de Mercurial). + +\section{Órdenes que soportan estilos y plantillas} + +Todas las órdenes de Mercurial``relacionadas con \texttt{log}'' le +permiten usar estilos y plantillas: \hgcmd{incoming}, \hgcmd{log}, +\hgcmd{outgoing} y \hgcmd{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. + +\section{Cuestiones básicas de plantillas} + +Una plantilla de Mercurial es sencillamente una pieza de texto. +Cierta porción nunca cambia, otras partes se \emph{expanden}, o +reemplazan con texto nuevo cuando es necesario. + +Antes de continuar, veamos de nuevo un ejemplo sencillo de la salida +usual de Mercurial: + +\interaction{template.simple.normal} + +Ahora, ejecutemos la misma orden, pero usemos una plantilla para +modificar su salida: + +\interaction{template.simple.simplest} + +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 \hgopt{log}{--template} de la +orden \hgcmd{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 +``\Verb+\n+''. Es una \emph{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~\ref{sec:template:escape}. + +Una plantilla que imprime una cadena fija de texto siempre no es muy +útil; intentemos algo un poco más complejo. + +\interaction{template.simple.simplesub} + +Como puede ver, la cadena ``\Verb+{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 (``\texttt{\{}'' y ``\texttt{\}}''), 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~\ref{sec:template:escape}. + +\section{Palabras claves más comunes en las plantillas} +\label{sec:template:keyword} + +Puede empezar a escribir plantillas sencillas rápidamente con las +palabras claves descritas a continuación: + +\begin{itemize} +\item[\tplkword{author}] Cadena. El autor NO modificado del conjunto + de cambios. +\item[\tplkword{branches}] Cadena. El nombre de la rama en la cual se + consignó el conjunto de cambios. Será vacía si el nombre de la rama es + \texttt{default}. +\item[\tplkword{date}] Información de fecha. La fecha en la cual se + consignó el conjunto de cambios. \emph{No} es legible por un + humano, debe pasarla por un firltro que la desplegará + apropiadamente. En la sección~\ref{sec:template:filter} hay más + detalles acerca de filtros. La fecha se expresa como un par de + números. El primer número corresponde a una marca de tiempo UNIX + UTC (segundos desde el primero de enero de 1970); la segunda es el + corrimiento horario de la zona horaria del UTC en la cual se encontraba + quien hizo la consignación, en segundos. +\item[\tplkword{desc}] Cadena. La descripción en texto del conjunto + de cambios. +\item[\tplkword{files}] Lista de cadenas. Todos los ficheros + modificados, adicionados o eliminados por este conjunto de cambios. +\item[\tplkword{file\_adds}] Lista de cadenas. Ficheros adicionados + por este conjunto de cambios. +\item[\tplkword{file\_dels}] Lista de cadenas. Ficheros eliminados + por este conjunto de cambios. +\item[\tplkword{node}] Cadena. El hash de identificación de este + conjunto de cambios como una cadena hexadecimal de 40 caracteres. +\item[\tplkword{parents}] Lista de cadenas. Los ancestros del + conjunto de cambios. +\item[\tplkword{rev}] Entero. El número de revisión del repositorio + local. +\item[\tplkword{tags}] Lista de cadenas. Todas las etiquetas + asociadas al conjunto de cambios. +\end{itemize} + +Unos experimentos sencillos nos mostrarán qué esperar cuando usamos +estas palabras claves; puede ver los resultados en la +figura~\ref{fig:template:keywords}. + +\begin{figure} + \interaction{template.simple.keywords} + \caption{Template keywords in use} + \label{fig:template:keywords} +\end{figure} + +Como mencionamos anteriormente, la palabra clave de fecha no produce +salida legible por un humano, debemos tratarla de forma especial. +Esto involucra usar un \emph{filtro}, acerca de lo cual hay más en la +sección~\ref{sec:template:filter}. + +\interaction{template.simple.datekeyword} + +\section{Secuencias de Control} +\label{sec:template:escape} + +El motor de plantillas de Mercurial reconoce las secuencias de control +más comunmente usadas dentro de las cadenas. Cuando ve un backslash +(``\Verb+\+''), ve el caracter siguiente y sustituye los dos +caracteres con un reemplazo sencillo, como se describe a continuación: + +\begin{itemize} +\item[\Verb+\textbackslash\textbackslash+] Backslash, ``\Verb+\+'', + ASCII~134. +\item[\Verb+\textbackslash n+] Nueva línea, ASCII~12. +\item[\Verb+\textbackslash r+] Cambio de línea, ASCII~15. +\item[\Verb+\textbackslash t+] Tab, ASCII~11. +\item[\Verb+\textbackslash v+] Tab Vertical, ASCII~13. +\item[\Verb+\textbackslash \{+] Corchete abierto, ``\Verb+{+'', ASCII~173. +\item[\Verb+\textbackslash \}+] Corchete cerrado, ``\Verb+}+'', ASCII~175. +\end{itemize} + +Como se indicó arriba, si desea que la expansión en una plantilla +contenga un caracter literal ``\Verb+\+'', ``\Verb+{+'', o + ``\Verb+{+'', debe escaparlo. + +\section{Uso de filtros con palabras claves} +\label{sec:template:filter} + +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 \emph{filtros} opcionales para modificar el resultado de +expandir una palabra clave. Ya ha visto el filtro usual +\tplkwfilt{date}{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. + +\begin{itemize} +\item[\tplfilter{addbreaks}] Cualquier texto. Añade una etiqueta XHTML + ``\Verb+<br/>+'' antes del final de cada línea excepto en la final. + Por ejemplo, ``\Verb+foo\nbar+'' se convierte en ``\Verb+foo<br/>\nbar+''. +\item[\tplkwfilt{date}{age}] palabra clave \tplkword{date}. Muestra + la edad de la fecha, relativa al tiempo actual. Ofrece una cadena como + ``\Verb+10 minutes+''. +\item[\tplfilter{basename}] Cualquier texto, pero de utilidad sobre + todo en palabras claves relativas a \tplkword{ficheros}. Trata el + texto como una ruta, retornando el nombre base. Por ejemplo, + ``\Verb+foo/bar/baz+'', se convierte en ``\Verb+baz+''. +\item[\tplkwfilt{date}{date}] \tplkword{date} palabra clave. Mostrar + la fecha en un formato similar a la orden \tplkword{date} de + in a similar format to the Unix, pero con la zona horaria incluída. + Una cadena como ``\Verb+Mon Sep 04 15:13:13 2006 -0700+''. +\item[\tplkwfilt{author}{domain}] Cualquier texto, pero de mayor + utilidad para la palabra clave \tplkword{author}. Encuentra la + primera cadena que luce como una dirección de correo electrónico, y + extrae solamente el componente del dominio. Por ejemplo, de + ``\Verb+Bryan O'Sullivan <bos@serpentine.com>+'' se extrae + ``\Verb+serpentine.com+''. +\item[\tplkwfilt{author}{email}] Cualquier texto, pero de mayor + utilidad para la palabra clave \tplkword{author}. Extrae la primera + cadena que luce como una dirección de correo. Por ejemplo, de + ``\Verb+Bryan O'Sullivan <bos@serpentine.com>+'' extrae + ``\Verb+bos@serpentine.com+''. +\item[\tplfilter{escape}] Cualquier texto. Reemplaza los caracteres + especiales de XML/XHTML: ``\Verb+&+'', ``\Verb+<+'' y ``\Verb+>+'' + con las entidades XML. +\item[\tplfilter{fill68}] Cualquier texto. Lograr que el texto ocupe + las primeras 68 columnas. Es útil emplearlo antes de pasar el texto + por el filtro \tplfilter{tabindent}, y queremos que aún quepa en una + ventana de fuente fija y 80 columnas. +\item[\tplfilter{fill76}] Cualquier texto. Lograr que el texto quepa + en 76 columnas. +\item[\tplfilter{firstline}] Cualquier texto. Mostrar la primera + línea de texto sin saltos de línea. +\item[\tplkwfilt{date}{hgdate}] \tplkword{date} palabra clave. + Mostrar la fecha como un par de números legibles. Muestra una + cadena como ``\Verb+1157407993 25200+''. +\item[\tplkwfilt{date}{isodate}] \tplkword{date} palabra clave. + Mostrar la fecha como una cadena de texto en el formato. Muestra + una cadena como ``\Verb+2006-09-04 15:13:13 -0700+''. +\item[\tplfilter{obfuscate}] Cualquier texto, pero de mayor utilidad + para la palabra clave \tplkword{author}. Muestra el campo de texto + como una secuencia de entidades XML. Esto ayuda a eliminar ciertos + robots estúpidos de adquisición de correo. +\item[\tplkwfilt{author}{person}] Cualquier texto, útil sobre todo + para la palabra clave \tplkword{author}. Muestra el texto que hay + antes de la dirección de correo electrónico. Por ejemplo, + ``\Verb+Bryan O'Sullivan <bos@serpentine.com>+'' mostraría + ``\Verb+Bryan O'Sullivan+''. +\item[\tplkwfilt{date}{rfc822date}] \tplkword{date} palabra clave. + Muestra una fecha con el mismo formato que se usa en los encabezados + de correo. Mostraría una cadena como + ``\Verb+Mon, 04 Sep 2006 15:13:13 -0700+''. +\item[\tplkwfilt{node}{short}] Hash del conjunto de cambios. Muestra + la forma corta de un hash de conjunto de cambios, + of a changeset hash, p.e.~una cadena hexadecimal de 12 bytes. +\item[\tplkwfilt{date}{shortdate}] \tplkword{date} palabra clave. + Mostrar año, mes y día de una fecha. Muestrauna cadena como + ``\Verb+2006-09-04+''. +\item[\tplfilter{strip}] Cualquier texto. Elimina todos los espacios + en blanco al principio y al final de la cadena. +\item[\tplfilter{tabindent}] Cualquier texto. Muestra el texto con + todas las líneas excepto la primera que comience con el caracter tab. +\item[\tplfilter{urlescape}] Cualquier texto. Escapa todos los + caracteres que se consideren como ``especiales'' por los parsers de + URL. Por ejemplo, \Verb+foo bar+ se convierte en \Verb+foo%20bar+. +\item[\tplkwfilt{author}{user}] Cualquier texto, útil sobre todo para + la palabra clave \tplkword{author}. Retorna el ``usuario'' de una + dirección de correo. Por ejemplo, + ``\Verb+Bryan O'Sullivan <bos@serpentine.com>+'' se convierte en + ``\Verb+bos+''. +\end{itemize} + +\begin{figure} + \interaction{template.simple.manyfilters} + \caption{Filtros de plantilla en acción} + \label{fig:template:filters} +\end{figure} + +\begin{note} + 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 + \tplkword{desc} con el filtro \tplkwfilt{date}{isodate} no resultará + algo útil. +\end{note} + +\subsection{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). + +\interaction{template.simple.combine} + +Observe el uso de ``\Verb+\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 +\tplkword{tabindent} indenta todas las líneas \emph{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 \Verb+fill68|tabindent+ es muy distinto al resultado de +usar \Verb+tabindent|fill68+. + + +\section{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 +\hgopt{log}{--template} desde la línea de órdenes. + + +\subsection{Los ficheros de estilo más sencillos} + +Nuestro fichero sencillo de estilo contiene una sola línea: + +\interaction{template.simple.rev} + +Se le indica a Mercurial, ``si está imprimiendo un conjunto de +cambios, use el texto de la derecha como la plantilla''. + +\subsection{Sintaxis de ficheros de estilo} + +Las reglas de sintaxis para un fichero de estilo son sencillas: + +\begin{itemize} +\item El fichero se procesa línea por línea. + +\item Se ignoran el espacio en blanco circundante. + +\item Se omiten las líneas en blanco. + +\item Si una línea comienza con los caracteres ``\texttt{\#}'' o + ``\texttt{;}'', la línea completa se trata como un comentario, y se + omite como si fuera vacía. + +\item Una línea comienza con una palabra clave. Esta debe comenzar + con una caracter alfabético o una raya al piso, y puede contener + subsecuentemente cualquier caracter alfanumérico o una raya al + piso. (En notación de expresiones regulares debe coincidir con + \Verb+[A-Za-z_][A-Za-z0-9_]*+.) + +\item El próximo elemento debe ser un caracter ``\texttt{=}'', que + puede estar precedido o seguido por una cantidad arbitraria de + espacio. + +\item Si el resto de la línea comienza y termina con caracteres + encerrados entre caracteres de comillas (bien sea sencillas o + dobles), se trata como cuerpo de la plantilla. + +\item Si el resto de la línea \emph{no} comienza con una comilla, se + trata como el nombre de un fichero; los contenidos de este fichero + se leerán y se usarán como cuerpo de la plantilla. +\end{itemize} + +\section{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. + +\subsection{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. + +\interaction{template.svnstyle.syntax.input} + +Tenga en cuenta que \filename{broken.style} trata de definir la +palabra clave \texttt{changeset}, pero omite dar un contenido para esta. +Cuando se le indica a Mercurial que use este fichero de estilo, se +queja inmediatamente. + +\interaction{template.svnstyle.syntax.error} + +Este mensaje de error luce intimidante, pero no es muy difícil de +seguir: + +\begin{itemize} +\item El primer componente es la forma como Mercurial dice ``me rindo''. + \begin{codesample4} + \textbf{abort:} broken.style:1: parse error + \end{codesample4} + +\item A continuación viene el nombre del fichero que contiene el error. + \begin{codesample4} + abort: \textbf{broken.style}:1: parse error + \end{codesample4} + +\item Siguendo el nombre del fichero viene el número de línea en la + que se encontró el error. + \begin{codesample4} + abort: broken.style:\textbf{1}: parse error + \end{codesample4} + +\item Finalmente, la descripción de lo que falló. + \begin{codesample4} + abort: broken.style:1: \textbf{parse error} + \end{codesample4} + La descripción del problema no siempre es clara (como en este caso), + pero aunque sea críptica, casi siempre es trivial la inspección + visual de la línea en el fichero de estilo y encontrar lo que está + mal. +\end{itemize} + +\subsection{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. +\interaction{template.svnstyle.id} +No es garantía de unicidad, pero no es útill en ciertos casos: +many cases. +\begin{itemize} +\item No funcionará en un repositorio completamente vacío, porque un + repositorio así no tiene una revisión~zero. +\item Tampoco funcionará en caso (muy raro) cuando el repositorio sea + una fusión de dos repositorios independientes y tiene los dos + directorios por ahí. +\end{itemize} +Hay ciertos casos en los cuales podría colocar el identificador: +\begin{itemize} +\item Como una llave en la tabla de una base de datos que administra + repositorios en un servidor. +\item Como una parte del par \{\emph{ID~repositorio}, \emph{ID~revisión}\}. + Almacene esta información de forma independiente cuando ejecute + construcciones automatizadas u otras actividades, de forma que pueda + ``reconstruir'' posteriormente en caso de ser necesario. +\end{itemize} + +\subsection{Mostrando salida parecida a Subversion} + +Intentemos emular la salida usual que usa otro sistema de control de +revisiones, Subversion. +\interaction{template.svnstyle.short} + +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. +\interaction{template.svnstyle.template} + +Esta plantilla difiere en algunos detalles de la salida producida por +Subversion: +\begin{itemize} +\item Subversion imprime una fecha ``legible'' (el ``\texttt{Wed, 27 Sep + 2006}'' en el ejemplo de salida anterior) en paréntesis. El motor + de plantillas de Mercurial no ofrece una forma sencilla de desplegar + una fecha en este formato sin imprimir también la hora y la zona horaria. +\item Emulamos las líneas de ``separación'' de subversion con caracteres + ``\texttt{-}'' en una línea. Usamos la palabra clave + \tplkword{header} del motor de plantillas para imprimir una línea de + separación como la primera línea de salida (ver más abajo), para + lograr una salida similara a la de Subversion. +\item La salida de subversion incluye un conteo en el encabezado del + número de líneas en el mensaje de consinación. No podemos + replicarlo en Mercurial; el motor de plantilla no ofrece en la + actualidad un filtro que cuente la cantidad de objetos que se le + pasen. +\end{itemize} +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. +\interaction{template.svnstyle.style} + +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 ``\verb!\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. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/tour-basic.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,690 @@ +\chapter{Una gira de Mercurial: lo básico} +\label{chap:tour-basic} + +\section{Instalar Mercurial en su sistema} +\label{sec:tour:install} +Hay paquetes binarios precompilados de Mercurial disponibles para cada +sistema operativo popular. Esto hace fácil empezar a usar Mercurial +en su computador inmediatamente. + +\subsection{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 \texttt{mercurial}. + +\begin{itemize} +\item[Debian] + \begin{codesample4} + apt-get install mercurial + \end{codesample4} + +\item[Fedora Core] + \begin{codesample4} + yum install mercurial + \end{codesample4} + +\item[Gentoo] + \begin{codesample4} + emerge mercurial + \end{codesample4} + +\item[OpenSUSE] + \begin{codesample4} + yum install mercurial + \end{codesample4} + +\item[Ubuntu] El paquete de Mercurial de Ubuntu está basado en el de + Debian. Para instalarlo, ejecute el siguiente comando. + \begin{codesample4} + apt-get install mercurial + \end{codesample4} + El paquete de Mercurial para Ubuntu tiende a atrasarse con respecto + a la versión de Debian por un margen de tiempo considerable + (al momento de escribir esto, 7 meses), lo que en algunos casos + significará que usted puede encontrarse con problemas que ya habrán + sido resueltos en el paquete de Debian. +\end{itemize} + +\subsection{Solaris} + +SunFreeWare, en \url{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. + +\subsection{Mac OS X} + +Lee Cantey publica un instalador de Mercurial para Mac OS~X en +\url{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~\cite{web:macpython}. 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 \command{sudo apt-get install mercurial-py25}. +Si usa MacPorts, \command{sudo port install mercurial}. + +\subsection{Windows} + +Lee Cantey publica un instalador de Mercurial para Windows en +\url{http://mercurial.berkwood.com}. Este paquete no tiene +% TODO traducción de it just works. Agreed? +dependencias externas; ``simplemente funciona''. + +\begin{note} + 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. +\end{note} + +\section{Arrancando} + +Para empezar, usaremos el comando \hgcmd{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. + +\interaction{tour.version} + +% TODO builtin-> integrado? +\subsection{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 \hgcmd{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. +\interaction{tour.help} +Para un nivel más impresionante de detalle (que usted no va a +necesitar usualmente) ejecute \hgcmdargs{help}{\hggopt{-v}}. La opción +\hggopt{-v} es la abreviación para \hggopt{--verbose}, y le indica a +Mercurial que imprima más información de lo que haría usualmente. + +\section{Trabajar con un repositorio} + +En Mercurial, todo sucede dentro de un \emph{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. + +\subsection{Hacer una copia local de un repositorio} + +\emph{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 \hgcmd{clone}\ndt{Del término +``clonar'' en inglés.}, porque crea una copia idéntica de un +repositorio existente. +\interaction{tour.clone} +Si nuestro clonado tiene éxito, deberíamos tener un directorio local +llamado \dirname{hello}. Este directorio contendrá algunos ficheros. +\interaction{tour.ls} +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 +% TODO figure out what to say instead of sandbox +``caja de arena'' privada que no afectará a nadie más. + +\subsection{Qué hay en un repositorio?} + +Cuando miramos en detalle dentro de un repositorio, podemos ver que +contiene un directorio llamado \dirname{.hg}. Aquí es donde Mercurial +mantiene todos los metadatos del repositorio. +\interaction{tour.ls-a} + +Los contenidos del directorio \dirname{.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 \dirname{.hg} es +el repositorio ``real'', y todos los ficheros y directorios que +coexisten con él están en el \emph{directorio de trabajo}. Una forma +sencilla de recordar esta distinción es que el \emph{repositorio} +contiene el \emph{historial} de su proyecto, mientras que el +\emph{directorio de trabajo} contiene una \emph{instantánea} de su +proyecto en un punto particular del historial. + +\section{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 \hgcmd{log} nos +permite ver el mismo. +\interaction{tour.log} +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 \emph{conjunto de +cambios}, porque pueden contener un registro de cambios a varios +ficheros. + +Los campos de la salida de \hgcmd{log} son los siguientes. +\begin{itemize} + \item[\texttt{changeset}]\hspace{-0.5em}\ndt{Conjunto de cambios.} Este campo + tiene un número, seguido por un + % TODO digo mejor seguido por un dos puntos ? string => + % cadena? + \texttt{:}, seguido por una cadena hexadecimal. Ambos son + \emph{identificadores} para el conjunto de cambios. Hay dos + identificadores porque el número es más corto y más fácil de + recordar que la cadena hexadecimal. + +\item[\texttt{user}]\hspace{-0.5em}\ndt{Usuario.} La identidad de la + persona que creó el conjunto de cambios. Este es un campo en el + que se puede almacenar cualquier valor, pero en la mayoría de los + casos contiene el nombre de una persona y su dirección de correo + electrónico. + +\item[\texttt{date}]\hspace{-0.5em}\ndt{Fecha.} La fecha y hora en la + que el conjunto de cambios fue creado, y la zona horaria en la que + fue creado. (La fecha y hora son locales a dicha zona horaria; + ambos muestran la fecha y hora para la persona que creó el + conjunto de cambios). + +\item[\texttt{summary}]\hspace{-0.5em}\ndt{Sumario.} + La primera línea del texto que usó la persona que creó el conjunto + de cambios para describir el mismo. +\end{itemize} +El texto impreso por \hgcmd{log} es sólo un sumario; omite una gran +cantidad de detalles. + +La figura~\ref{fig:tour-basic:history} es una representación +gráfica del historial del repositorio \dirname{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. + +\begin{figure}[ht] + \centering + \grafix{tour-history} + \caption{Historial gráfico del repositorio \dirname{hello}} + \label{fig:tour-basic:history} +\end{figure} + +\subsection{Conjuntos de cambios, revisiones, y comunicándose con +otras personas} + +%TODO sloppy => desordenado ? TODO hablar del inglés? o de español? +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 +% TODO insertar ? al revés. no sé cómo en un teclado de estos. +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''\ndt{Abreviatura para la expresión ``changeset'' en inglés.}, +y algunas veces un se hace referencia a un conjunto de cambios como +una ``revisión'' o ``rev''\ndt{De nuevo, como abreviación para el +término en inglés para ``revisión'' (``revision'').}. + +Si bien no es relevante qué \emph{palabra} use usted para referirse al +concepto ``conjunto de cambios'', el \emph{identificador} que usted +use para referise a ``un \emph{conjunto de cambios} particular'' es +muy importante. Recuerde que el campo \texttt{changeset} en la salida +de \hgcmd{log} identifica un conjunto de cambios usando tanto un +número como una cadena hexadecimal. + +\begin{itemize} + \item El número de revisión \emph{sólo es válido dentro del + repositorio}. + \item Por otro lado, la cadena hexadecimal es el + \emph{identificador permanente e inmutable} que siempre + identificará ése conjunto de cambios en \emph{todas} las + copias del repositorio. +\end{itemize} +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 \emph{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. + +\subsection{Ver revisiones específicas} + +Para reducir la salida de \hgcmd{log} a una sola revisión, use la +opción \hgopt{log}{-r} (o \hgopt{log}{--rev}). Puede usar un número +de revisión o un identificador hexadecimal de conjunto de cambios, y +puede pasar tantas revisiones como desee. + +\interaction{tour.log-r} + +Si desea ver el historial de varias revisiones sin tener que mencionar +cada una de ellas, puede usar la \emph{notación de rango}; esto le +permite expresar el concepto ``quiero ver todas las revisiones entre +$a$ y $b$, inclusive''. +\interaction{tour.log.range} +Mercurial también respeta el orden en que usted especifica las +revisiones, así que \hgcmdargs{log}{-r 2:4} muestra $2,3,4$ mientras +que \hgcmdargs{log}{-r 4:2} muestra $4,3,2$. + +\subsection{Información más detallada} +Aunque la información presentada por \hgcmd{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 \hggopt{-v} (or \hggopt{--verbose}) del +comando \hgcmd{log} le da este nivel extra de detalle. +\interaction{tour.log-v} + +Si desea ver tanto la descripción como el contenido de un cambio, +añada la opción \hgopt{log}{-p} (o \hgopt{log}{--patch}). Esto muestra +% TODO qué hacemos con diff unificado? convervarlo, por ser la +% acepción usual? +el contenido de un cambio como un \emph{diff unificado} (si usted +nunca ha visto un diff unificado antes, vea la +sección~\ref{sec:mq:patch} para un vistazo global). +\interaction{tour.log-vp} + +\section{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. +\begin{itemize} +\item Cada opción tiene un nombre largo. Por ejemplo, el comando + \hgcmd{log} acepta la opción \hgopt{log}{--rev}, como ya hemos + visto. +\item Muchas opciones tienen también un nombre corto. En vez de + \hgopt{log}{--rev}, podemos usar \hgopt{log}{-r}. (El motivo para + que algunas opciones no tengan nombres cortos es que dichas + opciones se usan rara vez.) +\item Las opciones largas empiezan con dos guiones (p.ej.~\hgopt{log}{--rev}), + mientras que las opciones cortas empiezan con uno (e.g.~\hgopt{log}{-r}). +\item El nombre y uso de las opciones es consistente en todos los + comandos. Por ejemplo, cada comando que le permite pasar un ID de + conjunto de cambios o un número de revisión acepta tanto la opción + \hgopt{log}{-r} como la \hgopt{log}{--rev}. +\end{itemize} +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 \hggopt{-v} (o +\hggopt{--verbose}\ndt{Prolijo.}), y menos cuando se les pase la opción \hggopt{-q} +(o \hggopt{--quiet}\ndt{Silencioso.}). + +\section{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 \hgcmd{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. +\interaction{tour.reclone} +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 \dirname{my-hello}, hay un fichero +\filename{hello.c} que contiene el clásico programa ``hello, +world''\ndt{Hola, mundo.}. Usaremos el clásico y venerado comando +\command{sed} para editar este fichero y hacer que imprima una segunda +línea de salida. (Estoy usando el comando \command{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 +\command{sed}; use su editor de texto preferido para hacer lo mismo). +\interaction{tour.sed} + +El comando \hgcmd{status} de Mercurial nos dice lo que Mercurial sabe +acerca de los ficheros en el repositorio. +\interaction{tour.status} +El comando \hgcmd{status} no imprime nada para algunos ficheros, sólo +una línea empezando con ``\texttt{M}'' para el fichero +\filename{hello.c}. A menos que usted lo indique explícitamente, +\hgcmd{status} no imprimirá nada respecto a los ficheros que no han +sido modificados. + +La ``\texttt{M}'' indica que Mercurial se dio cuenta de que nosotros +modificamos \filename{hello.c}. No tuvimos que \emph{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 \filename{hello.c}, +pero preferiríamos saber exactamente \emph{qué} cambios hicimos. +Para averiguar esto, usamos el comando \hgcmd{diff}. +\interaction{tour.diff} + +\section{Grabar cambios en un nuevo conjunto de cambios} + +Podemos modificar, compilar y probar nuestros cambios, y usar +\hgcmd{status} y \hgcmd{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 \hgcmd{commit} nos permite crear un nuevo conjunto de +cambios. Nos referiremos usualmente a esto como ``hacer una consigna'' +o consignar. + +\subsection{Definir un nombre de usuario} + +Cuando usted trata de ejecutar \hgcmd{commit}\ndt{Hacer una +consignación} 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 +% TODO consigna o consignación? +usuario adecuado con el cual registrar la consignación. Se intenta con +cada uno de los siguientes métodos, en el orden presentado. +\begin{enumerate} +\item Si usted pasa la opción \hgopt{commit}{-u} al comando \hgcmd{commit} + en la línea de comandos, seguido de un nombre de usuario, se le da a + esto la máxima precedencia. +\item A continuación se revisa si usted ha definido la variable de + entorno \envar{HGUSER}. +\item Si usted crea un fichero en su directorio personal llamado + \sfilename{.hgrc}, con una entrada \rcitem{ui}{username}, se usa + luego. Para revisar cómo debe verse este fichero, refiérase a la + sección~\ref{sec:tour-basic:username} más abajo. +\item Si usted ha definido la variable de entorno \envar{EMAIL}, será + usada a continuación. +\item Mercurial le pedirá a su sistema buscar su nombre de usuario + % TODO host => máquina + 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. +\end{enumerate} +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 \envar{HGUSER} y la opción +\hgopt{commit}{-u} del comando \hgcmd{commit} como formas de +\emph{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 \sfilename{.hgrc}; los detalles se encuentran más adelante. + +\subsubsection{Crear el fichero de configuración de Mercurial} +\label{sec:tour-basic:username} + +Para definir un nombre de usuario, use su editor de texto favorito +para crear un fichero llamado \sfilename{.hgrc} en su directorio +personal. Mercurial usará este fichero para obtener las +configuraciones personalizadas que usted haya hecho. El contenido +inicial de su fichero \sfilename{.hgrc} debería verse así. +\begin{codesample2} + # Este es un fichero de configuración de Mercurial. + [ui] + username = Primernombre Apellido <correo.electronico@dominio.net> +\end{codesample2} +La línea ``\texttt{[ui]}'' define una \emph{section} del fichero de +configuración, así que usted puede leer la línea ``\texttt{username = +...}'' como ``defina el valor del elemento \texttt{username} en la +sección \texttt{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 ``\texttt{\#}'' hasta el final de la línea +como un comentario. + +\subsubsection{Escoger un nombre de usuario} + +Usted puede usar el texto que desee como el valor del campo de +configuración \texttt{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. + +\begin{note} + % TODO web + 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 + spammers\ndt{Personas que envían correo no solicitado, también + conocido como correo basura}. Esto reduce la probabilidad de que + usted empiece a recibir más correo basura si publica un + repositorio en la red. +\end{note} + +\subsection{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 \emph{mensaje de consignación}. Será un registro de lo que hicimos +y porqué lo hicimos, y será impreso por \hgcmd{log} una vez hayamos +hecho la consignación. +\interaction{tour.commit} + +El editor en que \hgcmd{commit} nos ubica contendrá una línea vacía, +seguida de varias líneas que empiezan con la cadena ``\texttt{HG:}''. +\begin{codesample2} + \emph{línea vacía} + HG: changed hello.c +\end{codesample2} +Mercurial ignora las líneas que empiezan con ``\texttt{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. + +\subsection{Escribir un buen mensaje de consignación} + +Ya que por defecto \hgcmd{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 \emph{no} sigue esta +pauta, y debido a ello tiene un sumario que no es legible. +\begin{codesample2} + changeset: 73:584af0e231be + user: Persona Censurada <persona.censurada@ejemplo.org> + date: Tue Sep 26 21:37:07 2006 -0700 + summary: se incluye buildmeister/commondefs. Añade un módulo +\end{codesample2} + +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 \hgcmdargs{log}{--patch}. + +\subsection{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 \hgcmd{commit} sin ningún argumento, se +registran todos los cambios que hemos hecho, como lo indican +\hgcmd{status} y \hgcmd{diff}. + +\subsection{Admirar nuestro trabajo} + +Una vez hemos terminado la consignación, podemos usar el comando +\hgcmd{tip}\ndt{Punta.} para mostrar el conjunto de cambios que acabamos de crear. +La salida de este comando es idéntica a la de \hgcmd{log}, pero sólo +muestra la revisión más reciente en el repositorio. +\interaction{tour.tip} +Nos referimos a la revisión más reciente en el repositorio como la +revisión de punta, o simplemente la punta. + +\section{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 \dirname{my-hello}. Veamos +unas cuantas formas de propagar este cambio a otros repositorios. + +\subsection{Jalar cambios desde otro repositorio} +\label{sec:tour:pull} + +Para empezar, clonemos nuestro repositorio \dirname{hello} original, +el cual no contiene el cambio que acabamos de consignar. Llamaremos a +este repositorio temporal \dirname{hello-pull}. +\interaction{tour.clone-pull} + +Usaremos el comando \hgcmd{pull} para traer los cambios de +\dirname{my-hello} y ponerlos en \dirname{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 \hgcmd{incoming}\ndt{Entrante, o cambios entrantes.} para +decirnos qué cambios \emph{jalaría} el comando \hgcmd{pull} al +repositorio, sin jalarlos. +\interaction{tour.incoming} +(Por supuesto, alguien podría enviar más conjuntos de cambios al +repositorio en el tiempo que pasa entre la ejecución de +\hgcmd{incoming} y la ejecución de \hgcmd{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 \hgcmd{pull}, indicándole de qué repositorio debe jalarlos. +\interaction{tour.pull} +Como puede verse por las salidas antes-y-después de \hgcmd{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. + +\subsection{Actualizar el directorio de trabajo} + +Hasta ahora hemos pasado por alto la relación entre un repositorio y +su directorio de trabajo. El comando \hgcmd{pull} que ejecutamos en la +sección~\ref{sec:tour:pull} trajo los cambios al repositorio, pero si +revisamos, no hay rastro de esos cambios en el directorio de trabajo. +Esto pasa porque \hgcmd{pull} (por defecto) no modifica el directorio de +trabajo. En vez de eso, usamos el comando +\hgcmd{update}\ndt{Actualizar.} para hacerlo. +\interaction{tour.update} + +Puede parecer algo raro que \hgcmd{pull} no actualice el directorio de +trabajo automáticamente. De hecho, hay una buena razón para esto: +usted puede usar \hgcmd{update} para actualizar el directorio de +trabajo al estado en que se encontraba en \emph{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 \hgcmd{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 +\hgopt{pull}{-u} +a \hgcmd{pull}. +\begin{codesample2} + hg pull -u +\end{codesample2} +Si mira de vuelta la salida de \hgcmd{pull} en la +sección~\ref{sec:tour:pull} cuando lo ejecutamos sin la opción \hgopt{pull}{-u}, +verá que el comando imprimió un amable recordatorio de que tenemos que +encargarnos explícitamente de actualizar el directorio de trabajo: +\begin{codesample2} + (run 'hg update' to get a working copy) +\end{codesample2} + +Para averiguar en qué revisión se encuentra el directorio de trabajo, +use el comando \hgcmd{parents}. +\interaction{tour.parents} +Si mira de nuevo la figura~\ref{fig:tour-basic:history}, verá flechas +conectando cada conjunto de cambios. En cada caso, el nodo del que la flecha +\emph{sale} es un padre, y el nodo al que la flecha \emph{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 +\hgcmd{update}. +\interaction{tour.older} +Si no indica explícitamente una revisión, \hgcmd{update} actualizará +hasta la revisión de punta, como se vio en la segunda llamada a +\hgcmd{update} en el ejemplo anterior. + +\subsection{Empujar cambios a otro repositorio} + +Mercurial nos permite empujar cambios a otro repositorio, desde el +% TODO cambié "visitando" por "usando" +repositorio que estemos usando actualmente. De la misma forma que en +el ejemplo de \hgcmd{pull} arriba, crearemos un repositorio temporal +para empujar allí nuestros cambios. +\interaction{tour.clone-push} +El comando \hgcmd{outgoing}\ndt{Saliente. Cambios salientes.} nos dice +qué cambios serían empujados en el otro repositorio. +\interaction{tour.outgoing} +Y el comando \hgcmd{push} se encarga de empujar dichos cambios. +\interaction{tour.push} +Al igual que \hgcmd{pull}, el comando \hgcmd{push} no actualiza el +directorio de trabajo del repositorio en el que estamos empujando los +cambios. (A diferencia de \hgcmd{pull}, \hgcmd{push} no ofrece la +opción \texttt{-u} para actualizar el directorio de trabajo del otro +repositorio.) + +% TODO poner interrogante de apertura +Qué pasa si tratamos de jalar o empujar cambios y el repositorio +receptor ya tiene esos cambios? Nada emocionante. +\interaction{tour.push.nothing} + +\subsection{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 +% TODO poner ndt para URL +de red. Simplemente pase una URL en vez de una ruta local. +\interaction{tour.outgoing.net} +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. +\interaction{tour.push.net} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/tour-history.svg Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,298 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="tour-history.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs4"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective2812" /> + <marker + inkscape:stockid="Arrow1Mstart" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mstart" + style="overflow:visible"> + <path + id="path2973" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none" + transform="scale(0.4) translate(10,0)" /> + </marker> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mend" + style="overflow:visible;"> + <path + id="path3066" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;" + transform="scale(0.4) rotate(180) translate(10,0)" /> + </marker> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.4" + inkscape:cx="232.14286" + inkscape:cy="672.75296" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="906" + inkscape:window-height="659" + inkscape:window-x="5" + inkscape:window-y="49" + showgrid="false" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <rect + style="opacity:1;fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect1878" + width="94.285713" + height="20.714285" + x="138" + y="479.50504" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="162.09892" + y="493.12619" + id="text1872"><tspan + sodipodi:role="line" + id="tspan1874" + x="162.09892" + y="493.12619" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan1876">0</tspan>: REV0</tspan></text> + <rect + style="opacity:1;fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2800" + width="94.285713" + height="20.714285" + x="138" + y="432.63004" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="162.09892" + y="446.25119" + id="text2794"><tspan + sodipodi:role="line" + id="tspan2796" + x="162.09892" + y="446.25119" + style="font-family:Courier"><tspan + id="tspan2868" + style="font-weight:bold">1</tspan>: REV1</tspan></text> + <rect + style="opacity:1;fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2810" + width="94.285713" + height="20.714285" + x="138" + y="385.75504" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="162.09892" + y="399.37619" + id="text2804"><tspan + sodipodi:role="line" + id="tspan2806" + x="162.09892" + y="399.37619" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan2866">2</tspan>: REV2</tspan></text> + <rect + style="opacity:1;fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2820" + width="94.285713" + height="20.714285" + x="138" + y="338.88007" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="162.09892" + y="352.50122" + id="text2814"><tspan + sodipodi:role="line" + id="tspan2816" + x="162.09892" + y="352.50122" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan2864">3</tspan>: REV3</tspan></text> + <rect + style="opacity:1;fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2830" + width="94.285713" + height="20.714285" + x="138" + y="292.00504" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="162.09892" + y="305.62619" + id="text2824"><tspan + sodipodi:role="line" + id="tspan2826" + x="162.09892" + y="305.62619" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan2862">4</tspan>: REV4</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="173.57143" + y="443.79074" + id="text2832"><tspan + sodipodi:role="line" + id="tspan2834" + x="173.57143" + y="443.79074" /></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow1Mend)" + d="M 185.14286,478.50504 L 185.14286,454.34432" + id="path2894" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow1Mend)" + d="M 185.14286,431.63004 L 185.14286,407.46932" + id="path2896" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow1Mend)" + d="M 185.14286,384.75504 L 185.14286,360.59435" + id="path2898" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow1Mend)" + d="M 185.14286,337.88007 L 185.14286,313.71932" + id="path2900" + inkscape:connector-type="polyline" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times" + x="244.60992" + y="305.245" + id="text1902"><tspan + sodipodi:role="line" + id="tspan1904" + x="244.60992" + y="305.245">(la más nueva)</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times" + x="244.60992" + y="492.745" + id="text1906"><tspan + sodipodi:role="line" + id="tspan1908" + x="244.60992" + y="492.745">(la más antigua)</tspan></text> + <rect + style="opacity:1;fill:#d2e1e4;fill-opacity:1;stroke:#b1cbd0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect1907" + width="94.285713" + height="20.714285" + x="309.28571" + y="324.86218" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="333.38464" + y="338.48334" + id="text1909"><tspan + sodipodi:role="line" + id="tspan1911" + x="333.38464" + y="338.48334" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan1913">4</tspan>: REV4</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 332.14286,375.21932 L 335.71429,347.36218" + id="path2802" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 372.69968,375.21932 L 369.12825,347.36218" + id="path2986" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times" + x="335.14285" + y="387.21933" + id="text2988"><tspan + sodipodi:role="line" + x="335.14285" + y="387.21933" + id="tspan3020" + style="text-align:end;text-anchor:end">número de</tspan><tspan + sodipodi:role="line" + x="335.14285" + y="402.21933" + id="tspan3014" + style="text-align:end;text-anchor:end">revisión</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times" + x="368.71429" + y="387.21933" + id="text2994"><tspan + sodipodi:role="line" + id="tspan2996" + x="368.71429" + y="387.21933">identificador del</tspan><tspan + sodipodi:role="line" + x="368.71429" + y="402.21933" + id="tspan2998">conjunto de cambios</tspan></text> + </g> +</svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/tour-merge-conflict.svg Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,219 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="tour-merge-conflict.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs4"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective2861" /> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mend" + style="overflow:visible;"> + <path + id="path3053" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;" + transform="scale(0.4) rotate(180) translate(10,0)" /> + </marker> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.4" + inkscape:cx="251.65243" + inkscape:cy="733.42197" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="906" + inkscape:window-height="659" + inkscape:window-x="5" + inkscape:window-y="49" + showgrid="false" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + id="g1988" + transform="translate(84.85711,0)"> + <g + id="g1876"> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 118.57143,458.21933 L 118.57143,563.79075 L 191.42857,563.79075 L 204.28571,550.93361 L 203.57142,459.6479 L 118.57143,458.21933 z " + id="path1872" + sodipodi:nodetypes="cccccc" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 191.55484,563.36862 L 191.6923,560.98794 L 192.69126,552.44884 L 203.80416,551.31242" + id="path1874" + sodipodi:nodetypes="cccc" /> + </g> + <flowRoot + style="font-size:8px;font-family:Times New Roman" + id="flowRoot1898" + xml:space="preserve"><flowRegion + id="flowRegion1900"><rect + style="font-size:8px;font-family:Times New Roman" + y="464.50504" + x="122.85714" + height="93.571426" + width="76.428574" + id="rect1902" /></flowRegion><flowPara + id="flowPara1904">Saludos!</flowPara><flowPara + id="flowPara1906" /><flowPara + id="flowPara1908">Soy Mariam Abacha, la esposa del anterior dictador de Nigeria Sani Abacha. Le contacto en secreto, buscando los medios para desarrollar</flowPara></flowRoot> </g> + <g + id="g1966" + transform="translate(82,0.35715)"> + <g + transform="translate(-77.85718,-140.0714)" + id="g1910"> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 118.57143,458.21933 L 118.57143,563.79075 L 191.42857,563.79075 L 204.28571,550.93361 L 203.57142,459.6479 L 118.57143,458.21933 z " + id="path1912" + sodipodi:nodetypes="cccccc" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 191.55484,563.36862 L 191.6923,560.98794 L 192.69126,552.44884 L 203.80416,551.31242" + id="path1914" + sodipodi:nodetypes="cccc" /> + </g> + <flowRoot + transform="translate(-77.85718,-140.0714)" + style="font-size:8px;font-family:Times New Roman" + id="flowRoot1916" + xml:space="preserve"><flowRegion + id="flowRegion1918"><rect + style="font-size:8px;font-family:Times New Roman" + y="464.50504" + x="122.85714" + height="93.571426" + width="76.428574" + id="rect1920" /></flowRegion><flowPara + id="flowPara1922">Saludos!</flowPara><flowPara + id="flowPara1924" /><flowPara + id="flowPara1926">Soy <flowSpan + style="font-style:italic;fill:#ff0000" + id="flowSpan3094">Shehu Musa Abacha, sobrina del</flowSpan> anterior dictador de Nigeria Sani Abacha. Le contacto en secreto, buscando los medios para desarrollar</flowPara></flowRoot> </g> + <g + id="g1977" + transform="translate(81.99999,-0.35715)"> + <g + transform="translate(83.57141,-139.3571)" + id="g1932"> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 118.57143,458.21933 L 118.57143,563.79075 L 191.42857,563.79075 L 204.28571,550.93361 L 203.57142,459.6479 L 118.57143,458.21933 z " + id="path1934" + sodipodi:nodetypes="cccccc" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 191.55484,563.36862 L 191.6923,560.98794 L 192.69126,552.44884 L 203.80416,551.31242" + id="path1936" + sodipodi:nodetypes="cccc" /> + </g> + <flowRoot + transform="translate(83.57141,-139.3571)" + style="font-size:8px;font-family:Times New Roman" + id="flowRoot1938" + xml:space="preserve"><flowRegion + id="flowRegion1940"><rect + style="font-size:8px;font-family:Times New Roman" + y="464.50504" + x="122.85714" + height="93.571426" + width="76.428574" + id="rect1942" /></flowRegion><flowPara + id="flowPara1944">Saludos!</flowPara><flowPara + id="flowPara1946" /><flowPara + id="flowPara1948">Soy <flowSpan + style="font-style:italic;fill:#ff0000" + id="flowSpan3096">Alhaji Abba Abacha, hijo del</flowSpan> anterior dictador de Nigeria Sani Abacha. Le contacto en secreto, buscando los medios para desarrollar</flowPara></flowRoot> </g> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 215.502,457.71933 L 196.35507,424.5765" + id="path1999" + inkscape:connector-type="polyline" + inkscape:connection-start="#g1988" + inkscape:connection-end="#g1966" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 277.06936,457.71933 L 296.21629,424.5765" + id="path2001" + inkscape:connector-type="polyline" + inkscape:connection-start="#g1988" + inkscape:connection-end="#g1977" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="302.42859" + y="515.08905" + id="text1905"><tspan + sodipodi:role="line" + id="tspan1907" + x="302.42859" + y="515.08905">Versión inicial</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="30.57143" + y="374.1619" + id="text1917"><tspan + sodipodi:role="line" + id="tspan1919" + x="30.57143" + y="374.1619">Nuestros cambios</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="385.71429" + y="374.1619" + id="text1921"><tspan + sodipodi:role="line" + id="tspan1923" + x="385.71429" + y="374.1619">Sus cambios</tspan></text> + </g> +</svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/tour-merge-merge.svg Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,389 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="tour-merge-merge.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs4"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective2928" /> + <marker + inkscape:stockid="Arrow1Mstart" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mstart" + style="overflow:visible"> + <path + id="path2973" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none" + transform="scale(0.4) translate(10,0)" /> + </marker> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mend" + style="overflow:visible;"> + <path + id="path3066" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;" + transform="scale(0.4) rotate(180) translate(10,0)" /> + </marker> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.98994949" + inkscape:cx="328.35015" + inkscape:cy="790.24518" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="1278" + inkscape:window-height="756" + inkscape:window-x="0" + inkscape:window-y="0" + showgrid="false" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2995" + width="94.285713" + height="20.714285" + x="532.85718" + y="203.0479" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="173.57143" + y="443.79074" + id="text2832"><tspan + sodipodi:role="line" + id="tspan2834" + x="173.57143" + y="443.79074" /></text> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2830" + width="94.285713" + height="20.714285" + x="138" + y="297.76227" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="162.09892" + y="311.38342" + id="text2824"><tspan + sodipodi:role="line" + id="tspan2826" + x="162.09892" + y="311.38342" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan2862">4</tspan>: REV4</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 185.14286,343.63731 L 185.14286,319.47656" + id="path2900" + inkscape:connector-type="polyline" /> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2863" + width="94.285713" + height="20.714285" + x="91.428574" + y="250.47656" /> + <text + xml:space="preserve" + style="font-size:12.00001812px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="116.09886" + y="264.56592" + id="text1965" + transform="scale(1.000002,0.999998)"><tspan + sodipodi:role="line" + id="tspan1967" + x="116.09886" + y="264.56592" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan1973">5</tspan>: REV_my_new_hello</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1.00000143px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1;display:inline" + d="M 173.95727,296.76228 L 149.75702,272.19085" + id="path1971" + inkscape:connector-type="polyline" + inkscape:connection-end="#rect2863" + inkscape:connection-start="#rect2830" /> + <rect + style="fill:#78a5ad;fill-opacity:1;stroke:#507b84;stroke-width:2.00000286;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2911" + width="94.285995" + height="20.714283" + x="186.71414" + y="204.40514" /> + <text + xml:space="preserve" + style="font-size:12.00001812px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="210.81311" + y="218.02673" + id="text2913" + transform="scale(1.000002,0.999998)"><tspan + sodipodi:role="line" + id="tspan2915" + x="210.81311" + y="218.02673" + style="font-family:Courier"><tspan + id="tspan1966" + style="font-weight:bold">6</tspan>: REV6_my_new_hello</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1.00000143px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1;display:inline" + d="M 191.06908,296.76228 L 227.93092,226.11942" + id="path2919" + inkscape:connector-type="polyline" + inkscape:connection-start="#rect2830" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman;text-anchor:start;text-align:start;writing-mode:lr;line-height:125%" + x="295.28571" + y="217.56711" + id="text2871" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan2441">punta (y frente)</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="57.817253" + y="263.90753" + id="text2875" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan2439" + x="57.817253" + y="263.90753">frente</tspan></text> + <rect + style="fill:#c8aaa5;fill-opacity:1;stroke:#a07163;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:2, 4;stroke-dashoffset:0;stroke-opacity:1" + id="rect1913" + width="94.285713" + height="20.714285" + x="138" + y="156.90514" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:2, 2;stroke-dashoffset:0;stroke-opacity:1" + d="M 144.22399,249.47657 L 179.49029,178.61943" + id="path1915" + inkscape:connector-type="polyline" + inkscape:connection-start="#rect2863" + inkscape:connection-end="#rect1913" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:2, 2;stroke-dashoffset:0;stroke-opacity:1" + d="M 222.20966,203.40514 L 196.79033,178.61943" + id="path1917" + inkscape:connector-type="polyline" + inkscape:connection-start="#rect2911" + inkscape:connection-end="#rect1913" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="166.16823" + y="168.52228" + id="text2806"><tspan + sodipodi:role="line" + id="tspan2808" + x="166.16823" + y="168.52228" + style="font-family:Courier">fusión</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="246" + y="162.63338" + id="text2810"><tspan + sodipodi:role="line" + x="246" + y="162.63338" + id="tspan2814">directorio de trabajo</tspan><tspan + sodipodi:role="line" + x="246" + y="177.63338" + id="tspan3538">durante la fusión</tspan></text> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2816" + width="94.285713" + height="20.714285" + x="483.14636" + y="297.76227" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="507.24527" + y="311.38342" + id="text2818"><tspan + sodipodi:role="line" + id="tspan2820" + x="507.24527" + y="311.38342" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan2822">4</tspan>: REV4</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 530.28921,343.6373 L 530.28921,319.47655" + id="path2824" + inkscape:connector-type="polyline" /> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2826" + width="94.285713" + height="20.714285" + x="436.57492" + y="250.47656" /> + <text + xml:space="preserve" + style="font-size:12.00001812px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="461.24484" + y="264.56613" + id="text2828" + transform="scale(1.000002,0.999998)"><tspan + sodipodi:role="line" + id="tspan2830" + x="461.24484" + y="264.56613" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan2832">5</tspan>: REV_my_new_hello</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1.00000143px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1;display:inline" + d="M 519.10362,296.76227 L 494.90337,272.19084" + id="path2834" + inkscape:connector-type="polyline" /> + <rect + style="fill:#78a5ad;fill-opacity:1;stroke:#507b84;stroke-width:2.00000286;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2836" + width="94.285995" + height="20.714283" + x="483.14001" + y="156.548" /> + <text + xml:space="preserve" + style="font-size:12.00001812px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="555.95911" + y="218.02698" + id="text2838" + transform="scale(1.000002,0.999998)"><tspan + sodipodi:role="line" + id="tspan2840" + x="555.95911" + y="218.02698" + style="font-family:Courier"><tspan + id="tspan2842" + style="font-weight:bold">6</tspan>: REV6_my_new_hello</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1.00000143px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1;display:inline" + d="M 536.21543,296.76227 L 574.03453,224.76218" + id="path2844" + inkscape:connector-type="polyline" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="452.00058" + y="167.76765" + id="text2846" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan2443" + x="452.00058" + y="167.76765">punta</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker-start:none;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" + d="M 489.37034,249.47656 L 524.65575,178.26229" + id="path2856" + inkscape:connector-type="polyline" + inkscape:connection-end="#rect2836" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" + d="M 567.85714,202.0479 L 542.42591,178.26229" + id="path2858" + inkscape:connector-type="polyline" + inkscape:connection-end="#rect2836" + inkscape:connection-start="#rect2995" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="490.40295" + y="170.39714" + id="text2860"><tspan + sodipodi:role="line" + id="tspan2863" + x="490.40295" + y="170.39714" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan2997">7</tspan>: REV7_my_new_hello</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="90.323105" + y="120.21933" + id="text2929"><tspan + sodipodi:role="line" + id="tspan2931" + x="90.323105" + y="120.21933" + style="font-weight:bold">Directorio de trabajo durante la fusión</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="435.35226" + y="120.21933" + id="text2937"><tspan + sodipodi:role="line" + id="tspan2939" + x="435.35226" + y="120.21933" + style="font-weight:bold">Repositorio después de consignar la fusión</tspan></text> + </g> +</svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/tour-merge-pull.svg Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,297 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="tour-merge-pull.svg" + sodipodi:docbase="/home/bos/hg/hgbook/en" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs4"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective2979" /> + <marker + inkscape:stockid="Arrow1Mstart" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mstart" + style="overflow:visible"> + <path + id="path2973" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none" + transform="scale(0.4) translate(10,0)" /> + </marker> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mend" + style="overflow:visible;"> + <path + id="path3066" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;" + transform="scale(0.4) rotate(180) translate(10,0)" /> + </marker> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.4" + inkscape:cx="233.63208" + inkscape:cy="704.83702" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="906" + inkscape:window-height="659" + inkscape:window-x="237" + inkscape:window-y="103" + showgrid="false" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="173.57143" + y="443.79074" + id="text2832"><tspan + sodipodi:role="line" + id="tspan2834" + x="173.57143" + y="443.79074" /></text> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect1878" + width="94.285713" + height="20.714285" + x="138" + y="479.50504" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="162.09892" + y="493.12619" + id="text1872"><tspan + sodipodi:role="line" + id="tspan1874" + x="162.09892" + y="493.12619" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan1876">0</tspan>: REV0</tspan></text> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2800" + width="94.285713" + height="20.714285" + x="138" + y="432.63004" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="162.09892" + y="446.25119" + id="text2794"><tspan + sodipodi:role="line" + id="tspan2796" + x="162.09892" + y="446.25119" + style="font-family:Courier"><tspan + id="tspan2868" + style="font-weight:bold">1</tspan>: REV1</tspan></text> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2810" + width="94.285713" + height="20.714285" + x="138" + y="385.75504" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="162.09892" + y="399.37619" + id="text2804"><tspan + sodipodi:role="line" + id="tspan2806" + x="162.09892" + y="399.37619" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan2866">2</tspan>: REV2</tspan></text> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2820" + width="94.285713" + height="20.714285" + x="138" + y="338.88007" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="162.09892" + y="352.50122" + id="text2814"><tspan + sodipodi:role="line" + id="tspan2816" + x="162.09892" + y="352.50122" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan2864">3</tspan>: REV3</tspan></text> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2830" + width="94.285713" + height="20.714285" + x="138" + y="292.00504" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="162.09892" + y="305.62619" + id="text2824"><tspan + sodipodi:role="line" + id="tspan2826" + x="162.09892" + y="305.62619" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan2862">4</tspan>: REV4</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 185.14286,478.50504 L 185.14286,454.34432" + id="path2894" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 185.14286,431.63004 L 185.14286,407.46932" + id="path2896" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 185.14286,384.75504 L 185.14286,360.59435" + id="path2898" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 185.14286,337.88007 L 185.14286,313.71932" + id="path2900" + inkscape:connector-type="polyline" /> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2863" + width="94.285713" + height="20.714285" + x="91.428574" + y="244.71933" /> + <text + xml:space="preserve" + style="font-size:12.00001812px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="116.09886" + y="258.80865" + id="text1965" + transform="scale(1.000002,0.999998)"><tspan + sodipodi:role="line" + id="tspan1967" + x="116.09886" + y="258.80865" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan1973">5</tspan>: REV_my_new_hello</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1.00000143px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1;display:inline" + d="M 173.95727,291.00504 L 149.75702,266.43361" + id="path1971" + inkscape:connector-type="polyline" + inkscape:connection-end="#rect2863" + inkscape:connection-start="#rect2830" /> + <rect + style="fill:#78a5ad;fill-opacity:1;stroke:#507b84;stroke-width:2.00000286;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2911" + width="94.285995" + height="20.714283" + x="186.71414" + y="198.6479" /> + <text + xml:space="preserve" + style="font-size:12.00001812px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="210.81311" + y="212.26949" + id="text2913" + transform="scale(1.000002,0.999998)"><tspan + sodipodi:role="line" + id="tspan2915" + x="210.81311" + y="212.26949" + style="font-family:Courier"><tspan + id="tspan1966" + style="font-weight:bold">6</tspan>: REV6_my_new_hello</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1.00000143px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1;display:inline" + d="M 191.06908,291.00504 L 227.93092,220.36218" + id="path2919" + inkscape:connector-type="polyline" + inkscape:connection-start="#rect2830" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="291" + y="225.3813" + id="text2871"><tspan + sodipodi:role="line" + id="tspan2873" + x="291" + y="225.3813">tip (y principal)</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="76" + y="259.16046" + id="text2875"><tspan + sodipodi:role="line" + id="tspan2877" + x="76" + y="259.16046" + style="text-align:end;text-anchor:end">principal</tspan></text> + </g> +</svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/tour-merge-sep-repos.svg Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,480 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="tour-merge-sep-repos.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs4"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective3067" /> + <marker + inkscape:stockid="Arrow1Mstart" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mstart" + style="overflow:visible"> + <path + id="path2973" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none" + transform="scale(0.4) translate(10,0)" /> + </marker> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mend" + style="overflow:visible;"> + <path + id="path3066" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;" + transform="scale(0.4) rotate(180) translate(10,0)" /> + </marker> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.4" + inkscape:cx="307.20351" + inkscape:cy="683.39831" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="906" + inkscape:window-height="659" + inkscape:window-x="5" + inkscape:window-y="49" + showgrid="false" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="173.57143" + y="443.79074" + id="text2832"><tspan + sodipodi:role="line" + id="tspan2834" + x="173.57143" + y="443.79074" /></text> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect1878" + width="94.285713" + height="20.714285" + x="138" + y="479.50504" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="162.09892" + y="493.12619" + id="text1872"><tspan + sodipodi:role="line" + id="tspan1874" + x="162.09892" + y="493.12619" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan1876">0</tspan>: REV0</tspan></text> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2800" + width="94.285713" + height="20.714285" + x="138" + y="432.63004" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="162.09892" + y="446.25119" + id="text2794"><tspan + sodipodi:role="line" + id="tspan2796" + x="162.09892" + y="446.25119" + style="font-family:Courier"><tspan + id="tspan2868" + style="font-weight:bold">1</tspan>: REV1</tspan></text> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2810" + width="94.285713" + height="20.714285" + x="138" + y="385.75504" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="162.09892" + y="399.37619" + id="text2804"><tspan + sodipodi:role="line" + id="tspan2806" + x="162.09892" + y="399.37619" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan2866">2</tspan>: REV2</tspan></text> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2820" + width="94.285713" + height="20.714285" + x="138" + y="338.88007" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="162.09892" + y="352.50122" + id="text2814"><tspan + sodipodi:role="line" + id="tspan2816" + x="162.09892" + y="352.50122" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan2864">3</tspan>: REV3</tspan></text> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2830" + width="94.285713" + height="20.714285" + x="138" + y="292.00504" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="162.09892" + y="305.62619" + id="text2824"><tspan + sodipodi:role="line" + id="tspan2826" + x="162.09892" + y="305.62619" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan2862">4</tspan>: REV4</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 185.14286,478.50504 L 185.14286,454.34432" + id="path2894" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 185.14286,431.63004 L 185.14286,407.46932" + id="path2896" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 185.14286,384.75504 L 185.14286,360.59435" + id="path2898" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 185.14286,337.88007 L 185.14286,313.71932" + id="path2900" + inkscape:connector-type="polyline" /> + <rect + style="fill:#78a5ad;fill-opacity:1;stroke:#507b84;stroke-width:2.00000286;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect1963" + width="94.285995" + height="20.714283" + x="138" + y="245.18723" /> + <text + xml:space="preserve" + style="font-size:12.00001812px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="162.09877" + y="258.80865" + id="text1965" + transform="scale(1.000002,0.999998)"><tspan + sodipodi:role="line" + id="tspan1967" + x="162.09877" + y="258.80865" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan1973">5</tspan>: REV_my_hello</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1.00000143px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 185.143,291.06218 L 185.143,266.90143" + id="path1971" + inkscape:connector-type="polyline" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="136.90039" + y="232.25546" + id="text2921"><tspan + sodipodi:role="line" + id="tspan2923" + x="136.90039" + y="232.25546">my-hello</tspan></text> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2863" + width="94.285713" + height="20.714285" + x="370.71414" + y="479.49289" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="394.81305" + y="493.11404" + id="text2865"><tspan + sodipodi:role="line" + id="tspan2867" + x="394.81305" + y="493.11404" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan2869">0</tspan>: REV0</tspan></text> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2871" + width="94.285713" + height="20.714285" + x="370.71414" + y="432.61789" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="394.81305" + y="446.23904" + id="text2873"><tspan + sodipodi:role="line" + id="tspan2875" + x="394.81305" + y="446.23904" + style="font-family:Courier"><tspan + id="tspan2877" + style="font-weight:bold">1</tspan>: REV1</tspan></text> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2879" + width="94.285713" + height="20.714285" + x="370.71414" + y="385.74289" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="394.81305" + y="399.36404" + id="text2881"><tspan + sodipodi:role="line" + id="tspan2883" + x="394.81305" + y="399.36404" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan2885">2</tspan>: REV2</tspan></text> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2887" + width="94.285713" + height="20.714285" + x="370.71414" + y="338.86792" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="394.81305" + y="352.48907" + id="text2889"><tspan + sodipodi:role="line" + id="tspan2891" + x="394.81305" + y="352.48907" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan2893">3</tspan>: REV3</tspan></text> + <rect + style="fill:#a5c3c8;fill-opacity:1;stroke:#6396a0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2895" + width="94.285713" + height="20.714285" + x="370.71414" + y="291.99289" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="394.81305" + y="305.61404" + id="text2897"><tspan + sodipodi:role="line" + id="tspan2899" + x="394.81305" + y="305.61404" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan2901">4</tspan>: REV4</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 417.85701,478.4929 L 417.85701,454.33218" + id="path2903" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 417.85701,431.6179 L 417.85701,407.45718" + id="path2905" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 417.85701,384.7429 L 417.85701,360.58221" + id="path2907" + inkscape:connector-type="polyline" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 417.85701,337.86793 L 417.85701,313.70718" + id="path2909" + inkscape:connector-type="polyline" /> + <rect + style="fill:#78a5ad;fill-opacity:1;stroke:#507b84;stroke-width:2.00000286;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2911" + width="94.285995" + height="20.714283" + x="370.71414" + y="245.17511" /> + <text + xml:space="preserve" + style="font-size:12.00001812px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Courier" + x="394.81274" + y="258.79678" + id="text2913" + transform="scale(1.000002,0.999998)"><tspan + sodipodi:role="line" + id="tspan2915" + x="394.81274" + y="258.79678" + style="font-family:Courier"><tspan + style="font-weight:bold" + id="tspan2917">5</tspan>: REV_my_new_hello</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1.00000143px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 417.85715,291.05004 L 417.85715,266.88929" + id="path2919" + inkscape:connector-type="polyline" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="369.61453" + y="232.25546" + id="text2925"><tspan + sodipodi:role="line" + id="tspan2927" + x="369.61453" + y="232.25546">my-new-hello</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="300.54352" + y="252.12723" + id="text2933"><tspan + sodipodi:role="line" + id="tspan2935" + x="300.54352" + y="252.12723" + style="text-align:center;text-anchor:middle">Los cambios</tspan><tspan + sodipodi:role="line" + x="300.54352" + y="267.12723" + style="text-align:center;text-anchor:middle" + id="tspan3494">más recientes</tspan><tspan + sodipodi:role="line" + x="300.54352" + y="282.12723" + style="text-align:center;text-anchor:middle" + id="tspan3132">difieren</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;text-align:start;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="262.15436" + y="398.37112" + id="text2929"><tspan + sodipodi:role="line" + x="262.15436" + y="398.37112" + id="tspan3013" + style="text-align:start;text-anchor:start">historia común</tspan></text> + <g + id="g3107" + transform="translate(0,0.855744)"> + <path + id="path3101" + d="M 300.35713,381.29075 L 300.35713,304.50504" + style="fill:black;fill-opacity:1;fill-rule:evenodd;stroke:black;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:4, 4;stroke-dashoffset:0;stroke-opacity:1" /> + <path + id="path3105" + d="M 291.07142,301.64789 L 309.28571,301.64789" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#bfbfbf;stroke-width:0.60000002;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + style="fill:black;fill-opacity:1;fill-rule:evenodd;stroke:black;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:4, 4;stroke-dashoffset:0;stroke-opacity:1" + d="M 300.53571,486.38926 L 300.53571,409.60355" + id="path3113" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#bfbfbf;stroke-width:0.60000002;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 291.25,488.49641 L 309.46429,488.49641" + id="path3115" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="480.71429" + y="250.91507" + id="text1949"><tspan + sodipodi:role="line" + id="tspan1951" + x="480.71429" + y="250.91507" + style="text-align:start;text-anchor:start">revisión principal</tspan><tspan + sodipodi:role="line" + x="480.71429" + y="265.91507" + id="tspan1953" + style="text-align:start;text-anchor:start"> (sin hijos)</tspan></text> + </g> +</svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/tour-merge.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,308 @@ +\chapter{Una gira de Mercurial: fusionar trabajo} +\label{chap:tour-merge} + +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 \emph{fusionar} cambios de repositorios separados. + +% TODO cambié streams por líneas. check please +\section{Fusionar líneas de trabajo} + +Fusionar es una parte fundamental de trabajar con una herramienta +de control distribuido de versiones. +\begin{itemize} +\item Alicia y Roberto tienen cada uno una copia personal del + repositorio de un proyecto en el que están trabajando. Alicia + arregla un fallo en su repositorio; Roberto añade una nueva + característica en el suyo. Ambos desean que el repositorio + compartido contenga el arreglo del fallo y la nueva + característica. +\item Frecuentemente trabajo en varias tareas diferentes en un mismo + proyecto al mismo tiempo, cada una aislada convenientemente de las + otras en su propio repositorio. Trabajar de esta manera significa + que a menudo debo fusionar una parte de mi propio trabajo con + otra. +\end{itemize} + +Como fusionar es una operación tan necesaria y común, Mercurial la +facilita. Revisemos el proceso. Empezaremos clonando (otro) +% TODO poner interrogante de apertura +repositorio (ve lo seguido que aparecen?) y haciendo un cambio en él. +\interaction{tour.merge.clone} +Ahora deberíamos tener dos copias de \filename{hello.c} con contenidos +diferentes. El historial de los dos repositorios diverge ahora, como +se ilustra en la figura~\ref{fig:tour-merge:sep-repos}. +\interaction{tour.merge.cat} + +\begin{figure}[ht] + \centering + \grafix{tour-merge-sep-repos} + \caption{Historial reciente divergente de los repositorios + \dirname{my-hello} y \dirname{my-new-hello}} + \label{fig:tour-merge:sep-repos} +\end{figure} + +Ya sabemos que jalar los cambios desde nuestro repositorio +\dirname{my-hello} no tendrá efecto en el directorio de trabajo. +\interaction{tour.merge.pull} +Sin embargo, el comando \hgcmd{pull} dice algo acerca de +``frentes''\ndt{El autor se refiere a \emph{heads} aquí.}. + +\subsection{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 +% TODO cambio en la redacción de la frase, pero espero que conserve el +% sentido. Querido human@, apruebe o corrija :D +hijo. Sin embargo, un repositorio puede contener más de un frente. + +\begin{figure}[ht] + \centering + \grafix{tour-merge-pull} + \caption{Contenidos del repositorio después de jalar + \dirname{my-hello} a \dirname{my-new-hello}} + \label{fig:tour-merge:pull} +\end{figure} + +En la figura~\ref{fig:tour-merge:pull} usted puede ver el efecto que +tiene jalar los cambios de \dirname{my-hello} a \dirname{my-new-hello}. +El historial que ya existía en \dirname{my-new-hello} se mantiene +intacto, pero fue añadida una nueva revisión. Refiriéndonos a la +figura~\ref{fig:tour-merge:sep-repos}, podemos ver que el \emph{ID del +conjunto de cambios} se mantiene igual en el nuevo repositorio, pero +el \emph{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 \hgcmd{heads}\ndt{Frentes.}. +\interaction{tour.merge.heads} + +\subsection{Hacer la fusión} + +% TODO poner interrogante de apertura +Qué pasa si tratamos de usar el comando usual, \hgcmd{update}, para +actualizar el nuevo frente? +\interaction{tour.merge.update} +Mercurial nos indica que el comando \hgcmd{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 \hgcmd{update}, usamos el comando \hgcmd{merge} para hacer +la fusión entre los dos frentes. +\interaction{tour.merge.merge} + +\begin{figure}[ht] + \centering + \grafix{tour-merge-merge} + \caption{Directorio de trabajo y repositorio durante la fusión, y + consignación consecuente} + \label{fig:tour-merge:merge} +\end{figure} + +Esto actualiza el directorio de trabajo, de tal forma que contenga los +cambios de \emph{ambos} frentes, lo que se ve reflejado tanto en la +salida de \hgcmd{parents} como en los contenidos de \filename{hello.c}. +\interaction{tour.merge.parents} + +\subsection{Consignar los resultados de la fusión} + +Siempre que hacemos una fusión, \hgcmd{parents} mostrará dos padres +hasta que consignemos (\hgcmd{commit}) los resultados de la fusión. +\interaction{tour.merge.commit} +Ahora tenemos una nueva revisión de punta; note que tiene \emph{los +dos} frentes anteriores como sus padres. Estos son las mismas +revisiones que mostró previamente el comando \hgcmd{parents}. +\interaction{tour.merge.tip} +En la figura~\ref{fig:tour-merge:merge} 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. + +\section{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 \emph{conflicto}, en +donde usted debe decidir cómo reconciliar ambos cambios y producir un +resultado coherente. + +\begin{figure}[ht] + \centering + \grafix{tour-merge-conflict} + \caption{Cambios con conflictos a un documento} + \label{fig:tour-merge:conflict} +\end{figure} + +La figura~\ref{fig:tour-merge:conflict} 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 \command{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 \command{hgmerge}, definiendo la variable de entorno +\envar{HGMERGE} con el nombre del programa de su preferencia. + +\subsection{Usar una herramienta gráfica para fusión} + +Mi herramienta favorita para hacer fusiones es \command{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 +\command{kdiff3} ejecutándose, en la +figura~\ref{fig:tour-merge:kdiff3}. El tipo de fusión que la +herramienta hace se conoce como \emph{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. +\begin{itemize} +\item A la izquierda está la revisión \emph{base} del fichero, p.ej.~la + versión más reciente de la que descienden las dos versiones que + estamos tratando de fusionar. +\item En la mitad está ``nuestra'' versión del fichero, con las + modificaciones que hemos hecho. +\item A la derecha está la versión del fichero de ``ellos'', la que + forma parte del conjunto de cambios que estamos tratando de + fusionar. +\end{itemize} +En el panel inferior se encuentra el \emph{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 \emph{enlazados}; si avanzamos vertical o +horizontalmente en cualquiera de ellos, los otros son actualizados +para mostrar las secciones correspondientes del fichero que tengan +asociado. + +\begin{figure}[ht] + \centering + \grafix[width=\textwidth]{kdiff3} + \caption{Usando \command{kdiff3} para fusionar versiones de un + fichero} + \label{fig:tour-merge:kdiff3} +\end{figure} + +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 \emph{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). + +% TODO traduje "worked" como "real" +\subsection{Un ejemplo real} + +En este ejemplo, reproduciremos el historial de modificaciones al +fichero de la figura~\ref{fig:tour-merge:conflict} mostrada +anteriormente. Empecemos creando un repositorio con la versión base +de nuestro documento. +\interaction{tour-merge-conflict.wife} +Clonaremos el repositorio y haremos un cambio al fichero. +\interaction{tour-merge-conflict.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.) +\interaction{tour-merge-conflict.son} +Ahora que tenemos dos versiones diferentes de nuestro fichero, +crearemos un entorno adecuado para hacer la fusión. +\interaction{tour-merge-conflict.pull} + +En este ejemplo, no usaré el comando normal de Mercurial para hacer la +fusión (\command{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 +\envar{HGMERGE} para indicarle a Mercurial que use el comando +\command{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 \envar{HGMERGE}. +\interaction{tour-merge-conflict.merge} +Debido a que \command{merge} no puede resolver los conflictos que +aparecen, él deja \emph{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 +\command{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: +% TODO este mercurial no tiene el comando resolve. Revisar si sigue +% siendo necesario +\interaction{tour-merge-conflict.commit} + +\section{Simplificar el ciclo jalar-fusionar-consignar} +\label{sec:tour-merge:fetch} + +El proceso de fusionar cambios delineado anteriomente es directo, pero +requiere la ejecución de tres comandos en sucesión. +\begin{codesample2} + hg pull + hg merge + hg commit -m 'Fusionados cambios remotos' +\end{codesample2} +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 \hgext{fetch}\ndt{Descargar, traer.} que hace precisamente +esto. + +Mercurial cuenta con un mecanismo de extensión flexible que le permite +% TODO lets people => permite a usuarios +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 \hgext{fetch} añade un comando llamado, no +sorpresivamente, \hgcmd{fetch}. Esta extensión actúa como una +combinación de \hgcmd{pull}, \hgcmd{update} y \hgcmd{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 \hgext{fetch} es fácil. Edite su +\sfilename{.hgrc}, y vaya a (o cree) la sección +\rcsection{extensions}. Luego añada una línea que diga simplemente +``\Verb+fetch +''. +\begin{codesample2} + [extensions] + fetch = +\end{codesample2} +(Normalmente, a la derecha del ``\texttt{=}'' debería aparecer la +ubicación de la extensión, pero como el comando \hgext{fetch} es parte +de la distribución estándar, Mercurial sabe dónde buscarla.) + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/undo-manual-merge.dot Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,8 @@ +digraph undo_manual { + "primer cambio" -> "segundo cambio"; + "segundo cambio" -> "tercer cambio"; + reversar [label="reversar\nsegundo cambio", shape=box]; + "segundo cambio" -> reversar; + "tercer cambio" -> "fusión\nmanual"; + reversar -> "fusión\nmanual"; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/undo-manual.dot Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,6 @@ +digraph undo_manual { + "primer cambio" -> "segundo cambio"; + "segundo cambio" -> "tercer cambio"; + reversar [label="reversar\nsegundo cambio", shape=box]; + "segundo cambio" -> reversar; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/undo-non-tip.dot Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,9 @@ +digraph undo_non_tip { + "primer cambio" -> "segundo cambio"; + "segundo cambio" -> "tercer cambio"; + reversar [label="reversar\nsegundo cambio", shape=box]; + "segundo cambio" -> reversar; + merge [label="automatizar\nfusión", shape=box]; + "tercer cambio" -> fusión; + reversar -> fusión; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/undo-simple.dot Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,4 @@ +digraph undo_simple { + "primer cambio" -> "segundo cambio"; + "segundo cambio" -> "reversar\nsegundo cambio"; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/undo.tex Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,799 @@ +\chapter{Encontrar y arreglar sus equivocaciones} +\label{chap:undo} + +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. + +\section{Borrar el historial local} + +\subsection{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 \hgcmd{add}. Un conjunto de cambios``simplemente malo'' no es +tan común, pero sí resulta muy molesto. + +\subsection{Hacer rollback una transacción} +\label{sec:undo:rollback} + +En la sección~\ref{sec:concepts:txn}, mencioné que Mercurial trata +modificación a un repositorio como una \emph{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 \emph{roll back}\ndt{El significado igual que en los + ambientes de sistemas manejadores de bases de datos se refiere a + la atomicidad e integridad al devolver un conjunto de acciones que + permitan dejar el repositorio en un estado consistente previo}, +exactamente una de tales acciones usando la orden \hgcmd{rollback}. +(Ver en la sección~\ref{sec:undo:rollback-after-push} 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 \hgcmd{add}. +\interaction{rollback.commit} +La salida de \hgcmd{status} después de la consignación confirma +inmediatamente este error. +\interaction{rollback.status} +La consignación capturó los cambios en el fichero \filename{a}, pero +no el nuevo fichero \filename{b}. Si yo publicara este conjunto de +cambios a un repositorio compartido con un colega, es bastante +probable que algo en \filename{a} se refiriera a \filename{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 \hgcmd{rollback}, y Mercurial +hace desaparecer el último conjunto de cambios. +\interaction{rollback.rollback} +El conjunto de cambios ya no está en el historial del repositorio, y el +directorio de trabajo cree que el fichero \filename{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 \hgcmd{add} +al fichero \filename{b}, y hacer de nuevo la consignación. +\interaction{rollback.add} + +\subsection{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 \hgcmd{rollback} excluirá eficientemente los conjuntos de +cambios que haya acabado de jalar. Mercurial agrupa todos los cambios +de un \hgcmd{pull} a una única transacción y bastará con un +\hgcmd{rollback} para deshacer esta equivocación. + +\subsection{Después de publicar, un roll back es futil} +\label{sec:undo:rollback-after-push} + +El valor de \hgcmd{rollback} se anula cuando ha publicado sus cambios +a otro repositorio. Un cambio desaparece totalmente al hacer roll back, +pero \emph{solamente} en el repositorio en el cual aplica +\hgcmd{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ó, \emph{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.) + +\subsection{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á. +\interaction{rollback.twice} +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. + +\section{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 \hgcmd{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 \hgcmd{revert} con un ejemplo +pequeño. Comenzaremos modificando un fichero al cual Mercurial ya está +siguiendo. +\interaction{daily.revert.modify} +Si no queremos ese cambio, podemos aplicar \hgcmd{revert} al fichero. +\interaction{daily.revert.unmodify} +La orden \hgcmd{revert} nos brinda un grado adicional de seguridad +guardando nuestro fichero modificado con la extensión \filename{.orig}. +\interaction{daily.revert.status} + +Este es un resumen de casos en los cuales la orden \hgcmd{revert} es +de utilidad. Describiremos cada uno de ellos con más detalle en la +sección siguiente. +\begin{itemize} +\item Si usted modifica un fichero, lo restaurará a su estado sin + modificación previo. +\item Si usted hace \hgcmd{add} a un fichero, revertirá el estado de + ``adicionado'' del fichero, pero no lo tocará +\item Si borra un fichero sin decirle a Mercurial, restaurará el + fichero con sus contenidos sin modificación. +\item Si usa la orden \hgcmd{remove} para eliminar un fichero, deshará + el estado ``removido'' del fichero, y lo restaurará con sus + contenidos sin modificación. +\end{itemize} + +\subsection{Errores al administrar ficheros} +\label{sec:undo:mgmt} + +La orden \hgcmd{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---\hgcmd{add}, \hgcmd{remove}, y las +demás. + +Si usted hace \hgcmd{add} a un fichero, y no deseaba que Mercurial le +diera seguimiento, use \hgcmd{revert} para deshacer la adición. No se +preocupe; Mercurial no modificará de forma alguna el fichero. +Solamente lo ``desmarcará''. +\interaction{daily.revert.add} + +De forma similar, Si le solicita a Mercurial hacer \hgcmd{remove} a un +fichero, puede usar \hgcmd{revert} para restarurarlo a los contenidos +que tenía la revisión padre del directorio de trabajo. +\interaction{daily.revert.remove} +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''). +\interaction{daily.revert.missing} + +Si usted revierte un \hgcmd{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. +\interaction{daily.revert.copy} + +\subsubsection{Un caso ligeramente especial:revertir un renombramiento} + +Si hace \hgcmd{rename} a un fichero, hay un detalle que debe tener en +cuenta. Cuando aplica \hgcmd{revert} a un cambio de nombre, no es +suficiente proveer el nombre del fichero destino, como puede verlo en +el siguiente ejemplo. +\interaction{daily.revert.rename} +Como puede ver en la salida de \hgcmd{status}, el fichero con el nuevo +nombre no se identifica más como agregado, pero el fichero con el +nombre-\emph{inicial} se elimna! Esto es contra-intuitivo (por lo +menos para mí), pero por lo menos es fácil arreglarlo. +\interaction{daily.revert.rename-orig} +Por lo tanto, recuerde, para revertir un \hgcmd{rename}, debe proveer +\emph{ambos} nombres, la fuente y el destino. + +% TODO: the output doesn't look like it will be removed! + +(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. + +\section{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 +\hgcmd{backout} deshace cambios \emph{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~\ref{sec:undo:aaaiiieee}. + +\subsection{Retroceder un conjunto de cambios} + +La orden \hgcmd{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 \emph{no} se deshace del conjunto +de cambios que usted desea deshacer. En cambio, crea un nuevo +conjunto de cambios que \emph{reversa} el conjunto de cambios que +usted indique. + +La operación de la orden \hgcmd{backout} es un poco intrincada, y lo +ilustraremos con algunos ejemplos. Primero crearemos un repositorio +con algunos cambios sencillos. +\interaction{backout.init} + +La orden \hgcmd{backout} toma un ID de conjunto de cambios como su +argumento; el conjunto de cambios a retroceder. Normalmente +\hgcmd{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 \hgopt{backout}{-m}. + +\subsection{Retroceder el conjunto de cambios punta} + +Comenzamos retrocediendo el último conjunto de cambios que consignamos. +\interaction{backout.simple} +Puede ver que la segunda línea de \filename{myfile} ya no está +presente. La salida de \hgcmd{log} nos da una idea de lo que la orden +\hgcmd{backout} ha hecho. +\interaction{backout.simple.log} +Vea que el nuevo conjunto de cambios que \hgcmd{backout} ha creado es +un hijo del conjunto de cambios que retrocedimos. Es más sencillo de +ver en la figura~\ref{fig:undo:backout}, que presenta una vista +gráfica del historial de cambios. Como puede ver, el historial es +bonito y lineal. + +\begin{figure}[htb] + \centering + \grafix{undo-simple} + \caption{Retroceso de un cambio con la orden \hgcmd{backout}} + \label{fig:undo:backout} +\end{figure} + +\subsection{Retroceso de un cambio que no es la punta} + +Si desea retrocede un cambio distinto al último que ha consignado, use +la opción \hgopt{backout}{--merge} a la orden \hgcmd{backout}. +\interaction{backout.non-tip.clone} +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. +\interaction{backout.non-tip.backout} + +Si ve los contenidos del fichero \filename{myfile} después de +finalizar el retroceso, verá que el primer y el tercer cambio están +presentes, pero no el segundo. +\interaction{backout.non-tip.cat} + +Como lo muestra el historial gráfico en la +figura~\ref{fig:undo:backout-non-tip}, Mercurial realmente consigna +\emph{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. + +% TODO: to me it looks like mercurial doesn't commit the second merge automatically! + +\begin{figure}[htb] + \centering + \grafix{undo-non-tip} + \caption{Retroceso automatizado de un cambio a algo que no es la punta con la orden \hgcmd{backout}} + \label{fig:undo:backout-non-tip} +\end{figure} + +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. + +\subsubsection{Use siempre la opción \hgopt{backout}{--merge}} + +De hecho, dado que la opción \hgopt{backout}{--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 \emph{siempre} esta opción cuando +ejecuta la orden \hgcmd{backout}. + +\subsection{Más control sobre el proceso de retroceso} + +A pesar de que recomiendo usar siempre la opción +\hgopt{backout}{--merge} cuando está retrocediendo un cambio, la orden +\hgcmd{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 \hgcmd{backout} está haciendo automáticamente para +usted. Para ilustrarlo, clonemos nuestro primer repositorio, pero +omitamos el retroceso que contiene. + +\interaction{backout.manual.clone} +Como en el ejemplo anterior, consignaremos un tercer cambio, después +haremos retroceso de su padre, y veremos qué pasa. +\interaction{backout.manual.backout} +Nuestro nuevo conjunto de cambios es de nuevo un descendiente del +conjunto de cambio que retrocedimos; es por lo tanto una nueva cabeza, +\emph{no} un descendiente del conjunto de cambios que era la punta. La +orden \hgcmd{backout} fue muy explícita diciéndolo. +\interaction{backout.manual.log} + +De nuevo, es más sencillo lo que pasó viendo una gráfica del +historial de revisiones, en la figura~\ref{fig:undo:backout-manual}. +Esto nos aclara que cuando usamos \hgcmd{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). + +\begin{figure}[htb] + \centering + \grafix{undo-manual} + \caption{Retroceso usando la orden \hgcmd{backout}} + \label{fig:undo:backout-manual} +\end{figure} + +Después de que la orden \hgcmd{backout} ha terminado, deja un nuevo +conjunto de cambios de ``retroceso'' como el padre del directorio de trabajo. +\interaction{backout.manual.parents} +Ahora tenemos dos conjuntos de cambios aislados. +\interaction{backout.manual.heads} + +Reflexionemos acerca de lo que esperamos ver como contenidos de +\filename{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, \emph{no} +esperamos ver el tercer cambio presente en \filename{myfile}. +\interaction{backout.manual.cat} +Para que el tercer cambio esté en el fichero, hacemos una fusión usual +de las dos cabezas. +\interaction{backout.manual.merge} +Después de eso, el historial gráfica de nuestro repositorio luce como +la figura~\ref{fig:undo:backout-manual-merge}. + +\begin{figure}[htb] + \centering + \grafix{undo-manual-merge} + \caption{Fusión manual de un retroceso} + \label{fig:undo:backout-manual-merge} +\end{figure} + +\subsection{Por qué \hgcmd{backout} hace lo que hace} + +Esta es una descripción corta de cómo trabaja la orden \hgcmd{backout}. +\begin{enumerate} +\item Se asegura de que el directorio de trabajo es ``limpio'', esto + es, que la salida de \hgcmd{status} debería ser vacía. +\item Recuerda el padre actual del directorio de trabajo. A este + conjunto de cambio lo llamaremos \texttt{orig} +\item Hace el equivalente de un \hgcmd{update} para sincronizar el + directorio de trabajo con el conjunto de cambios que usted quiere + retroceder. Lo llamaremos \texttt{backout} +\item Encuentra el padre del conjunto de cambios. Lo llamaremos + \texttt{parent}. +\item Para cada fichero del conjunto de cambios que el + \texttt{retroceso} afecte, hará el equivalente a + \hgcmdargs{revert}{-r parent} sobre ese fichero, para restaurarlo a + los contenidos que tenía antes de que el conjunto de cambios fuera + consignado. +\item Se consigna el resultado como un nuevo conjunto de cambios y + tiene a \texttt{backout} como su padre. +\item Si especifica \hgopt{backout}{--merge} en la línea de comandos, + se fusiona con \texttt{orig}, y se consigna el resultado de la + fusión. +\end{enumerate} + +Una vía alternativa de implementar la orden \hgcmd{backout} sería usar +\hgcmd{export} sobre el conjunto de cambios a retroceder como un diff +y después usar laa opción \cmdopt{patch}{--reverse} de la orden +\command{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 \hgcmd{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 \emph{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 +\command{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 \command{patch} usa para +determinar si puede aplicar un parche (si esto suena como cháchara, +vea una discusión de la orden \command{patch} en \ref{sec:mq:patch}). +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 \command{patch} puede manejar. + +\section{Cambios que nunca debieron ocurrir} +\label{sec:undo:aaaiiieee} + +En la mayoría de los casos, la orden \hgcmd{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 \emph{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 \hgcmd{rollback} con +seguridad, como detallé en la sección~\ref{sec:undo:rollback}. + +Después de que usted haya publicado un cambio en otro repositorio, usted +\emph{podría} usar la orden \hgcmd{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 \emph{intentar} +deshacerse del conjunto de cambios de \emph{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 \emph{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. + +%Dejamos de traducir lo que viene a continuación, porque será +%modificado por upstream... + +XXX This needs filling out. The \texttt{hg-replay} script in the +\texttt{examples} directory works, but doesn't handle merge +changesets. Kind of an important omission. + +\subsection{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~\ref{chap:hook}), 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. + +\section{Al encuentro de la fuente de un fallo} +\label{sec:undo:bisect} + +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 +\hgcmd{bisect}, que ayuda a automatizar este proceso y a alcanzarlo +muy eficientemente. + +La idea tras la orden \hgcmd{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 \hgcmd{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. +\begin{itemize} +\item La versión más reciente de su programa tiene un fallo que usted + recuerda no estaba hace unas semanas, pero no sabe cuándo fue + introducido. En este caso, su prueba binaria busca la presencia de + tal fallo. +\item Usted arregló un fallo en un apurto, y es hora de dar por + cerrado el caso en la base de datos de fallos de su equipo de + trabajo. La base de datos de fallos requiere el ID del conjunto de + cambios que permita dar por cerrado el caso, pero usted no recuerda + qué conjunto de cambios arregló tal fallo. De nuevo la prueba + binaria revisa la presencia del fallo. +\item Su programa funciona correctamente, pero core ~15\% más lento + que la última vez que lo midió. Usted desea saber qué conjunto de + cambios introdujo esta disminución de desempeño. En este caso su + prueba binaria mide el desempeño de su programa, para ver dónde es + ``rápido'' y dónde es ``lento''. +\item Los tamaños de los componentes del proyecto que usted lleva se + expandieron recientemente, y sospecha que algo cambio en la forma en + que se construye su proyecto. +\end{itemize} + +Para estos ejemplos debería ser claro que la orden \hgcmd{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 \emph{prueba} es algo que \emph{usted} ejecuta cuando +\hgcmd{bisect} elige un conjunto de cambios. Un \emph{sondeo} es lo que +\hgcmd{bisect} ejecuta para decidir si una revisión es buena. Finalmente, +usaremos la palabra ``biseccionar', en frases como ``buscar con la +orden \hgcmd{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 \emph{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 \hgcmd{bisect} usa su conocimiento de la ``forma'' del +historial de revisiones de su proyecto para hacer una búsqueda +proporcional al \emph{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 \hgcmd{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. + +\subsection{Uso de la orden \hgcmd{bisect}} + +A continuación un ejemplo de \hgcmd{bisect} en acción. + +\begin{note} + En las versiones 0.9.5 y anteriores de Mercurial, \hgcmd{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. +\end{note} + +Creamos un repostorio para probar el comando \hgcmd{bisect} de forma +aislada +\interaction{bisect.init} +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''. +\interaction{bisect.commits} + +A continuación observaremos cómo usar la orden \hgcmd{bisect}. Podemos +usar el mecanismo de ayuda embebida que trae Mercurial. +\interaction{bisect.help} + +La orden \hgcmd{bisect} trabaja en etapas, de la siguiente forma: +\begin{enumerate} +\item Usted ejecuta una prueba binaria. + \begin{itemize} + \item Si la prueba es exitosa, usted se lo indicará a \hgcmd{bisect} + ejecutando la orden \hgcmdargs{bisect}{good}. + \item Si falla, ejecutará la orden \hgcmdargs{bisect}{--bad}. + \end{itemize} +\item La orden usa su información para decidir qué conjuntos de + cambios deben probarse a continuación. +\item Actualiza el directorio de trabajo a tal conjunto de cambios y + el proceso se lleva a cabo de nuevo. +\end{enumerate} +El proceso termina cuando \hgcmd{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 +\hgcmdargs{bisect}{--reset}. +\interaction{bisect.search.init} + +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''. +\interaction{bisect.search.bad-init} + +Nuestra próxima tarea es nominar al conjunto de cambios que sabemos +\emph{no} tiene el fallo; la orden \hgcmd{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''.) +\interaction{bisect.search.good-init} + +Note que esta orden mostró algo. +\begin{itemize} +\item Nos dijo cuántos conjuntos de cambios debe considerar antes de + que pueda identifica aquel que introdujo el fallo, y cuántas pruebas + se requerirán. +\item Actualizó el directorio de trabajo al siguiente conjunto de + cambios, y nos dijo qué conjunto de cambios está evaluando. +\end{itemize} + +Ahora ejecutamos nuestra prueba en el directorio de trabajo. Usamos la +orden \command{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. +\interaction{bisect.search.step1} + +Esta prueba luce como candidata perfecta para automatizarse, por lo +tanto la convertimos en una función de interfaz de comandos. +\interaction{bisect.search.mytest} +Ahora podemos ejecutar un paso entero de pruebas con un solo comando, +\texttt{mytest}. +\interaction{bisect.search.step2} +Unas invocaciones más de nuestra prueba, y hemos terminado. +\interaction{bisect.search.rest} + +Aunque teníamos unos~40 conjuntos de cambios en los cuales buscar, la +orden \hgcmd{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 \hgcmd{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. + +\subsection{Limpieza después de la búsqueda} + +Cuando haya terminado de usar la orden \hgcmd{bisect} en un +repositorio, puede usar la orden \hgcmdargs{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, \hgcmd{bisect} no le +permitirá comenzar una nueva búsqueda sobre el repositorio hasta que +no aplique \hgcmdargs{bisect}{reset}. +\interaction{bisect.search.reset} + +\section{Consejos para encontrar fallos efectivamente} + +\subsection{Dar una entrada consistente} + +La orden \hgcmd{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, \emph{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. + +\subsection{Automatizar tanto como se pueda} + +Cuando comencé a usar la orden \hgcmd{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 \hgcmd{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 +\hgcmd{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: +\begin{itemize} +\item pruebe siempre buscando el mismo síntoma y +\item ofrezca siempre datos consistentes a la orden \hgcmd{bisect}. +\end{itemize} +En mi tutorial de ejemplo anterior, la orden \command{grep} busca el +síntoma, y la construcción \texttt{if} toma el resultado de esta +prueba y verifica que siempre alimentamos con los mismos datos a la +orden \hgcmd{bisect}. La función \texttt{mytest} los une de una forma +reproducible, logrando que cada prueba sea uniforme y consistente. + +\subsection{Verificar los resultados} + +Dado que la salida de la búsqueda de \hgcmd{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: +\begin{itemize} +\item El conjunto de cambios que se reportó como la primera versión + erronea. Su prueba debería dar un reporte de fallo. +\item El conjunto de cambios padre (cada padre, si es una fusión). + Su prueba debería reportar este(os) conjunto(s) de cambios como + bueno(s). +\item Un hijo del conjunto de cambios. Su prueba debería reportar al + conjunto de cambios hijo como malo. +\end{itemize} + +\subsection{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 \hgcmd{bisect} no podrá ayudarle +directamente. En cambio, puede marcar este conjunto de cambios como +al ejecutar \hgcmdargs{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 \hgcmd{bisect}. + +Otra situación en la cual sería de mucha utilidad emplear a +\hgcmdargs{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. + +\subsection{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 \hgcmd{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 +\hgcmd{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 \hgcmd{bisect}, gracias al comportamiento logarítmico. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "00book" +%%% End:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/wdir-after-commit.svg Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,413 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg5971" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docbase="/home/bos/hg/hgbook/en" + sodipodi:docname="wdir-after-commit.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs5973"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective3128" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6445" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.000474,0,0,0.790947,-240.246,50.9948)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <marker + inkscape:stockid="Arrow1Mstart" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mstart" + style="overflow:visible"> + <path + id="path4855" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none" + transform="scale(0.4) translate(10,0)" /> + </marker> + <linearGradient + id="linearGradient6049"> + <stop + style="stop-color:#686868;stop-opacity:1;" + offset="0" + id="stop6051" /> + <stop + style="stop-color:#f0f0f0;stop-opacity:1;" + offset="1" + id="stop6053" /> + </linearGradient> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mend" + style="overflow:visible;"> + <path + id="path4852" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;" + transform="scale(0.4) rotate(180) translate(10,0)" /> + </marker> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6083" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-240.0462,-8.633237e-6)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6142" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-42.00893,-30.49544)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6193" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-240.0462,-8.633237e-6)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6216" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-6.0462,-0.664361)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6232" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.000474,0,0,0.790947,222.8399,50.85693)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6772" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.000474,0,0,0.790947,222.8399,50.85693)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.90509668" + inkscape:cx="390.0539" + inkscape:cy="602.10507" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showguides="true" + inkscape:guide-bbox="true" + inkscape:window-width="906" + inkscape:window-height="659" + inkscape:window-x="11" + inkscape:window-y="8" + showgrid="false"> + <sodipodi:guide + orientation="vertical" + position="-1.4285714" + id="guide6022" /> + </sodipodi:namedview> + <metadata + id="metadata5976"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <rect + y="245.98355" + x="328.23956" + height="258.57144" + width="174.28572" + id="rect6047" + style="fill:url(#linearGradient6216);fill-opacity:1;stroke:#686868;stroke-width:0.74800003;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + id="g6261" + transform="translate(234,0)"> + <rect + y="258.7149" + x="114.11369" + height="44.537449" + width="134.53746" + id="rect5983" + style="fill:#b1b1b1;fill-opacity:1;stroke:black;stroke-width:0.7482574;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:1.49651474, 0.74825737;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text5985" + y="284.47562" + x="138.7962" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="284.47562" + x="138.7962" + id="tspan5987" + sodipodi:role="line">dfbbb33f3fa3</tspan></text> + </g> + <rect + style="fill:#d4d4d4;fill-opacity:1;stroke:black;stroke-width:0.7482574;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:1.49651474, 0.74825737;stroke-dashoffset:0;stroke-opacity:1" + id="rect5996" + width="134.53746" + height="44.537449" + x="348.11371" + y="320.38159" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="372.7962" + y="346.1423" + id="text5998"><tspan + sodipodi:role="line" + id="tspan6000" + x="372.7962" + y="346.1423" + style="font-family:Courier">e7639888bb2f</tspan></text> + <rect + style="fill:#d4d4d4;fill-opacity:1;stroke:black;stroke-width:0.7482574;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:1.49651474, 0.74825737;stroke-dashoffset:0;stroke-opacity:1" + id="rect6004" + width="134.53746" + height="44.537449" + x="348.11371" + y="382.04825" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="370.65421" + y="407.80896" + id="text6006"><tspan + sodipodi:role="line" + id="tspan6008" + x="370.65421" + y="407.80896" + style="font-family:Courier">7b064d8bac5e</tspan></text> + <path + inkscape:connector-type="polyline" + id="path6018" + d="M 415.38242,303.62646 L 415.38242,320.00744" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" /> + <path + inkscape:connection-end="#rect6004" + inkscape:connector-type="polyline" + id="path6020" + d="M 415.38242,365.29315 L 415.38243,381.67412" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" /> + <rect + style="fill:#ededed;fill-opacity:1;stroke:#797979;stroke-width:0.74800003;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect6039" + width="134.53746" + height="44.537449" + x="348.11359" + y="443.71487" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#979797;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="372.79706" + y="469.47556" + id="text6041"><tspan + sodipodi:role="line" + id="tspan6043" + x="372.79706" + y="469.47556" + style="fill:#979797;fill-opacity:1;font-family:Courier">000000000000</tspan></text> + <path + inkscape:connection-end="#rect6039" + inkscape:connector-type="polyline" + id="path6045" + d="M 415.38238,426.95981 L 415.38235,443.34087" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#686868;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="327.66046" + y="231.36218" + id="text6102"><tspan + sodipodi:role="line" + id="tspan6104" + x="327.66046" + y="231.36218">Historia en el repositorio</tspan></text> + <rect + y="245.94225" + x="557.28418" + height="204.51619" + width="174.36833" + id="rect6140" + style="fill:url(#linearGradient6232);fill-opacity:1;stroke:#686868;stroke-width:0.66539276;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + id="g6130" + transform="translate(262.3254,24.38544)"> + <rect + style="fill:#d4d4d4;fill-opacity:1;stroke:black;stroke-width:0.7482574;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:1.49651474, 0.74825737;stroke-dashoffset:0;stroke-opacity:1" + id="rect6106" + width="134.53746" + height="44.537449" + x="314.87415" + y="257.95059" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="339.55664" + y="283.7113" + id="text6108"><tspan + sodipodi:role="line" + id="tspan6110" + x="339.55664" + y="283.7113" + style="font-family:Courier">dfbbb33f3fa3</tspan></text> + </g> + <g + id="g6135" + transform="translate(263.0396,49.83106)"> + <rect + inkscape:transform-center-y="102.85714" + inkscape:transform-center-x="129.28571" + style="fill:#ededed;fill-opacity:1;stroke:#797979;stroke-width:0.74800003;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect6112" + width="134.53746" + height="44.537449" + x="314.15985" + y="326.52203" /> + <text + inkscape:transform-center-y="102.7311" + inkscape:transform-center-x="128.69672" + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#979797;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="338.84335" + y="352.28271" + id="text6114"><tspan + sodipodi:role="line" + id="tspan6116" + x="338.84335" + y="352.28271" + style="fill:#979797;fill-opacity:1;font-family:Courier">000000000000</tspan></text> + </g> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="576.63208" + y="270.479" + id="text6118"><tspan + sodipodi:role="line" + id="tspan6120" + x="576.63208" + y="270.479">Primer padre</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="576.07544" + y="364.49615" + id="text6122"><tspan + sodipodi:role="line" + id="tspan6124" + x="576.07544" + y="364.49615">Segundo padre</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="556.61743" + y="231.36218" + id="text6195"><tspan + sodipodi:role="line" + id="tspan6197" + x="556.61743" + y="231.36218">Padres del directorio de trabajo</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 576.82542,297.63008 L 483.02528,287.95831" + id="path6266" + inkscape:connector-type="polyline" + inkscape:connection-start="#g6130" + inkscape:connection-end="#g6261" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 665.12232,418.17579 L 665.12232,418.17579" + id="path6270" + inkscape:connector-type="polyline" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="316.86407" + y="275.6496" + id="text6573"><tspan + sodipodi:role="line" + id="tspan6575" + x="316.86407" + y="275.6496" + style="text-align:end;text-anchor:end">Nuevo</tspan><tspan + sodipodi:role="line" + x="316.86407" + y="290.6496" + id="tspan6577" + style="text-align:end;text-anchor:end">conjunto</tspan><tspan + sodipodi:role="line" + x="316.86407" + y="305.6496" + style="text-align:end;text-anchor:end" + id="tspan3470">de</tspan><tspan + sodipodi:role="line" + x="316.86407" + y="320.6496" + style="text-align:end;text-anchor:end" + id="tspan3472">cambios</tspan></text> + </g> +</svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/wdir-branch.svg Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,427 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg5971" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docbase="/home/bos/hg/hgbook/en" + sodipodi:docname="wdir-branch.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs5973"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective3193" /> + <marker + inkscape:stockid="Arrow1Mstart" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mstart" + style="overflow:visible"> + <path + id="path4855" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none" + transform="scale(0.4) translate(10,0)" /> + </marker> + <linearGradient + id="linearGradient6049"> + <stop + style="stop-color:#686868;stop-opacity:1;" + offset="0" + id="stop6051" /> + <stop + style="stop-color:#f0f0f0;stop-opacity:1;" + offset="1" + id="stop6053" /> + </linearGradient> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mend" + style="overflow:visible;"> + <path + id="path4852" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;" + transform="scale(0.4) rotate(180) translate(10,0)" /> + </marker> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6083" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-240.0462,-8.633237e-6)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6142" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-42.00893,-30.49544)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6193" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-240.0462,-8.633237e-6)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6216" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.000474,0,0,0.790947,-240.246,50.9948)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6232" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.000473,0,0,0.790947,-11.16012,50.85693)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6445" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.000474,0,0,0.790947,-240.246,50.9948)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6974" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.911882,0,0,0.789965,-574.7896,51.22599)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6996" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.000473,0,0,0.790947,112.8399,50.85693)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.90509668" + inkscape:cx="345.85973" + inkscape:cy="690.49342" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showguides="true" + inkscape:guide-bbox="true" + inkscape:window-width="906" + inkscape:window-height="659" + inkscape:window-x="75" + inkscape:window-y="69" + showgrid="false"> + <sodipodi:guide + orientation="vertical" + position="-1.4285714" + id="guide6022" /> + </sodipodi:namedview> + <metadata + id="metadata5976"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <rect + y="246.06918" + x="64.325172" + height="204.26233" + width="333.2135" + id="rect6047" + style="fill:url(#linearGradient6974);fill-opacity:1;stroke:#686868;stroke-width:0.91925466;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + id="g1935"> + <rect + y="266.24374" + x="84.113708" + height="44.537449" + width="134.53746" + id="rect5996" + style="fill:#d4d4d4;fill-opacity:1;stroke:black;stroke-width:0.7482574;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:1.49651474, 0.74825737;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text5998" + y="292.00446" + x="108.7962" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="292.00446" + x="108.7962" + id="tspan6000" + sodipodi:role="line">e7639888bb2f</tspan></text> + </g> + <g + id="g6976" + transform="translate(70,0)"> + <rect + y="327.9104" + x="40.113693" + height="44.537449" + width="134.53746" + id="rect6004" + style="fill:#d4d4d4;fill-opacity:1;stroke:black;stroke-width:0.7482574;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:1.49651474, 0.74825737;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text6006" + y="353.67111" + x="62.654205" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="353.67111" + x="62.654205" + id="tspan6008" + sodipodi:role="line">7b064d8bac5e</tspan></text> + </g> + <path + inkscape:connector-type="polyline" + id="path6020" + d="M 160.92915,311.15532 L 167.83571,327.53627" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1;display:inline" + inkscape:connection-end="#g6976" + inkscape:connection-start="#g1935" /> + <rect + style="fill:#ededed;fill-opacity:1;stroke:#797979;stroke-width:0.74800003;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect6039" + width="134.53746" + height="44.537449" + x="110.11359" + y="389.57703" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#979797;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="134.79706" + y="415.33771" + id="text6041"><tspan + sodipodi:role="line" + id="tspan6043" + x="134.79706" + y="415.33771" + style="fill:#979797;fill-opacity:1;font-family:Courier">000000000000</tspan></text> + <path + inkscape:connection-end="#rect6039" + inkscape:connector-type="polyline" + id="path6045" + d="M 177.38238,372.82195 L 177.38235,389.20303" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#686868;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" /> + <rect + y="245.94225" + x="447.28412" + height="204.51619" + width="174.36833" + id="rect6140" + style="fill:url(#linearGradient6996);fill-opacity:1;stroke:#686868;stroke-width:0.66539276;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + id="g6130" + transform="translate(152.3254,24.38544)"> + <rect + style="fill:#d4d4d4;fill-opacity:1;stroke:black;stroke-width:0.7482574;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:1.49651474, 0.74825737;stroke-dashoffset:0;stroke-opacity:1" + id="rect6106" + width="134.53746" + height="44.537449" + x="314.87415" + y="257.95059" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="339.55664" + y="283.7113" + id="text6108"><tspan + sodipodi:role="line" + id="tspan6110" + x="339.55664" + y="283.7113" + style="font-family:Courier">ffb20e1701ea</tspan></text> + </g> + <g + id="g6135" + transform="translate(153.0396,49.83106)"> + <rect + inkscape:transform-center-y="102.85714" + inkscape:transform-center-x="129.28571" + style="fill:#ededed;fill-opacity:1;stroke:#797979;stroke-width:0.74800003;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect6112" + width="134.53746" + height="44.537449" + x="314.15985" + y="326.52203" /> + <text + inkscape:transform-center-y="102.7311" + inkscape:transform-center-x="128.69672" + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#979797;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="338.84335" + y="352.28271" + id="text6114"><tspan + sodipodi:role="line" + id="tspan6116" + x="338.84335" + y="352.28271" + style="fill:#979797;fill-opacity:1;font-family:Courier">000000000000</tspan></text> + </g> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="466.63208" + y="270.479" + id="text6118"><tspan + sodipodi:role="line" + id="tspan6120" + x="466.63208" + y="270.479">Primer padre</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="466.07544" + y="364.49615" + id="text6122"><tspan + sodipodi:role="line" + id="tspan6124" + x="466.07544" + y="364.49615">Segundo padre</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="446.61743" + y="231.36218" + id="text6195"><tspan + sodipodi:role="line" + id="tspan6197" + x="446.61743" + y="231.36218">Padres del directorio de trabajo</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline" + d="M 466.82542,300.21999 L 377.00207,294.39744" + id="path6266" + inkscape:connector-type="polyline" + inkscape:connection-start="#g6130" + inkscape:connection-end="#rect1925" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 665.12232,418.17579 L 665.12232,418.17579" + id="path6270" + inkscape:connector-type="polyline" /> + <g + id="g2845"> + <rect + y="266.24374" + x="242.09048" + height="44.537449" + width="134.53746" + id="rect1925" + style="fill:#9f9f9f;fill-opacity:1;stroke:black;stroke-width:0.7482574;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:1.49651474, 0.74825737;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text1927" + y="292.00446" + x="266.77298" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="292.00446" + x="266.77298" + id="tspan1929" + sodipodi:role="line">ffb20e1701ea</tspan></text> + </g> + <path + inkscape:connector-type="polyline" + id="path1933" + d="M 260.89978,311.15532 L 225.84185,327.53627" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1;display:inline" + inkscape:connection-end="#g6976" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="109.45568" + y="231.4554" + id="text2837"><tspan + sodipodi:role="line" + id="tspan2839" + x="109.45568" + y="231.4554">Cabeza Pre-existente</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="237.54184" + y="231.4554" + id="text2841"><tspan + sodipodi:role="line" + id="tspan2843" + x="237.54184" + y="231.4554">Cabeza recién creada (y tip)</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow1Mend)" + d="M 148.05048,235.87482 L 149.94915,265.86962" + id="path2850" + inkscape:connector-type="polyline" + inkscape:connection-end="#g1935" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow1Mend)" + d="M 303.83495,238.08453 L 306.87874,265.86962" + id="path2852" + inkscape:connector-type="polyline" + inkscape:connection-end="#g2845" /> + </g> +</svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/wdir-merge.svg Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,434 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg5971" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docbase="/home/bos/hg/hgbook/en" + sodipodi:docname="wdir-merge.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs5973"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective3259" /> + <marker + inkscape:stockid="Arrow1Mstart" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mstart" + style="overflow:visible"> + <path + id="path4855" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none" + transform="scale(0.4) translate(10,0)" /> + </marker> + <linearGradient + id="linearGradient6049"> + <stop + style="stop-color:#686868;stop-opacity:1;" + offset="0" + id="stop6051" /> + <stop + style="stop-color:#f0f0f0;stop-opacity:1;" + offset="1" + id="stop6053" /> + </linearGradient> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mend" + style="overflow:visible;"> + <path + id="path4852" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;" + transform="scale(0.4) rotate(180) translate(10,0)" /> + </marker> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6083" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-240.0462,-8.633237e-6)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6142" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-42.00893,-30.49544)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6193" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-240.0462,-8.633237e-6)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6216" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.000474,0,0,0.790947,-240.246,50.9948)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6232" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.000473,0,0,0.790947,-11.16012,50.85693)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6445" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.000474,0,0,0.790947,-240.246,50.9948)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6974" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.911882,0,0,0.789965,-574.7896,51.22599)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6996" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.000473,0,0,0.790947,112.8399,50.85693)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.28" + inkscape:cx="345.85973" + inkscape:cy="690.49342" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showguides="true" + inkscape:guide-bbox="true" + inkscape:window-width="906" + inkscape:window-height="659" + inkscape:window-x="338" + inkscape:window-y="50" + showgrid="false"> + <sodipodi:guide + orientation="vertical" + position="-1.4285714" + id="guide6022" /> + </sodipodi:namedview> + <metadata + id="metadata5976"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <rect + y="246.06918" + x="64.325172" + height="204.26233" + width="333.2135" + id="rect6047" + style="fill:url(#linearGradient6974);fill-opacity:1;stroke:#686868;stroke-width:0.91925466;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + id="g6976" + transform="translate(70,0)"> + <rect + y="327.9104" + x="40.113693" + height="44.537449" + width="134.53746" + id="rect6004" + style="fill:#d4d4d4;fill-opacity:1;stroke:black;stroke-width:0.7482574;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:1.49651474, 0.74825737;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text6006" + y="353.67111" + x="62.654205" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="353.67111" + x="62.654205" + id="tspan6008" + sodipodi:role="line">7b064d8bac5e</tspan></text> + </g> + <path + inkscape:connector-type="polyline" + id="path6020" + d="M 160.92915,311.15532 L 167.83571,327.53627" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1;display:inline" + inkscape:connection-end="#g6976" + inkscape:connection-start="#g1935" /> + <rect + style="fill:#ededed;fill-opacity:1;stroke:#797979;stroke-width:0.74800003;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect6039" + width="134.53746" + height="44.537449" + x="110.11359" + y="389.57703" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#979797;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="134.79706" + y="415.33771" + id="text6041"><tspan + sodipodi:role="line" + id="tspan6043" + x="134.79706" + y="415.33771" + style="fill:#979797;fill-opacity:1;font-family:Courier">000000000000</tspan></text> + <path + inkscape:connection-end="#rect6039" + inkscape:connector-type="polyline" + id="path6045" + d="M 177.38238,372.82195 L 177.38235,389.20303" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#686868;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" /> + <rect + y="245.94225" + x="447.28412" + height="204.51619" + width="174.36833" + id="rect6140" + style="fill:url(#linearGradient6996);fill-opacity:1;stroke:#686868;stroke-width:0.66539276;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + id="g6130" + transform="translate(152.3254,24.38544)"> + <rect + style="fill:#d4d4d4;fill-opacity:1;stroke:black;stroke-width:0.7482574;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:1.49651474, 0.74825737;stroke-dashoffset:0;stroke-opacity:1" + id="rect6106" + width="134.53746" + height="44.537449" + x="314.87415" + y="257.95059" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="339.55664" + y="283.7113" + id="text6108"><tspan + sodipodi:role="line" + id="tspan6110" + x="339.55664" + y="283.7113" + style="font-family:Courier">ffb20e1701ea</tspan></text> + </g> + <g + id="g6135" + transform="translate(153.0396,49.83106)"> + <rect + inkscape:transform-center-y="102.85714" + inkscape:transform-center-x="129.28571" + style="fill:#d4d4d4;fill-opacity:1;stroke:#797979;stroke-width:0.74800003;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect6112" + width="134.53746" + height="44.537449" + x="314.15985" + y="326.52203" /> + <text + inkscape:transform-center-y="102.7311" + inkscape:transform-center-x="128.69672" + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#979797;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="338.84335" + y="352.28271" + id="text6114"><tspan + sodipodi:role="line" + id="tspan6116" + x="338.84335" + y="352.28271" + style="fill:black;fill-opacity:1;font-family:Courier">e7639888bb2f</tspan></text> + </g> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="466.63208" + y="270.479" + id="text6118"><tspan + sodipodi:role="line" + id="tspan6120" + x="466.63208" + y="270.479">Primer padre (sin cambio)</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="466.07544" + y="364.49615" + id="text6122"><tspan + sodipodi:role="line" + id="tspan6124" + x="466.07544" + y="364.49615">Segundo padre</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="446.61743" + y="231.36218" + id="text6195"><tspan + sodipodi:role="line" + id="tspan6197" + x="446.61743" + y="231.36218">Padres del directorio de trabajo</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline" + d="M 466.82542,300.21999 L 377.00207,294.39744" + id="path6266" + inkscape:connector-type="polyline" + inkscape:connection-start="#g6130" + inkscape:connection-end="#rect1925" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 665.12232,418.17579 L 665.12232,418.17579" + id="path6270" + inkscape:connector-type="polyline" /> + <g + id="g2845"> + <rect + y="266.24374" + x="242.09048" + height="44.537449" + width="134.53746" + id="rect1925" + style="fill:#9f9f9f;fill-opacity:1;stroke:black;stroke-width:0.7482574;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:1.49651474, 0.74825737;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text1927" + y="292.00446" + x="266.77298" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="292.00446" + x="266.77298" + id="tspan1929" + sodipodi:role="line">ffb20e1701ea</tspan></text> + </g> + <path + inkscape:connector-type="polyline" + id="path1933" + d="M 260.89978,311.15532 L 225.84185,327.53627" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1;display:inline" + inkscape:connection-end="#g6976" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="109.45568" + y="231.4554" + id="text2837"><tspan + sodipodi:role="line" + id="tspan2839" + x="109.45568" + y="231.4554">Cabeza pre-existente</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="237.54184" + y="231.4554" + id="text2841"><tspan + sodipodi:role="line" + id="tspan2843" + x="237.54184" + y="231.4554">Cabeza recién creada(y tip)</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow1Mend)" + d="M 148.05048,235.87482 L 149.94915,265.86962" + id="path2850" + inkscape:connector-type="polyline" + inkscape:connection-end="#g1935" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow1Mend)" + d="M 303.83495,238.08453 L 306.87874,265.86962" + id="path2852" + inkscape:connector-type="polyline" + inkscape:connection-end="#g2845" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline" + d="M 466.82545,379.17944 L 219.0253,307.95488" + id="path3016" + inkscape:connector-type="polyline" + inkscape:connection-start="#g6135" + inkscape:connection-end="#g1935" /> + <g + id="g1935"> + <rect + y="266.24374" + x="84.113708" + height="44.537449" + width="134.53746" + id="rect5996" + style="fill:#d4d4d4;fill-opacity:1;stroke:black;stroke-width:0.7482574;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:1.49651474, 0.74825737;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text5998" + y="292.00446" + x="108.7962" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="292.00446" + x="108.7962" + id="tspan6000" + sodipodi:role="line">e7639888bb2f</tspan></text> + </g> + </g> +</svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/wdir-pre-branch.svg Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,373 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg5971" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docbase="/home/bos/hg/hgbook/en" + sodipodi:docname="wdir-pre-branch.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs5973"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective3314" /> + <marker + inkscape:stockid="Arrow1Mstart" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mstart" + style="overflow:visible"> + <path + id="path4855" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none" + transform="scale(0.4) translate(10,0)" /> + </marker> + <linearGradient + id="linearGradient6049"> + <stop + style="stop-color:#686868;stop-opacity:1;" + offset="0" + id="stop6051" /> + <stop + style="stop-color:#f0f0f0;stop-opacity:1;" + offset="1" + id="stop6053" /> + </linearGradient> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mend" + style="overflow:visible;"> + <path + id="path4852" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;" + transform="scale(0.4) rotate(180) translate(10,0)" /> + </marker> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6083" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-240.0462,-8.633237e-6)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6142" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-42.00893,-30.49544)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6193" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-240.0462,-8.633237e-6)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6216" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.000474,0,0,0.790947,-240.246,50.9948)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6232" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.000473,0,0,0.790947,-11.16012,50.85693)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6445" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.000474,0,0,0.790947,-240.246,50.9948)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6974" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.000474,0,0,0.790947,-314.246,50.85694)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6996" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.000473,0,0,0.790947,-85.16012,50.85693)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.64" + inkscape:cx="235.37429" + inkscape:cy="726.21069" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showguides="true" + inkscape:guide-bbox="true" + inkscape:window-width="906" + inkscape:window-height="659" + inkscape:window-x="2" + inkscape:window-y="43" + showgrid="false"> + <sodipodi:guide + orientation="vertical" + position="-1.4285714" + id="guide6022" /> + </sodipodi:namedview> + <metadata + id="metadata5976"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <rect + y="245.94225" + x="20.198257" + height="204.51619" + width="174.36833" + id="rect6047" + style="fill:url(#linearGradient6974);fill-opacity:1;stroke:#686868;stroke-width:0.66539276;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <rect + style="fill:#d4d4d4;fill-opacity:1;stroke:black;stroke-width:0.7482574;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:1.49651474, 0.74825737;stroke-dashoffset:0;stroke-opacity:1" + id="rect5996" + width="134.53746" + height="44.537449" + x="40.113693" + y="266.24374" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="64.796204" + y="292.00446" + id="text5998"><tspan + sodipodi:role="line" + id="tspan6000" + x="64.796204" + y="292.00446" + style="font-family:Courier">e7639888bb2f</tspan></text> + <g + id="g6976"> + <rect + y="327.9104" + x="40.113693" + height="44.537449" + width="134.53746" + id="rect6004" + style="fill:#d4d4d4;fill-opacity:1;stroke:black;stroke-width:0.7482574;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:1.49651474, 0.74825737;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text6006" + y="353.67111" + x="62.654205" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="353.67111" + x="62.654205" + id="tspan6008" + sodipodi:role="line">7b064d8bac5e</tspan></text> + </g> + <path + inkscape:connection-end="#rect6004" + inkscape:connector-type="polyline" + id="path6020" + d="M 107.38242,311.15529 L 107.38242,327.53626" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" /> + <rect + style="fill:#ededed;fill-opacity:1;stroke:#797979;stroke-width:0.74800003;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect6039" + width="134.53746" + height="44.537449" + x="40.113571" + y="389.57703" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#979797;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="64.797073" + y="415.33771" + id="text6041"><tspan + sodipodi:role="line" + id="tspan6043" + x="64.797073" + y="415.33771" + style="fill:#979797;fill-opacity:1;font-family:Courier">000000000000</tspan></text> + <path + inkscape:connection-end="#rect6039" + inkscape:connector-type="polyline" + id="path6045" + d="M 107.38238,372.82195 L 107.38235,389.20301" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#686868;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="19.660461" + y="231.36218" + id="text6102"><tspan + sodipodi:role="line" + id="tspan6104" + x="19.660461" + y="231.36218">Historia en el repositorio</tspan></text> + <rect + y="245.94225" + x="249.28412" + height="204.51619" + width="174.36833" + id="rect6140" + style="fill:url(#linearGradient6996);fill-opacity:1;stroke:#686868;stroke-width:0.66539276;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + id="g6130" + transform="translate(-45.67459,24.38544)"> + <rect + style="fill:#d4d4d4;fill-opacity:1;stroke:black;stroke-width:0.7482574;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:1.49651474, 0.74825737;stroke-dashoffset:0;stroke-opacity:1" + id="rect6106" + width="134.53746" + height="44.537449" + x="314.87415" + y="257.95059" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="339.55664" + y="283.7113" + id="text6108"><tspan + sodipodi:role="line" + id="tspan6110" + x="339.55664" + y="283.7113" + style="font-family:Courier">7b064d8bac5e</tspan></text> + </g> + <g + id="g6135" + transform="translate(-44.96042,49.83106)"> + <rect + inkscape:transform-center-y="102.85714" + inkscape:transform-center-x="129.28571" + style="fill:#ededed;fill-opacity:1;stroke:#797979;stroke-width:0.74800003;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect6112" + width="134.53746" + height="44.537449" + x="314.15985" + y="326.52203" /> + <text + inkscape:transform-center-y="102.7311" + inkscape:transform-center-x="128.69672" + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#979797;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="338.84335" + y="352.28271" + id="text6114"><tspan + sodipodi:role="line" + id="tspan6116" + x="338.84335" + y="352.28271" + style="fill:#979797;fill-opacity:1;font-family:Courier">000000000000</tspan></text> + </g> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="268.63208" + y="270.479" + id="text6118"><tspan + sodipodi:role="line" + id="tspan6120" + x="268.63208" + y="270.479">Primer padre</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="268.07544" + y="364.49615" + id="text6122"><tspan + sodipodi:role="line" + id="tspan6124" + x="268.07544" + y="364.49615">Segundo padre</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="248.61746" + y="231.36218" + id="text6195"><tspan + sodipodi:role="line" + id="tspan6197" + x="248.61746" + y="231.36218">Padres del directorio de trabajo</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline" + d="M 268.82543,318.06163 L 175.02528,336.72225" + id="path6266" + inkscape:connector-type="polyline" + inkscape:connection-end="#g6976" + inkscape:connection-start="#g6130" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 665.12232,418.17579 L 665.12232,418.17579" + id="path6270" + inkscape:connector-type="polyline" /> + </g> +</svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/es/wdir.svg Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,357 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg5971" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docbase="/home/bos/hg/hgbook/en" + sodipodi:docname="wdir.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs5973"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective3368" /> + <marker + inkscape:stockid="Arrow1Mstart" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mstart" + style="overflow:visible"> + <path + id="path4855" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none" + transform="scale(0.4) translate(10,0)" /> + </marker> + <linearGradient + id="linearGradient6049"> + <stop + style="stop-color:#686868;stop-opacity:1;" + offset="0" + id="stop6051" /> + <stop + style="stop-color:#f0f0f0;stop-opacity:1;" + offset="1" + id="stop6053" /> + </linearGradient> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mend" + style="overflow:visible;"> + <path + id="path4852" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;" + transform="scale(0.4) rotate(180) translate(10,0)" /> + </marker> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6083" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-240.0462,-8.633237e-6)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6142" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-42.00893,-30.49544)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6193" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-240.0462,-8.633237e-6)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6216" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.000474,0,0,0.790947,-240.246,50.9948)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6232" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.000473,0,0,0.790947,-11.16012,50.85693)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient6049" + id="linearGradient6445" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.000474,0,0,0.790947,-240.246,50.9948)" + x1="333.91171" + y1="488.79077" + x2="508.94543" + y2="263.79077" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.8101934" + inkscape:cx="301.66555" + inkscape:cy="721.33993" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showguides="true" + inkscape:guide-bbox="true" + inkscape:window-width="906" + inkscape:window-height="659" + inkscape:window-x="355" + inkscape:window-y="55" + showgrid="false"> + <sodipodi:guide + orientation="vertical" + position="-1.4285714" + id="guide6022" /> + </sodipodi:namedview> + <metadata + id="metadata5976"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + id="g6431" + transform="translate(0,-0.137863)"> + <rect + style="fill:url(#linearGradient6445);fill-opacity:1;stroke:#686868;stroke-width:0.66539276;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect6047" + width="174.36833" + height="204.51619" + x="94.198257" + y="246.08011" /> + <rect + y="266.38159" + x="114.11369" + height="44.537449" + width="134.53746" + id="rect5996" + style="fill:#d4d4d4;fill-opacity:1;stroke:black;stroke-width:0.7482574;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:1.49651474, 0.74825737;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text5998" + y="292.1423" + x="138.7962" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="292.1423" + x="138.7962" + id="tspan6000" + sodipodi:role="line">e7639888bb2f</tspan></text> + <rect + y="328.04825" + x="114.11369" + height="44.537449" + width="134.53746" + id="rect6004" + style="fill:#d4d4d4;fill-opacity:1;stroke:black;stroke-width:0.7482574;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:1.49651474, 0.74825737;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text6006" + y="353.80896" + x="136.65421" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="353.80896" + x="136.65421" + id="tspan6008" + sodipodi:role="line">7b064d8bac5e</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 181.38242,311.29315 L 181.38242,327.67412" + id="path6020" + inkscape:connector-type="polyline" + inkscape:connection-end="#rect6004" /> + <rect + y="389.71487" + x="114.11357" + height="44.537449" + width="134.53746" + id="rect6039" + style="fill:#ededed;fill-opacity:1;stroke:#797979;stroke-width:0.74800003;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text6041" + y="415.47556" + x="138.79707" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#979797;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="fill:#979797;fill-opacity:1;font-family:Courier" + y="415.47556" + x="138.79707" + id="tspan6043" + sodipodi:role="line">000000000000</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#686868;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 181.38238,372.95981 L 181.38235,389.34087" + id="path6045" + inkscape:connector-type="polyline" + inkscape:connection-end="#rect6039" /> + </g> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="93.660484" + y="231.36218" + id="text6102"><tspan + sodipodi:role="line" + id="tspan6104" + x="93.660484" + y="231.36218">Historia en el repositorio</tspan></text> + <g + id="g6416"> + <rect + style="fill:url(#linearGradient6232);fill-opacity:1;stroke:#686868;stroke-width:0.66539276;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect6140" + width="174.36833" + height="204.51619" + x="323.28412" + y="245.94225" /> + <g + transform="translate(28.32541,24.38544)" + id="g6130"> + <rect + y="257.95059" + x="314.87415" + height="44.537449" + width="134.53746" + id="rect6106" + style="fill:#d4d4d4;fill-opacity:1;stroke:black;stroke-width:0.7482574;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:1.49651474, 0.74825737;stroke-dashoffset:0;stroke-opacity:1" /> + <text + id="text6108" + y="283.7113" + x="339.55664" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + style="font-family:Courier" + y="283.7113" + x="339.55664" + id="tspan6110" + sodipodi:role="line">e7639888bb2f</tspan></text> + </g> + <g + transform="translate(29.03958,49.83106)" + id="g6135"> + <rect + y="326.52203" + x="314.15985" + height="44.537449" + width="134.53746" + id="rect6112" + style="fill:#ededed;fill-opacity:1;stroke:#797979;stroke-width:0.74800003;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + inkscape:transform-center-x="129.28571" + inkscape:transform-center-y="102.85714" /> + <text + id="text6114" + y="352.28271" + x="338.84335" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#979797;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve" + inkscape:transform-center-x="128.69672" + inkscape:transform-center-y="102.7311"><tspan + style="fill:#979797;fill-opacity:1;font-family:Courier" + y="352.28271" + x="338.84335" + id="tspan6116" + sodipodi:role="line">000000000000</tspan></text> + </g> + <text + id="text6118" + y="270.479" + x="342.63208" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + y="270.479" + x="342.63208" + id="tspan6120" + sodipodi:role="line">Primer padre</tspan></text> + <text + id="text6122" + y="364.49615" + x="342.07544" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + xml:space="preserve"><tspan + y="364.49615" + x="342.07544" + id="tspan6124" + sodipodi:role="line">Segundo padre</tspan></text> + </g> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="322.61746" + y="231.36218" + id="text6195"><tspan + sodipodi:role="line" + id="tspan6197" + x="322.61746" + y="231.36218">Padres del directorio de trabajo</tspan></text> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline" + d="M 342.82543,299.89384 L 249.02528,293.36123" + id="path6266" + inkscape:connector-type="polyline" + inkscape:connection-start="#g6130" + inkscape:connection-end="#rect5996" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 665.12232,418.17579 L 665.12232,418.17579" + id="path6270" + inkscape:connector-type="polyline" /> + </g> +</svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/html/index.es.html Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,53 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" +"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html lang="es"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <link rel="icon" href="/hgicon.png" type="image/png"> + <meta name="robots" content="index,follow"> + <title>Control Distribuido de Revisiones con Mercurial</title> + </head> + + <body> + <h1>Control Distribuido de Revisiones con Mercurial</h1> + + <p>Bienvenido al sito del libro “Control Distribuido de Revisiones con Mercurial”, en español, + por <a href="http://www.serpentine.com/blog/">Bryan O'Sullivan</a>. + Este libro está cobijado por una <a href="hgbookap4.html">licencia abierta</a> + y trata del sistema de control de revisiones + <a href="http://www.selenic.com/mercurial">Mercurial</a>. + + <p>Los traductores son <a href="http://devnull.li/~jerojasro/blog/">Javier Rojas</a> e + <a href="http://igor.tamarapatino.org/">Igor Támara</a>. En este sitio usted puede encontrar: + <ul> + <li>La <a href="onepage.html">versión HTML</a> una sola página.</li> + <li>La <a href="hgbook.pdf">versión PDF</a> (1.9 megabytes.)</li> + <li>El <a href="http://mercurial.intuxication.org/hg/mercurial_book_es/">código + fuente</a> de la traducción, si desea revisarla o colaborar con el proyecto. En + <a href="http://hg.serpentine.com/mercurial/book">este sitio</a> puede + encontrar la versión original en inglés.</li> + </ul> + Para más detalles acerca del proceso de traducción, por favor vea <a + href="http://mercurial.intuxication.org/hg/mercurial_book_es/file/tip/es/Leame.1st">este + fichero</a>. + + <h2>¿Cómo puede usted ayudar a Mercurial, y el software libre?</h2> + + <p>Mercurial es miembro del <a + href="http://conservancy.softwarefreedom.org/">Conservatorio + de Software Libre</a>, una maravillosa organización sin ánimo + de lucro que ofrece a sus proyectos miembros consejo legal y + administrativo. La SFC acepta <a href="http://conservancy.softwarefreedom.org/?donate">donaciones</a> + (deducibles de impuestos bajo IRS 501(c)(3), dentro de los Estados Unidos) + en representación de sus proyectos miembros. Si desea dar un apoyo + directo a Mercurial, por favor considere hacer una donación a SFC + en su representación.</p> + + <p>Si desea apoyar a los desarrolladores de software libre en su + importante servicio público sin estar impedido por cuestiones + legales, por favor considere donar a la organización hermana de + SFC, el <a + href="http://www.softwarefreedom.org/">Centro de Leyes de Software + Libre</a>.</p> + </body> +</html>
--- a/html/index.html.var Wed Jan 21 14:16:38 2009 +0100 +++ b/html/index.html.var Mon Feb 09 22:59:50 2009 -0800 @@ -1,3 +1,7 @@ URI: index.en.html Content-Language: en Content-Type: text/html; charset=UTF-8 + +URI: index.es.html +Content-Language: es +Content-Type: text/html; charset=UTF-8
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/latex-to-docbook Mon Feb 09 22:59:50 2009 -0800 @@ -0,0 +1,192 @@ +#!/usr/bin/python +# +# This is the most horrible of hacks. Pretend you're not looking.</para> + +import cStringIO as StringIO +import re, sys + +sections = { + 'chapter': 'chapter', + 'section': 'sect1', + 'subsection': 'sect2', + 'subsubsection': 'sect3', + } + +envs = { + 'codesample2': 'programlisting', + 'codesample4': 'programlisting', + 'enumerate': 'orderedlist', + 'figure': 'figure', + 'itemize': 'itemizedlist', + 'note': 'note', + 'quote': 'blockquote', + } + +def process(ifp, ofp): + stack = [] + para = True + inlist = False + for line in ifp: + if line.startswith('%%% Local Variables:'): + break + line = (line.rstrip() + .replace(' ', ' ') + .replace('&', '&') + .replace('&emdash;', '&emdash;') + .replace('\_', '_') + .replace('\{', '{') + .replace('\}', '}') + .replace('\$', '$') + .replace('\%', '%') + .replace('\#', '#') + .replace('<', '<') + .replace('>', '>') + .replace('<quote>', '<quote>') + .replace("</quote>", '</quote>') + .replace('\\', '\\')) + line = re.sub(r'\s*\\(?:centering|small)\b\s*', '', line) + line = re.sub(r'\\(?:hgrc\\|hgrc)\b', + r'<filename role="special"> /.hgrc</filename>', line) + line = re.sub(r'\\item\[(?P<key>[^]]+)\]', r'\item \g<key>:', line) + line = re.sub(r'\\bug{(?P<id>\d+)}', + r'<ulink role="hg-bug" url="http://www.selenic.com/mercurial/bts/issue\g<id>">issue \g<id></ulink>', line) + line = re.sub(r'\\cite{([^}]+)}', r'<citation>\1</citation>', line) + line = re.sub(r'\\hggopt{(?P<opt>[^}]+)}', + r'<option role="hg-opt-global">\g<opt></option>', line) + line = re.sub(r'\\hgxopt{(?P<ext>[^}]+)}{(?P<cmd>[^}]+)}{(?P<opt>[^}]+)}', + r'<option role="hg-ext-\g<ext>-cmd-\g<cmd>-opt">\g<opt></option>', line) + line = re.sub(r'\\hgxcmd{(?P<ext>[^}]+)}{(?P<cmd>[^}]+)}', + r'<command role="hg-ext-\g<ext>">\g<cmd></command>', line) + line = re.sub(r'\\hgext{(?P<ext>[^}]+)}', + r'<literal role="hg-ext">\g<ext></literal>', line) + line = re.sub(r'\\hgopt{(?P<cmd>[^}]+)}{(?P<opt>[^}]+)}', + r'<option role="hg-opt-\g<cmd>">\g<opt></option>', + line) + line = re.sub(r'\\cmdopt{(?P<cmd>[^}]+)}{(?P<opt>[^}]+)}', + r'<option role="cmd-opt-\g<cmd>">\g<opt></option>', + line) + line = re.sub(r'\\hgcmd{(?P<cmd>[^}]+)}', + r'<command role="hg-cmd">hg \g<cmd></command>', line) + line = re.sub(r'\\caption{(?P<text>[^}]+?)}', + r'<caption>\g<text></caption>', line) + line = re.sub(r'\\grafix{(?P<name>[^}]+)}', + r'<mediaobject><imageobject><imagedata fileref="\g<name>"/></imageobject><textobject><phrase>XXX add text</phrase></textobject></mediaobject>', line) + line = re.sub(r'\\envar{(?P<name>[^}]+)}', + r'<envar>\g<name></envar>', line) + line = re.sub(r'\\rcsection{(?P<sect>[^}]+)}', + r'<literal role="rc-\g<sect>">\g<sect></literal>', line) + line = re.sub(r'\\rcitem{(?P<sect>[^}]+)}{(?P<name>[^}]+)}', + r'<envar role="rc-item-\g<sect>">\g<name></envar>', line) + line = re.sub(r'\\dirname{(?P<dir>[^}]+?)}', + r'<filename class="directory">\g<dir></filename>', line) + line = re.sub(r'\\filename{(?P<file>[^}]+?)}', + r'<filename>\g<file></filename>', line) + line = re.sub(r'\\tildefile{(?P<file>[^}]+)}', + r'<filename role="home"> /\g<file></filename>', line) + line = re.sub(r'\\sfilename{(?P<file>[^}]+)}', + r'<filename role="special">\g<file></filename>', line) + line = re.sub(r'\\sdirname{(?P<dir>[^}]+)}', + r'<filename role="special" class="directory">\g<dir></filename>', line) + line = re.sub(r'\\interaction{(?P<id>[^}]+)}', + r'<!-- &interaction.\g<id>; -->', line) + line = re.sub(r'\\excode{(?P<id>[^}]+)}', + r'<!-- &example.\g<id>; -->', line) + line = re.sub(r'\\pymod{(?P<mod>[^}]+)}', + r'<literal role="py-mod">\g<mod></literal>', line) + line = re.sub(r'\\pymodclass{(?P<mod>[^}]+)}{(?P<class>[^}]+)}', + r'<literal url="py-mod-\g<mod>">\g<class></ulink>', line) + line = re.sub(r'\\url{(?P<url>[^}]+)}', + r'<ulink url="\g<url>">\g<url></ulink>', line) + line = re.sub(r'\\href{(?P<url>[^}]+)}{(?P<text>[^}]+)}', + r'<ulink url="\g<url>">\g<text></ulink>', line) + line = re.sub(r'\\command{(?P<cmd>[^}]+)}', + r'<command>\g<cmd></command>', line) + line = re.sub(r'\\option{(?P<opt>[^}]+)}', + r'<option>\g<opt></option>', line) + line = re.sub(r'\\ref{(?P<id>[^}]+)}', r'<xref id="\g<id>"/>', line) + line = re.sub(r'\\emph{(?P<txt>[^}]+)}', + r'<emphasis>\g<txt></emphasis>', line) + line = re.sub(r'\\texttt{(?P<txt>[^}]+)}', + r'<literal>\g<txt></literal>', line) + line = re.sub(r'\\textbf{(?P<txt>[^}]+)}', + r'<emphasis role="bold">\g<txt></emphasis>', line) + line = re.sub(r'\\hook{(?P<name>[^}]+)}', + r'<literal role="hook">\g<name></literal>', line) + line = re.sub(r'\\tplfilter{(?P<name>[^}]+)}', + r'<literal role="template-filter">\g<name></literal>', line) + line = re.sub(r'\\tplkword{(?P<name>[^}]+)}', + r'<literal role="template-keyword">\g<name></literal>', line) + line = re.sub(r'\\tplkwfilt{(?P<tpl>[^}]+)}{(?P<name>[^}]+)}', + r'<literal role="template-kw-filt-\g<tpl>">\g<name></literal>', line) + line = re.sub(r'\\[vV]erb(.)(?P<txt>[^\1]+?)\1', + r'<literal>\g<txt></literal>', line) + line = re.sub(r'\\package{(?P<name>[^}]+)}', + r'<literal role="package">\g<name></literal>', line) + line = re.sub(r'\\hgcmdargs{(?P<cmd>[^}]+)}{(?P<args>[^}]+)}', + r'<command role="hg-cmd">hg \g<cmd> \g<args></command>', + line) + line = re.sub(r'\\cmdargs{(?P<cmd>[^}]+)}{(?P<args>[^}]+)}', + r'<command>\g<cmd> \g<args></command>', + line) + m = re.match(r'\\(chapter|section|subsection|subsubsection){(.*)}', line) + if m: + kind, content = m.groups() + sec = sections[kind] + while stack and stack[-1] >= sec: + close = stack.pop() + print >> ofp, '</%s>' % close + stack.append(sec) + print >> ofp, '<%s>\n<title>%s</title>' % (sec, content) + else: + m = re.match(r'\s*\\(begin|end){(?P<sect>[^}]+)}', line) + if m: + if not para: + print >> ofp, '</para>' + if inlist: + ofp.write('</listitem>') + para = True + state, env = m.groups() + env = envs[env] + if state == 'begin': + ofp.write('<') + if env == 'itemizedlist': + inlist = True + else: + ofp.write('</') + if env == 'itemizedlist': + inlist = False + print >> ofp, env + '>' + else: + if line.startswith('\\item '): + para = True + line = line[6:] + if line and para: + if inlist: + ofp.write('<listitem>') + ofp.write('<para>') + para = False + if not line and not para: + print >> ofp, '</para>' + if inlist: + ofp.write('</listitem>') + para = True + print >> ofp, line + while stack: + print >> ofp, '</%s>' % stack.pop() + ofp.write('\n'.join(['<!--', + 'local variables: ', + 'sgml-parent-document: ("00book.xml" "book" "chapter")', + 'end:', + '-->'])) + + +if __name__ == '__main__': + for name in sys.argv[1:]: + if not name.endswith('.tex'): + continue + newname = name[:-3] + 'xml' + ofp = StringIO.StringIO() + process(open(name), ofp) + s = ofp.getvalue() + s = re.sub('\n+</para>', '</para>', s, re.M) + open(newname, 'w').write(s)