[svnbook] r4356 committed - * en/book/ch04-branching-and-merging.xml...

svnbook at googlecode.com svnbook at googlecode.com
Tue Jan 22 17:35:12 CST 2013


Revision: 4356
Author:   cmpilato at gmail.com
Date:     Tue Jan 22 15:34:56 2013
Log:      * en/book/ch04-branching-and-merging.xml
   Rework the Vendor Branches section, dropping all discussion of
   svn_load_dirs.pl.  I'm totally unhappy with the approach here, but I
   don't want to lose the writing I've done so far.  So ... this is
   temporary.  See https://code.google.com/p/svnbook/issues/detail?id=182
   for the planned next steps.

http://code.google.com/p/svnbook/source/detail?r=4356

Modified:
  /trunk/en/book/ch04-branching-and-merging.xml

=======================================
--- /trunk/en/book/ch04-branching-and-merging.xml	Fri Jan 18 12:59:05 2013
+++ /trunk/en/book/ch04-branching-and-merging.xml	Tue Jan 22 15:34:56 2013
@@ -3675,7 +3675,7 @@
    <!-- =================================================================  
-->
    <!-- =================================================================  
-->
    <sect1 id="svn.advanced.vendorbr">
-    <title>Vendor Branches</title>
+    <title>Tracking Third-Party Data</title>

      <para>As is especially the case when developing software, the data
        that you maintain under version control is often closely related
@@ -3703,14 +3703,14 @@
        you could attempt to synchronize that information with your own
        in several ways.  Most painfully, you could issue oral or
        written instructions to all the contributors of your project,
-      telling them to make sure they have the specific versions
-      of that third-party information that your project needs.  If the
+      telling them to make sure they have the specific versions of
+      that third-party information that your project needs.  If the
        third-party information is maintained in a Subversion
        repository, you could also use Subversion's externals
        definitions to effectively <quote>pin down</quote> specific
        versions of that information to some location in your own
-      working copy (see
-      <xref linkend="svn.advanced.externals" />).</para>
+      working copy (see <xref linkend="svn.advanced.externals"
+      />).</para>

      <para>But sometimes you want to maintain custom modifications to
        third-party code in your own version control system.  Returning
@@ -3739,309 +3739,276 @@
        the vendor's data that you decide to absorb into your project is
        called a <firstterm>vendor drop</firstterm>.</para>

-    <para>Vendor branches provide two benefits.  First, by storing
-      the currently supported vendor drop in your own version control
-      system, you ensure that the members of your project never need to  
question
-      whether they have the right version of the vendor's data.  They
-      simply receive that correct version as part of their regular
-      working copy updates.  Second, because the data lives in your
-      own Subversion repository, you can store your custom changes to
-      it in-place—you have no more need of an automated (or
-      worse, manual) method for swapping in your customizations.</para>
+    <para>Vendor branches provide two benefits.  First, by storing the
+      currently supported vendor drop in your own version control
+      system, you ensure that the members of your project never need
+      to question whether they have the right version of the vendor's
+      data.  They simply receive that correct version as part of their
+      regular working copy updates.  Second, because the data lives in
+      your own Subversion repository, you can store your custom
+      changes to it in-place—you have no more need of an
+      automated (or worse, manual) method for swapping in your
+      customizations.</para>
+
+    <para>Unfortunately, there is no single best way to manage vendor
+      branches in Subversion.  The flexibility of the system offers
+      several different approaches, each of which has its advantages
+      and disadvantages, and—unfortunately—none of which
+      is a sort of <quote>silver bullet</quote> for the
+      problem.  We'll cover a few of these approaches at a high level
+      in the following sections, using the common example of a
+      software project which depends on a third-party library.</para>

      <!-- ===============================================================  
-->
-    <sect2 id="svn.advanced.vendorbr.general">
-      <title>General Vendor Branch Management Procedure</title>
+    <sect2 id="svn.advanced.vendorbr.read-only-mirroring">
+      <title>Read-only Mirroring of Third-party Data</title>

