changeset 19:187702df428b

Piles of new content for MQ chapter - cookbook stuff.
author Bryan O'Sullivan <bos@serpentine.com>
date Fri, 07 Jul 2006 19:56:53 -0700
parents e6f4088ebe52
children 2888fe6176b3
files en/99defs.tex en/Makefile en/examples/data/netplug-1.2.5.tar.bz2 en/examples/data/netplug-1.2.8.tar.bz2 en/examples/data/remove-redundant-null-checks.patch en/examples/mq.diff en/examples/mq.tarball en/examples/mq.tools en/examples/run-example en/mq.tex
diffstat 10 files changed, 454 insertions(+), 40 deletions(-) [+]
line wrap: on
line diff
--- a/en/99defs.tex	Tue Jul 04 16:41:31 2006 -0700
+++ b/en/99defs.tex	Fri Jul 07 19:56:53 2006 -0700
@@ -6,8 +6,10 @@
 \newcommand{\hgext}[1]{\index{\texttt{#1} extension}\texttt{#1}}
 \newcommand{\hgcmd}[1]{\index{\texttt{#1} command}``\texttt{hg #1}''}
 \newcommand{\command}[1]{\index{\texttt{#1} command}\texttt{#1}}
+\newcommand{\cmdargs}[2]{\index{\texttt{#1} command}\texttt{#1 #2}}
 \newcommand{\hgcmdargs}[2]{\index{\texttt{#1} command}``\texttt{hg #1 #2}''}
 \newcommand{\hgopt}[2]{\index{\texttt{#1} command!\texttt{#2} option}\texttt{#2}}
+\newcommand{\cmdopt}[2]{\index{\texttt{#1} command!\texttt{#2} option}\texttt{#2}}
 \newcommand{\package}[1]{\index{\texttt{#1} package}\texttt{#1}}
 
 \newsavebox{\notebox}
--- a/en/Makefile	Tue Jul 04 16:41:31 2006 -0700
+++ b/en/Makefile	Fri Jul 07 19:56:53 2006 -0700
@@ -16,6 +16,9 @@
 example-sources := \
 	examples/run-example \
 	examples/mq.qinit-help \
+	examples/mq.diff \
+	examples/mq.tarball \
+	examples/mq.tools \
 	examples/mq.tutorial
 
 latex-options = \
Binary file en/examples/data/netplug-1.2.5.tar.bz2 has changed
Binary file en/examples/data/netplug-1.2.8.tar.bz2 has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/en/examples/data/remove-redundant-null-checks.patch	Fri Jul 07 19:56:53 2006 -0700
@@ -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/en/examples/mq.diff	Fri Jul 07 19:56:53 2006 -0700
@@ -0,0 +1,12 @@
+#$ name: diff
+
+echo 'this is my first line' > oldfile
+echo 'my first line is here' > newfile
+
+diff -u oldfile newfile > tiny.patch
+
+cat tiny.patch
+
+patch < tiny.patch
+
+cat newfile
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/en/examples/mq.tarball	Fri Jul 07 19:56:53 2006 -0700
@@ -0,0 +1,48 @@
+cp $EXAMPLE_DIR/data/netplug-*.tar.bz2 .
+ln -s /bin/true download
+
+#$ 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/en/examples/mq.tools	Fri Jul 07 19:56:53 2006 -0700
@@ -0,0 +1,9 @@
+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
--- a/en/examples/run-example	Tue Jul 04 16:41:31 2006 -0700
+++ b/en/examples/run-example	Fri Jul 07 19:56:53 2006 -0700
@@ -76,6 +76,7 @@
         rcfp = open(rcfile, 'w')
         print >> rcfp, 'PS1="%s"' % self.prompt
         print >> rcfp, 'unset HISTFILE'
+        print >> rcfp, 'export EXAMPLE_DIR="%s"' % os.getcwd()
         print >> rcfp, 'export LANG=C'
         print >> rcfp, 'export LC_ALL=C'
         print >> rcfp, 'export TZ=GMT'
@@ -117,7 +118,7 @@
                         if nl: hunk += '\n'
                     ofp.write(hunk)
                     # then its output
-                    ofp.write(output)
+                    ofp.write(tex_escape(output))
             self.status('\n')
         finally:
             try:
@@ -140,7 +141,9 @@
     for name in os.listdir(path):
         if name == 'run-example' or name.startswith('.'): continue
         if name.endswith('.out') or name.endswith('~'): continue
-        example(os.path.join(path, name)).run()
+        pathname = os.path.join(path, name)
+        if os.path.isfile(pathname):
+            example(pathname).run()
     print >> open(os.path.join(path, '.run'), 'w'), time.asctime()
 
 if __name__ == '__main__':
--- a/en/mq.tex	Tue Jul 04 16:41:31 2006 -0700
+++ b/en/mq.tex	Fri Jul 07 19:56:53 2006 -0700
@@ -126,6 +126,62 @@
 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{Understanding patches}
+
+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.diff.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 ome of the more subtle aspects of patches later (in
+section~\ref{ex:mq:adv-patch}), but you should have enough information
+now to use MQ.
+
 \section{Getting started with Mercurial Queues}
 \label{sec:mq:start}
 
@@ -200,6 +256,7 @@
 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
@@ -319,45 +376,12 @@
 \hgcmd{qrefresh} the core patch, and \hgcmd{qpush} back to the UI
 patch to continue where you left off.
 
-\section{Mercurial Queues and GNU patch}
-\label{sec:mq:patch}
-
-MQ uses the GNU \command{patch} command to apply patches.  Because MQ
-doesn't hide its patch-oriented nature, it is helpful to understand
-the data that MQ and \command{patch} work with, and a few aspects of
-how \command{patch} operates.
-
-The \command{diff} command generates a list of modifications by
-comparing two files.  The \command{patch} command applies a list of
-modifications to a file.  The kinds of files that \command{diff} and
-\command{patch} work with are referred to as both ``diffs'' and
-``patches;'' there is no difference between a diff and a patch.
-
-A patch file can start with arbitrary text; MQ uses this text as the
-commit message when creating changesets.  It treats the first line
-that starts with the string ``\texttt{diff~-}'' as the separator
-between header and content.
+\section{More about patches}
+\label{sec:mq:adv-patch}
 
-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.
-Each unmodified line begins with a space characters.  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.
-
-The \command{diff} command runs hunks together when there's not enough
-context between modifications to justify
+MQ uses the GNU \command{patch} command to apply patches, so it's
+helpful to know about a few more detailed aspects of how
+\command{patch} works.
 
 When \command{patch} applies a hunk, it tries a handful of
 successively less accurate strategies to try to make the hunk apply.
@@ -622,6 +646,7 @@
 confuse MQ's idea of which patches are applied.
 
 \section{Commands 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
@@ -636,6 +661,12 @@
 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
@@ -645,6 +676,122 @@
 invocation of \command{filterdiff} can generate a smaller patch that
 only touches files whose names match a particular glob pattern.
 
+\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 \hgcmd{qapplied} and \hgcmd{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 \hgcmd{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 \hgcmd{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}
+
+It's easy to combine entire patches.
+
+\begin{enumerate}
+\item \hgcmd{qpop} your applied patches until neither patch is
+  applied.
+\item Concatenate the patches that you want to combine together:
+  \begin{codesample4}
+    cat patch-to-drop.patch >> patch-to-augment.patch
+  \end{codesample4}
+  The description from the first patch (if you have one) will be used
+  as the commit comment when you \hgcmd{qpush} the combined patch.
+  Edit the patch description if you need to.
+\item Use the \hgcmd{qdel} command to delete the patch you're dropping
+  from the \sfilename{series} file.
+\item \hgcmd{qpush} the combined patch.  Fix up any rejects.
+\item \hgcmd{qrefresh} the combined patch to tidy it up.
+\end{enumerate}
+
+\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 \hgcmd{qpush} it (from the hunks you moved into the
+other patch), and you can simply \hgcmd{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 a \emph{file number} to identify each file modified in the patch;
+\item the line number within a modified file that a hunk starts at; and
+\item 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}.
+
 %%% Local Variables: 
 %%% mode: latex
 %%% TeX-master: "00book"