-      <para>Managing vendor branches generally works like this: first,
-        you create a top-level directory (such as
-        <filename>/vendor</filename>) to hold the vendor branches.
-        Then you import the third-party code into a subdirectory of
-        that top-level directory.  You then copy that subdirectory
-        into your main development branch (e.g.,
-        <filename>/trunk</filename>) at the appropriate location.  You
-        always make your local changes in the main development branch.
-        With each new release of the code you are tracking, you bring
-        it into the vendor branch and merge the changes into
-        <filename>/trunk</filename>, resolving whatever conflicts
-        occur between your local changes and the upstream
-        changes.</para>
+      <para>The simplest scenario which involves vendor branches is
+        when that branch is more like a vendor tag—you never
+        modify the third-party library yourself, and are merely
+        mirroring it in your own repository for convenience.
+        Subversion offers various approaches to this.</para>

-      <para>An example will help to clarify this algorithm.  We'll use
-        a scenario where your development team is creating a
-        calculator program that links against a third-party complex
-        number arithmetic library, libcomplex.  We'll begin with the
-        initial creation of the vendor branch and the import of the
-        first vendor drop.  We'll call our vendor branch directory
-        <filename>libcomplex</filename>, and our code drops will go
-        into a subdirectory of our vendor branch called
-        <filename>current</filename>.  And since <command>svn
-        import</command> creates all the intermediate parent
-        directories it needs, we can actually accomplish both of these
-        steps with a single command:</para>
+      <para>For example, if the third-party library is itself already
+        available via Subversion, you can use Subversion's
+        <command>svnsync</command> tool to quite literally mirror the
+        whole third-party repository—or a portion
+        thereof—with a local repository (see
+        <xref linkend="svn.reposadmin.maint.replication" />).  Or, if
+        you wanted to merge the third-party library's history into the
+        same repository you use for your own codebase, you could
+        use <command>svnrdump dump</command> with <command>svnadmin
+        load --parent-dir</command> to replicate the history of the
+        third-party codebase into a subdirectory of your own
+        repository.  (We cover the use of <command>svnrdump</command>
+        in <xref linkend="svn.reposadmin.maint.migrate.svnrdump"
+        />.)</para>

-      <informalexample>
-        <screen>
-$ svn import /path/to/libcomplex-1.0 \
-             http://svn.example.com/repos/vendor/libcomplex/current \
-             -m "importing initial 1.0 vendor drop"
-…
-</screen>
-      </informalexample>
+      <para>Often, you don't need the full history (every single
+        revision) of the vendor's data, but are only interested in
+        stable snapshots thereof.  The <command>svn import</command>
+        subcommand is designed specifically for quickly adding to
+        version control whole trees of locally unversioned
+        information, such as the contents of a vendor's source code
+        release archive file.  Alternatively, if the third-party
+        library is already Subversion-accessible, <command>svn
+        export</command> can create just such a local unversioned
+        directory tree—from, say, a tag in the vendor's
+        repository—that <command>svn import</command> can then
+        stash in your own repository.</para>

-      <para>We now have the current version of the libcomplex source
-        code in <filename>/vendor/libcomplex/current</filename>.  Now,
-        we tag that version (see <xref linkend="svn.branchmerge.tags" />)
-        and then copy it into the main development branch.  Our copy
-        will create a new directory called
-        <filename>libcomplex</filename> in our existing
-        <filename>calc</filename> project directory.  It is in this
-        copied version of the vendor data that we will make our
-        customizations:</para>
+      <para>Whatever approach you use, simply tracking in a read-only
+        manner a collection of information provided by another vendor
+        is relatively easy in Subversion.  It's when you need to
+        maintain customizations to that data that things become more
+        challenging.</para>

-      <informalexample>
-        <screen>
-$ svn copy http://svn.example.com/repos/vendor/libcomplex/current  \
-           http://svn.example.com/repos/vendor/libcomplex/1.0      \
-           -m "tagging libcomplex-1.0"
-…
-$ svn copy http://svn.example.com/repos/vendor/libcomplex/1.0  \
-           http://svn.example.com/repos/calc/libcomplex        \
-           -m "bringing libcomplex-1.0 into the main branch"
-…
-</screen>
-      </informalexample>
+    </sect2>

-      <para>We check out our project's main branch—which now
-        includes a copy of the first vendor drop—and we get to
-        work customizing the libcomplex code.  Before we know it, our
-        modified version of libcomplex is now completely integrated
-        into our calculator program.<footnote><para>And is entirely
-        bug-free, of course!</para></footnote></para>
+    <!-- ===============================================================  
-->
+    <sect2 id="svn.advanced.vendorbr.customizing">
+      <title>Vendor Branches</title>

-      <para>A few weeks later, the developers of libcomplex release a
-        new version of their library—version 1.1—which
-        contains some features and functionality that we really want.
-        We'd like to upgrade to this new version, but without losing
-        the customizations we made to the existing version.  What we
-        essentially would like to do is to replace our current
-        baseline version of libcomplex 1.0 with a copy of libcomplex
-        1.1, and then re-apply the custom modifications we previously
-        made to that library to the new version.  But we actually
-        approach the problem from the other direction, applying the
-        changes made to libcomplex between versions 1.0 and 1.1 to our
-        modified copy of it.</para>
+      <para>Depending on third-party data can be a minor hassle.  But
+        depending on third-party data which needs to be tweaked for
+        your own purposes can be downright complicated.</para>

-      <para>To perform this upgrade, we check out a copy of our vendor
-        branch and replace the code in the
-        <filename>current</filename> directory with the new libcomplex
-        1.1 source code.  We quite literally copy new files on top of
-        existing files, perhaps exploding the libcomplex 1.1 release
-        tarball atop our existing files and directories.  The goal
-        here is to make our <filename>current</filename> directory
-        contain only the libcomplex 1.1 code and to ensure that all
-        that code is under version control.  Oh, and we want to do
-        this with as little version control history disturbance as
-        possible.</para>
+      <para>Maintaining customizations to a third-party library
+        involves three data sources: the version of the third-party
+        library upon which your modifications were last based, the
+        customized version (that is, the actual vendor branch) of that
+        library which is used by your project, and any new version of
+        the vendor's library to which you may be hoping to upgrade.
+        Managing the vendor branch (which should live within your
+        source code repository per our definition of the thing), then,
+        essentially boils down to performing merge operations (in the
+        general sense).  But different teams take different approaches
+        to the other data sources—the pristine versions of the
+        third-party library code.  Thus, there are likewise different
+        specific ways to perform the requisite merges.</para>

-      <para>After replacing the 1.0 code with 1.1 code, <command>svn
-        status</command> will show files with local modifications as
-        well as, perhaps, some unversioned files.  If we did what we
-        were supposed to do, the unversioned files are only those new
-        files introduced in the 1.1 release of libcomplex—we
-        run <command>svn add</command> on those to get them under
-        version control.  If the 1.1 code no longer has certain files
-        that were in the 1.0 tree, it may be hard to notice them;
-        you'd have to compare the two trees with some external tool
-        and then <command>svn delete</command> any files present in
-        1.0 but not in 1.1.  (Although it might also be just fine to
-        let these same files live on in unused obscurity!)  Finally,
-        once our <filename>current</filename> working copy contains
-        only the libcomplex 1.1 code, we commit the changes we made to
-        get it looking that way.</para>
+      <para>Strictly speaking, there are a couple of different ways
+        that those merges can be performed in the general sense.  For
+        the sake of simplicity and with the goal of at least providing
+        <emphasis>something</emphasis> concrete in this section of the
+        book, we'll assume that there is but a single vendor branch
+        which is rebased against each successive new release of the
+        third-party library on which it is based by receiving updates
+        that describe the differences between the current and new
+        pristine versions of that library.</para>

-      <para>Our <filename>current</filename> branch now contains the
-        new vendor drop.  We tag the new version as 1.1 (in the same
-        way we previously tagged the version 1.0 vendor drop), and
-        then merge the differences between the tag of the previous
-        version and the new current version into our main development
-        branch:</para>
+      <note>
+        <para>Another approach is to create new vendor branches for
+          each successive pristine library version, applying the
+          differences between the current pristine library and the
+          customized version thereof (from the current vendor branch)
+          to the new branch.  There's nothing wrong with that
+          approach—we just don't feel compelled to document
+          every legitimate possibility in this
+          space.</para>
+      </note>

-      <informalexample>
-        <screen>
-$ cd working-copies/calc
-$ svn merge ^/vendor/libcomplex/1.0      \
-            ^/vendor/libcomplex/current  \
-            libcomplex
-… # resolve all the conflicts between their changes and our changes
-$ svn commit -m "merging libcomplex-1.1 into the main branch"
-…
-</screen>
-      </informalexample>
+      <para>Let's continue looking into how to manage our vendor
+        branches.  In the examples which follow, we'll assume that the
+        third-party library is called libcomplex, and that we've
+        implemented a vendor branch based on libcomplex 1.0 which
+        lives in our repository
+        at <filename>^/vendor/libcomplex/custom</filename>.  We'll
+        be looking at how we can upgrade to libcomplex 1.1 while still
+        preserving our customizations to the library.</para>

-      <para>In the trivial use case, the new version of our
-        third-party tool would look, from a files-and-directories
-        point of view, just like the previous version.  None of the
-        libcomplex source files would have been deleted, renamed, or
-        moved to different locations—the new version would
-        contain only textual modifications against the previous one.
-        In a perfect world, our modifications would apply cleanly to
-        the new version of the library, with absolutely no
-        complications or conflicts.</para>
+      <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  
-->
+      <sect3 id="svn.advanced.vendorbr.customizing.from-svn">
+        <title>Vendor branches from Subversion sources</title>

-      <para>But things aren't always that simple, and in fact it is
-        quite common for source files to get moved around between
-        releases of software.  This complicates the process of
-        ensuring that our modifications are still valid for the new
-        version of code, and things can quickly degrade into a
-        situation where we have to manually re-create our
-        customizations in the new version.  Once Subversion knows
-        about the history of a given source file—including all
-        its previous locations—the process of merging in the new
-        version of the library is pretty simple.  But we are
-        responsible for telling Subversion how the source file layout
-        changed from vendor drop to vendor drop.</para>
+        <para>When a vendor branch is based on third-party code that
+          is delivered via Subversion, the 2-URL form of
+          the <command>svn merge</command> command is probably the
+          best option for rebasing your vendor branch.  Since
+          Subversion 1.5, <command>svn merge</command> has even been
+          able to perform so-called <quote>foreign repository
+          merges</quote>, where the sources of the merge live in a
+          different repository than the repository from which the
+          merge target working copy was checked
+          out.<footnote><para>There are some notable limitations with
+          foreign repository merges, such as the lack of merge
+          tracking support, but those shouldn't affect the vendor
+          branch maintenance procedure discussed
+          here.</para></footnote> Let's assume that the pristine 1.0
+          and 1.1 versions of libcomplex are already available in our
+          own repository, imported
+          at <filename>^/vendor/libcomplex/1.0</filename>
+          and <filename>^/vendor/libcomplex/1.1</filename>,
+          respectively.</para>

-    </sect2>
+        <para>In order to get the changes made between versions 1.0
+          and 1.1 of libcomplex into our vendor branch, we must first
+          have a working copy of that branch.</para>

-    <!-- TODO: Try to clarify some of the steps for svn_load_dirs.pl
-         (Garrett sez they've been "glossed over".  Also, consider
-         another section on bypassing svn_load_dirs.pl altogether and
-         running with just svn merge, now that it ignores ancestry.
-         Another idea would be presenting this using foreign repos
-         merges. -->
+        <informalexample>
+          <screen>
+$ svn co http://svn.example.com/projects/repos/vendor/libcomplex/custom \
+         libcomplex-custom
+A    libcomplex-custom
+A    libcomplex-custom/README
+A    libcomplex-custom/LICENSE
+…
+A    libcomplex-custom/src/code.h
+A    libcomplex-custom/src/code.c
+A    libcomplex-custom/tests/TODO
+Checked out revision 1156.
+$
+</screen>
+        </informalexample>

-    <!-- ===============================================================  
-->
-    <sect2 id="svn.advanced.vendorbr.svn_load_dirs">
-      <title>svn_load_dirs.pl</title>
+        <para>This working copy already contains our customized
+          version of libcomplex 1.0.  So, upgrading to libcomplex 1.1 is
+          a simple matter of applying the difference between 1.0 and 1.1
+          to our working copy.  That's pretty much exactly
+          what <command>svn merge</command> is made for.</para>

-      <para>Vendor drops that contain more than a few deletes,
-        additions, and moves complicate the process of upgrading to
-        each successive version of the third-party data.  So
-        Subversion supplies the <command>svn_load_dirs.pl</command>
-        script to assist with this process.  This script automates the
-        importing steps we mentioned in the general vendor branch
-        management procedure to make sure mistakes are minimized.
-        You will still be responsible for using the merge commands to
-        merge the new versions of the third-party data into your main
-        development branch, but <command>svn_load_dirs.pl</command>
-        can help you more quickly and easily arrive at that
-        stage.</para>
+        <informalexample>
+          <screen>
+$ cd /path/to/libcomplex-custom
+$ svn merge --ignore-ancestry \
+            ^/vendor/libcomplex/1.0 \
+            ^/vendor/libcomplex/1.1
+--- Merging differences between repository URLs into '.':
+A    src/new-module
+A    src/new-module/init.c
+A    src/new-module/header.h
+…
+U    src/code.h
+C    src/code.c
+U    README
+Summary of conflicts:
+  Text conflicts: 1
+Conflict discovered in file 'src/code.c'.
+Select: (p) postpone, (df) diff-full, (e) edit, (m) merge,
+        (mc) mine-conflict, (tc) theirs-conflict, (s) show all options:
+</screen>
+        </informalexample>

-      <para>In short, <command>svn_load_dirs.pl</command> is an
-        enhancement to <command>svn import</command> that has several
-        important characteristics:</para>
+        <para>The first thing to note is that we used
+          the <option>--ignore-ancestry</option> option with
+          our <command>svn merge</command> command.  This is required
+          because in our repository, the
+          <filename>^/vendor/libcomplex/1.0</filename>
+          and <filename>^/vendor/libcomplex/1.1</filename> branches
+          share no common ancestry—both were fresh imports of
+          the respective libcomplex source release tarballs.  Had we
+          omitted this option, <command>svn merge</command> would have
+          determine that the safest way to transform the 1.0 tree into
+          the 1.1 tree would be to simply delete the former and add
+          the latter, with disastrous results in out working
+          copy.</para>

-      <itemizedlist>
-        <listitem>
-          <para>It can be run at any point in time to bring an existing
-            directory in the repository to exactly match an external
-            directory, performing all the necessary adds and deletes,
-            and optionally performing moves, too.</para>
-        </listitem>
-        <listitem>
-          <para>It takes care of complicated series of operations between
-            which Subversion requires an intermediate commit—such
-            as before renaming a file or directory twice.</para>
-        </listitem>
-        <listitem>
-          <para>It will optionally tag the newly imported directory.</para>
-        </listitem>
-        <listitem>
-          <para>It will optionally add arbitrary properties to files and
-            directories that match a regular expression.</para>
-        </listitem>
-      </itemizedlist>
+        <para>Notice also that <command>svn merge</command> was able
+          to flag a conflict which arose from the merge.  It seems
+          that the libcomplex development team changed one or more
+          regions of <filename>src/code.c</filename> that we had
+          previously customized.  Fortunately, Subversion gives us the
+          change to resolve those conflicts so that our customizations
+          continue to make sense within the context of the upgraded
+          libcomplex codebase.</para>

-      <para><command>svn_load_dirs.pl</command> takes three mandatory
-        arguments.  The first argument is the URL to the base
-        Subversion directory to work in.  This argument is followed by
-        the URL—relative to the first argument—into which the
-        current vendor drop will be imported.  Finally, the third
-        argument is the local directory to import.  Using our previous
-        example, a typical run of <command>svn_load_dirs.pl</command>
-        might look like this:</para>
+        <para>Once all conflicts have been resolved, and we've
+          reviewed and tested the vendor branch code for correctness,
+          we can commit the results of our merge.  Our vendor branch
+          is now up-to-date with libcomplex 1.1!</para>

-      <informalexample>
-        <screen>
-$ svn_load_dirs.pl http://svn.example.com/repos/vendor/libcomplex \
-                   current                                        \
-                   /path/to/libcomplex-1.1
-…
-</screen>
-      </informalexample>
+        <para>We mentioned previously that Subversion can also perform
+          such merges when the pristine vendor data lives in a
+          different repository.  We use the same syntax for such a
+          merge, except that we refer to the foreign repository URLs
+          and <emphasis>shouldn't</emphasis> need to use
+          the <option>--ignore-ancestry</option> option (because the
+          tags in the vendor's repository
+          probably <emphasis>do</emphasis> share a common
+          ancestry):</para>

-      <para>You can indicate that you'd like
-        <command>svn_load_dirs.pl</command> to tag the new vendor drop
-        by passing the <option>-t</option> command-line option and
-        specifying a tag name.  This tag is another URL relative to
-        the first program argument.</para>
-
-      <informalexample>
-        <screen>
-$ svn_load_dirs.pl -t libcomplex-1.1                              \
-                   http://svn.example.com/repos/vendor/libcomplex \
-                   current                                        \
-                   /path/to/libcomplex-1.1
+        <informalexample>
+          <screen>
+$ cd /path/to/libcomplex-custom
+$ svn merge http://svn.othervendor.com/repos/libcomplex/tags/1.0 \
+            http://svn.othervendor.com/repos/libcomplex/tags/1.1
+--- Merging differences between foreign repository URLs into '.':
+A    src/new-module
+A    src/new-module/init.c
+A    src/new-module/header.h
  …
+U    src/code.h
+C    src/code.c
+U    README
+Summary of conflicts:
+  Text conflicts: 1
+Conflict discovered in file 'src/code.c'.
+Select: (p) postpone, (df) diff-full, (e) edit, (m) merge,
+        (mc) mine-conflict, (tc) theirs-conflict, (s) show all options:
  </screen>
-      </informalexample>
+        </informalexample>

-      <para>When you run <command>svn_load_dirs.pl</command>, it
-        examines the contents of your existing <quote>current</quote>
-        vendor drop and compares them with the proposed new vendor
-        drop.  In the trivial case, no files will be in
-        one version and not the other, and the script will perform the
-        new import without incident.  If, however, there are
-        discrepancies in the file layouts between versions,
-        <command>svn_load_dirs.pl</command> will ask you how
-        to resolve those differences.  For example, you
-        will have the opportunity to tell the script that you know
-        that the file <filename>math.c</filename> in version 1.0 of
-        libcomplex was renamed to <filename>arithmetic.c</filename> in
-        libcomplex 1.1.  Any discrepancies not explained by moves
-        are treated as regular additions and deletions.</para>
+        <para>As you can see, Subversion provides the same essential
+          functionality.</para>

-      <para>The script also accepts a separate configuration file for
-        setting properties on <emphasis>added</emphasis> files and
-        directories which match a regular expression.  This configuration
-        file is specified to <command>svn_load_dirs.pl</command> using the
-        <option>-p</option> command-line option.  Each line of the
-        configuration file is a whitespace-delimited set of two or
-        four values: a Perl-style regular expression against which to  
match the
-        added path, a control keyword (either
-        <literal>break</literal> or <literal>cont</literal>), and then
-        optionally a property name and value.</para>
+        <para>Admittedly, this is a pretty simple use-case.
+          <command>svn merge</command> is pretty good about handling
+          content modifications and simple tree changes (additions and
+          deletions).  Unfortunately, things quickly devolve into
+          madness when files or directories have been renamed.  This
+          is still an area in which Subversion needs to improve.</para>

-      <informalexample>
-        <screen>
-\.png$              break   svn:mime-type   image/png
-\.jpe?g$            break   svn:mime-type   image/jpeg
-\.m3u$              cont    svn:mime-type   audio/x-mpegurl
-\.m3u$              break   svn:eol-style   LF
-.*                  break   svn:eol-style   native
-</screen>
-      </informalexample>
+      </sect3>

-      <para>For each added path, the configured property changes whose
-        regular expression matches the path are applied in order,
-        unless the control specification is <literal>break</literal>
-        (which means that no more property changes should be applied
-        to that path).  If the control specification is
-        <literal>cont</literal>—an abbreviation for
-        <literal>continue</literal>—matching will continue
-        with the next line of the configuration file.</para>
+      <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  
-->
+      <sect3 id="svn.advanced.vendorbr.customizing.from-non-svn">
+        <title>Vendor branches from non-Subversion sources</title>

-      <para>Any whitespace in the regular expression, property name,
-        or property value must be surrounded by either single or
-        double quotes.  You can escape quotes that
-        are not used for wrapping whitespace by preceding them with a
-        backslash (<literal>\</literal>) character.  The backslash
-        escapes only quotes when parsing the configuration file, so do
-        not protect any other characters beyond what is necessary for
-        the regular expression.</para>
+        <para>### TODO ###</para>

+      </sect3>
      </sect2>
+
    </sect1>

    <!-- =================================================================  
-->




More information about the svnbook-dev mailing list